#!/usr/bin/env python
import argparse
from re import subn
from  axiom.validation.validator import Validator
import axiom.utilities as au
from axiom.report import generate_report
import sys
import os
from tabulate import tabulate
import axiom.drs.cli as adc
import axiom.qa.cli as aqc
import axiom.drs as ad


def parse_and_dispatch(parser):
    """Parse arguments for the script and dispatch to the correct function.

    Args:
        argparse.ArgumentParser : Parser object.
    """
    # Parse and convert to dictionary
    args = vars(parser.parse_args())

    if 'func' in args.keys():
        func = args.pop('func')
        func(**args)
    else:
        parser.print_help()


def _validate(schema_filepath, input_filepath, report_filepath=None, **kwargs):

    # Create a validator
    v = Validator(schema=schema_filepath)

    # Work out what data type we're using
    filename, ext = os.path.splitext(input_filepath)

    # Load the metadata
    if ext == '.nc':
        metadata = au.extract_metadata(input_filepath)
    elif ext == '.json':
        metadata = au.load_metadata_json(input_filepath)
    else:
        raise TypeError('Axiom only supports reading metadata from NetCDF or JSON files.')

    # Validate
    v.validate(metadata)

    # Generate a report
    if report_filepath:
        generate_report(v, input_filepath, report_filepath)

    # Return
    exit_code = 0 if v.is_valid else 1
    sys.exit(exit_code)


def _convert_cf(table_filepath, output_filepath, **kwargs):
    """Convert the CF conventions standard name table XML to Axiom schema.

    Args:
        table_filepath (str): Path.
        output_filepath (str): Path.
    """
    # Load
    schema = au.load_cf_standard_name_table(table_filepath)

    # Save
    au.save_schema(schema, output_filepath)


def _convert_cordex(csv_filepath, output_filepath, **kwargs):
    """Convert a CORDEX attributes CSV to Axiom schema.

    Args:
        csv_filepath (str): Path.
        output_filepath (str): Path.
    """
    # Load
    schema = au.load_cordex_csv(csv_filepath)

    # Save
    au.save_schema(schema, output_filepath)


def _diff(a, b, ignore, **kwargs):
    """Perform a metadata diff between Files a and b.

    Args:
        a (str): Path to file a.
        b (str): Path to file b.
        ignore (str) : Directive to ignore
    """

    # Load the metadata, difference it.
    diff = au.diff_metadata(
        au.extract_metadata(a),
        au.extract_metadata(b)
    )

    # Set up a list to ignore from checks
    _ignore = dict()

    if ignore is not None:
        for directive in ignore.split(','):
            var, att = directive.split(':')
            if var not in _ignore.keys():
                _ignore[var] = list()
            _ignore[var].append(att)

    # Assume the files have the same metadata until they fail
    different = False

    # Start building a table
    lines = list()
    header = ['variable', 'attribute', 'a', 'b']
    lines.append(header)

    # Check global
    for key, value in diff['_global'].items():

        # Raise a failure if this is not to be ignored
        if '_global' not in _ignore.keys() or key not in _ignore['_global']:
            different = True

        lines.append(['_global', key, value[0], value[1]])


    # Check variables
    for variable in diff['variables'].keys():
        for key, value in diff['variables'][variable].items():

            if variable not in _ignore.keys() or key not in _ignore[variable]:
                different = True

            lines.append([variable, key, value[0], value[1]])

    # Simple dump to screen
    print(f'a = {a}')
    print(f'b = {b}')

    if different:
        table = tabulate(lines)
        print(table)
        sys.exit(1)

    else:
        print('Files have the same metadata, except for that which is ignored.')
        sys.exit(0)


def _drs(**kwargs):
    """DRS subsystem.
    
    Args:
        **kwargs : Arguments required for DRS.
    """
    ad.process(**kwargs)

def _drs_consume(**kwargs):
    """Consume JSON files for DRS subsystem.
    
    Args:
        **kwargs : Arguments.
    """
    logger = au.get_logger(__name__)
    for json_filepath in kwargs['input_filepaths']:
        logger.info(f'Consuming {json_filepath}')
        ad.consume(json_filepath)


def get_parser():
    """Get a parser object.

    Returns:
        argparse.ArgumentParser() : Parser.
    """

    # Base parser, for dispatch to subparsers
    parser = argparse.ArgumentParser()
    parser.set_defaults(func=None)
    subparsers = parser.add_subparsers(help='Sub command')

    # Validation parser
    parser_validate = subparsers.add_parser('validate')
    parser_validate.description = 'Validate an input file against a schema.'
    parser_validate.add_argument('schema_filepath', type=str, help='Path to schema file.')
    parser_validate.add_argument('input_filepath', type=str, help='File to validate')
    parser_validate.add_argument('-r', '--report_filepath', type=str, default=None, help='Path to write validation report.')
    parser_validate.set_defaults(func=_validate)

    # CF Conversion
    parser_cf = subparsers.add_parser('convert_cf')
    parser_cf.description = 'Convert CF Conventions standard names table XML to Axiom schema.'
    parser_cf.add_argument('table_filepath', type=str, help='Path to the standard names table.')
    parser_cf.add_argument('output_filepath', type=str, help='Path to which to write the schema.')
    parser_cf.set_defaults(func=_convert_cf)

    # CORDEX
    parser_cordex = subparsers.add_parser('convert_cordex')
    parser_cordex.description = 'Convert CORDEX attribute CSV Axiom schema.'
    parser_cordex.add_argument('csv_filepath', type=str, help='Path to the CSV file.')
    parser_cordex.add_argument('output_filepath', type=str, help='Path to which to write the schema.')
    parser_cordex.set_defaults(func=_convert_cordex)

    # Diff (metadata) tool
    parser_diff = subparsers.add_parser('diff')
    parser_diff.description = 'Difference 2 files for metadata.'
    parser_diff.add_argument('a')
    parser_diff.add_argument('b')
    parser_diff.add_argument('-i', '--ignore', help='Comma-separated list of keys to ignore, format is variable:att', type=str, default=None)
    parser_diff.set_defaults(func=_diff)

    # DRS
    parser_drs = adc.get_parser(parent=subparsers)
    parser_drs.set_defaults(func=_drs)

    # DRS parser that can consume JSON files
    parser_drs_consume = adc.get_parser_consume(parent=subparsers)
    parser_drs_consume.set_defaults(func=_drs_consume)

    # DRS parser that can launch payload-processing jobs.   
    parser_drs_launch = adc.get_parser_launch(parent=subparsers)

    # QA modules
    parser_qa_timeseries = aqc.get_parser(parent=subparsers)

    # Generate payloads
    parser_gen_payloads = adc.get_parser_generate_payloads(parent=subparsers)

    # Rerun payloads
    parser_rerun = adc.get_parser_rerun_failures(parent=subparsers)

    # Generate user config
    parser_gen_user_config = adc.get_parser_generate_user_config(parent=subparsers)

    # Return the fully constructed parser
    return parser


if __name__ == '__main__':

    # Get and dispatch arguments
    parser = get_parser()
    parse_and_dispatch(parser)
