"""
For documentation on this script, run with -h flag
"""

import sys
import logging
import os

from argparse import ArgumentParser
from argparse import RawTextHelpFormatter
from datetime import datetime as dt


def run_app():
    long_tbl, template, logger = get_input_parameter()
    field_parsing(long_tbl, template, logger)


def get_input_parameter():
    try:

        # Parse arguments
        parser = ArgumentParser(description='Add fields to input table if they do not yet exist in the table and do '
                                            'exist in the template', formatter_class=RawTextHelpFormatter)

        parser.add_argument('lt', help='Path to the input Long Table.')
        parser.add_argument('t', help='Path to the Template.')
        parser.add_argument('--log_level', default='INFO', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
                            help='Log level')
        parser.add_argument('--log_dir', help='Path to log Directory')
        args = parser.parse_args()

        log_name = 'main_logger'
        logger = logging.getLogger(log_name)

        log_fmt = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
        log_file_base_name = os.path.basename(sys.argv[0])
        log_file_extension = 'log'
        timestamp = dt.now().strftime('%Y-%m-%d_%H-%M-%S')
        log_file = "{}_{}.{}".format(timestamp, log_file_base_name, log_file_extension)

        logger.setLevel(args.log_level)

        sh = logging.StreamHandler()
        sh.setLevel(args.log_level)
        sh.setFormatter(log_fmt)
        logger.addHandler(sh)

        if args.log_dir:
            try:
                os.makedirs(args.log_dir)
            except OSError:
                pass

            fh = logging.FileHandler(os.path.join(args.log_dir, log_file))
            fh.setLevel(args.log_level)
            fh.setFormatter(log_fmt)
            logger.addHandler(fh)

        # Start the script
        return args.lt, args.t, logger

    except Exception as e:
        print('Unexpected exception. Program terminating.')
        exit(1)


