from gi.repository import Gdk, GObject, Gtk, Pango

from gourmand.defaults import defaults as _defaults
from gourmand.i18n import _

MAJOR = 0
MINOR = 1
TINY = 2
SEP = 3
SHOW_PERCENT = True
DONT_SHOW_PERCENT = False
MAIN_NUT_LAYOUT = [
    (_("Calories"), MAJOR, "kcal", "kcal", DONT_SHOW_PERCENT, ""),
    SEP,
    (_("Total Fat"), MAJOR, "fat", ["fasat", "famono", "fapoly"], SHOW_PERCENT, "g"),
    (_("Saturated Fat"), MINOR, "fasat", "fasat", SHOW_PERCENT, "g"),
    (_("Cholesterol"), MAJOR, "cholestrl", "cholestrl", SHOW_PERCENT, "mg"),
    (_("Sodium"), MAJOR, "sodium", "sodium", SHOW_PERCENT, "mg"),
    (_("Total Carbohydrate"), MAJOR, "carb", "carb", SHOW_PERCENT, "g"),
    (_("Dietary Fiber"), MINOR, "fiber", "fiber", SHOW_PERCENT, "g"),
    (_("Sugars"), MINOR, "sugar", "sugar", DONT_SHOW_PERCENT, "g"),
    (_("Protein"), MAJOR, "protein", "protein", SHOW_PERCENT, "g"),
]

DETAIL_NUT_LAYOUT = [
    (_("Alpha-carotene"), TINY, "alphac", "alphac", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Ash"), TINY, "ash", "ash", DONT_SHOW_PERCENT, "g"),
    (_("Beta-carotene"), TINY, "betac", "betac", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Beta Cryptoxanthin"), TINY, "betacrypt", "betacrypt", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Calcium"), TINY, "calcium", "calcium", SHOW_PERCENT, "mg"),
    (_("Copper"), TINY, "copper", "copper", SHOW_PERCENT, "mg"),
    (_("Folate Total"), TINY, "folatetotal", "folatetotal", SHOW_PERCENT, "\u00B5g"),
    (_("Folic acid"), TINY, "folateacid", "folateacid", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Food Folate"), TINY, "foodfolate", "foodfolate", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Dietary folate equivalents"), TINY, "folatedfe", "folatedfe", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Iron"), TINY, "iron", "iron", SHOW_PERCENT, "mg"),
    (_("Lycopene"), TINY, "lypocene", "lypocene", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Lutein+Zeazanthin"), TINY, "lutzea", "lutzea", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Magnesium"), TINY, "magnesium", "magnesium", SHOW_PERCENT, "mg"),
    (_("Manganese"), TINY, "manganese", "manganese", SHOW_PERCENT, "mg"),
    (_("Niacin"), TINY, "niacin", "niacin", SHOW_PERCENT, "mg"),
    (_("Pantothenic Acid"), TINY, "pantoacid", "pantoacid", SHOW_PERCENT, "mg"),
    (_("Phosphorus"), TINY, "phosphorus", "phosphorus", SHOW_PERCENT, "mg"),
    (_("Potassium"), TINY, "potassium", "potassium", SHOW_PERCENT, "mg"),
    (_("Vitamin A"), TINY, "vitarae", "vitarae", SHOW_PERCENT, "\u00B5g"),
    (_("Retinol"), TINY, "retinol", "retinol", SHOW_PERCENT, "\u00B5g"),
    (_("Riboflavin"), TINY, "riboflavin", "riboflavin", SHOW_PERCENT, "mg"),
    (_("Selenium"), TINY, "selenium", "selenium", SHOW_PERCENT, "\u00B5g"),
    (_("Thiamin"), TINY, "thiamin", "thiamin", SHOW_PERCENT, "mg"),
    (_("Vitamin A (IU)"), TINY, "vitaiu", "vitaiu", SHOW_PERCENT, "IU"),
    (_("Vitamin A (RAE)"), TINY, "vitarae", "vitarae", DONT_SHOW_PERCENT, "\u00B5g"),
    (_("Vitamin B6"), TINY, "vitaminb6", "vitaminb6", SHOW_PERCENT, "mg"),
    (_("Vitamin B12"), TINY, "vitb12", "vitb12", SHOW_PERCENT, "\u00B5g"),
    (_("Vitamin C"), TINY, "vitaminc", "vitaminc", SHOW_PERCENT, "mg"),
    (_("Vitamin E"), TINY, "vite", "vite", SHOW_PERCENT, "mg"),
    (_("Vitamin K"), TINY, "vitk", "vitk", SHOW_PERCENT, "mg"),
    (_("Zinc"), TINY, "zinc", "zinc", SHOW_PERCENT, "mg"),
]

