#GOF script
# -*- coding: utf-8 -*-

import arcpy
import os

import pandas as pd
import numpy as np


class Toolbox:
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        self.label = "Goodness of Fit"
        self.alias = "Goodness of Fit"

        # List of tool classes associated with this toolbox
        self.tools = [GoF_calc]


class helpers(object):
    
    # This function converts backslash (accepted through the ArcGIS tool) to forwardslash (needed in python script) in a path
    def convert_backslash_forwardslash(self,inText):
        # inText: input path
        
        inText = fr"{inText}"
        if inText.find('\t'):
            inText = inText.replace('\t', '\\t')
        elif inText.find('\n'):
            inText = inText.replace('\n', '\\n')
        elif inText.find('\r'):
            inText = inText.replace('\r', '\\r')

        inText = inText.replace('\\','/')
        
        return inText
    
    
    def field_removal(self,inputFeatures,keepFields):
        
        allFields = [field.name for field in arcpy.ListFields(inputFeatures)]
        
        for f in allFields:
            if f in keepFields:
                pass
            else:
                try:
                    arcpy.DeleteField_management(inputFeatures, f)
                except arcpy.ExecuteError:
                    pass
        
        return inputFeatures
    
    
    def cleaning(self,workspace):
        
        arcpy.env.workspace = workspace
        try:
            featureclasses = arcpy.ListFeatureClasses()
            for fc in featureclasses:
                arcpy.Delete_management(fc)
        except:
            arcpy.AddWarning('Could not delete temporary data ' + str(fc))
        try:
            rasters = arcpy.ListRasters()
            for ra in rasters:
                arcpy.Delete_management(ra)
        except:
            arcpy.AddWarning('Could not delete temporary data ' + str(ra))
        
        try:
            arcpy.Delete_management(workspace)
        except:
            arcpy.AddWarning('Could not delete temporary folder ' + str(workspace[:-1]))
            
        return
    

