#!/usr/bin/env python2

import os
import sys
import subprocess as subproc

# Ensure this script and its deps will not force python to write bytecode files
sys.dont_write_bytecode = True

sys.path[0] = os.path.abspath('tools')
os.putenv('PYTHONPATH', os.getenv('PYTHONPATH', '') + ':' + sys.path[0])


import os
# import shutil
# import subprocess as subproc


BUILDDIR = os.path.abspath('build')
VENVDIR = os.path.join(BUILDDIR, 'venv')
DEVENVDIR = os.path.join(BUILDDIR, 'devenv')
DEVPYTHON = os.path.join(DEVENVDIR, 'bin', 'python')
SNAPSHOTDIR = os.path.abspath('snapshot')
VARDIR = os.path.join(SNAPSHOTDIR, 'var')
BASE_PYTEST_ARGS = ['-m', 'pytest', 'tests', '-v', '--tb=short', '-rxs']
PYTHON = os.getenv('PYTHON', sys.executable)


def _extra_pytest_keywords(args, kw):
    keywords_idx = None
    keywords = ''

    for idx, arg in enumerate(args):
        if arg == '-k':
            keywords_idx = idx + 1
            keywords = args[keywords_idx]
            break
    else:
        keywords_idx = None
        keywords = ''

    if keywords:
        keywords = '(%s) and %s' % (keywords, kw)
    else:
        keywords = kw

    if keywords_idx is not None:
        args[keywords_idx] = keywords
    else:
        args.extend(('-k', keywords))


if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()

    proc = subproc.Popen([PYTHON, '-s', '-m', 'doit', 'list', '-f', './do'], stdout=subproc.PIPE)
    tasks = [s for s in [s.split(' ', 1)[0].strip() for s in proc.stdout.read().split('\n')] if s]
    proc.wait()
    if not proc.returncode == 0:
        os._exit(int(proc.returncode))

    subparsers = parser.add_subparsers(dest='task')
    subparsers.add_parser('clean')
    subparsers.add_parser('dumpdb')
    subparsers.add_parser('forget')
    run_parser = subparsers.add_parser('run')
    run_parser.add_argument('-f', '--force', action='store_true')
    test_parser = subparsers.add_parser('test')
    benchmark_parser = subparsers.add_parser('benchmark')
    dependencies_parser = subparsers.add_parser('dependencies')

    for task in tasks:
        cmd_parser = subparsers.add_parser(task)
        cmd_parser.add_argument('--forget', action='store_true')
        cmd_parser.add_argument('--ignore', action='store_true')
        cmd_parser.add_argument('--strace', action='store_true')

    argv = sys.argv[1:]
    argv_groups = {'': []}
    for idx in range(len(argv) - 1, -1, -1):
        value = argv[idx]
        argv_groups[''].insert(0, value)
        if value in tasks + ['clean', 'dumpdb', 'forget', 'run', 'test', 'benchmark', 'dependencies']:
            if value not in argv_groups:
                argv_groups[value] = argv_groups['']
            argv_groups[''] = []

    allargs = []
    for groupname, group in argv_groups.items():
        if not group:
            continue
        if groupname in ('run', 'test', 'benchmark', 'dependencies'):
            args, argv = parser.parse_known_args(argv_groups[''] + group)
            allargs.append((args, argv))
        else:
            args = parser.parse_args(argv_groups[''] + group)
            if args:
                allargs.append(args)

    def _rundoit(cmd, args, f=True):
        fargs = (
            [PYTHON, '-m', 'doit'] +
            [cmd] +
            (['-f', './do'] if f else []) +
            args
        )
        proc = subproc.Popen(fargs)
        try:
            proc.wait()
        except KeyboardInterrupt:
            proc.wait()

        if proc.returncode != 0:
            os._exit(proc.returncode)
        return True

    for args in allargs:
        if isinstance(args, tuple):
            args, argv = args
        else:
            argv = None

        if args.task == 'run':
            binary = os.path.join(DEVENVDIR, 'bin/skybone-coord')
            if not os.path.exists(binary) or args.force:
                _rundoit('run', ['-v2', 'dev'])
            os.execvp(
                binary, [
                    binary,
                    '--appdir', DEVENVDIR,
                    '--config', 'share/config.yaml',
                    '--workdir', VARDIR,
                    '-v4'
                ] + argv
            )

        elif args.task == 'test':
            if not os.path.exists(os.path.join(DEVENVDIR, 'bin/skybone-coord')):
                _rundoit('run', ['-v2', 'dev'])
            cov_args = [
                '--cov=skybone_coord',
                '--cov-config=tools/python-coverage.conf',
                '--cov-report=html'
            ]
            _extra_pytest_keywords(argv, 'not benchmark')
            if '--cov' in argv:
                argv.remove('--cov')
                argv.extend(cov_args)
            argv = [DEVPYTHON] + BASE_PYTEST_ARGS + argv
            os.execvp(argv[0], argv)

        elif args.task == 'benchmark':
            if not os.path.exists(os.path.join(DEVENVDIR, 'bin/skybone-coord')):
                _rundoit('run', ['-v2', 'dev'])
            _extra_pytest_keywords(argv, 'benchmark')
            argv = [DEVPYTHON] + BASE_PYTEST_ARGS + ['--benchmark', '-s'] + argv
            os.execvp(argv[0], argv)

        elif args.task == 'dependencies':
            os._exit(0)

        elif args.task in ('dumpdb', ):
            _rundoit(args.task, [], f=False)

        elif args.task in ('clean', 'forget'):
            _rundoit(args.task, [])

        elif args.task in tasks:
            if args.forget:
                _rundoit('forget', [args.task])
            elif args.ignore:
                _rundoit('ignore', [args.task])
            elif args.strace:
                _rundoit('strace', [args.task])
            else:
                _rundoit('run', ['-v2', args.task])

    os._exit(0)


