#!/usr/bin/env python
#coding=utf-8
#

import os
import datetime
import argparse
import tempfile
import subprocess

def clean_status():
    cmd = 'git status -s'.split()
    result = subprocess.check_output(cmd).splitlines()
    for line in result:
        root = os.path.dirname(line.split()[1])
        ignore_file = '/'.join([root, '.otto_release_not'])
        if os.path.exists(ignore_file) is False:
            return False
    return True

def rev_detail(rev):
    cmd = 'git show {}'.format(rev).split()
    try:
        result = subprocess.check_output(cmd).split('\n')[0].split()[1][0:8]
        open('rlz_ver', 'w').writelines('VCS_VER := 0x{}\n'.format(result))
    except:
        result = False

    return result

def switch_to_rev(rev):
    cmd = 'git show --oneline -s'.split()
    show_line = subprocess.check_output(cmd).split()
    if (show_line[2] == '->'):
        original_rev = show_line[3].split(')')[0]
    else:
        original_rev = show_line[0]

    cmd = "git checkout {}".format(rev).split()
    subprocess.call(cmd);

    return original_rev

def check_preconfig_existence(cfgs, is_uboot):
    cfg_root = './uboot/vendors' if is_uboot else './project'
    all_set = set(os.listdir(cfg_root))

    for cfg in cfgs:
        if cfg not in all_set:
            print('EE: {}/{} doesn"t exist'.format(cfg_root, cfg))
            return False
    return True

def get_p_ignore_path(pcfg):
    all_project_set = set(os.listdir('./project'))
    all_ext_set = set(os.listdir('./ext'))
    all_template_set = set(os.listdir('./src/template'))
    all_platform_set = set(os.listdir('./src/platform'))

    template_set = set()
    for cfg in pcfg:
        info_in = './project/{}/info.in'.format(cfg)
        for line in open(info_in).readlines():
            words = line.strip().split()
            if ('template_name' == words[0]):
                template_set.add(words[2])
                break;

    platform_set = set()
    for template in template_set:
        info_in = './src/template/{}/info.in'.format(template)
        for line in open(info_in).readlines():
            words = line.strip().split()
            if ('platform_name' == words[0]):
                platform_set.add(words[2])
                break;

    ignore_ext_set = all_ext_set - platform_set

    ignore_set = set()
    for item in (all_project_set - set(pcfg)):
        ignore_set.add('./project/{}'.format(item))

    for item in (all_template_set - template_set):
        ignore_set.add('./src/template/{}'.format(item))

    for item in (all_platform_set - platform_set):
        ignore_set.add('./src/platform/{}'.format(item))

    for item in (all_ext_set - platform_set):
        ignore_set.add('./ext/{}'.format(item))

    return ignore_set

def get_u_board_from_vendor(ucfg):
    vendor_board_map = {
        'CONFIG_RTL8370UG=y': 'rtl8370ug',
        'CONFIG_LUNA=y': 'luna',
        'CONFIG_RTL9601B=y': 'rtl9601b',
        'CONFIG_RTL9601C=y': 'rtl9601C',
        'CONFIG_RTL9602C=y': 'rtl9602C',
        'CONFIG_RTL8685S=y': 'rtl8685S',
        'CONFIG_GFAST=y': 'gfast',
        'CONFIG_RTL9300=y': 'rtl9300',
        'CONFIG_RTL9310=y': 'rtl9310',
        'CONFIG_APRO=y': 'apro',
    }
    board_set = set()
    for cfg in ucfg:
        uboot_config_in = './uboot/vendors/{}/uboot_config'.format(cfg)
        for line in open(uboot_config_in).readlines():
            config_string = line.strip()
            if config_string in vendor_board_map:
                board_set.add(vendor_board_map[config_string])
                break
            elif 'CONFIG_TARGET_NAME=' in config_string:
                board_set.add(config_string.split('"')[1])
                break
    return board_set

