#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Note:
#   Python profiling article: http://www.marinamele.com/7-tips-to-time-python-scripts-and-control-memory-and-cpu-usage

import logging
import os
import sys
import platform
import subprocess
import shutil
import click
from cleepcli.git import Git
from cleepcli.module import Module
from cleepcli.file import File
from cleepcli.watch import CleepWatchdog
from cleepcli.test import Test
from cleepcli.distrib import Distrib
from cleepcli.docs import Docs
import cleepcli.config as config
from cleepcli.version import VERSION

#logging.basicConfig(level=logging.DEBUG, format=u'%(asctime)s %(levelname)s [%(name)s:%(lineno)d]: %(message)s', stream=sys.stdout)
logging.basicConfig(level=logging.INFO, format=u'%(message)s', stream=sys.stdout)

# install logging trace level
level = logging.TRACE = logging.DEBUG - 5
def log_logger(self, message, *args, **kwargs):
    if self.isEnabledFor(level):
        self._log(level, message, args, **kwargs)
logging.getLoggerClass().trace = log_logger
def log_root(msg, *args, **kwargs):
    logging.log(level, msg, *args, **kwargs)
logging.addLevelName(level, "TRACE")
logging.trace = log_root

def print_version(ctx, param, value):
    if not value or ctx.resilient_parsing:
        return
    click.echo('Version %s' % VERSION)
    ctx.exit()

#@click.command()
#@click.option('--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True)
@click.version_option(version=VERSION)

@click.group()
def core():
    pass

@core.command()
def coreget():
    """ 
    Get or update core content from official repository
    """
    #core
    g = Git()
    if not os.path.exists(config.CORE_SRC):
        res = g.clone_core()
    else:
        res = g.pull_core()

    if not res:
        sys.exit(1)

    #force coresync to synchronize installation with repo
    f = File()
    res = f.core_sync()

    #default modules
    for module in config.DEFAULT_MODULES:
        module_path = os.path.join(config.MODULES_SRC, module)
        if not os.path.exists(module_path):
            res = g.clone_mod(module)
        else:
            res = g.pull_mod(module)

        if not res:
            logging.error('Error getting "%s" default module' % module)
            sys.exit(1)

@core.command()
def coresync():
    """
    Synchronize core content between source and execution folders
    """
    f = File()
    res = f.core_sync()

    if not res:
        sys.exit(1)

@core.command()
@click.option('--coverage', is_flag=True, help='Display coverage report.')
def coretests(coverage):
    """
    Execute core tests
    """
    m = Test()
    res = m.core_test(coverage)

    if not res:
        sys.exit(1)

@core.command()
def coretestscov():
    """
    Display core tests coverage
    """
    m = Test()
    res = m.core_test_coverage()

    if not res:
        sys.exit(1)

@core.command()
@click.option('--publish', is_flag=True, help='Publish documentation to github pages.')
def coredocs(publish):
    """
    Generate core documentation in appropriate format an publish if specified
    """
    d = Docs()
    res = d.generate_core_docs(publish)

    if not res:
        sys.exit(1)

@core.command()
def cpuprof():
    """
    Run CPU profiler on cleep
    """
    try:
        import socket
        public_ip = socket.gethostbyname(socket.gethostname())
        port = 4000

        #stop running process
        cmd = '/bin/systemctl stop cleep'
        subprocess.call(cmd, shell=True)

        logging.info('Follow live profiler analysis on "http://%s:%s". CTRL-C to stop.' % (public_ip, port))
        cmd = '/usr/local/bin/cprofilev -a "%s" -p %s "/usr/bin/cleep"' % (public_ip, port)
        logging.debug('Cpuprof cmd: %s' % cmd)
        subprocess.call(cmd, shell=True)

    except KeyboardInterrupt:
        pass

    except:
        if logging.getLogger().getEffectiveLevel()==logging.DEBUG:
            logging.exception('Error occured during cpuprof:')

    finally:
        #restart process
        cmd = '/bin/systemctl start cleep'
        subprocess.call(cmd, shell=True)

@core.command()
@click.option('--interval', default=10.0, help='Sampling period (in seconds). Default 10 seconds.')
def memprof(interval):
    """
    Run memory profiler on cleep
    """
    MPROF = '/usr/local/bin/mprof'

    try:
        #memory profiling libraries are not installed because it is long to install.
        #And this process is not the most important to perform due to raspberry pi
        #memory amount
        import memory_profiler
        import psutil
        import matplotlib
    except:
        logger.error(u'Missing libraries. Please run following command: "pip2 install memory-profiler>=0.55.0,psutil>=5.4.6,matplotlib>=2.2.4"')
        sys.exit(1)

    try:
        #stop running process
        cmd = '/bin/systemctl stop cleep'
        subprocess.call(cmd, shell=True)

        logging.info('Memory profiling is running. CTRL-C to stop.')
        cmd = 'cd /tmp; "%(BIN)s" clean; "%(BIN)s" run --interval %(INTERVAL)s --multiprocess "/usr/bin/cleep"' % {'BIN': MPROF, 'INTERVAL': interval}
        logging.debug('Memprof cmd: %s' % cmd)
        subprocess.call(cmd, shell=True)

    except KeyboardInterrupt:
        #generate output graph
        cmd = 'cd /tmp; "%(BIN)s" plot --output "%(OUTPUT)s"' %{'BIN': MPROF, 'OUTPUT': '/tmp/cleep_memprof.png'}
        logging.debug('Memprof cmd: %s' % cmd)
        subprocess.call(cmd, shell=True)

    except:
        if logging.getLogger().getEffectiveLevel()==logging.DEBUG:
            logging.exception('Error occured during memprof:')

    finally:
        #restart process
        cmd = '/bin/systemctl start cleep'
        subprocess.call(cmd, shell=True)

