#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
logger = logging.getLogger('__unittest__')

REPORT_LEVEL_FAIL = logging.ERROR
REPORT_LEVEL_INSPECT = logging.WARNING
REPORT_LEVEL_PASS = logging.INFO


def __get_repr__(value, data_filter=repr):
    if type(value) == dict:
        return '{ ' + ', '.join(['%s: %s' % (repr(key), __get_repr__(value.get(key))) for key in value.keys()]) + ' }'
    elif type(value) == list:
        return '[ ' + ', '.join(['%s' % (__get_repr__(v)) for v in value]) + ' ]'
    else:
        return data_filter(value)


def __report_result__(result, description, data_filter=repr):
    logger.debug('Result (%s): %s (%s)', description, __get_repr__(result, data_filter), repr(type(result)))


def __report_expectation_equivalency__(expectation, description, data_filter=repr):
    logger.debug('Expectation (%s): result = %s (%s)', description, __get_repr__(expectation, data_filter), repr(type(expectation)))


def __report_expectation_range__(min_expectation, max_expectation, description):
    logger.debug('Expectation (%s): %s <= result <= %s', description, __get_repr__(min_expectation), __get_repr__(max_expectation))


def __equivalent_dict__(result, expectation, report_comment_fail=None, dict_key='test_variable'):
    result_keys = set(result.keys())
    expect_keys = set(expectation.keys())
    #
    log_lvl = REPORT_LEVEL_PASS
    #
    # missing elements
    #
    missing_keys = expect_keys - result_keys
    if len(missing_keys) > 0:
        logger.error('Missing key(s) in dict (%s): %s.' + report_comment_fail, dict_key, ', '.join(['%s' % repr(key) for key in missing_keys]))
        log_lvl = REPORT_LEVEL_FAIL
    #
    # odd elements
    #
    odd_keys = result_keys - expect_keys
    if len(odd_keys) > 0:
        logger.error('Odd key(s) in dict (%s): %s.' + report_comment_fail, dict_key, ', '.join(['%s' % repr(key) for key in odd_keys]))
        log_lvl = REPORT_LEVEL_FAIL
    #
    # differences
    #
    common_keys = result_keys - missing_keys - odd_keys
    for key in common_keys:
        ll = __equivalent__(result[key], expectation[key], report_comment_fail=report_comment_fail, dict_key=dict_key + ('.' if dict_key != '' else '') + str(key))
        if log_lvl < ll:
            log_lvl = ll
    return log_lvl


def __equivalent_list__(result, expectation, report_comment_fail=None, list_key='test_variable'):
    _odd_ = []
    _result_ = result[:]
    e_index = list(range(0, len(expectation)))
    log_lvl = REPORT_LEVEL_PASS
    r = 0
    while len(_result_) > 0:
        value = _result_.pop(0)
        just_type_diff = None
        for e in e_index:
            ll = __equivalent__(value, expectation[e], None)
            if ll == REPORT_LEVEL_PASS:
                e_index.pop(e_index.index(e))
                break
            elif ll == REPORT_LEVEL_INSPECT:
                just_type_diff = e
        else:
            if just_type_diff is None:
                _odd_.append(value)
            else:
                log_lvl = __equivalent__(value, expectation[just_type_diff], report_comment_fail, dict_key='%s[%d]' % (list_key, r))
                e_index.pop(e_index.index(just_type_diff))
        r += 1
    #
    # missing elements
    #
    if len(e_index) > 0:
        logger.error('Missing value(s) in list (%s): %s.' + report_comment_fail, list_key, ', '.join(['%s' % repr(expectation[e]) for e in e_index]))
        log_lvl = REPORT_LEVEL_FAIL
    #
    # odd elements
    #
    if len(_odd_) > 0:
        logger.error('Odd value(s) in list (%s): %s.' + report_comment_fail, list_key, ', '.join(['%s' % repr(v) for v in _odd_]))
        log_lvl = REPORT_LEVEL_FAIL
    return log_lvl


