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

import shutil
import os
import datetime
import argparse
import tempfile
import subprocess

def arrange_file(rev, orig, patch_base):
    if os.path.islink(orig) or os.path.isfile(orig):
        mkdir = "{}/{}".format(patch_base, os.path.dirname(orig))
        if os.path.exists(mkdir) is False:
            os.makedirs(mkdir);
    else:
        # git doesn't track empty dir., so need not worry about dir type.
        print("EE: {}: unexpected type: {}".format(rev, orig))

    copy_cmd = "cp -af {} {}".format(orig, mkdir);
    os.popen(copy_cmd)
    return

def process_rev(rev, base_dir, change_list, ignored_modes, dealing_modes):
    os.makedirs(base_dir);

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

    for i, fn in enumerate(change_list):
        mode, fn = fn.strip().split()[:2]
        if (mode in ignored_modes) or (mode == '#'):
            True # does nothing
        elif mode in dealing_modes:
            arrange_file(rev, fn, base_dir)
        else:
            print("EE: {}: unknown mode: {} for {}".format(rev, mode, fn))
            exit(1)
    return

def prepare_patch_dir(from_rev, to_rev, prefix):
    now = datetime.datetime.now()
    patch_root_path = "%s_%s_%s_%s" % (prefix, now.strftime('%y%m%d%H'), from_rev, to_rev)

    if os.path.exists(patch_root_path):
        shutil.rmtree(patch_root_path)

    os.makedirs(patch_root_path);

    return patch_root_path

def create_patch(from_rev, to_rev, patch_root, change_list):
    before_dir = "%s/before" % (patch_root)
    print('II: {}: filling {}'.format(from_rev, before_dir))
    process_rev(from_rev, before_dir, change_list, 'A', 'MDT')

    after_dir = "%s/after" % (patch_root)
    print('II: {}: filling {}'.format(to_rev, after_dir))
    process_rev(to_rev, after_dir, change_list, 'D', 'MAT')

    return;

def get_change_list_from_file(fn):
    change_list = open(fn, 'r').readlines()
    from_rev = ''
    to_rev = ''

    for i, line in enumerate(change_list):

        if (':' not in line):
            break

        var, res = line.strip().split(':')
        if ('#' == var[0]):
            if 'from_rev' in var:
                from_rev = res.strip()
            elif 'to_rev' in var:
                to_rev = res.strip()
        else:
            break

    return change_list, from_rev, to_rev

def get_change_list_from_diff(from_rev, to_rev):
    cmd_tmp = "git diff --name-status {} {}".format(from_rev, to_rev)
    change_list = os.popen(cmd_tmp).readlines();
    change_list[0:0] = [
        '# from_rev: {}\n'.format(from_rev),
        '# to_rev: {}\n'.format(to_rev),
        '# cmd: ${}\n'.format(cmd_tmp)]

    return change_list

def refine_change_list(fn, change_list, no_edit):
    open(fn, 'w').writelines(change_list)

    if no_edit is True:
        return change_list

    editor = os.environ.get('EDITOR','vim').split()
    editor.append(fn)

    subprocess.call(editor)

    return open(fn, 'r').readlines()

def rev_exist(rev):
    cmd = 'git show {}'.format(rev).split()
    try:
        result = subprocess.check_output(cmd).split('\n')[0].split()[0]
    except:
        result = 'Exception'

    if result == 'commit':
        return True
    else:
        return False

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

    if _args.l:
        if os.path.isfile(_args.l):
            change_list, from_rev, to_rev = get_change_list_from_file(_args.l)
        else:
            print('EE: can\'t find {}'.format(_args.l))
    else:
        from_rev, to_rev = _args.r.split(':')

    if (not rev_exist(from_rev)) or (not rev_exist(to_rev)):
        args.print_help()
        return
    else:
        patch_root_path = prepare_patch_dir(from_rev, to_rev, _args.p)

    if _args.r:
        change_list = get_change_list_from_diff(from_rev, to_rev)

    change_list_fn = './{}/vcs_{}_to_{}.lst'.format(patch_root_path, from_rev, to_rev)
    change_list = refine_change_list(change_list_fn, change_list, _args.no_edit)

    create_patch(from_rev, to_rev, patch_root_path, change_list)

    return

if __name__ == "__main__":
    desc = "Given from-rev and to-rev, bnapatch produces patch in before-and-after " \
           "structure, so that from-rev + patch = to-rev. For example, " \
           "`git bnapatch -r xxxx:yyyy` generates patch to update xxxx to yyyy; and " \
           "`git bnapatch -l vcs_diff_log.lst` gets from_rev, to_rev, and change list " \
           "from vcs_diff_log.lst"
    parser = argparse.ArgumentParser(description = desc)
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-r', metavar='from_rev:to_rev', help='revision to patch from and to')
    group.add_argument('-l', help='change list file in `diff --name-status` format')


    parser.add_argument('-p', default='otto', help='prefix of patch directory')
    parser.add_argument('--no-edit', action='store_true', help='don\'t edit change list')
    main(parser)
