import numpy as np
import sys
import os
import csv
import tempfile
import copy
import pandas as pd
from arcgis.features import GeoAccessor, GeoSeriesAccessor
import arcpy
from arcpy import env
from arcpy.sa import *


import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from basinmaker.func.arcgis import *
from basinmaker.func.pdtable import *
arcpy.env.overwriteOutput = True
arcpy.CheckOutExtension("Spatial")



def GenerateHRUS_arcgis(
    Path_Subbasin_Ply,
    Landuse_info,
    Soil_info,
    Veg_info,
    Inmportance_order,
    min_hru_area_pct_sub,
    Sub_Lake_ID="HyLakeId",
    Sub_ID="SubId",
    Path_Connect_Lake_ply="#",
    Path_Non_Connect_Lake_ply="#",
    Lake_Id="Hylak_id",
    Path_Landuse_Ply="#",
    Landuse_ID="Landuse_ID",
    Path_Soil_Ply="#",
    Soil_ID="Soil_ID",
    Path_Veg_Ply="#",
    Veg_ID="Veg_ID",
    Path_Other_Ply_1="#",
    Other_Ply_ID_1="O_ID_1",
    Path_Other_Ply_2="#",
    Other_Ply_ID_2="O_ID_2",
    DEM="#",
    Project_crs = '3573',
    OutputFolder="#",
):
    """Generate HRU polygons and their attributes needed by hydrological model

    Function that be used to overlay: subbasin polygon, lake polygon (optional)
    , Land use polygon (optional), soil type polygon(optional),
    vegetation polygon (optional), and two other user defined polygons
    (optional).
    Non-lake HRU polygons in a subbasin is defined by an unique
    combination of all user provided datasets.
    A lake HRU polygon is defined the same as the provided lake polygon.
    All value of landuse and Veg polygon covered by lake will
    be changed to 1, indicating it is a covered by lake.
    All value of the soil polygon covered by the lake will be change to
    the soil id of the polygon covered by the lake with largest area.

    Parameters
    ----------
    Path_Subbasin_Ply                 : string
        It is the path of the subbasin polygon, which is generated by
        toolbox. if not generated by toolbox, the attribute table should
        including following attribute.
        ##############Subbasin related attributes###########################
        SubID           - integer, The subbasin Id
        DowSubId        - integer, The downstream subbasin ID of this
                                   subbasin
        IsLake          - integer, If the subbasin is a lake / reservior
                                   subbasin. 1 yes, <0, no
        IsObs           - integer, If the subbasin contains a observation
                                   gauge. 1 yes, < 0 no.
        RivLength       - float,   The length of the river in current
                                   subbasin in m
        RivSlope        - float,   The slope of the river path in
                                   current subbasin, in m/m
        FloodP_n        - float,   Flood plain manning's coefficient, in -
        Ch_n            - float,   main channel manning's coefficient, in -
        BkfWidth        - float,   the bankfull width of the main channel
                                   in m
        BkfDepth        - float,   the bankfull depth of the main channel
                                   in m
        HyLakeId        - integer, the lake id
        LakeVol         - float,   the Volume of the lake in km3
        LakeDepth       - float,   the average depth of the lake m
        LakeArea        - float,   the area of the lake in m2
    Landuse_info                      : string
        Path to a csv file that contains landuse information, including
        following attributes:
        Landuse_ID (can be any string)  - integer, the landuse ID in the
                                                   landuse polygon
        LAND_USE_C                      - string,  the landuse class name
                                                   for each landuse Type
    Soil_info                        : string
        Path to a csv file that contains soil information, including
        following attributes:
        Soil_ID (can be any string)     - integer, the Soil ID in the
                                                   soil polygon
        SOIL_PROF                       - string,  the Soil profile name
                                                   for each soil type
    Veg_info                         : string
        Path to a csv file that contains vegetation information, including
        following attributes:
        Veg_ID (can be any string)      - integer, the vegetation ID in the
                                                   vegetation polygon
        VEG_C                           - string,  the vegetation class name
                                                   for each vegetation Type
    Sub_Lake_ID                      : string (optional)
        The column name of the lake id in the subbasin polygon
    Sub_ID                           : string (optional)
        The column name of the subbasin id in the subbasin polygon
    Path_Connect_Lake_ply            : string (Optional)
        Path to the connected lake's polygon
    Path_Non_Connect_Lake_ply        : string (Optional)
        Path to the non connected lake's polygon
    Lake_Id                          : string (Optional)
        The the column name in lake polygon indicate the lake ID.
    Path_Landuse_Ply                 : string (Optional)
        Path to the landuse polygon. when Path_Landuse_Ply is not
        provided. The Landuse ID in Landuse_info should be
        1: land, -1: lake
    Landuse_ID                       : string (Optional)
        the the column name in landuse polygon and Landuse_info csv
        indicate the landuse ID. when Path_Landuse_Ply is not
        provided. The Landuse ID should be
        1: land, -1: lake.
    Path_Soil_Ply                    : string (Optional)
        Path to the soil polygon. when soil polygon is not
        provided. The Soil ID in Soil_info should be the same
        as Landuse ID.
    Soil_ID                          : string (Optional)
        the the column name in soil polygon and soil_info csv
        indicate the soil ID. when soil polygon is not
        provided. The Soil ID in Soil_info should be the same
        as Landuse ID.
    Path_Veg_Ply                     : string (Optional)
        Path to the vegetation polygon. when Veg polygon is not
        provided. The Veg ID in Veg_info should be the same
        as Landuse ID.
    Veg_ID                           : string (Optional)
        the the column name in vegetation polygon and veg_info csv
        indicate the vegetation ID. when Veg polygon is not
        provided. The Veg ID in Veg_info should be the same
        as Landuse ID.
    Path_Other_Ply_1                 : string (Optional)
        Path to the other polygon that will be used to define HRU,
        such as elevation band, or aspect.
    Other_Ply_ID_1                   : string (Optional)
        the the column name in Other_Ply_1 polygon
        indicate the landuse ID.
    Path_Other_Ply_2                 : string (Optional)
        Path to the other polygon that will be used to define HRU,
        such as elevation band, or aspect.
    Other_Ply_ID_2                   : string (Optional)
        the the column name in Other_Ply_2 polygon
        indicate the landuse ID.
    DEM                              : string (optional)
        the path to a raster elevation dataset, that will be used to
        calcuate average apspect, elevation and slope within each HRU.
        if no data is provided, basin average value will be used for
        each HRU.
    Project_crs                      : string
        the EPSG code of a projected coodinate system that will be used to
        calcuate HRU area and slope.
    OutputFolder                     : string
        The path to the folder that will save output HRU polygon.

    Notes
    -------
    Following ouput files will be generated in "<OutputFolder>/"
    'finalcat_hru_info.shp'              - HRU polygon and it's attributes


    Returns:
    -------
       None

    Examples
    -------
    >>> from ToolboxClass import LRRT
    >>> import pandas as pd
    >>> DataFolder = "C:/Path_to_foldr_of_example_dataset_provided_in_Github_wiki/"
    >>> RTtool=LRRT()
    >>> RTtool.GenerateHRUS(OutputFolder = DataFolder,
                           Path_Subbasin_Ply = os.path.join(DataFolder,"finalcat_info.shp"),
                           Path_Connect_Lake_ply = os.path.join(DataFolder,'Con_Lake_Ply.shp'),
                           Path_Non_Connect_Lake_ply = os.path.join(DataFolder,'Non_Con_Lake_Ply.shp'),
                           Path_Landuse_Ply = os.path.join(DataFolder,'modislanduse_exp_lg_pre.shp'),
                           Landuse_ID = 'gridcode',
                           Path_Soil_Ply = os.path.join(DataFolder,'ca_all_slc_v3r2_exp_lg.shp'),
                           Soil_ID = 'POLY_ID',
                           Landuse_info=os.path.join(DataFolder,'landuse_info.csv'),
                           Soil_info=os.path.join(DataFolder,'soil_info.csv'),
                           Veg_info=os.path.join(DataFolder,'veg_info.csv'),
                           DEM = os.path.join(DataFolder,'na_dem_15s_1.tif')
                           )

    """

    if not os.path.exists(OutputFolder):
        os.makedirs(OutputFolder)

    tempfolder = os.path.join(
        tempfile.gettempdir(), "basinmaker_hru" + str(np.random.randint(1, 10000 + 1))
    )
    if not os.path.exists(tempfolder):
        os.makedirs(tempfolder)
    print(Project_crs[5:len(Project_crs)])
    prj_crs = int(Project_crs[5:len(Project_crs)])
    Merge_layer_list = []
    ### First overlay the subbasin layer with lake polygon, the new unique
    # id will be 'HRULake_ID'

    Sub_Lake_HRU_Layer, trg_crs, fieldnames_list = GeneratelandandlakeHRUS(
        OutputFolder,
        tempfolder,
        Path_Subbasin_ply=Path_Subbasin_Ply,
        Path_Connect_Lake_ply=Path_Connect_Lake_ply,
        Path_Non_Connect_Lake_ply=Path_Non_Connect_Lake_ply,
        Sub_ID=Sub_ID,
        Sub_Lake_ID=Sub_Lake_ID,
        Lake_Id=Lake_Id,
    )

    lakehruinfo = pd.DataFrame.spatial.from_featureclass(Sub_Lake_HRU_Layer)
    hru_lake_info = lakehruinfo.loc[lakehruinfo['HRU_IsLake'] > 0].copy()
    lakehruinfo_landhrus = lakehruinfo.loc[lakehruinfo['HRU_IsLake'] <= 0].copy()

    lakehruinfo_landhrus = lakehruinfo_landhrus.spatial.to_featureclass(
        location=os.path.join(tempfolder,'land_hrus.shp'),
        overwrite=True,sanitize_columns=False,
    )

    fieldnames_list.extend(
        [
            Landuse_ID,
            Soil_ID,
            Veg_ID,
            Other_Ply_ID_1,
            Other_Ply_ID_2,
            "LAND_USE_C",
            "VEG_C",
            "SOIL_PROF",
            "HRU_Slope",
            "HRU_Area",
            "HRU_Aspect",
            "SHAPE",
        ]
    )
    dissolve_filedname_list = ["HRULake_ID"]

    Merge_layer_list.append([os.path.join(tempfolder,'land_hrus.shp'),1])

    #### check which data will be inlucded to determine HRU
    if Path_Landuse_Ply != "#":
        layer_path = Reproj_Clip_Dissolve_Simplify_Polygon_arcgis(
            layer_path = Path_Landuse_Ply,
            Class_Col = Landuse_ID,
            tempfolder = tempfolder,
            mask_layer = os.path.join(tempfolder,'land_hrus.shp')
        )
        Merge_layer_list.append([layer_path,2])
        dissolve_filedname_list.append(Landuse_ID)

    if Path_Soil_Ply != "#":
        layer_path = Reproj_Clip_Dissolve_Simplify_Polygon_arcgis(
            layer_path = Path_Soil_Ply,
            Class_Col = Soil_ID,
            tempfolder = tempfolder,
            mask_layer = os.path.join(tempfolder,'land_hrus.shp')
        )
        Merge_layer_list.append([layer_path,2])
        dissolve_filedname_list.append(Soil_ID)

    if Path_Veg_Ply != "#":
        layer_path = Reproj_Clip_Dissolve_Simplify_Polygon_arcgis(
            layer_path = Path_Veg_Ply,
            Class_Col = Veg_ID,
            tempfolder = tempfolder,
            mask_layer = os.path.join(tempfolder,'land_hrus.shp')
        )
        Merge_layer_list.append([layer_path,2])
        dissolve_filedname_list.append(Veg_ID)

    if Path_Other_Ply_1 != "#":
        layer_path = Reproj_Clip_Dissolve_Simplify_Polygon_arcgis(
            layer_path = Path_Other_Ply_1,
            Class_Col = Other_Ply_ID_1,
            tempfolder = tempfolder,
            mask_layer = os.path.join(tempfolder,'land_hrus.shp')
        )
        Merge_layer_list.append([layer_path,2])
        dissolve_filedname_list.append(Other_Ply_ID_1)

    if Path_Other_Ply_2 != "#":
        layer_path = Reproj_Clip_Dissolve_Simplify_Polygon_arcgis(
            layer_path = Path_Other_Ply_2,
            Class_Col = Other_Ply_ID_2,
            tempfolder = tempfolder,
            mask_layer = os.path.join(tempfolder,'land_hrus.shp')
        )
        Merge_layer_list.append([layer_path,2])
        dissolve_filedname_list.append(Other_Ply_ID_2)

    fieldnames = np.array(fieldnames_list)

    print("begin union")
    #### uniion polygons in the Merge_layer_list
    print(Merge_layer_list)
    arcpy.Union_analysis(Merge_layer_list, os.path.join(tempfolder,'hrus_land_union.shp'))
    print("union done ")

    hru_land_info = pd.DataFrame.spatial.from_featureclass( os.path.join(tempfolder,'hrus_land_union.shp'))

    #####

    Landuse_info_data = pd.read_csv(Landuse_info)
    Soil_info_data = pd.read_csv(Soil_info)
    Veg_info_data = pd.read_csv(Veg_info)

    # landuse polygon is not provided,landused id the same as IS_lake
    if Path_Landuse_Ply == "#":
        hru_land_info[Landuse_ID] = -hru_land_info['HRU_IsLake']
    hru_lake_info[Landuse_ID] = -1
    # if soil is not provied, it the value,will be the same as land use
    if Path_Soil_Ply == "#":
        hru_land_info[Soil_ID] = hru_land_info[Landuse_ID]
    hru_lake_info[Soil_ID] = -1
    # if no vegetation polygon is provide vegetation, will be the same as landuse
    if Path_Veg_Ply == "#":
        hru_land_info[Veg_ID] = hru_land_info[Landuse_ID]
    hru_lake_info[Veg_ID] = -1
    if Path_Other_Ply_1 == "#":
        hru_land_info[Other_Ply_ID_1] = -hru_land_info['HRU_IsLake']
    hru_lake_info[Other_Ply_ID_1] = -1
    if Path_Other_Ply_2 == "#":
        hru_land_info[Other_Ply_ID_2] = -hru_land_info['HRU_IsLake']
    hru_lake_info[Other_Ply_ID_2] = -1

    hru_lake_info = clean_attribute_name_arcgis(hru_lake_info,fieldnames)
    hru_land_info = clean_attribute_name_arcgis(hru_land_info,fieldnames)


    hruinfo = pd.concat([hru_lake_info, hru_land_info], ignore_index=True)

    HRU_draf_final = Define_HRU_Attributes_arcgis(
        prj_crs = prj_crs,
        trg_crs = trg_crs,
        hruinfo = hruinfo,
        dissolve_filedname_list = dissolve_filedname_list,
        Sub_ID = Sub_ID,
        Landuse_ID = Landuse_ID,
        Soil_ID = Soil_ID,
        Veg_ID = Veg_ID,
        Other_Ply_ID_1 = Other_Ply_ID_1,
        Other_Ply_ID_2 = Other_Ply_ID_2,
        Landuse_info_data = Landuse_info_data,
        Soil_info_data = Soil_info_data,
        Veg_info_data = Veg_info_data,
        DEM = DEM,
        Path_Subbasin_Ply = Path_Subbasin_Ply,
        min_hru_area_pct_sub = min_hru_area_pct_sub,
        Inmportance_order = Inmportance_order,
        OutputFolder = OutputFolder,
        tempfolder = tempfolder,
    )

    COLUMN_NAMES_CONSTANT_HRU_extend = COLUMN_NAMES_CONSTANT_HRU.extend(
        [
            Landuse_ID,
            Soil_ID,
            Veg_ID,
            Other_Ply_ID_1,
            Other_Ply_ID_2,
        ]
    )
    HRU_draf_final = clean_attribute_name_arcgis(HRU_draf_final,COLUMN_NAMES_CONSTANT_HRU)
    HRU_draf_final.spatial.to_featureclass(location = os.path.join(OutputFolder,'finalcat_hru_info.shp'),overwrite=True,sanitize_columns=False)

