# -*- coding: utf-8 -*-

__author__ = 'Yuvv'

import functools


def fun4filter(rule, tgt, conditions):
    if rule[-3] == tgt:
        if rule[0] == '&':
            for cdt in rule[1:-3]:
                if cdt[0] not in conditions:
                    return False
        elif rule[0] == '|':
            for cdt in rule[1]:
                if cdt not in conditions:
                    return False
        else:
            if rule[1] not in conditions:
                return False
        return True
    return False


def fun4reduce(a, b):
    """reduce function for infer_engine."""
    if a >= 0 and b >= 0:
        return a + b - a * b
    elif a <= 0 and b <= 0:
        return a + b + a * b
    else:
        return (a + b) / (1 - min(abs(a), abs(b)))


def fun4map(rule, factors):
    """map function for infer_engine."""
    if rule[0] == '&':
        temp_c = rule[1:-3]
        temp_c.insert(0, 0)
        cf_e = functools.reduce(
            lambda x, y: x + factors[y[0]] * y[1],
            temp_c
        )
    elif rule[0] == '|':
        cf_e = max(map(lambda x: factors[x], rule[1]))
    else:
        cf_e = factors[rule[1]]

    if cf_e < rule[-1]:
        return 0
    return rule[-2] * max(0, cf_e)


def infer_engine(target, valid_cdt, factors, rules):
    """infer the target's certainty factor.
    :param target: target to infer
    :param valid_cdt: valid conditions
    :param factors: conditions' certainty factor
    :param rules: rules the engine rely on
    :type target: str
    :type valid_cdt: list
    :type factors: dict
    :type rules: dict
    :return: target factor if it can be calculate else None
    """
    try:
        related_rules = filter(lambda x: fun4filter(x, target, valid_cdt), rules)
        factor_list = map(lambda x: fun4map(x, factors), related_rules)
        result = functools.reduce(fun4reduce, factor_list)
    except TypeError:
        result = None   # 此时reduce的可迭代序列为空

    return result


def get_req_cdt(temp, humi, visib, speed, explanations):
    valid_conditions = []
    for item in explanations['ranges']['temperature'].items():
        if item[1][0] <= temp <= item[1][1]:
            valid_conditions.append(item[0])
    for item in explanations['ranges']['humidity'].items():
        if item[1][0] <= humi <= item[1][1]:
            valid_conditions.append(item[0])
    for item in explanations['ranges']['visibility'].items():
        if item[1][0] <= visib <= item[1][1]:
            valid_conditions.append(item[0])
    for item in explanations['ranges']['windspeed'].items():
        if item[1][0] <= speed <= item[1][1]:
            valid_conditions.append(item[0])
    return valid_conditions


def infer(_explanations, _factors, _rules, temp=0, humi=0, visib=0, speed=0):
    valid_cdts = get_req_cdt(temp, humi, visib, speed, _explanations)
    inferences = {}
    for key in _explanations['conclusions']:
        result = infer_engine(key, valid_cdts, _factors, _rules)
        inferences[_explanations['conclusions'][key]] =\
            str(result) if result else 'cannot infer with current conditions'

    return inferences
