#! /usr/bin/env python3

# This script pulls all scenario events out of a single file, e.g. generated by the AMASE Setup Tool or by hand,
# and saves them as individual files. It names files as <MessageType>[_Id_<ID>][_<uid>], where <ID> is some digit
# string that corresponds to an element with an (ID|Id), (TaskID|TaskId), \w+(ID|Id), or (ID|Id)\w+ tag if found,
# and uid is a unique alpha identifier used if there would otherwise be two files with the same name. Files are
# saved in directory ./MessagesToSend. Also outputs file ./SendMessagesService_cfg.xml containing a code snippet
# for UxAS's SendMessagesService, set to expect messages in ../MessagesToSend, i.e. that UxAS will be run in some
# other directory at the same level as MessagesToSend, which is the standard convention.  This code snippet
# assumes messages should be sent in the order in which they appear in the input file. Each message is delayed by
# 10ms, except for AutomationRequest messages, which are delayed by 2000ms.
#
# This script could be easily modified to allow a user to specify the output directory and the directory for
# SendMessagesService to reference.

import getopt
import os
import sys
import re
import string
import math
import xml.etree.ElementTree as ETree
from xml.dom import minidom


def main(argv):
    # Process input arguments
    try:
        opts, args = getopt.getopt(argv, "h", [])
    except getopt.GetoptError:
        print('usage: amase2uxas.py \033[4m' + 'input_file' + '\033[0m')
        sys.exit()
    #for opt, arg in opts:
    #    if opt == '-h':
    #        print 'usage: amase2uxas.py [-h] \033[4m' + 'input_file' + '\033[0m'
    #        sys.exit()
    if len(args) < 1:
        print('usage: amase2uxas.py \033[4m' + 'input_file' + '\033[0m')
        sys.exit()
    input_file = args[0]
    try:
        input_tree = ETree.parse(input_file)
    except:
        print('error: could not open file ' + args[0])
        print('error details:')
        for info in sys.exc_info():
            print(sys.exc_info())
        sys.exit()
    output_dir = os.path.relpath('./MessagesToSend')
    try:
        os.makedirs(output_dir)
    except OSError:
        if not os.path.isdir(output_dir):
            raise
            print('error: could not create directory "' + output_dir + '"')
            sys.exit()

    # Process input xml. Save each event in ScenarioEventList as output_dir/<MessageType>[_Id_<ID>][_<uid>]
    root = input_tree.getroot()
    scenario_events = list(root.find('ScenarioEventList'))
    duplicate_filename_counts = dict()
    base_filenames = []
    for scenario_event in scenario_events:
        tag_types = []
        text_values = []
        # Record elements of scenario_event with digit text
        for element in list(scenario_event):
            if element.text is not None and element.text.isdigit():
                tag_types.append(element.tag)
                text_values.append(element.text)
        # Find element that is preferably just ID|Id, just TaskID|TaskId, one that ends in ID|Id,
        # or one that begins with ID|Id
        idx = [i for i, item in enumerate(tag_types) if re.match('^((ID)|(Id))$', item)]
        if len(idx) == 0:
            idx = [i for i, item in enumerate(tag_types) if re.search('((TaskID)|(TaskId))$', item)]
        if len(idx) == 0:
            idx = [i for i, item in enumerate(tag_types) if re.search('((ID)|(Id))$', item)]
        if len(idx) == 0:
            idx = [i for i, item in enumerate(tag_types) if re.match('^((ID)|(Id))', item)]
        # Generate filename for scenario_event based on event type and ID value (if applicable)
        filename = scenario_event.tag
        if len(idx) > 0:
            filename = filename + '_Id_' + text_values[idx[0]]
        # Keep a count of how many times a filename has been duplicated
        if filename in duplicate_filename_counts:
            duplicate_filename_counts[filename] += 1
        else:
            duplicate_filename_counts[filename] = 0
        # Add a uid if no ID or if filename seen before
        if len(idx) == 0:
            alpha = number_to_alpha(duplicate_filename_counts[filename]+1)
            filename = filename + '_' + alpha
        elif duplicate_filename_counts[filename] > 0:
            alpha = number_to_alpha(duplicate_filename_counts[filename])
            filename = filename + '_' + alpha
        # Save to file
        full_filename = os.path.join(output_dir, filename + '.xml')
        print('Writing ' + full_filename)
        (ETree.ElementTree(scenario_event)).write(full_filename)
        base_filenames.append(filename)
    # Create stub for UxAS's SendMessagesService
    cfg_file = open('SendMessagesService_cfg.xml','w')
    print('Saving SendMessagesService configuration stub to ' + cfg_file.name)
    cfg_root = ETree.Element('Service',{'Type':'SendMessagesService', 'PathToMessageFiles': '../'+output_dir})
    time_ms = 100
    for base_filename in base_filenames:
        if re.match('^AutomationRequest', base_filename):
            time_ms += 1500
        else:
            time_ms += 10
        ETree.SubElement(cfg_root, 'Message', {'MessageFileName': base_filename+'.xml', 'SendTime_ms': str(time_ms)})
    cfg_file.write(minidom.parseString(ETree.tostring(cfg_root)).toprettyxml())
    cfg_file.close()
    print('Done!')


# Give an alpha-based uid based on a count. Convert count to base 26, subtract 1 from leftmost digit, then
# translate to alpha characters. Sequence is 1=a, 2=b, ..., 26=z, 27=aa, 28=ab, ..., 52=az, 53=ba, ...
def number_to_alpha(number):
    # Translation process relies on a conversion to base 26 with 0=a, 1=b, ... , 25=z, with tweaks to allow
    # for strings of the form a[a-z]+, since strings starting with 'a' would normally not be valid in base 26

    # Ensure count is an integer, decrement it, return empty string if less than 0
    assert(type(number) == int)
    number -= 1
    if number < 0:
        return ""

    # Compute representation in base 26
    base = 26

    # For each digit place n (except close to boundaries), we need to subtract out base^n because we
    # want to allow strings to start with 'a'=0
    exponent_counter = 1
    number_to_subtract = 0
    while number_to_subtract <= number:
        number_to_subtract += int(math.pow(base,exponent_counter))
        exponent_counter += 1
    if number_to_subtract > number:
        exponent_counter -= 1
        number_to_subtract -= int(math.pow(base,exponent_counter))
    assert(type(number_to_subtract) == int)
    modified_number = number - number_to_subtract

    # Represent modified number as a map/list, converting to base 26
    modified_number_map = convert_from_base10(modified_number,26)

    # add 0s
    while len(modified_number_map) < len(convert_from_base10(number_to_subtract,26)):
        modified_number_map.insert(0,0)

    # construct string
    alpha = ""
    alphabet = [string.letters[index] for index in range(0, 26)]
    for idx in modified_number_map:
        alpha = alpha + alphabet[idx]

    return alpha


# Convert a number from base 10 to a specified base
# Returned as a list, so no special characters are needed for digits > 9
def convert_from_base10(number, base):
    assert(type(number) == int)
    assert(type(base) == int)
    output = []
    if number == 0:
        output = [0]
    while number > 0:
        output.insert(0,number % base)
        number = number/base
    return output


if __name__ == "__main__":
   main(sys.argv[1:])
