#!/usr/bin/env python
import subprocess
import argparse
import pprint
import glob
import json
import os

from pych.version import APP_NAME, APP_VERSION
from pych.compiler import moduralize
from pych import CONFIG
from pych.utils import info, warn, error

#
# Helper decorators
#

def needs_config(func):
    """Checks if configuration is available, exist with error when its not."""

    def inner(*args, **kwargs):
        if not CONFIG:
            error("Cannot find configuration file (pych.json).")
            exit(1)

        return func(*args, **kwargs)

    return inner
#
# pych commands
#

@needs_config
def check(arg):
    """Sanity checks the configuration."""

    info("Checking installation...")
    info(" * Templates")
    for slang in CONFIG["specializers"]["templates"]:
        for template_dir in CONFIG["specializers"]["templates"][slang]:
            if not os.path.exists(template_dir):
                error(
                    "Missing template dir(%s) for slang(%s)" % (
                    template_dir,
                    slang
                ))

    info(" * Object Storage")
    try:
        for slang in CONFIG["object_store"]["output_paths"]:
            o_path = CONFIG["object_store"]["output_paths"][slang]
            path = os.sep.join([
                o_path,
                'write_test'
            ])
            with open(path, 'w') as fd:
                fd.write("test")
    except Exception as e:
        error("Error trying to write to object-store: %s" % e)

    # Check that Chapel libraries are there
    info(" * Libraries")

    # Check that c-headers are there
    info(" * Headers")

    # Check that the commands are invokable
    info(" * Commands")

    errors = []
    warnings = []
    required = ["g++", "chpl", "gcc", "py.test"]
    recommended = ["py.test"]
    for executable in required+recommended:
        try:
            process = subprocess.Popen(
                [executable, "--version"],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            out, err = process.communicate()
        except OSError as exc:
            if executable in required:
                errors.append((exc, "stacktrace here"))
            elif executable in recommended:
                warnings.append((exc, "stacktrace here"))

def version(arg):
    """Print the pych version."""

    info("%s %s" % (APP_NAME, APP_VERSION))

@needs_config
def compile(sfile):
    """Compile a Chapel module into a Python module."""

    info("Compile this %s" % sfile)
    (source_file, output_file, wrap_fp) = moduralize(sfile)
    info("Compiled sfile(%s) into output_file(%s)"
         " with wrapped-source(%s)" % (
        source_file, output_file, wrap_fp
    ))

@needs_config
def bfiles(arg):
    """pprint bfiles."""

    info("** bfiles **")
    CONFIG.pprint_specializers(['bfiles'])

@needs_config
def sfiles(arg):
    """pprint sfiles."""

    info("** sfiles **")
    CONFIG.pprint_specializers(['sfiles'])

@needs_config
def templates(arg):
    """pprint templates."""
    
    info("** Templates **")
    CONFIG.pprint_specializers(['templates'])

@needs_config
def object_store(arg):
    """pprint objects."""

    info("** Objects **")
    CONFIG.pprint_objects()

@needs_config
def testing(arg):

    for path in CONFIG._config["testing"]["paths"]:
        info("Running tests in %s" % path)
        process = subprocess.Popen(
            ["py.test", path]
        )
        out, err = process.communicate()

if __name__ == "__main__":
    
    parser = argparse.ArgumentParser(   # Setup cli-argument parser
        description='Tool aiding the pych module'
    )

    exclusive = parser.add_mutually_exclusive_group()
    exclusive.add_argument(
        '-c', '--compile',
        metavar="source_file", type=str,
        help='Compile the given Chapel module into a Python module.'
    )
    exclusive.add_argument(
        '-k', '--check',
        action="store_const", const=True, default=False,
        help="Check the 'pych' installation, configuration, and environment."
    )
    exclusive.add_argument(
        '-z', '--testing',
        action="store_const", const=True, default=False,
        help="Run 'pych' testing."
    )
    exclusive.add_argument(
        '-s', '--sfiles',
        action="store_const", const=True, default=False,
        help="Show information about source-files (sfiles)."
    )
    exclusive.add_argument(
        '-b', '--bfiles',
        action="store_const", const=True, default=False,
        help='Show information about function-body files (bfiles).'
    )
    exclusive.add_argument(
        '-t', '--templates',
        action="store_const", const=True, default=False,
        help='Show information about templates.'
    )
    exclusive.add_argument(
        '-a', '--object-store',
        action="store_const", const=True, default=False,
        help='Show information about the object-store (.so files).'
    )
    exclusive.add_argument(
        '-v', '--version',
        action="store_const", const=True, default=False,
        help="Print version"
    )

    args = parser.parse_args()

    cmd = cmd_args = cmd_exec = None    # Grab a command, the "--long-name" of
    for arg in args.__dict__:           # the arg maps to the function-name
        cmd_args = args.__dict__[arg]
        if cmd_args:
            cmd = arg
            cmd_exec = locals()[arg]

    if cmd_exec:                        # Invoke it
        cmd_exec(cmd_args)
    else:                               # Print help when no commands provided
        parser.print_usage()
        error("No command provided, see help(-h, --help) for commands.")
