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

import sys
import os
import time
import logging
import pandas as pd

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


class ArcPyLogHandler(logging.StreamHandler):
    """
    Custom logging class that bounces messages to the arcpy tool window as well
    as reflecting back to the file.
    """

    def emit(self, record):
        """
        Write the log message
        """
        try:
            msg = record.msg.format(record.args)
        except:
            msg = record.msg

        if record.levelno == logging.ERROR:
            arcpy.AddError(msg)
        elif record.levelno == logging.WARNING:
            arcpy.AddWarning(msg)
        elif record.levelno == logging.INFO:
            arcpy.AddMessage(msg)
        else:
            arcpy.AddMessage(msg)

        super(ArcPyLogHandler, self).emit(record)


def run_app():
    tools_and_temp_path, input_fc, logger = get_input_parameter()
    validate_data(tools_and_temp_path, input_fc, logger)


def get_input_parameter():
    try:
        # Parse arguments
        parser = ArgumentParser(description='This script uses the TEIS_Metadata_* and Domain_* tables in the '
                                            'Tools_and_Templates.gdb file geodatabase to validate an input feature '
                                            'class or table against defined attribute domains and ranges.',
                                formatter_class=RawTextHelpFormatter)
        parser.add_argument('tat', help='Path to master TEIS environment "Tools_and_Templates.gdb"')
        parser.add_argument('fc', help='Feature class to be validated')
        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)
        logger.handlers = []

        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)

        if os.path.basename(sys.executable).lower() == 'python.exe':
            arc_env = False
        else:
            arc_env = True

        if arc_env:
            arc_handler = ArcPyLogHandler()
            arc_handler.setLevel(args.log_level)
            arc_handler.setFormatter(log_fmt)
            logger.addHandler(arc_handler)

        # Start the script
        return args.tat, args.fc, logger

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


def validate_data(tools_and_temp_path, input_fc, logger):
    start_time = time.time()
    logger.info('    - Initializing Script')
    script_dir = os.path.split(os.path.split(sys.argv[0])[0])[0]
    arcpy.OverwriteOutput = 1

    teis_metadata_items = tools_and_temp_path + r'\TEIS_Metadata_Items'
    albers_prj = script_dir + r'\prj\NAD_1983_BC_Environment_Albers.prj'

    if not arcpy.Exists(input_fc):
        logger.error('** ERROR: INPUT DOES NOT EXIST: ' + input_fc)
        sys.exit(100)

    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)

    if not arcpy.Exists(tools_and_temp_path):
        logger.error(' **** Tools and Templates FGDB does not exist: ' + tools_and_temp_path)
        sys.exit(100)

    input_description = arcpy.Describe(input_fc)
    input_datatype = input_description.dataType

    logger.info('    - Loading Domains, Ranges, Critical Ranges and Mandatory Fields to Memory')

    original_workspace = arcpy.env.workspace

    # Get a list of all the domains in the Tools and Templates FGDB based on naming convention.
    domains = {}
    str_bec_version = ''

    arcpy.env.workspace = tools_and_temp_path
    domain_list = arcpy.ListTables('Domain_*')

    logger.info('        - There are ' + stra(len(domain_list)) + ' domains to load.')

    map_codes = os.path.join(os.path.dirname(input_fc), 'TEI_Ecosystem_Code_Submission')

    # Load each domain into a Python Dictionary
    for domain in domain_list:
        domain_name = domain.split('Domain_')[1].upper()
        num_features = int(arcpy.GetCount_management(domain).getOutput(0))
        num_processed = 0

        logger.debug('        - Getting individual values for ' + domain_name + ' (' + stra(num_features) + ' codes).')

        domain_values = []

        if domain_name == "BGC_LBL":
            dom_fields = ['CODE', 'NOTES']
        elif domain_name == "SITE_SERIES":
            dom_fields = ['SHORT_NAME', 'CODE']
        else:
            dom_fields = ['CODE']


        with arcpy.da.SearchCursor(domain, dom_fields) as cursor:
            for row in cursor:
                if num_processed % 1000 == 0:
                    logger.debug('            - Processed ' + stra(num_processed))

                curr_val = row[0]
                logger.debug('                - ' + stra(curr_val))

                if domain_name == "BGC_LBL":
                    if curr_val is not None:
                        curr_val = curr_val.replace(' ', '')
                    str_bec_version = row[1]

                if domain_name == "SITE_SERIES":
                    if row[1] == 'All Units':
                        curr_val = curr_val.replace(' ', '')

                domain_values.append(curr_val)

                num_processed = num_processed + 1

        logger.debug('            - Processed all ' + stra(num_processed))
        
        # When validating the contractor package, values coming from the TEI_Ecosystem_Code_Submission are accepted

        if domain_name in ['BGC_LBL', 'MAPCODES', 'SITE_SERIES'] and arcpy.Exists(map_codes):
            map_codes_count = arcpy.GetCount_management(map_codes)
            
            if int(map_codes_count.getOutput(0)) > 0:                   

                if domain_name == "BGC_LBL":

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

                    map_code_fields = ['BGC_LBL']

                    with arcpy.da.SearchCursor(map_codes, map_code_fields) as cursor:
                        for row in cursor:
                            if num_processed % 1000 == 0:
                                logger.debug('            - Processed ' + stra(num_processed))

                            curr_val = ''
                            if row[0]:
                                logger.debug('        - Adding BGC_LBL values from ' + map_codes + 'to user submitted ecosystem codes.')
                                curr_val = stra(row[0])

                            logger.debug('                - ' + stra(curr_val))
                            domain_values.append(curr_val)
                            num_processed = num_processed + 1

                if domain_name == "MAPCODES":

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

                    map_code_fields = ['SITEMC_S']

                    with arcpy.da.SearchCursor(map_codes, map_code_fields) as cursor:
                        for row in cursor:
                            if num_processed % 1000 == 0:
                                logger.debug('            - Processed ' + stra(num_processed))

                            curr_val = ''
                            if row[0]:
                                logger.debug('        - Adding SITEMC_S values from ' + map_codes + 'to user submitted ecosystem codes.')
                                curr_val = str(row[0])

                            logger.debug('                - ' + stra(curr_val))
                            domain_values.append(curr_val)
                            num_processed = num_processed + 1

                if domain_name == "SITE_SERIES":

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

                    map_code_fields = ['SITE_S', 'NBEC_S']
                    
                    with arcpy.da.SearchCursor(map_codes, map_code_fields) as cursor:
                        for row in cursor:
                            if num_processed % 1000 == 0:
                                logger.debug('            - Processed ' + stra(num_processed))

                            curr_val = ''
                            if row[0]:
                                logger.debug('        - Adding SITE_S values from ' + map_codes + 'to user submitted ecosystem codes.')
                                logger.debug('                - ' + stra(curr_val))
                                domain_values.append(str(row[0]))
                                num_processed = num_processed + 1

                            if row[1]:
                                logger.debug('        - Adding NBEC_S values from ' + map_codes + 'to user submitted ecosystem codes.')
                                logger.debug('                - ' + stra(curr_val))
                                domain_values.append(str(row[1]))
                                num_processed = num_processed + 1

        domains[domain_name] = domain_values

    # Update to match the Metadata tables, not the QC_* ones
    domain_cross_reference = {}
    ranges = {}
    critical_ranges = {}
    mandatory_fields = []
    mandatory_fields_t = []
    mandatory_fields_em = []
    mandatory_fields_sei = []

    num_features = int(arcpy.GetCount_management(teis_metadata_items).getOutput(0))
    logger.debug('        - Getting Domain Cross References, Ranges, Critical Ranges and Mandatory Fields for ' +
                 stra(num_features) + ' items.')
    num_processed = 0

    meta_data_fields = ['FGDB_Name', 'Item_Domain_Name', 'Item_Range_Minimum', 'Item_Range_Maximum', 'Oracle_Type',
                        'Oracle_Length', 'TEIS_Master_Short_Tbl', 'TEIS_Master_Long_Tbl',
                        'TEIS_Master_Project_Boundaries', 'Item_Mandatory', 'Item_Mandatory_TER', 'Item_Mandatory_TEM',
                        'Item_Mandatory_SEI']
    with arcpy.da.SearchCursor(teis_metadata_items, meta_data_fields) as cursor:
        for row in cursor:

            if num_processed % 1000 == 0:
                logger.debug('            - Processed ' + stra(num_processed))

            # Load Domains

            cur_field = row[0].upper()
            cur_domain_name = row[1]
            if cur_domain_name in [None, '']:
                cur_domain_name = 'NO DOMAIN FOUND'
            cur_domain_name = cur_domain_name.upper()
            logger.debug('                - ' + cur_field + ' : ' + cur_domain_name)

            if cur_domain_name not in domains.keys() and cur_domain_name != 'NO DOMAIN FOUND':
                logger.warning('** Warning Domain Name Referenced without Values: ' + cur_domain_name)
            elif cur_domain_name in domains.keys():
                domain_cross_reference[cur_field] = cur_domain_name

            # Load Ranges

            cur_range_min = row[2]
            cur_range_max = row[3]
            logger.debug(cur_field + ' : ' + stra(cur_range_min) + ' to ' + stra(cur_range_max))

            if cur_range_min and cur_range_max:
                if cur_range_min > cur_range_max:
                    logger.warning('** Warning Range Values Invalid (may be swapped): ' + cur_field)
                elif cur_range_min is not None and cur_range_max is not None:
                    ranges[cur_field] = {'Minimum': cur_range_min, 'Maximum': cur_range_max}
                elif cur_range_min is not None or cur_range_max is not None:
                    logger.warning('** Warning Range Values Invalid (min and max not defined): ' + cur_field)

            # Load Critical Ranges

            cur_oracle_type = row[4]
            cur_oracle_len = row[5]
            cur_short_tbl_num = row[6]
            cur_proj_bound_num = row[8]

            if (cur_short_tbl_num is not None or cur_proj_bound_num is not None) and cur_oracle_type == 'NUMBER':
                cur_crit_range_max = 10 ** cur_oracle_len - 1
                cur_crit_range_min = 0
                critical_ranges[cur_field] = {'Minimum': cur_crit_range_min, 'Maximum': cur_crit_range_max}
                logger.debug(cur_field + ' : ' + stra(cur_crit_range_min) + ' to ' + stra(cur_crit_range_max))

            # Load Mandatory Fields

            cur_mandatory = row[9]
            cur_mandatory_t = row[10]
            cur_mandatory_em = row[11]
            cur_mandatory_sei = row[12]
            logger.debug('            - ' + cur_field + ' : ')

            if cur_mandatory == 'Y':
                logger.debug('                - Mandatory for all project types')
                mandatory_fields.append(cur_field)
            if cur_mandatory_t == 'Y':
                logger.debug('                - Mandatory for %TER% project types')
                mandatory_fields_t.append(cur_field)
            if cur_mandatory_em == 'Y':
                logger.debug('                - Mandatory for %TEM% project types')
                mandatory_fields_em.append(cur_field)
            if cur_mandatory_sei == 'Y':
                logger.debug('                - Mandatory for %SEI% project types')
                mandatory_fields_sei.append(cur_field)

            num_processed = num_processed + 1

    logger.debug('            - Processed all ' + stra(num_processed))
    fields_x_domains = dict(domain_cross_reference)
    arcpy.env.workspace = original_workspace

    check_everything(input_fc, fields_x_domains, ranges, critical_ranges, domains, tools_and_temp_path,
                     mandatory_fields, mandatory_fields_t, mandatory_fields_em, mandatory_fields_sei,
                     str_bec_version, logger)

    if input_datatype == 'FeatureClass':
        test_geometry(input_fc, logger)
        test_tolerance(input_fc, logger)
        test_projection(albers_prj, input_fc, logger)
    else:
        logger.info(' ** Input dataset is not a feature class with geometry. No geometry testing needed. **')

    end_time = time.time()

    execution_time = sanitize_elapsed_time(end_time - start_time)
    logger.info('    Execution Time: ' + stra(execution_time))