def GeneratelandandlakeHRUS(
    OutputFolder,
    tempfolder,
    Path_Subbasin_ply,
    Path_Connect_Lake_ply="#",
    Path_Non_Connect_Lake_ply="#",
    Sub_ID="SubId",
    Sub_Lake_ID="HyLakeId",
    Lake_Id="Hylak_id",
):

    """Overlay subbasin polygon and lake polygons

    Function that will overlay subbasin polygon and lake polygon

    Parameters
    ----------
    processing                        : qgis object
    context                           : qgis object
    OutputFolder                     : string
        The path to the folder that will save output HRU polygon.

    Path_Subbasin_Ply                 : string
        It is the path of the subbasin polygon, which is generated by
        toolbox. if not generated by toolbox, the attribute table should
        including following attribute.
        ##############Subbasin related attributes###########################
        SubID           - integer, The subbasin Id
        DowSubId        - integer, The downstream subbasin ID of this
                                   subbasin
        IsLake          - integer, If the subbasin is a lake / reservior
                                   subbasin. 1 yes, <0, no
        IsObs           - integer, If the subbasin contains a observation
                                   gauge. 1 yes, < 0 no.
        RivLength       - float,   The length of the river in current
                                   subbasin in m
        RivSlope        - float,   The slope of the river path in
                                   current subbasin, in m/m
        FloodP_n        - float,   Flood plain manning's coefficient, in -
        Ch_n            - float,   main channel manning's coefficient, in -
        BkfWidth        - float,   the bankfull width of the main channel
                                   in m
        BkfDepth        - float,   the bankfull depth of the main channel
                                   in m
        HyLakeId        - integer, the lake id
        LakeVol         - float,   the Volume of the lake in km3
        LakeDepth       - float,   the average depth of the lake m
        LakeArea        - float,   the area of the lake in m2
    Path_Connect_Lake_ply            : string (Optional)
        Path to the connected lake's polygon
    Path_Non_Connect_Lake_ply        : string (Optional)
        Path to the non connected lake's polygon
    Sub_ID                           : string (optional)
        The column name of the subbasin id in the subbasin polygon
    Sub_Lake_ID                      : string (optional)
        The column name of the lake id in the subbasin polygon
    Lake_Id                          : string (Optional)
        The the column name in lake polygon indicate the lake ID.


    Notes
    -------
        None

    Returns:
    -------
        Sub_Lake_HRU['OUTPUT']                   : qgis object
            it is a polygon after overlay between subbasin polygon and
            lake polygon
        Sub_Lake_HRU['OUTPUT'].crs().authid()    : string
            it is a string indicate the geospatial reference used by SubBasin
            polygon
        ['HRULake_ID','HRU_IsLake',Sub_ID]       : list
            it is a string list
    """

    # Fix geometry errors in subbasin polygon
    arcpy.RepairGeometry_management(Path_Subbasin_ply)

    # Create a file name list that will be strored in output attribute table
    fieldnames_list = [
        "HRULake_ID",
        "HRU_IsLake",
        Lake_Id,
        Sub_ID,
        Sub_Lake_ID,
        "SHAPE"
    ]  ### attribubte name in the need to be saved
    fieldnames = np.array(fieldnames_list)

    # if no lake polygon is provided, use subId as HRULake_ID.
    if Path_Connect_Lake_ply == "#" and Path_Non_Connect_Lake_ply == "#":

        cat_info = pd.DataFrame.spatial.from_featureclass(Path_Subbasin_ply)
        cat_info['Hylak_id'] = -1
        cat_info['HRULake_ID'] = cat_info['SubId']
        cat_info['HRU_IsLake'] = -1

        # remove column not in fieldnames
        cat_info = clean_attribute_name_arcgis(cat_info,fieldnames)
        cat_info.spatial.to_featureclass(location=os.path.join(OutputFolder,'finalcat_hru_lake_info.shp'),overwrite=True,sanitize_columns=False)
        crs_id = arcpy.Describe(Path_Subbasin_ply).spatialReference.factoryCode
        return os.path.join(OutputFolder,'finalcat_hru_lake_info.shp'), crs_id, ["HRULake_ID", "HRU_IsLake", Sub_ID]

    # fix lake polygon  geometry
    if Path_Connect_Lake_ply != "#":
        arcpy.RepairGeometry_management(Path_Connect_Lake_ply)
    # fix lake polygon geometry
    if Path_Non_Connect_Lake_ply != "#":
        arcpy.RepairGeometry_management(Path_Non_Connect_Lake_ply)
    # Merge connected and non connected lake polygons first
    if Path_Connect_Lake_ply != "#" and Path_Non_Connect_Lake_ply != "#":
        arcpy.Merge_management([Path_Connect_Lake_ply, Path_Non_Connect_Lake_ply], os.path.join(tempfolder,'merged_lake_ply.shp'))
    elif Path_Connect_Lake_ply != "#" and Path_Non_Connect_Lake_ply == "#":
        arcpy.CopyFeatures_management(Path_Connect_Lake_ply, os.path.join(tempfolder,'merged_lake_ply.shp'))
    elif Path_Connect_Lake_ply == "#" and Path_Non_Connect_Lake_ply != "#":
        arcpy.CopyFeatures_management(Path_Non_Connect_Lake_ply, os.path.join(tempfolder,'merged_lake_ply.shp'))
    else:
        print("should never happened......")

    # union merged polygon and subbasin polygon
