"""
For documentation on this script, run with -h flag
"""
from __future__ import division

import sys
import logging
import os
import time

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


def run_app():
    """Run the logic of application"""
    fc, re, logger = get_input_parameter()

    start_time = time.time()
    input_fc = fc
    if re in ['Renumber', 'true']:
        renumber = True
    else:
        renumber = False

    logger.info('    Python ' + sys.version)

    # Set the Geoprocessing environment...
    arcpy.OverwriteOutput = 1

    logger.info('    - Setting Up Workspace')

    if not arcpy.Exists(input_fc):
        logger.error(' **** User-specified input feature class does not exist: ' + input_fc)
        sys.exit(100)

    check_unique_teis_id(input_fc, renumber, logger)

    end_time = time.time()
    execution_time = sanitize_elapsed_time(end_time - start_time)

    logger.info('    Execution Time: ' + execution_time)
    logger.info('Next Script: StandardizeNoData')
    logger.info('Preconditions for next step: None')


def get_input_parameter():
    try:
        # Parse arguments
        parser = ArgumentParser(description='This script reports and optionally renumbers duplicate or null values '
                                            'found in the TEIS_ID field of the input feature class. If the Renumber '
                                            'option is used, it will determine the current highest number in the '
                                            'TEIS_ID field and renumber the duplicates beginning at a number one '
                                            'higher than that highest number. If the NoRenumber option is specified, '
                                            'the script will simply report on screen that duplicates were detected, '
                                            'and list the first ten duplicate values found.',
                                formatter_class=RawTextHelpFormatter)
        parser.add_argument('fc', help='Feature class to be validated (long table feature class in an FGDB).')
        parser.add_argument('re', help='{Renumber|NoRenumber} : Specify Renumber if you wish to renumber any '
                            'duplicate TEIS_ID values found.')
        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)

        return args.fc, args.re, logger

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


def check_unique_teis_id(input_fc, renumber, logger):
    logger.info('    - Checking uniqueness of TEIS_ID')
    process_status = 0

    # check if TEIS_ID field exists
    if "TEIS_ID" not in [f.name for f in arcpy.ListFields(input_fc)]:
        logger.error(' **** Input feature class does not contain a TEIS_ID field.')
        sys.exit(100)

    cursor_field_list = ["TEIS_ID"]

    teis_id_dict = {}
    highest_teis_id = 0
    duplicate_list = []
    dup_counter = 0

    num_features = int(arcpy.GetCount_management(input_fc).getOutput(0))
    num_processed = 0

    logger.info('        - Reading individual values for ' + str(num_features) + ' features.')

    dt_calc_start = time.time()
    
    with arcpy.da.UpdateCursor(input_fc, cursor_field_list) as cursor:

        for row in cursor:

            num_processed += 1

            if str(row[0]) == 'None':
                if renumber:
                    dup_counter += 1
                else:
                    if len(duplicate_list) < 10:
                        duplicate_list.append(row[0])
                        # just store the first 10 duplicate values and list them at the end if there are any

            else:

                if row[0] > highest_teis_id:
                    highest_teis_id = row[0]
                    # lists of values stored in groups of 100, stored in dictionary indexed on key value
                    # int(TEIS_ID / 100). Makes checking for duplicates faster.
                dict_key = int(str(row[0]).zfill(3)[:-2])
                if dict_key not in teis_id_dict:
                    teis_id_dict[dict_key] = []

                if row[0] not in teis_id_dict[dict_key]:
                    teis_id_dict[dict_key].append(row[0])
                else:
                    if renumber:
                        dup_counter += 1
                        row[0] = None
                        # set duplicate value to Null for now; will renumber all Nulls in next step once we know the
                        # current highest value to start numbering at.
                        cursor.updateRow(row)
                    else:
                        if len(duplicate_list) < 10:
                            # just store the first 10 duplicate values and list them at the end if there are any
                            duplicate_list.append(row[0])

                if (num_processed <= 10000 and num_processed % 1000 == 0) or (
                        10000 < num_processed <= 100000 and num_processed % 10000 == 0) or (
                        100000 < num_processed and num_processed % 100000 == 0):
                    dt_calc_now = time.time()
                    dt_calc_elapsed = dt_calc_now - dt_calc_start
                    dt_calc_remaining = (dt_calc_elapsed / num_processed) * (num_features - num_processed)
                    logger.info('            - Processed ' + str(num_processed) + ' of ' + str(
                                 num_features) + ' (' + sanitize_elapsed_time(dt_calc_elapsed)[
                                 :-1] + ' elapsed, ' + sanitize_elapsed_time(dt_calc_remaining)[
                                 :-1] + ' remaining)')

    logger.info('            - Processed all ' + str(num_processed))

    if renumber:
        if dup_counter > 0:

            num_processed = 0
            logger.info('        - Renumbering ' + str(dup_counter) +
                        ' duplicate/null TEIS_ID value(s); updated value(s) will start at ' + str(highest_teis_id + 1))
            dt_calc_start = time.time()

            with arcpy.da.UpdateCursor(input_fc, cursor_field_list) as cursor:

                for row in cursor:

                    num_processed += 1

                    if row[0] is None:
                        highest_teis_id += 1
                        row[0] = highest_teis_id
                        cursor.updateRow(row)

                    if (num_processed <= 10000 and num_processed % 1000 == 0) or (
                            10000 < num_processed <= 100000 and num_processed % 10000 == 0) or (
                            100000 < num_processed and num_processed % 100000 == 0):
                        dt_calc_now = time.time()
                        dt_calc_elapsed = dt_calc_now - dt_calc_start
                        dt_calc_remaining = (dt_calc_elapsed / num_processed) * (num_features - num_processed)
                        logger.info('            - Processed ' + str(num_processed) + ' of ' + str(num_features) +
                                    ' (' + sanitize_elapsed_time(dt_calc_elapsed)[:-1] + ' elapsed, ' +
                                    sanitize_elapsed_time(dt_calc_remaining)[:-1] + ' remaining)')

            logger.info('            - Processed all ' + str(num_processed))

        else:
            logger.info('        - No duplicate or null TEIS_ID values found.')

    elif not renumber:
        if len(duplicate_list) == 0:
            logger.info('        - No duplicate or null TEIS_ID values found.')
        else:
            logger.info('    **** Duplicate and/or null TEIS_ID values found. Listing up to the first ten '
                        'duplicate/null values found: ' + str(duplicate_list).replace('[', '').replace(']', '').
                        replace(' ', '').replace('None', '<Null>'))

    return process_status


def stra(value):
    """
    Converts any value to a string. Replaces Python's stra() which incurs an error if the value
    happens to have a unicode character in it. Unicode characters will be replaced with ?

    :param any value: input value
    :return: value as string
    :rtype: str
    """
    if sys.version_info[0] < 3:
        return unicode(value).encode('ascii', 'replace')
    else:
        return str(value)


def sanitize_elapsed_time(seconds):
    """
    Routine to convert a raw number of seconds into a human-friendly statement of duration

    :param seconds: number of seconds
    :return: formatted duration
    :rtype: str
    """

    if seconds < 120:
        str_elapsed_time = stra(int(seconds)) + ' seconds.'
    elif seconds < 3600:
        str_elapsed_time = stra(int(seconds)) + ' seconds (' + stra(round(seconds / 60, 1)) + ' minutes).'
    else:
        str_elapsed_time = stra(int(seconds)) + ' seconds (' + stra(round(seconds / 3600, 2)) + ' hours).'

    return str_elapsed_time


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)
