#!/usr/bin/env python
import getopt
import hashlib
import logging
import os
import shutil
import subprocess
import sys
import tempfile
import time
import yaml


yaml.add_multi_constructor('tag:yaml.org,2002:python/object:', lambda a, b, c: None)


def find_plugins():
    if os.path.exists('__init__.py'):
        yield '.'
    else:
        for dp, dn, fn in os.walk('.'):
            for d in dn:
                dir = os.path.join(dp, d)
                if os.path.exists(os.path.join(dir, 'plugin.yml')):
                    yield dir


def run_bower(path, cmdline):
    bower_json = os.path.join(path, 'bower.json')
    bower_rc = os.path.join(path, '.bowerrc')

    if not os.path.exists(bower_json):
        logging.warn('Plugin at %s has no bower.json' % path)
        return

    with open(bower_rc, 'w') as f:
        f.write('{"directory" : "resources/vendor"}')

    if not os.path.exists(os.path.join(path, 'resources/vendor')):
        os.makedirs(os.path.join(path, 'resources/vendor'))

    logging.info('Running bower %s in %s' % (cmdline, path))
    code = subprocess.call('bower -V --allow-root %s' % cmdline, shell=True, cwd=path)
    if code != 0:
        logging.error('Bower failed for %s' % path)
    os.unlink(bower_rc)


def run_build(plugin, cache_enabled):
    cache_path = '/tmp/.ajenti-resource-cache'
    if not os.path.exists(cache_path):
        os.makedirs(cache_path)

    def get_hash(name):
        return hashlib.sha512(name).hexdigest()

    def get_cached(name):
        if os.path.exists(os.path.join(cache_path, get_hash(name))):
            return open(os.path.join(cache_path, get_hash(name))).read()

    def get_cached_time(name):
        if os.path.exists(os.path.join(cache_path, get_hash(name))):
            return os.stat(os.path.join(cache_path, get_hash(name))).st_mtime

    def set_cached(name, content):
        open(os.path.join(cache_path, get_hash(name)), 'w').write(content)

    resources = yaml.load(open(os.path.join(plugin, 'plugin.yml')))['resources']

    if not resources:
        return
    logging.info('Building resources for %s' % plugin)

    if not os.path.exists(os.path.join(plugin, 'resources/build')):
        os.makedirs(os.path.join(plugin, 'resources/build'))

    all_js = ''
    all_css = ''
    for resource in resources:
        path = os.path.join(plugin, resource)
        if resource.endswith('.coffee'):
            if not cache_enabled or not get_cached(path) or get_cached_time(path) < os.stat(path).st_mtime:
                logging.info('Compiling %s' % path)
                set_cached(path, subprocess.check_output(['coffee', '-p', '-c', path]) + '\n')
            all_js += get_cached(path)
        if resource.endswith('.js'):
            logging.info('Including %s' % path)
            all_js += open(path).read() + '\n'
        if resource.endswith('.less'):
            if not cache_enabled or not get_cached(path) or get_cached_time(path) < os.stat(path).st_mtime:
                logging.info('Compiling %s' % path)
                set_cached(path, subprocess.check_output(['lessc', path]) + '\n')
            all_css += get_cached(path)
        if resource.endswith('.css'):
            logging.info('Including %s' % path)
            all_css += open(path).read() + '\n'

    with open(os.path.join(plugin, 'resources/build/all.js'), 'w') as f:
        f.write(all_js)
    with open(os.path.join(plugin, 'resources/build/all.css'), 'w') as f:
        f.write(all_css)