#    cat_info.spatial.to_featureclass(location=os.path.join(tempfolder,'cat_ply.shp'))
#    arcpy.RepairGeometry_management(os.path.join(tempfolder,'cat_ply.shp'))

    inFeatures = [[Path_Subbasin_ply, 1], [os.path.join(tempfolder,'merged_lake_ply.shp'), 2]]

    arcpy.Union_analysis(inFeatures, os.path.join(tempfolder,'cat_lake_union.shp'))

    arcpy.RepairGeometry_management(os.path.join(tempfolder,'cat_lake_union.shp'))
    sub_lake_info = pd.DataFrame.spatial.from_featureclass(location=os.path.join(tempfolder,'cat_lake_union.shp'))
    sub_lake_info['HRULake_ID'] = -9999
    sub_lake_info['HRU_IsLake'] = -9999

    crs_id = arcpy.Describe(os.path.join(tempfolder,'cat_lake_union.shp')).spatialReference.factoryCode

    sub_lake_info['HRU_ID_Temp'] = sub_lake_info['FID'] + 1

    sub_lake_info = Determine_Lake_HRU_Id(sub_lake_info)
    # copy determined lake hru id to vector
    sub_lake_info = clean_attribute_name_arcgis(sub_lake_info,fieldnames)
    save_modified_attributes_to_outputs(
        mapoldnew_info = sub_lake_info,
        tempfolder = tempfolder,
        OutputFolder = OutputFolder,
        cat_name = 'finalcat_hru_lake_info.shp',
        riv_name = '#',
        Path_final_riv = '#',
        dis_col_name='HRULake_ID'
    )

    return os.path.join(OutputFolder,'finalcat_hru_lake_info.shp'), crs_id, ["HRULake_ID", "HRU_IsLake", Sub_ID]


