"""
 Tool Name:     AreaChange
 Description:  AreaChange uses ESRI's symetrical difference geoprocessing tool to quantify changes in HUCs.
                Retired HUC codes and newly created HUC codes are also flagged.
 Author:    Patrick Longley (plongley@usgs.gov)
 Created:   08/19/2020
 Language: Written in python3 (arcpro).  Modified to also work in python2 (arcmap).
 History:
    10/06/2020 validation
    10/06/2020 constants
    10/06/2020 metdata
    10/13/2020 general review
"""

# IMPORTS
import os
import sys
import arcpy
import wbd_f
import wbd_params

# Constants
PYTHON_VERSION = sys.version_info.major
MEMORY_FPATH = wbd_f.get_memoryfpath(PYTHON_VERSION)
AREA_CHANGE = 'area_change'

# Area_change tool
class AreaChange(object):
    """
    arcpy tool that calculates change in area using ESRI's symetrical differrence function.

    Args:
        polygonfc_old (polygon feature class): Polygon feature class representing the original WBD polygons.
            Must contain a HUC code field.  Ex: huc12 or huc8.
        polygonfc_updated (polygon feat class): Polygon feature class representing the updated WBD polygons.
            Must contain the same HUC code field as polygonfc_old.

    Outputs:
        returns: None
        output parameter: Creates new polygon feature class with, deleted HUCs, newly created HUCs, and
            change in area.

    """
    def __init__(self):
        self.label       = "G6 Area change/HUC check"
        self.description = "Area_change uses ESRI's symetrical difference geoprocessing tool to quantify changes" + \
                           "in polygon area."
        self.callfrom_pyt = True
        self.category = 'Geometry checks'

    def getParameterInfo(self):
        """
        Define the parameters for use in arcmap/pro.
        """
        params = [wbd_params.polygonfc_old,
                  wbd_params.polygonfc_updated,
                  wbd_params.out_fc]
        return params

    def updateMessages(self, params):
        """
        Modify the messages created by internal validation for each tool
        parameter.This method is called after internal validation. 
        """
        if params[0].altered and params[1].altered:
            match = wbd_f.check_hucfieldsmatch([params[0].valueAsText, params[1].valueAsText])
            if not match:
                message = "Both input feature classes must contain the same HUC field."
                params[0].setErrorMessage(message)
                params[1].setErrorMessage(message)

    def create_outputfile(self):
        with arcpy.EnvManager(overwriteOutput=True):
            arcpy.CreateFeatureclass_management(
                os.path.dirname(self.out_fc),
                os.path.basename(self.out_fc),
                "POLYGON",
                spatial_reference=arcpy.Describe(self.polygonfc_updated).spatialReference,
            )
            arcpy.AddField_management(self.out_fc, AREA_CHANGE, field_type='FLOAT')
            arcpy.AddField_management(self.out_fc, self.hucfield, field_type='TEXT', field_length=int(self.hucfield[3:]))

    def create_polygon_fls(self):
        """
        Creates polygon feature layers using the inputed polygon feature classes.
        """
        # create feature layers
        with arcpy.EnvManager(overwriteOutput=True):
            return (
                arcpy.MakeFeatureLayer_management(self.polygonfc_old, 'oldp') ,
                arcpy.MakeFeatureLayer_management(self.polygonfc_updated, 'newp')
            )

    def categorize_hucs(self, old_polygons, new_polygons):
        """
        Get area for old hucs.
        Get maintained hucs.
        Get deleted hucs.
        Get new hucs.
        """
        with arcpy.da.SearchCursor(old_polygons, [self.hucfield, "SHAPE@AREA"]) as cursor:
            oldarea_dict = dict(cursor)
        with arcpy.da.SearchCursor(new_polygons, [self.hucfield]) as cursor:
            new_polygons = {x[0] for x in cursor}
        old_polygons = set(oldarea_dict.keys())
        maintained_hucs = set(oldarea_dict.keys()).intersection(new_polygons)
        deleted_hucs = old_polygons - new_polygons
        new_hucs = new_polygons - old_polygons
        return (
            oldarea_dict,
            maintained_hucs,
            deleted_hucs,
            new_hucs,
        )

    def append_nonmaintained_hucs(self, fc, huc_list, flag_value):
        """
        Append new/deleted polygons to output.
        """
        huc_list = '(' + ','.join([wbd_f.add_quotes(x) for x in huc_list]) + ')'
        where = """{} in {}""".format(arcpy.AddFieldDelimiters(fc, self.hucfield), huc_list)
        with arcpy.EnvManager(overwriteOutput=True):
            fl = arcpy.MakeFeatureLayer_management(fc, 'temp_fl', where_clause=where)
            memory_fc = arcpy.CopyFeatures_management(fl, os.path.join(MEMORY_FPATH, 'temp_fc'))
        arcpy.AddField_management(memory_fc, AREA_CHANGE, field_type='FLOAT')
        arcpy.CalculateField_management(memory_fc,
                                        AREA_CHANGE,
                                        flag_value,
                                        "PYTHON")
        arcpy.Append_management(memory_fc, self.out_fc, schema_type='NO_TEST')

    def select_polygon(self, huc,):
        """
        Select single polygon by attribute using HUC field.
        """
        # select polygon feature layers by attribute
        where_oldpoly = """ {} = '{}' """.format(
            arcpy.AddFieldDelimiters(self.oldp_fl, self.hucfield),
            huc
            )
        where_newpoly = """ {} = '{}' """.format(
            arcpy.AddFieldDelimiters(self.newp_fl, self.hucfield),
            huc
            )
        arcpy.SelectLayerByAttribute_management(self.newp_fl, where_clause=where_newpoly)
        arcpy.SelectLayerByAttribute_management(self.oldp_fl, where_clause=where_oldpoly)

    def calc_symdiff(self,huc):
        """
        Creates polygon representing the symetrical difference.
        """
        with arcpy.EnvManager(overwriteOutput=True):
            symdiff = arcpy.SymDiff_analysis(self.oldp_fl, self.newp_fl, os.path.join(MEMORY_FPATH, 'symdiff'))
            self.symdiff_polygon = arcpy.Dissolve_management(symdiff,
                                                 os.path.join(MEMORY_FPATH, 'dissolve_temp'),
                                                 multi_part="MULTI_PART",
                                                 unsplit_lines="DISSOLVE_LINES")

    def calc_areachange(self, old_area, huc):
        """
        Calculates area change as the symetrical difference divided by the old area.
        """
        arcpy.AddField_management(self.symdiff_polygon, AREA_CHANGE, field_type='FLOAT')
        arcpy.AddField_management(self.symdiff_polygon, self.hucfield, field_type='TEXT', field_length=int(self.hucfield[3:]))
        with arcpy.da.UpdateCursor(self.symdiff_polygon, ["SHAPE@AREA", AREA_CHANGE, self.hucfield]) as cursor:     # No workspace needed for memory workspace?
            for row in cursor:
                row[1] = row[0] / old_area
                row[2] = str(huc)
                cursor.updateRow(row)

    def execute(self, params, messages):
        """
        to calculate fractional change in area.
        """
        # parameters
        if self.callfrom_pyt:
            self.polygonfc_old = params[0].valueAsText
            self.polygonfc_updated = params[1].valueAsText
            self.out_fc = params[2].valueAsText
        else:
            self.polygonfc_old = params[0]
            self.polygonfc_updated = params[1]
            self.out_fc = params[2]
        self.hucfield = wbd_f.get_hucfield(self.polygonfc_old)
        # prep functions
        self.create_outputfile()
        self.oldp_fl, self.newp_fl = self.create_polygon_fls()
        old_area, maintained_hucs, deleted_hucs, new_hucs =  self.categorize_hucs(self.oldp_fl, self.newp_fl)
        if deleted_hucs:
            deleted_hucs = self.append_nonmaintained_hucs(self.polygonfc_old, deleted_hucs, '-9999')
        if new_hucs:
            new_hucs = self.append_nonmaintained_hucs(self.polygonfc_updated, new_hucs, '9999')
        # calculate change in area for each maintained huc
        if maintained_hucs:
            for huc in maintained_hucs:
                self.select_polygon(huc)
                self.calc_symdiff(old_area[huc])
                self.calc_areachange(old_area[huc], huc)
                arcpy.Append_management(self.symdiff_polygon, self.out_fc, schema_type='NO_TEST')

if __name__ == '__main__':
    """
    Execute as standalone script.
    """
    polygonfc_old = r'C:\Users\plongley\Desktop\tooltest_05172021\1908030501_prep\1908030501_data.gdb\initial_data\HU10_1908030501'
    polygonfc_updated = r'C:\Users\plongley\Desktop\tooltest_05172021\MyProject2.gdb\WBDHU10'
    out_fc = r'C:\Users\plongley\Desktop\tooltest_05172021\MyProject2.gdb\WBDHU10_areachange'
    params = (polygonfc_old, polygonfc_updated, out_fc)
    area_change = AreaChange()
    area_change.callfrom_pyt = False
    area_change.execute(params, None)
