# -*- coding: utf-8 -*-
"""
Python wrapper to the Fortran APIs of Telemac-Mascaret

Author(s): Fabrice Zaoui, Yoann Audouin, Cedric Goeury, Renaud Barate

Copyright EDF 2017
"""
import logging
import ctypes
import os
import sys

import numpy as np

# Artelia
from io import StringIO
from datetime import datetime

# end
# from execution.mascaret_cas import MascaretCas
# from utils.exceptions import TelemacException


# def build_api_input(steering_file):
#     """
#     Build the file_name and files_type from steering file
#     """
#
#     cas = MascaretCas(steering_file)
#
#     files_name = []
#     files_type = []
#
#     for ffile, ftype in cas.in_files.items():
#         if ftype not in ['', 'loi']:
#             files_name.append(ffile)
#             files_type.append(ftype)
#     # handling loi files as one file can be used multiple times
#     lois = cas.get('parametresLoisHydrauliques/lois/structureParametresLoi/donnees/fichier')
#     for loi in lois:
#         files_name.append(loi)
#         files_type.append('loi')
#
#     for ffile, ftype in cas.out_files.items():
#         if ftype != '':
#            files_name.append(ffile)
#             files_type.append(ftype)
#
#     return files_name, files_type
#


class Mascaret:
    """The Python class for MASCARET APIs"""

    libmascaret = None
    # Créer un StringIO pour stocker les messages de journalisation

    logger = logging.getLogger(__name__)

    _error = 0
    nb_nodes = None

    @property
    def error(self):
        """Error property"""
        return self._error

    @error.setter
    def error(self, value):
        """Detect errors

        Overwright attribute setter to detect API errors.
        If :attr:`error` is not set null, an error is raised and the programme
        is terminated.

        :param int value: value to assign
        """
        if value != 0:
            self.logger.error("API error:\n{}".format(self.error_message()))
            # raise SystemExit(1)
            self._error = 1
        else:
            self._error = 0

    def error_message(self):
        """Error message wrapper

        @return (str) Error message
        """
        err_mess_c = (ctypes.c_char_p * 1)((" " * 256).encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        error = self.libmascaret.C_GET_ERREUR_MASCARET(id_masc_c, ctypes.byref(err_mess_c))
        if error != 0:
            return "Error could not be retrieved from MASCARET..."
        return err_mess_c[0]

    # def load_mascaret(self, libmascaret):
    #     """Load Mascaret library
    #
    #     :param str libmascaret: path to the library
    #     """
    #     ld_library = os.getenv('LD_LIBRARY_PATH')
    #     self.logger.debug('LD_LIBRARY_PATH: {}'.format(ld_library))
    #     self.logger.info('Loading {}...'.format(libmascaret))
    #     if sys.platform.startswith('linux') \
    #        or sys.platform.startswith('darwin') \
    #        or sys.platform.startswith('cygwin'):
    #         try:
    #             self.libmascaret = ctypes.CDLL(libmascaret)
    #         except Exception as tbe:
    #
    #             self.logger.exception("Unable to load: {}. Check the "
    #                                   "environment variable LIBMASCARET: {}"
    #                                   .format(libmascaret, tbe))
    #             raise SystemExit
    #         else:
    #             self.logger.info('Library loaded.')
    #     else:
    #         self.logger.error('Unsupported OS. Only macOS or Unix!')
    #         raise SystemExit

    def __init__(self, log_level="INFO"):
        """
        Constructor for apiModule

        @param log_level (str) Logger level
        """
        self.log_stream = StringIO()
        # Artelia
        if log_level == "INFO":
            i_log = logging.INFO
        elif log_level == "DEBUG":
            i_log = logging.DEBUG
        else:
            i_log = logging.CRITICAL

        self.logger.setLevel(getattr(logging, log_level.upper(), i_log))
        # Ajouter un gestionnaire pour écrire les logs dans log_stream
        handler = logging.StreamHandler(self.log_stream)
        handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
        self.logger.addHandler(handler)
        # end
        self.logger.info("Using MascaretApi")
        # Load the library libmascaret.(so|dll)
        # MDU modif
        # libmascaret = './libmascaret.so'
        test = sys.platform
        if "linux" in test or test == "cygwin":
            # libmascaret = '../api/libmascaret.so'
            libmascaret = os.path.join(os.path.dirname(__file__), "libmascaret.so")
        elif test == "win32":
            # libmascaret = '../api/libmascaret.dll'
            libmascaret = os.path.join(os.path.dirname(__file__), "libmascaret.dll")
            # libmascaret = '../api/libmascaret_old.dll'
        else:
            print("{0} platform  doesn't allow to run simulation.".format(test))

        # self.load_mascaret(libmascaret)
        self.libmascaret = ctypes.CDLL(libmascaret)
        # MDU fin modif
        self.iprint = 0
        self.id_masc = None

    def create_mascaret(self, iprint):
        """Create an instance of Mascaret

        Uses Mascaret Api :meth:`C_CREATE_MASCARET`

        @param iprint (int) flag value for the Mascaret listing files
        """
        id_masc = ctypes.c_int()
        self.logger.debug("Creating a model...")
        self.error = self.libmascaret.C_CREATE_MASCARET(ctypes.byref(id_masc))
        self.logger.debug("Model created.")
        self.id_masc = ctypes.c_int(id_masc.value)
        # .opt and .lis written only if iprint = 1 at import AND calcul steps
        self.iprint = iprint

    def import_model(self, files_name, files_type):
        """Read model from Mascaret files

        Uses Mascaret Api :meth:`C_IMPORT_MODELE_MASCARET`

        @param files_name (str) array of the Mascaret data files
        @param files_type (str) array of the file name extensions
        """
        len_file = len(files_name)
        file_type = []
        file_name = []
        for name, typ in zip(files_name, files_type):
            file_type.append(typ.encode("utf8"))
            file_name.append(name.encode("utf8"))
        file_name_c = (ctypes.c_char_p * len_file)(*file_name)
        file_type_c = (ctypes.c_char_p * len_file)(*file_type)
        self.logger.debug("Importing a model...")
        self.error = self.libmascaret.C_IMPORT_MODELE_MASCARET(
            self.id_masc, file_name_c, file_type_c, len_file, self.iprint
        )
        self.logger.info(
            "Model imported with:\n"
            + "-> file_name: {}\n-> file_type: {}.".format(file_name, file_type)
        )

    def import_model_onefile(self, masc_file):
        """Read model from Mascaret files

        Uses Mascaret Api :meth:`C_IMPORT_MODELE_MASCARET_ONEFILE`

        @param masc_file (str) file name listing all the Mascaret files
        """
        masc_file_c = (ctypes.c_char_p * 1)(masc_file.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        iprint_c = (ctypes.c_int * 1)(self.iprint)
        self.logger.debug("Importing a model...")
        self.error = self.libmascaret.C_IMPORT_MODELE_MASCARET_ONEFILE(
            id_masc_c, iprint_c, masc_file_c
        )
        self.logger.info("Model imported with:\n" + "-> masc_file: {}\n".format(masc_file))

    def delete_mascaret(self):
        """Delete a model."""
        self.logger.debug("Deleting instance #{}...".format(self.id_masc.value))
        self.error = self.libmascaret.C_DELETE_MASCARET(self.id_masc)
        self.logger.debug("Model #{} deleted.".format(self.id_masc.value))

    def save_state(self):
        """Save a state

        Mascaret Api :meth:`C_SAVE_ETAT_MASCARET`

        @return (int) id number for the saved state
        """
        saved_state_c = ctypes.c_int()
        self.logger.debug("Save MASCARET state...")
        self.error = self.libmascaret.C_SAVE_ETAT_MASCARET(
            self.id_masc, ctypes.byref(saved_state_c)
        )
        self.logger.debug("Save MASCARET state done.")

        return saved_state_c.value

    def free_saved_state(self, saved_state):
        """Free all the saved states

        Mascaret Api :meth:`C_FREE_SAVE_ETAT_MASCARET`

        @param saved_state (int) id number of the state to delete
        """
        saved_state_c = ctypes.c_int(saved_state)
        self.logger.debug("Free saved state mascaret...")
        self.error = self.libmascaret.C_FREE_SAVE_ETAT_MASCARET(saved_state_c)
        self.logger.debug("Free save etat mascaret done.")

    def get_hydro(self):
        """Get the water levels (m) and discharge values (m3/s)

        @return (list) water levels and discharges for all 1D nodes
        """
        if self.nb_nodes is None:
            self.nb_nodes, _, _ = self.get_var_size("Model.X")
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        # (Q, Z)
        q_c = (ctypes.c_double * self.nb_nodes)(0.0)
        z_c = (ctypes.c_double * self.nb_nodes)(0.0)
        self.logger.debug("Get hydro (Q,Z) at the current time...")
        self.error = self.libmascaret.C_GET_LIGNE(id_masc_c, ctypes.byref(q_c), ctypes.byref(z_c))
        self.logger.debug("Getter successfull at the current time.")
        return np.array(z_c), np.array(q_c)

    def set_state(self, id_state, to_delete=1, id_masc=None):
        """Set state of a Mascaret model

        Mascaret Api :meth:`C_SET_ETAT_MASCARET`

        @param id_state (int) id number of the state to restore
        @param to_delete (int) delete saved state after restore
        @param id_masc (int) id number of the instance on which to restore
        """
        if id_masc is None:
            id_masc = self.id_masc
        id_state_c = ctypes.c_int(id_state)
        to_delete_c = ctypes.c_int(to_delete)
        self.logger.debug("Set MASCARET state...")
        self.error = self.libmascaret.C_SET_ETAT_MASCARET(id_masc, id_state_c, to_delete_c)
        self.logger.debug("Set MASCARET state done.")

    def free_all_saved_states(self):
        """
        Mascaret Api :meth:`C_FREE_ALL_SAVE_ETAT_MASCARET`
        """
        self.logger.debug("Free all saved states mascaret...")
        self.error = self.libmascaret.C_FREE_ALL_SAVE_ETAT_MASCARET(self.id_masc)
        self.logger.debug("Free all saved states mascaret done.")

    def init_hydro(self, z_val, q_val):
        """Initialize the model from hydraulic values

        Mascaret Api :meth:`C_INIT_LIGNE_MASCARET`

        @param z_val (list or array) water levels (m)
        @param q_val (list or array) discharges (m3/s)
        """
        if self.nb_nodes is None:
            self.nb_nodes, _, _ = self.get_var_size("Model.X")
        # Initialize Mascaret Model from values
        q_c = (ctypes.c_double * self.nb_nodes)(*q_val)
        z_c = (ctypes.c_double * self.nb_nodes)(*z_val)
        self.logger.debug("Initializing MASCARET from constant value...")
        self.error = self.libmascaret.C_INIT_LIGNE_MASCARET(
            self.id_masc, ctypes.byref(q_c), ctypes.byref(z_c), self.nb_nodes
        )
        self.logger.debug("State constant initialisation successfull from constant value.")

    def init_hydro_from_file(self, hydro_file):
        """Initialize the model from a .lig file

        Mascaret Api :meth:`C_INIT_ETAT_MASCARET`

        @param hydro_file (str) '.lig' Mascaret file
        """
        init_file_name_c = (ctypes.c_char_p * 1)(hydro_file.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        iprint_c = (ctypes.c_int * 1)(self.iprint)
        self.logger.debug("Initializing MASCARET from .lig ...")
        self.error = self.libmascaret.C_INIT_ETAT_MASCARET(id_masc_c, init_file_name_c, iprint_c)
        self.logger.debug("State initialisation successfull from .lig")

    def init_tracer_state(self):
        """Initialize the tracer state from a conc file or from rest

        Mascaret Api :meth:`C_INIT_ETAT_TRACER`
        """
        self.logger.debug("Initializing Tracer State ...")
        self.error = self.libmascaret.C_INIT_ETAT_TRACER(self.id_masc, self.iprint)
        self.logger.debug("Tracer state initialisation successful")

    def init_tracer(self, c_val):
        """Initialize the tracer state from values

        Mascaret Api :meth:`C_INIT_LIGNE_TRACER`
        """
        nb_nodes, _, _ = self.get_var_size("Model.X")
        nb_trac = self.get("Model.Tracer.Number")
        nb_conc = nb_nodes * nb_trac
        c_np = np.reshape(np.array(c_val, dtype=np.float64), nb_conc, order="F")
        c_c = (ctypes.c_double * nb_conc)(*c_np)
        self.logger.debug("Initializing Tracer State from Vaues...")
        self.error = self.libmascaret.C_INIT_LIGNE_TRACER(
            self.id_masc, ctypes.byref(c_c), nb_nodes, nb_trac, self.iprint
        )
        self.logger.debug("Tracer state initialisation successful")

    def get_tracer(self):
        """Get the tracer concentrations

        @return (list) tracer concentrations
        """
        nb_nodes, nb_trac, _ = self.get_var_size("State.Tracer.Concentration")
        nb_conc = nb_nodes * nb_trac
        # (Ctraceur)
        c_c = (ctypes.c_double * nb_conc)(0.0)
        self.logger.debug("Get tracer Concentration at the current time...")
        self.error = self.libmascaret.C_GET_LIGNE_TRACER(self.id_masc, ctypes.byref(c_c))
        self.logger.debug("Getter successfull at the current time.")
        return np.reshape(np.array(c_c, dtype=np.float64), (nb_nodes, nb_trac), order="F")

    def get_double(self, var_name, i=0, j=0, k=0):
        """Get the real value of a Mascaret variable

        Mascaret Api :meth:`C_GET_DOUBLE_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable

        @return (float) scalar value
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        val_c = ctypes.c_double()
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        self.logger.debug("Getting {}...".format(var_name))
        self.error = self.libmascaret.C_GET_DOUBLE_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(i_c),
            ctypes.byref(j_c),
            ctypes.byref(k_c),
            ctypes.byref(val_c),
        )
        self.logger.debug("Value: val={}.".format(val_c.value))

        return val_c.value

    def get_int(self, var_name, i=0, j=0, k=0):
        """Get the integer value of a Mascaret variable

        Mascaret Api :meth:`C_GET_INT_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable

        @return (int) scalar value
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        val_c = ctypes.c_int()
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        self.logger.debug("Getting {}...".format(var_name))
        self.error = self.libmascaret.C_GET_INT_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(i_c),
            ctypes.byref(j_c),
            ctypes.byref(k_c),
            ctypes.byref(val_c),
        )
        self.logger.debug("Value: val={}.".format(val_c.value))

        return val_c.value

    def get_bool(self, var_name, i=0, j=0, k=0):
        """Get the boolean value of a Mascaret variable

        Mascaret Api :meth:`C_GET_BOOL_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable
        @return (bool) scalar value
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        val_c = ctypes.c_int()
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        self.logger.debug("Getting {}...".format(var_name))
        self.error = self.libmascaret.C_GET_BOOL_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(i_c),
            ctypes.byref(j_c),
            ctypes.byref(k_c),
            ctypes.byref(val_c),
        )
        self.logger.debug("Value: val={}.".format(val_c.value))
        return bool(val_c.value)
        # MDU return val_c.value == val_c.value

    def get_string(self, var_name, i=0, j=0, k=0):
        """Get the string value of a Mascaret variable

        Mascaret Api :meth:`C_GET_STRING_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable
        @return (str) scalar value
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        val_c = (ctypes.c_char_p * 1)((" " * 256).encode("utf8"))
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        self.logger.debug("Getting {}...".format(var_name))
        self.error = self.libmascaret.C_GET_STRING_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(i_c),
            ctypes.byref(j_c),
            ctypes.byref(k_c),
            ctypes.byref(val_c),
        )
        self.logger.debug("Value: val={}.".format(val_c[0]))

        return str(val_c[0], "utf-8")

    def set_int(self, var_name, val, i=0, j=0, k=0):
        """Set the integer value of a Mascaret variable

        Mascaret Api :meth:`C_SET_INT_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param val (int) scalar value to set
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        val_c = ctypes.c_int(val)
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        self.logger.debug("Setting {}...".format(var_name))
        self.error = self.libmascaret.C_SET_INT_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(i_c),
            ctypes.byref(j_c),
            ctypes.byref(k_c),
            ctypes.byref(val_c),
        )
        self.logger.debug("Value: val={}.".format(val_c.value))

    def set_bool(self, var_name, val, i=0, j=0, k=0):
        """Set the boolean value of a Mascaret variable

        Mascaret Api :meth:`C_SET_BOOL_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param val (bool) scalar value to set
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        val_c = ctypes.c_int(val)
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        self.logger.debug("Setting {}...".format(var_name))
        self.error = self.libmascaret.C_SET_BOOL_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(i_c),
            ctypes.byref(j_c),
            ctypes.byref(k_c),
            ctypes.byref(val_c),
        )
        self.logger.debug("Value: val={}.".format(val_c.value))

    def set_string(self, var_name, val, i=0, j=0, k=0):
        """Set the string value of a Mascaret variable

        Mascaret Api :meth:`C_SET_STRING_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param val (str) scalar value to set
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        val_c = (ctypes.c_char_p * 1)(val.encode("utf-8"))
        self.logger.debug("Setting {}...".format(var_name))
        self.error = self.libmascaret.C_SET_STRING_MASCARET(
            id_masc_c, var_name_c, ctypes.byref(i_c), ctypes.byref(j_c), ctypes.byref(k_c), val_c
        )
        self.logger.debug("Value: val={}.".format(ctypes.string_at(val_c)))

    def set_double(self, var_name, val, i=0, j=0, k=0):
        """Set the real value of a Mascaret variable

        Mascaret Api :meth:`C_SET_DOUBLE_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param val (float) scalar value to set
        @param i (int) first index of the Mascaret variable
        @param j (int) second index of the Mascaret variable
        @param k (int) third index of the Mascaret variable
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        val_c = ctypes.c_double(val)
        i_c = ctypes.c_int(i)
        j_c = ctypes.c_int(j)
        k_c = ctypes.c_int(k)
        self.logger.debug("Setting {}...".format(var_name))
        self.error = self.libmascaret.C_SET_DOUBLE_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(i_c),
            ctypes.byref(j_c),
            ctypes.byref(k_c),
            ctypes.byref(val_c),
        )
        self.logger.debug("Value: val={}.".format(val_c.value))

    def get_type_var(self, var_name):
        """Get the type of a Mascaret variable

        Use Mascaret Api :meth:`C_GET_TYPE_VAR_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @return (str, str, int, int) type, category, modifiable, dimension
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        var_type_c = (ctypes.c_char_p * 1)((" " * 10).encode("utf8"))
        category_c = (ctypes.c_char_p * 1)((" " * 10).encode("utf8"))
        acces_c = ctypes.c_int()
        var_dim_c = ctypes.c_int()

        self.logger.debug("Getting the type of {}...".format(var_name))

        self.error = self.libmascaret.C_GET_TYPE_VAR_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(var_type_c),
            ctypes.byref(category_c),
            ctypes.byref(acces_c),
            ctypes.byref(var_dim_c),
        )

        self.logger.debug(
            "type = {} {} {} {}.".format(
                var_type_c[0], category_c[0], acces_c.value, var_dim_c.value
            )
        )
        return var_type_c[0], category_c[0], acces_c.value, var_dim_c.value

    def get_var_size(self, var_name, index=0):
        """Get the size(s) of a Mascaret variable

        Use Mascaret Api :meth:`C_GET_TAILLE_VAR_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param index (int) only for cross-sections, graphs, weirs,
                          junctions, storage areas
        @return (int, int, int) sizes
        """
        var_name_c = (ctypes.c_char_p * 1)(var_name.encode("utf8"))
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        index = ctypes.c_int(index + 1)
        size1 = ctypes.c_int()
        size2 = ctypes.c_int()
        size3 = ctypes.c_int()
        self.logger.debug("Getting the size of {}...".format(var_name))
        self.error = self.libmascaret.C_GET_TAILLE_VAR_MASCARET(
            id_masc_c,
            var_name_c,
            ctypes.byref(index),
            ctypes.byref(size1),
            ctypes.byref(size2),
            ctypes.byref(size3),
        )
        self.logger.debug("size = {} {} {}.".format(size1.value, size2.value, size3.value))

        return size1.value, size2.value, size3.value

    def set_var_size(self, var_name, size1, size2, size3, index=0):
        """Set the size(s) of a Mascaret variable

        Use Mascaret Api :meth:`C_SET_TAILLE_VAR_MASCARET`

        @param var_name (str) name of the Mascaret variable
        @param size1 (int) size values to set
        @param size2 (int) size values to set
        @param size3 (int) size values to set
        @param index (int) only for cross-sections, graphs,
                          weirs, junctions, storage areas
        """
        var_name_c = ctypes.c_char_p(var_name.encode("utf-8"))

        index = ctypes.c_int(index)
        size1_c = ctypes.c_int(size1)
        size2_c = ctypes.c_int(size2)
        size3_c = ctypes.c_int(size3)
        self.logger.debug("Setting the size of {}...".format(var_name))
        self.error = self.libmascaret.C_SET_TAILLE_VAR_MASCARET(
            self.id_masc, var_name_c, index, size1_c, size2_c, size3_c
        )
        self.logger.debug("size = {} {} {}.".format(size1_c.value, size2_c.value, size3_c.value))

    def compute(self, t_0, t_end, time_step):
        """Direct computation of Mascaret

        Use Mascaret Api :meth:`C_CALCUL_MASCARET`.

        @param t_0 (float) initial time of the computation (s)
        @param t_end (float) end time of the computation (s)
        @param time_step (float) time step of the computation (s)
        """
        t0_c = ctypes.c_double(t_0)
        tend_c = ctypes.c_double(t_end)
        dt_c = ctypes.c_double(time_step)
        self.logger.debug("Running Mascaret... from {}".format(t_0))
        self.error = self.libmascaret.C_CALCUL_MASCARET(
            self.id_masc, t0_c, tend_c, dt_c, self.iprint
        )
        self.logger.debug("Running Mascaret... to {}".format(t_end))

    def compute_bc(self, t_0, t_end, time_step, tab_timebc, nb_timebc, nb_bc, tab_cl1, tab_cl2):
        """Indirect computation of Mascaret with a
            control on the boundary conditions

        Use Mascaret Api :meth:`C_CALCUL_MASCARET_CONDITION_LIMITE`.

        @param t_0 (float) initial time of the computation (s)
        @param t_end (float) end time of the computation (s)
        @param time_step (float) time step of the computation (s)
        @param tab_timebc (float) array of time values for boundary conditions
        @param nb_timebc (int) size of tab_timebc
        @param nb_bc (int) total number of boundary conditions
        @param tab_cl1 (float) values of boundary conditions
        @param tab_cl2 (float) values of boundary conditions
        """
        t0_c = ctypes.c_double(t_0)
        tend_c = ctypes.c_double(t_end)
        dt_c = ctypes.c_double(time_step)
        nb_timebc_c = ctypes.c_int(nb_timebc)

        tab_timebc_c = (ctypes.c_double * nb_timebc)()
        for j in range(nb_timebc):
            tab_timebc_c[j] = tab_timebc[j]
            tab_cl1_c = (ctypes.POINTER(ctypes.c_double) * nb_bc)()
            tab_cl2_c = (ctypes.POINTER(ctypes.c_double) * nb_bc)()

        for i in range(nb_bc):
            tab_cl1_c[i] = (ctypes.c_double * nb_timebc)()
            tab_cl2_c[i] = (ctypes.c_double * nb_timebc)()
            for j in range(nb_timebc):
                tab_cl1_c[i][j] = tab_cl1[j][i]
                tab_cl2_c[i][j] = tab_cl2[j][i]

        self.logger.debug("Running Mascaret cl...from {}".format(t_0))
        self.error = self.libmascaret.C_CALCUL_MASCARET_CONDITION_LIMITE(
            self.id_masc,
            t0_c,
            tend_c,
            dt_c,
            ctypes.byref(tab_timebc_c),
            nb_timebc_c,
            ctypes.byref(tab_cl1_c),
            ctypes.byref(tab_cl2_c),
            self.iprint,
        )
        self.logger.debug("Running Mascaret cl...to {}".format(t_end))

    def get_var_desc(self, prefix=""):
        """Get info on the Mascaret variables

        Use Mascaret Api :meth:`C_GET_DESC_VAR_MASCARET`

        @return (str, str, int) information on all the Mascaret variables
         ('Model' or 'State')
        """
        tab_name_c = ctypes.POINTER(ctypes.c_char_p)()
        tab_desc_c = ctypes.POINTER(ctypes.c_char_p)()
        size_c = ctypes.c_int()

        self.logger.debug("Get var desc MASCARET...")
        self.error = self.libmascaret.C_GET_DESC_VAR_MASCARET(
            self.id_masc, ctypes.byref(tab_name_c), ctypes.byref(tab_desc_c), ctypes.byref(size_c)
        )
        self.logger.debug("Get var desc MASCARET done.")

        var_desc = {}
        for i in range(size_c.value):
            if str(ctypes.string_at(tab_name_c[i]), "utf-8").startswith(prefix):
                var_desc[str(ctypes.string_at(tab_name_c[i]), "utf-8")] = str(
                    ctypes.string_at(tab_desc_c[i]), "utf-8"
                )
        return var_desc

    def version(self):
        """Version info wrapper

        Use Mascaret Api :meth:`C_VERSION_MASCARET`

        @return (str) Version X.Y.Z
        """
        v_c1 = ctypes.c_int()
        v_c2 = ctypes.c_int()
        v_c3 = ctypes.c_int()
        error = self.libmascaret.C_VERSION_MASCARET(
            ctypes.byref(v_c1), ctypes.byref(v_c2), ctypes.byref(v_c3)
        )
        if error != 0:
            return "Version number could not be retrieved from MASCARET..."
        return "v" + str(v_c1.value) + "p" + str(v_c2.value) + "r" + str(v_c3.value)

    def import_xml(self, file_name, import_model):
        """Import Model or State of Mascaret from xml files

        Use Mascaret Api :meth:`C_IMPORT_XML`

        @param file_name (str) name the xml file
        @param import_model (int) flag to import Model or State
        """
        import_model_c = ctypes.c_int(import_model)
        file_name_c = ctypes.c_char_p(file_name.encode("utf-8"))

        self.logger.debug("Import XML...")
        self.error = self.libmascaret.C_IMPORT_XML(self.id_masc, file_name_c, import_model_c)
        self.logger.debug("Import XML done.")

    def export_xml(self, file_name, description, export_model):
        """Export Model or State of Mascaret to xml files

        Use Mascaret Api :meth:`C_EXPORT_XML`

        @param file_name (str) name the xml file
        @param description (int)  flag to add info on variables
        @param export_model (int) flag to export Model or State
        """
        export_model_c = ctypes.c_int(export_model)
        description_c = ctypes.c_int(description)
        file_name_c = ctypes.c_char_p(file_name.encode("utf-8"))

        self.logger.debug("Export XML...")
        self.error = self.libmascaret.C_EXPORT_XML(
            self.id_masc, file_name_c, description_c, export_model_c
        )
        self.logger.debug("Export XML done.")

    def export_xml_saint_venant(self, file_name):
        """Export data for the SVT code

        Use Mascaret Api :meth:`C_EXPORT_XML_SAINT_VENANT`

        @param file_name (str) name of the xml file
        """
        file_name_c = ctypes.c_char_p(file_name.encode("utf-8"))

        self.logger.debug("Export XML SAINT-VENANT...")
        self.error = self.libmascaret.C_EXPORT_XML_SAINT_VENANT(self.id_masc, file_name_c)
        self.logger.debug("Export XML SAINT-VENANT done.")

    def open_tag_xml(self, file_name, unit, anchor):
        """Open xml file and root tag

        Use Mascaret Api :meth:`C_OUVERTURE_BALISE_XML`

        @param file_name (str) name of the xml file
        @param unit (int) logical unit
        @param anchor (str) root tag name
        """
        file_name_c = ctypes.c_char_p(file_name.encode("utf-8"))
        anchor_c = ctypes.c_char_p(anchor.encode("utf-8"))
        unit_c = ctypes.c_int(unit)

        self.logger.debug("Open XML anchor...")
        self.error = self.libmascaret.C_OUVERTURE_BALISE_XML(
            self.id_masc, file_name_c, unit_c, anchor_c
        )
        self.logger.debug("Open XML anchor done.")

    def export_var_xml(self, unit, var_name, description):
        """Export of a Mascaret variable to xml file

        Use Mascaret Api :meth:`C_EXPORT_VAR_XML`

        @param unit (int) logical unit
        @param var_name (str) name of the Mascaret variable
        @param description (int) flag to add info of the variable
        """
        var_name_c = ctypes.c_char_p(var_name.encode("utf-8"))
        unit_c = ctypes.c_int(unit)
        description_c = ctypes.c_int(description)

        self.logger.debug("Export variable in xml...")
        self.error = self.libmascaret.C_EXPORT_VAR_XML(
            self.id_masc, unit_c, var_name_c, description_c
        )
        self.logger.debug("Export variable in xml done.")

    def export_uservar_xml(self, unit, var_name, var_type, description, var_val):
        """Export of user variable to xml file

        Use Mascaret Api :meth:`C_EXPORT_USERVAR_XML`.

        @param unit (int) logical unit
        @param var_name (str) name of the user variable
        @param var_type (str) type of the user variable
        @param description (str) info of the user variable
        @param var_val (str) info to write on the xml tag
        """
        var_name_c = ctypes.c_char_p(var_name.encode("utf-8"))
        var_type_c = ctypes.c_char_p(var_type.encode("utf-8"))
        var_val_c = ctypes.c_char_p(var_val.encode("utf-8"))
        unit_c = ctypes.c_int(unit)
        description_c = ctypes.c_char_p(description.encode("utf-8"))

        self.logger.debug("Export user variable in xml...")
        self.error = self.libmascaret.C_EXPORT_USERVAR_XML(
            self.id_masc, unit_c, var_name_c, var_type_c, description_c, var_val_c
        )
        self.logger.debug("Export user variable in xml done.")

    def close_tag_xml(self, unit, anchor):
        """Close xml file and root tag

        Use Mascaret Api :meth:`C_FERMETURE_BALISE_XML`

        @param unit (int) logical unit
        @param anchor (str) root tag name
        """
        anchor_c = ctypes.c_char_p(anchor.encode("utf-8"))
        unit_c = ctypes.c_int(unit)

        self.logger.debug("Close xml anchor...")
        self.error = self.libmascaret.C_FERMETURE_BALISE_XML(self.id_masc, unit_c, anchor_c)
        self.logger.debug("Close xml anchor done.")

    def get_nb_cl(self):
        """Get the number of boundary conditions

        Use Mascaret Api :meth:`C_GET_NB_CONDITION_LIMITE_MASCARET`

        @return (int) the number of BC of type 1,2,3,7
        """
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        nb_bc_c = (ctypes.c_int * 1)()
        self.logger.debug("Getting the number of boundary conditions...")
        self.error = self.libmascaret.C_GET_NB_CONDITION_LIMITE_MASCARET(id_masc_c, nb_bc_c)
        self.logger.debug("Number of boundary conditions: {}.".format(nb_bc_c[0]))

        return nb_bc_c[0]

    def get_name_cl(self, num_cl):
        """Get the names of boundary conditions

        Use Mascaret Api :meth:`C_GET_NOM_CONDITION_LIMITE_MASCARET`

        @param num_cl (int) number of the boundary condition to consider

        @return (int, str) the number of BC and name of type 1,2,3,7
        """
        id_masc_c = (ctypes.c_int * 1)(self.id_masc)
        num_cl_c = (ctypes.c_int * 1)(num_cl)
        name_all_bc = (ctypes.c_char_p * 1)((" " * 30).encode("utf8"))
        n_law = ctypes.c_int()
        self.logger.debug("Getting names of boundary condition #{}".format(num_cl_c))
        self.error = self.libmascaret.C_GET_NOM_CONDITION_LIMITE_MASCARET(
            id_masc_c, num_cl_c, ctypes.byref(name_all_bc), ctypes.byref(n_law)
        )

        return n_law.value, name_all_bc[0]

    def get(self, varname, i=0, j=0, k=0):
        """
        Get the value of a variable of Mascaret

        @param varname (str) Name of the variable
        @param i (int) index on first dimension
        @param j (int) index on second dimension
        @param k (int) index on third dimension

        @return scalar value
        """
        value = None
        vartype, _, _, ndim = self.get_type_var(varname)
        # Calling get_var_size with index=i in case of tracer
        dim1, dim2, dim3 = self.get_var_size(varname, i)
        # Checking that index are within bound
        if ndim >= 1:
            if not 0 <= i < dim1:
                raise TelemacException("i=%i is not within [0,%i]" % (i, dim1))
            index_i = i + 1
        else:
            index_i = 0

        if ndim >= 2:
            if not 0 <= j < dim2:
                raise TelemacException("j=%i is not within [0,%i]" % (j, dim2))
            index_j = j + 1
        else:
            index_j = 0

        if ndim == 3:
            if not 0 <= k < dim3:
                raise TelemacException("k=%i is not within [0,%i]" % (k, dim3))
            index_k = k + 1
        else:
            index_k = 0

        # Getting value depending on type
        if b"DOUBLE" in vartype:
            value = self.get_double(varname, index_i, index_j, index_k)
        elif b"INT" in vartype:
            value = self.get_int(varname, index_i, index_j, index_k)
        elif b"STRING" in vartype:
            value = self.get_string(varname, index_i, index_j, index_k)
        elif b"BOOL" in vartype:
            value = self.get_bool(varname, index_i, index_j, index_k)
        else:
            raise TelemacException("Unknown data type %s for %s" % (vartype, varname))

        return value

    def set(self, varname, value, i=0, j=0, k=0):
        """
        Set the value of a variable of Mascaret

        @param varname (str) Name of the variable
        @param value (str/float/int) to set
        @param i (int) index on first dimension
        @param j (int) index on second dimension
        @param k (int) index on third dimension
        """
        vartype, _, modifiable, ndim = self.get_type_var(varname)

        dim1, dim2, dim3 = self.get_var_size(varname, i)

        # Check modifiable value
        if not modifiable:
            raise TelemacException("Variable %s is readonly" % varname)

        # Checking that index are within bound
        if ndim >= 1:
            if not 0 <= i < dim1:
                raise TelemacException("i=%i is not within [0,%i]" % (i, dim1))
            index_i = i + 1
        else:
            index_i = 0

        if ndim >= 2:
            if not 0 <= j < dim2:
                raise TelemacException("j=%i is not within [0,%i]" % (i, dim2))
            index_j = j + 1
        else:
            index_j = 0
        if ndim == 3:
            if not 0 <= k < dim3:
                raise TelemacException("k=%i is not within [0,%i]" % (i, dim3))
            index_k = k + 1
        else:
            index_k = 0

        # Getting value depending on type
        if b"DOUBLE" in vartype:
            self.set_double(varname, value, index_i, index_j, index_k)
        elif b"INT" in vartype:
            self.set_int(varname, value, index_i, index_j, index_k)
        elif b"STRING" in vartype:
            self.set_string(varname, value, index_i, index_j, index_k)
        elif b"BOOL" in vartype:
            self.set_bool(varname, value, index_i, index_j, index_k)
        else:
            raise TelemacException("Unknown data type %s for %s" % (vartype, varname))

            # MDU modif

    def save_lig_restart(self, out_file="RestartLigneEau.lig", k_s=None):
        """
        Save a lig restart files

        :param str out_file: name (and path) of the output restart file
        :param str k_s:       OPTIONAL : Ks other than None activate
            CF1 and CF2 storage
        """
        # Size informations
        if self.nb_nodes is None:
            self.nb_nodes, _, _ = self.get_var_size("Model.X")
        nbbf = self.get_var_size("Model.Connect.FirstNdNum")[0]
        # Formatting parameters
        nbcol = 5
        nbent = int((nbbf - 1) / nbcol) + 1
        nbfmt = int(self.nb_nodes / nbcol) * nbcol
        nbmod = self.nb_nodes % nbcol
        nblin = int(nbfmt / nbcol)
        # get coords
        xcoord = []
        # Local abscissa
        oribf = [self.get("Model.Connect.FirstNdNum", i) - 1 for i in range(nbbf)]
        endbf = [self.get("Model.Connect.LastNdNum", i) for i in range(nbbf)]
        # Assign the bief number to each section (piecewise constant list)
        ibief = [ib + 0 * i for ib in range(nbbf) for i in range(oribf[ib], endbf[ib])]
        for i in range(self.nb_nodes):
            xcoord.append(
                self.get("Model.X", i)
                - self.get("Model.X", oribf[ibief[i]])
                + self.get("Model.CrossSection.RelAbs", self.get("Model.IDT", oribf[ibief[i]]) - 1)
            )
        del ibief
        del oribf
        del endbf

        # variables
        elevation = [self.get("State.Z", i) for i in range(self.nb_nodes)]
        discharge = [self.get("State.Q", i) for i in range(self.nb_nodes)]

        with open(out_file, "w") as lig:
            # Header
            lig.write(
                "RESULTATS CALCUL, DATE :  "
                "{}\n".format(datetime.now().strftime("%d/%m/%Y %H:%M"))
            )
            lig.write("FICHIER RESULTAT MASCARET\n")
            lig.write(
                "------------------------------------------------\
                -----------------------\n"
            )
            lig.write(" IMAX =%6i " % self.nb_nodes)
            lig.write("NBBIEF=%5i\n" % nbbf)
            lig.write((nbent * " ENTETE NON RELUE\n"))
            # X
            np.savetxt(
                lig,
                np.asarray(xcoord[0:nbfmt]).reshape(nblin, nbcol),
                fmt=(nbcol * "%13.2f"),
                header="X",
                comments=" ",
            )
            if nbmod != 0:
                np.savetxt(
                    lig, np.asarray(xcoord[nbfmt:]).reshape(1, nbmod), fmt=(nbmod * "%13.2f")
                )
            # Z
            np.savetxt(
                lig,
                np.asarray(elevation[0:nbfmt]).reshape(nblin, nbcol),
                fmt=(nbcol * "%13.3f"),
                header="Z",
                comments=" ",
            )
            if nbmod != 0:
                np.savetxt(
                    lig, np.asarray(elevation[nbfmt:]).reshape(1, nbmod), fmt=(nbmod * "%13.3f")
                )
            # Q
            np.savetxt(
                lig,
                np.asarray(discharge[0:nbfmt]).reshape(nblin, nbcol),
                fmt=(nbcol * "%13.3f"),
                header="Q",
                comments=" ",
            )
            if nbmod != 0:
                np.savetxt(
                    lig, np.asarray(discharge[nbfmt:]).reshape(1, nbmod), fmt=(nbmod * "%13.3f")
                )

            # Optional Friction coefficients
            if k_s is not None:
                # CF1
                k_s = [self.get("Model.FricCoefMainCh", i) for i in range(self.nb_nodes)]
                np.savetxt(
                    lig,
                    np.asarray(k_s[0:nbfmt]).reshape(nblin, nbcol),
                    fmt=(nbcol * "%13.3f"),
                    header="CF1",
                    comments=" ",
                )
                if nbmod != 0:
                    np.savetxt(
                        lig, np.asarray(k_s[nbfmt:]).reshape(1, nbmod), fmt=(nbmod * "%13.3f")
                    )

                # CF2
                k_s = [self.get("Model.FricCoefFP", i) for i in range(self.nb_nodes)]
                np.savetxt(
                    lig,
                    np.asarray(k_s[0:nbfmt]).reshape(nblin, nbcol),
                    fmt=(nbcol * "%13.3f"),
                    header="CF2",
                    comments=" ",
                )
                if nbmod != 0:
                    np.savetxt(
                        lig, np.asarray(k_s[nbfmt:]).reshape(1, nbmod), fmt=(nbmod * "%13.3f")
                    )

            # Footer
            lig.write(" FIN")
        lig.close()


class TelemacException(Exception):
    """Generic exception class for all of Telemac-Mascaret Exceptions"""

    pass


class MascaretException(TelemacException):
    """Generic exception class for all of Telemac-Mascaret Exceptions"""

    pass
    # MDU fin modif
