import re, sys, argparse
from pathlib import Path
from argparse import RawTextHelpFormatter


'''
TODO list:
  1. Parse macro values (the names of bits in all registers in header file)
  2. Failed SPI write handling
  3. SX126x/SX128x handling
'''


def get_macro_name(value, macros):
    for macro in macros:
        if macro[1] == value:
            return macro[0]
    return 'UNKNOWN_VALUE'


def get_macro_value(value):
    return '                         0x{0:02X}\n'.format(int(value, 16))


parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter, description='''
    RadioLib debug output decoder script. Turns RadioLib Serial dumps into readable text.

    Step-by-step guid on how to use the decoder:
    1. Uncomment lines 312 (#define RADIOLIB_DEBUG) and 313 (#define RADIOLIB_VERBOSE) in RadioLib/src/BuildOpt.h
    2. Recompile and upload the failing Arduino sketch
    3. Open Arduino IDE Serial Monitor and enable timestamps
    4. Copy the Serial output and save it into a .txt file
    5. Run this script

    Output will be saved in the file specified by --out and printed to the terminal
''')
parser.add_argument('file', metavar='file', type=str, help='Text file of the debug output')
parser.add_argument('--out', metavar='out', default='./out.txt', type=str, help='Where to save the decoded file (defaults to ./out.txt)')
args = parser.parse_args()

# open the log file
log = open(args.file, 'r').readlines()

# find modules that are in use
used_modules = []
pattern_module = re.compile('(([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?.[0-9]{3} -> )?M\t')
for entry in log:
    m = pattern_module.search(entry)
    if m != None:
        used_modules.append(entry[m.end():].rstrip())

# get paths to all relevant header files
header_files = []
for path in Path('../../src').rglob('*.h'):
    for module in used_modules:
        if module in path.name:
            header_files.append(path)

# extract names of address macros from the header files
macro_addresses = []
pattern_define = re.compile('#define \w* +\w*(\n| +\/\/){1}')
for path in header_files:
    file = open(path, 'r').readlines()
    for line in file:
        m = pattern_define.search(line)
        if m != None:
            s = re.split(' +', m.group().rstrip())
            if (s.__len__() > 1) and ('_REG' in s[1]):
                macro_addresses.append([s[1], int(s[2], 0)])

'''
# extract names of value macros for each adddress macro
macro_values = []
for path in header_files:
    file = open(path, 'r').readlines()
    for line in file:
        for module in used_modules:
            pattern_addr_macro = re.compile('\/\/ SI443X_REG_\w+'.format(module.capitalize()))
'''

# parse every line in the log file
out = []
pattern_debug = re.compile('(([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?.[0-9]{3} -> )?[RWM]\t.+')
for entry in log:
    m = pattern_debug.search(entry)
    if m != None:
        s = re.split('( |\t)+', entry.rstrip())
        cmd_len = int((s.__len__() - 7)/2)
        new_entry = s[0] + s[1] + s[2] + s[3]
        if s[4] == 'W':
            macro_address = int(s[6], 16)
            new_entry += 'write {0:>2} 0x{1:02X} {2}\n'.format(cmd_len, macro_address, get_macro_name(macro_address, macro_addresses))
            for i in range(cmd_len):
                new_entry += get_macro_value(s[8 + 2*i]);
        elif s[4] == 'R':
            macro_address = int(s[6], 16)
            new_entry += 'read  {0:>2} 0x{1:02X} {2}\n'.format(cmd_len, macro_address, get_macro_name(macro_address, macro_addresses))
            for i in range(cmd_len):
                new_entry += get_macro_value(s[8 + 2*i]);
        elif s[4] == 'M':
            new_entry += 'module {}\n'.format(s[6])
        out.append(new_entry)
    else:
        out.append(entry)

# write the output file
out_file = open(args.out, 'w')
for line in out:
    print(line, end='')
    out_file.write(line)
out_file.close()