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

"""
/***************************************************************************
 PipelineEngineer
                                 A QGIS plugin
 A collection of Pipeline Engineering Tools.
 Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
                              -------------------
        begin                : 2025-12-06
        copyright            : (C) 2025 by Pipeline Engineer
        email                : info@pipelineengineer.com.au
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""

__author__ = 'Pipeline Engineer'
__date__ = '2025-12-06'
__copyright__ = '(C) 2025 by Pipeline Engineer'

# This will get replaced with a git SHA1 when you do a git archive

__revision__ = '$Format:%H$'

import os

from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (
    QgsProcessing,
    QgsFeatureSink,
    QgsProcessingAlgorithm,
    QgsProcessingParameterFeatureSource,
    QgsProcessingParameterFeatureSink,
    QgsProcessingParameterField,
    QgsFeatureRequest,
    QgsProcessingParameterBoolean,
    QgsProcessingParameterNumber,
    QgsProcessingParameterVectorLayer,
    QgsProcessingParameterMultipleLayers,
    QgsProcessingParameterEnum,
    QgsProcessingOutputString,
    QgsProcessingException,
    QgsProcessingOutputMultipleLayers,
    QgsProcessingParameterFileDestination
)

from qgis.PyQt.QtGui import QIcon

from .logic.running_pipeflow import *

class PipeFlowAlgorithm(QgsProcessingAlgorithm):

    INPUT = 'INPUT'
    CHECK_SELECTED = 'CHECK_SELECTED'
    OUTPUT = 'OUTPUT'
    LAYERS = 'LAYERS'
    FLUID = 'FLUID'
    CALC_MODE = 'CALC_MODE'
    MAX_ITER_HYD = 'MAX_ITER_HYD'
    MAX_ITER_THERM = 'MAX_ITER_THERM'
    PRES_TOL = 'PRES_TOL'
    VEL_TOL = 'VEL_TOL'
    TEMP_TOL = 'TEMP_TOL'
    RES_TOL = 'RES_TOL'
    AMBIENT_TEMP = 'AMBIENT_TEMP'
    FRIC_MODEL = 'FRIC_MODEL'
    ALPHA = 'ALPHA'
    NONLINEAR_METHOD = 'NONLINEAR_METHOD'
    UPDATE_HYD_ONLY = 'UPDATE_HYD_ONLY'
    CHECK_CONNECTIVITY = 'CHECK_CONNECTIVITY'
    QUIT_ON_INCONSISTENCY = 'QUIT_ON_INCONSISTENCY'
    USE_NUMBA = 'USE_NUMBA'
    LOAD_LAYERS = 'LOAD_LAYERS'
    
    def initAlgorithm(self, config):

        fluids = ["hgas","lgas","hydrogen","methane","water",
                     "biomethane_pure","biomethane_treated","air"]

        script_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
        user_fluids_path = os.path.join(script_dir, 'user_settings','user_fluids.csv')

        user_fluids_df = pd.read_csv(user_fluids_path)
        
        user_fluids_list = user_fluids_df['name'].tolist()
        
        fluids.extend(user_fluids_list)

        self.addParameter(
            QgsProcessingParameterMultipleLayers(
                name=self.LAYERS,
                description='Select Network Layers',
                layerType=QgsProcessing.TypeVector
            )
        )

        self.addParameter(
            QgsProcessingParameterEnum(
                name=self.FLUID,
                description='Select Fluid',
                options=fluids,
                defaultValue=0  
            )
        )

        self.addParameter(
            QgsProcessingParameterEnum(
                name=self.CALC_MODE,
                description='Calculation Mode',
                options=['hydraulics', 'bidirectional', 'sequential'],
                defaultValue=0  
            )
        )

        self.addParameter(QgsProcessingParameterNumber(
            self.MAX_ITER_HYD, 'Maximum Iterations (Hydraulic)',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=1e5, defaultValue=10000
        ))

        self.addParameter(QgsProcessingParameterNumber(
            self.MAX_ITER_THERM, 'Maximum Iterations (Thermal)',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=1e5, defaultValue=10000
        ))

        self.addParameter(QgsProcessingParameterNumber(
            self.PRES_TOL, 'Pressure Error Tolerance',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=1, defaultValue=1e-4
        ))

        self.addParameter(QgsProcessingParameterNumber(
            self.VEL_TOL, 'Velocity Error Tolerance',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=1, defaultValue=1e-4
        ))

        self.addParameter(QgsProcessingParameterNumber(
            self.TEMP_TOL, 'Temperature Error Tolerance',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=1, defaultValue=1e-4
        ))

        self.addParameter(QgsProcessingParameterNumber(
            self.RES_TOL, 'Residual Tolerance',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=1, defaultValue=1e-3
        ))

        self.addParameter(QgsProcessingParameterNumber(
            self.AMBIENT_TEMP, 'Ambient Temperature',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=100000, defaultValue=293.15
        ))

        self.addParameter(
            QgsProcessingParameterEnum(
                name=self.FRIC_MODEL,
                description='Friction Model',
                options=['nikuradse', 'colebrook'],
                defaultValue=1
            )
        )

        self.addParameter(QgsProcessingParameterNumber(
            self.ALPHA, 'Alpha',
            type=QgsProcessingParameterNumber.Double,
            minValue=0, maxValue=1e5, defaultValue=1
        ))

        self.addParameter(
            QgsProcessingParameterEnum(
                name=self.NONLINEAR_METHOD,
                description='Nonlinear Method',
                options=['constant', 'automatic'],
                defaultValue=0
            )
        )

        self.addParameter(QgsProcessingParameterBoolean(
            self.UPDATE_HYD_ONLY, 'Update Hydraulic Matrix Only?',
            defaultValue=False
        ))

        self.addParameter(QgsProcessingParameterBoolean(
            self.CHECK_CONNECTIVITY, 'Check Connectivity?',
            defaultValue=True
        ))

        self.addParameter(QgsProcessingParameterBoolean(
            self.QUIT_ON_INCONSISTENCY, 'Quit on Inconsistency?',
            defaultValue=False
        ))

        self.addParameter(QgsProcessingParameterBoolean(
            self.USE_NUMBA, 'Use Numba?',
            defaultValue=True
        ))

        self.addParameter(QgsProcessingParameterBoolean(
            self.LOAD_LAYERS, 'Load Layers?',
            defaultValue=True
        ))

        # Output: list of layer IDs
        self.addParameter(QgsProcessingParameterFileDestination(
                self.OUTPUT,
                'Output GeoPackage',
                fileFilter='GeoPackage (*.gpkg)'
            )
        )

    # -------------------------------------------------------
    # PROCESS
    # -------------------------------------------------------

    def processAlgorithm(self, parameters, context, feedback):

        # MULTIPLE LAYERS
        layers = self.parameterAsLayerList(parameters, self.LAYERS, context)

        if not layers:
            raise QgsProcessingException("No valid input layers selected.")

        # Extract all parameters
        fluid_index = self.parameterAsInt(parameters, self.FLUID, context)
        mode_index = self.parameterAsInt(parameters, self.CALC_MODE, context)
        max_iter_hyd = self.parameterAsDouble(parameters, self.MAX_ITER_HYD, context)
        max_iter_therm = self.parameterAsDouble(parameters, self.MAX_ITER_THERM, context)
        tol_p = self.parameterAsDouble(parameters, self.PRES_TOL, context)
        tol_m = self.parameterAsDouble(parameters, self.VEL_TOL, context)
        tol_T = self.parameterAsDouble(parameters, self.TEMP_TOL, context)
        tol_res = self.parameterAsDouble(parameters, self.RES_TOL, context)
        ambient_temp = self.parameterAsDouble(parameters, self.AMBIENT_TEMP, context)
        friction_model_index = self.parameterAsInt(parameters, self.FRIC_MODEL, context)
        alpha = self.parameterAsDouble(parameters, self.ALPHA, context)
        nonlinear_method_index = self.parameterAsInt(parameters, self.NONLINEAR_METHOD, context)
        only_update = self.parameterAsBool(parameters, self.UPDATE_HYD_ONLY, context)
        check_conn = self.parameterAsBool(parameters, self.CHECK_CONNECTIVITY, context)
        quit_incons = self.parameterAsBool(parameters, self.QUIT_ON_INCONSISTENCY, context)
        use_numba = self.parameterAsBool(parameters, self.USE_NUMBA, context)
        
        load_layers = self.parameterAsBool(parameters, self.LOAD_LAYERS, context)


        fluids = ["hgas","lgas","hydrogen","methane","water",
                     "biomethane_pure","biomethane_treated","air"]

        script_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
        user_fluids_path = os.path.join(script_dir, 'user_settings','user_fluids.csv')

        user_fluids_df = pd.read_csv(user_fluids_path)
        
        user_fluids_list = user_fluids_df['name'].tolist()
        
        fluids.extend(user_fluids_list)

        modes=['hydraulics', 'bidirectional', 'sequential']


        friction_models =['nikuradse', 'colebrook']

        nonlinear_methods =['constant', 'automatic']

        fluid = fluids[fluid_index]
        mode = modes[mode_index]
        friction_model = friction_models[friction_model_index]
        nonlinear_method = nonlinear_methods[nonlinear_method_index]

        settings = {'max_iter_hyd': max_iter_hyd, 'max_iter_therm': max_iter_therm, 
            'tol_p': tol_p, 'tol_m': tol_m, 'tol_T': tol_T, 'tol_res': tol_res, 
            'ambient_temperature': ambient_temp, 
            'friction_model': friction_model, 'alpha': alpha, 'nonlinear_method': nonlinear_method, 'mode': mode, 
            'only_update_hydraulic_matrix': only_update, 'check_connectivity': check_conn, 
            'quit_on_inconsistency_connectivity': quit_incons, 'use_numba': use_numba}

        gpkg_path = self.parameterAsFileOutput(
                                                parameters,
                                                self.OUTPUT,
                                                context
                                            )

        # Run your solver
        feedback.pushInfo("Running pipeflow solver…")

        result_layers = run_pipeflow(
            layers=layers,
            fluid=fluid,
            args=settings,
            load_layers=load_layers
        )

        processing.run("native:package", 
                    {'LAYERS':result_layers,
                     'OUTPUT':gpkg_path,
                     'OVERWRITE':False,
                     'SAVE_STYLES':True,
                     'SAVE_METADATA':True,
                     'SELECTED_FEATURES_ONLY':False,
                     'EXPORT_RELATED_LAYERS':False}
                   )

        return {}
        
    # -------------------------------------------------------

    def name(self):
        return 'pandapipes_pipeflow'

    def displayName(self):
        return self.tr('Pipeflow')

    def group(self):
        return self.tr(self.groupId())

    def tr(self, string):
        return QCoreApplication.translate('Processing', string)

    def shortHelpString(self):
        return self.tr("Runs a pandapipes pipeflow for selected layers.")

    def createInstance(self):
        return PipeFlowAlgorithm()
