# Reservoir.py -- Pamhyr
# Copyright (C) 2023-2024  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 logging

from Model.Tools.PamhyrDB import SQLSubModel

logger = logging.getLogger()


class Reservoir(SQLSubModel):
    _sub_classes = []
    _id_cnt = 0

    def __init__(self, id: int = -1, name: str = "",
                 status=None):
        super(Reservoir, self).__init__()

        self._status = status

        if id == -1:
            self.id = Reservoir._id_cnt
        else:
            self.id = id

        self._name = name
        self._node = None
        self._data = []

        Reservoir._id_cnt = max(Reservoir._id_cnt + 1, self.id)

    @classmethod
    def _db_create(cls, execute):
        execute("""
          CREATE TABLE reservoir(
            id INTEGER NOT NULL PRIMARY KEY,
            name TEXT NOT NULL,
            node INTEGER,
            FOREIGN KEY(node) REFERENCES river_node(id)
          )
        """)

        execute("""
          CREATE TABLE reservoir_data(
            id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
            ind INTEGER NOT NULL,
            elevation REAL NOT NULL,
            surface REAL NOT NULL,
            reservoir INTEGER,
            FOREIGN KEY(reservoir) REFERENCES reservoir(id)
          )
        """)

        return cls._create_submodel(execute)

    @classmethod
    def _db_update(cls, execute, version):
        major, minor, release = version.strip().split(".")
        if major == minor == "0":
            if int(release) < 5:
                cls._db_create(execute)

        return True

    @classmethod
    def _db_load(cls, execute, data=None):
        new = []

        table = execute(
            "SELECT id, name, node " +
            "FROM reservoir "
        )

        for row in table:
            id = row[0]
            name = row[1]
            node_id = row[2]
            new_reservoir = cls(id, name, status=data["status"])

            new_reservoir._node = None
            if node_id != -1:
                new_reservoir._node = next(
                    filter(
                        lambda n: n.id == node_id, data["nodes"]
                    )
                )

            new_data = []
            table = execute(
                "SELECT elevation, surface " +
                "FROM reservoir_data " +
                f"WHERE reservoir = {id} " +
                "ORDER BY ind ASC"
            )
            for t in table:
                new_data.append((t[0], t[1]))

            new_reservoir._data = new_data

            new.append(new_reservoir)

        return new

    def _db_save(self, execute, data=None):

        execute(f"DELETE FROM reservoir WHERE id = {self.id}")
        execute(f"DELETE FROM reservoir_data WHERE reservoir = {self.id}")

        node_id = -1
        if self._node is not None:
            node_id = self._node.id

        sql = (
            "INSERT INTO " +
            "reservoir(id, name, node) " +
            "VALUES (" +
            f"{self.id}, '{self._db_format(self._name)}', " +
            f"{node_id}" +
            ")"
        )
        execute(sql)

        ind = 0
        for d in self._data:
            sql = (
                "INSERT INTO " +
                "reservoir_data(ind, elevation, surface, reservoir) " +
                f"VALUES ({ind}, '{d[0]}', {d[1]}, {self.id})"
            )
            execute(sql)
            ind += 1

        return True

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

    @property
    def name(self):
        if self._name == "":
            return f"R{self.id + 1}"

        return self._name

    @name.setter
    def name(self, name):
        self._name = name
        self._status.modified()

    @property
    def node(self):
        return self._node

    @node.setter
    def node(self, node):
        self._node = node
        self._status.modified()

    def has_node(self):
        return self._node is not None

    @property
    def data(self):
        return self._data.copy()

    @property
    def _default_elevation(self):
        return 0.0

    @property
    def _default_surface(self):
        return 0.0

    def is_define(self):
        return len(self._data) != 0

    def new_from_data(self, data):

        try:
            new_0 = float(data[0])
            new_1 = float(data[1])
        except Exception as e:
            logger.error(e)
            new_0 = None
            new_1 = None

        return (new_0, new_1)

    def add(self, index: int):
        value = (self._default_elevation, self._default_surface)
        self._data.insert(index, value)
        self._status.modified()
        return value

    def insert(self, index: int, value):
        self._data.insert(index, value)
        self._status.modified()

    def delete_i(self, indexes):
        self._data = list(
            map(
                lambda e: e[1],
                filter(
                    lambda e: e[0] not in indexes,
                    enumerate(self.data)
                )
            )
        )
        self._status.modified()

    def delete(self, els):
        self._data = list(
            filter(
                lambda e: e not in els,
                self.data
            )
        )
        self._status.modified()

    def sort(self, _reverse=False, key=None):
        if key is None:
            self._data.sort(reverse=_reverse)
        else:
            self._data.sort(reverse=_reverse, key=key)
        self._status.modified()

    def get_i(self, index):
        return self.data[index]

    def get_range(self, _range):
        lst = []
        for r in _range:
            lst.append(r)
        return lst

    def _set_i_c_v(self, index, column, value):
        v = list(self._data[index])
        v[column] = value
        self._data[index] = tuple(v)
        self._status.modified()

    def set_i_elevation(self, index: int, value):
        self._set_i_c_v(index, 0, value)

    def set_i_surface(self, index: int, value):
        self._set_i_c_v(index, 1, value)

    def move_up(self, index):
        if index < len(self):
            next = index - 1
            d = self._data
            d[index], d[next] = d[next], d[index]
            self._status.modified()

    def move_down(self, index):
        if index >= 0:
            prev = index + 1
            d = self._data
            d[index], d[prev] = d[prev], d[index]
            self._status.modified()
