"""
 Tool Name:  NameFill
 Description: This tool uses the results of NameCheck to assist 
    the user in changing the names of incorrectly named watersheds.
 Created: 08/27/2020
 Language: Written in python3 (arcpro). Modified to work in arcmap (python2)
 History: 
    Proofread 09/10/2020
    metadata 20201005
    constants 20201006
    objectid issues 10/21/2020
 TODO: Naming suggestions for frontal watersheds.
 TODO: If you add a prefixed feature update attribute table to reflect this and prevent future prefix conflicts (not that hard)
 TODO: keep old name autofill >>> error >>> requires bonus clicking
"""

import sys
import arcpy
import numpy as np
import wbd_f
import wbd_params
import wbd_c

# PYTHON 2/3 issues
PYTHON_VERSION = sys.version_info.major
MEMORY_FPATH = wbd_f.get_memoryfpath(PYTHON_VERSION)
NAME_FLAG = wbd_c.F_NAME + wbd_c.FLAG_SUFFIX


class NameUpdate(object):
    """
    This tool uses the results of NameCheck to assist
    the user in changing the names of incorrectly named watersheds.
    This tool only works as a tool in arcpro (not as a standalone script).

    Args:
        issue_table (ESRI table): Table generated by NameCheck that flags incorrectly named watersheds.
        incorrect_name (str): Incorrect name to be changed.
        custom_newname (str): Optional user defined new name.
        potential_names (str): Auto generated list of name options for replacing the selected incorrect name.
        polygonfc_updated (polygon feature class): The polygon feature class that will be updated in place.

    Outputs:
        returns: None
        output parameter: polygonfc_updated are both modified in place.
    """

    def __init__(self):
        """
        Initialize variables
        """
        self.label = "6) Polygon: Name Update"
        self.description = (
            "Provides suggestions for correct names and updates the WBD feature class with correct names."
        )
        self.callfrom_pyt = True
        self.category = "Attribution"

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

    def zoomto_feature(self, polygon_fl, huc, old_name):
        """
        Zoom to feature
        """
        where = """{} = {} AND {} = {}""".format(
            arcpy.AddFieldDelimiters(polygon_fl, self.HUCFIELD),
            "'{}'".format(huc),
            arcpy.AddFieldDelimiters(polygon_fl, wbd_c.F_NAME),
            "'{}'".format(old_name),
        )
        with arcpy.EnvManager(addOutputsToMap=False):
            arcpy.SelectLayerByAttribute_management(polygon_fl, "NEW_SELECTION", where_clause=where)
        if PYTHON_VERSION == 3:
            try:
                new_extent = [r[0].extent for r in arcpy.da.SearchCursor(polygon_fl, ["SHAPE@"])][0]
                aprx = arcpy.mp.ArcGISProject("CURRENT")
                current_map = aprx.activeView  # map object
                current_map.camera.setExtent(new_extent)
            except:
                pass
        elif PYTHON_VERSION == 2:
            try:
                new_extent = [r[0] for r in arcpy.da.SearchCursor(polygon_fl, ["SHAPE@"])][0]
                mxd = arcpy.mapping.MapDocument("CURRENT")
                df = arcpy.mapping.ListDataFrames(mxd)[0]
                df.extent = new_extent
            except:
                pass

    def updateMessages(self, params):
        """Modify the messages created by internal validation for each tool
        parameter.  This method is called after internal validation."""
        # check feature class is useable
        if params[0].altered:
            fc = params[0].ValueAsText
            self.HUCFIELD = wbd_f.get_hucfield(fc)
            required_fields = [
                wbd_c.F_NAME,
                NAME_FLAG,
                wbd_c.F_MAJORFEATURE,
                wbd_c.F_FRONTALFEATURE,
                wbd_c.F_PREFIX,
                wbd_c.F_POTENTIALNAMES,
            ]
            if not wbd_f.check_fieldsexist(fc, required_fields) or not self.HUCFIELD:
                message = "Feature class must contain the following fields: {}.".format(
                    ", ".join(required_fields + ["huc"])
                )
                params[0].setErrorMessage(message)
            else:
                # create df
                where_poly = """{} <> '' AND {} <> ''  AND {} <> ''  AND {} IS NOT NULL AND {} IS NOT NULL AND {} IS NOT NULL""".format(
                    arcpy.AddFieldDelimiters(fc, self.HUCFIELD),
                    arcpy.AddFieldDelimiters(fc, wbd_c.F_HUTYPE),
                    arcpy.AddFieldDelimiters(fc, wbd_c.F_NAME),
                    arcpy.AddFieldDelimiters(fc, self.HUCFIELD),
                    arcpy.AddFieldDelimiters(fc, wbd_c.F_HUTYPE),
                    arcpy.AddFieldDelimiters(fc, wbd_c.F_NAME),
                )
                with arcpy.EnvManager(addOutputsToMap=False):
                    polygon_fl = arcpy.MakeFeatureLayer_management(fc, where_clause=where_poly)
                df = wbd_f.create_df(polygon_fl, columns=required_fields + [self.HUCFIELD])
                df = df.loc[df[NAME_FLAG] == wbd_c.WRONG_FLAG]
                if len(df) == 0:
                    params[0].setErrorMessage("No incorrectly named watersheds found.")
                # give user list of incorrectly named watersheds as options
                params[1].filter.list = self.get_incorrect_names(df)
                if params[1].altered and params[1].value:
                    old_name = params[1].valueAsText.split(": ")[-1]
                    huc = params[1].valueAsText.split(": ")[0]
                    self.zoomto_feature(polygon_fl, huc, old_name)
                    potential_names = self.get_potentialnames(params[1].valueAsText, df)
                    if not potential_names:
                        params[1].value = None
                        params[2].value = None
                        params[3].value = None
                        params[4].value = None
                        params[2].enabled = True
                        params[3].enabled = True
                        params[4].enabled = True
                    else:
                        params[4].filter.list = list(set(potential_names))
                        if params[2].value:
                            params[3].value = None
                            params[4].value = None
                            params[3].enabled = False
                            params[4].enabled = False
                        elif params[3].value:
                            params[2].value = None
                            params[4].value = None
                            params[2].enabled = False
                            params[4].enabled = False
                        elif params[4].value:
                            params[2].value = None
                            params[3].value = None
                            params[2].enabled = False
                            params[3].enabled = False
                        else:
                            params[2].enabled = True
                            params[3].enabled = True
                            params[4].enabled = True

    def get_incorrect_names(self, df):
        """
        Gets list of hucs with incorrect names. Formated as "HUC: Name".
        """
        incorrect_hucs = list(
            df[self.HUCFIELD][np.logical_or(df[NAME_FLAG] == wbd_c.WRONG_FLAG, df[NAME_FLAG] == wbd_c.NODATA_FLAG)]
        )
        incorrect_names = list(
            df[wbd_c.F_NAME][np.logical_or(df[NAME_FLAG] == wbd_c.WRONG_FLAG, df[NAME_FLAG] == wbd_c.NODATA_FLAG)]
        )
        return ["".join([x, ": ", y]) for x, y in zip(incorrect_hucs, incorrect_names)]

    def add_prefixes(self, used_prefixes, name, potential_minorfeatures, huccode, df):
        """
        Adds prefixes to potential_names.
        """
        if name.endswith(wbd_f.river_synonyms):
            prefixed_names = []
            if wbd_c.HEADWATERS not in used_prefixes:
                prefixed_names.append(" ".join([wbd_c.HEADWATERS, name]))
            if wbd_c.OUTLET not in used_prefixes:
                prefixed_names.append(" ".join([wbd_c.OUTLET, name]))
            if wbd_c.UPPER in used_prefixes and wbd_c.LOWER not in used_prefixes:
                prefixed_names.append(" ".join([wbd_c.LOWER, name]))
            if wbd_c.LOWER in used_prefixes and wbd_c.UPPER not in used_prefixes:
                prefixed_names.append(" ".join([wbd_c.UPPER, name]))
            if wbd_c.LOWER in used_prefixes and wbd_c.UPPER in used_prefixes and wbd_c.MIDDLE not in used_prefixes:
                prefixed_names.append(" ".join([wbd_c.MIDDLE, name]))
            if not wbd_c.LOWER in used_prefixes and not wbd_c.UPPER in used_prefixes:
                hyphenated_name = "".join(["-", name])
                completed_names = self.add_minorfeature(hyphenated_name, potential_minorfeatures, huccode, df)
                prefixed_names.extend(completed_names)
            return prefixed_names
        elif not wbd_c.LOWER in used_prefixes and not wbd_c.UPPER in used_prefixes:
            hyphenated_name = "".join(["-", name])
            completed_names = self.add_minorfeature(hyphenated_name, potential_minorfeatures, huccode, df)
            return completed_names
        else:
            return None

    def add_minorfeature(self, name, potential_minorfeatures, huccode, df):
        """
        Adds minor feature or huc code to potential_names if hyphenated naming structure is used.
        """
        completed_names = []
        # no potential minor features >>> use huccode with hyphenated naming structure
        if not potential_minorfeatures:
            completed_names.append("".join([huccode, name]))
        # minor features >>> use minor feature with hyphenated naming structure
        else:
            completed_names = ["".join([x, name]) for x in potential_minorfeatures]
            for name in completed_names:
                # remove already used names
                if name in list(df[wbd_c.F_NAME]):
                    completed_names.remove(name)
            if not completed_names:
                completed_names.append("".join([huccode, name]))
        return completed_names

    def get_potentialnames(self, selection, df):
        """Get a list of potential names for the selection.

        Args:
            selection (str): HUC selected by the user for correction
            df (pandas dataframe): Dataframe containing inocrrectly named HUCs

        Returns:
            [list]: List of potential names.  Returns an empty list when the selection could not be found in the dataframe.
                This occurs when the selection has already been corrected.
        """
        name_choices = []
        lst = selection.split(":")
        try:
            row = df.loc[(df[wbd_c.F_NAME] == lst[1].strip()) & (df[self.HUCFIELD] == lst[0])].iloc[0]
        except IndexError:
            return name_choices
        potential_names = row[wbd_c.F_POTENTIALNAMES].split(",")
        try:
            potential_names.remove("None")
        except ValueError:
            pass
        # no feature in huc >>> use huc code
        if not potential_names:
            name_choices.append(row[self.HUCFIELD])
        else:
            for name in potential_names:
                potential_minorfeatures = [x for x in potential_names if x != name]
                # if potential name has not been used as major feature before >>>
                # can use potential name WITHOUT prefix
                if name not in list(df[wbd_c.F_MAJORFEATURE]):
                    name_choices.append(name)
                # if potential name has been used as major feature before >>>
                # use potential name WITH prefix/hyphenation
                else:
                    used_prefixes = df[wbd_c.F_PREFIX][df[wbd_c.F_MAJORFEATURE] == name].iloc[0]
                    names_prefixadded = self.add_prefixes(
                        used_prefixes, name, potential_minorfeatures, row[self.HUCFIELD], df
                    )
                    if names_prefixadded:
                        name_choices.extend(names_prefixadded)
        return name_choices

    def execute(self, params, messages):
        """
        Updates incorrect names in the feature class and the ESRI table.
        """
        # parameters
        polygon_fc = params[0].valueAsText
        HUCFIELD = wbd_f.get_hucfield(polygon_fc)
        huc = params[1].valueAsText.split(":")[0]
        old_name = params[1].valueAsText.split(":")[1].strip()
        if params[2].value:
            new_name = old_name
        elif params[3].valueAsText:
            new_name = params[3].valueAsText
        elif params[4].valueAsText:
            new_name = params[4].valueAsText
        else:
            raise arcpy.ExecuteError("Must enter a new name or select to keep the old name.")
        arcpy.AddMessage((polygon_fc, huc, old_name, new_name))
        # update polygon_fc
        where = """{} = {} AND {} = {}""".format(
            arcpy.AddFieldDelimiters(polygon_fc, HUCFIELD),
            "'{}'".format(huc),
            arcpy.AddFieldDelimiters(polygon_fc, wbd_c.F_NAME),
            "'{}'".format(old_name),
        )
        polygon_fl = arcpy.MakeFeatureLayer_management(polygon_fc, where_clause=where)
        arcpy.CalculateField_management(polygon_fl, NAME_FLAG, "'{}'".format(wbd_c.CORRECT_FLAG), "PYTHON")
        arcpy.CalculateField_management(polygon_fl, wbd_c.F_NAME, "'{}'".format(new_name), "PYTHON")