def check_everything(input_fc, fields_x_domain, ranges, critical_ranges, domains, tools_and_temp_path,
                     mandatory_fields, mandatory_fields_t, mandatory_fields_em, mandatory_fields_sei,
                     str_bec_version, logger):

    domain_error_table = input_fc + r'_DomainErrors'
    range_error_table = input_fc + r'_RangeErrors'
    critical_range_error_table = input_fc + r'_CriticalRangeErrors'
    row_error_table = input_fc + r'_RowErrors'
    critical_field_error_table = input_fc + r'_CriticalFieldErrors'
    summary_error_table = input_fc + r'_SummaryErrors'

    # Create/overwrite the old error tables
    logger.info('        -- Initializing Domain Error Table.')
    if arcpy.Exists(domain_error_table):
        arcpy.Delete_management(domain_error_table)
    create_templates(tools_and_temp_path, os.path.split(domain_error_table)[0],
                     [os.path.split(domain_error_table)[1]], logger)

    logger.info('        -- Initializing Range Error Table.')
    if arcpy.Exists(range_error_table):
        arcpy.Delete_management(range_error_table)
    create_templates(tools_and_temp_path, os.path.split(range_error_table)[0],
                     [os.path.split(range_error_table)[1]], logger)

    logger.info('        -- Initializing Critical Range Error Table.')
    if arcpy.Exists(critical_range_error_table):
        arcpy.Delete_management(critical_range_error_table)
    create_templates(tools_and_temp_path, os.path.split(critical_range_error_table)[0],
                     [os.path.split(critical_range_error_table)[1]], logger)

    logger.info('        -- Initializing Row Error Table.')
    if arcpy.Exists(row_error_table):
        arcpy.Delete_management(row_error_table)
    create_templates(tools_and_temp_path, os.path.split(row_error_table)[0],
                     [os.path.split(row_error_table)[1]], logger)

    logger.info('        -- Initializing Critical Field Error Table.')
    if arcpy.Exists(critical_field_error_table):
        arcpy.Delete_management(critical_field_error_table)
    create_templates(tools_and_temp_path, os.path.split(critical_field_error_table)[0],
                     [os.path.split(critical_field_error_table)[1]], logger)

    logger.info('        -- Initializing Summary Error Table.')
    if arcpy.Exists(summary_error_table):
        arcpy.Delete_management(summary_error_table)
    create_templates(tools_and_temp_path, os.path.split(summary_error_table)[0],
                     [os.path.split(summary_error_table)[1]], logger)

    rows = ['TDEC_1', 'TDEC_2', 'TDEC_3', 'SDEC_1', 'SDEC_2', 'SDEC_3', 'TTEX_1', 'TTEX_1A', 'TTEX_1B', 'TTEX_1C',
            'TTEX_2', 'TTEX_2A', 'TTEX_2B', 'TTEX_2C', 'TTEX_3', 'TTEX_3A', 'TTEX_3B', 'TTEX_3C', 'TTTEX_1', 'TTTEX_1A',
            'TTTEX_1B', 'TTTEX_1C', 'TTTEX_2', 'TTTEX_2A', 'TTTEX_2B', 'TTTEX_2C', 'TTTEX_3', 'TTTEX_3A', 'TTTEX_3B',
            'TTTEX_3C', 'STTEX_1', 'STTEX_1A', 'STTEX_1B', 'STTEX_1C', 'STTEX_2', 'STTEX_2A', 'STTEX_2B', 'STTEX_2C',
            'STTEX_3', 'STTEX_3A', 'STTEX_3B', 'STTEX_3C', 'SURF_E1', 'SURF_E1A', 'SURF_E1B', 'SURF_E1C', 'SURF_E2',
            'SURF_E2A', 'SURF_E2B', 'SURF_E2C', 'SURF_E3', 'SURF_E3A', 'SURF_E3B', 'SURF_E3C', 'TSURF_E1', 'TSURF_E1A',
            'TSURF_E1B', 'TSURF_E1C', 'TSURF_E2', 'TSURF_E2A', 'TSURF_E2B', 'TSURF_E2C', 'TSURF_E3', 'TSURF_E3A',
            'TSURF_E3B', 'TSURF_E3C', 'SSURF_E1', 'SSURF_E1A', 'SSURF_E1B', 'SSURF_E1C', 'SSURF_E2', 'SSURF_E2A',
            'SSURF_E2B', 'SSURF_E2C', 'SSURF_E3', 'SSURF_E3A', 'SSURF_E3B', 'SSURF_E3C', 'GEOPRO_1', 'GEOPRO_Q1', 
            'GEOPRO_2', 'GEOPRO_Q2', 'GEOPRO_3', 'GEOPRO_Q3', 'GEOPRO_4', 'GEOPRO_Q4', 'GEOPRO_5', 'GEOPRO_Q5', 
            'GEOPRO_6', 'GEOPRO_Q6', 'GEOPRO_COM', 'SLPLL_1', 'SLPUL_1',
            'SLPLL_2', 'SLPUL_2', 'SITE_S1', 'SITE_S2', 'SITE_S3', 'PROJ_ID', 'SITEMC_S1', 'SITEMC_S2', 'SITEMC_S3',
            'REALM_1', 'REALM_2', 'REALM_3', 'GROUP_1', 'GROUP_2', 'GROUP_3', 'CLASS_1', 'CLASS_2', 'CLASS_3', 
            'SITE_M1A', 'SITE_M2A', 'SITE_M3A', 'SITE_M1B', 'SITE_M2B', 'SITE_M3B',
            'SITEAM_S1A', 'SITEAM_S1B', 'SITEAM_S1C', 'SITEAM_S1D', 'SITEAM_S2A', 'SITEAM_S2B', 'SITEAM_S2C', 'SITEAM_S2D',
            'SITEAM_S3A', 'SITEAM_S3B', 'SITEAM_S3C', 'SITEAM_S3D', 'BGC_LBL', 'SDEC_1', 'SDEC_2', 'SDEC_3']

    logger.info('    - Checking Domains, Ranges, Critical Ranges, Rows, Mandatory Fields')

    # Get the Field List
    fields = arcpy.ListFields(input_fc)
    field_names = []
    for field in fields:
        field_names.append(field.name)
    
    cursor_fields = []
    domain_check_fields = []
    
    logger.info('        - FieldsXDomains: ' + stra(fields_x_domain))
    
    for cur_field in fields:

        if cur_field.name in fields_x_domain:
            domain_check_fields.append(cur_field.name)
            if cur_field.name not in cursor_fields:
                cursor_fields.append(cur_field.name)
            logger.debug('            - Checking Field: ' + stra(cur_field.name))
        else:
            logger.debug('            - Not Checking Field: ' + stra(cur_field.name))

    logger.info('        - Checking Fields for Domains: ' + stra(domain_check_fields).replace('[', '')
                .replace(']', '')
                .replace("', u'", ', ')
                .replace("u'", "'")
                .replace("'", ""))

    range_check_fields = []
    
    logger.info('        - Ranges: ' + stra(ranges.keys()))
    
    for cur_field in fields:
        if cur_field.name.upper() in ranges:
            range_check_fields.append(cur_field.name)
            if cur_field.name not in cursor_fields:
                cursor_fields.append(cur_field.name)
            logger.debug('            - Checking Field: ' + stra(cur_field.name))
        else:
            logger.debug('            - Not Checking Field: ' + stra(cur_field.name))

    logger.info('        - Checking Fields for Ranges: ' + stra(range_check_fields)
                .replace('[', '')
                .replace(']', '')
                .replace("', u'", ', ')
                .replace("u'", "'")
                .replace("'", ""))

    crit_range_check_fields = []
    
    logger.info('        - Critical Ranges: ' + stra(critical_ranges.keys()))
    
    for cur_field in fields:
        if cur_field.name.upper() in critical_ranges:
            crit_range_check_fields.append(cur_field.name)
            if cur_field.name not in cursor_fields:
                cursor_fields.append(cur_field.name)
            logger.debug('            - Checking Field: ' + stra(cur_field.name))
        else:
            logger.debug('            - Not Checking Field: ' + stra(cur_field.name))

    logger.info('        - Checking Fields for Critical Ranges: ' + stra(crit_range_check_fields)
                .replace('[', '')
                .replace(']', '')
                .replace("', u'", ', ')
                .replace("u'", "'")
                .replace("'", ""))

    for cur_field_name in rows:
        if cur_field_name in field_names and cur_field_name not in cursor_fields:
            cursor_fields.append(cur_field_name)

    mandatory_check_fields = []
    mandatory_check_fields_t = []
    mandatory_check_fields_em = []
    mandatory_check_fields_sei = []

    for cur_field in fields:

        if cur_field.name in mandatory_fields:
            mandatory_check_fields.append(cur_field.name)
            if cur_field.name not in cursor_fields:
                cursor_fields.append(cur_field.name)

        if cur_field.name in mandatory_fields_t:
            mandatory_check_fields_t.append(cur_field.name)
            if cur_field.name not in cursor_fields:
                cursor_fields.append(cur_field.name)

        if cur_field.name in mandatory_fields_em:
            mandatory_check_fields_em.append(cur_field.name)
            if cur_field.name not in cursor_fields:
                cursor_fields.append(cur_field.name)

        if cur_field.name in mandatory_fields_sei:
            mandatory_check_fields_sei.append(cur_field.name)
            if cur_field.name not in cursor_fields:
                cursor_fields.append(cur_field.name)

    logger.info('        - Checking Mandatory Fields: ' +
                stra(mandatory_check_fields)
                .replace('[', '')
                .replace(']', '')
                .replace("', u'", ', ')
                .replace("u'", "'")
                .replace("'", ""))
    logger.info('        - Checking Mandatory Fields for %TER% Project Types: ' +
                stra(mandatory_check_fields_t)
                .replace('[', '')
                .replace(']', '')
                .replace("', u'", ', ')
                .replace("u'", "'")
                .replace("'", ""))
    logger.info('        - Checking Mandatory Fields for %TEM% Project Types: ' +
                stra(mandatory_check_fields_em)
                .replace('[', '')
                .replace(']', '')
                .replace("', u'", ', ')
                .replace("u'", "'")
                .replace("'", ""))
    logger.info('        - Checking Mandatory Fields for %SEI% Project Types: ' +
                stra(mandatory_check_fields_sei)
                .replace('[', '')
                .replace(']', '')
                .replace("', u'", ', ')
                .replace("u'", "'")
                .replace("'", ""))

    #  Key on TEIS_ID, or, for tables (like Project_Details) without TEIS_ID, key on the OID field
    obj_id_field = arcpy.ListFields(input_fc, "*", "OID")[0].name
    if obj_id_field not in cursor_fields:
        cursor_fields.append(obj_id_field)

    if "TEIS_ID" in field_names:
        obj_id_field = "TEIS_ID"
        if "TEIS_ID" not in cursor_fields:
            cursor_fields.append("TEIS_ID")

    if "PROJ_TYPE" in field_names and "PROJ_TYPE" not in cursor_fields:
        cursor_fields.append("PROJ_TYPE")

    if "SHAPE@XY" not in cursor_fields:
        cursor_fields.append("SHAPE@XY")

    input_fc_fields = [f.name for f in arcpy.ListFields(input_fc)]
    lst_bgc_fields = [i for i in input_fc_fields if i in ["BGC_ZONE", "BGC_SUBZON", "BGC_VRT", "BGC_PHASE"]]
    if len(lst_bgc_fields) != 0:
        bl_BGC_LBL = False
        if "BGC_ZONE" not in cursor_fields:
            cursor_fields.append("BGC_ZONE")

        if "BGC_SUBZON" not in cursor_fields:
            cursor_fields.append("BGC_SUBZON")

        if "BGC_VRT" not in cursor_fields:
            cursor_fields.append("BGC_VRT")

        if "BGC_PHASE" not in cursor_fields:
            cursor_fields.append("BGC_PHASE")
    else:
        bl_BGC_LBL = True
        if "BGC_LBL" not in cursor_fields:
            cursor_fields.append("BGC_LBL")

    logger.info('        - Keying on the input table\'s ' + obj_id_field + ' field.')

    num_features = int(arcpy.GetCount_management(input_fc).getOutput(0))
    num_processed = 0
    
    logger.info('        - Checking individual values for ' + stra(num_features) + ' features.')

    field_name_dict = {}
    x = 0
    for cur_field_name in cursor_fields:
        field_name_dict[cur_field_name] = x
        x += 1

    lst_det = []
    lst_ret = []
    lst_cret = []
    lst_rowet = []
    lst_mfet = []
    lst_set = []

    dt_calc_start = time.time()
    error_count = 0

    # When validating the contractor package, values coming from the TEI_Ecosystem_Code_Submission are accepted

    map_codes = os.path.join(os.path.dirname(input_fc), 'TEI_Ecosystem_Code_Submission')
    eco_subm_list = []
    if arcpy.Exists(map_codes):
        map_codes_count = arcpy.GetCount_management(map_codes)
        
        if int(map_codes_count.getOutput(0)) > 0:
            
            all_eco_codes_fields = ['BGC_LBL', 'SITE_S', 'SITEMC_S', 'ECO_REALM', 'ECO_GROUP', 'ECO_CLASS']

            with arcpy.da.SearchCursor(map_codes, all_eco_codes_fields) as cursor:
                for row in cursor:
                    eco_subm_list.append([elem if elem not in [None, 'None', 0, '0', '00', ''] else '-' for elem in row])

    # Get valid eco code combinations of TEI_EcosystemCodes excel and append new user-submitted codes if there are any
    eco_codes_path = find_eco_codes_excel()
    if eco_codes_path:
        logger.info('       - Found TEI_EcosystemCodes Excel file: {}'.format(eco_codes_path))
        df = pd.read_excel(eco_codes_path, sheetname='TEI_EcosystemMappingCodes', parse_cols="B:D,K:M")
        if eco_subm_list:
            logger.info('       - Appending rows from Ecosystem Codes Submission table for validation.')
            df_to_append = pd.DataFrame(eco_subm_list)
            df_to_append.columns = df.columns
            df = pd.concat([df, df_to_append], ignore_index=True)
        df = df.drop_duplicates()
    else:
        logger.error('      - Could not find TEI_EcosystemCodes.xlsx - this file is necessary to cross-validate ecosystem code fields')

    with arcpy.da.SearchCursor(input_fc, cursor_fields) as cursor:

        for row in cursor:
            summary_error_list = []
            bapid = row[field_name_dict['BAPID']]

            # Check Domains

            for field_name in domain_check_fields:
                # Get all item values, check to see if they match the valid ones.
                # Testing numeric domains here!
                if field_name == 'BGC_LBL':
                    test_value = stra(row[field_name_dict[field_name]]).replace(' ', '')
                else:
                    test_value = stra(row[field_name_dict[field_name]])
                test_domain_name = fields_x_domain[field_name]

                if test_value in domains[test_domain_name] or stra(test_value) == 'None' or\
                        stra(test_value) == '' or stra(test_value) == ' ':
                    pass
                else:
                    cur_oid = row[field_name_dict[obj_id_field]]
                    problem_string = 'Invalid Value in ' + field_name + ' (' + stra(test_value) + ').'
                    lst_det.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # Check BEC Domain

            str_bec = ''
            if not bl_BGC_LBL:
                if row[field_name_dict['BGC_ZONE']]:
                    str_bec = stra(row[field_name_dict['BGC_ZONE']])
                if row[field_name_dict['BGC_SUBZON']]:
                    str_bec += stra(row[field_name_dict['BGC_SUBZON']])
                if row[field_name_dict['BGC_VRT']]:
                    str_bec += stra(row[field_name_dict['BGC_VRT']])
                if row[field_name_dict['BGC_PHASE']]:
                    str_bec += stra(row[field_name_dict['BGC_PHASE']])
            else:
                if row[field_name_dict['BGC_LBL']]:
                    str_bec = stra(row[field_name_dict['BGC_LBL']]).replace(' ', '')

            if str_bec in domains['BGC_LBL'] or stra(str_bec) == 'None' or \
                    stra(str_bec) == '' or stra(str_bec) == ' ':
                pass
            else:
                cur_oid = row[field_name_dict[obj_id_field]]
                problem_string = 'Invalid BGC Combination for BEC ' + stra(str_bec_version)\
                                 + ' (' + stra(str_bec) + ').'
                lst_det.append([input_fc, cur_oid, problem_string, bapid])
                summary_error_list.append(problem_string)

            # Check Site Series Domain

            for x in range(1, 4):
                site_sx = 'SITE_S' + stra(x)
                if row[field_name_dict[site_sx]]:
                    ss_code = row[field_name_dict[site_sx]]
                    if ss_code in domains['SITE_SERIES'] or stra(ss_code) == 'None' or \
                            stra(ss_code) == '' or stra(ss_code) == ' ':
                        pass
                    else:
                        cur_oid = row[field_name_dict[obj_id_field]]
                        problem_string = 'Invalid Value in ' + site_sx + ' (' + stra(ss_code) + ').'
                        lst_det.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            # Check Ranges

            for field_name in range_check_fields:
                # Get all item values, check to see if they match the valid ones.
                test_value = row[field_name_dict[field_name]]

                test_range_name = field_name
                test_validation = 'Number'

                if stra(test_value) in ['None', '', ' ']:
                    test_validation = 'Empty'
                if test_validation == 'Number':
                    try:
                        test_value = int(test_value)
                    except Exception as e:
                        cur_oid = row[field_name_dict[obj_id_field]]
                        test_validation = 'NotNumber'
                        problem_string = 'Invalid Value (not an integer) in ' + field_name + ' (' + stra(
                            test_value) + ').'
                        lst_ret.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

                if test_validation == 'Number':
                    if not (ranges[test_range_name]['Minimum'] <= test_value <=
                            ranges[test_range_name]['Maximum']):
                        cur_oid = row[field_name_dict[obj_id_field]]
                        problem_string = 'Invalid Value (out of range) in ' + field_name + ' (' + stra(
                            test_value) + ').'
                        lst_ret.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            # Check Critical Ranges
            for field_name in crit_range_check_fields:
                # Get all item values, check to see if they match the valid ones.
                test_value = row[field_name_dict[field_name]]

                test_range_name = field_name

                test_validation = 'Number'

                if stra(test_value) in ['None', '', ' ']:
                    test_validation = 'Empty'
                if test_validation == 'Number':
                    try:
                        test_value = int(test_value)
                    except Exception as e:
                        cur_oid = row[field_name_dict[obj_id_field]]
                        test_validation = 'NotNumber'
                        problem_string = '** CRITICAL ERROR ** - Invalid Value (not an integer) in ' +\
                                         field_name + ' (' + stra(test_value) + ').'
                        lst_cret.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

                if test_validation == 'Number':
                    if (critical_ranges[test_range_name]['Minimum'] <= test_value <=
                            critical_ranges[test_range_name]['Maximum']):
                        pass
                    else:
                        cur_oid = row[field_name_dict[obj_id_field]]
                        problem_string = '** CRITICAL ERROR ** - invalid number of digits in ' +\
                                         field_name + ' (' + stra(test_value) + ').'
                        lst_cret.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            # Check Rows
            cur_oid = row[field_name_dict[obj_id_field]]

            if check_exists(['TDEC_1', 'TDEC_2', 'TDEC_3'], field_name_dict):
                tdec_1 = row[field_name_dict['TDEC_1']]
                tdec_2 = row[field_name_dict['TDEC_2']]
                tdec_3 = row[field_name_dict['TDEC_3']]
                if tdec_1 is None:
                    tdec_1 = 0
                if tdec_2 is None:
                    tdec_2 = 0
                if tdec_3 is None:
                    tdec_3 = 0
                tdec_sum = tdec_1 + tdec_2 + tdec_3

                if tdec_sum != 10 and tdec_sum != 0:
                    problem_string = 'Invalid decile sum (TDEC_1 + TDEC_2 + TDEC_3 = '\
                                     + stra(tdec_sum) + r').'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

                # Check that the deciles are in order (1 >= 2 >= 3)
                if tdec_1 < tdec_2:
                    problem_string = 'Invalid decile values in (TDEC_1 and TDEC_2).'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

                if tdec_2 < tdec_3:
                    problem_string = 'Invalid decile values in (TDEC_2 and TDEC_3).'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

                # Check that if there is a TDEC value, then the SURFM is filled in.
                if (tdec_1 > 0) and ('SURFM_1' in field_name_dict):
                    surfm_1 = stra(row[field_name_dict['SURFM_1']])
                    if surfm_1 in ['', 'None']:
                        problem_string = 'SURFM_1 is unexpectedly blank.'
                        lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

                if (tdec_2 > 0) and ('SURFM_2' in field_name_dict):
                    surfm_2 = stra(row[field_name_dict['SURFM_2']])
                    if surfm_2 in ['', 'None']:
                        problem_string = 'SURFM_2 is unexpectedly blank.'
                        lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

                if (tdec_3 > 0) and ('SURFM_3' in field_name_dict):
                    surfm_3 = stra(row[field_name_dict['SURFM_3']])
                    if surfm_3 in ['', 'None']:
                        problem_string = 'SURFM_3 is unexpectedly blank.'
                        lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            if check_exists(['SDEC_1', 'SDEC_2', 'SDEC_3'], field_name_dict):
                sdec_1 = row[field_name_dict['SDEC_1']]
                sdec_2 = row[field_name_dict['SDEC_2']]
                sdec_3 = row[field_name_dict['SDEC_3']]
                if sdec_1 is None:
                    sdec_1 = 0
                if sdec_2 is None:
                    sdec_2 = 0
                if sdec_3 is None:
                    sdec_3 = 0
                sdec_sum = sdec_1 + sdec_2 + sdec_3
                logger.debug('                    - S-Deciles add to: ' + stra(sdec_sum))
                if sdec_sum != 10 and sdec_sum != 0:
                    problem_string = 'Invalid decile sum (SDEC_1 + SDEC_2 + SDEC_3 = '\
                                     + stra(sdec_sum) + r').'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

                # Check that the deciles are in order (1 >= 2 >= 3)
                if sdec_1 < sdec_2:
                    problem_string = 'Invalid decile values in (SDEC_1 and SDEC_2).'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

                if sdec_2 < sdec_3:
                    problem_string = 'Invalid decile values in (SDEC_2 and SDEC_3).'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            lst_component_fields = [
                                    ['TTEX_1A', 'TTEX_1B', 'TTEX_1C'],
                                    ['TTEX_2A', 'TTEX_2B', 'TTEX_2C'],
                                    ['TTEX_3A', 'TTEX_3B', 'TTEX_3C'],
                                    ['TTTEX_1A', 'TTTEX_1B', 'TTTEX_1C'],
                                    ['TTTEX_2A', 'TTTEX_2B', 'TTTEX_2C'],
                                    ['TTTEX_3A', 'TTTEX_3B', 'TTTEX_3C'],
                                    ['STTEX_1A', 'STTEX_1B', 'STTEX_1C'],
                                    ['STTEX_2A', 'STTEX_2B', 'STTEX_2C'],
                                    ['STTEX_3A', 'STTEX_3B', 'STTEX_3C'],
                                    ['SURF_E1A', 'SURF_E1B', 'SURF_E1C'],
                                    ['SURF_E2A', 'SURF_E2B', 'SURF_E2C'],
                                    ['SURF_E3A', 'SURF_E3B', 'SURF_E3C'],
                                    ['TSURF_E1A', 'TSURF_E1B', 'TSURF_E1C'],
                                    ['TSURF_E2A', 'TSURF_E2B', 'TSURF_E2C'],
                                    ['TSURF_E3A', 'TSURF_E3B', 'TSURF_E3C'],
                                    ['SSURF_E1A', 'SSURF_E1B', 'SSURF_E1C'],
                                    ['SSURF_E2A', 'SSURF_E2B', 'SSURF_E2C'],
                                    ['SSURF_E3A', 'SSURF_E3B', 'SSURF_E3C'],
                                    ['GEOP_SCM1A', 'GEOP_SCM1B', 'GEOP_SCM1C', 'GEOP_INZ1'],
                                    ['GEOP_SCM2A', 'GEOP_SCM2B', 'GEOP_SCM2C', 'GEOP_INZ2'],
                                    ['GEOP_SCM3A', 'GEOP_SCM3B', 'GEOP_SCM3C', 'GEOP_INZ3']
                                   ]

            # Check if component fields are populated
            for lst_fields in lst_component_fields:
                if check_exists(lst_fields, field_name_dict):
                    if any("GEOP_INZ" in s for s in lst_fields):
                        if row[field_name_dict[lst_fields[3]]]:
                            lst_fields_to_check = [lst_fields[3], lst_fields[0], lst_fields[1]]
                        else:
                            lst_fields_to_check = [lst_fields[0], lst_fields[1], lst_fields[2]]
                    else:
                        lst_fields_to_check = [lst_fields[0], lst_fields[1], lst_fields[2]]
                    problem_list = compare_components(lst_fields_to_check, field_name_dict, row)
                    if problem_list:
                        for problem in problem_list:
                            lst_rowet.append([input_fc, cur_oid, problem, bapid])
                            summary_error_list.append(problem)

            # DEPRECATED - TTEX_1 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TTEX_1', 'TTEX_1C', 'TTEX_1B', 'TTEX_1A'], field_name_dict):
                problem_string = check_components(['TTEX_1', 'TTEX_1C', 'TTEX_1B', 'TTEX_1A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TTEX_2 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TTEX_2', 'TTEX_2C', 'TTEX_2B', 'TTEX_2A'], field_name_dict):
                problem_string = check_components(['TTEX_2', 'TTEX_2C', 'TTEX_2B', 'TTEX_2A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TTEX_3 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TTEX_3', 'TTEX_3C', 'TTEX_3B', 'TTEX_3A'], field_name_dict):
                problem_string = check_components(['TTEX_3', 'TTEX_3C', 'TTEX_3B', 'TTEX_3A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TTTEX_1 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TTTEX_1', 'TTTEX_1C', 'TTTEX_1B', 'TTTEX_1A'], field_name_dict):
                problem_string = check_components(['TTTEX_1', 'TTTEX_1C', 'TTTEX_1B', 'TTTEX_1A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TTTEX_2 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TTTEX_2', 'TTTEX_2C', 'TTTEX_2B', 'TTTEX_2A'], field_name_dict):
                problem_string = check_components(['TTTEX_2', 'TTTEX_2C', 'TTTEX_2B', 'TTTEX_2A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TTTEX_3 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TTTEX_3', 'TTTEX_3C', 'TTTEX_3B', 'TTTEX_3A'], field_name_dict):
                problem_string = check_components(['TTTEX_3', 'TTTEX_3C', 'TTTEX_3B', 'TTTEX_3A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - STTEX_1 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['STTEX_1', 'STTEX_1C', 'STTEX_1B', 'STTEX_1A'], field_name_dict):
                problem_string = check_components(['STTEX_1', 'STTEX_1C', 'STTEX_1B', 'STTEX_1A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - STTEX_2 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['STTEX_2', 'STTEX_2C', 'STTEX_2B', 'STTEX_2A'], field_name_dict):
                problem_string = check_components(['STTEX_2', 'STTEX_2C', 'STTEX_2B', 'STTEX_2A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - STTEX_3 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['STTEX_3', 'STTEX_3C', 'STTEX_3B', 'STTEX_3A'], field_name_dict):
                problem_string = check_components(['STTEX_3', 'STTEX_3C', 'STTEX_3B', 'STTEX_3A'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - SURF_E1 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['SURF_E1', 'SURF_E1A', 'SURF_E1B', 'SURF_E1C'], field_name_dict):
                problem_string = check_components(['SURF_E1', 'SURF_E1A', 'SURF_E1B', 'SURF_E1C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - SURF_E2 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['SURF_E2', 'SURF_E2A', 'SURF_E2B', 'SURF_E2C'], field_name_dict):
                problem_string = check_components(['SURF_E2', 'SURF_E2A', 'SURF_E2B', 'SURF_E2C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - SURF_E3 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['SURF_E3', 'SURF_E3A', 'SURF_E3B', 'SURF_E3C'], field_name_dict):
                problem_string = check_components(['SURF_E3', 'SURF_E3A', 'SURF_E3B', 'SURF_E3C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TSURF_E1 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TSURF_E1', 'TSURF_E1A', 'TSURF_E1B', 'TSURF_E1C'], field_name_dict):
                problem_string = check_components(['TSURF_E1', 'TSURF_E1A', 'TSURF_E1B', 'TSURF_E1C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TSURF_E2 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TSURF_E2', 'TSURF_E2A', 'TSURF_E2B', 'TSURF_E2C'], field_name_dict):
                problem_string = check_components(['TSURF_E2', 'TSURF_E2A', 'TSURF_E2B', 'TSURF_E2C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - TSURF_E3 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['TSURF_E3', 'TSURF_E3A', 'TSURF_E3B', 'TSURF_E3C'], field_name_dict):
                problem_string = check_components(['TSURF_E3', 'TSURF_E3A', 'TSURF_E3B', 'TSURF_E3C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - SSURF_E1 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['SSURF_E1', 'SSURF_E1A', 'SSURF_E1B', 'SSURF_E1C'], field_name_dict):
                problem_string = check_components(['SSURF_E1', 'SSURF_E1A', 'SSURF_E1B', 'SSURF_E1C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - SSURF_E2 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['SSURF_E2', 'SSURF_E2A', 'SSURF_E2B', 'SSURF_E2C'], field_name_dict):
                problem_string = check_components(['SSURF_E2', 'SSURF_E2A', 'SSURF_E2B', 'SSURF_E2C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            # DEPRECATED - SSURF_E3 no longer exists in the input, however this is kept in for historical purposes.
            if check_exists(['SSURF_E3', 'SSURF_E3A', 'SSURF_E3B', 'SSURF_E3C'], field_name_dict):
                problem_string = check_components(['SSURF_E3', 'SSURF_E3A', 'SSURF_E3B', 'SSURF_E3C'],
                                                  field_name_dict, row)
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if check_exists(['GEOP_SCM1', 'GEOP_SCM1A', 'GEOP_SCM1B', 'GEOP_SCM1C', 'GEOP_INZ1'], field_name_dict):
                if stra(row[field_name_dict['GEOP_INZ1']]) not in ['', ' ', 'None']:
                    problem_string = check_components(['GEOP_SCM1', 'GEOP_INZ1', 'GEOP_SCM1A', 'GEOP_SCM1B'],
                                                      field_name_dict, row)
                else:
                    problem_string = check_components(['GEOP_SCM1', 'GEOP_SCM1A', 'GEOP_SCM1B', 'GEOP_SCM1C'],
                                                      field_name_dict, row)
                
                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if check_exists(['GEOP_SCM2', 'GEOP_SCM2A', 'GEOP_SCM2B', 'GEOP_SCM2C', 'GEOP_INZ2'], field_name_dict):
                if stra(row[field_name_dict['GEOP_INZ2']]) not in ['', ' ', 'None']:
                    problem_string = check_components(['GEOP_SCM2', 'GEOP_INZ2', 'GEOP_SCM2A', 'GEOP_SCM2B'],
                                                      field_name_dict, row)
                else:
                    problem_string = check_components(['GEOP_SCM2', 'GEOP_SCM2A', 'GEOP_SCM2B', 'GEOP_SCM2C'],
                                                      field_name_dict, row)

                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if check_exists(['GEOP_SCM3', 'GEOP_SCM3A', 'GEOP_SCM3B', 'GEOP_SCM3C', 'GEOP_INZ3'], field_name_dict):
                if stra(row[field_name_dict['GEOP_INZ3']]) not in ['', ' ', 'None']:
                    problem_string = check_components(['GEOP_SCM3', 'GEOP_INZ3', 'GEOP_SCM3A', 'GEOP_SCM3B'],
                                                      field_name_dict, row)
                else:
                    problem_string = check_components(['GEOP_SCM3', 'GEOP_SCM3A', 'GEOP_SCM3B', 'GEOP_SCM3C'],
                                                      field_name_dict, row)

                if problem_string != '':
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if check_exists(['SLPLL_1', 'SLPUL_1'], field_name_dict):
                slpll_1 = row[field_name_dict['SLPLL_1']]
                if stra(slpll_1) == 'None':
                    slpll_1 = 0
                slpul_1 = row[field_name_dict['SLPUL_1']]
                if stra(slpul_1) == '0':
                    pass
                if stra(slpul_1) == 'None':
                    slpul_1 = 0
                if slpll_1 >= slpul_1 != 0:
                    problem_string = 'SLPLL_1 is higher than or equal to SLPUL_1 (' + stra(slpll_1) +\
                                     ' vs ' + stra(slpul_1) + ').'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if check_exists(['SLPLL_2', 'SLPUL_2'], field_name_dict):
                slpll_2 = row[field_name_dict['SLPLL_2']]
                if stra(slpll_2) == 'None':
                    slpll_2 = 0
                slpul_2 = row[field_name_dict['SLPUL_2']]
                if stra(slpul_2) == '0':
                    pass
                if stra(slpul_2) == 'None':
                    slpul_2 = 0
                if slpll_2 >= slpul_2 != 0:
                    problem_string = 'SLPLL_2 is higher than or equal to SLPUL_2 (' + stra(slpll_2)\
                                     + ' vs ' + stra(slpul_2) + ').'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if check_exists(['PROJ_ID'], field_name_dict):
                proj_id = row[field_name_dict['PROJ_ID']]
                if proj_id is None:
                    problem_string = 'Missing values in (PROJ_ID)'
                    lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if check_exists(['SITEMC_S1', 'SITEMC_S2', 'SITEMC_S3'], field_name_dict):
                lst_mc = ['SITEMC_S1', 'SITEMC_S2', 'SITEMC_S3']
                for mc in lst_mc:
                    if row[field_name_dict[mc]] in [' ']:   # changed to accept nulls and '' (empty string because when a user deletes an erroneous value in Arc it is not reset to null)
                        problem_string = 'Missing values in ({})'.format(mc)
                        lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            current_project_type = stra(row[field_name_dict['PROJ_TYPE']])

            if 'EM' in current_project_type:
                for i in range(1, 4):

                    # one of SITE_Sx and SITEMC_Sx must have a value
                    if check_exists(['SITE_S{}'.format(i), 'SITEMC_S{}'.format(i), 'SDEC_{}'.format(i)],
                                    field_name_dict):
                        site_s = stra(row[field_name_dict['SITE_S{}'.format(i)]])
                        sitemc_s = stra(row[field_name_dict['SITEMC_S{}'.format(i)]])
                        sdec_s = stra(row[field_name_dict['SDEC_{}'.format(i)]])

                        if site_s in ('', ' ', 'None') and sitemc_s in ('', ' ', 'None')\
                                and sdec_s not in ('', ' ', 'None'):

                            problem_string = 'SITE_S{} and SITEMC_S{} are both Null'.format(i, i)
                            lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)

                    # Commenting out the error check below because it is not an error for SITE_S to be empty when SDEC_S is populated.
                    # It is only an error if SDEC_S is populated and both SITE_S and SITEMC_S are empty.

                    # if check_exists(['SITE_S{}'.format(i), 'SDEC_{}'.format(i)], field_name_dict) and i > 1:
                    #     site_s = stra(row[field_name_dict['SITE_S{}'.format(i)]])
                    #     sdec_s = stra(row[field_name_dict['SDEC_{}'.format(i)]])                      

                        # if site_s in ('', ' ', 'None') and sdec_s not in ('', ' ', 'None'):
                        #     problem_string = 'SITE_S{} is Null (SDEC_{}={})'.format(i, i, sdec_s)
                        #     lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                        #     summary_error_list.append(problem_string)

                    if check_exists(['REALM_{}'.format(i), 'SDEC_{}'.format(i)], field_name_dict) and\
                            check_exists(['REALM_1'], mandatory_check_fields_em):
                        realm_s = stra(row[field_name_dict['REALM_{}'.format(i)]])
                        sdec_s = stra(row[field_name_dict['SDEC_{}'.format(i)]])

                        if realm_s in ('', ' ', 'None') and sdec_s not in ('', ' ', 'None'):
                            problem_string = 'REALM_{} is Null (SDEC_{}={})'.format(i, i, sdec_s)
                            lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)

                    if check_exists(['GROUP_{}'.format(i), 'SDEC_{}'.format(i)], field_name_dict) and\
                            check_exists(['GROUP_1'], mandatory_check_fields_em):
                        group_s = stra(row[field_name_dict['GROUP_{}'.format(i)]])
                        sdec_s = stra(row[field_name_dict['SDEC_{}'.format(i)]])

                        if group_s in ('', ' ', 'None') and sdec_s not in ('', ' ', 'None'):
                            problem_string = 'GROUP_{} is Null (SDEC_{}={})'.format(i, i, sdec_s)
                            lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)

                    if check_exists(['CLASS_{}'.format(i), 'SDEC_{}'.format(i)], field_name_dict) and\
                            check_exists(['CLASS_1'], mandatory_check_fields_em):
                        class_s = stra(row[field_name_dict['CLASS_{}'.format(i)]])
                        sdec_s = stra(row[field_name_dict['SDEC_{}'.format(i)]])

                        if class_s in ('', ' ', 'None') and sdec_s not in ('', ' ', 'None') and group_s != 'T':
                            problem_string = 'CLASS_{} is Null (SDEC_{}={})'.format(i, i, sdec_s)
                            lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)
                     
                    if check_exists(['SITE_M{}A'.format(i), 'SITE_M{}B'.format(i)], field_name_dict):
                        site_ma = stra(row[field_name_dict['SITE_M{}A'.format(i)]])
                        site_mb = stra(row[field_name_dict['SITE_M{}B'.format(i)]])

                        if site_ma > site_mb and site_ma not in ('None', '') and site_mb not in ('None', ''):
                            problem_string = 'SITE_M{}A and SITE_M{}B are not in alphabetical order'.format(i, i)
                            lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)

                        if site_ma == site_mb and site_ma not in ('None', '') and site_mb not in ('None', ''):
                            problem_string = 'SITE_M{}A and SITE_M{}B have the same code'.format(i, i)
                            lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)

                        if site_ma in ('None', '') and site_mb not in ('None', ''):
                            problem_string = 'SITE_M{}A must have a value for SITE_M{}B to have a value'.format(i, i)
                            lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)

                    if check_exists(['SITEAM_S{}A'.format(i), 'SITEAM_S{}B'.format(i), 'SITEAM_S{}C'.format(i), 'SITEAM_S{}D'.format(i)], field_name_dict):
                        siteam_list = []
                        siteam_list.append(stra(row[field_name_dict['SITEAM_S{}A'.format(i)]]))
                        siteam_list.append(stra(row[field_name_dict['SITEAM_S{}B'.format(i)]]))
                        siteam_list.append(stra(row[field_name_dict['SITEAM_S{}C'.format(i)]]))
                        siteam_list.append(stra(row[field_name_dict['SITEAM_S{}D'.format(i)]]))

                        none_or_empty_found = False
                        for x in siteam_list:
                            if x in ['None', '']:
                                none_or_empty_found = True
                            elif none_or_empty_found and x not in ['None', '']:
                                problem_string = 'Assumed site modifier fields not populated in order; nulls or empty strings not allowed between codes in sequence'
                                lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                                summary_error_list.append(problem_string)
                                break

                        siteams = [item for item in siteam_list if item not in ('None', '')]
                        if len(siteams) != len(set(siteams)):
                            problem_string = 'Duplicate code in assumed site modifiers (SITEAM_Sxx)'
                            lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)

                        sorted_siteams = sorted(siteams)
                        if siteams != sorted_siteams:
                            problem_string = 'Assumed site modifiers (SITEAM_Sxx) are not in alphabetical order'
                            lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                            summary_error_list.append(problem_string)
                    
                    if check_exists(['SDEC_{}'.format(i), 'BGC_LBL', 'SITE_S{}'.format(i), 'SITEMC_S{}'.format(i), 'REALM_{}'.format(i), 'GROUP_{}'.format(i), 'CLASS_{}'.format(i)], field_name_dict) and eco_codes_path:
                        eco_fields = ['SDEC_{}'.format(i), 'BGC_LBL', 'SITE_S{}'.format(i), 'SITEMC_S{}'.format(i), 'REALM_{}'.format(i), 'GROUP_{}'.format(i), 'CLASS_{}'.format(i)]
                        record = [stra(row[field_name_dict[x]]) if row[field_name_dict[x]] not in [None, 'None', 0, '0', '00', ''] else '-' for x in eco_fields]

                        if record[0] != '-':
                            del record[0]
                            record_all = list(record)
                            record_all[0] = 'ALL'
                            
                            if not df.apply(lambda row: list(row.values) == record, axis=1).any() and not df.apply(lambda row: list(row.values) == record_all, axis=1).any():
                                problem_string = 'Invalid combo in comp {} BGC_LBL, SITE_Sx, SITEMC_Sx, REALM_x, GROUP_x, CLASS_x: {}'.format(i, record)
                                lst_rowet.append([input_fc, cur_oid, problem_string, bapid])
                                summary_error_list.append(problem_string)

            # Check Mandatory Fields
            for Mandatory_Field_Name in mandatory_check_fields:
                test_value = stra(row[field_name_dict[Mandatory_Field_Name]])
                if stra(test_value) in ['None', '', ' ']:
                    cur_oid = row[field_name_dict[obj_id_field]]
                    problem_string = 'Mandatory value missing in ' + Mandatory_Field_Name + '.'
                    lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                    summary_error_list.append(problem_string)

            if 'T' in current_project_type:
                for Mandatory_Field_Name_T in mandatory_check_fields_t:
                    test_value = stra(
                        row[field_name_dict[Mandatory_Field_Name_T]])
                    logger.debug('            - Testing: ' + Mandatory_Field_Name_T + ' for mandatory value')
                    if stra(test_value) in ['', ' ']:   # removed 'None' as nulls should be allowed
                        cur_oid = row[field_name_dict[obj_id_field]]
                        problem_string = 'Mandatory value (for %TER% project types) missing in ' +\
                                         Mandatory_Field_Name_T + '.'
                        lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            if 'EM' in current_project_type:
                for Mandatory_Field_Name_EM in mandatory_check_fields_em:
                    test_value = stra(
                        row[field_name_dict[Mandatory_Field_Name_EM]])
                    logger.debug('            - Testing: ' + Mandatory_Field_Name_EM + ' for mandatory value')
                    if stra(test_value) in ['', ' ']:   # removed 'None' as nulls should be allowed

                        # BGC_SUBZON can be blank IF BGC_ZONE is alpine
                        if Mandatory_Field_Name_EM == 'BGC_SUBZON':
                            if 'BGC_ZONE' in field_name_dict:
                                bgc_zone = row[field_name_dict['BGC_ZONE']]
                                if bgc_zone in ('BAFA', 'CMA', 'IMA'):
                                    continue

                        # CLASS_1 can be blank if GROUP_1 is 'T' (treed/forested)
                        if Mandatory_Field_Name_EM == 'CLASS_1':
                            if 'GROUP_1' in field_name_dict:
                                group_1 = row[field_name_dict['GROUP_1']]
                                if group_1 == 'T':
                                    continue

                        cur_oid = row[field_name_dict[obj_id_field]]
                        problem_string = 'Mandatory value (for %TEM% project types) missing in ' +\
                                         Mandatory_Field_Name_EM + '.'
                        lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            if 'SEI' in current_project_type:
                for Mandatory_Field_Name_SEI in mandatory_check_fields_sei:
                    test_value = stra(row[field_name_dict[
                        Mandatory_Field_Name_SEI]])
                    logger.debug('            - Testing: ' + Mandatory_Field_Name_SEI + ' for mandatory value')
                    if stra(test_value) in ['None', '', ' ']:
                        cur_oid = row[field_name_dict[obj_id_field]]
                        problem_string = 'Mandatory value (for %SEI% project types) missing in ' +\
                                         Mandatory_Field_Name_SEI + '.'
                        lst_mfet.append([input_fc, cur_oid, problem_string, bapid])
                        summary_error_list.append(problem_string)

            # Write Error Summary

            if 0 < len(summary_error_list) < 51:
                if len(summary_error_list) == 1:
                    problem_string = '1 Error: '
                else:
                    problem_string = stra(len(summary_error_list)) + ' Errors: '
                for error in summary_error_list:
                    problem_string += error + ' '
                problem_string = problem_string[:-1]
                problem_string = problem_string[:499]
                lst_set.append([cur_oid, problem_string, bapid, dt.now().strftime("%m/%d/%Y")])
                error_count += 1
            elif len(summary_error_list) >= 51:
                problem_string = '** MANY ERRORS (' + stra(len(summary_error_list)) + ')'
                lst_set.append([cur_oid, problem_string, bapid, dt.now().strftime("%m/%d/%Y")])
                error_count += 1

            num_processed = num_processed + 1
            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 ' + stra(num_processed) + ' of '
                            + stra(num_features) + ' (' + stra(error_count)
                            + ' records found with errors) (' + sanitize_elapsed_time(dt_calc_elapsed)[:-1]
                            + ' elapsed, ' + sanitize_elapsed_time(dt_calc_remaining)[:-1] + ' remaining)')

    # Check Topology
    topology_errors = validate_topology(input_fc, logger)

    # Inserting records into error tables if any were found
    fld_errors = ['CLASS', 'FEATURE_ID', 'PROBLEM', 'BAPID']

    if lst_det:
        with arcpy.da.InsertCursor(domain_error_table, fld_errors) as det_cursor:
            for row in lst_det:
                det_cursor.insertRow(row)

    if lst_ret:
        with arcpy.da.InsertCursor(range_error_table, fld_errors) as ret_cursor:
            for row in lst_ret:
                ret_cursor.insertRow(row)

    if lst_cret:
        with arcpy.da.InsertCursor(critical_range_error_table, fld_errors) as cret_cursor:
            for row in lst_cret:
                cret_cursor.insertRow(row)

    if lst_rowet:
        with arcpy.da.InsertCursor(row_error_table, fld_errors) as rowet_cursor:
            for row in lst_rowet:
                rowet_cursor.insertRow(row)

    if lst_mfet:
        with arcpy.da.InsertCursor(critical_field_error_table, fld_errors) as mfet_cursor:
            for row in lst_mfet:
                mfet_cursor.insertRow(row)

    if lst_set:
        fld_errors = ['FEATURE_ID', 'PROBLEMS', 'BAPID', 'Date_Processed']
        with arcpy.da.InsertCursor(summary_error_table, fld_errors) as set_cursor:
            for row in lst_set:
                set_cursor.insertRow(row)

    logger.debug('            - Processed all ' + stra(num_processed))


def validate_topology(input_fc, logger):
    logger.info('    - Validating Topology')

    fc_intersect = '{}_intersect'.format(input_fc)
    fc_union = '{}_union'.format(input_fc)

    arcpy.Intersect_analysis([input_fc], fc_intersect)
    arcpy.Union_analysis([input_fc], fc_union, gaps='NO_GAPS')

    topology_fc = '{}_TopologyErrors'.format(input_fc)
    if arcpy.Exists(topology_fc):
        arcpy.Delete_management(topology_fc)
    arcpy.CreateFeatureclass_management(os.path.dirname(topology_fc), os.path.basename(topology_fc),
                                        'POLYGON', fc_intersect, spatial_reference=fc_intersect)
    arcpy.AddField_management(topology_fc, 'ERROR_TYPE', 'TEXT', field_length=10)

    intersect_count = int(arcpy.GetCount_management(fc_intersect).getOutput(0))
    if intersect_count > 0:
        arcpy.AddField_management(fc_intersect, 'ERROR_TYPE', 'TEXT', field_length=10)
        with arcpy.da.UpdateCursor(fc_intersect, 'ERROR_TYPE') as u_cursor:
            for row in u_cursor:
                row[0] = 'OVERLAP'
                u_cursor.updateRow(row)
        arcpy.Append_management(fc_intersect, topology_fc)

    f_lyr = arcpy.MakeFeatureLayer_management(fc_union, 'f_lyr', 'FID_{} < 0'.format(os.path.basename(input_fc)))
    union_count = int(arcpy.GetCount_management(f_lyr).getOutput(0))
    if union_count > 0:
        arcpy.AddField_management(f_lyr, 'ERROR_TYPE', 'TEXT', field_length=10)
        with arcpy.da.UpdateCursor(f_lyr, 'ERROR_TYPE') as u_cursor:
            for row in u_cursor:
                row[0] = 'GAP'
                u_cursor.updateRow(row)
        arcpy.Append_management(f_lyr, topology_fc)

    if (union_count + intersect_count) > 0:
        logger.warning('{} topology errors detected'.format(union_count + intersect_count))
    else:
        logger.info('No topology errors detected')

    arcpy.Delete_management(f_lyr)
    arcpy.Delete_management(fc_intersect)
    arcpy.Delete_management(fc_union)

    return topology_fc


def test_geometry(input_fc, logger):

    geometry_error_table = input_fc + r'_GeometryErrors'
    if arcpy.Exists(geometry_error_table):
        arcpy.Delete_management(geometry_error_table)

    logger.info('    - Testing Geometry')

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

    dt_calc_start = time.time()
    logger.info('        - Scanning Geometry for {} features (may take several hours for'
                ' large datasets).'.format(stra(num_features)))
    arcpy.CheckGeometry_management(input_fc, geometry_error_table)
    dt_calc_now = time.time()
    dt_calc_elapsed = dt_calc_now - dt_calc_start
    logger.info('            - Scan completed in ' + sanitize_elapsed_time(dt_calc_elapsed))

    num_errors = int(arcpy.GetCount_management(geometry_error_table).getOutput(0))

    if num_errors > 0:
        logger.info('            - Found ' + stra(num_errors) + ' geometry issues.')
        arcpy.JoinField_management(geometry_error_table, 'FEATURE_ID', input_fc, 'OBJECTID', 'BAPID')
    else:
        logger.info('            - Found no geometry issues.')
        arcpy.AddField_management(geometry_error_table, 'BAPID', 'SHORT')


def test_tolerance(input_fc, logger):
    logger.info('    - Testing Tolerance')

    desc = arcpy.Describe(input_fc)
    if desc.SpatialReference.XYTolerance != 0.001:
        logger.error('** XYTolerance is ' + stra(desc.SpatialReference.XYTolerance)
                     + ', not the correct value of 0.001m.')
        sys.exit()
    else:
        logger.info('        - XYTolerance set to correct value of .001m.')


def test_projection(albers_prj, input_fc, logger):
    logger.info('    - Testing Projection')
    desc = arcpy.Describe(input_fc)
    test_albers_sr = desc.SpatialReference
    correct_albers_sr = arcpy.SpatialReference(3005)
    tests = []
    # removed semiMinorAxis, semiMajorAxis and longitudeOfOrigin from the following comparison for Arc 10.1 because
    # the results for these are
    # both very near zero but slightly different between the data and the prj file, resulting in a false mismatch
    for Parameter in ['type', 'name', 'isHighPrecision', 'spheroidName', 'datumName', 'angularUnitName', 'falseEasting',
                      'falseNorthing', 'centralMeridian', 'standardParallel1', 'standardParallel2', 'linearUnitName']:
        tests.append(('correct_albers_sr.' + Parameter, 'test_albers_sr.' + Parameter))
    for test in tests:
        test_result = None
        if sys.version_info[0] < 3:
            exec ('test_result = ' + stra(test[0]) + ' == ' + stra(test[1]))
        else:
            # The following two lines are used for Python 3.x instead of the exec call
            str_compare = stra(test[0]) + ' == ' + stra(test[1])
            test_result = eval(str_compare)

        if test_result:
            logger.debug('        - Match: ' + stra(test[0]) + ' with ' + stra(test[1]))
        else:
            logger.info('** Projection Issue: ' + stra(test[0]) + ' does not match ' +
                        stra(test[1]))
            sys.exit()
    logger.info('        - Projection Matches Template.')
    # (rough) feature envelope for the province of BC in Albers projection coordinates
    bc_min_x = 263000
    bc_max_x = 1881000
    bc_min_y = 349000
    bc_max_y = 1746000
    if desc.Extent.XMin < bc_min_x or desc.Extent.XMax > bc_max_x or desc.Extent.YMin < bc_min_y or\
            desc.Extent.YMax > bc_max_y:
        logger.info('** Projection Issue: Feature class extent lies outside of BC boundaries. Check that the dataset '
                    'is in the proper projection.')
        sys.exit()
    else:
        logger.info('        - Feature class extent OK (lies completely within BC boundaries).')


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


def check_exists(lst_values, dict_values):
    for value in lst_values:
        if value not in dict_values:
            return False
    return True


def check_components(lst_fields, field_name_dict, row):
    fld_concat = lst_fields[0]
    fld_comp1 = lst_fields[1]
    fld_comp2 = lst_fields[2]
    fld_comp3 = lst_fields[3]
    val_concat = stra(row[field_name_dict[fld_concat]])
    val_comp1 = row[field_name_dict[fld_comp1]]
    val_comp2 = row[field_name_dict[fld_comp2]]
    val_comp3 = row[field_name_dict[fld_comp3]]
    str_composite = stra(val_comp1) + stra(val_comp2) + stra(val_comp3)
    str_composite = str_composite.replace(' ', '')
    str_composite = str_composite.replace('None', '')
    val_concat = val_concat.replace(' ', '')
    val_concat = val_concat.replace('None', '')

    if val_concat != str_composite:
        problem_string = '{} does not match components ({} vs {}).'.format(fld_concat, val_concat, str_composite)
        return problem_string

    return ''


def compare_components(lst_fields, field_name_dict, row):
    fld_comp1 = lst_fields[0]
    fld_comp2 = lst_fields[1]
    fld_comp3 = lst_fields[2]

    val_comp1 = row[field_name_dict[fld_comp1]]
    val_comp2 = row[field_name_dict[fld_comp2]]
    val_comp3 = row[field_name_dict[fld_comp3]]

    problem_list = []
    if val_comp2:
        if not val_comp1:
            problem_list.append('{} requires a value when {} is populated.'.format(fld_comp1, fld_comp2))
    if val_comp3:
        if not val_comp1 or not val_comp2:
            problem_list.append('{} and {} require values when {} '
                                'is populated.'.format(fld_comp1, fld_comp2, fld_comp3))
    return problem_list


def create_templates(tat_database, output_path, templates, logger):

    if not arcpy.Exists(tat_database):
        logger.error('The specified Tools and Templates database does not exist. Program Terminating.')
        sys.exit(1)

    tbl_metadata_items = tat_database + "/TEIS_Metadata_Items"
    tbl_metadata_tables = tat_database + "/TEIS_Metadata_Tables"

    if not arcpy.Exists(tbl_metadata_items):
        logger.error('The Tools and Templates database does not contain the '
                     'TEIS_Metadata_Items table. Program Terminating.')
        sys.exit(1)

    if not arcpy.Exists(tbl_metadata_tables):
        logger.error('The Tools and Templates database does not contain the '
                     'TEIS_Metadata_Tables table. Program Terminating.')
        sys.exit(1)

    if not arcpy.Exists(output_path):
        logger.error('The output geodatabase specified does not exist. Program Terminating.')
        sys.exit(1)

    lst_template_names = []
    lst_fgdb_names = []
    lst_fc_fields = ['Feature_Class', 'Feature_Type', 'Spatial_Reference']

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

    lst_templates_create = []
    if templates:
        for temp in templates:
            if temp in lst_template_names:
                lst_templates_create.append(temp)
            else:
                logger.warning('{} does not exist as a template in the TEIS Metadata Items table'.format(temp))

        if len(lst_templates_create) == 0:
            logger.error('None of the specified templates exist in the TEIS Metadata Items Table.'
                         ' Program Terminating.')
            sys.exit(1)
        lst_template_names = lst_templates_create

    with arcpy.da.SearchCursor(tbl_metadata_tables, lst_fc_fields) as s_cursor:
        for row in s_cursor:
            fc_name = str(row[0])
            if fc_name in lst_template_names:
                fc_type = str(row[1]).upper()

                if str(row[2]) == 'None':
                    sr = None
                else:
                    sr = arcpy.SpatialReference(str(row[2]).replace('_', ' '))

                if 'VIEW' in fc_type:
                    fc_type = fc_type[:-4]
                temp_name = fc_name

                logger.info('Creating {}'.format(temp_name))

                temp_path = output_path + '/' + temp_name

                if arcpy.Exists(output_path + '/' + temp_name):
                    arcpy.Delete_management(output_path + '/' + temp_name)

                if fc_type != 'TABLE':
                    arcpy.CreateFeatureclass_management(output_path, temp_name, fc_type, spatial_reference=sr)
                else:
                    arcpy.CreateTable_management(output_path, temp_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_2:
                    for itm in s_cursor_2:
                        fld_name = str(itm[0])
                        fld_alias = str(itm[1])
                        fld_type = str(itm[2])
                        fld_width = itm[3]
                        fld_precision = itm[4]
                        fld_scale = itm[5]
                        arcpy.AddField_management(temp_path, field_name=fld_name, field_type=fld_type,
                                                  field_precision=fld_precision, field_scale=fld_scale,
                                                  field_length=fld_width, field_alias=fld_alias)


def find_eco_codes_excel():
    file_name = "TEI_EcosystemCodes.xlsx"
    current_directory = os.path.dirname(__file__)
    parent_directory = os.path.dirname(current_directory)
    grandparent_directory = os.path.dirname(parent_directory)

    for root, _, files in os.walk(grandparent_directory):
        if file_name in files:
            file_path = os.path.join(root, file_name)
            return file_path

    return False


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)