def run_setuptools(plugin, cmd):
    info = yaml.load(open(os.path.join(plugin, 'plugin.yml')))
    workspace = tempfile.mkdtemp()
    logging.info('Working under %s' % workspace)
    shutil.copytree(plugin, os.path.join(workspace, 'ajenti_plugin_%s' % info['name']))
    shutil.copy(os.path.join(plugin, 'requirements.txt'), workspace)

    setuppy = '''#!/usr/bin/env python
from distutils.core import setup
from setuptools import find_packages

import os

__requires = filter(None, open('requirements.txt').read().splitlines())

setup(
    name='ajenti.plugin.%(name)s',
    version='%(version)s',
    install_requires=__requires,
    description='Ajenti %(name)s plugin',
    author='%(author)s',
    author_email='%(email)s',
    url='%(url)s',
    packages=find_packages(),
    include_package_data=True,
)
    ''' % info
    with open(os.path.join(workspace, 'setup.py'), 'w') as f:
        f.write(setuppy)

    manifest = '''
recursive-exclude . *.pyc
recursive-include ajenti_plugin_%(name)s/content *
recursive-include ajenti_plugin_%(name)s/resources *
include ajenti_plugin_%(name)s/plugin.yml
include MANIFEST.in
include requirements.txt
    ''' % info
    with open(os.path.join(workspace, 'MANIFEST.in'), 'w') as f:
        f.write(manifest)

    subprocess.check_call('python setup.py %s' % cmd, cwd=workspace, shell=True)

    dist = os.path.join(workspace, 'dist')
    sdist = os.path.join(plugin, 'dist')
    if os.path.exists(sdist):
        shutil.rmtree(sdist)
    if os.path.exists(dist):
        shutil.copytree(dist, sdist)

    if 'upload' in cmd.split():
        open(os.path.join(plugin, '.last-upload'), 'w').write(str(time.time()))


def run_find_outdated(plugin):
    last_upload = 0
    last_file = os.path.join(plugin, '.last-upload')
    if os.path.exists(last_file):
        last_upload = float(open(last_file).read())

    last_changed = 0
    for d, dn, fn in os.walk(plugin):
        for f in fn:
            last_changed = max(last_changed, os.stat(os.path.join(d, f)).st_mtime)

    if last_changed > last_upload + 10:
        logging.warn('Plugin %s has unpublished changes' % plugin)


def usage():
    #General commands
    #    --create-plugin <name>     - creates a new plugin in a subdirectory
    print("""
Usage: %s [options]

Plugin commands (these operate on all plugins found within current directory)
    --run                      - Run Ajenti with plugins from the current directory
    --run-dev                  - Run Ajenti in dev mode with plugins from the current directory
    --bower '<cmdline>'        - Run Bower, e.g. --bower install
    --build                    - Compile resources
    --rebuild                  - Force recompile resources
    --setuppy '<args>          - Run a setuptools build
    --find-outdated            - Find plugins that have unpublished changes
    """ % sys.argv[0])


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    sys.path.insert(0, '.')

    try:
        opts, args = getopt.getopt(
            sys.argv[1:],
            '',
            [
                'run',
                'run-dev',
                'bower=',
                'build',
                'rebuild',
                'setuppy=',
                'find-outdated',
            ]
        )
    except getopt.GetoptError as e:
        print(str(e))
        usage()
        sys.exit(2)

    for o, a in opts:
        if o.startswith('--run'):
            cmd = [
                'ajenti-panel',
                '-v', '--plugins', '.'
            ]
            if o == '--run-dev':
                cmd += ['--dev']
            try:
                subprocess.call(cmd)
            except KeyboardInterrupt:
                pass
            sys.exit(0)
        if o == '--bower':
            for plugin in find_plugins():
                run_bower(plugin, a)
            sys.exit(0)
        if o == '--build':
            for plugin in find_plugins():
                run_build(plugin, True)
            sys.exit(0)
        if o == '--rebuild':
            for plugin in find_plugins():
                run_build(plugin, False)
            sys.exit(0)
        if o == '--setuppy':
            for plugin in find_plugins():
                run_setuptools(plugin, a)
            sys.exit(0)
        if o == '--find-outdated':
            for plugin in find_plugins():
                run_find_outdated(plugin)
            sys.exit(0)
        elif o == '--set-platform':
            ajenti.platform = a

    usage()
    sys.exit(2)
