# PamhyrDB.py -- Pamhyr abstract model database classes
# 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 sqlite3
import logging

from pathlib import Path

from tools import SQL
from Model.Except import NotImplementedMethodeError

logger = logging.getLogger()

# Top level model class


class SQLModel(SQL):
    _sub_classes = []

    def _init_db_file(self, db, is_new=True):
        exists = Path(db).exists()

        if exists and is_new:
            os.remove(db)

        self._db = sqlite3.connect(db)
        self._cur = self._db.cursor()

        if is_new:
            logger.info("Create database")
            self._create()      # Create db
            # self._save()        # Save
        else:
            self._update()      # Update db scheme if necessary
            # self._load()        # Load data

    def __init__(self, filename=None):
        self._db = None

    def _create_submodel(self):
        def fn(sql): return self.execute(
            sql,
            fetch_one=False,
            commit=False
        )

        for cls in self._sub_classes:
            requests = cls._sql_create(fn)

        self.commit()
        return True

    def _create(self):
        raise NotImplementedMethodeError(self, self._create)

    def _update_submodel(self, version):
        def fn(sql): return self.execute(
            sql,
            fetch_one=False,
            commit=False
        )

        ok = True
        for cls in self._sub_classes:
            ok &= cls._sql_update(fn, version)

        self.commit()
        return ok

    def _update(self):
        raise NotImplementedMethodeError(self, self._update)

    def _save_submodel(self, objs, data=None):
        def fn(sql): return self.execute(
            sql,
            fetch_one=False,
            commit=False
        )

        ok = True
        for obj in objs:
            ok &= obj._sql_save(fn)

        self.commit()
        return ok

    def _save(self):
        raise NotImplementedMethodeError(self, self._save)

    @classmethod
    def _load(cls, filename=None):
        raise NotImplementedMethodeError(cls, cls._load)

# Sub model class


class SQLSubModel(object):
    _sub_classes = []

    def _sql_format(self, value):
        # Replace ''' by '&#39;' to preserve SQL injection
        if value is str:
            value = value.replace("'", "&#39;")
        elif value is bool:
            value = 'TRUE' if value else 'FALSE'
        return value

    @classmethod
    def _create_submodel(cls, execute):
        for sc in cls._sub_classes:
            sc._sql_create(execute)

    @classmethod
    def _sql_create(cls, execute):
        """Create data base scheme

        Args:
            execute: Function to exec SQL resquest

        Returns:
            Return true, otherelse false if an issue appear
        """
        raise NotImplementedMethodeError(cls, cls._sql_create)

    @classmethod
    def _update_submodel(cls, execute, version):
        for sc in cls._sub_classes:
            sc._sql_update(execute, version)

    @classmethod
    def _sql_update(cls, execute, version):
        """Update data base scheme

        Args:
            execute: Function to exec SQL resquest
            version: Current database version

        Returns:
            Return true, otherelse false if an issue appear
        """
        raise NotImplementedMethodeError(cls, cls._sql_update)

    @classmethod
    def _sql_load(cls, execute, data=None):
        """Load instance of this class from SQL data base

        Args:
            execute: Function to exec SQL request
            data: Optional data for the class constructor

        Returns:
            Return new instance of class
        """
        raise NotImplementedMethodeError(cls, cls._sql_load)

    def _save_submodel(self, execute, objs, data=None):
        for o in objs:
            o._sql_save(execute, data=data)

    def _sql_save(self, execute, data=None):
        """Save class data to data base

        Args:
            execute: Function to exec SQL resquest
            data: Optional additional information for save

        Returns:
            Return true, otherelse false if an issue appear during
            save
        """
        raise NotImplementedMethodeError(self, self._sql_save)
