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

#******************************************************************************
#
# Freewat
# ---------------------------------------------------------
#
#
# Copyright (C) 2014 - 2015 Iacopo Borsi (iacopo.borsi@tea-group.com)
#
# This source 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 2 of the License, or (at your option)
# any later version.
#
# This code 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.
#
# A copy of the GNU General Public License is available on the World Wide Web
# at <http://www.gnu.org/licenses/>. You can also obtain it by writing
# to the Free Software Foundation, 51 Franklin Street, Suite 500 Boston,
# MA 02110-1335 USA.
#
#******************************************************************************

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from qgis.core import *

import os
import datetime

from PyQt4 import uic
from freewat.freewat_utils import getFieldNames, getVectorLayerByName, getVectorLayerNames, getModelsInfoLists, getModelInfoByName
from freewat.mdoCreate_utils import createFlowObsLayer
from freewat.sqlite_utils import getTableNamesList

from freewat.oat.plugin import databaseManager
from freewat.oat.oatlib import sensor, method

from pyspatialite import dbapi2 as db

FORM_CLASS, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__), 'ui', 'ui_createRVOBLayer.ui'))


class CreateROVBLayerDialog(QDialog, FORM_CLASS):

    def __init__(self, iface):
        QDialog.__init__(self)
        self.iface = iface
        self.setupUi(self)
        self.buttonBox.rejected.connect(self.reject)
        self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.createFlowObs)
        self.cmbPackage.currentIndexChanged.connect(self.reloadFields)
        self.cmbGageLine.currentIndexChanged.connect(self.reloadFields)

        self.oatCheck.stateChanged.connect(self.oat_state_change)

        self.db = None

        self.manageGui()

    def manageGui(self):

        self.cmbModelName.clear()
        self.cmbGageLine.clear()
        layerNameList = getVectorLayerNames()

        layerNameList.sort()

        (modelNameList, pathList) = getModelsInfoLists(layerNameList)

        self.cmbModelName.addItems(modelNameList)
        self.cmbGageLine.addItems(layerNameList)

        # fill the Grid combobox with only the _grid layer
        grid_layers = []
        for nametemp in layerNameList:
            if '_grid' in nametemp:
                grid_layers.append(nametemp)

        self.cmbGridLayer.addItems(grid_layers)

        self.oat_sensors_name()

##
##
    def reloadFields(self):
        # Insert list of MDO for RIV or DRN or GHB, according to the selected package:
        self.cmbGridLayer.clear()
        layerNameList = getVectorLayerNames()
        packageName = self.cmbPackage.currentText()
        self.suffix = '_ghb'
        self.package = 'GHB'
        if 'RIV' in packageName:
            self.suffix = '_riv'
            self.package = 'RIV'
        if 'DRN' in packageName:
            self.suffix = '_drn'
            self.package = 'DRN'
        my_layers = []
        for nametemp in layerNameList:
            if self.suffix in nametemp:
                my_layers.append(nametemp)

        self.cmbGridLayer.addItems(my_layers)

        # Add Items to ComboBox for selecting fields of Line Layer
        linelayer = getVectorLayerByName(self.cmbGageLine.currentText())
        self.cmbName.clear()
        self.cmbName.addItems(getFieldNames(linelayer))
        # self.cmbFlow.clear()
        # self.cmbFlow.addItems(getFieldNames(linelayer))
        # self.cmbDate.clear()
        # self.cmbDate.addItems(getFieldNames(linelayer))
        # self.cmbTime.clear()
        # self.cmbTime.addItems(getFieldNames(linelayer))

##
##
    def reject(self):
        QDialog.reject(self)

