"""Command line client functionality"""

from __future__ import absolute_import
from nilmdb.printf import *
import nilmdb.client

import datetime_tz
import dateutil.parser
import sys
import re
import argparse
from argparse import ArgumentDefaultsHelpFormatter as def_form

version = "0.1"

# Valid subcommands.  Defined in separate files just to break
# things up -- they're still called with Cmdline as self.
subcommands = [ "info", "create", "list", "metadata", "insert", "extract" ]

# Import the subcommand modules.  Equivalent way of doing this would be
# from . import info as cmd_info
subcmd_mods = {}
for cmd in subcommands:
    subcmd_mods[cmd] = __import__("nilmdb.cmdline." + cmd, fromlist = [ cmd ])

class Cmdline(object):

    def __init__(self, argv):
        self.argv = argv

    def arg_time(self, toparse):
        """Parse a time string argument"""
        try:
            return self.parse_time(toparse).totimestamp()
        except ValueError as e:
            raise argparse.ArgumentTypeError(sprintf("%s \"%s\"",
                                                     str(e), toparse))

    def parse_time(self, toparse):
        """
        Parse a free-form time string and return a datetime_tz object.
        If the string doesn't contain a timestamp, the current local
        timezone is assumed (e.g. from the TZ env var).
        """
        # If string doesn't contain at least 6 digits, consider it
        # invalid.  smartparse might otherwise accept empty strings
        # and strings with just separators.
        if len(re.findall(r"\d", toparse)) < 6:
            raise ValueError("not enough digits for a timestamp")

        # Try to just parse the time as given
        try:
            return datetime_tz.datetime_tz.smartparse(toparse)
        except ValueError:
            pass

        # Try to extract a substring in a condensed format that we expect
        # to see in a filename or header comment
        res = re.search(r"(^|[^\d])("            # non-numeric or SOL
                        r"(199\d|2\d\d\d)"       # year
                        r"[-/]?"                 # separator
                        r"(0[1-9]|1[012])"       # month
                        r"[-/]?"                 # separator
                        r"([012]\d|3[01])"       # day
                        r"[-T ]?"                # separator
                        r"([01]\d|2[0-3])"       # hour
                        r"[:]?"                  # separator
                        r"([0-5]\d)"             # minute
                        r"[:]?"                  # separator
                        r"([0-5]\d)?"            # second
                        r"([-+]\d\d\d\d)?"       # timezone
                        r")", toparse)
        if res is not None:
            try:
                return datetime_tz.datetime_tz.smartparse(res.group(2))
            except ValueError:
                pass

        # Could also try to successively parse substrings, but let's
        # just give up for now.
        raise ValueError("unable to parse timestamp")

    def time_string(self, timestamp):
        """
        Convert a Unix timestamp to a string for printing, using the
        local timezone for display (e.g. from the TZ env var).
        """
        dt = datetime_tz.datetime_tz.fromtimestamp(timestamp)
        return dt.strftime("%a, %d %b %Y %H:%M:%S.%f %z")

    def parser_setup(self):
        version_string = sprintf("nilmtool %s, client library %s",
                                 version, nilmdb.Client.client_version)

        self.parser = argparse.ArgumentParser(add_help = False,
                                              formatter_class = def_form)

        group = self.parser.add_argument_group("General options")
        group.add_argument("-h", "--help", action='help',
                           help='show this help message and exit')
        group.add_argument("-V", "--version", action="version",
                           version=version_string)

        group = self.parser.add_argument_group("Server")
        group.add_argument("-u", "--url", action="store",
                           default="http://localhost:12380/",
                           help="NilmDB server URL (default: %(default)s)")

        sub = self.parser.add_subparsers(title="Commands",
                                         dest="command",
                                         description="Specify --help after "
                                         "the command for command-specific "
                                         "options.")

        # Set up subcommands (defined in separate files)
        for cmd in subcommands:
            subcmd_mods[cmd].setup(self, sub)

    def die(self, formatstr, *args):
        fprintf(sys.stderr, formatstr + "\n", *args)
        self.client.close()
        sys.exit(-1)

    def run(self):
        # Clear cached timezone, so that we can pick up timezone changes
        # while running this from the test suite.
        datetime_tz._localtz = None

        # Run parser
        self.parser_setup()
        self.args = self.parser.parse_args(self.argv)

        self.client = nilmdb.Client(self.args.url)

        # Make a test connection to make sure things work
        try:
            server_version = self.client.version()
        except nilmdb.client.Error as e:
            self.die("Error connecting to server: %s", str(e))

        # Now dispatch client request to appropriate function.  Parser
        # should have ensured that we don't have any unknown commands
        # here.
        retval = self.args.handler(self) or 0

        self.client.close()
        sys.exit(retval)
