#
# Copyright (c) Daniel Sheffield 2023
#
# All rights reserved
#
# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
from typing import Dict, Tuple
from bottle import template
from pandas import DataFrame
from itertools import chain
from . import ALL_UNITS, BOOLEAN

def get_option_groups(
    data: DataFrame, filter_data: Dict[str, Tuple[set, set]],
    k: str, g: str, _type: str
):
    if k in data or (k == 'tag' and 'tags' in data):
        k_data_in_chart = chain(*data["tags"]) if k == "tag" else data[k]
    else:
        k_data_in_chart = []
    
    groups = sorted(set(data[g] if g is not None and g in data else []))
    groups.append(None)
    if _type == "exclude":
        prefix = "!"
    else:
        prefix = ""
    
    for group in groups:
        selected = []
        unselected = []
        if group is None:
            if _type == "include":
                selected.extend(filter_data[k][0] - (
                    set(k_data_in_chart if g is not None else set())
                ))
                unselected.extend(((
                    set(k_data_in_chart if g is None else set())
                ) | filter_data[k][1]) - filter_data[k][0])
            else:
                selected.extend(filter_data[k][1])
                unselected.extend((
                    set(k_data_in_chart if g is None else set())
                ) | (
                    filter_data[k][0] - set(k_data_in_chart) - filter_data[k][1]
                ))
        else:
            k_grouped_data_in_chart = set(data[data[g].apply(lambda x,axis=None: x == group)][k]) if k in data else set()
            if _type == "include":
                selected.extend(filter_data[k][0] & set(k_grouped_data_in_chart))
                unselected.extend(k_grouped_data_in_chart - filter_data[k][0])
            else:
                unselected.extend(set(k_grouped_data_in_chart))
        assert set(selected) - set(unselected) == set(selected), f"{set(selected)} {set(unselected)}"
        options = sorted(map(lambda x: {
            "selected": x[0],
            "value": f"{prefix}{x[1]}",
            "display": x[1]  
        }, chain(
            map(lambda x: (True, x), set(selected)),
            map(lambda x: (False, x), set(unselected)),
        )), key=lambda x: x["display"] if "display" in x else x["value"])
        if len(options) > 0:
            yield {
                "optgroup": group,
                "options": options,
            }


def get_form(action: str, method: str, filter_data: Dict[str, Tuple[set, set]], organic: bool, data: DataFrame):
    keys = sorted(filter(lambda x: x not in ('unit', 'tag', 'organic'), filter_data), key=lambda x: {
        'product': 0,
        'category': 1,
        'group': 2,
    }[x])
    return template('form-filter', action=action, method=method,
        params=filter_data,
        **{
            k: {
                "name": k,
                "_include": {
                    "option_groups": get_option_groups(data, filter_data, k, g, "include"),
                },
                "_exclude": {
                    "option_groups": get_option_groups(data, filter_data, k, g, "exclude"),
                }
            } for k, g in zip(keys, [*keys[1:], None])
        },
        tags={
            "name": "tag",
            "_include": {
                "option_groups": get_option_groups(data, filter_data, "tag", None, "include")
            },
            "_exclude": {
                "option_groups": get_option_groups(data, filter_data, "tag", None, "exclude")
            },
        },
        units={
            "name": "unit",
            "options": sorted(map(lambda x: {
                "selected": x[0],
                "value": x[1],
            },chain(
                map(lambda x: (True, x), filter_data['unit'][0]),
                map(lambda x: (False, x), ALL_UNITS - filter_data['unit'][0])
            )), key=lambda x: x["display"] if "display" in x else x["value"])
        },
        organic=BOOLEAN[organic],
    )