##
    def createFlowObs(self):

        # ------------ Load input data  ------------

        modelName = self.cmbModelName.currentText()
        newName = self.lineNewLayerEdit.text()
        groupName = self.cmbName.currentText()

        gridLayer = getVectorLayerByName(self.cmbGridLayer.currentText())
        segmentLayer = getVectorLayerByName(self.cmbGageLine.currentText())
        use_oat = self.oatCheck.isChecked()

        if use_oat:
            try:
                self.get_data_from_oat(modelName, segmentLayer, groupName)
            except Exception as e:
                QMessageBox.warning(self, self.tr('Warning!'), 'Exception: {}'.format(e))
                return

        # Remark: pathfile from model table and number of stress periods (nsp) from time table
        (pathfile, nsp) = getModelInfoByName(modelName)

        # Retrieve the information of the model and the name
        dbName = os.path.join(pathfile, '{}.sqlite'.format(modelName))
        tableList = getTableNamesList(dbName)
        layerName = newName + "_obs" + self.suffix

        if layerName in tableList:
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('Table %s already exists!' % layerName))
            return
        try:
            self.check_if_layer_exists(newName)
        except Exception as e:
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('{}'.format(e)))
            return

        # Create a Flow Observations Layer, call the main function imported at the beginning of the file
        # createFlowObsLayer(newName, dbName, gridLayer, groupName, segmentLayer, self.package, use_oat)
        try:
            createFlowObsLayer(newName, dbName, gridLayer, groupName, segmentLayer, self.package, use_oat)
        except Exception as e:
            QMessageBox.warning(self, self.tr('Warning!'), self.tr('{}'.format(e)))
            return

        # Close the dialog window after the execution of the algorithm
        self.close()

    def check_if_layer_exists(self, layer_name):
        """
            Check if table already exists inside the database
        """
        sql = "SELECT name FROM sqlite_master WHERE type='table' AND (name=? OR name=?)"

        if self.package == 'RIV':
            typeobs = '_rvob'
        elif self.package == 'DRN':
            typeobs = '_drob'
        elif self.package == 'GHB':
            typeobs = '_gbob'
        else:
            typeobs = '_chob'

        params = ('{}{}'.format(layer_name, typeobs), '{}temp{}'.format(layer_name, typeobs))

        if len(self.db.execute_query(sql, params)) != 0:
            raise Exception("Layer {} already exists".format(layer_name))

    def oat_state_change(self, state):
        """
            Toggle oat panel
        """
        if state == Qt.Checked:
            self.oatFrame.setEnabled(True)
        else:
            self.oatFrame.setEnabled(False)

    def oat_sensors_name(self):
        """
            Fill oat QComboBox with oat sensors name
        """

        self.db = databaseManager.DatabaseManager()
        sql = "SELECT name from freewat_sensors"

        try:
            res = self.db.execute_query(sql)
        except Exception as e:
            #print e
            # No table exists
            return

        for elem in res:
            self.upSensor.addItem(elem[0])
            self.downSensor.addItem(elem[0])

    def get_data_from_oat(self, model_name, segment_layer, group_name):
        """
        """
        up_name = self.upSensor.currentText()
        down_name = self.downSensor.currentText()

        up = sensor.Sensor.from_sqlite(self.db.get_db_path(), up_name)
        down = sensor.Sensor.from_sqlite(self.db.get_db_path(), down_name)

        if not up.data_availability == down.data_availability:
            raise Exception("The timeseries are different")

        up.ts_from_sqlite(self.db.get_db_path())
        down.ts_from_sqlite(self.db.get_db_path())

        if not up or not down:
            raise Exception("please select compatible sensors")

        if self.oatAdapt.isChecked():
            up, down = self.adapt_timestep(model_name, up, down)

        tmp = up
        tmp.ts.data = up.ts.data - down.ts.data

        oat_sens = tmp  # up.process(method.Subtract(down))  # self.subtract_timeseries(up, down)

        oat_sens.ts = oat_sens.ts.rename(columns={'quality': 'weight', 'obs_index': 'obsname'})

        oat_sens.ts['statistics'] = oat_sens.statflag

        idx_list = []
        for i in range(0, len(oat_sens.ts.index)):
            idx_list.append(oat_sens.name[0:3] + str(i + 1))

        oat_sens.ts['obsname'] = idx_list

        gage_name = self.get_gage_name(segment_layer, group_name)
        conn = db.connect(self.db.get_db_path())
        oat_sens.ts.to_sql(gage_name, conn, if_exists='replace')

    def adapt_timestep(self, model_name, up, down):
        """
            Adapt timeseries to match stress period and time step
        """
        import pandas as pd
        import numpy as np

        # #########################
        # Read data from db
        # #########################
        model_table = "modeltable_{}".format(model_name)
        time_table = "timetable_{}".format(model_name)

        sql = "SELECT * FROM {}".format(model_table)
        res_mod = self.db.execute_query(sql)[0]

        sql = "SELECT * FROM {}".format(time_table)
        res_time = self.db.execute_query(sql)

        date = "{}T{}".format(res_mod[5], res_mod[6])

        start_date = datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%S')
        time_unit = res_mod[2]

        frames_up = []
        frames_down = []

        for sp in res_time:

            # adapt time variable
            int_sec = CreateROVBLayerDialog.convert_to_sec(sp[2], time_unit)
            end_date = start_date + datetime.timedelta(seconds=int_sec)
            ts_sec = int((end_date - start_date).total_seconds() / sp[3])  # sp[3] == timestep

            # adapt upstream ts
            sp_frame_up = up.ts[up.ts.index >= start_date]
            sp_frame_up = sp_frame_up[sp_frame_up.index < end_date]
            sp_frame_up = sp_frame_up.resample('{}S'.format(ts_sec)).agg({'data': np.sum, 'quality': np.min, 'use': np.min})
            frames_up.append(sp_frame_up)

            # adapt downstream ts
            sp_frame_down = down.ts[down.ts.index >= start_date]
            sp_frame_down = sp_frame_down[sp_frame_down.index < end_date]
            sp_frame_down = sp_frame_down.resample('{}S'.format(ts_sec)).agg({'data': np.sum, 'quality': np.min, 'use': np.min})
            frames_down.append(sp_frame_down)

            start_date = end_date

        # replace "original" timeseries with adapted ones
        up.ts = pd.concat(frames_up)
        down.ts = pd.concat(frames_down)

        return up, down

    @staticmethod
    def convert_to_sec(interval, unit):

        if unit == 'year':
            return interval * 365 * 24 * 3600
        elif unit == 'month':
            return interval
        elif unit == 'day':
            return interval * 24 * 3600
        elif unit == 'hour':
            return interval * 3600
        elif unit == 'min':
            return interval * 60
        else:
            return interval

    @staticmethod
    def get_gage_name(layer, field):
        """
            Get gage name from layer
        """
        for feat in layer.getFeatures():
            return feat[field]

        raise Exception("Gage name not found, please select correct field")