class GoF_calc:
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = "GoF_calculation"
        self.description = ""

    def getParameterInfo(self):
        """Define the tool parameters."""
        parameters = None
        
        param0 = arcpy.Parameter(
            displayName="Input first shapefile",
            name="in_poly_1",
            datatype="GPFeatureLayer",
            parameterType="Required",
            direction="Input")
        
        param1 = arcpy.Parameter(
            displayName="Input second shapefile",
            name="in_poly_2",
            datatype="GPFeatureLayer",
            parameterType="Required",
            direction="Input")
        
        param2 = arcpy.Parameter(
            displayName="Workspace",
            name="workspace",
            datatype="DEWorkspace",
            parameterType="Required",
            direction="Input")  
        
        param3 = arcpy.Parameter(
            displayName="Output Feature name",
            name="out_feat",
            datatype="GPString",
            parameterType="Required",
            direction="Output")
        
        param4 = arcpy.Parameter(
            displayName="Do you want to delete the temporary files?",
            name="del_temp",
            datatype="GPBoolean",
            parameterType="Optional",
            direction="Input")
        
        parameters = [param0, param1, param2, param3, param4]
    
        return parameters

    def isLicensed(self):
        """Set whether the tool is licensed to execute."""
        return True

    def updateParameters(self, parameters):
        """Modify the values and properties of parameters before internal
        validation is performed.  This method is called whenever a parameter
        has been changed."""
        return

    def updateMessages(self, parameters):
        """Modify the messages created by internal validation for each tool
        parameter. This method is called after internal validation."""
        
        if parameters[0].valueAsText is not None:
            folder = os.path.basename(os.path.dirname(parameters[0].valueAsText))
            #desc = arcpy.Describe(gdb)
            if folder.lower().endswith(('.gdb', '.mdb')):
                parameters[0].setErrorMessage("Geodatabases cannot be used in this version of the Toolbox")

        if parameters[1].valueAsText is not None:
            folder = os.path.basename(os.path.dirname(parameters[1].valueAsText))
            #desc = arcpy.Describe(gdb)
            if folder.lower().endswith(('.gdb', '.mdb')):
                parameters[1].setErrorMessage("Geodatabases cannot be used in this version of the Toolbox")
        
        if parameters[2].valueAsText is not None:
            folder = os.path.basename(os.path.dirname(parameters[2].valueAsText))
            #desc = arcpy.Describe(gdb)
            if folder.lower().endswith(('.gdb', '.mdb')):
                parameters[2].setErrorMessage("Geodatabases cannot be used in this version of the Toolbox")
        
        return

    def execute(self, parameters, messages):
        """The source code of the tool."""
        
        arcpy.env.overwriteOutput = True
        in_poly_1 = parameters[0].valueAsText
        in_poly_2 = parameters[1].valueAsText
        workspace = parameters[2].valueAsText
        out_feat = parameters[3].valueAsText
        del_temp = parameters[4].valueAsText
        
        # enable the helper functions
        helper = helpers()
        arcpy.env.parallelProcessingFactor = "50%"
        
        arcpy.AddMessage('Reading the variables ...') 
        in_poly_1 = helper.convert_backslash_forwardslash(in_poly_1)
        in_poly_2 = helper.convert_backslash_forwardslash(in_poly_2)
        workspace = helper.convert_backslash_forwardslash(workspace)
        
        if out_feat[-4:] != '.shp':
            out_feat = out_feat + '.shp'
            
        if in_poly_1.rfind("/") < 0:
            aprx = arcpy.mp.ArcGISProject("CURRENT")
            m = aprx.activeMap            
            for lyr in m.listLayers():
                if lyr.isRasterLayer:
                    if in_poly_1 == lyr.name: 
                        in_poly_1 = helper.convert_backslash_forwardslash(lyr.dataSource)
                        
        if in_poly_2.rfind("/") < 0:
            aprx = arcpy.mp.ArcGISProject("CURRENT")
            m = aprx.activeMap            
            for lyr in m.listLayers():
                if lyr.isFeatureLayer:
                    if in_poly_2 == lyr.name: 
                        in_poly_2 = helper.convert_backslash_forwardslash(lyr.dataSource)
        
        if parameters[2].value is not None:
            if workspace.rfind("/") < 0:
                aprx = arcpy.mp.ArcGISProject("CURRENT")
                m = aprx.activeMap            
                for lyr in m.listLayers():
                    if lyr.isRasterLayer:
                        if workspace == lyr.name: 
                            workspace = helper.convert_backslash_forwardslash(lyr.dataSource)
        
                
        tempWS = workspace + "\\temp\\"
        arcpy.env.overwriteOutput = True
        if not os.path.exists(tempWS):
            os.mkdir(tempWS)
                      
        arcpy.AddMessage('Creating the geodatabase ...') 
        arcpy.CreateFileGDB_management(tempWS[:-1], "gof_calc.gdb")
        
        arcpy.FeatureClassToGeodatabase_conversion(in_poly_1, tempWS + "gof_calc.gdb")
        feat_1 = os.path.basename(in_poly_1)
        arcpy.FeatureClassToGeodatabase_conversion(in_poly_2, tempWS + "gof_calc.gdb")
        feat_2 = os.path.basename(in_poly_2[:-4])
        
        arcpy.AddMessage(feat_1)
        arcpy.AddMessage(feat_2)
        arcpy.env.workspace = tempWS + "gof_calc.gdb"
        
        #INTERSECTION OF THE TWO POLYGONS
        inFeatures = [feat_1, feat_2]
        arcpy.analysis.Intersect(inFeatures, "intersect")
        
        arcpy.AddField_management(feat_1, "Area_a", "DOUBLE", "8", "6")
        arcpy.management.CalculateGeometryAttributes(feat_1, "Area_a AREA", "", "SQUARE_METERS")
        arcpy.AddField_management(feat_2, "Area_b", "DOUBLE", "8", "6")
        arcpy.management.CalculateGeometryAttributes(feat_2, "Area_b AREA", "", "SQUARE_METERS")
        arcpy.AddField_management("intersect", "Area_i", "DOUBLE", "8", "6")
        arcpy.management.CalculateGeometryAttributes("intersect", "Area_i AREA", "", "SQUARE_METERS")

        

        #SPATIAL JOIN OF A and B
        arcpy.SpatialJoin_analysis(feat_1, feat_2, "sj_polys",
                           "JOIN_ONE_TO_MANY","KEEP_ALL", "","INTERSECT")
        sel1 = arcpy.management.SelectLayerByAttribute("sj_polys", "NEW_SELECTION",
                                                       "Join_Count = 0")
        arcpy.management.CopyFeatures(sel1, "sj_polys_um")
        
        #SPATIAL JOIN OF A and C
        arcpy.SpatialJoin_analysis(feat_1, "intersect", "sj_inters",
                   "JOIN_ONE_TO_MANY","KEEP_ALL", "","INTERSECT")
        sel2 = arcpy.management.SelectLayerByAttribute("sj_inters", "NEW_SELECTION", 
                                                       "Join_Count = 0")
        arcpy.management.CopyFeatures(sel2, "sj_inters_um")
                        
        #merge unmatched features
        
        arcpy.management.Merge(["sj_polys_um", "sj_inters_um"], "sj_all_unmatched")
        
        # Join the feature layer to a table
        joined_table = arcpy.management.AddJoin("sj_polys", "TARGET_FID", "sj_inters", 
                                                    "TARGET_FID")
        arcpy.management.CopyFeatures(joined_table, "joined_table")
        
        keepFields = ["sj_polys_TARGET_FID", "sj_polys_JOIN_FID", "sj_polys_Area_a", 
                      "sj_polys_Area_b", "sj_inters_TARGET_FID", "sj_inters_JOIN_FID",
                      "sj_inters_Area_i"]
        helper.field_removal("joined_table", keepFields)
        
        
        arcpy.management.AlterField("joined_table", "sj_polys_TARGET_FID", "TARG_FID_AB", "TARG_FID_AB")
        arcpy.management.AlterField("joined_table", "sj_polys_JOIN_FID", "JOIN_FID_AB", "JOIN_FID_AB")
        arcpy.management.AlterField("joined_table", "sj_polys_Area_a", "Area_a", "Area_a")
        arcpy.management.AlterField("joined_table", "sj_polys_Area_b", "Area_b", "Area_b")
        arcpy.management.AlterField("joined_table", "sj_inters_TARGET_FID", "TARG_FID_AC", "TARG_FID_AC")
        arcpy.management.AlterField("joined_table", "sj_inters_JOIN_FID", "JOIN_FID_AC", "JOIN_FID_AC")
        arcpy.management.AlterField("joined_table", "sj_inters_Area_i", "Area_i", "Area_i")
        
        
        #GOF calculation
        arcpy.AddField_management("joined_table", "GoF_base", "DOUBLE", "8", "6")
        arcpy.CalculateField_management("joined_table", "GoF_base", '(!Area_i!/!Area_a!)*(!Area_i!/!Area_b!)', "PYTHON3")
        
        arcpy.AddMessage('Saving official shapefile and csv ...')
        arcpy.conversion.ExportFeatures("joined_table", workspace + "/" + out_feat, "", "NOT_USE_ALIAS")
        arcpy.conversion.ExportFeatures("sj_all_unmatched", workspace + "/" + "non_matching.shp", "", "NOT_USE_ALIAS")
        
        
        arcpy.conversion.ExportTable("joined_table", workspace + "/" + "joined_table" + ".csv")
        df = pd.read_csv(workspace  + "/" + "joined_table" + ".csv")
        
        arcpy.AddMessage(df)
        arcpy.AddMessage(df['Area_i'])
        # Calculate basic gof for each row
        #df['Area_i'].replace(r'\s+', np.nan, regex=True, inplace = True)
        #df.dropna(subset=['Area_i'])
        #df['GoF_base'] = (df['Area_i'] / df['Area_a']) * (df['Area_i'] / df['Area_b'])
        
        # Sum earlygof for each TARGET_FID
        gof_sum = df.groupby('TARG_FID_AB')['GoF_base'].sum().reset_index()
        gof_sum.columns = ['TARG_FID_AB', 'GoF_summed']
        
        # Create the range for GOF values
        gof_thresholds = np.arange(0, 1.01, 0.01)
        
        # Calculate the number of polygons with GOF >= each threshold
        polygon_counts = [sum(gof_sum['GoF_summed'] >= threshold) for threshold in gof_thresholds]
        
        # Calculate percentages
        total_polygons = len(gof_sum)
        percentages = [(count / total_polygons) * 100 for count in polygon_counts]
        
        # Calculate area under the curve
        areas = [percentages[i] + (percentages[i-1] - percentages[i]) / 2 for i in range(1, len(percentages))]
        areas.insert(0, percentages[0])  # Insert the first area which is A1
        
        # Create a DataFrame to store the results
        results_df = pd.DataFrame({
            'GOF_Threshold': gof_thresholds,
            'Polygon_Counts': polygon_counts,
            'Percentages': percentages,
            'Areas': areas
        })
        
        unique_gof = sum(areas) / 10000
        
        # Add the unique GOF value as a single-row DataFrame and concatenate
        unique_gof_df = pd.DataFrame({'Unique_GOF': [unique_gof]})
        results_df = pd.concat([results_df, unique_gof_df], axis=1)
                
        results_df.to_csv(workspace + "/" + "gof_results_table" + ".csv", index=False)
        
        #Delete temporary files if requested
        if str(del_temp) == 'true':
            arcpy.AddMessage('Deleting temporary files ...')
            helper.cleaning(tempWS[:-1])  
        
        return

    def postExecute(self, parameters):
        """This method takes place after outputs are processed and
        added to the display."""
        return