NUT_LAYOUT = MAIN_NUT_LAYOUT + DETAIL_NUT_LAYOUT

RECOMMENDED_INTAKE = {
    "fat": 0.30 / 9,  # 30% of calories * 9 cal / g.
    "fasat": 0.10 / 9,  # 10% of calories * 9 cal /g.
    "carb": 0.6 / 4,  # 60% of calories * 4 cal / g.
    "protein": 0.10 / 4,  # 10% of calories * 4 cal/g.
    "fiber": 11.5 / 1000,  # 11.5 grams / 1000calories
    "sodium": 2400.0 / 2000,  # 2400 mg per typical 2000 c diet
    "potassium": 3500.0 / 2000,
    "iron": 18.0 / 2000,
    "calcium": 1000.0 / 2000,
    "cholestrl": 300.0 / 2000,
    "vitarae": 900.0 / 2000,
    "vitaminc": 60.0 / 2000,
    "vitaiu": 5000.0 / 2000,
    "vite": 30.0 / 2000,
    "vitk": 80.0 / 2000,
    "thiamin": 1.5 / 2000,
    "riboflavin": 1.7 / 2000,
    "niacin": 20.0 / 2000,
    "vitaminb6": 2.0 / 2000,
    "folatetotal": 400.0 / 2000,
    "vitb12": 6.0 / 2000,
    "pantoacid": 10.0 / 2000,
    "phosphorus": 1000.0 / 2000,
    "magnesium": 400.0 / 2000,
    "zinc": 15.0 / 2000,
    "selenium": 70.0 / 2000,
    "copper": 2.0 / 2000,
    "manganese": 2.0 / 2000,
    "chromium": 120.0 / 2000,
    "molybdenum": 75.0 / 2000,
    "chloride": 3400.0 / 2000,
}


