#!/usr/bin/env python
"""
OPAL Admin script.
In which we expose useful commandline functionality to our users.
"""
import argparse
import inspect
import os
import shutil
import subprocess
import sys

import ffs
from ffs import nix
from ffs.contrib import mold

import opal
from opal.core import scaffold as scaffold_utils

USERLAND_HERE    = ffs.Path('.').abspath
SCRIPT_HERE      = ffs.Path(__file__).parent
OPAL             = ffs.Path(opal.__file__).parent
SCAFFOLDING_BASE = OPAL/'scaffolding'
SCAFFOLD         = SCAFFOLDING_BASE/'scaffold'
PLUGIN_SCAFFOLD  = SCAFFOLDING_BASE/'plugin_scaffold'
TRAVIS           = os.environ.get('TRAVIS', False)


def _userland_here_has__file(filename):
    """
    Predicate function to determine whether we have FILENAME is in the current working directory
    """
    return bool(ffs.Path(USERLAND_HERE/filename))

def _find_application_name():
    """
    Return the name of the current OPAL application
    """
    for d in  USERLAND_HERE.ls():
        if d.is_dir:
            if d/'settings.py':
                return d[-1]

    print "\n\nCripes!\n"
    print "We can't figure out what the name of your application is :(\n"
    print "Are you in the application root directory? \n\n"
    sys.exit(1)

def startproject(args):
    scaffold_utils.startproject(args, USERLAND_HERE)
    return

def startplugin(args):
    """
    The steps to create our plugin are:

    * Copy across the scaffold to our plugin directory
    * Interpolate our name into the appropriate places.
    * Rename the code dir
    * Create template/static directories
    """
    name = args.name

    print 'Bootstrapping "{0}" - your new OPAL plugin...'.format(name)

    if 'opal' in name:
        reponame = name
        name = name.replace('opal-', '')
    else:
        reponame = 'opal-{0}'.format(name)

    root = USERLAND_HERE/reponame

    # 1. Copy across scaffold
    shutil.copytree(PLUGIN_SCAFFOLD, root)

    # 2n. Interpolate scaffold
    scaffold_utils.interpolate_dir(root, name=name)

    # 3. Rename the code dir
    code_root = root/name
    nix.mv(root/'app', code_root)

    # 4. Create some extra directories.
    templates = code_root/'templates'
    templates.mkdir()
    static = code_root/'static'
    static.mkdir()
    jsdir = static/'js/{0}'.format(name)
    jsdir.mkdir()
    controllers = jsdir/'controllers'
    controllers.mkdir()
    services = jsdir/'services'
    services.mkdir()
    return


def scaffold(args):
    """
    Create record boilierplates:

    1. Run a south auto migration
    2. Create display templates
    3. Create forms
    """
    app = args.app
    name = _find_application_name()
    scaffold_utils._set_settings_module(name)
    sys.path.append(os.path.abspath('.'))

    # 1. Let's run a Django migration
    dry_run = ''
    if args.dry_run:
        dry_run = '--dry-run'

    makemigrations_cmd = 'python manage.py makemigrations {app} --traceback {dry_run}'.format(
        app=app, dry_run=dry_run)
    migrate_cmd = 'python manage.py migrate {app} --traceback '.format(app=app)

    os.system(makemigrations_cmd)
    if not args.dry_run:
        os.system(migrate_cmd)

    # 2. Let's create some display templates
    from opal.models import Subrecord, EpisodeSubrecord, PatientSubrecord
    from opal.utils import stringport

    models = stringport('{0}.models'.format(app))
    for i in dir(models):
        thing = getattr(models, i)
        if inspect.isclass(thing) and issubclass(thing, Subrecord):
            if thing in [Subrecord, EpisodeSubrecord, PatientSubrecord]:
                continue
            if not thing.get_display_template():
                if args.dry_run:
                    print 'No Display template for {0}'.format(thing)
                else:
                    scaffold_utils.create_display_template_for(thing, SCAFFOLDING_BASE)
            if not thing.get_modal_template():
                if args.dry_run:
                    print 'No Form template for {0}'.format(thing)
                else:
                    scaffold_utils.create_form_template_for(thing, SCAFFOLDING_BASE)
    return

def _run_py_tests(args):
    """
    Run our Python test suite
    """
    print "Running Python Unit Tests"

    # We have a custom test runner - e.g. it's OPAL itself or a plugin.
    if _userland_here_has__file('runtests.py'):
        test_args= ['python', 'runtests.py']
        if args.test:
            test_args.append(args.test)
        if args.coverage:
            test_args = ['coverage', 'run', 'runtests.py']

    # We have a manage.py script - assume that we're in an application
    elif _userland_here_has__file('manage.py'):
        test_args = ['python', 'manage.py', 'test']

        if args.test:
            test_args.append(args.test)

        if args.coverage:
            test_args = ['coverage', 'run', 'manage.py', 'test',]

    else:
        print "\n\nCripes!\n"
        print "We can't figure out how to run your tests :(\n"
        print "Are you in the root directory? \n\n"
        sys.exit(1)

    try:
        subprocess.check_call(test_args)
    except subprocess.CalledProcessError:
        sys.exit(1)

    if args.coverage:
        try:
            subprocess.check_call(['coverage', 'html'])
        except subprocess.CalledProcessError:
            sys.exit(1)

    return

