#!/usr/bin/env python
#
# vim:ts=4:sw=4:expandtab
#
######################################################################

from __future__ import print_function

import argparse
import logging
import os
import sys
import yaml

from yaml_utilities import version


_description = """
This small utility takes yaml files or directories as input data sources,
and output on stdout the YAML resulting from their merge without replacement.
"""

_epilog = """
Example(s):
    %(script)s -h                      - Provides this help
    %(script)s file.yml  dir/file.yml  - Returns the merged content of the files
    %(script)s file.yml directory      - Same with a directory as input
""" % {
    'script': os.path.basename(sys.argv[0]),
}


def get_value(data, section, parameter, default_value):
    assert type(data) is dict

    value = data
    if section is not None:
        section_path = section.split('.')
        logging.debug('section_path=%s', section_path)
        for s in section_path:
            logging.debug('s=%s', s)
            logging.debug('value=%s', value)
            try:
                value = value[s]
            except KeyError:
                raise KeyError("Invalid section key", s)

    if parameter is not None:
        parameter_path = parameter.split('.')
        logging.debug('parameter_path=%s', parameter_path)
        for p in parameter_path:
            logging.debug('p=%s', p)
            logging.debug('value=%s', value)
            try:
                value = value[p]
            except KeyError:
                if default_value is not None:
                    value = default_value
                else:
                    raise KeyError("Invalid parameter key", p)
    return value


def get_file_list(data_sources, reverse_order=False):
    """
    Turns every data source into a list of files.
    """
    assert len(data_sources) > 0, 'No data source provided!'

    files = []

    for ds in data_sources:
        if os.path.isfile(ds):
            files.append(ds)

        if os.path.isdir(ds):
            directory = ds
            listdir = os.listdir(directory)
            # Files are sorted by alphabetical order
            listdir.sort()
            logging.debug('listdir=%s', listdir)
            for bname in listdir:
                fullpath = os.path.join(directory, bname)
                if os.path.isfile(fullpath):
                    files.append(fullpath)
                elif os.path.isdir(fullpath):
                    # Not used and not supported at this point
                    continue
                    # Recurse
                    tree = get_file_list(fullpath)
                    files.append(tree)
                    # What about hidden directory?
                else:
                    raise Exception('What is that? %s' % fullpath)

    if reverse_order:
        files.reverse()

    return files


def merge_without_replacement(dict_1, dict_2, recursion_depth=0):
    assert type(dict_1) is dict, 'dict_1 not a dictionary'
    assert type(dict_2) is dict, 'dict_2 not a dictionary'
    assert type(recursion_depth) is int, 'depth not int'
    assert recursion_depth >= 0, 'depth is <0!'

    new_dict = dict_1

    depth_indicator1 = '----' * recursion_depth
    depth_indicator2 = '    ' * recursion_depth

    for k, v in dict_2.items():
        if k in new_dict.keys():
            logging.debug('%s Same key found: %s', depth_indicator1, k)
            if ((type(new_dict[k]) is dict) and (type(dict_2) is dict)):
                logging.debug('%s   Getting ready to recurse', depth_indicator2)
                new_dict[k] = merge_without_replacement(
                    new_dict[k], dict_2[k], recursion_depth+1)
            else:
                logging.debug('%s Keeping original value! %s',
                              depth_indicator2, dict_1[k])
        else:
            logging.debug('%s Adding new key/value!: %s %s',
                          depth_indicator1, k, dict_2[k])
            new_dict[k] = dict_2[k]
    return new_dict


def fetch_data(ordered_data_files, with_replacement=False):

    data = dict()

    for filename in ordered_data_files:
        logging.debug('')
        logging.debug('*** Processing file %s ***', filename)
        logging.debug('')
        with open(filename, 'r') as f:
            filedata = yaml.load(f)

        logging.debug('BEFORE MERGE: %s | %s', data, filedata)
        logging.debug('')
        if filedata is None:
            logging.debug('File is empty. Continue with next file!')
            continue
        else:
            try:
                data = merge_without_replacement(data, filedata)
            except AssertionError as e:
                logging.error('filedata is not as expected! %s' % e.message)

        logging.debug('')
        logging.debug('AFTER MERGE: %s', data)

    return data


def main():
    parser = argparse.ArgumentParser(
        description=_description,
        epilog=_epilog,
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument(
        '-d', '--debug',
        action='store_true',
        default=False,
        help='To show debugging outputs',
        required=False,
    )
    parser.add_argument(
        '-O', '--output-file',
        action='store',
        help='output file',
        required=False,
    )
    parser.add_argument(
        '-r', '--reverse-order',
        action='store_true',
        default=False,
        help='To reverse order of input files',
        required=False,
    )
    parser.add_argument(
        '-v', '--version',
        action='store_true',
        default=False,
        help='To return the version of this utility',
        required=False,
    )
    parser.add_argument(
        'data_source',
        help='A YAML file or a directory. Data sources to merge.',
        nargs='*',
    )
    args = parser.parse_args()
    data_sources = args.data_source
    reverse_order = args.reverse_order
    output_file = args.output_file
    data = dict()

    if args.debug > 0:
        logging.basicConfig(level=logging.DEBUG)
        logging.debug('debug=%s', args.debug)

    logging.debug('data_sources=%s', data_sources)
    logging.debug('output_file=%s', output_file)
    logging.debug('reverse_order=%s', reverse_order)

    if args.version is True:
        print(version)
        return

    if len(data_sources) == 0:
        print('Error: No data_source provided!', file=sys.stderr)
        exit(1)

    yaml_files = get_file_list(data_sources, reverse_order)
    logging.debug('yaml_files=%s', yaml_files)

    data = fetch_data(yaml_files)
    logging.debug('data=%s', data)

    kwargs = {
        'allow_unicode': None,
        # canonical=True,               # Includes type
        'default_flow_style': False,
        # 'default_style': '"',            # Quote everything!
        'encoding': 'utf-8',
        'explicit_end': True,              # ...
        'explicit_start': True,            # ---
        'indent': None,
        'line_break': None,
        'width': None,
    }

    dump = yaml.dump(data, **kwargs)

    if output_file is not None:
        with open(output_file, 'w') as f:
            print(dump, end='', file=f)
    else:
        print(dump)


main()
