#!/usr/bin/env python
# -*- coding: utf-8 -*-

# THIS FILE IS PART OF THE CYLC SUITE ENGINE.
# Copyright (C) 2008-2017 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 [util] cycle-point [OPTIONS] [POINT]

Cycle point date-time offset computation, and filename templating.

Filename templating replaces elements of a template string with corresponding
elements of the current or given cycle point.

Use ISO 8601 or posix date-time format elements:
  % cylc cyclepoint 2010080T00 --template foo-CCYY-MM-DD-Thh.nc
  foo-2010-08-08-T00.nc
  % cylc cyclepoint 2010080T00 --template foo-%Y-%m-%d-Thh.nc
  foo-2010-08-08-T00.nc

Other examples:

1) print offset from an explicit cycle point:
  % cylc [util] cycle-point --offset-hours=6 20100823T1800Z
  20100824T0000Z

2) print offset from $CYLC_TASK_CYCLE_POINT (as in suite tasks):
  % export CYLC_TASK_CYCLE_POINT=20100823T1800Z
  % cylc cycle-point --offset-hours=-6
  20100823T1200Z

3) cycle point filename templating, explicit template:
  % export CYLC_TASK_CYCLE_POINT=2010-08
  % cylc cycle-point --offset-years=2 --template=foo-CCYY-MM.nc
  foo-2012-08.nc

4) cycle point filename templating, template in a variable:
  % export CYLC_TASK_CYCLE_POINT=2010-08
  % export MYTEMPLATE=foo-CCYY-MM.nc
  % cylc cycle-point --offset-years=2 --template=MYTEMPLATE
  foo-2012-08.nc

