import re
import os
import sys
import subprocess
import shlex
import getopt


# --------------------------
# Read Map File and return
# all function lenghts
# --------------------------
def parse_map(version):

    if version not in ("us", "jp"):
        print('fatal: version', version, 'not supported!')
        sys.exit(2)

    version_code = version[0]

    infile = False
    prevromaddr = 0
    lengths = {}

    p1 = re.compile(r"^ \.text\s+0x00000000([0-9a-f]{8})\s+0x([0-9a-f]+)\s+build\/" + re.escape(version_code) + r"\/(.*)\/\S+.\w$")
    p2 = re.compile(r"\s+0x00000000([0-9a-f]{8})\s+(\S+)$")

    map_file = open('build/' + version_code + 'ge007.' + version_code + '.map', 'r')
    lines = map_file.readlines()

    for line in lines:

        m1 = p1.findall(line)
        m2 = p2.findall(line)

        if m1:
            filestart = int(m1[0][0], 16)
            filelen = int(m1[0][1], 16)
            segment = m1[0][2]
            infile = True
            prevromaddr = 0
            prevfuncname = None

            if segment not in lengths.keys():
                lengths[segment] = {}

        elif infile and m2:
            romaddr = int(m2[0][0], 16)
            funcname = m2[0][1]

            if prevfuncname:
                lengths[segment][prevfuncname] = romaddr - prevromaddr

            prevromaddr = romaddr
            prevfuncname = funcname

        elif infile:
            infile = False

            if prevfuncname:
                lengths[segment][prevfuncname] = (filestart + filelen) - prevromaddr

    return lengths

# --------------------------
# Find and return all
# ASM Function names that still
# exists in Source Files
# Only scans for C and S files
# --------------------------
def find_asm_functions(version):

    p1 = re.compile(r"^glabel (\S+)$")
    p2 = re.compile(r"^#ifdef VERSION_(\S+)$")
    p3 = re.compile(r"^#endif$")

    asm_functions = []
    version = version.upper()
    func_version = None

    for root, dirs, files in os.walk('src'):
        for file in files:
            if file.endswith(".c") or file.endswith(".s"):
                func_version = None
                with open(os.path.join(root, file)) as _file:
                    for i, line in enumerate(_file.readlines()):

                        m1 = p1.findall(line)
                        m2 = p2.findall(line)
                        m3 = p3.findall(line)

                        # Found an #endif command
                        if m3:
                            func_version = None

                        # Found a version definition
                        elif m2:
                            func_version = m2[0]

                        # Found an asm function
                        elif m1:
                            if not func_version or (func_version == version):
                                asm_functions.append(m1[0])

                            func_version = None

    return asm_functions

# --------------------------
# Find and return the number of completed files
# Only scans for C and S files
# --------------------------
def find_files_completed():

    p = re.compile(r"^glabel (\S+)$")
    files_complete = {}

    files_complete['completed'] = 0
    files_complete['total'] = 0

    for root, dirs, files in os.walk('src'):
        for file in files:
            if file.endswith(".c") or file.endswith(".s"):
                with open(os.path.join(root, file)) as _file:

                    completed = True
                    files_complete['total'] += 1

                    for i, line in enumerate(_file.readlines()):
                        if p.findall(line):
                            completed = False
                            break

                    if completed:
                        files_complete['completed'] += 1

    return files_complete

# --------------------------
# Find and return the Name of most recent file
# Only scans for C and S files
# --------------------------
def find_last_modified_file():
    lastdate = 0
    lastname = ''
    timestamp = 0

    for root, dirs, files in os.walk('src'):
        for file in files:
            if file.endswith(".c") or file.endswith(".s"):
                _file = os.path.join(root, file)

                result = subprocess.run(['git', 'log', '-1', '--format=\"%ct\"', '--', _file], stdout=subprocess.PIPE, universal_newlines=True)

                timestamp = int(result.stdout.rstrip().replace('"', ''))

                if timestamp > lastdate:
                    lastdate = timestamp
                    lastname = _file

    return lastname

