#!/usr/bin/env python3
#
# Show a summary of what's on the schedule
#

# General syntax:
#     pscheduler schedule [options]

import datetime
import optparse
import os
import pscheduler
import pytz
import shlex
import sys
import urllib


pscheduler.set_graceful_exit()


#
# Utilities
#

def get_time_with_delta(string):
    """
    Get an absolute time or delta and return an ISO8601 string with
    the absolute time.
    """

    # If it looks like an ISO time, return that.
    try:
        absolute = pscheduler.iso8601_as_datetime(string)
        # Default behavior is to localize naive times.
        if absolute.tzinfo is None:
            absolute = pytz.utc.localize(absolute)
            return pscheduler.datetime_as_iso8601(absolute)
    except ValueError:
        pass

    try:
        if string[0:1] == "+P":
            delta = pscheduler.iso8601_as_timedelta(string[1:])
        elif string[0:1] == "-P":
            delta = -1 * pscheduler.iso8601_as_timedelta(string[1:])
        else:
            pass
    except ValueError:
        pscheduler.fail("Invalid time delta '%s'" % (string))

    # Let this throw what it's going to throw.
    delta = pscheduler.iso8601_as_timedelta(string)

    return pscheduler.datetime_as_iso8601(
        pscheduler.time_now() + delta)


#
# Gargle the arguments
#

whoami = os.path.basename(sys.argv[0])
args = sys.argv[1:]


# Pre-convert any trailing arguments that look like times or deltas
# into raw times so the option parser doesn't choke on negative
# deltas.

if len(args) > 0:
    arg = -1
    while True and abs(arg) <= len(args):
        try:
            args[arg] = (get_time_with_delta(str(args[arg])))
        except ValueError:
            break
        arg -= 1



class VerbatimParser(optparse.OptionParser):
    def format_epilog(self, formatter):
        return self.epilog


opt_parser = VerbatimParser(
    usage="Usage: %prog [ OPTIONS ] [ delta | start end ]",
    epilog=
"""
Example:

  schedule
      Show the schedule on the local host for the next hour

  schedule --host ps3.example.net
      Show the schedule on ps3.example.net for the next hour

  schedule -PT1H
      Show the schedule on the local host for an hour in the
      the past

  schedule +PT25M
      Show the schedule on the local host for 25 minutes in
      the future

  schedule -PT1H +PT30M
      Show the schedule on the local host for an hour in the
      the past and 30 minutes into the future

  schedule 2016-05-01T12:40:00 2016-05-01T12:55:00
      Show the schedule on the local host between the times
      specified.
"""
    )
opt_parser.disable_interspersed_args()

opt_parser.add_option("--bind",
                      help="Make the request from the provided address",
                      default=None,
                      action="store", type="string",
                      dest="bind")

opt_parser.add_option("--filter-test",
                      help="Include the named type of test (May be repeated for multiple)",
                      default=[],
                      action="append", type="string",
                      dest="filter_test")

opt_parser.add_option("--format",
                      help="Select output format (plain or json)",
                      default="plain",
                      action="store", type="string",
                      dest="out_format")

opt_parser.add_option("--host",
                      help="Request schedule from named host",
                      default=pscheduler.api_local_host(),
                      action="store", type="string",
                      dest="host")

opt_parser.add_option("--invert",
                      help="If filtering, invert the result",
                      default=False,
                      action="store_true",
                      dest="invert")

opt_parser.add_option("--task",
                      help="Show only runs for the specified task URL",
                      default=None,
                      action="store", type="string",
                      dest="task")

opt_parser.add_option("--verbose",
                      help="Add additional information where appropriate",
                      default=False,
                      action="store_true",
                      dest="verbose")


(options, remaining_args) = opt_parser.parse_args(args)




if len(options.filter_test) > 0:
    filter_test = dict( (value, 1) for value in options.filter_test )
else:
    filter_test = None

filter_invert = options.invert

host = options.host

if options.out_format not in [ "plain", "json" ]:
    pscheduler.fail("Format must be plain or json.")

if options.task is not None:

    if not pscheduler.api_is_task(options.task):
        pscheduler.fail("Task does not look like a valid task URL")

    parsed = list(urllib.parse.urlsplit(options.task))
    new_host = parsed[1]
    if host is not None and new_host != host:
        pscheduler.fail("Hosts in --host and --task must be the same.")
    host = new_host
    task_uuid = parsed[2].split("/")[-1]
else:
    task_uuid = None


now = pscheduler.time_now()

if len(remaining_args) == 0:

    # Default; show an hour's worth if no task, otherwise show everything.
    if task_uuid is not None:
        start = None
        end = None
    else:
        start = now
        end = start + datetime.timedelta(hours=1)


elif len(remaining_args) == 1:

    # One argument is an absolute time or a timedelta.

    try:
        arg = pscheduler.iso8601_as_datetime(remaining_args[0])
    except ValueError:
        pscheduler.fail("Invalid time specification")

    if arg < now:
        start = arg
        end = now
    else:
        start = now
        end = arg

elif len(remaining_args) == 2:

    try:
        start = pscheduler.iso8601_as_datetime(remaining_args[0])
        end = pscheduler.iso8601_as_datetime(remaining_args[1])
    except ValueError:
        pscheduler.fail("Invalid time specification")


    if end < start:
        start, end = end, start

else:
    opt_parser.print_usage()
    pscheduler.fail()



#
# Fetch the schedule
#

params={}

if start is not None:
    params["start"] = pscheduler.datetime_as_iso8601(start)

if end is not None:
    params["end"] = pscheduler.datetime_as_iso8601(end)

if task_uuid is not None:
    params["task"] = task_uuid


status, schedule = pscheduler.url_get(
    pscheduler.api_url(host=host, path="schedule"),
    bind=options.bind, params=params, throw=False)

if status != 200:
    pscheduler.fail("Server returned status %d: %s" % (status, schedule))


#
# Dump it out
#

if options.out_format == 'json':
    pscheduler.succeed_json(schedule)

assert options.out_format == 'plain'

if len(schedule) == 0:
    print("Nothing scheduled %s to %s" % (
        pscheduler.datetime_as_iso8601(start),
        pscheduler.datetime_as_iso8601(end)
        ))
    pscheduler.succeed()

first = True

for run in schedule:

    test = run["task"]["test"]["type"]

    if filter_test is not None:
        in_list = test in filter_test
        if (in_list ^ (not filter_invert)):
            continue

        

    task_cli = [ test ]
    task_cli.extend([ shlex.quote(arg) for arg in run["cli"] ])

    if not first:
        print()
        print()
    else:
        first = False


    print("%s - %s (%s)" % (
        run["start-time"],
        run["end-time"],
        run["state-display"]
        ))

    if options.verbose and run["state"] == "nonstart":
        print("Non-Start Reason:")
        error = run.get("errors") or "Unspecified error"
        print(pscheduler.indent(error.strip()))

    priority = run.get("priority", None)
    task_cli.append("(Run with tool '{}'{})".format(
        run["task"]["tool"],
        "" if priority is None else " at priority {}".format(priority))
    )


    if sys.stdout.isatty():
        print(pscheduler.prefixed_wrap(
            "", " ".join(task_cli), indent=2))
    else:
        print(" ".join(task_cli))

    print(run["href"])

    # TODO: If task finished, fetch the result?

pscheduler.succeed()

