#!/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 [prep] validate [OPTIONS] ARGS

Validate a suite definition against the official specification
files held in $CYLC_DIR/conf/suiterc/.

If the suite definition uses include-files reported line numbers
will correspond to the inlined version seen by the parser; use
'cylc view -i,--inline SUITE' for comparison."""

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

import cylc.flags
from cylc.option_parsers import CylcOptionParser as COP
from cylc.version import CYLC_VERSION
from cylc.config import SuiteConfig, SuiteConfigError
from cylc.prerequisite import TriggerExpressionError
from cylc.task_proxy import TaskProxy
from cylc.templatevars import load_template_vars
from cylc.profiler import Profiler


def main():

    parser = COP(__doc__, jset=True, prep=True)

    parser.add_option(
        "--ict",
        help="Set an initial cycle time to validate against. This "
             "may be required if the suite does not supply one.")

    parser.add_option(
        "--strict",
        help="Fail any use of unsafe or experimental features. "
             "Currently this just means naked dummy tasks (tasks with no "
             "corresponding runtime section) as these may result from "
             "unintentional typographic errors in task names.",
        action="store_true", default=False, dest="strict")

    parser.add_option(
        "--no-write",
        help="Don't write out the processed suite definition.",
        action="store_true", default=False, dest="nowrite")

    parser.add_option(
        "--profile", help="Output profiling (performance) information",
        action="store_true", default=False, dest="profile_mode")

    (options, args) = parser.parse_args()

    profiler = Profiler(options.profile_mode)
    profiler.start()

    suite, suiterc = parser.get_suite()

    cfg = SuiteConfig.get_inst(
        suite, suiterc,
        load_template_vars(options.templatevars, options.templatevars_file),
        cli_initial_point_string=options.ict,
        validation=True, strict=options.strict,
        write_proc=not options.nowrite,
        mem_log_func=profiler.log_memory)

    # Instantiate tasks and force evaluation of trigger expressions.
    # (Taken from config.py to avoid circular import problems.)
    # TODO - This is not exhaustive, it only uses the initial cycle point.
    if cylc.flags.verbose:
        print "Instantiating tasks to check trigger expressions"
    for name in cfg.taskdefs.keys():
        try:
            itask = TaskProxy(
                cfg.taskdefs[name],
                cfg.start_point,
                is_startup=True,
                validate_mode=True)
        except Exception as exc:
            print >> sys.stderr, str(exc)
            raise SuiteConfigError(
                'ERROR, failed to instantiate task %s' % name)
        if itask.point is None:
            if cylc.flags.verbose:
                print >> sys.stderr, (
                    " + Task out of bounds for " + str(cfg.start_point) +
                    ": " + itask.name)
            continue

        # Warn for purely-implicit-cycling tasks (these are deprecated).
        if itask.tdef.sequences == itask.tdef.implicit_sequences:
            print >> sys.stderr, (
                "WARNING, " + name + ": not explicitly defined in " +
                "dependency graphs (deprecated)"
            )

        # force trigger evaluation now
        try:
            itask.state.prerequisites_eval_all()
        except TriggerExpressionError as exc:
            print >> sys.stderr, str(exc)
            raise SuiteConfigError(
                "ERROR, " + name + ": invalid trigger expression.")
        except Exception as exc:
            print >> sys.stderr, str(exc)
            raise SuiteConfigError(
                'ERROR, ' + name + ': failed to evaluate triggers.')
        if cylc.flags.verbose:
            print "  + " + itask.identity + " ok"

    print "Valid for cylc-" + CYLC_VERSION
    profiler.stop()


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