# MageMesh.py -- Pamhyr
# Copyright (C) 2023  INRAE
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

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

import os
import logging

from ctypes import (
    cdll,
    byref, Structure,
    c_char_p, c_wchar_p,
    create_string_buffer,
    POINTER, c_void_p,
    c_int, c_double, c_bool
)

from Scripts.AScript import AScript

logger = logging.getLogger()

bief_lib = None
c_init_bief_from_geo_file = None
c_get_nb_sections = None
c_get_nb_points_section = None
c_set_bief_name = None
c_st_to_m_compl = None
c_interpolate_profils_pas_transversal = None
c_update_pk = None
c_output_bief = None

##########################
# Binding initialisation #
##########################


def init_clib(lib_path):
    global bief_lib
    bief_lib = cdll.LoadLibrary(lib_path)

    init_c_init_bief_from_geo_file(bief_lib)
    init_c_get_nb_sections(bief_lib)
    init_c_get_nb_points_section(bief_lib)
    init_c_set_bief_name(bief_lib)
    init_c_st_to_m_compl(bief_lib)
    init_c_interpolate_profils_pas_transversal(bief_lib)
    init_c_update_pk(bief_lib)
    init_c_output_bief(bief_lib)


def init_c_init_bief_from_geo_file(bief_lib):
    global c_init_bief_from_geo_file

    c_init_bief_from_geo_file = getattr(
        bief_lib, 'c_init_bief_from_geo_file'
    )
    c_init_bief_from_geo_file.argtypes = [
        c_char_p, POINTER(c_int), POINTER(c_int)
    ]
    c_init_bief_from_geo_file.restype = None


def init_c_get_nb_sections(bief_lib):
    global c_get_nb_sections

    c_get_nb_sections = getattr(bief_lib, 'c_get_nb_sections')
    c_get_nb_sections.argtypes = [POINTER(c_int)]
    c_get_nb_sections.restype = None


def init_c_get_nb_points_section(bief_lib):
    global c_get_nb_points_section

    c_get_nb_points_section = getattr(bief_lib, 'c_get_nb_points_section')
    c_get_nb_points_section.argtypes = [POINTER(c_int), POINTER(c_int)]
    c_get_nb_points_section.restype = None


def init_c_set_bief_name(bief_lib):
    global c_set_bief_name

    c_set_bief_name = getattr(bief_lib, 'c_set_bief_name')
    c_set_bief_name.argtypes = [c_char_p]
    c_set_bief_name.restype = None


def init_c_st_to_m_compl(bief_lib):
    global c_st_to_m_compl

    c_st_to_m_compl = getattr(bief_lib, 'c_st_to_m_compl')
    c_st_to_m_compl.argtypes = [POINTER(c_int), c_char_p, c_char_p]
    c_st_to_m_compl.restype = None


def init_c_interpolate_profils_pas_transversal(bief_lib):
    global c_interpolate_profils_pas_transversal

    c_interpolate_profils_pas_transversal = getattr(
        bief_lib, 'c_interpolate_profils_pas_transversal'
    )
    c_interpolate_profils_pas_transversal.argtypes = [
        POINTER(c_int), POINTER(c_int),
        c_char_p, c_char_p,
        POINTER(c_double), POINTER(c_bool),
        POINTER(c_int), POINTER(c_bool)
    ]
    c_interpolate_profils_pas_transversal.restype = None


def init_c_update_pk(bief_lib):
    global c_update_pk

    c_update_pk = getattr(bief_lib, 'c_update_pk')
    c_update_pk.argtypes = [c_char_p]
    c_update_pk.restype = None


def init_c_output_bief(bief_lib):
    global c_output_bief

    c_output_bief = getattr(bief_lib, 'c_output_bief')
    c_output_bief.argtypes = [c_char_p]
    c_output_bief.restype = None

#####################
# Binding functions #
#####################