def _run_js_tests(args):
    """
    Run our Javascript test suite
    """
    print "Running Javascript Unit Tests"
    env = os.environ.copy()
    if TRAVIS:
        karma = './node_modules/karma/bin/karma'
    else:
        karma = 'karma'
        env['DISPLAY'] = ':10'

    sub_args = [
        karma,
        'start',
        'config/karma.conf.js',
        '--single-run'
    ]

    try:
        subprocess.check_call(sub_args, env=env)
    except subprocess.CalledProcessError:
        sys.exit(1)
    return

def test(args):
    """
    Run our test suites
    """
    if args.what == 'all':
        suites = ['py', 'js']
    else:
        suites = args.what
    for suite in suites:
        globals()['_run_{0}_tests'.format(suite)](args)
    return



def check_for_uncommitted():
    changes = subprocess.check_output(["git", "status", "--porcelain"])
    return len(changes)


def get_requirements():
    """
    looks for a requirements file in the same directory as the
    fabfile. Parses it,
    """

    with USERLAND_HERE:
        requirements = subprocess.check_output(["less", "requirements.txt"]).split("\n")

        package_to_version = {}

        for requirement in requirements:
            parsed_url = parse_github_urls(requirement)

            if parsed_url:
                package_to_version.update(parsed_url)

    return package_to_version


def parse_github_urls(some_url):
    """
    takes in something that looks like a git hub url in a fabfile e.g.
    -e git+https://github.com/openhealthcare/opal-referral.git@v0.1.2#egg=opal_referral
    returns opal-referral
    """

    if "github" in some_url and "opal" in some_url:
        package_name = some_url.split("@")[0].split("/")[-1]
        package_name = package_name.replace(".git", "")
        version = some_url.split("@")[-1].split("#")[0]
        return {package_name: version}


def checkout(args):
    """
    This is our main entrypoint for the checkout command.
    """
    package_name_version = get_requirements()
    SOURCE_DIR = USERLAND_HERE.parent
    with SOURCE_DIR:
        existing_packages = subprocess.check_output(["ls"]).split("\n")
        uncommitted = []

        for package_name, version in package_name_version.iteritems():
            if package_name in existing_packages:
                with SOURCE_DIR/package_name:
                    if check_for_uncommitted():
                        uncommitted.append(package_name)

        if len(uncommitted):
            print "we have uncommited changes in {} quitting".format(
                ", ".join(uncommitted)
            )
            return

        for package_name, version in package_name_version.iteritems():
            if package_name in existing_packages:
                with SOURCE_DIR/package_name:
                    print "checking out {0} to {1}".format(
                        package_name, version
                    )
                    os.system("git checkout {}".format(version))
                    os.system("python setup.py develop")
            else:
                print "cloning {}".format(package_name)
                os.system("git clone {}".format(package_name))
                with SOURCE_DIR/package_name:
                    print "checking out {0} to {1}".format(
                        package_name, version
                    )
                    os.system("git checkout {}".format(version))
                    os.system("python setup.py develop")

def main():
    parser = argparse.ArgumentParser(
        description="OPAL a Clinical Transactional Digital Services Framework",
        usage="opal <command> [<args>]",
        epilog="Brought to you by Open Health Care UK"
    )
    parser.add_argument(
        '--version', '-v',
        action='version',
        version = 'OPAL {0}'.format(opal.__version__)
    )
    subparsers = parser.add_subparsers(help="OPAL Commands")

    parser_project = subparsers.add_parser(
        'startproject'
    )
    parser_project.add_argument(
        'name', help="name of your project"
    )
    parser_project.set_defaults(func=startproject)

    parser_plugin = subparsers.add_parser('startplugin')
    parser_plugin.add_argument(
        'name', help="name of your plugin"
    )
    parser_plugin.set_defaults(func=startplugin)

    parser_scaffold = subparsers.add_parser("scaffold")
    parser_scaffold.add_argument('app', help='Django app to scaffold')
    parser_scaffold.add_argument(
        '--dry-run',
        action='store_true',
        help="Just print the templates we would create - don't actually create them")
    parser_scaffold.set_defaults(func=scaffold)

    parser_test = subparsers.add_parser("test")
    parser_test.add_argument(
        'what', default='all', choices=['all', 'py', 'js'], nargs='*')
    parser_test.add_argument(
        '-t', '--test', help='Test case or method to run'
    )
    parser_test.add_argument(
        '-c', '--coverage', action='store_true', help='Generate a test coverage report'
    )
    parser_test.set_defaults(func=test)

    parser_checkout = subparsers.add_parser("checkout")
    parser_checkout.set_defaults(func=checkout)

    args = parser.parse_args()
    args.func(args)

    sys.exit(0)

if __name__ == '__main__':
    main()
