#!/usr/bin/env python2

# THIS FILE IS PART OF THE CYLC SUITE ENGINE.
# Copyright (C) 2008-2019 NIWA & British Crown (Met Office) & Contributors.
#
# 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] diff|compare [OPTIONS] SUITE1 SUITE2

Compare two suite definitions and display any differences.

Differencing is done after parsing the suite.rc files so it takes
account of default values that are not explicitly defined, it disregards
the order of configuration items, and it sees any include-file content
after inlining has occurred.

Files in the suite bin directory and other sub-directories of the
suite definition directory are not currently differenced."""

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

import cylc.flags
from cylc.option_parsers import CylcOptionParser as COP
from cylc.config import SuiteConfig
from cylc.suite_srv_files_mgr import SuiteSrvFilesManager
from cylc.templatevars import load_template_vars

n_oone = 0
n_otwo = 0
n_diff = 0


def diffdict(one, two, oone, otwo, diff):
    global n_oone, n_otwo, n_diff
    # Recursively difference two dictionaries in which any element
    # may be another dictionary, keeping items that appear only
    # in one or the other, and items that appear in both but differ.
    for key in one:
        if key not in two:
            oone[key] = one[key]
            n_oone += 1
        elif one[key] != two[key]:
            if isinstance(one[key], dict):
                for item in oone, otwo, diff:
                    if key not in item:
                        item[key] = {}
                diffdict(one[key], two[key], oone[key], otwo[key], diff[key])
            else:
                diff[key] = (one[key], two[key])
                n_diff += 1

    for key in two:
        if key not in one:
            otwo[key] = two[key]
            n_otwo += 1


def prdict(dct, arrow='<', section='', level=0, diff=False, nested=False):
    """Recursively print, in pseudo 'diff' format, the contents of
    one of the three dictionaries populated by the diffdict() function
    above (any element may itself be a dictionary).
    """

    if section != '':
        prfx = section + ' '
    else:
        prfx = ''

    if section == '':
        sctn = '(top)'
    else:
        sctn = section

    foo = False

    for key in dct:
        if isinstance(dct[key], dict):
            lvl = level + 1
            if nested:
                pre = prfx + '\n' + '   ' * lvl
            else:
                pre = prfx
            prdict(dct[key], arrow,
                   pre + '[' * lvl + str(key) + ']' * lvl, lvl,
                   diff, nested)
        else:
            if not foo:
                if nested:
                    print '  ', sctn
                else:
                    print '\n  ', sctn
                foo = True

            if diff:
                print ' <  ', key, '=', dct[key][0]
                print ' >  ', key, '=', dct[key][1]
            else:
                print ' ' + arrow + '  ', key, '=', dct[key]


def main():
    parser = COP(
        __doc__, jset=True, prep=True, icp=True,
        argdoc=[('SUITE1', 'Suite name or path'),
                ('SUITE2', 'Suite name or path')])

    parser.add_option(
        "-n", "--nested",
        help="print suite.rc section headings in nested form.",
        action="store_true", default=False, dest="nested")

    (options, args) = parser.parse_args()

    srv_files_manager = SuiteSrvFilesManager()
    suite1, suite1rc = srv_files_manager.parse_suite_arg(options, args[0])
    suite2, suite2rc = srv_files_manager.parse_suite_arg(options, args[1])
    if suite1 == suite2:
        parser.error("You can't diff a single suite.")
    print "Parsing %s (%s)" % (suite1, suite1rc)
    template_vars = load_template_vars(
        options.templatevars, options.templatevars_file)
    config1 = SuiteConfig(suite1, suite1rc, options, template_vars).cfg
    print "Parsing %s (%s)" % (suite2, suite2rc)
    config2 = SuiteConfig(
        suite2, suite2rc, options, template_vars, is_reload=True).cfg

    if config1 == config2:
        print "Suite definitions %s and %s are identical" % (suite1, suite2)
        sys.exit(0)

    print "Suite definitions %s and %s differ" % (suite1, suite2)

    suite1_only = {}
    suite2_only = {}
    diff_1_2 = {}

    diffdict(config1, config2, suite1_only, suite2_only, diff_1_2)

    if n_oone > 0:
        print
        msg = str(n_oone) + ' items only in ' + suite1 + ' (<)'
        print msg
        prdict(suite1_only, '<', nested=options.nested)

    if n_otwo > 0:
        print
        msg = str(n_otwo) + ' items only in ' + suite2 + ' (>)'
        print msg
        prdict(suite2_only, '>', nested=options.nested)

    if n_diff > 0:
        print
        msg = (str(n_diff) + ' common items differ ' +
               suite1 + '(<) ' + suite2 + '(>)')
        print msg
        prdict(diff_1_2, '', diff=True, nested=options.nested)


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