import argparse
import collections
import sys


def parse_export_file(src):
    for line in src:
        line = line.strip()

        if line and '#' not in line:
            words = line.split()
            if len(words) == 2 and words[0] == 'linux_version':
                yield {'linux_version': words[1]}
            elif len(words) == 2:
                yield {'lang': words[0], 'sym': words[1]}
            elif len(words) == 1:
                yield {'lang': 'C', 'sym': words[0]}
            else:
                raise Exception('unsupported exports line: "{}"'.format(line))


def to_c(sym):
    symbols = collections.deque(sym.split('::'))
    c_prefixes = [  # demangle prefixes for c++ symbols
        '_ZN',      # namespace
        '_ZTIN',    # typeinfo for
        '_ZTSN',    # typeinfo name for
        '_ZTTN',    # VTT for
        '_ZTVN',    # vtable for
        '_ZNK',     # const methods
    ]
    c_sym = ''
    while symbols:
        s = symbols.popleft()
        if s == '*':
            c_sym += '*'
            break
        if '*' in s and len(s) > 1:
            raise Exception('Unsupported format, cannot guess length of symbol: ' + s)
        c_sym += str(len(s)) + s
    if symbols:
        raise Exception('Unsupported format: ' + sym)
    if c_sym[-1] != '*':
        c_sym += 'E*'
    return ['{prefix}{sym}'.format(prefix=prefix, sym=c_sym) for prefix in c_prefixes]


def to_gnu(src, dest):
    d = collections.defaultdict(list)
    version = None
    for item in parse_export_file(src):
        if item.get('linux_version'):
            if not version:
                version = item.get('linux_version')
            else:
                raise Exception('More than one linux_version defined')
        elif item['lang'] == 'C++':
            d['C'].extend(to_c(item['sym']))
        else:
            d[item['lang']].append(item['sym'])
   
    if version:
        dest.write('{} {{\nglobal:\n'.format(version))
    else:
        dest.write('{\nglobal:\n')

    for k, v in d.items():
        dest.write('    extern "' + k + '" {\n')

        for x in v:
            dest.write('        ' + x + ';\n')

        dest.write('    };\n')

    dest.write('local: *;\n};\n')


def to_msvc(src, dest):
    dest.write('EXPORTS\n')
    for item in parse_export_file(src):
        if item.get('linux_version'):
            continue
        if item.get('lang') == 'C':
            dest.write('    {}\n'.format(item.get('sym')))


def to_darwin(src, dest):
    pre = ''
    for item in parse_export_file(src):
        if item.get('linux_version'):
            continue

        if item['lang'] == 'C':
            dest.write(pre + '-Wl,-exported_symbol,_' + item['sym'])
        elif item['lang'] == 'C++':
            for sym in to_c(item['sym']):
                dest.write(pre + '-Wl,-exported_symbol,_' + sym)
        else:
            raise Exception('unsupported lang: ' + item['lang'])
        if pre == '':
            pre = ' '


def main():
    parser = argparse.ArgumentParser(description='Convert self-invented platform independent export file format to the format required by specific linker')
    parser.add_argument('src', type=argparse.FileType('r', encoding='UTF-8'), help='platform independent export file path')
    parser.add_argument('dest', type=argparse.FileType('w', encoding='UTF-8'), help='destination export file for required linker')
    parser.add_argument('--format', help='destination file type format: gnu, msvc or darwin')

    args = parser.parse_args()
    if args.format == 'gnu':
        to_gnu(args.src, args.dest)
    elif args.format == 'msvc':
        to_msvc(args.src, args.dest)
    elif args.format == 'darwin':
        to_darwin(args.src, args.dest)
    else:
        print('Unknown destination file format: {}'.format(args.format), file=sys.stderr)
        sys.exit(1)

    args.src.close()
    args.dest.close()


if __name__ == '__main__':
    main()