######################################################################
#             / ____|  ____|  __ \| |  | |  ____|  ____|             #
#            | |    | |__  | |__) | |__| | |__  | |__                #
#            | |    |  __| |  ___/|  __  |  __| |  __|               #
#            | |____| |____| |    | |  | | |____| |____              #
#             \_____|______|_|    |_|  |_|______|______|             #
######################################################################
#
# CEPHEE
# Copyright (C) 2024 Toulouse INP
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details :
# <http://www.gnu.org/licenses/>.
#
######################################################################

import os
from shapely.geometry import Point
from yaml import safe_load,YAMLError


class Parameters(dict):
    """ TODO

    """
    def __init__(self):
        super().__init__()
        self.work_path = None
        self.verbose = True
        self.reset_I()
        self.reset_C()
        self.reset_N()
        self.reset_XS()
        self.reset_H()
        self.reset_B()
        self.reset_P()
        if self['I']['outlet_point']:
            if type(self['I']['outlet_point']) == str:
                coords = parse_tuple_string(self['I']['outlet_point'])
            else:
                coords = self['I']['outlet_point']
            self['I']['outlet_point'] = Point(coords)

    def reset_I(self):
        self['I'] = {
            'DEM_path': None, # path to the one DEM file or folder containing multiple DEM files
            'DEM_file_extension' : None, # extensions of the DEM files (if DEM_path is a folder)
            'boundary_filepath': None, # path to the catchment boundaries shapefile (polygon)
            'network_filepath': None, # path to the network shapefile
            'XS_filepath': None, # path to the cross-sections shapefile
            'riverbanks_filepath': None, # path to the riverbanks shapefile (polygon)
            'outlet_point' : None # outlet Point (X,Y)
        }
    def reset_C(self):
        self['C'] = {
            'DEM_CRS_ID': 'EPSG:2154',  # identifiant du système de projection
            'resolution': 20, # résolution du raster agrégeant toutes les dalles d'un même BV
            'window_size': 2,
            'computeGlobal': True,
            'hydro_lib': None,
            'mask_water_value' : 0
        }

    def reset_N(self):
        self['N'] = {
            'classeMax': 6, #ordre maximum du réseau hydrographique
            'minAccumulativeArea': 500, #surface minimale de l'aire drainée pour débuter le réseau hydrographique automatique
            'minDistJunction': 500,
            'npoly' : 1
        }

    def reset_XS(self):
        self['XS'] = {
            'creation_step': 100, # distance between the created XS (m)
            'width': 100, # half-width of the created XS (m)
            'number_of_points': 100, # number of point for each created XS
            'width_from_banks': False,
            'method_banks' : 'channel',
            'interpolate_XS' : False,
            'interpolation_step': 100,  # interpolation distance (m)
            'interpolation_method': 'CEPHEE', # interpolation method
            'optimize_XS': False,
            'optimization_method': 'angle',
            'distSearchMin' : 0 #distance de recherche du fond du lit autour du tracé shp
        }

    def reset_H(self):
        self['H'] = {
            'outletDischarge': 100,
            'dischargeMethod': 'Uniform',
            'dxlat': 5,
            'levee': True,
            'hWaterOutlet' : 0,
            'himposed' : 1,
            'hydraulicCurve': False,
            'dz': 2,
            'hmax': 50,
            'createBanks': False,
            'createBanksMethods': 'Himposed',
            'frictionLaw' : 'Manning',
            'frictionValue': 0.03,
            'frictionMap' : False,
            'friction_filename' : None,
            'mapping_method' : 'interpolation',
            'slope_structure' : 0.05,
            'hinf': 0.1,
            'hsup': 30,
            'eps': 1e-2,
            'MaxIter': 30,
            'output_resolution': 50,
            'write_discharge': False,
            'step_section' : 1
        }

    def reset_B(self):
        self['B'] = {
            'fromQ': False,
            'bathymetricSections': 'Parabolic',
            'depth': 5,
            'mapping_method': 'interpolation',
            'output_resolution': 5,
            'smoothSlope': False,
            'average_slope': None
        }

    def reset_P(self):
        self['P'] = {
            'findDikes': False,
            'overtopping': 1,
            'averageSize': 10,
            'densityZone': False,
            'minMeshSize': 1,
            'maxMeshSize': 10,
            'findFlatZone': False,
            'eps': 1
        }