def task_sdist():
    """ Make source distribution at dist/ """
    return {
        'actions': [
            [PYTHON, '-B', 'tools/setup.py', 'sdist', '--formats=bztar'],
            'rm -rf skybone_coord.egg-info'
        ],
    }


def task_venv():
    return {
        'actions': [
            [PYTHON, '-s', '-m', 'virtualenv', '--system-site-packages', VENVDIR],
            ['ln', '-sfn', '/skynet/python/lib/libpython2.7.so', os.path.join(VENVDIR, 'lib')]
        ]
    }


def task_bundle():
    def _fix_launcher(path):
        lines = [l.rstrip() for l in open(path, 'rb').readlines()]
        if lines[1].startswith('# EASY-INSTALL-ENTRY-SCRIPT'):
            lines[0] = '#!/skynet/python/bin/python'

            extra = [
                'import os',
                'root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))',
                'lib = os.path.join(root, \'lib\')',
                '__import__(\'site\').addsitedir(lib)',
                '__import__(\'sys\').path.insert(0, lib)',
            ]

            lines = lines[:1] + ['', '# MOCKSOUL-SITE-FIX'] + extra + [''] + lines[1:]
            open(path, 'wb').write('\n'.join(lines))

    return {
        'actions': [
            [
                os.path.join(VENVDIR, 'bin', 'python'), '-u',
                os.path.join(VENVDIR, 'bin', 'pip'),
                'install',
                '-i', 'http://pypi.yandex-team.ru/simple',
                'dist/skybone-coord-dev.tar.bz2'
            ],
            'rm -rf "{SNAPSHOTDIR}"'.format(**globals()),
            'mkdir -p "{SNAPSHOTDIR}/bin"'.format(**globals()),
            ['cp', '-r', os.path.join(VENVDIR, 'bin', 'skybone-coord'), os.path.join(SNAPSHOTDIR, 'bin')],
            ['cp', '-r', os.path.join(VENVDIR, 'bin', 'skybone-coord-shard'), os.path.join(SNAPSHOTDIR, 'bin')],
            ['cp', '-r', os.path.join(VENVDIR, 'bin', 'skybone-coord-router'), os.path.join(SNAPSHOTDIR, 'bin')],
            [
                'cp', '-r',
                os.path.join(VENVDIR, 'lib', 'python2.7', 'site-packages'),
                os.path.join(SNAPSHOTDIR, 'lib')
            ],
            (_fix_launcher, (os.path.join(SNAPSHOTDIR, 'bin', 'skybone-coord'), )),
            (_fix_launcher, (os.path.join(SNAPSHOTDIR, 'bin', 'skybone-coord-shard'), )),
            (_fix_launcher, (os.path.join(SNAPSHOTDIR, 'bin', 'skybone-coord-router'), )),
            ['cp', 'skybone-coord.scsd', SNAPSHOTDIR],
        ],
        'task_dep': ['sdist', 'venv']
    }


def task_devel():
    pylib = os.path.join(DEVENVDIR, 'lib/python2.7/site-packages')

    return {
        'actions': [
            [PYTHON, '-s', '-m', 'virtualenv', '--system-site-packages', DEVENVDIR],
            [
                os.path.join(DEVENVDIR, 'bin/pip'),
                'install', '-i', 'http://pypi.yandex-team.ru/simple', '-r', 'tools/requirements.txt'
            ],
            [DEVPYTHON, '-B', 'tools/setup.py', 'egg_info', 'install_scripts'],
            ['rm', '-rf', os.path.join(pylib, 'skybone_coord.egg-info')],
            ['cp', '-r', 'skybone_coord.egg-info', pylib],
            ['rm', '-rf', 'skybone_coord.egg-info'],
            ['rm', '-rf', os.path.join(pylib, 'skybone_coord')],
            ['ln', '-sf', os.path.abspath('src'), os.path.join(pylib, 'skybone_coord')],
            subproc.list2cmdline(['echo', ';'.join([
                'import sys, types, os',
                'p = os.path.join(sys._getframe(1).f_locals["sitedir"], *("skybone_coord",))',
                'ie = os.path.exists(os.path.join(p, "__init__.py"))',
                'm = not ie and sys.modules.setdefault("skybone_coord", types.ModuleType("skybone_coord"))',
                'mp = (m or []) and m.__dict__.setdefault("__path__", [])',
                '(p not in mp) and mp.append(p)'
            ]), '>', os.path.join(pylib, 'skybone-coord-py2.7-nspkg.pth')]),
        ]
    }


def task_update_config():
    def _update_config():
        from library import config
        import py

        config.registrydir = lambda: py.path.local('snapshot/etc/registry')

        print('updating registry...')
        config.update()

        print('updating bundled config...')
        import yaml
        yaml.dump(
            config.query('skynet.services.skybone-coord', as_dict=True),
            open('share/config.yaml', 'wb'),
            default_flow_style=False
        )

        print('config written to share/config.yaml')
        print('dont forget to commit!')

    return {
        'actions': [_update_config]
    }
