#!/usr/bin/env python

# THIS FILE IS PART OF THE CYLC SUITE ENGINE.
# Copyright (C) 2008-2016 NIWA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""cylc [task] submit|single [OPTIONS] ARGS

Submit a single task to run just as it would be submitted by its suite.  Task
messaging commands will print to stdout but will not attempt to communicate
with the suite (which does not need to be running).

For tasks present in the suite graph the given cycle point is adjusted up to
the next valid cycle point for the task. For tasks defined under runtime but
not present in the graph, the given cycle point is assumed to be valid.

WARNING: do not 'cylc submit' a task that is running in its suite at the
same time - both instances will attempt to write to the same job logs."""

import sys
from cylc.remote import remrun
if remrun().execute():
    sys.exit(0)

import os
import subprocess
from time import sleep

import cylc.flags
from parsec.OrderedDict import OrderedDict
from cylc.execute import execute
from cylc.job_file import JobFile
from cylc.config import SuiteConfig
from cylc.option_parsers import CylcOptionParser as COP
from cylc.registration import RegistrationDB
from cylc.task_id import TaskID
from cylc.get_task_proxy import get_task_proxy
from cylc.cycling.loader import get_point
from cylc.job_host import RemoteJobHostManager
from cylc.suite_host import get_suite_host, get_hostname
from cylc.regpath import RegPath
from cylc.suite_logging import SuiteLog
from cylc.cfgspec.globalcfg import GLOBAL_CFG
from cylc.mp_pool import SuiteProcPool
import cylc.version  # Ensures '$CYLC_VERSION' is set.
from cylc.task_state import TASK_STATUS_SUBMIT_FAILED
from cylc.templatevars import load_template_vars


def commandline_parser():
    parser = COP(
        __doc__, jset=True,
        argdoc=[("REG", "Suite name"),
                ("TASK", "Target task (" + TaskID.SYNTAX + ")")])

    parser.set_defaults(sched=False, dry_run=False)

    parser.add_option(
        "-d", "--dry-run",
        help="Generate the job script for the task, but don't submit it.",
        action="store_true", dest="dry_run")

    return parser


def main():
    parser = commandline_parser()
    (options, args) = parser.parse_args()
    if options.debug:
        cylc.flags.debug = True
    suite = args[0]
    suiterc = RegistrationDB(options.db).get_suiterc(suite)

    owner = options.owner
    host = options.host

    suite_env = {}
    suite_task_env = {}

    task_id = args[1]

    suite_dir = os.path.dirname(suiterc)
    # For user-defined job submission methods:
    sys.path.append(os.path.join(suite_dir, 'python'))

    # check task
    if not TaskID.is_valid_id(task_id):
        sys.exit("Invalid task ID " + task_id)

    task_name, point_string = TaskID.split(task_id)

    # load suite config
    config = SuiteConfig.get_inst(
        suite, suiterc,
        load_template_vars(options.templatevars, options.templatevars_file))

    # No TASK EVENT HOOKS are set for the submit command because there is
    # no scheduler instance watching for task failure etc.

    # Running in UTC time? (else just use the system clock)
    utc = config.cfg['cylc']['UTC mode']

    # create log (after CYLC_MODE is exported)
    os.environ['CYLC_MODE'] = 'submit'

    GLOBAL_CFG.create_cylc_run_tree(suite)
    suite_log_dir = SuiteLog.get_dir_for_suite(suite)

    RemoteJobHostManager.get_inst().single_task_mode = True

    ict = config.cfg['scheduling']['initial cycle point']
    fct = config.cfg['scheduling']['final cycle point']

    # static cylc and suite-specific variables:
    suite_env = {
        'CYLC_UTC': str(utc),
        'CYLC_MODE': 'submit',
        'CYLC_DEBUG': str(cylc.flags.debug),
        'CYLC_VERBOSE': str(cylc.flags.verbose),
        'CYLC_DIR_ON_SUITE_HOST': os.environ['CYLC_DIR'],
        'CYLC_SUITE_NAME': suite,
        'CYLC_SUITE_REG_NAME': suite,  # DEPRECATED
        'CYLC_SUITE_HOST': str(get_suite_host()),
        'CYLC_SUITE_OWNER': owner,
        'CYLC_SUITE_PORT': 'None',
        'CYLC_SUITE_REG_PATH': RegPath(suite).get_fpath(),  # DEPRECATED
        'CYLC_SUITE_DEF_PATH_ON_SUITE_HOST': suite_dir,
        'CYLC_SUITE_INITIAL_CYCLE_POINT': str(ict),  # may be "None"
        'CYLC_SUITE_FINAL_CYCLE_POINT': str(fct),  # may be "None"
        'CYLC_SUITE_INITIAL_CYCLE_TIME': str(ict),  # may be "None"
        'CYLC_SUITE_FINAL_CYCLE_TIME': str(fct),  # may be "None"
        'CYLC_SUITE_LOG_DIR': suite_log_dir  # needed by the test battery
    }

    # Note: a suite contact env file is not written by this command (it
    # would overwrite the real one if the suite is running).

    # Set local values of variables that are potenitally task-specific
    # due to different directory paths on different task hosts. These
    # are overridden by tasks prior to job submission, but in
    # principle they could be needed locally by event handlers:
    suite_task_env = {
        'CYLC_SUITE_RUN_DIR': GLOBAL_CFG.get_derived_host_item(
            suite, 'suite run directory'),
        'CYLC_SUITE_WORK_DIR': GLOBAL_CFG.get_derived_host_item(
            suite, 'suite work directory'),
        'CYLC_SUITE_SHARE_DIR': GLOBAL_CFG.get_derived_host_item(
            suite, 'suite share directory'),
        'CYLC_SUITE_SHARE_PATH': '$CYLC_SUITE_SHARE_DIR',  # DEPRECATED
        'CYLC_SUITE_DEF_PATH': suite_dir
    }
    # (GLOBAL_CFG automatically expands environment variables in local paths)

    JobFile.get_inst().set_suite_env(suite_env)

    point = get_point(point_string).standardise()
    # Try to get a graphed task of the given name.
    itask = get_task_proxy(task_name, point.standardise(), is_startup=True)

    if not options.dry_run:
        proc_pool = SuiteProcPool.get_inst(pool_size=1)

    if options.dry_run:
        itask.prep_submit(dry_run=True)
        print "JOB SCRIPT=%s" % itask.get_job_log_path(
            itask.HEAD_MODE_LOCAL, tail=itask.JOB_FILE_BASE)
    else:
        if itask.prep_submit() is None:
            sys.exit(1)
        itask.submit()
        while proc_pool.results:
            proc_pool.handle_results_async()
        proc_pool.close()
        proc_pool.join()
        if itask.summary['submit_method_id'] is not None:
            print 'Job ID:', itask.summary['submit_method_id']

    sys.exit(itask.state.status == TASK_STATUS_SUBMIT_FAILED)


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        if cylc.flags.debug:
            raise
        sys.exit(str(exc))