############


def Reproj_Clip_Dissolve_Simplify_Polygon_arcgis(
    layer_path, Class_Col, tempfolder,mask_layer
):
    """Preprocess user provided polygons

    Function that will reproject clip input polygon with subbasin polygon
    and will dissolve the input polygon based on their ID, such as landuse id
    or soil id.

    Parameters
    ----------
    processing                        : qgis object
    context                           : qgis object
    layer_path                        : string
        The path to a specific polygon, for example path to landuse layer
    Project_crs                       : string
        the EPSG code of a projected coodinate system that will be used to
        calcuate HRU area and slope.
    trg_crs                           : string
        the EPSG code of a  coodinate system that will be used to
        calcuate reproject input polygon
    Class_Col                         : string
        the column name in the input polygon (layer_path) that contains
        their ID, for example land use ID or soil ID.
    Layer_clip                        : qgis object
        A shpfile with extent of the watershed, will be used to clip input
        input polygon
    Notes
    -------
        # TODO: May be add some function to simplify the input polygons
                for example, remove the landuse type with small areas
                or merge small landuse polygon into the surrounding polygon

    Returns:
    -------
        layer_dis                  : qgis object
            it is a polygon after preprocess
    """
    arcpy.Project_management(
        layer_path,
        os.path.join(tempfolder,Class_Col+"_proj.shp"),
        arcpy.Describe(mask_layer).spatialReference,
        )
    arcpy.Clip_analysis(
        os.path.join(tempfolder,Class_Col+"_proj.shp"),
        mask_layer,
        os.path.join(tempfolder,Class_Col+"_clip.shp")
    )

    arcpy.Dissolve_management(
        os.path.join(tempfolder,Class_Col+"_clip.shp"),
        os.path.join(tempfolder,Class_Col+"_dislve.shp"),
        [Class_Col]
    )

    arcpy.RepairGeometry_management(os.path.join(tempfolder,Class_Col+"_dislve.shp"))

    arcpy.AddSpatialIndex_management(os.path.join(tempfolder,Class_Col+"_dislve.shp"))

    return os.path.join(tempfolder,Class_Col+"_dislve.shp")


