# 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.DB import SQLSubModel

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(SQLSubModel):
    def __init__(self, solver_type = None, status = None):
        super(SolverParametersList, self).__init__()

        self._status = status
        self._solver = solver_type
        self._parameters = 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(f"UPDATE solver_parameter SET name='mage_implicitation' WHERE name='mage_implication'")
                execute(f"UPDATE solver_parameter SET name='mage_iteration_type' WHERE name='mage_iter_type'")

        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._parameters:
            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

    def __len__(self):
        return len(self._parameters)

    @property
    def parameters(self):
        return self._parameters.copy()

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

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

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

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

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

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

    def insert(self, index, new):
        self._parameters.insert(index, new)
        self._status.modified()

    def delete(self, parameters):
        for parameter in parameters:
            self._parameters.remove(parameter)
        self._status.modified()

    def delete_i(self, indexes):
        parameters = list(
            map(
                lambda x: x[1],
                filter(
                    lambda x: x[0] in indexes,
                    enumerate(self._parameters)
                )
            )
        )
        self.delete(parameters)

    def sort(self, reverse=False, key=None):
        self._parameters.sort(reverse=reverse, key=key)
        self._status.modified()

    def move_up(self, index):
        if index < len(self._parameters):
            next = index - 1

            l = self._parameters
            l[index], l[next] = l[next], l[index]
            self._status.modified()

    def move_down(self, index):
        if index >= 0:
            prev = index + 1

            l = self._parameters
            l[index], l[prev] = l[prev], l[index]
            self._status.modified()