def get_ignore_set_in_board_dir(board_set):
    board_path = './uboot/board/Realtek'

    # most boards have sym. link to this dir. for mem. test code
    board_set.add('rtk_mem_test')

    all_set = set(os.listdir(board_path))
    ignore_set = {'/'.join([board_path, board]) for board in all_set - board_set}

    return ignore_set

def get_ignore_set_in_arch_cpu_dir(board_set):
    arch_cpu_path = './uboot/arch/otto/cpu'
    arch_cpu_set = set(os.listdir(arch_cpu_path))

    ignore_set = set()
    for root, dirname, filename in os.walk(arch_cpu_path):
        # e.g., ./uboot/arch/otto/cpu/mipsIA is 6 after split
        is_cpu_dir = True if (len(root.split('/')) == 6) else False

        if is_cpu_dir is False:
            continue

        all_board_set = set(dirname)
        rest_board_set = all_board_set - board_set
        if rest_board_set == all_board_set:
            ignore_set.add(root)
        elif len(rest_board_set) > 0:
            ignore_set |= {'/'.join([root, board]) for board in rest_board_set}
        else:
            pass

    return ignore_set

def get_ignore_set_in_arch_include_dir(board_set):
    asm_path = './uboot/arch/otto/include/asm'
    asm_set = set(os.listdir(asm_path))
    ignore_set = set()
    for name in asm_set:
        full_path = '/'.join([asm_path, name])
        if os.path.isdir(full_path) is False:
            continue
        if (len(name) > 5) and (name.split('-')[1] in board_set):
            continue
        ignore_set.add(full_path)

    return ignore_set

def get_ignore_set_in_include_configs_dir(board_set):
    header_path = './uboot/include/configs'
    header_set = set(os.listdir(header_path))
    ignore_set = set()
    for name in header_set:
        full_path = '/'.join([header_path, name])
        if os.path.isdir(full_path):
            continue
        if name.split('.')[0] in board_set:
            continue
        ignore_set.add(full_path)

    return ignore_set

def get_ignore_set_from_ucfg(ucfg):
    vendor_path = './uboot/vendors'
    all_set = set(os.listdir(vendor_path))
    ignore_set = {'/'.join([vendor_path, vendor]) for vendor in all_set - set(ucfg)}

    return ignore_set

def get_u_ignore_path(ucfg):
    board_set = get_u_board_from_vendor(ucfg)

    ignore_set = get_ignore_set_in_board_dir(board_set)
    ignore_set |= get_ignore_set_in_arch_cpu_dir(board_set)
    ignore_set |= get_ignore_set_in_arch_include_dir(board_set)
    ignore_set |= get_ignore_set_in_include_configs_dir(board_set)
    ignore_set |= get_ignore_set_from_ucfg(ucfg)
    return ignore_set

def get_ignore_from_gitignore():
    cmd = "find . -name .gitignore".split()
    gitignores = subprocess.check_output(cmd).split()

    ignore_set = set()
    for name in gitignores:
        if './uboot_todaro' in name:
            continue

        for line in open(name).readlines():
            line = line.strip()

            if len(line) == 0:
                continue

            if '/' == line[0]:
                ignore_set.add('{}{}'.format(os.path.dirname(name), line))

    return ignore_set

def get_misc_ignore_path():
    ignore_set = get_ignore_from_gitignore()
    ignore_set |=  {
        './uboot/arch/mips',
        './rlz.makefile',
        './toolkit_for_verification.cfg',
    }
    ignore_set.remove('./rlz_ver')
    ignore_set.remove('./util/bin')
    return ignore_set

def dump_ignore_set_to_file(ignore_set, keep):
    not_keep = not keep
    f = tempfile.NamedTemporaryFile(mode='w+b', delete=not_keep)
    for item in ignore_set:
        item = item.replace('./', 'otto/')
        f.writelines('{}\n'.format(item))
    f.flush()
    return f

def in_repository_root():
    if (os.path.isdir('.git')):
        return True
    else:
        return False

def dump_aval_preconfigs():
    print('II: Preloader:./project')
    print([p for p in os.listdir('./project')])
    print('II: U-Boot:./uboot/vendors')
    print([v for v in os.listdir('./uboot/vendors')])

