# Originally written by Brian Dobbins
#
# 2015-08-04:   Changed check for blank lines such that errant spaces won't cause program
# to try to parse that line (causing an "Index exceeds matrix dimensions"
# error (Allison Hsiang)
#
# 2015-09: Ported to python (Kaylea Nelson)
# 2022-04: Python2 to Python3 conversion by Allison Hsiang

import configparser
from io import StringIO
from datetime import datetime
import numpy as np
import sys
import os
import getpass


def parse(filename):
    # Set up defaults
    defaults = {'pixel_size_x': 1,
                'pixel_size_y': 1,
                'threshold': 0.20,
                'box_once': True,
                'minimum_size': 100,
                'maximum_size': 2000,
                'source': 'Unspecified source',
                'age': 'Unspecified age',
                'input_ext': 'tif',
                'output_ext': 'tif',
                'location': 'Unspecified location',
                'catalog_prefix': None,
                'unit': 'microns',
                'scale_bar_length': 100,
                'box_thickness': 20,
                'skip_last_plane': True,
                'author': None,
                'unique_id': None,
                'id': None,
                'fill_kernel': 0,
                'debug_images': False,
                'single_plane': False,
                'single_dir': True,
                'include_csv': False,
                'transpose_csv': False,
                'param_file': None
                }

    required_list = ['mode', 'directory', 'output']

    # Parse setting
    settings = defaults

    settings['timestamp'] = datetime.now().strftime('%Y-%m-%d at %H:%M:%S')

    parser = configparser.ConfigParser(allow_no_value=True)
    parser.optionxform = str  # preserve case

    if os.path.isfile(filename):
        try:
            parser.read(filename)
        except ConfigParser.MissingSectionHeaderError:
            vfile = StringIO(u'[settings]\n%s' % open(filename).read())
            parser.readfp(vfile)
    else:
        sys.exit("Error: Cannot open settings file "+filename)

    # Set default threshold values
    num_permutations = 1
    thresholds = [defaults['threshold']]

    # set optional variables
    for setting in parser.options('settings'):
        currentSetting = parser.get('settings',setting)
        if currentSetting:
            if setting == 'threshold':
                threshold_str = parser.get('settings', 'threshold')
                thresholds = parse_thresholds(threshold_str)
                num_permutations = len(thresholds)

            # Backwards compatibility tweaks
            elif setting == "minimumSize":
                settings['minimum_size'] = float(parser.get('settings', 'minimumSize'))
            elif setting == "maximumSize":
                settings['maximum_size'] = float(parser.get('settings', 'maximumSize'))

            # Special formatting tweaks for robustness
            # FLOAT
            elif 'size' in setting:
                settings[setting] = float(parser.get('settings', setting))
            # INT
            elif setting in ['fill_kernel', 'box_thickness', 'scale_bar_length']:
                settings[setting] = int(parser.get('settings', setting))
            # BOOLEAN
            elif setting in ['skip_last_plane', 'debug_images', 'single_plane', 'single_dir', 'include_csv', 'transpose_csv']:
                if parser.get('settings', setting) in ['False', 'false']:
                    settings[setting] = False
                elif parser.get('settings', setting) in ['True', 'true']:
                    settings[setting] = True
            # PARAM FILE STR
            elif setting in ['param_file']:
                settings[setting] = str(parser.get('settings', setting))
                if not settings[setting] == None:
                    if not os.path.exists(settings['param_file']):
                            sys.exit('Error: User-specified parameter file does not exist.')
                    if settings[setting].endswith(os.sep):
                        settings[setting] = settings[setting].rstrip(os.sep)
            else:
                settings[setting] = str(parser.get('settings', setting))

    # check for required parameters
    for required in required_list:
        if required not in settings.keys():
            sys.exit('Error: '+required+' must be set in settings file.')

    settings['units_per_pixel'] = (settings['pixel_size_x'] + settings['pixel_size_y'])*0.5

    # Backwards compatibility tweaks
    if settings['mode'] == 'save':
        settings['mode'] = 'final'

    if settings['mode'] not in ['final', 'sample']:
        sys.exit('Error: unrecognized mode settings. Please specify final or sample only.')
    if settings['mode'] != 'sample' and num_permutations > 1 and settings['param_file'] == None:
        sys.exit('Error: for final mode, only give a single threshold value, not a range.')

    # Set up additional global settings
    if settings['directory'].endswith(os.sep):
        settings['directory'] = settings['directory'].rstrip(os.sep)
    if settings['output'].endswith(os.sep):
        settings['output'] = settings['output'].rstrip(os.sep)

    # Define full output directory
    if settings['id'] is None:
        settings['subdirectory'] = os.path.split(settings['directory'])[1]
        settings['id'] = os.path.split(settings['directory'])[1]
    else:
        settings['subdirectory'] = settings['id']
    new_directory = settings['output'] + os.sep + settings['subdirectory']
    settings['full_output'] = new_directory + os.sep + settings['mode']

    # Create a unique id
    if settings['unique_id'] is None:
        settings['unique_id'] = settings['id']

    # Duplicate settings for each value of threshold
    all_settings = []
    for i in range(num_permutations):
        all_settings.append(settings.copy())
        all_settings[i]['threshold'] = thresholds[i]

    return all_settings


def drop_extra_settings(settings):

    del settings['subdirectory']
    del settings['unique_id']
    del settings['image_label']
    del settings['full_output']
    del settings['timestamp']
    del settings['image_file_label']
    del settings['units_per_pixel']
    del settings['bigtiff']

    return settings


def parse_thresholds(threshold_str):
    '''
    Remove human language from threshold specification.
    '''
    default_step_size = 0.01
    thresholds = threshold_str.replace('-', ',').replace('by', ',').split(',')
    thresholds = list(map(float, thresholds))

    if len(thresholds) == 2 or len(thresholds) == 3:
        if len(thresholds) == 2: # Use default step size
            thresholds.append(default_step_size)
        num_values = int(np.round((thresholds[1]-thresholds[0])/thresholds[2]))+1
        thresholds = np.linspace(thresholds[0], thresholds[1], num_values, endpoint=True)

    elif len(thresholds) > 3 or len(thresholds) < 1:
        sys.exit('Error: unrecognized syntax for threshold setting')

    return thresholds


def save(settings, id=''):

    write_directory = settings['full_output']
    settings = drop_extra_settings(settings)

    parser = configparser.ConfigParser()
    parser.optionxform = str  # preserve case

    username = getpass.getuser()
    timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    filename = 'settings_%s%s_%s.txt' % (username, id, timestamp)

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

    section = 'settings'
    parser.add_section(section)

    for option, value in settings.items():

        parser.set(section, str(option), str(value))

    settings_file_path = os.path.join(write_directory, filename)

    with open(settings_file_path, 'w') as f:
        f.write('# Saved Settings:\n')
        parser.write(f)

    return settings_file_path
