import os
import sys
import time
import logging
import argparse

from . import db
from .env import ProctorEnv
from .utils import find_subclasses
from . import gen

def get_argument_parser():
    parser = argparse.ArgumentParser()

    parser.add_argument(
        "-v", "--verbose", action="count", default=0, dest="verbosity"
    )

    parser.add_argument(
        "-c", "--create", action="store_true",
        help="Create the environment if it doesn't exist",
    )

    parser.add_argument(
        "--root", nargs=1,
        default=os.environ.get("PROCTOR_ROOT", "~/.akindi-proctor"),
        type=os.path.expanduser,
        help="""
            The environment's root directory. Default: $PROCTOR_ROOT or
            ~/.akindi-proctor.
        """,
    )

    subparsers = parser.add_subparsers()

    for cls in find_subclasses(SubCommand):
        if not cls.__name__.endswith("_command"):
            raise ValueError((
                "Error: class %r is a subclass of SubCommand; its name should "
                "have a suffix of '_command'"
            ) %(cls,))
        cls_parser = subparsers.add_parser(
            cls.__name__.rpartition("_")[0],
            help=cls.help,
        )
        cls_parser.set_defaults(handler=cls)
        cls.parser_add_arguments(cls_parser)

    return parser


def logging_get_handler(handler_name):
    logger = logging.getLogger("")
    for handler in logger.handlers:
        if handler.name == handler_name:
            return logger, handler
    handler = logging.StreamHandler(sys.stderr)
    handler.name = handler_name
    logger.addHandler(handler)
    return logger, handler


def setup_logging(args):
    handler_name = "stderr"
    format = '%(levelname)s [%(name)s]: %(message)s'
    verbosity = args.verbosity
    level = (
        logging.CRITICAL if verbosity < 0 else
        logging.WARNING if verbosity == 0 else
        logging.INFO if verbosity == 1 else
        logging.DEBUG
    )
    logger, handler = logging_get_handler(handler_name)
    formatter = logging.Formatter(format)
    handler.setFormatter(formatter)
    logger.setLevel(level)
    handler.setLevel(level)


def main(argv):
    parser = get_argument_parser()
    args = parser.parse_args(argv[1:])
    setup_logging(args)
    env = ProctorEnv.activate_from_dir(args.root, create=args.create)
    return args.handler(env, args).run() or 0


def entry_point_main():
    sys.exit(main(sys.argv))


class SubCommand(object):
    help = ""

    def __init__(self, env, args):
        self.env = env
        self.args = args

    @classmethod
    def parser_add_arguments(cls, parser):
        pass

    def run(self):
        with db.atomic():
            self.subrun()


class repo_command(SubCommand):
    help = """
        Repository management.
    """

    @classmethod
    def parser_add_arguments(cls, parser):
        subs = parser.add_subparsers()

        impt = subs.add_parser("add")
        impt.set_defaults(func=cls.add)
        impt.add_argument("name")
        impt.add_argument("source")

        lst = subs.add_parser("list")
        lst.set_defaults(func=cls.list)

        refresh = subs.add_parser("refresh")
        refresh.set_defaults(func=cls.refresh)
        refresh.add_argument("name")

        fetch = subs.add_parser("fetch")
        fetch.set_defaults(func=cls.fetch)
        fetch.add_argument("name")

        drop = subs.add_parser("drop")
        drop.set_defaults(func=cls.drop)
        drop.add_argument("name")

    def subrun(self):
        handler = self.args.func
        return handler(self)

    def get_repo(self):
        return self.env.repository_get(self.args.name)

    def list(self):
        for repo in self.env.repository_list():
            print "%s\t%s (%s PDFs)" %(repo.name, repo.root, repo.pdfs.count())

    def add(self):
        self.env.repository_import(self.args.name, self.args.source)

    def refresh(self):
        self.get_repo().refresh()

    def fetch(self):
        self.get_repo().fetch()

    def drop(self):
        self.get_repo().drop()


class reindex_command(SubCommand):
    help = """
        Re-build a dimension index.
    """

    @classmethod
    def parser_add_arguments(cls, parser):
        parser.add_argument("--clean", action="store_true")
        parser.add_argument("dimension", nargs="*", help="""
            Available dimensions: %s
        """ %(", ".join(dim.name for dim in gen.Dimensions.all_dimensions)))

    def subrun(self):
        dimensions = self.args.dimension or [
            dim.name for dim in gen.Dimensions.all_dimensions
        ]
        gen.Dimensions.reindex(self.env, dimensions, clean=self.args.clean)


class shell_command(SubCommand):
    def subrun(self):
        import IPython
        IPython.embed()


class gc_command(SubCommand):
    def subrun(self):
        blob_root = os.path.abspath(os.path.join(self.env.root, "blobs/"))
        unrefd_hashes = set(os.listdir(blob_root))
        for root, _, files in os.walk(os.path.join(self.env.root, "repos/")):
            for f in files:
                try:
                    target = os.readlink(os.path.join(root, f))
                except OSError:
                    continue
                target = os.path.abspath(target)
                if not target.startswith(blob_root):
                    raise Exception("hu? %s" %(target, ))
                unrefd_hashes.remove(os.path.basename(target))
        for h in unrefd_hashes:
            os.unlink(os.path.join(blob_root, h))
        print "Removed %s unreferenced blobs" %(len(unrefd_hashes), )


class gen_command(SubCommand):
    help = """
        Generate test cases
    """

    @classmethod
    def parser_add_arguments(cls, parser):
        parser.add_argument("--seed", default=int(time.time()), type=int)
        parser.add_argument("-f", "--filter", nargs="*", default=[], help="""
            Filters to apply. Currently supported: repo=REPO.
        """)
        parser.add_argument("-s", "--stratify", default=["pdf"], help="""
            Dimensions to stratify across.
        """)
        parser.add_argument("-n", "--number", default=0, type=int, help="""
            Number of pages to produce.
        """)

    def subrun(self):
        generator = gen.Generator.from_args(
            self.env,
            self.args.seed,
            self.args.filter,
            self.args.stratify,
            self.args.number,
        )
        generator.get_test_case()


class init_command(SubCommand):
    help = """
        Initialize the proctor environment.
    """

    def subrun(self):
        if not self.args.create:
            print "Error: run with -c:"
            print "  %s -c init" %(sys.argv[0], )
            return 1
        print "(re)initialized %s" %(self.env.root, )