@core.command()
@click.pass_context
def reset(ctx):
    """
    Clear all installed Cleep stuff and reinstall all necessary. All sources must exist (use coreget command).
    """
    confirm = False
    if os.path.exists(config.CORE_DST) or os.path.exists(config.HTML_DST):
        if click.confirm('Existing installed Cleep files (not repo!) will be deleted. Confirm ?'):
            confirm = True
    else:
        confirm = True

    if confirm:
        try:
            if os.path.exists(config.CORE_DST):
                shutil.rmtree(config.CORE_DST)
            if os.path.exists(config.HTML_DST):
                shutil.rmtree(config.HTML_DST)
            ctx.invoke(coresync)
            for module in config.DEFAULT_MODULES:
                ctx.invoke(modsync, module=module)
        except:
            logging.exception('Error occured during init:')

@click.group()
def mod():
    pass

def get_module_name(ctx, param, value):
    module_name = value if value else None
    try:
        current_path = os.getcwd()
        if current_path.startswith(config.MODULES_SRC) and not module_name:
            # we are already in module directory and no module specified
            module_name = current_path.replace(config.MODULES_SRC + '/', '').split('/',1)[0]
    except:
        pass

    if not module_name:
        # prompt for module name
        module_name = click.prompt('Module name')

    return module_name

@mod.command()
@click.option('--module', callback=get_module_name, help='Module name.')
def modsync(module):
    """
    Synchronize core content between source and execution folders
    """
    f = File()
    res = f.module_sync(module)

    if not res:
        sys.exit(1)

@mod.command()
@click.option('--module', prompt='Module name', help='Module name.')
def modcreate(module):
    """
    Create new module skeleton
    """
    m = Module()
    res = m.create(module)

    if not res:
        sys.exit(1)

@mod.command()
@click.option('--module', prompt='Module name', help='Module name.')
def moddelete(module):
    """
    Delete all installed files for specified module
    """
    if click.confirm('All installed files for module "%s" will be deleted. Confirm ?' % module):
        m = Module()
        res = m.delete(module)

        if not res:
            sys.exit(1)

@click.group()
def watchdog():
    pass

@watchdog.command()
@click.option('--quiet', is_flag=True, help='Disable logging.')
@click.option('--loglevel', default=logging.INFO, help='Logging level (10=DEBUG, 20=INFO, 30=WARN, 40=ERROR).')
def watch(quiet, loglevel):
    """
    Start watchdog that monitors filesystem changes on Cleep sources
    """
    if logging.getLogger().getEffectiveLevel()==logging.DEBUG:
        #do not overwrite root logger level if configured to DEBUG (dev mode)
        pass
    elif quiet:
        logging.disable(logging.CRITICAL)
    else:
        logging.getLogger().setLevel(loglevel)
        
    w = CleepWatchdog()
    res = w.watch()

    if not res:
        sys.exit(1)

@click.group()
def test():
    pass

@test.command()
@click.option('--module', prompt='Module name', help='Module name.')
@click.option('--coverage', is_flag=True, help='Display coverage report.')
def modtests(module, coverage):
    """
    Execute module tests
    """
    m = Test()
    res = m.module_test(module, coverage)

    if not res:
        sys.exit(1)

@test.command()
@click.option('--module', prompt='Module name', help='Module name.')
@click.option('--missing', is_flag=True, help='Display missing statements.')
def modtestscov(module, missing):
    """
    Display module tests coverage summary
    """
    m = Test()
    res = m.module_test_coverage(module, missing)

    if not res:
        sys.exit(1)

@click.group()
def distrib():
    pass

@distrib.command()
def corebuild():
    """
    Build Cleep debian package
    """
    d = Distrib()
    res = d.build_cleep()

    if not res:
        sys.exit(1)

@distrib.command()
@click.option('--version', prompt='Version', help='Version to publish.')
def corepublish(version):
    """
    Publish built Cleep debian package
    """
    d = Distrib()
    res = d.publish_cleep(version)

    if not res:
        sys.exit(1)

@click.group()
def docs():
    pass

@docs.command()
@click.option('--module', prompt='Module name', help='Module name.')
@click.option('--preview', is_flag=True, help='Preview generated doc as text.')
def moddocs(module, preview):
    """
    Generate module documentation in appropriate format
    """
    d = Docs()
    res = d.generate_module_docs(module, preview)

    if not res:
        sys.exit(1)

@docs.command()
@click.option('--module', prompt='Module name', help='Module name.')
def moddocspath(module):
    """
    Display generated docs archive path
    """
    d = Docs()
    res = d.get_module_docs_archive_path(module)

    if not res:
        sys.exit(1)

#@click.group()
#def general():
#    pass
#
#@general.command()
#def version():
#    """
#    Display cleep-cli version
#    """
#    click.echo(VERSION)


cli = click.CommandCollection(sources=[core, mod, watchdog, test, distrib, docs])
if __name__ == '__main__':

    def is_raspbian():
        if platform.system()!='Linux':
            return False
        res = subprocess.Popen(u'cat /etc/os-release | grep -i raspbian | wc -l', stdout=subprocess.PIPE, shell=True)
        stdout = res.communicate()[0]
        if stdout.strip()=='0':
            return False
        return True

    #execute only on raspbian
    if is_raspbian():
        cli()
    else:
        click.echo('Cleep-cli runs only on raspbian distribution')