# --------------------------
# Calculate the decomp stats
# on each folder
# --------------------------
def do_stats(version, map_file, analyse_folders):

    asm_functions = find_asm_functions(version)
    segments = {}

    for folder in analyse_folders:

        segments[folder] = {}
        segments[folder]['done'] = 0
        segments[folder]['left'] = 0
        segments[folder]['total'] = 0

        num_done = 0
        num_left = 0

        for function in map_file[folder]:
            if function in asm_functions:
                num_left += map_file[folder][function]
            else:
                num_done += map_file[folder][function]

        segments[folder]['done'] = num_done / 4
        segments[folder]['left'] = num_left / 4
        segments[folder]['total'] = segments[folder]['done'] + segments[folder]['left']

    return segments

def generate_report(segments, files_completed, last_modified_file, Ver):
    printstring = "./tools/report/report "

    totals = {}
    totals['done'] = 0
    totals['total'] = 0

    for key in segments.keys():
        printstring = printstring + str(segments[key]['done']) + ' ' + str(segments[key]['total']) + ' '
        totals['done'] += segments[key]['done']
        totals['total'] += segments[key]['total']

    printstring = printstring + str(totals['done']) + ' ' + str(totals['total']) + ' '
    printstring = printstring + str(files_completed['completed']) + ' ' + str(files_completed['total']) + ' '
    printstring = printstring + './tools/report/template.html '
    if Ver.upper() in ("US"):
        printstring = printstring + './tools/report/index.html ' + last_modified_file + ' 0'
    elif Ver.upper() in ("JP"):
        printstring = printstring + './tools/report/JPN.htm ' + last_modified_file + ' 0'
    else:
        printstring = printstring + './tools/report/index.html ' + last_modified_file + ' 0'
    subprocess.Popen(printstring.split())

def print_stats(version, segments, files_completed, last_modified_file):
    totals = {}
    totals['done'] = 0
    totals['total'] = 0

    print()
    print('~> Decomp Statistics for ' + version.upper() +' version')
    print('__________________________________________________________________')
    print()

    print('FILES')
    print('\tlast modified file\t' + last_modified_file )
    print('\ttotal files\t{:10,} / {:,} \t{:.2f}%'.format(int(files_completed['completed']), int(files_completed['total']), (files_completed['completed'] / files_completed['total'] * 100)))

    print('__________________________________________________________________')
    print()

    print('WORDS')
    for key in segments.keys():
        print('\t{:10}\t{:10,} / {:,} \t{:.2f}%'.format(key, int(segments[key]['done']), int(segments[key]['total']), (segments[key]['done'] / segments[key]['total'] * 100)))
        totals['done'] += segments[key]['done']
        totals['total'] += segments[key]['total']

    print('\t----------------------------------------------------------')
    print('\tTOTAL\t\t{:10,} / {:,} \t{:.2f}%'.format(int(totals['done']), int(totals['total']), (totals['done'] / totals['total'] * 100)))
    print('__________________________________________________________________')
    print()

def print_help():
    print('Usage: python3 ge-stats.py [OPTION]')
    print('Generate Decompilation Stats for Goldeneye 007')
    print('Example: python3 ge-stats.py --version us')
    print(' ')
    print('Options:')
    print('  -v, --version=CODE     generate decomp stats for version CODE (us or jp). Default is us')
    print('  -r, --report           generate html report using report tool')
    print('  -h, --help             display this help text and exit')

def main():

    version = 'us'
    run_report = False

    if (len(sys.argv) > 1):
        arguments, values = getopt.getopt(sys.argv[1:], "hv:r", ["help", "version=", "report"])

        for current_argument, current_value in arguments:
            if current_argument in ("-h", "--help"):
                print_help()
                sys.exit()
            elif current_argument in ("-v", "--version"):
                version = current_value
            elif current_argument in ("-r", "--report"):
                run_report = True

    files_completed = find_files_completed()

    last_modified_file = find_last_modified_file()

    map_file = parse_map(version)

    folders = ['src', 'src/game', 'src/inflate', 'src/libultra']

    segments = do_stats(version, map_file, folders)

    print_stats(version, segments, files_completed, last_modified_file)

    if run_report:
        generate_report(segments, files_completed, last_modified_file, version)

if __name__ == '__main__':
    main()
