# -*- coding: utf-8 -*-

import numpy as np

from time import time
from typing import List
from copy import deepcopy
from operator import itemgetter

from Model.Geometry.Profile import Profile
from Model.Geometry.ProfileXYZ import ProfileXYZ

from Model.Except import FileFormatError, exception_message_box

class Reach:
    def __init__(self, edge):
        self._edge = edge
        self._list_profiles: List[Profile] = []

    def __repr__(self):
        return f"\n===== Début liste des profils ======> {np.array(self._list_profiles)}" \
               f"\n<====== Fin liste des profils ====="

    @property
    def number_profiles(self):
        """
        Returns:
            Number of profiles
        """
        return len(self._list_profiles)

    def get_geometry(self) -> List[Profile]:
        """
        Returns:
            The profiles list.
        """
        return self._list_profiles

    def get_profile_i(self, i: int) -> Profile:
        """
        Args:
            i: Index

        Returns:
            The profile at index i.
        """
        try:
            return self._list_profiles[i]
        except IndexError:
            raise IndexError(f"Invalid profile index: {i}")

    def add_XYZ(self):
        """Add a new profile at the end of profiles list

        Returns:
            Nothing.
        """
        nb_profile = self.number_profiles
        profile = ProfileXYZ()
        profile.num = nb_profile + 1
        self._list_profiles.append(profile)

    def _update_profile_numbers(self):
        """Update profiles index

        Returns:
            Nothing.
        """
        for ind, profile in enumerate(self.get_geometry()):
            profile.num = ind + 1

    def insert(self, index: int):
        """Insert new profile in list

        Args:
            index: The position of the new profile.

        Returns:
            Nothing.
        """
        profile = ProfileXYZ()
        self._list_profiles.insert(index, profile)
        self._update_profile_numbers()

    def delete(self, list_index: list):
        """Delete some elements in profile list

        Args:
            list_index: The list of element index

        Returns:
            Nothing.
        """
        try:
            if list_index:
                indices = sorted(list(set(list_index)), reverse=True)
                for idx in indices:
                    # if idx < len(self._list_profiles) :
                    try:
                        self._list_profiles.pop(idx)
                        self._update_profile_numbers()
                    except IndexError:
                        print(f"Invalid profile index: {idx}")
        except TypeError:
            if isinstance(list_index, int):
                self._list_profiles.pop(list_index)
                self._update_profile_numbers()
            else:
                raise TypeError(f"{list_index} is instance of unexpected type '{type(list_index)}'")

    def _sort(self, is_reversed: bool = False):
        self._list_profiles = sorted(
            self._list_profiles,
            key=lambda profile: profile.kp(),
            reverse=is_reversed
        )

    def sort_ascending(self):
        """Sort profiles by increasing KP

        Returns:
            Nothing.
        """
        self._sort(is_reversed=False)

    def sort_descending(self):
        """Sort profiles by decreasing KP

        Returns:
            Nothing.
        """
        self._sort(is_reversed=True)

    def copy(self, index_list: List[int]):
        self.__list_copied_profiles.clear()
        index_list = list(set(index_list))  # delete duplicate index

        for index in index_list:
            try:
                self.__list_copied_profiles.append(deepcopy(self.get_profile_i(index)))
            except IndexError:
                raise IndexError(f"Invalid profile index: {index}")

    def paste(self):
        if self.__list_copied_profiles:
            for profile in self.__list_copied_profiles:
                self._list_profiles.append(profile)

    def import_geometry(self, file_path_name: str):
        """Import a geometry from file (.ST or .st)

        Args:
            file_path_name: The absolute path of geometry file (.ST or .st) to import.

        Returns:
            Nothing.
        """
        try:
            list_profile, list_header = self.read_file_st(str(file_path_name))

            if list_profile and list_header:
                for ind, profile in enumerate(list_profile):
                    prof = ProfileXYZ(*list_header[ind])
                    prof.import_points(profile)
                    self._list_profiles.append(prof)
                    self._update_profile_numbers()
        except FileNotFoundError as e:
            print(e)
            exception_message_box(e)
        except FileFormatError as e:
            print(e)
            e.alert()

    def read_file_st(self):
        """Read the ST file

        Returns:
            List of profiles and list of headers.
        """
        t0 = time()
        line_is_header = True
        list_point_profile = []
        list_profile = []
        list_header = []
        stop_code = "999.999"

        with open(self.file_st, encoding="utf-8") as file_st:
            for line in file_st:
                if not (line.startswith("#") or line.startswith("*")):
                    line = line.split()

                    if line_is_header:
                        if len(line) >= 6:
                            list_header.append(line[:6])
                        elif len(line) == 5:
                            line.append("")
                            list_header.append(line)
                        else:
                            print(f"Point {line} invalide ==> pas pris en compte")
                            line_is_header = False
                    else:
                        # Read until "999.9990     999.9990" as found
                        if len(line) == 3:
                            x, y, z = line
                            if stop_code in x and stop_code in y:
                                line_is_header = True
                                list_profile.append(list_point_profile)
                                list_point_profile = []
                            else:
                                line.append("")
                                list_point_profile.append(line)
                        elif len(line) == 4:
                            x, y, z, ld = line
                            if stop_code in x and stop_code in y:
                                list_profile.append(list_point_profile)
                                list_point_profile = []
                                line_is_header = True
                            else:
                                list_point_profile.append(line)
                        else:
                            pass

        if list_profile and list_header:
            raise FileFormatError(self.file_st, f"{list_profile}, {list_header}")

        print("******  Fichier {} lu et traité en {} secondes *******".format(self.file_st, time() - t0))
        return list_profile, list_header