def __equivalent__(result, expectation, report_comment_fail=None, dict_key='test_variable'):
    report_comment_fail = (' ' + report_comment_fail) if report_comment_fail is not None else ''
    log_lvl = REPORT_LEVEL_PASS
    if type(result) == dict and type(expectation) == dict:
        ll = __equivalent_dict__(result, expectation, report_comment_fail, dict_key)
        if log_lvl < ll:
            log_lvl = ll
    elif type(result) == list and type(expectation) == list:
        ll = __equivalent_list__(result, expectation, report_comment_fail, dict_key)
        if log_lvl < ll:
            log_lvl = ll
    else:
        if result != expectation:
            log_lvl = REPORT_LEVEL_FAIL
            logger.error('Content %s is incorrect' + (' for %s' % dict_key if dict_key != '' else '') + '.' + report_comment_fail, __get_repr__(result))
        if type(result) != type(expectation):
            if log_lvl < REPORT_LEVEL_INSPECT:
                log_lvl = REPORT_LEVEL_INSPECT
            logger.warning('Type %s is NOT %s%s (%s). ' + report_comment_fail.strip() or '', __get_repr__(type(result)), __get_repr__(type(expectation)), (' for %s' % dict_key if dict_key != '' else ''), __get_repr__(result))
    return log_lvl


def equivalency_chk(result, expectation, tcl, description='Variable', report_comment_fail=None, data_filter=repr):
    """
    Routine to check values for equivalency inside a test run and report to a testCaseLogger.

    :param result: The result of a test execution of a module
    :type result: All types are supported
    :param expectation: The expected value (shall be equivalent to result)
    :type expectation: All types are supported
    :param description: A descrition of the result. It will be reported like "xxx is correct." Example: descrition="stringrepresentation created by modulename"
    :type description: str
    :param report_level_pass: The reporting level as defined in :class:`logging` (e.g.: logging.INFO)
    :param report_level_fail: The reporting level as defined in :class:`logging` (e.g.: logging.ERROR)
    :param report_comment_fail: Comment for a failed Testexecution. Will be added in brakets after the Result-Text.
    :type report_comment_fail: str
    """
    __report_result__(result, description, data_filter=data_filter)
    __report_expectation_equivalency__(expectation, description, data_filter=data_filter)
    report_level = __equivalent__(result, expectation, report_comment_fail=report_comment_fail, dict_key='result')
    if report_level == REPORT_LEVEL_PASS:
        tcl.log(report_level, description + ' is correct (Content %s and Type is %s).', data_filter(result), repr(type(result)))
    else:
        tcl.log(report_level, description + ' is NOT correct. See detailed log for more information.')
    return report_level


class equivalency_order_chk(object):
    def __init__(self, ordered_values, tcl, description='Variable', report_comment_fail=None):
        self._expected_values = ordered_values
        self._tcl = tcl
        self._description = description
        self._report_comment_fail = report_comment_fail
        self._reported_values = []

    def report_value(self, value):
        self._reported_values.append(value)

    def report(self):
        __report_result__(self._reported_values, self._description)
        __report_expectation_equivalency__(self._expected_values, self._description)
        report_lvl = REPORT_LEVEL_PASS
        for i in range(0, min(len(self._expected_values), len(self._reported_values))):
            report_lvl = max(report_lvl, equivalency_chk(self._reported_values[i], self._expected_values[i], logger, 'Submitted value number %d' % (i + 1), self._report_comment_fail))
        if report_lvl <= REPORT_LEVEL_PASS:
            self._tcl.log(report_lvl, self._description + ': Values and number of submitted values is correct. See detailed log for more information.')
        else:
            self._tcl.log(report_lvl, self._description + ': Values and number of submitted values is NOT correct. See detailed log for more information.')
        return report_lvl

    def report_range_check(self, minus_tollerance, plus_tollerance):
        __report_result__(self._reported_values, self._description)
        report_lvl = REPORT_LEVEL_PASS
        report_lvl = max(report_lvl, equivalency_chk(len(self._reported_values), len(self._reported_values), self._tcl, 'Number of submitted values', self._report_comment_fail))
        for i in range(0, min(len(self._expected_values), len(self._reported_values))):
            report_lvl = max(report_lvl, range_chk(self._reported_values[i], self._expected_values[i] - minus_tollerance, self._expected_values[i] + plus_tollerance, logger, 'Submitted value number %d' % (i + 1), self._report_comment_fail))
        if report_lvl <= REPORT_LEVEL_PASS:
            self._tcl.log(report_lvl, self._description + ': Valueaccuracy and number of submitted values is correct. See detailed log for more information.')
        else:
            self._tcl.log(report_lvl, self._description + ': Valueaccuracy and number of submitted values is NOT correct. See detailed log for more information.')
        return report_lvl