def init_bief_from_geo_file(name, with_charriage, with_water):
    logger.info("! call init_bief_from_geo_file:")
    cname = create_string_buffer(name.encode())
    c_init_bief_from_geo_file(
        cname,
        byref(c_int(with_charriage)),
        byref(c_int(with_water))
    )

def get_nb_sections():
    nb_sections = c_int(0)
    c_get_nb_sections(byref(nb_sections))
    return nb_sections.value


def get_nb_points_section(section):
    nb_points = c_int(0)
    c_get_nb_points_section(byref(c_int(section)), byref(nb_points))
    return nb_points.value


def set_bief_name(name):
    logger.info(f"! call set_bief_name: {repr(name)}")
    cname = create_string_buffer(name.encode())
    c_set_bief_name(cname)


def st_to_m_compl(npoints, tag1='   ', tag2='   '):
    logger.info(f"! call st_to_m_compl: {npoints}")
    cnpoints = c_int(npoints)
    ctag1 = create_string_buffer(tag1.encode())
    ctag2 = create_string_buffer(tag2.encode())

    c_st_to_m_compl(byref(cnpoints), ctag1, ctag2)


def interpolate_profils_pas_transversal(limite1, limite2,
                                        directrice1, directrice2,
                                        pas, lplan=False,
                                        lm=3, lineaire=False):
    logger.info("! call interpolate_profils_pas_transversal:")
    climite1 = c_int(limite1)
    climite2 = c_int(limite2)
    cpas = c_double(pas)
    clplan = c_bool(lplan)
    clm = c_int(lm)
    clineaire = c_bool(lineaire)
    cdirectrice1 = create_string_buffer(directrice1.encode())
    cdirectrice2 = create_string_buffer(directrice2.encode())

    c_interpolate_profils_pas_transversal(
        byref(climite1), byref(climite2),
        cdirectrice1, cdirectrice2,
        byref(cpas), byref(clplan),
        byref(clm), byref(clineaire)
    )


def update_pk(directrice):
    logger.info("! call update_pk:")
    cdirectrice = create_string_buffer(directrice.encode())
    c_update_pk(cdirectrice)


def output_bief(name):
    logger.info("! call output_bief:")
    cname = create_string_buffer(name.encode())
    c_output_bief(cname)


##########
# Script #
##########


class MageMesh(AScript):
    name = "MageMesh"
    description = "Mesh ST file to M"

    def usage(self):
        logger.info(
            f"Usage : {self._args[0]} {self._args[1]} " +
            "<SO_FILE> <ST_FILE> <STEP>"
        )

    def run(self):
        if len(self._args) < 5:
            return 1

        try:
            so = self._args[2]
            st = self._args[3]
            step = float(self._args[4])
        except Exception as e:
            logger.error(f"Argument format error: {e}")
            return 2

        try:
            init_clib(so)
        except Exception as e:
            logger.error(f"Bindings failed: {e}")
            return 3

        self.meshing(st, step)

        return 0

    def meshing(self, st_file: str, step: float):
        workdir = self.get_workdir(st_file)
        file_name = st_file.rsplit(".", 1)[0]
        reach_name = os.path.basename(file_name)

        # Open
        init_bief_from_geo_file(st_file, 0, 0)
        set_bief_name(reach_name)

        ns = get_nb_sections()
        npts_max = max(
            map(
                lambda i: get_nb_points_section(i),
                range(1, ns)
            )
        )

        # Transform
        st_to_m_compl(0, ' ', ' ')
        interpolate_profils_pas_transversal(
            1, ns,
            'un', 'np',
            step
        )
        update_pk("un")

        # Save
        if os.path.isfile(f"{file_name}.M"):
            os.remove(f"{file_name}.M")

        logger.info(f"Saved meshing geometry to {file_name}.M")
        output_bief(f"{file_name}.M")

    def get_workdir(self, file):
        workdir = os.path.abspath(
            os.path.dirname(file)
        )

        os.makedirs(workdir, exist_ok=True)
        logger.info(f"Set working dir to {workdir}")

        return workdir