def Union_Ply_Layers_And_Simplify(
    processing,
    context,
    Merge_layer_list,
    dissolve_filedname_list,
    fieldnames,
    OutputFolder,
):
    """Union input QGIS polygon layers

    Function will union polygon layers in Merge_layer_list
    dissove the union result based on field name in
    dissolve_filedname_list
    and remove all field names not in fieldnames

    Parameters
    ----------
    processing                        : qgis object
    context                           : qgis object
    Merge_layer_list                  : list
        a list contain polygons layers needs to be unioned
    dissolve_filedname_list           : list
        a list contain column name of ID in each polygon layer
        in Merge_layer_list
    fieldnames                        : list
        a list contain column names that are needed in the return
        QGIS polygon layers
    OutputFolder                      : String
        The path to a folder to save result during the processing

    Returns:
    -------
    mem_union_dis                  : qgis object
        it is a polygon object that generated by overlay all input
        layers
    """
    num = str(np.random.randint(1, 10000 + 1))
    ##union polygons
    if len(Merge_layer_list) == 1:
        mem_union = Merge_layer_list[0]
        mem_union_fix_ext = qgis_vector_fix_geometries(
            processing, context, INPUT=mem_union, OUTPUT="memory:"
        )["OUTPUT"]
    elif len(Merge_layer_list) > 1:
        for i in range(0, len(Merge_layer_list)):
            if i == 0:
                mem_union = Merge_layer_list[i]
                mem_union_fix_ext = qgis_vector_fix_geometries(
                    processing, context, INPUT=mem_union, OUTPUT="memory:"
                )["OUTPUT"]
                qgis_vector_create_spatial_index(processing, context, mem_union_fix_ext)
            else:
                mem_union_fix_temp = mem_union_fix_ext
                del mem_union_fix_ext
                mem_union = processing.run(
                    "saga:polygonunion",
                    {
                        "A": mem_union_fix_temp,
                        "B": Merge_layer_list[i],
                        "SPLIT": True,
                        "RESULT": "TEMPORARY_OUTPUT",
                    },
                    context=context,
                )["RESULT"]
                # mem_union = qgis_vector_union_two_layers(
                #     processing,
                #     context,
                #     mem_union_fix_temp,
                #     Merge_layer_list[i],
                #     "memory:",
                #     OVERLAY_FIELDS_PREFIX="",
                # )["OUTPUT"]
                mem_union_fix = qgis_vector_fix_geometries(
                    processing, context, INPUT=mem_union, OUTPUT="memory:"
                )["OUTPUT"]
                mem_union_fix_ext = mem_union_fix
                qgis_vector_create_spatial_index(processing, context, mem_union_fix_ext)
    else:
        print("No polygon needs to be overlaied.........should not happen ")

    ## remove non interested filed
    mem_union_fix_ext, temp_out_notused = Clean_Attribute_Name(
        mem_union_fix_ext, fieldnames, Input_Is_Feature_In_Mem=True
    )
    mem_union_dis = mem_union_fix_ext
    # qgis_vector_dissolve(
    #     processing,
    #     context,
    #     INPUT=mem_union_fix_ext,
    #     FIELD=dissolve_filedname_list,
    #     OUTPUT=os.path.join(tempfile.gettempdir(),num + "_dissolve_union.shp")
    # )
    #
    # mem_union_dis = qgis_vector_dissolve(
    #     processing,
    #     context,
    #     INPUT=os.path.join(tempfile.gettempdir(),num + "_dissolve_union.shp"),
    #     FIELD=dissolve_filedname_list,
    #     OUTPUT="memory:",
    # )["OUTPUT"]

    return mem_union_dis