def __range__(result, min_expectation, max_expectation, report_comment_fail):
    report_comment_fail = (' ' + report_comment_fail) if report_comment_fail is not None else ''
    log_lvl = REPORT_LEVEL_PASS
    if result < min_expectation or result > max_expectation:
        log_lvl = REPORT_LEVEL_FAIL
        logger.error('Content %s is incorrect.' + report_comment_fail, __get_repr__(result))
    if type(result) != type(min_expectation) or type(result) != type(max_expectation):
        if log_lvl < REPORT_LEVEL_INSPECT:
            log_lvl = REPORT_LEVEL_INSPECT
        logger.warning('Type %s is incorrect.' + report_comment_fail, __get_repr__(type(result)))
    return log_lvl


def range_chk(result, min_expectation, max_expectation, tcl, description='Value', report_comment_fail=None):
    """
    Routine to check values to be in a range inside a test run and report to a testCaseLogger.

    :param result: The result of a test execution of a module
    :type result: All numeric types are supported
    :param min: The result shall be more or equal
    :type min: All numeric types are supported
    :param max: The result shall be less or equivalent
    :type max: All numeric types are supported
    :param description: A descrition of the result. It will be reported like "xxx is correct." Example: descrition="stringrepresentation created by modulename"
    :type description: str
    :param report_level_pass: The reporting level as defined in :class:`logging` (e.g.: logging.INFO)
    :param report_level_fail: The reporting level as defined in :class:`logging` (e.g.: logging.ERROR)
    :param report_comment_fail: Comment for a failed Testexecution. Will be added in brakets after the Result-Text.
    :type report_comment_fail: str
    """
    __report_result__(result, description)
    __report_expectation_range__(min_expectation, max_expectation, description)
    report_level = __range__(result, min_expectation, max_expectation, report_comment_fail=report_comment_fail)
    if report_level == REPORT_LEVEL_PASS:
        tcl.log(report_level, description + ' is correct (Content %s in [%s ... %s] and Type is %s).', repr(result), repr(min_expectation), repr(max_expectation), repr(type(result)))
    else:
        tcl.log(report_level, description + ' is NOT correct. See detailed log for more information.')
    return report_level


def in_list_chk(result, expectation_list, tcl, description='Value', report_level_pass=logging.INFO, report_level_fail=logging.ERROR, report_comment_fail=None):
    """
    Routine to check values to be in a range inside a test run and report to a testCaseLogger.

    :param result: The result of a test execution of a module
    :type result: All types are supported
    :param expectation_list: The list of allowed values
    :type expectation_list: A list of all types is supported
    :param description: A descrition of the result. It will be reported like "xxx is correct." Example: descrition="stringrepresentation created by modulename"
    :type description: str
    :param report_level_pass: The reporting level as defined in :class:`logging` (e.g.: logging.INFO)
    :param report_level_fail: The reporting level as defined in :class:`logging` (e.g.: logging.ERROR)
    :param report_comment_fail: Comment for a failed Testexecution. Will be added in brakets after the Result-Text.
    :type report_comment_fail: str
    """
    __report_values__(result, expectation)
    tcl.log(REPORT_LEVEL_FAIL, 'in_list check not yet implemented')
    return REPORT_LEVEL_FAIL