# SolverParametersList.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 -*-

from copy import copy
from tools import trace, timer
from Solver.Solvers import solver_type_list

from Model.Tools.PamhyrList import PamhyrModelList


class Parameter():
    def __init__(self,
                 name="", value="",
                 status=None):
        self._status = status

        self._name = name
        self._value = value

    @property
    def name(self):
        return self._name

    @property
    def value(self):
        return self._value

    def __getitem__(self, key):
        if key == "name":
            return self._name
        elif key == "value":
            return self._value

        return None

    def __setitem__(self, key, value):
        if key == "name":
            self._name = str(value)
        elif key == "value":
            self._value = str(value)

        self._status.modified()

    @classmethod
    def from_tuple(cls, data, status):
        new = cls(status=status)
        new["name"] = data[0]
        new["value"] = data[1]

        return new


class SolverParametersList(PamhyrModelList):
    def __init__(self, solver_type=None, status=None):
        super(SolverParametersList, self).__init__()

        self._status = status
        self._solver = solver_type
        self._lst = list(
            map(
                lambda t: Parameter.from_tuple(t, self._status),
                solver_type.default_parameters()
            )
        )

    @classmethod
    def _sql_create(cls, execute):
        execute("""
          CREATE TABLE solver_parameter(
            id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
            ind INTEGER NOT NULL,
            name TEXT NOT NULL,
            value TEXT NOT NULL,
            solver TEXT NOT NULL
          )
        """)

        return cls._create_submodel(execute)

    @classmethod
    def _sql_update(cls, execute, version):
        major, minor, release = version.strip().split(".")

        if major == minor == "0":
            if int(release) < 3:
                execute(
                    "UPDATE solver_parameter SET name='mage_implicitation' " +
                    "WHERE name='mage_implication'"
                )
                execute(
                    "UPDATE solver_parameter SET name='mage_iteration_type' " +
                    "WHERE name='mage_iter_type'"
                )

            if int(release) < 4:
                solvers = execute(
                    "SELECT DISTINCT solver FROM solver_parameter")

                for solver in solvers:
                    if solver != "mage8":
                        continue

                    data = execute("SELECT ind, name, value " +
                                   "FROM solver_parameter " +
                                   f"WHERE solver = '{solver}' " +
                                   "ORDER BY ind ACS")

                    execute(
                        "DELETE FROM solver_parameter " +
                        f"WHERE solver = '{solver}'"
                    )

                    def insert(name, value, ind): return execute(
                        "INSERT INTO " +
                        "solver_parameter(ind, name, value, solver) " +
                        "VALUES (" +
                        f"{ind}, " +
                        f"'{cls._sql_format(name)}', " +
                        f"'{cls._sql_format(value)}', " +
                        f"'{cls._sql_format(solver)}'" +
                        ")"
                    )

                    ind = 0
                    lst = []
                    for v in data:
                        insert(v[0], v[1], ind)
                        ind += 1

                    new = [
                        ("mage_sediment_masse_volumique", "2650.0"),
                        ("mage_sediment_angle_repos", "40.0"),
                        ("mage_sediment_porosity", "0.40"),
                        ("mage_distance_han", "0.0"),
                        ("mage_distance_chargement_d50", "100.0"),
                        ("mage_distance_chargement_sigma", "100.0"),
                        ("mage_methode_modification_geometrie", "1"),
                        ("mage_shields_critique", "1"),
                        ("mage_shields_correction", "1"),
                        ("mage_capacite_solide", "1"),
                        ("mage_pas_de_temps_charriage", "1"),
                        ("mage_facteur_multiplicateur", "1.0"),
                    ]

                    for v in new:
                        insert(v[0], v[1], ind)
                        ind += 1

        return cls._update_submodel(execute, version)

    @classmethod
    def _sql_load(cls, execute, data=None):
        status = data["status"]
        solvers = execute("SELECT DISTINCT solver FROM solver_parameter")
        new = {}

        for solver in solvers:
            solver = solver[0]
            if solver not in solver_type_list:
                continue

            st = solver_type_list[solver]
            n = cls(solver_type=st, status=status)

            table = execute(
                "SELECT ind, name, value " +
                "FROM solver_parameter " +
                f"WHERE solver = '{solver}'"
            )

            for row in table:
                ind = row[0]
                name = row[1]
                value = row[2]

                n.set_value(name, value)

            new[solver] = n

        return new

    def _sql_save(self, execute, data=None):
        t = self._solver._type
        execute(
            "DELETE FROM solver_parameter " +
            f"WHERE solver = '{t}'"
        )

        ind = 0
        for param in self._lst:
            sql = (
                "INSERT INTO " +
                "solver_parameter(ind, name, value, solver) " +
                "VALUES (" +
                f"{ind}, " +
                f"'{self._sql_format(param.name)}', " +
                f"'{self._sql_format(param.value)}', " +
                f"'{self._sql_format(t)}'" +
                ")"
            )
            execute(sql)
            ind += 1

        return True

    @property
    def parameters(self):
        return self.lst

    def get(self, index):
        return self._lst[index]

    def get_by_key(self, key):
        try:
            return next(
                filter(
                    lambda p: p["name"] == key,
                    self._lst
                )
            )["value"]
        except Exception:
            return None

    def set(self, index, new):
        self._lst[index] = new
        self._status.modified()

    def set_value(self, key, value):
        for p in self._lst:
            if p["name"] == key:
                p["value"] = value
                self._status.modified()
                return

        self._lst.append(
            Parameter(
                name=key,
                value=value,
                status=self._status
            )
        )
        self._status.modified()

    def new(self, index):
        n = Parameter(status=self._status)
        self._lst.insert(index, n)
        self._status.modified()
        return n
