#!/usr/bin/env python3
#
# Attach to a task and watch runs as they happen
#

# General syntax:
#     pscheduler attach [options] task-url

import optparse
import os
import pscheduler
import subprocess
import sys
import time


pscheduler.set_graceful_exit()

#
# Gargle the arguments
#

usage = "Usage: %prog [options] task-url"
opt_parser = optparse.OptionParser(usage = usage)
opt_parser.disable_interspersed_args()

# GENERAL OPTIONS

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("--first",
                      help="Start with the first run",
                      action="store_true", default=False,
                      dest="first")

opt_parser.add_option("--format",
                      help="Result format (text, html, json, none)",
                      action="store", type="string",
                      default=None,
                      dest="format")

opt_parser.add_option("--output",
                      help="Write result(s) to a file, substituting a run number for %n",
                      action="store", type="string",
                      dest="output")

opt_parser.add_option("--full",
                      help="Produce full diagnostics and archiving information with result",
                      action="store_true", default=False,
                      dest="full")

opt_parser.add_option("--quiet",
                      help="Operate quietly",
                      action="store_true", default=False,
                      dest="quiet")

opt_parser.add_option("--runs",
                      help="Exit after this many runs (default 0=infinite)",
                      action="store", type="int", default=0,
                      dest="runs")

opt_parser.add_option("--debug", action="store_true", dest="debug")


(options, remaining_args) = opt_parser.parse_args()

if len(remaining_args) < 1:
    opt_parser.print_usage()
    pscheduler.fail()

#
# Validate the command line
#

if options.runs < 0:
    pscheduler.fail("Invalid --runs; must be 0 or more")
max_runs = options.runs

formats = {
    'html': 'text/html',
    'json': 'application/json',
    'none': None,
    'text': 'text/plain',
    # Not "officially" supported, but here for completeness
    'text/html': 'text/html',
    'application/json': 'application/json',
    'text/plain': 'text/plain',
    }

try:
    opt_format = options.format if options.format is not None else "text"
    out_format = formats[opt_format]
except KeyError:
    pscheduler.fail("Invalid --format '%s'; must be text, html, json or none"
                    % (options.format) )



if len(remaining_args) != 1:
    opt_parser.print_usage()
    pscheduler.fail()

[task_url] = remaining_args

if not pscheduler.api_is_task(task_url):
    pscheduler.fail("Invalid task URL.")



verbose = (not options.quiet) and (options.format is None)


log = pscheduler.Log(verbose=verbose, debug=options.debug, quiet=True, propagate=True)


# Get the task with details and find out its class.

log.debug("Fetching %s", task_url)
status, task = pscheduler.url_get(task_url, params={"detail":True},
                                  bind=options.bind, throw=False)

if status != 200:
    pscheduler.fail("Unable to fetch task: " + task)


try:
    multi_result = task["detail"]["multi-result"]
except KeyError:
    pscheduler.fail("Server returned malformed test specification.")


if multi_result:
    print(pscheduler.prefixed_wrap(
        "",  # No prefix
        "This task produces results asynchronously and"
        " cannot be watched.  The results it has produced"
        " since it started running and now can be retrieved"
        " by running the following command:"))
    print()
    print("pscheduler result ", task_url)
    pscheduler.succeed()


run_count = 0

last_url = ""

try:
    first_run_url = task["detail"]["first-run-href"]
    next_run_url = task["detail"]["next-run-href"]
except KeyError:
    pscheduler.fail("Server returned malformed task information.")


while True:

    if run_count == 0 and options.first:
        fetch = first_run_url
    else:
        fetch = next_run_url

    log.debug("Fetching next run from %s", fetch)
    status, run_data = pscheduler.url_get(fetch,
                                          params={ "wait": 0 },
                                          bind=options.bind,
                                          throw=False)
    if status == 404:
        if run_count == 0:
            pscheduler.fail("No runs scheduled for this task.")
        else:
            if options.format is None and not options.output:

                print()
                print("No further runs scheduled.")
            pscheduler.succeed()
    if status != 200:
        pscheduler.fail("Unable to fetch runs: %s" %(run_data))

    run_url = run_data['href']


    if run_url == last_url:
        # This will get us away from a just-finished run because
        # everything is normalized out to one-second boundaries.
        log.debug("Still working in last task's time slot.  Sleeping.")
        time.sleep(1.0)
        continue

    last_url = run_url

    if verbose:
        print()
        print("Next scheduled run:")
        print(run_url)

    try:
        status, run_json = pscheduler.url_get(run_url, bind=options.bind)
    except pscheduler.psurl.URLException as ex:
        pscheduler.fail(str(ex))


    # Wait out non-starters

    if run_json['state'] == 'nonstart':

        if verbose:
            print()
            print("Run scheduled at", run_json['start-time'], "is a non-starter:")
            print(run_json['errors'])

        # If the task wasn't a repeater, don't hang around.
        try:
            repeating = task['schedule']['repeat']
        except KeyError:
            pscheduler.succeed()

        end_time = run_json['end-time']
        wait_time = pscheduler.time_until_seconds(
            pscheduler.iso8601_as_datetime(end_time))
        if verbose:
            print("Waiting until this run would have ended (%s, ~%s seconds)" \
                % (end_time, int(wait_time)))
        time.sleep(wait_time)
        run_count += 1
        continue

    #
    # Wait for the run to start and finish and fetch the results
    #

    start_time = run_json['start-time']
    wait_time = pscheduler.time_until_seconds(
        pscheduler.iso8601_as_datetime(start_time))
    if verbose:
        print("Starts %s (~%s seconds)" % (start_time, int(wait_time)))
    time.sleep(wait_time)

    end_time = run_json['end-time']
    wait_time = pscheduler.time_until_seconds(
        pscheduler.iso8601_as_datetime(end_time))
    if verbose:
        print("Ends   %s (~%s seconds)" % (end_time, int(wait_time)))
    time.sleep(wait_time)

    if verbose:
        print("Waiting for result...")

    status, result = pscheduler.url_get( run_url,
                                         params={ 'wait-merged': True },
                                         bind=options.bind,
                                         throw=False )

    if status == 404:
        pscheduler.succeed("Run not found; task may have been canceled.")
    if status != 200:
        pscheduler.fail("Failed to fetch run: %d: %s" % (status, result))

    if options.format is None \
       and out_format == "text/plain" \
       and not options.output:
        print()

    args = [ "pscheduler", "result", "--quiet", "--format", out_format ]

    # If an output file was specified, use it.
    if options.output is not None:
        output_path = options.output.replace("%n", str(run_count+1))
        log.debug("Sending output to %s", output_path)
        args.append("--output")
        args.append(output_path)

    # Bind if required
    if options.bind is not None:
        args.append("--bind")
        args.append(options.bind)

    if options.full:
        args.append("--full")

    if options.debug:
        args.append("--debug")
    args.append(run_url)
    log.debug("Calling %s", args)
    subprocess.call(args)

    run_count += 1

    if run_count == max_runs:
        log.debug("Last run")
        break



pscheduler.succeed()
