"""
 Tool Name:  ReferenceGNISISID
 Description: Checks/fills GNISReference_IDs
 Author: Patrick Longley (plongley@usgs.gov)
 Created: 09/17/2020
 Language: Written in python3 (arcpro). Does not work in python2 (arcmap).
 History:
    metadata 20201005,
    fields as constants 20201005,
    check fields in update parameters 20201005
    general review 10/13/2020
    objectid 10/26/2020
"""

import os
import sys
import re
import arcpy
import wbd_f
import wbd_params
import wbd_c

# Constants
PYTHON_VERSION = sys.version_info.major
MEMORY_FPATH = wbd_f.get_memoryfpath(PYTHON_VERSION)
GNISID_FLAG = wbd_c.F_REFERENCEGNIS_IDS + wbd_c.FLAG_SUFFIX
GNISID_CALC = wbd_c.F_REFERENCEGNIS_IDS + wbd_c.CALC_SUFFIX
NAME_FLAG = wbd_c.F_NAME + wbd_c.FLAG_SUFFIX
AUTO_FLAGS = wbd_f.AUTO_FLAGS
WHITESPACE = r'^\s*$'

class ReferenceGNISISID(object):
    def __init__(self):
        """
        Initialize variables
        """
        self.label       = "7) Polygon: ReferenceGNIS_IDs Check"
        self.description = "Check or Fill GNISReference_Ids"
        self.callfrom_pyt = True
        self.category = 'Attribution'

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

    def updateMessages(self, params):
        """
        Modify the messages created by internal validation for each tool
        parameter.This method is called after internal validation.
        """
        MESSAGE = "Feature class must contain the following fields: {}."
        if params[0].altered:
            try:
                polygon_fcs = params[0].valueAsText.split(';')
            except AttributeError:
                pass
            else:
                required_fields = [wbd_c.F_NAME, wbd_c.F_REFERENCEGNIS_IDS]
                for fc in polygon_fcs:
                    if not wbd_f.check_fieldsexist(fc, required_fields):
                        params[0].setErrorMessage(MESSAGE.format(', '.join(required_fields)))
        if params[1].altered:
            if not wbd_f.check_fieldsexist(params[1].valueAsText, [wbd_c.F_FEATURE_NAME]):
                params[1].setErrorMessage(MESSAGE.format(wbd_c.F_FEATURE_NAME))

    def creategnis_dict(self):
        """
        Create dictionary where the key is the GNIS objectid and the value is the feature id.
        Leading 0 is added to the FEATURE_ID.
        """
        self.gnis_dict = wbd_f.create_dict(self.gnis_fc, self.GNIS_OID, wbd_c.F_FEATURE_ID)
        self.gnis_dict = {key:str(value) for (key,value) in self.gnis_dict.items()}

    def getname_list(self, watershed_name):
        """
        Get list of gnis_names from the watershed name
        """
        prefix_removed = re.sub(wbd_f.prefix_regex2, '', watershed_name)
        name_list = re.sub(r'^\d+$|^\d+-|Frontal\s*', '', prefix_removed).strip('-').split('-')
        arcpy.AddMessage(name_list)
        return [n for n in name_list if n]

    def calcnear_byname(self, gnis_name, temp_id):
        """
        Find the nearest GNIS point that has the correct name.
        """
        # In SQL Query single quotes need to be escaped by using two singles quotes
        # https://community.esri.com/t5/python-questions/selecting-from-a-fgdb-where-the-item-has/td-p/52082
        gnis_name = gnis_name.replace("'", "''")
        gnis_where = """ {} = '{}'""".format(arcpy.AddFieldDelimiters(self.gnis_fc, wbd_c.F_FEATURE_NAME), gnis_name)
        wbd_where = """ {} = {}""".format(arcpy.AddFieldDelimiters(self.updatedpolygons_fc, self.TEMPID), temp_id)
        # select single feature in wbd copy
        arcpy.SelectLayerByAttribute_management(self.wbd_fl, where_clause=wbd_where)
        # select features with the correct name
        arcpy.SelectLayerByAttribute_management(self.gnis_fl, where_clause=gnis_where)
        # get objectid of closest feature with correct name added into "NEAR_FID" field
        arcpy.Near_analysis(self.wbd_fl, self.gnis_fl)

    def getgnis_featureid(self):
        """
        Get GNIS feature ID from dictionary using objectid as key.
        """
        with arcpy.da.SearchCursor(self.wbd_fl, ['NEAR_FID']) as cursor:
            for row in cursor:
                try:
                    return str(int(float(self.gnis_dict[row[0]])))
                except KeyError:
                    return None

    def copy_fc(self, fc):
        """
        Create copy This tool  This allows for manipulation during edit session.
        """
        self.TEMPID = 'tempid'
        with arcpy.EnvManager(overwriteOutput=True):
            arcpy.AddField_management(fc, self.TEMPID, 'LONG')
            arcpy.CalculateField_management(fc, self.TEMPID, '!{}!'.format(self.WBD_OID), 'PYTHON')
            wbdfc_copy = arcpy.CopyFeatures_management(fc, os.path.join(MEMORY_FPATH, 'wbd_fc'))
            return arcpy.MakeFeatureLayer_management(wbdfc_copy, 'wbd_fl')

    def add_fields(self, fc):
        fields = [f.name for f in arcpy.ListFields(fc)]
        if GNISID_CALC not in fields:
            arcpy.AddField_management(fc, GNISID_CALC, 'TEXT', field_length=50)
        if GNISID_FLAG not in fields:
            arcpy.AddField_management(fc, GNISID_FLAG, 'TEXT', wbd_c.FLAG_LENGTH)
            arcpy.CalculateField_management(fc, GNISID_FLAG, "'{}'".format(wbd_c.CORRECT_FLAG), 'PYTHON')
        if NAME_FLAG not in fields:
            arcpy.AddField_management(fc, NAME_FLAG, 'TEXT', wbd_c.FLAG_LENGTH)
            arcpy.CalculateField_management(fc, NAME_FLAG, "'{}'".format(wbd_c.CORRECT_FLAG), 'PYTHON')

    def execute(self, params, messages):
        """
        Execute above functions to fill/check the gnis ReferenceGNISISID field.
        """
        # parameters
        if self.callfrom_pyt:
            self.updatedpolygons_fc = params[0].valueAsText
            self.gnis_fc = params[1].valueAsText
            self.fill_missing = params[2].value
        else:
            self.updatedpolygons_fc = params[0]
            self.gnis_fc = params[1]
            self.fill_missing = params[2]
        try:
            self.updatedpolygons_fc = self.updatedpolygons_fc.split(';')
        except AttributeError:
            pass
        self.WBD_OID = arcpy.Describe(self.updatedpolygons_fc[0]).OIDFieldName
        self.GNIS_OID = arcpy.Describe(self.gnis_fc).OIDFieldName
        # prep gnis data
        self.creategnis_dict()
        with arcpy.EnvManager(overwriteOutput=True):
            self.gnis_fl = arcpy.MakeFeatureLayer_management(self.gnis_fc, 'gnis_fl')
        # loop through each inputed polygon feature class.
        for fc in self.updatedpolygons_fc:
            # copy featureclass
            self.wbd_fl = self.copy_fc(fc)
            self.add_fields(fc)
            # update cursor
            workspace = os.path.dirname(wbd_f.get_fpath(fc))
            if '.gdb' in workspace and not workspace.endswith('.gdb'):
                workspace = os.path.dirname(workspace)
            with arcpy.da.Editor(workspace) as edit:
                # loop through entire feature class
                fields = ['OID@', wbd_c.F_NAME, NAME_FLAG, wbd_c.F_REFERENCEGNIS_IDS, GNISID_CALC, GNISID_FLAG]
                with arcpy.da.UpdateCursor(fc, fields) as cursor:
                    for row in cursor:
                        # name is a HUC code >>> gnisid should be null
                        if re.match(r'^\d+$', row[1]):
                            row[4] = None
                            if row[3] == None:
                                new_flag = wbd_c.CORRECT_FLAG
                            elif re.match(WHITESPACE, row[3]):
                                new_flag = wbd_c.NODATA_FLAG
                            else:
                                new_flag = wbd_c.WRONG_FLAG
                            if row[5] in AUTO_FLAGS:
                                row[5] = new_flag
                        # name is wrong or missing
                        elif row[2] in (wbd_c.NODATA_FLAG, wbd_c.WRONG_FLAG) or not row[1] or re.match(WHITESPACE, row[1]):
                            row[4] = None
                            row[5] = wbd_f.update_flag(row[5], wbd_c.NOTCHECKED_FLAG)
                        # fill in GNISID_CALC field
                        else:
                            gnisids_list = []
                            name_list = self.getname_list(row[1])
                            for name in name_list:
                                self.calcnear_byname(name, row[0])
                                gnisid = self.getgnis_featureid()
                                gnisids_list.append(gnisid)
                            if None not in gnisids_list:
                                new_gnisid = ','.join(gnisids_list)
                                row[4] = new_gnisid
                                if not row[3] or re.match(WHITESPACE, row[3]):
                                    if self.fill_missing:
                                        new_flag = wbd_c.FILLED_FLAG
                                        row[3] = new_gnisid
                                        if row[5] in wbd_f.AUTO_FLAGS:
                                            row[5] = new_flag  # overwrite nodata flag with filled flag
                                    else:
                                        new_flag = wbd_c.NODATA_FLAG
                                elif row[3] != row[4]:
                                    new_flag = wbd_c.WRONG_FLAG
                                else:
                                    new_flag = wbd_c.CORRECT_FLAG
                                row[5] = wbd_f.update_flag(row[5], new_flag)
                            else:
                                # could not find all the names in the gnis featureclass
                                row[4] = None
                                row[5] = wbd_f.update_flag(row[5], wbd_c.NOTCHECKED_FLAG)
                        cursor.updateRow(row)
            arcpy.DeleteField_management(fc, self.TEMPID)



if __name__ == '__main__':
    """
    Execute as standalone script.
    """
    gnis_id = ReferenceGNISISID()
    gnis_id.callfrom_pyt = False
    arcpy.env.workspace = r'D:\OneDrive - DOI\WBD\ReferenceGNIS_IDs\Data\WBD_04_HU2_GDB\WBD_04_HU2_GDB.gdb\WBD'
    polygons = [r'WBDHU12']
    gnis = r"B:\BaseData\GNIS_National_2023.gdb\GNIS\GNIS_National"
    fill_missing = False
    params = (polygons, gnis, fill_missing)
    gnis_id.execute(params, None)