Arguments:
   [POINT]  ISO 8601 date-time, e.g. 20140201T0000Z, default
      $CYLC_TASK_CYCLE_POINT"""

import os
import sys
import re
from optparse import OptionParser

import cylc.flags
import cylc.cycling.iso8601
import isodatetime.data
import isodatetime.dumpers
import isodatetime.parsers


def main():
    parser = OptionParser(__doc__)

    parser.add_option(
        "--offset-hours", metavar="HOURS",
        help="Add N hours to CYCLE (may be negative)",
        action="store", dest="offsethours")

    parser.add_option(
        "--offset-days", metavar="DAYS",
        help="Add N days to CYCLE (N may be negative)",
        action="store", dest="offsetdays")

    parser.add_option(
        "--offset-months", metavar="MONTHS",
        help="Add N months to CYCLE (N may be negative)",
        action="store", dest="offsetmonths")

    parser.add_option(
        "--offset-years", metavar="YEARS",
        help="Add N years to CYCLE (N may be negative)",
        action="store", dest="offsetyears")

    parser.add_option(
        "--offset", metavar="ISO_OFFSET",
        help="Add an ISO 8601-based interval representation to CYCLE",
        action="store", dest="offset")

    parser.add_option(
        "--equal", metavar="POINT2",
        help="Succeed if POINT2 is equal to POINT (format agnostic).",
        action="store", dest="point2")

    parser.add_option(
        "--template", metavar="TEMPLATE",
        help="Filename template string or variable",
        action="store", dest="template")

    parser.add_option(
        "--time-zone", metavar="TEMPLATE",
        help="Control the formatting of the result's timezone e.g. "
             "(Z, +13:00, -hh",
        action="store", default=None, dest="time_zone")

    parser.add_option(
        "--num-expanded-year-digits", metavar="NUMBER",
        help="Specify a number of expanded year digits to print in the result",
        action="store", default=0, dest="num_expanded_year_digits")

    parser.add_option(
        "--print-year", help="Print only CCYY of result",
        action="store_true", default=False, dest="print_year")

    parser.add_option(
        "--print-month", help="Print only MM of result",
        action="store_true", default=False, dest="print_month")

    parser.add_option(
        "--print-day", help="Print only DD of result",
        action="store_true", default=False, dest="print_day")

    parser.add_option(
        "--print-hour", help="Print only hh of result",
        action="store_true", default=False, dest="print_hour")

    (options, args) = parser.parse_args()

    if len(args) == 0:
        # input cycle point must be defined in the environment.
        if 'CYLC_TASK_CYCLE_POINT' not in os.environ:
            parser.error("Provide CYCLE arg, or define $CYLC_TASK_CYCLE_POINT")
        cycle_point_string = os.environ['CYLC_TASK_CYCLE_POINT']

    elif len(args) == 1:
        # must be cycle point
        cycle_point_string = args[0]

    else:
        parser.error("Wrong number of arguments!")

    # template string
    template = None
    if options.template:
        if (options.print_month or options.print_year or options.print_day or
                options.print_hour):
            parser.error(
                '"print only" options are incompatible with templating')
        tmp = options.template
        if tmp in os.environ:
            # name of a variable that contains a template
            template = os.environ[tmp]
        else:
            # or a raw template string
            template = tmp
    else:
        n_chosen = 0

        if options.print_year:
            n_chosen += 1
            if options.num_expanded_year_digits:
                template = u"±XCCYY"
            else:
                template = "CCYY"

        if options.print_month:
            n_chosen += 1
            template = "MM"

        if options.print_day:
            n_chosen += 1
            template = "DD"

        if options.print_hour:
            n_chosen += 1
            template = "%H"

        if n_chosen != 0 and n_chosen != 1:
            parser.error("Choose NONE or ONE of print_(year|month|day|hour)")

    cylc.cycling.iso8601.init(
        num_expanded_year_digits=options.num_expanded_year_digits,
        time_zone=options.time_zone
    )
    iso_point_parser = isodatetime.parsers.TimePointParser(
        num_expanded_year_digits=options.num_expanded_year_digits
    )
    iso_point_dumper = isodatetime.dumpers.TimePointDumper(
        num_expanded_year_digits=options.num_expanded_year_digits
    )
    try:
        cycle_point = iso_point_parser.parse(
            cycle_point_string, dump_as_parsed=(template is None))
    except ValueError as exc:
        parser.error('ERROR: invalid cycle: %s' % exc)

    if options.point2:
        try:
            cycle_point2 = iso_point_parser.parse(
                options.point2, dump_as_parsed=(template is None))
        except ValueError as exc:
            parser.error('ERROR: invalid cycle: %s' % exc)
        if cycle_point2 == cycle_point:
            sys.exit(0)
        else:
            sys.exit(1)

    offset_props = {}

    if options.offsethours:
        try:
            offset_props["hours"] = int(options.offsethours)
        except ValueError:
            parser.error('ERROR: offset must be integer')

    if options.offsetdays:
        try:
            offset_props["days"] = int(options.offsetdays)
        except ValueError:
            parser.error('ERROR: offset must be integer')

    if options.offsetmonths:
        try:
            offset_props["months"] = int(options.offsetmonths)
        except ValueError:
            parser.error('ERROR: offset must be integer')

    if options.offsetyears:
        try:
            offset_props["years"] = int(options.offsetyears)
        except ValueError:
            parser.error('ERROR: offset must be integer')

    offset = isodatetime.data.Duration(**offset_props)

    if options.offset:
        opt_offset = options.offset
        sign_factor = 1
        if options.offset.startswith("-"):
            opt_offset = options.offset[1:]
            sign_factor = -1
        try:
            offset += isodatetime.parsers.DurationParser().parse(
                opt_offset) * sign_factor
        except ValueError as exc:
            parser.error('ERROR: offset not valid: %s' % exc)
    cycle_point += offset
    if template is None:
        print cycle_point
    else:
        dump_string = iso_point_dumper.dump(cycle_point, template)
        if (dump_string == template and
                not any(_ in template for _ in ["CCYY", "MM", "DD", "%"])):
            # A pure time string, no date - not well handled in isodatetime.
            print iso_point_dumper.dump(cycle_point, "T" + template)[1:]
        else:
            print dump_string


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