def read_param(filename =None):
    """Read the parameter file

        :param filename: parameter file name
        :type filename: string
    """

    if isinstance(filename,str):
        if filename.endswith(('.py')):
            # Créer un dictionnaire pour stocker les variables et classes définies dans le fichier
            name = {}
            # Ouvrir et lire le fichier Python
            with open(filename, "r") as file:
                code = file.read()
            # Exécuter le contenu du fichier dans le namespace
            exec(code, name)
            Param = name.get('Parameters')
            params =Param()


        elif filename.endswith('.yaml'):
            cfg = read_config(filename)
            params = Parameters()
            #read input
            if 'verbose' in cfg:
                params.verbose = cfg['verbose']

            if 'workdir' in cfg:
                params.work_path = cfg['workdir']
            else:
                params.work_path =os.getcwd()
            if 'work_path' in cfg:
                params.work_path = cfg['work_path']
            else:
                params.work_path =os.getcwd()

            name_dict =['I','C','N','XS','H','B']

            for dic in name_dict:
                if dic in cfg and dic in params:
                    for category in cfg[dic]:
                        if type(cfg[dic][category]) == str or type(cfg[dic][category]) == float or type(cfg[dic][category]) == int or type(cfg[dic][category]) == bool or type(cfg[dic][category]) == list:
                            params[dic][category] = cfg[dic][category]

            if 'I' in cfg:
                if 'outlet_point' in cfg['I']:
                    coords = parse_tuple_string(cfg['I']['outlet_point'])
                    params['I']['outlet_point'] = Point(coords)

        else:
            print(filename)

    elif isinstance(filename,dict):

        #try:
        cfg=filename
        params = Parameters()
        # read input
        if 'verbose' in cfg:
            params.verbose = cfg['verbose']
        if 'workdir' in cfg:
            params.work_path = cfg['workdir']
        else:
            params.work_path = os.getcwd()
        if 'work_path' in cfg:
            params.work_path = cfg['work_path']
        else:
            params.work_path = os.getcwd()
        name_dict = ['I', 'C', 'N', 'XS', 'H', 'B']
        for dic in name_dict:
            if dic in cfg and dic in params:
                for category in cfg[dic]:
                    if type(cfg[dic][category]) == str or type(cfg[dic][category]) == float or type(
                            cfg[dic][category]) == int or type(cfg[dic][category]) == bool or type(
                            cfg[dic][category]) == list:
                        params[dic][category] = cfg[dic][category]
        if 'I' in cfg:
            if 'outlet_point' in cfg['I']:
                coords = parse_tuple_string(cfg['I']['outlet_point'])
                params['I']['outlet_point'] = Point(coords)

    if not filename:
        params = Parameters()





    return params


def parse_tuple_string(s):
    # Supprimer les caractères indésirables
    s = s.strip().replace("[", "").replace("]", "").replace("(", "").replace(")", "")

    # Séparer par virgule
    parts = s.split(",")

    # Nettoyer chaque élément et convertir en int ou float
    result = []
    for part in parts:
        part = part.strip()
        if "." in part:
            result.append(float(part))
        else:
            result.append(int(part))

    return tuple(result)


def read_config(path):
    # input file format check
    if not (isinstance(path, str) and (path.endswith('.yaml') or path.endswith('.yml'))) :
        print('read_config path argument should be a path to the YAML config file (here: {})'.format(path))
        raise ValueError()
    # input file existence check
    if not os.path.isfile(path):
         print('The input config file \'{}\' doesn\'t exist'.format(path))
         raise FileNotFoundError()

    with open(path, 'r') as stream:
        try:
            cfg = safe_load(stream)
        except YAMLError as e:
            print('Exception occured while reading the configuration file: {}\nException: {}'.format(path, str(e)))
            raise YAMLError(str(e))
    return cfg
