import pandas as pd
import difflib

import numpy as np
from numpy import dot
from numpy.linalg import norm

from pyvis.network import Network
import streamlit.components.v1 as components

color_code_node = {
    0: '#4B9EFF',
    1: '#4BD4FF',
    2: '#3CDFCB',
    3: '#37DF8E',
    4: '#A0C159',
    5: '#CA804B',
    6: '#CA524B',
    7: '#CA4B97',
    8: '#C04BCA',
    9: '#5D4BCA',
    10: '#213ABA',
    11: '#0E6697',
}

class HealthseaSearch:
    def __init__(self, _health_aspects, _products, _conditions, _benefits):
        self.health_aspects = _health_aspects
        self.products = _products
        self.conditions = _conditions
        self.benefits = _benefits

    def __call__(self, query):
        return query

    # Load product meta
    def get_products(self, _aspect, n):
        product_list = []
        product_ids = {}
        _n = n
        _aspect = _aspect.replace(" ", "_")
        if _aspect in self.health_aspects:
            aspect = self.health_aspects[_aspect]
        else:
            _aspect = difflib.get_close_matches("_aspect", self.health_aspects.keys())[
                0
            ]
            aspect = self.health_aspects[_aspect]

        product_scoring = aspect["products"]
        if n != 0:
            if n > len(product_scoring):
                n = len(product_scoring)
            product_scoring = aspect["products"][:n]

        for product in product_scoring:
            if product[1] not in product_ids:
                product_list.append((product[0], self.products[product[1]], _aspect))
                product_ids[product[1]] = 1

        for alias in aspect["alias"]:
            n = _n
            _product_scoring = self.health_aspects[alias]["products"]
            if n != 0:
                if n > len(_product_scoring):
                    n = len(_product_scoring)
                _product_scoring = self.health_aspects[alias]["products"][:n]

            for product in _product_scoring:
                if product[1] not in product_ids:
                    product_list.append((product[0], self.products[product[1]], alias))
                    product_ids[product[1]] = 1

        n = _n
        if len(product_list) > n and n != 0:
            product_list = product_list[:n]
        product_list = sorted(product_list, key=lambda tup: tup[0], reverse=True)

        return product_list

    # Load product meta and return as DataFrame
    def get_products_df(self, _aspect, n):
        product_list = self.get_products(_aspect, n)
        product_data = {
            "product": [],
            "score": [],
            "health_aspect": [],
            "rating": [],
            "reviews": [],
        }
        for product in product_list:
            product_data["score"].append(product[0])
            product_data["product"].append(product[1]["name"])
            product_data["health_aspect"].append(product[2])
            product_data["rating"].append(product[1]["rating"])
            product_data["reviews"].append(product[1]["review_count"])

        datatypes = {
            "product": str,
            "score": int,
            "health_aspect": str,
            "rating": str,
            "reviews": int,
        }

        df = pd.DataFrame(data=product_data)
        df = df.astype(datatypes)

        return df

    # Get health aspect
    def get_aspect(self, _aspect):
        _aspect = _aspect.replace(" ", "_")
        if _aspect in self.health_aspects:
            return self.health_aspects[_aspect]
        else:
            _aspect = difflib.get_close_matches("_aspect", self.health_aspects.keys())[
                0
            ]
            return self.health_aspects[_aspect]

    # Get health aspect meta
    def get_aspect_meta(self, _aspect):
        _aspect = _aspect.replace(" ", "_")
        if _aspect in self.conditions:
            return self.conditions[_aspect]
        elif _aspect in self.benefits:
            return self.benefits[_aspect]
        else:
            _aspect = difflib.get_close_matches("_aspect", self.conditions.keys())[0]
            return self.conditions[_aspect]

    def pyvis(self, vectors):
        net = Network(height='500px', width='700px', bgcolor="#0E1117", font_color="#ffffff")
        net.barnes_hut(gravity=-2500)
        net.set_edge_smooth("dynamic")
        net.toggle_stabilization(False)

        net.add_node(vectors[0][0], label=vectors[0][0], color="#4EA0DB", value=100, shape="circle") # node id = 1 and label = Node 1

        for vector in vectors[1:]:
            net.add_node(vector[0], label=vector[0], color="#FE51B9", value=70, shape="circle") # node id = 1 and label = Node 1

        for i, vector in enumerate(vectors):
            current_vector = vectors[i]
            if i < len(vectors):
                if i == 0:
                    for _vector in vectors[i+1:]:
                        sim = self.calculate_cosine_sim(current_vector[1],_vector[1])
                        net.add_edge(current_vector[0],_vector[0], weight=sim, value=sim, title=sim)
                else:
                    for _vector in vectors[i+1:]:
                        sim = self.calculate_cosine_sim(current_vector[1],_vector[1])
                        net.add_edge(current_vector[0],_vector[0], weight=sim, value=sim/2, title=sim)

        #for _vector in vectors[1:]:
        #    sim = self.calculate_cosine_sim(vectors[0][1],_vector[1])
        #    net.add_edge(vectors[0][0],_vector[0], weight=sim, value=sim*0.1, title=sim)


        net.save_graph("viz.html")
        HtmlFile = open("viz.html", 'r', encoding='utf-8')
        source_code = HtmlFile.read() 
        components.html(source_code, height = 500, width=700)

    # Experimental
    def get_recursive_alias(self, _aspect, n, node_list, edge_list, _max):
        aspect = self.get_aspect(_aspect)

        aspect_name = aspect["name"].replace(" ","_")

        if aspect_name not in node_list:
            node_list[aspect_name] = {"level":n}

        aspect_alias = aspect["alias"]

        if len(aspect_alias) > 0 and n <= _max:
            for alias in aspect_alias: 
                if alias not in node_list:
                    edge_list.append((aspect_name,alias,n))
                    self.get_recursive_alias(alias, n+1, node_list, edge_list,_max)

            return node_list, edge_list
        else:
            return node_list, edge_list

    def add_to_network(self, network, node_list, edge_list):
        for node in node_list:
            value = 100-(15*node_list[node]["level"])
            network.add_node(node, label=node, color=color_code_node[node_list[node]["level"]], value=value, shape="dot", title = str(node_list[node]["level"]))

        for edge in edge_list:
            value = 1-(0.15*edge[2])
            network.add_edge(edge[0], edge[1], value=value)

    def pyvis2(self, node_list, edge_list):
        net = Network(height='500px', width='700px', bgcolor="#0E1117", font_color="#ffffff")
        net.barnes_hut(gravity=-2500-(len(node_list)*2))
        net.set_edge_smooth("dynamic")

        self.add_to_network(net, node_list, edge_list)

        net.save_graph("viz.html")
        HtmlFile = open("viz.html", 'r', encoding='utf-8')
        source_code = HtmlFile.read() 
        components.html(source_code, height = 500, width=700)

    def calculate_cosine_sim(self,a,b):
        cos_sim = dot(a, b)/(norm(a)*norm(b))
        return cos_sim

    # Load substance meta
    def get_substances(self, _aspect, n):
        substance_list = []
        substance_ids = {}
        exclude = ["sodium", "sugar", "sugar_alcohol"]
        _n = n
        _aspect = _aspect.replace(" ", "_")
        if _aspect in self.health_aspects:
            aspect = self.health_aspects[_aspect]
        else:
            _aspect = difflib.get_close_matches("_aspect", self.health_aspects.keys())[
                0
            ]
            aspect = self.health_aspects[_aspect]

        substance_scoring = aspect["substance"]
        if n != 0:
            if n > len(substance_scoring):
                n = len(substance_scoring)
            substance_scoring = aspect["substance"][:n]

        for substance in substance_scoring:
            if substance[1] in exclude:
                continue
            if substance[1] not in substance_ids:
                substance_list.append((substance[0], substance[1], _aspect))
                substance_ids[substance[1]] = 1

        for alias in aspect["alias"]:
            n = _n
            _substance_scoring = self.health_aspects[alias]["substance"]
            if n != 0:
                if n > len(_substance_scoring):
                    n = len(_substance_scoring)
                _substance_scoring = self.health_aspects[alias]["substance"][:n]

            for substance in _substance_scoring:
                if substance[1] in exclude:
                    continue
                if substance[1] not in substance_ids:
                    substance_list.append((substance[0], substance[1], alias))
                    substance_ids[substance[1]] = 1

        n = _n
        if len(substance_list) > n and n != 0:
            substance_list = substance_list[:n]
        substance_list = sorted(substance_list, key=lambda tup: tup[0], reverse=True)

        return substance_list

    # Load substance meta and return as DataFrame
    def get_substances_df(self, _aspect, n):
        substance_list = self.get_substances(_aspect, n)
        substance_data = {"substance": [], "score": [], "health_aspect": []}
        for substance in substance_list:
            substance_data["score"].append(substance[0])
            substance_data["substance"].append(substance[1])
            substance_data["health_aspect"].append(substance[2])

        datatypes = {"substance": str, "score": int, "health_aspect": str}

        df = pd.DataFrame(data=substance_data)
        df = df.astype(datatypes)

        return df

    # Get all health aspect indices
    def get_all_conditions(self):
        condition_list = []
        for condition_key in self.conditions:
            if condition_key in self.health_aspects:
                alias = len(self.health_aspects[condition_key]["alias"])
            else:
                alias = 0
            condition_list.append((self.conditions[condition_key]["frequency"],condition_key,alias))

        condition_list = sorted(condition_list, key=lambda tup: tup[0], reverse=True)
        return condition_list

    def get_all_conditions_df(self):
        condition_list = self.get_all_conditions()[:100]
        condition_data = {
            "Condition": [],
            "Frequency": [],
            "Alias": []
        }
        for condition in condition_list:
            condition_data["Frequency"].append(condition[0])
            condition_data["Condition"].append(condition[1])
            condition_data["Alias"].append(condition[2])

        datatypes = {
            "Frequency": int,
            "Condition": str,
            "Alias": int
        }

        df = pd.DataFrame(data=condition_data)
        df = df.astype(datatypes)

        return df

    def get_all_benefits(self):
        benefit_list = []
        for benefit_key in self.benefits:
            if benefit_key in self.health_aspects:
                alias = len(self.health_aspects[benefit_key]["alias"])
            else:
                alias = 0
            benefit_list.append((self.benefits[benefit_key]["frequency"],benefit_key,alias))

        benefit_list = sorted(benefit_list, key=lambda tup: tup[0], reverse=True)
        return benefit_list

    def get_all_benefits_df(self):
        benefit_list = self.get_all_benefits()[:100]
        benefit_data = {
            "Benefit": [],
            "Frequency": [],
            "Alias": []
        }
        for benefit in benefit_list:
            benefit_data["Frequency"].append(benefit[0])
            benefit_data["Benefit"].append(benefit[1])
            benefit_data["Alias"].append(benefit[2])


        datatypes = {
            "Frequency": int,
            "Benefit": str,
            "Alias": int
        }

        df = pd.DataFrame(data=benefit_data)
        df = df.astype(datatypes)

        return df