def field_parsing(long_tbl, template, logger):

    # Fields to add
    textural_field_base_names = ['TTEX_', 'SURF_E', 'STTEX_', 'SSURF_E', 'TTTEX_', 'TSURF_E']
    geo_letter_field = 'GEOP_SCM'
    geo_prime_field = 'GEOP_INZ'

    lst_fgdb_names = []
    template_field_dict = {}

    for field in arcpy.ListFields(template):
        fld_name = str(field.name)
        if fld_name.startswith('FGDB_'):
            lst_fgdb_names.append(fld_name)

    long_tbl_name = os.path.basename(long_tbl)
    if not long_tbl_name.endswith('Tbl'):
        long_tbl_name = long_tbl_name.split('Tbl')[0] + 'Tbl'
    where_clause = long_tbl_name + ' IS NOT NULL'
    sql_clause = 'ORDER BY ' + long_tbl_name
    with arcpy.da.SearchCursor(template, lst_fgdb_names + [long_tbl_name], where_clause=where_clause,
                               sql_clause=(None, sql_clause)) as s_cursor:
        for row in s_cursor:
            fld_name = str(row[0])
            fld_alias = str(row[1])
            fld_type = str(row[2])
            fld_width = row[3]
            fld_precision = row[4]
            fld_scale = row[5]

            template_field_dict[fld_name] = [fld_alias, fld_type, fld_width, fld_precision, fld_scale]

    # List of existing fields
    existing_field_names = [f.name for f in arcpy.ListFields(long_tbl)]

    # Fields to add
    fields_to_add = []

    # We only want to add and parse fields where the base field actually exists
    for number in range(1, 4):
        for field in textural_field_base_names:
            if field + str(number) in existing_field_names:
                fields_to_add.append(field + str(number))

        if geo_letter_field + str(number) in existing_field_names:
            fields_to_add.append(geo_letter_field + str(number))
            fields_to_add.append(geo_prime_field + str(number))

    if len(fields_to_add) == 0:
        logger.info("There are no fields to add - This script only needs to be run on BAPIDS containing fields:")
        logger.info(", ".join(textural_field_base_names + [geo_letter_field, geo_prime_field]))
        return

    # Add all fields, if they do not already exist
    fields_to_update = []
    errors = 0
    logger.info("Adding new fields to " + os.path.basename(long_tbl))
    for number in range(1, 4):

        # Add fields with numbers appended to the end
        field_name = geo_prime_field + str(number)
        if field_name in fields_to_add:
            fields_to_update.append(field_name)
            errors += add_field(long_tbl, field_name, template_field_dict, existing_field_names, logger)

        # Add fields with numbers and letters appended to the end
        for field_base in [geo_letter_field, geo_prime_field] + textural_field_base_names:
            field_name = field_base + str(number)
            if field_name in fields_to_add:
                fields_to_update.append(field_name)
                for letter in ['A', 'B', 'C']:
                    fields_to_update.append(field_name + letter)
                    errors += add_field(long_tbl, field_name + letter,
                                        template_field_dict, existing_field_names, logger)

    # We assume all fields were added successfully when updating fields.
    # If there were errors at this point we must exit

    if errors > 0:
        logger.error(str(errors) + " fields could not be added, no fields have been updated. "
                                   "Fix the issues preventing field creation and run the script again")

        sys.exit(0)

    # Create lookup to access fields by name in constant time
    long_tbl_i = dict(zip(fields_to_update, range(len(fields_to_update))))
    updates = 0

    logger.info("Updating new fields in " + os.path.basename(long_tbl))

    with arcpy.da.UpdateCursor(long_tbl, fields_to_update) as cursor:
        for row in cursor:
            updates += 1
            if updates % 1000 == 0:
                logger.debug("   Updating row " + str(updates))

            for number in range(1, 4):

                # ------------------------
                # Populate textural fields
                for field_name in textural_field_base_names:

                    # If the field doesn't exist, do not try to process it
                    if field_name + str(number) not in fields_to_update:
                        continue

                    base_col = long_tbl_i[field_name + str(number)]
                    base_val = row[base_col] if row[base_col] is not None else ''

                    # TEX fields are populated backwards
                    if 'TEX' in field_name:
                        row[long_tbl_i[field_name + str(number) + 'A']] = base_val[-1:]
                        row[long_tbl_i[field_name + str(number) + 'B']] = base_val[-2:-1]
                        row[long_tbl_i[field_name + str(number) + 'C']] = base_val[-3:-2]
                    else:
                        row[long_tbl_i[field_name + str(number) + 'A']] = base_val[0:1]
                        row[long_tbl_i[field_name + str(number) + 'B']] = base_val[1:2]
                        row[long_tbl_i[field_name + str(number) + 'C']] = base_val[2:3]

                # -------------------
                # Populate Geo fields

                # If the field doesn't exist, do not try to process it
                if geo_letter_field + str(number) not in fields_to_update:
                    continue

                base_col = long_tbl_i[geo_letter_field + str(number)]
                base_val = row[base_col] if row[base_col] is not None else ''

                prime_val = '"' if base_val.startswith('"') else ''
                base_val = base_val if prime_val == "" else base_val[1:]

                row[long_tbl_i[geo_prime_field + str(number)]] = prime_val
                row[long_tbl_i[geo_letter_field + str(number) + 'A']] = base_val[0:1]
                row[long_tbl_i[geo_letter_field + str(number) + 'B']] = base_val[1:2]
                row[long_tbl_i[geo_letter_field + str(number) + 'C']] = base_val[2:3]
            cursor.updateRow(row)
    logger.info("Updated " + str(updates) + " rows")


def concatenate_bgc(long_tbl):
    lst_fields = ['BGC_VLD', 'BGC_ZONE', 'BGC_SUBZON', 'BGC_VRT', 'BGC_PHASE']

    with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
        for row in u_cursor:
            str_bec = ''
            if row[1]:
                str_bec = str(row[1])
            if row[2]:
                str_bec += str(row[2])
            if row[3]:
                str_bec += str(row[3])
            if row[4]:
                str_bec += str(row[4])
            row[0] = str_bec
            u_cursor.updateRow(row)


def concatenate_site_dist(long_tbl):
    for i in range(1, 3):
        lst_fields = ['DISTCLS_VLD{}'.format(i), 'DISTCLS_{}'.format(i),
                      'DISTSCLS_{}'.format(i), 'DISSSCLS_{}'.format(i)]

        with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
            for row in u_cursor:
                str_dist = ''
                if row[1]:
                    str_dist = str(row[1])
                if row[2]:
                    str_dist += " " + str(row[2])
                if row[3]:
                    str_dist += " " + str(row[3])
                row[0] = str_dist
                u_cursor.updateRow(row)