def install_me():
    user = os.environ.get('USER')
    home_path = '/'.join(['/home', user])
    paths = os.environ.get('PATH').split(':')
    for path in paths:
        if home_path != path[:len(home_path)]:
            continue

        dst = '/'.join([path, os.path.basename(__file__)])
        if (os.path.exists(dst)):
            print('Error: {} already exists'.format(dst))
            return

        src = os.path.realpath(__file__)
        os.symlink(src, dst)
        print('Created soft-link: {} -> {}'.format(dst, src))
        return

    print("Error: can't find personal path: {}".format(path))
    return

def get_major_ver():
    cmd = 'git branch -r'.split()
    result = subprocess.check_output(cmd).split('\n')
    for line in result:
        if '->' in line:
            branch = line.split('/')[-1]
            continue
    if branch == 'master':
        return 4
    elif branch == 'products':
        return 3
    else:
        return 'x'

def check_major_options(args):
    if args.r is None:
        return False
    if args.p is None:
        return False
    if args.u is None:
        args.u = []
    return True

def main(args):
    _args = args.parse_args()

    if _args.l:
        dump_aval_preconfigs()
        return

    if _args.i:
        install_me()
        return

    if check_major_options(_args) is False:
        args.error('-r and -p are required')

    ucfg = _args.u
    pcfg = _args.p
    rev = _args.r
    up_decouple = _args.d

    if in_repository_root() is False:
        args.error('not in repository root')

    if clean_status() is False:
        args.error('repository is dirty. Apply stash?')

    rev = rev_detail(rev)
    if rev is False:
        args.error('given rev does not exist')

    original_rev = switch_to_rev(rev)

    is_uboot = False
    check_preconfig_existence(pcfg, is_uboot)

    is_uboot = True
    check_preconfig_existence(ucfg, is_uboot)

    if up_decouple is False:
        check_preconfig_existence(pcfg, is_uboot)
        ucfg += pcfg

    ignore_set = get_p_ignore_path(pcfg)
    ignore_set |= get_u_ignore_path(ucfg)
    ignore_set |= get_misc_ignore_path()

    ignore_list_file = dump_ignore_set_to_file(ignore_set, _args.k)

    now = datetime.datetime.now()
    tar_name = 'otto{}_{}_{}'.format(get_major_ver(), now.strftime('%y%m%d%H'), rev)
    tar_extra_options = [
        '--xform=s|^otto|{}|'.format(tar_name),
        '--exclude-vcs',
        '--exclude-vcs-ignores',
        '--exclude-tag-all=.otto_release_not',
        '--exclude-ignore={}'.format(ignore_list_file.name),
        'otto']
    tar_cmd = "tar -C .. -cjf ../{}.tar.bz2".format(tar_name).split()
    tar_cmd += tar_extra_options
    print(' '.join(tar_cmd))
    subprocess.call(tar_cmd)

    ignore_list_file.close()

    switch_to_rev(original_rev)

    return

if __name__ == "__main__":
    desc = "Given rev and preconfigs, produces a tarball containing just the given preconfig."
    parser = argparse.ArgumentParser(description = desc)

    major = parser.add_argument_group('Major Options')
    major.add_argument('-r', metavar='revision',
                       help='revision/branch/tag to be released')
    major.add_argument('-p', metavar='p-preconfig', nargs='+',
                       help='Preloader preconfig')
    major.add_argument('-u', metavar='u-preconfig', nargs='*',
                       help='U-Boot preconfig')
    major.add_argument('-d', action='store_true',
                       help='U-Boot preconfig list does not include Preloader preconfigs')
    major.add_argument('-k', action='store_true',
                       help='Keep ignore list for debug')

    util = parser.add_argument_group('Misc. Support Options')
    util.add_argument('-l', action='store_true',
                      help='list aval. preconfigs')
    util.add_argument('-i', action='store_true',
                      help='install git-release')

    main(parser)