class NutritionLabel(Gtk.VBox):
    """Provide a nutritional label that looks like standard FDA
    labels."""

    __gtype_name__ = "NutritionLabel"

    bold_font = Pango.FontDescription()
    bold_font.set_weight(Pango.Weight.BOLD)
    tiny_font = Pango.FontDescription()
    tiny_font.set_size(Pango.SCALE * 9)
    background = Gdk.Color(255, 0, 0)
    foreground = Gdk.Color(255, 255, 255)

    calories_per_day = 2000

    __gsignals__ = {
        "calories-changed": (GObject.SignalFlags.RUN_LAST, None, ()),
        "ingredients-changed": (GObject.SignalFlags.RUN_LAST, None, ()),
        "label-changed": (GObject.SignalFlags.RUN_LAST, None, ()),
    }

    def __init__(self, prefs=None, rec=None, custom_label=None, pressable=True):
        self.pressable = pressable
        self.toggles = {}
        self.__toggling__ = False
        self.active_name = None
        self.active_unit = None
        self.active_label = None
        self.active_properties = None
        self.active_button_markup = ('<span background="#ffff00">', "</span>")
        self.custom_label = custom_label
        self.prefs = prefs
        self.rec = rec
        start_at = 4
        Gtk.VBox.__init__(self)
        # ,2,len(NUT_LAYOUT)+start_at)
        self.show()
        self.yieldLabel = Gtk.Label()
        self.set_yields(0)
        self.nutrition_display_info = []
        # self.attach(self.yieldLabel,
        #            0,2,0,1)
        self.yieldLabel.show()
        self.yieldLabel.set_alignment(0, 0.5)
        self.missingLabel = self.make_missing_label()
        # self.attach(self.missingLabel,0,2,1,2)
        self.pack_start(self.missingLabel, False, False, 0)
        self.pack_start(self.yieldLabel, False, False, 0)
        # setup daily value button to display calories/day assumption
        # and to allow changing it via a nifty little button
        dvb, eb = self.make_dv_boxes()
        dvb.show()
        self.cal_per_day_box = Gtk.HBox()
        self.cal_per_day_box.show()
        vb = Gtk.VBox()
        vb.show()
        vb.pack_start(dvb, False, False, 0)
        vb.pack_start(eb, False, False, 0)
        self.cal_per_day_box.pack_end(vb, True, True, 0)
        self.pack_start(self.cal_per_day_box, True, True, 0)
        self.main_table = Gtk.Table()
        self.main_table.show()
        self.main_table.set_col_spacings(18)
        self.pack_start(self.main_table, True, True, 0)
        self.sub_table = Gtk.Table()
        self.sub_table.show()
        self.sub_table.set_col_spacings(18)
        self.nutexpander = Gtk.Expander(label=_("Vitamins and minerals"))
        self.nutexpander.show()
        self.nutexpander.add(self.sub_table)
        self.pack_start(self.nutexpander, True, True, 0)
        for table, layout in [
            (self.main_table, MAIN_NUT_LAYOUT),
            (self.sub_table, DETAIL_NUT_LAYOUT),
        ]:
            for n, nutstuff in enumerate(layout):
                if nutstuff == SEP:
                    hs = Gtk.HSeparator()
                    table.attach(hs, 0, 2, n + start_at, n + start_at + 1, xoptions=Gtk.AttachOptions.FILL)
                    hs.show()
                    continue
                label, typ, name, properties, show_percent, unit = nutstuff
                hb = Gtk.HBox()
                permanentl = Gtk.Label()
                permanentl.set_alignment(0, 0.5)
                if typ == MAJOR:
                    permanentl.set_markup("<b>" + label + "</b>")
                elif typ == MINOR:
                    permanentl.set_markup("  " + label)
                elif typ == TINY:
                    permanentl.set_markup('  <span size="smaller">' + label + "</span>")
                if self.pressable:
                    b = Gtk.Button()
                    b.add(permanentl)
                    b.set_relief(Gtk.ReliefStyle.NONE)
                    b.connect("clicked", self.toggle_label, label, name, properties, unit)
                    hb.pack_start(b, False, True, 0)
                    self.toggles[name] = b
                else:
                    hb.pack_start(permanentl, True, True, 0)
                unit_label = Gtk.Label()
                unit_label.set_alignment(0, 0.5)
                if name != "kcal":
                    hb.pack_start(unit_label, True, True, 0)
                else:
                    # print 'pack end'
                    # hb.pack_end(unit_label,expand=True)
                    table.attach(unit_label, 1, 2, n + start_at, n + start_at + 1, xoptions=Gtk.AttachOptions.FILL)
                    unit_label.set_alignment(1, 0.5)
                    unit_label.show()
                hb.show_all()
                table.attach(hb, 0, 1, n + start_at, n + start_at + 1, xoptions=Gtk.AttachOptions.FILL)
                if show_percent == SHOW_PERCENT:
                    percent_label = Gtk.Label()
                    percent_label.modify_font(self.bold_font)
                    percent_label.set_alignment(1, 0.5)
                    percent_label.show()
                    table.attach(percent_label, 1, 2, n + start_at, n + start_at + 1, xoptions=Gtk.AttachOptions.FILL)
                self.nutrition_display_info.append(
                    {
                        "props": properties,
                        "percent_label": (show_percent == SHOW_PERCENT and percent_label),
                        "unit_label": unit_label,
                        "unit": unit,
                        "usda_rec_per_cal": (name in RECOMMENDED_INTAKE and RECOMMENDED_INTAKE[name]),
                        "box": hb,
                        "type": typ,
                    }
                )

    def set_prefs(self, prefs):
        self.prefs = prefs

    def toggle_label(self, button, label, name, properties, unit):
        if self.__toggling__:
            return
        self.__toggling__ = True
        if name != self.active_name:
            for b in list(self.toggles.values()):
                lab = b.get_children()[0]
                if b != button:
                    orig = lab.get_label()
                    if orig.find(self.active_button_markup[0]) == 0:
                        lab.set_markup_with_mnemonic(orig[len(self.active_button_markup[0]) : (-len(self.active_button_markup[1]))])
                else:
                    lab.set_markup_with_mnemonic(self.active_button_markup[0] + lab.get_label() + self.active_button_markup[1])
            self.active_name = name
            self.active_unit = unit
            self.active_label = label
            self.active_properties = properties
        else:
            self.active_name = None
            self.active_unit = None
            self.active_label = None
            lab = button.get_children()[0]
            orig = lab.get_label()
            if orig.find(self.active_button_markup[0]) == 0:
                lab.set_markup_with_mnemonic(orig[len(self.active_button_markup[0]) : (-len(self.active_button_markup[1]))])
        self.emit("label-changed")
        self.__toggling__ = False

    def make_missing_label(self):
        hb = Gtk.HBox()
        label = Gtk.Label()
        self.missingLabelLabel = label
        label.set_alignment(0, 0.5)
        b = Gtk.Button(stock=Gtk.STOCK_EDIT)
        hb.pack_start(label, True, True, 0)
        hb.pack_start(b, True, True, 0)
        b.connect("clicked", self.solidify_vapor_cb)
        b.show(), label.show(), hb.show()
        self.edit_missing_button = b
        return hb

    def set_missing_label_text(self, missing, total):
        self.missingLabelLabel.set_markup(
            '<span color="red" style="italic">' + _("""Missing nutritional information\nfor %(missing)s of %(total)s ingredients.""") % locals() + "</span>"
        )
        if missing == 0:
            self.missingLabelLabel.hide()
        else:
            self.missingLabelLabel.show()
        if missing == total:
            method = "hide"
        else:
            method = "show"
        getattr(self.main_table, method)()
        getattr(self.nutexpander, method)()
        getattr(self.yieldLabel, method)()
        getattr(self.cal_per_day_box, method)()

    def make_dv_boxes(self):
        dvLabel = Gtk.Label()
        dvLabel.set_markup('<span weight="bold" size="small">' + _("% _Daily Value") + "</span>")
        dvLabel.set_use_underline(True)
        dvLabel.set_alignment(1, 0.5)
        hb = Gtk.HBox()
        self.edit_button = Gtk.ToggleButton()
        dvLabel.set_mnemonic_widget(self.edit_button)
        i = Gtk.Image()
        i.set_from_stock(Gtk.STOCK_EDIT, Gtk.IconSize.MENU)
        self.edit_button.add(i)
        hb.pack_end(dvLabel, False, False, 0)
        self.edit_button.set_alignment(1, 0.5)
        hb.pack_end(self.edit_button, False, False, 6)
        self.edit_button.show_all()
        self.set_edit_tip()
        self.edit_button.connect("clicked", self.toggle_edit_calories_per_day)
        hb.show_all()
        self.cpd_editor = Gtk.HBox()
        cpd_ed_label = Gtk.Label(label="<i>_" + "Calories per day:" + "</i>")
        cpd_ed_label.set_use_underline(True)
        cpd_ed_label.set_use_markup(True)
        self.cpd_editor.pack_start(cpd_ed_label, True, True, 12)
        cpd_ed_label.set_alignment(1, 0.5)
        self.cpd_sb = Gtk.SpinButton()
        self.cpd_sb.set_numeric(True)
        self.cpd_sb.connect("activate", lambda *args: self.edit_button.set_active(False))
        self.cpd_sb.connect("value-changed", self.edit_calories_per_day)
        cpd_ed_label.set_mnemonic_widget(self.cpd_sb)
        adj = self.cpd_sb.get_adjustment()
        adj.lower, adj.upper = 0, 4000
        adj.step_increment, adj.page_increment = 50, 500
        self.cpd_editor.pack_start(self.cpd_sb, True, True, 0)
        # cancelb = Gtk.Button(stock=Gtk.STOCK_CANCEL)
        # okb = Gtk.Button(stock=Gtk.STOCK_OK)
        # self.cpd_editor.pack_start(cancelb,True, True,12)
        # self.cpd_editor.pack_start(okb,)
        # okb.connect('clicked',self.edit_calories_per_day)
        # cancelb.connect('clicked',lambda *args: self.cpd_editor.hide())
        return hb, self.cpd_editor

    def set_edit_tip(self):
        self.edit_button.set_tooltip_text(
            _("Percentage of recommended daily value based on %i calories per day. Click to edit number of calories per day.") % self.calories_per_day
        )

    def toggle_edit_calories_per_day(self, b):
        if b.get_active():
            self.cpd_sb.set_value(self.calories_per_day)
            self.cpd_editor.show_all()
            self.cpd_sb.grab_focus()
        else:
            self.edit_calories_per_day()
            self.cpd_editor.hide()

    def edit_calories_per_day(self, *args):
        self.cpd_sb.update()
        self.calories_per_day = self.cpd_sb.get_value()
        self.update_display()
        self.set_edit_tip()
        self.emit("calories-changed")

    def set_yields(self, n, unit="servings"):
        self.yields = n
        self.yield_unit = unit
        self.setup_yield_label()
        # self.update_display()

    def set_nutinfo(self, nutinfo):
        """Set nutrition info from a nutrition info object.

        A nutinfo object has attributes with our relevant data.
        """
        self.nutinfo = nutinfo
        if len(self.nutinfo) == 0:
            self.main_table.hide()
            self.nutexpander.hide()
            self.yieldLabel.hide()
            self.cal_per_day_box.hide()
            self.missingLabel.hide()
        else:
            self.update_display()
            vapor = self.nutinfo._get_vapor()
            if vapor:
                len(vapor)
                self.nutinfo.recursive_length()
                self.set_missing_label_text(len(vapor), self.nutinfo.recursive_length())
                self.missingLabel.show()
            else:
                self.main_table.show()
                self.nutexpander.show()
                self.yieldLabel.show()
                self.cal_per_day_box.show()
                self.missingLabel.hide()

    def update_display(self):
        """Update the display of labels based on values in nutinfo,
        adjusted by yields and calories_per_day.
        """
        for itm in self.nutrition_display_info:
            props = itm["props"]
            if isinstance(props, str):
                rawval = getattr(self.nutinfo, props) or 0
            else:
                # sum a list of properties
                rawval = sum([getattr(self.nutinfo, p) or 0 for p in props])
            if self.yields:
                rawval = float(rawval) / self.yields
            if itm["type"] != MAJOR:
                # If the item is not "MAJOR", then we hide it if the
                # rawval is 0
                if rawval == 0:
                    itm["box"].hide()
                    if itm["percent_label"]:
                        itm["percent_label"].hide()
                else:
                    itm["box"].show()
                    if itm["percent_label"]:
                        itm["percent_label"].show()
            if itm["unit_label"]:
                itm["unit_label"].set_text("%i%s" % (rawval, itm["unit"]))
            if itm["usda_rec_per_cal"] and itm["percent_label"]:
                totrec = itm["usda_rec_per_cal"] * self.calories_per_day
                if totrec:
                    percent = 100 * (float(rawval) / totrec)
                    itm["percent_label"].set_text("%i%%" % percent)

    def setup_yield_label(self):
        if self.custom_label:
            self.yieldLabel.set_markup("<b>" + self.custom_label + "</b>")
        elif self.yields:
            singular_unit = _defaults.get_pluralized_form(self.yield_unit, 1)
            self.yieldLabel.set_markup("<b>" + _("Amount per %s" % singular_unit) + "</b>")
        else:
            self.yieldLabel.set_markup("<b>" + _("Amount per recipe") + "</b>")

    def set_nutritional_info(self, info):
        """Set nutrition from a NutritionInfo or NutritionInfoList object."""
        self.nutinfo = info
        self.update_display()

    def solidify_vapor_cb(self, *args):
        self.show_druid(fix_vapor=True)

    def show_druid(self, nd=None, fix_vapor=False):
        vapor = fix_vapor and self.nutinfo._get_vapor()
        if not nd:
            if vapor:
                nd = vapor[0].__nd__
            else:
                raise Exception("No nutritional database handed to us!")
        from . import nutritionDruid

        self.ndruid = nutritionDruid.NutritionInfoDruid(nd, prefs=self.prefs, rec=self.rec)
        self.ndruid.connect("key-changed", lambda w, tpl: self.emit("ingredients-changed"))
        self.ndruid.connect("unit-changed", lambda w, tpl: self.emit("ingredients-changed"))
        if vapor:
            ings = [(v.__key__, [(v.__amt__, v.__unit__)]) for v in vapor]
            self.ndruid.add_ingredients(
                ings,
            )
        else:
            self.ndruid.setup_nutrition_index()
        self.ndruid.connect("finish", self.update_nutinfo)
        self.ndruid.show()

    def update_nutinfo(self, *args):
        self.nutinfo._reset()
        self.update_display()
        vapor = self.nutinfo._get_vapor()
        if vapor:
            self.set_missing_label_text(len(vapor), len(self.nutinfo))
            self.missingLabel.show()
        else:
            self.missingLabel.hide()
        self.emit("ingredients-changed")