def concatenate_sec(long_tbl):
    for i in range(1, 3):
        lst_fields = ['SECL_VLD{}'.format(i), 'SECL_{}'.format(i), 'SESUBCL_{}'.format(i)]

        with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
            for row in u_cursor:
                str_sec = ''
                if row[1]:
                    str_sec = str(row[1])
                if row[2]:
                    str_sec += " " + str(row[2])
                row[0] = str_sec
                u_cursor.updateRow(row)


def concatenate_geop(long_tbl):
    for i in range(1, 3):
        lst_fields = ['GEOP_VLD{}A'.format(i), 'GEOP_VLD{}B'.format(i), 'GEOP_VLD{}C'.format(i),
                      'GEOP_{}'.format(i), 'GEOP_SCM{}A'.format(i), 'GEOP_SCM{}B'.format(i), 'GEOP_SCM{}C'.format(i)]

        with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
            for row in u_cursor:
                str_geop = ''
                if row[3]:
                    str_geop = str(row[3])
                if row[4]:
                    row[0] = str_geop + " " + str(row[4])
                if row[5]:
                    row[1] = str_geop + " " + str(row[5])
                if row[6]:
                    row[2] = str_geop + " " + str(row[6])

                u_cursor.updateRow(row)


def parse_modifiers(long_tbl, tat_database, logger):

    long_tbl_fields = get_field_list(tat_database, 'TEIS_Master_Long_Tbl')

    # 2020-03-10 Contractor Package updated to include ZONE, SUBZON, VRT, and PHASE
    # It is no longer required to populate them here

    # lst_fields = ['BGC_VLD', 'BGC_ZONE', 'BGC_SUBZON', 'BGC_VRT', 'BGC_PHASE']
    # lst_fc_fields = [f.name for f in arcpy.ListFields(long_tbl)]
    # if lst_fields[0] in lst_fc_fields:
    #     for field in lst_fields[1:]:
    #         if field not in lst_fc_fields and field in long_tbl_fields.keys():
    #             add_field(long_tbl, field, long_tbl_fields, lst_fc_fields, logger)
    #     with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
    #         for row in u_cursor:
    #             if row[0]:
    #                 str_bec = row[0]
    #                 lst_bec = str_bec.split(' ')
    #
    #                 row[1] = lst_bec[0]
    #                 lst_bec = lst_bec[1:]
    #                 if lst_bec:
    #                     row[2] = lst_bec[0]
    #                     lst_bec = lst_bec[1:]
    #                     if lst_bec:
    #                         if str(lst_bec[0]).isdigit():
    #                             row[3] = lst_bec[0]
    #                             lst_bec = lst_bec[1:]
    #                             if lst_bec:
    #                                 row[4] = lst_bec[0]
    #                         else:
    #                             row[4] = lst_bec[0]
    #                 u_cursor.updateRow(row)

    for i in range(1, 3):
        lst_fields = ['DISTCLS_VLD{}'.format(i), 'DISTCLS_{}'.format(i),
                      'DISTSCLS_{}'.format(i), 'DISSSCLS_{}'.format(i)]

        lst_fc_fields = [f.name for f in arcpy.ListFields(long_tbl)]
        if lst_fields[0] in lst_fc_fields:
            for field in lst_fields[1:]:
                if field not in lst_fc_fields and field in long_tbl_fields.keys():
                    add_field(long_tbl, field, long_tbl_fields, lst_fc_fields, logger)

            with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
                for row in u_cursor:
                    if row[0]:
                        str_dist = row[0]
                        lst_dist = list(str_dist)
                        if ' ' in lst_dist:
                            lst_dist.remove(' ')

                        row[1] = lst_dist[0]
                        lst_dist = lst_dist[1:]
                        if lst_dist:
                            row[2] = lst_dist[0]
                            lst_dist = lst_dist[1:]
                            if lst_dist:
                                row[3] = lst_dist[0]

                        u_cursor.updateRow(row)

    for i in range(1, 3):
        lst_fields = ['SECL_VLD{}'.format(i), 'SECL_{}'.format(i), 'SESUBCL_{}'.format(i)]
        lst_fc_fields = [f.name for f in arcpy.ListFields(long_tbl)]
        if lst_fields[0] in lst_fc_fields:
            for field in lst_fields[1:]:
                if field not in lst_fc_fields and field in long_tbl_fields.keys():
                    add_field(long_tbl, field, long_tbl_fields, lst_fc_fields, logger)

            with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
                for row in u_cursor:
                    if row[0]:
                        str_sec = row[0]
                        lst_sec = str_sec.split(' ')

                        row[1] = lst_sec[0]
                        lst_sec = lst_sec[1:]
                        if lst_sec:
                            row[2] = lst_sec[0]

                        u_cursor.updateRow(row)

    for i in range(1, 3):
        lst_fields = ['GEOP_VLD{}A'.format(i), 'GEOP_VLD{}B'.format(i), 'GEOP_VLD{}C'.format(i),
                      'GEOP_SCM{}A'.format(i), 'GEOP_SCM{}B'.format(i), 'GEOP_SCM{}C'.format(i)]

        lst_fc_fields = [f.name for f in arcpy.ListFields(long_tbl)]
        if lst_fields[0] in lst_fc_fields and lst_fields[1] in lst_fc_fields and lst_fields[2] in lst_fc_fields:
            for field in lst_fields[3:]:
                if field not in lst_fc_fields and field in long_tbl_fields.keys():
                    add_field(long_tbl, field, long_tbl_fields, lst_fc_fields, logger)

            with arcpy.da.UpdateCursor(long_tbl, lst_fields) as u_cursor:
                for row in u_cursor:
                    if row[0] and len(row[0]) > 1:
                        row[3] = row[0][-1]
                    if row[1] and len(row[1]) > 1:
                        row[4] = row[1][-1]
                    if row[2] and len(row[2]) > 1:
                        row[5] = row[2][-1]

                    u_cursor.updateRow(row)


