# -*- coding: utf-8 -*-
"""
/***************************************************************************
 TileNetworkOrder
                                 A QGIS plugin
 Tile Network Order plugin generates Ordering ID, Burying Depths, Flow Direction etc.
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2022-03-28
        git sha              : $Format:%H$
        copyright            : (C) 2022 by FALASY  Anamelechi
        email                : fvw.services@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtCore import QVariant
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction, QFileDialog
from qgis.core import *
from qgis.core import QgsProject, QgsProcessingException, Qgis
from qgis.utils import *

from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox

from csv import writer
from csv import reader
from csv import DictReader
from csv import DictWriter

from qgis import processing
import os
import glob

import errno
import os.path
import sys
import csv
import pandas as pd

from os.path import dirname

# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .tile_network_order_dialog import TileNetworkOrderDialog


class TileNetworkOrder:
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        # initialize plugin directory
        self.plugin_dir = os.path.dirname(__file__)
        # initialize locale
        locale = QSettings().value('locale/userLocale')[0:2]
        locale_path = os.path.join(
            self.plugin_dir,
            'i18n',
            'TileNetworkOrder_{}.qm'.format(locale))

        if os.path.exists(locale_path):
            self.translator = QTranslator()
            self.translator.load(locale_path)
            
            if qVersion() > '4.3.3':
                QCoreApplication.installTranslator(self.translator)

        # Create the dialog (after translation) and keep reference
        self.dlg = TileNetworkOrderDialog()
        
        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Tile Network Order')

        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'Tile Network Order')
        self.toolbar.setObjectName(u'Tile Network Order')

        self.dlg.lineEdit.clear()
        self.initFolder();
        self.dlg.pushButton.clicked.connect(self.selectOutputFile);

    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('TileNetworkOrder', message)


    def add_action(
        self,
        icon_path,
        text,
        callback,
        enabled_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.triggered.connect(callback)
        action.setEnabled(enabled_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            # Adds plugin icon to Plugins toolbar
            self.iface.addToolBarIcon(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions.append(action)

        return action

    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""

        icon_path = ':/plugins/tile_network_order/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Tile Network Order plugin generates Ordering ID, Burying Depths, Flow Direction etc.'),
            callback=self.run,
            parent=self.iface.mainWindow())

        # will be set False in run()
        self.first_start = True


    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        for action in self.actions:
            self.iface.removePluginMenu(
                self.tr(u'&Tile Network Order'),
                action)
            self.iface.removeToolBarIcon(action)


    def initFolder(self):
        path_project = QgsProject.instance().fileName()
        path_project = path_project[:path_project.rfind("/"):]

        self.folderName = path_project

        self.dlg.lineEdit.setText(self.folderName);

    def selectOutputFile(self):
        folderTmp = QFileDialog.getExistingDirectory(self.dlg,
                                                     self.tr("Select Output Folder "), self.folderName)

        if folderTmp != "":
            self.folderName = folderTmp

        self.dlg.lineEdit.setText(self.folderName);

    def isFileOpened(self, file_path):
        if os.path.exists(file_path):
            try:
                os.rename(file_path, file_path + "_")
                os.rename(file_path + "_", file_path)
                return False
            except OSError as e:
                return True                        
                    
    def combo_layers1(self):                                                       #load raster layers in comboBox 1
        self.dlg.mMapLayerComboBox.clear()
        self.layer = self.dlg.mMapLayerComboBox.currentLayer()
        
    def combo_layers2(self):                                                       #loads tile lies layers in comboBox 2
        self.dlg.mMapLayerComboBox_2.clear()
        self.layer = self.dlg.mMapLayerComboBox_2.currentLayer()
        
    def select_layer_fields(self, tilez):                                          #loads fields in comboBox 2 layer
        self.dlg.mFieldComboBox_1.clear()
        self.dlg.mFieldComboBox_1.setLayer(tilez)
        field = self.dlg.mFieldComboBox_1.setLayer(tilez)    
    
    def select_layer_fields2(self, tilez):                                         #loads fields in comboBox 2 layer
        self.dlg.mFieldComboBox_2.clear()
        self.dlg.mFieldComboBox_2.setLayer(tilez)
        field = self.dlg.mFieldComboBox_2.setLayer(tilez)
        
    def run(self):
        """Run method that performs all the real work"""

        # Get all loaded layers in the Maplayer Canvas
        layers = QgsProject.instance().mapLayers().values()
                       
        # Fetch all currently loaded layer from comboBox 2
        tilez = self.dlg.mMapLayerComboBox_2.currentLayer()                             #new line
        self.dlg.mFieldComboBox_1.setLayer(tilez)                                       #new line
        self.dlg.mMapLayerComboBox_2.layerChanged.connect(self.select_layer_fields)  
                
        # Fetch all currently loaded layer from comboBox 3
        tilez = self.dlg.mMapLayerComboBox_2.currentLayer()                             #new line
        self.dlg.mFieldComboBox_2.setLayer(tilez)                                       #new line
        self.dlg.mMapLayerComboBox_2.layerChanged.connect(self.select_layer_fields2)
        
        # Fetch all currently loaded layer from comboBox 1
        field_dems = self.dlg.mMapLayerComboBox.currentLayer()
        
        # Fetch all currently loaded layer from comboBox 2
        tile_linez = self.dlg.mMapLayerComboBox_2.currentLayer()                        #new line
                
        # Fetch all currently loaded fields in layer from comboBox 2
        #fa_id = self.dlg.mFieldComboBox_1.currentField()
        fa_idz = self.dlg.mFieldComboBox_1.currentField()
        
        # Fetch all currently loaded fields in layer from comboBox 2
        #fa_to = self.dlg.mFieldComboBox_2.currentField()
        fa_toz = self.dlg.mFieldComboBox_2.currentField()               

        # show the dialog
        self.dlg.show()
        
        # Run the dialog event loop
        result = self.dlg.exec_()
        
        # See if OK was pressed
        if result:
            dirName = self.dlg.lineEdit.text().strip()
            if len(dirName) == 0:
                self.iface.messageBar().pushMessage(self.tr("Warning"),
                                                    self.tr("Please select target folder"), level=Qgis.Warning)
                return
            if not (
            self.dlg.checkBox_2.isChecked(), self.dlg.radioButton_2.isChecked(), self.dlg.checkBox_2.isChecked() or self.dlg.checkBox.isChecked()):
                self.iface.messageBar().pushMessage(self.tr("Warning"),
                                                    self.tr(
                                                   "No checkBox is checked. At least check open output file after running algorithm"),
                                                    level=Qgis.Warning)
                return
                
                
            # # # Reference Layer in ComboBox 1
            index = self.dlg.mMapLayerComboBox.currentIndex()
            selection = self.dlg.mMapLayerComboBox.itemData(index)     # DEM layer
            
            # # # Reference Layer in ComboBox 2
            index = self.dlg.mMapLayerComboBox_2.currentIndex()
            selection2 = self.dlg.mMapLayerComboBox_2.itemData(index)  # Tile Lines layer                        
                                                                     
            checkedLayers = QgsProject.instance().layerTreeRoot().checkedLayers()
                                                                                                       
            # search existence of output folder, if not create it
            if not os.path.isdir(self.folderName):
                raise FileNotFoundError(
                    errno.ENOENT, os.strerror(errno.ENOENT), self.folderName)
            
            directory = self.folderName + "/Plugin_Outputs"
            if not os.path.exists(directory) and self.dlg.checkBox_2.isChecked():
                os.makedirs(directory)
                
                
            # Analyses part
            
            # Determine Details from Tile Lines
            tile_layer_used = self.dlg.mMapLayerComboBox.currentLayer()
            #tile_layer_used = self.dlg.mMapLayerComboBox_2.currentLayer()
            
            tile_layer_used = self.iface.activeLayer()
            
            fa_to = tile_layer_used.fields().indexFromName('Tile_TO')
            fa_from = tile_layer_used.fields().indexFromName('Tile_FROM')
            fa_id = tile_layer_used.fields().indexFromName('Tile_ID')
            
            #fields_number = int(tile_layer_used.fields().count())
            fc = tile_layer_used.featureCount()
            
            f = 1
            
            tileorder = ""
            
            #tileorder = {}            

            # Burying Tile Drianage System (MAINS)
            pv = tile_layer_used.dataProvider()
            pv.addAttributes([QgsField("Bury_ID",QVariant.Double, len=5, prec=0)])
                      
            tile_layer_used.updateFields()
                                    
            context = QgsExpressionContext()
            context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(tile_layer_used))

            with edit(tile_layer_used):
                for f in tile_layer_used.getFeatures():
                    
                    if fa_to == "Out":
                       tileorder = fa_id                        
                                                  
                    elif fa_to == tileorder:       
                         tileorder += 1         #tileorder = fa_id  
        
                    else:
                        tileorder = fa_id
                    
                    f['Bury_ID'] = tileorder
                    tile_layer_used.updateFeature(f)                          
            
            for layer in checkedLayers:
                out = None
                if layer.type() == QgsMapLayer.RasterLayer and layer != selection and self.dlg.checkBox_2.isChecked():
                    
                    field_dems = self.dlg.mMapLayerComboBox.currentLayer()
                                        
                    # get extension about the raster
                    filename, file_extension = os.path.splitext(layer.source())
                    output1 = self.folderName + "/Plugin_Outputs/Ordering_" + layer.name() + ".shp"
                    output2 = self.folderName + "/Plugin_Outputs/Profile_" + layer.name() + ".shp"
                    output3 = self.folderName + "/Plugin_Outputs/Renamz_" + layer.name() + ".shp"
                    output4 = self.folderName + "/Plugin_Outputs/Tiles_" + layer.name() + ".shp"   
                                                           
                    # check file isn't opened and is writable
                    version = 0
                    while self.isFileOpened(output1):
                            output1 = self.folderName + "/Plugin_Outputs/Ordering_" + layer.name() + "("+ str(version) + ").shp"
                            version +=1
                            
                    # check file isn't opened and is writable                                        
                    version = 0
                    while self.isFileOpened(output2):
                            output2 = self.folderName + "/Plugin_Outputs/Profile_" + layer.name() + "("+ str(version) + ").shp"
                            version +=1
                    
                    # check file isn't opened and is writable
                    version = 0
                    while self.isFileOpened(output3):
                            output3 = self.folderName + "/Plugin_Outputs/Renamz_" + layer.name() + "("+ str(version) + ").shp"
                            version +=1
                    
                    # check file isn't opened and is writable
                    version = 0
                    while self.isFileOpened(output4):
                            output4 = self.folderName + "/Plugin_Outputs/Tile_" + layer.name() + "("+ str(version) + ").shp"
                            version +=1                                               
                                                                                     
                                     # #001### Getting Unique ID Index for Tile Burying
                    processing.run("native:addautoincrementalfield", {"INPUT": tile_layer_used, "FIELD_NAME": 'ORDERING', "START": 1, "SORT_EXPRESSION": 'Tile_TO', "SORT_ASCENDING": True, "OUTPUT": output1})
                              
                                    # #002### Getting Main Terrain Profiles
                    processing.run("saga:profilesfromlines", {"DEM": field_dems, "LINES": output1, "NAME": 'Bury_ID', "PROFILE": output2})
                                        
                                    # #003### Renaming field name in the mains
                    processing.run('qgis:renametablefield', {"INPUT": output2, "FIELD": 'Z', "NEW_NAME": 'Surf_Elev', "OUTPUT": output3})
                    
                                    # #004### Delecting the Unwanted column
                    processing.run('qgis:deletecolumn', {"INPUT": output3, "COLUMN": ['DIST_SURF'], "OUTPUT": output4})  
                    
                    # Execution Status Report
                    self.iface.messageBar().pushMessage("Success", "Output file written and loaded at " + output1, level=Qgis.Success, duration=3)
                                                                            
                    # load layer
                    if self.dlg.checkBox.isChecked():
                        out = iface.addVectorLayer(output1, "", "ogr")
                        if not out:
                            iface.messageBar().pushMessage(self.tr("Error"), self.tr("Could not load ") +  output1, level=Qgis.Warning)
                            
                    # Execution Status Report
                    self.iface.messageBar().pushMessage("Success", "Output file written and loaded at " + output4, level=Qgis.Success, duration=3)
                    
                    # load layer
                    if self.dlg.checkBox.isChecked():
                        out = iface.addVectorLayer(output4, "", "ogr")
                        if not out:
                            iface.messageBar().pushMessage(self.tr("Error"), self.tr("Could not load ") +  output4, level=Qgis.Warning)                                
            
            # # Get layer object for Mains
            fn = 'output4'
            m_layer = QgsVectorLayer(fn, '', 'ogr')
                                                                                          
            # Determine max and min values from mains
            m_layer= self.iface.activeLayer()
            emex=m_layer.fields().indexFromName('Surf_Elev')
            idex=m_layer.fields().indexFromName('DIST')
                    
            # Determine max Distance values from mains
            max_distance = m_layer.maximumValue(idex)            # highest value in the distances from the mains layer
                               
            #cend = (max_distance + 1)
                               
            cend = max_distance
                                                                          
            # Determine max and min Elevation values from mains
            min_elevation = m_layer.minimumValue(emex)           # lowest value in the elevation from the mains layer
            max_elevation = m_layer.maximumValue(emex)           # highest value in the elevation from the mains layer
                    
            nmasidiya = min_elevation
            
            # Determine other Input Parameters for burying the mains and laterals
            const_slope = self.dlg.doubleSpinBox.value()
            constant_slope = (const_slope * 0.01)
                    
            upper = self.dlg.doubleSpinBox_2.value()
            lower = self.dlg.doubleSpinBox_3.value()
                    
            abs_upper = self.dlg.doubleSpinBox_5.value()
            abs_lower = self.dlg.doubleSpinBox_6.value()
                    
            slope_mx = self.dlg.doubleSpinBox_8.value()
            slope_mn = self.dlg.doubleSpinBox_9.value()
                    
            max_slope = (slope_mx * 0.01) 
            min_slope = (slope_mn * 0.01)
            
            # Burying Tile Drianage System (MAINS)
            pv = m_layer.dataProvider()
            pv.addAttributes([QgsField("bury_tiles",QVariant.Double)])
            
            m_layer.updateFields()
                        
            context = QgsExpressionContext()
            context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(m_layer))
                        
            with edit(m_layer):
                for f in m_layer.getFeatures():
                                     
                    # Plotting with Slope Calculations
                    offset = self.dlg.doubleSpinBox_7.value()
                    
                    # lateral_no = self.dlg.doubleSpinBox_4.value()                                       
                    middle_depth = (upper + lower) / 2
                    start_depth = nmasidiya - middle_depth
                    average_plot = (middle_depth - start_depth) / 4.00
                    
                    # Use Constant Slope
                    if self.dlg.checkBox.isChecked()and 0 < 4.00 < cend:
                        tiledepth = start_depth + 4.00 * constant_slope / 100
                                                                                
                    if '4.00' == '0':   
                        tiledepth = start_depth
                                            
                    elif average_plot < min_slope:
                        tiledepth = start_depth + 4.00 * min_slope
                                    
                    elif tiledepth < f['Surf_Elev'] - abs_lower:
                        tiledepth = f['Surf_Elev'] - abs_lower
                    
                    elif average_plot > max_slope:
                        tiledepth = start_depth + 4.00 * max_slope
                                            
                    elif tiledepth > f['Surf_Elev'] - abs_upper:
                        tiledepth = f['Surf_Elev'] - abs_upper
                    else:
                        tiledepth = middle_depth
                    
                    # # Use Constant Slope
                    # if self.dlg.checkBox.isChecked() and dk > cstart and dk < cend:
                        # tiledepth = start_depth + (dist_diff) * constant_slope / 100
                    
                    f['bury_tiles'] = tiledepth + offset
                    m_layer.updateFeature(f)                                    
                                                                                                                                                                                                                                                                                                                                     
                      
            for layer in checkedLayers:
            
                            # # Export Final Bury Mains File (if displayed and checked)
                if layer.type() == QgsMapLayer.RasterLayer and layer != selection and self.dlg.checkBox_2.isChecked():
                
                    # get extension about the vector
                    filename, file_extension = os.path.splitext(layer.source())
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
                    # output14 = self.folderName + "/Plugin_Results/Bury_Mains_" + layer.name() + ".shp"
                    output5 = self.folderName + "/Buried_Lines_" + layer.name() + ".shp"                                                                                             
                                                               
                    # # check file isn't opened and is writable
                    version = 0
                    while self.isFileOpened(output5):
                        output5 = self.folderName + "/Buried_Lines_" + layer.name() + "(" + str(
                            version) + ").shp"
                        version += 1 
                                               
                    # #005### Getting Individual Lateral Terrain Profiles Layers Seperately
                    processing.run("qgis:splitvectorlayer", {"INPUT": m_layer, "FIELD": 'LINE_ID', "FILE_TYPE": 1, "OUTPUT": output5})                                                 
                    
                    # Execution Status Report
                    self.iface.messageBar().pushMessage("Success", "Output file written and loaded at " + output5, level=Qgis.Success, duration=3)
                                                          
                    # load layer
                    if self.dlg.checkBox.isChecked():
                        out = self.iface.addVectorLayer(output5, "", "ogr")
                        if not out:
                            self.iface.messageBar().pushMessage(self.tr("Error"), self.tr("Could not load ") + output5, level=Qgis.Warning)                                                                                     
                                                     