if __name__ == "__main__":
    import random

    class fakenut:

        __attdict__ = {}

        def __len__(self):
            return 7

        def __getattr__(self, n):
            if n == "has_vapor":
                return self.has_vapor
            if n == "_get_vapor":
                return self._get_vapor_
            if n in self.__attdict__:
                return self.__attdict__[n]
            elif n == "kcal":
                self.__attdict__[n] = self.carb * 4 + self.sugar * 4 + self.protein * 4 + self.famono * 9 + self.fasat * 9
                return self.__attdict__[n]
            else:
                # n = random.randint(0,100)
                self.__attdict__[n] = random.randint(0, 100)
                return self.__attdict__[n]

        def has_vapor(self):
            True

        def recursive_length(self):
            return self.__len__()

        def _get_vapor_(self):
            return [("black pepper", [(1, "tsp.")]), ("red pepper", [(1, ""), (2, "c.")])]

    ni = fakenut()
    w = Gtk.Window()
    nl = NutritionLabel({})
    vb = Gtk.VBox()
    w.add(vb)
    hb = Gtk.HBox()
    vb.pack_start(hb, False, False, 0)
    hb.pack_start(nl, False, False, 0)
    vb.show()
    b = Gtk.Button("Test me")
    vb.add(b)
    nl.tt.set_tip(b, "What about this?")
    b.show()
    hb.show()
    # nl.set_yields(2)
    nl.set_nutinfo(ni)

    def display_info(w):
        print(w.active_name, w.active_unit, w.active_label)

    nl.connect("label-changed", display_info)
    nl.show()
    w.show()
    w.connect("delete-event", lambda *args: Gtk.main_quit())
    Gtk.main()