def get_field_list(tat_database, fc_name):
    tbl_metadata_items = os.path.join(tat_database, 'TEIS_Metadata_Items')

    lst_fgdb_names = []
    dict_fields = {}

    for field in arcpy.ListFields(tbl_metadata_items):
        fld_name = str(field.name)
        if fld_name.startswith('FGDB_'):
            lst_fgdb_names.append(fld_name)

    where_clause = fc_name + ' IS NOT NULL'
    sql_clause = 'ORDER BY ' + fc_name
    with arcpy.da.SearchCursor(tbl_metadata_items, lst_fgdb_names + [fc_name], where_clause=where_clause,
                               sql_clause=(None, sql_clause)) as s_cursor:
        for row in s_cursor:
            fld_name = str(row[0])
            fld_alias = str(row[1])
            fld_type = str(row[2])
            fld_width = row[3]
            fld_precision = row[4]
            fld_scale = row[5]

            dict_fields[fld_name] = [fld_alias, fld_type, fld_width, fld_precision, fld_scale]

    return dict_fields


# Add field to table if it does not yet exist in the table and exists in the template
def add_field(table, field_name, template_fields, existing_field_names, logger):
        if field_name in existing_field_names:
            logger.info("    SKIPPING - field [" + field_name + "] already exists")
            return 0

        if field_name in template_fields:
            try:

                fld_alias = template_fields[field_name][0]
                fld_type = template_fields[field_name][1]
                fld_width = template_fields[field_name][2]
                fld_precision = template_fields[field_name][3]
                fld_scale = template_fields[field_name][4]
                arcpy.AddField_management(table,
                                          field_name=field_name,
                                          field_alias=fld_alias,
                                          field_type=fld_type,
                                          field_length=fld_width,
                                          field_precision=fld_precision,
                                          field_scale=fld_scale)
                logger.info("    Added field [" + field_name + "]")
                return 0

            except Exception as e:

                logger.warning("    Could not add field [" + field_name + "]")
                logger.warning(str(e))
                return 1
        else:
            logger.warning("    Field [" + field_name + "] does not exist in template")
            return 1


if __name__ == '__main__':
    try:
        # Import arcpy
        import arcpy
    except:
        logging.error('No ArcGIS licenses available to run this tool.  Program terminating.')
        sys.exit(1)
    run_app()
else:
    try:
        # Import arcpy
        import arcpy
    except:
        logging.error('No ArcGIS licenses available to run this tool.  Program terminating.')
        sys.exit(1)