def Define_HRU_Attributes_arcgis(
    prj_crs,
    trg_crs,
    hruinfo,
    dissolve_filedname_list,
    Sub_ID,
    Landuse_ID,
    Soil_ID,
    Veg_ID,
    Other_Ply_ID_1,
    Other_Ply_ID_2,
    Landuse_info_data,
    Soil_info_data,
    Veg_info_data,
    DEM,
    Path_Subbasin_Ply,
    Inmportance_order,
    min_hru_area_pct_sub,
    OutputFolder,
    tempfolder,
):

    """Generate attributes of each HRU

    Function will generate attributes that are needed by Raven and
    other hydrological models for each HRU

    Parameters
    ----------
    processing                        : qgis object
    context                           : qgis object
    Project_crs                       : string
        the EPSG code of a projected coodinate system that will be used to
        calcuate HRU area and slope.
    hru_layer                         : qgis object
        a polygon layer generated by overlay all input polygons
    dissolve_filedname_list           : list
        a list contain column name of ID in each polygon layer
        in Merge_layer_list
    Sub_ID                            : string
        The column name of the subbasin id in the subbasin polygon
    Landuse_ID                        : string
        the the column name in landuse polygon and Landuse_info csv
        indicate the landuse ID. when Path_Landuse_Ply is not
        provided. The Landuse ID should be
        1: land, -1: lake.
    Soil_ID                           : string
        the the column name in soil polygon and soil_info csv
        indicate the soil ID. when soil polygon is not
        provided. The Soil ID in Soil_info should be the same
        as Landuse ID.
    Veg_ID                            : string
        the the column name in vegetation polygon and veg_info csv
        indicate the vegetation ID. when Veg polygon is not
        provided. The Veg ID in Veg_info should be the same
        as Landuse ID.

    Landuse_info                      : Dataframe
        a dataframe that contains landuse information, including
        following attributes:
        Landuse_ID (can be any string)  - integer, the landuse ID in the
                                                   landuse polygon
        LAND_USE_C                      - string,  the landuse class name
                                                   for each landuse Type
    Soil_info                         : Dataframe
        a dataframe that contains soil information, including
        following attributes:
        Soil_ID (can be any string)     - integer, the Soil ID in the
                                                   soil polygon
        SOIL_PROF                       - string,  the Soil profile name
                                                   for each soil type
    Veg_info                          : Dataframe
        a dataframe file that contains vegetation information, including
        following attributes:
        Veg_ID (can be any string)      - integer, the vegetation ID in the
                                                   vegetation polygon
        VEG_C                           - string,  the vegetation class name
                                                   for each vegetation Type
    DEM                               : string (optional)
        the path to a raster elevation dataset, that will be used to
        calcuate average apspect, elevation and slope within each HRU.
        if no data is provided, basin average value will be used for
        each HRU.
    Path_Subbasin_Ply                 : string
        It is the path of the subbasin polygon, which is generated by
        toolbox. if not generated by toolbox, the attribute table should
        including following attribute.
        ##############Subbasin related attributes###########################
        SubID           - integer, The subbasin Id
        DowSubId        - integer, The downstream subbasin ID of this
                                   subbasin
        IsLake          - integer, If the subbasin is a lake / reservior
                                   subbasin. 1 yes, <0, no
        IsObs           - integer, If the subbasin contains a observation
                                   gauge. 1 yes, < 0 no.
        RivLength       - float,   The length of the river in current
                                   subbasin in m
        RivSlope        - float,   The slope of the river path in
                                   current subbasin, in m/m
        FloodP_n        - float,   Flood plain manning's coefficient, in -
        Ch_n            - float,   main channel manning's coefficient, in -
        BkfWidth        - float,   the bankfull width of the main channel
                                   in m
        BkfDepth        - float,   the bankfull depth of the main channel
                                   in m
        HyLakeId        - integer, the lake id
        LakeVol         - float,   the Volume of the lake in km3
        LakeDepth       - float,   the average depth of the lake m
        LakeArea        - float,   the area of the lake in m2
    OutputFolder                      : String
        The path to a folder to save result during the processing

    Returns:
    -------
    HRU_draf_final                  : qgis object
        it is a polygon object that generated by overlay all input
        layers and inlcude all needed attribue for hydrological model
        like RAVEN
    """
    num = str(np.random.randint(1, 10000 + 1))
    hruinfo["LAND_USE_C"] = '-9999'
    hruinfo["VEG_C"] = '-9999'
    hruinfo["SOIL_PROF"] = '-9999'
    hruinfo["HRU_CenX"] = -9999.9999
    hruinfo["HRU_CenY"] = -9999.9999
    hruinfo["HRU_ID_New"] = -9999
    hruinfo["HRU_Area"] = -9999.99
    hruinfo.spatial.to_featureclass(location=os.path.join(tempfolder,'hru_add_area.shp'),overwrite=True,sanitize_columns=False)

    arcpy.CalculateGeometryAttributes_management(
        os.path.join(tempfolder,'hru_add_area.shp'),
        [["HRU_Area", "AREA_GEODESIC"]],
        area_unit = 'SQUARE_METERS',
        coordinate_system = arcpy.SpatialReference(prj_crs)
    )
    ### calcuate area of each feature
    hruinfo_area = pd.DataFrame.spatial.from_featureclass(os.path.join(tempfolder,'hru_add_area.shp'))

    hruinfo_area['HRU_ID'] = hruinfo_area['FID'] + 1
    hruinfo_area["HRU_ID_New"] = hruinfo_area["FID"] + 1

    hruinfo_area_update_attribute = Determine_HRU_Attributes(
        hruinfo_area,
        Sub_ID,
        Landuse_ID,
        Soil_ID,
        Veg_ID,
        Other_Ply_ID_1,
        Other_Ply_ID_2,
        Landuse_info_data,
        Soil_info_data,
        Veg_info_data,
    )
    hruinfo_area_update_attribute['HRU_ID_New'] = hruinfo_area_update_attribute['HRU_ID_New'].astype(int)
    save_modified_attributes_to_outputs(
        mapoldnew_info = hruinfo_area_update_attribute,
        tempfolder = tempfolder,
        OutputFolder = tempfolder,
        cat_name = 'finalcat_hru_info.shp',
        riv_name = '#',
        Path_final_riv = '#',
        dis_col_name='HRU_ID_New'
    )

    arcpy.RepairGeometry_management(os.path.join(tempfolder,'finalcat_hru_info.shp'))

    arcpy.CalculateGeometryAttributes_management(
        os.path.join(tempfolder,'finalcat_hru_info.shp'),
        [["HRU_Area", "AREA_GEODESIC"]],
        area_unit = 'SQUARE_METERS',
        coordinate_system = arcpy.SpatialReference(prj_crs)
    )

    hruinfo_new = pd.DataFrame.spatial.from_featureclass(os.path.join(tempfolder,'finalcat_hru_info.shp'))
    hruinfo_simple = simplidfy_hrus(
        min_hru_pct_sub_area = min_hru_area_pct_sub,
        hruinfo = hruinfo_new,
        importance_order = Inmportance_order,
    )

    save_modified_attributes_to_outputs(
        mapoldnew_info = hruinfo_simple,
        tempfolder = tempfolder,
        OutputFolder = tempfolder,
        cat_name = 'hru_simple.shp',
        riv_name = '#',
        Path_final_riv = '#',
        dis_col_name='HRU_ID_New'
    )

    arcpy.CalculateGeometryAttributes_management(
        os.path.join(tempfolder,'hru_simple.shp'),
        [["HRU_CenX", "CENTROID_X"],
        ["HRU_CenY", "CENTROID_Y"]],
        coordinate_system = arcpy.SpatialReference(4326)
    )
    #
    arcpy.JoinField_management(
        os.path.join(tempfolder,'hru_simple.shp'),
        'SubId',
        os.path.join(Path_Subbasin_Ply),
        'SubId',
    )
    arcpy.CalculateGeometryAttributes_management(
        os.path.join(tempfolder,'hru_simple.shp'),
        [["HRU_Area", "AREA_GEODESIC"]],
        area_unit = 'SQUARE_METERS',
        coordinate_system = arcpy.SpatialReference(prj_crs)
    )

    if DEM != "#":

        arcpy.Project_management(
            os.path.join(tempfolder,'hru_simple.shp'),
            os.path.join(tempfolder,"hru_proj.shp"),
            arcpy.SpatialReference(int(prj_crs)),
            )

        extract_dem = ExtractByMask(DEM, os.path.join(tempfolder,'hru_simple.shp'))
        arcpy.ProjectRaster_management(
            extract_dem,
            os.path.join(tempfolder,"demproj.tif"),
            arcpy.SpatialReference(int(prj_crs)),
            "NEAREST"
        )
        Slopeout = Slope(extract_dem, "DEGREE", 0.3043)
        Slopeout.save(os.path.join(OutputFolder,'slope.tif'))
        Aspectout = Aspect(extract_dem)

        # Save the output
        Aspectout.save(os.path.join(OutputFolder,'aspect.tif'))
        table_zon_slope = ZonalStatisticsAsTable(
            os.path.join(tempfolder,"hru_proj.shp"),
            'HRU_ID_New',
            Slopeout,
            os.path.join(tempfolder,"slope_zonal.dbf"),
            "DATA",
            "MEAN",
        )
        table_zon_aspect = ZonalStatisticsAsTable(
            os.path.join(tempfolder,"hru_proj.shp"),
            'HRU_ID_New',
            Aspectout,
            os.path.join(tempfolder,"asp_zonal.dbf"),
            "DATA",
            "MEAN",
        )
        table_zon_elev = ZonalStatisticsAsTable(
            os.path.join(tempfolder,"hru_proj.shp"),
            'HRU_ID_New',
            os.path.join(tempfolder,"demproj.tif"),
            os.path.join(tempfolder,"elv_zonal.dbf"),
            "DATA",
            "MEAN",
        )
        hruinfo_add_slp_asp = pd.DataFrame.spatial.from_featureclass(os.path.join(tempfolder,'hru_simple.shp'))
        table_slp = Dbf_To_Dataframe(os.path.join(tempfolder,"slope_zonal.dbf"))
        table_asp = Dbf_To_Dataframe(os.path.join(tempfolder,"asp_zonal.dbf"))
        table_elv = Dbf_To_Dataframe(os.path.join(tempfolder,"elv_zonal.dbf"))

        table_slp['HRU_S_mean'] = table_slp['MEAN']
        table_slp = table_slp[['HRU_ID_New','HRU_S_mean']]
        table_asp['HRU_A_mean'] = table_asp['MEAN']
        table_asp = table_asp[['HRU_ID_New','HRU_A_mean']]
        table_elv['HRU_E_mean'] = table_elv['MEAN']
        table_elv = table_elv[['HRU_ID_New','HRU_E_mean']]


        hruinfo_add_slp_asp = pd.merge(hruinfo_add_slp_asp, table_slp, on='HRU_ID_New')
        hruinfo_add_slp_asp = pd.merge(hruinfo_add_slp_asp, table_asp, on='HRU_ID_New')
        hruinfo_add_slp_asp = pd.merge(hruinfo_add_slp_asp, table_elv, on='HRU_ID_New')
        hruinfo_add_slp_asp['HRU_ID'] = hruinfo_add_slp_asp['FID'] + 1
    else:
        arcpy.AddMessage(os.path.join(tempfolder,'hru_simple.shp'))
        hruinfo_add_slp_asp = pd.DataFrame.spatial.from_featureclass(os.path.join(tempfolder,'hru_simple.shp'))
        hruinfo_add_slp_asp['HRU_ID'] = hruinfo_add_slp_asp['FID'] + 1
        hruinfo_add_slp_asp['HRU_S_mean'] = hruinfo_add_slp_asp['BasSlope']
        hruinfo_add_slp_asp['HRU_A_mean'] = hruinfo_add_slp_asp['BasAspect']
        hruinfo_add_slp_asp['HRU_E_mean'] = hruinfo_add_slp_asp['MeanElev']

    return hruinfo_add_slp_asp
