from .modifiable_property import (
    ModifiableProperty,
    ModifiablePropertyWithAst,
    is_modifiable_property,
)


def resolve(knowledge_base, elements, value):
    if isinstance(value, int):
        return elements[value]
    elif isinstance(value, tuple) or isinstance(value, list):
        return integrate_information(knowledge_base, {
            "elements": elements,
            "parsed": value,
        })
    return value


# TODO: improve typing
def infer_type(result):
    if isinstance(result, bool):
        return "bool"
    elif isinstance(result, int):
        return "int"

    else:
        raise Exception("Unknown type for value: {}".format(result))


def get_subquery_type(knowledge_base, atom):
    subquery_result = integrate_information(knowledge_base,
                                            {
                                                "parsed": atom,
                                                "elements": [],
                                            })
    assert (subquery_result is not None)
    result = subquery_result.getter()

    result_type = infer_type(result)
    return result_type


def property_for_value(knowledge_base, value):
    if value in knowledge_base:
        # Annotate the property as property
        groups = knowledge_base[value].get('groups', {'property'})
        groups.add('property')
        knowledge_base[value]['groups'] = groups

        # And find the property "name"
        if 'as_property' in knowledge_base[value]:
            return knowledge_base[value]['as_property']

        return knowledge_base[value].get('groups', {'property'})
    else:
        # Consider that any property is... a property
        knowledge_base[value] = {'groups': {'property'}}
        return {'property'}


def modifiable_property_from_property(prop, path, value):
    def getter():
        nonlocal prop, path, value
        if isinstance(path, set):
            # If the property is from a set, it's true if any possible
            # path has a element as true
            return any(map(lambda possible_path: ((possible_path in prop)
                                                  and
                                                  (prop[possible_path] == value)),
                           path))
        else:
            return (path in prop) and prop[path] == value

    def setter():
        nonlocal prop, path, value
        if isinstance(path, set):
            for possible_path in path:
                prop[possible_path] = value
        else:
            prop[path] = value

    return ModifiableProperty(
        getter=getter,
        setter=setter,
    )


def exists_property_with_value(knowledge_base, elements, subj, value):
    subj = resolve(knowledge_base, elements, subj)
    value = resolve(knowledge_base, elements, value)

    if subj not in knowledge_base:
        knowledge_base[subj] = {}

    return modifiable_property_from_property(
        prop=knowledge_base[subj],
        path=property_for_value(knowledge_base, value),
        value=value
    )


def modifiable_element_for_existance_in_set(container, set_name, element):
    def getter():
        nonlocal container, set_name, element
        return (set_name in container) and (element in container[set_name])

    def setter():
        nonlocal container, set_name, element
        return container[set_name].add(element)

    return ModifiableProperty(
        getter=getter,
        setter=setter,
    )

def modifiable_element_for_existance_in_group(container, element, backlink, set_name='groups'):
    def getter():
        nonlocal container, element, backlink, set_name
        return (set_name in container) and (element in container[set_name])

    def setter():
        nonlocal container, set_name, element
        backlink['groups'].add(set_name)
        return container[set_name].add(element)

    return ModifiableProperty(
        getter=getter,
        setter=setter,
    )

def pertenence_to_group(knowledge_base, elements, subj, group):
    subj = resolve(knowledge_base, elements, subj)
    group = resolve(knowledge_base, elements, group)

    if subj not in knowledge_base:
        knowledge_base[subj] = {}

    if "groups" not in knowledge_base[subj]:
        knowledge_base[subj]["groups"] = set()

    if group not in knowledge_base:
        knowledge_base[group] = {}

    if "groups" not in knowledge_base[group]:
        knowledge_base[group]["groups"] = set()

    return modifiable_element_for_existance_in_group(
        container=knowledge_base[subj],
        element=group,
        backlink=knowledge_base[group],
    )

def has_capacity(knowledge_base, elements, subj, capacity):
    subj = resolve(knowledge_base, elements, subj)
    capacity = resolve(knowledge_base, elements, capacity)

    if subj not in knowledge_base:
        knowledge_base[subj] = {}

    if "capacities" not in knowledge_base[subj]:
        knowledge_base[subj]["capacities"] = set()

    return modifiable_element_for_existance_in_set(
        container=knowledge_base[subj],
        set_name="capacities",
        element=capacity
    )


def question(knowledge_base, elements, subj):
    subj = resolve(knowledge_base, elements, subj)

    if is_modifiable_property(subj):
        return subj.getter()
    return subj

def implies(knowledge_base, elements, precedent, consequent):
    precedent = resolve(knowledge_base, elements, precedent)
    consequent = resolve(knowledge_base, elements, consequent)

    if precedent not in knowledge_base:
        knowledge_base[precedent] = {}

    if "implications" not in knowledge_base[precedent]:
        knowledge_base[precedent]["implications"] = set()

    return modifiable_element_for_existance_in_set(
        container=knowledge_base[precedent],
        set_name="implications",
        element=consequent
    )


def property_has_value(knowledge_base, elements, subj, prop, value):
    subj = resolve(knowledge_base, elements, subj)
    prop = resolve(knowledge_base, elements, prop)
    value = resolve(knowledge_base, elements, value)

    if subj not in knowledge_base:
        knowledge_base[subj] = {}

    if prop not in knowledge_base[subj]:
        knowledge_base[subj][prop] = set()

    return modifiable_element_for_existance_in_set(
        container=knowledge_base[subj],
        set_name=prop,
        element=value
    )


knowledge_ingestion = {
    "exists-property-with-value": exists_property_with_value,
    "pertenence-to-group": pertenence_to_group,
    "has-capacity": has_capacity,
    "question": question,
    "implies": implies,
    "property-has-value": property_has_value,
}


def tagged_with_ast(ast, elements, modifiable_property):
    if not isinstance(modifiable_property, ModifiableProperty):
        return modifiable_property

    return ModifiablePropertyWithAst(modifiable_property.getter,
                                     modifiable_property.setter,
                                     ast, elements)


def integrate_information(knowledge_base, example):
    ast = example['parsed']
    method = ast[0]
    args = ast[1:]
    elements = example.get('elements', None)

    return tagged_with_ast(
        ast, elements,
        knowledge_ingestion[method](knowledge_base, elements, *args))
