# progress tab shows the current status of a order and provides 
# an interface to modify or cancel a order

from lib import AsyncTk, update
from lib import Ticket
from lib import MenuWidget, WidgetType, MenuType
from lib import LabelButton
from lib import ScrollFrame
from lib import WidgetCache
from lib import TicketType
from lib import ToggleSwitch
from lib import TICKET_COMPLETE, TICKET_QUEUED
from lib import Ticket
from .order import Order
from .order_display import EditOptions
from .checkout_display import ChangeCalculator

from operator import itemgetter
from collections import UserList
from tkinter import ttk
import functools
import tkinter as tk
import asyncio
import decimal
import logging


def alert(message):
    logging.getLogger("main.POS.gui.stdout").info(f"ALERT - {message}")

# essentially the same as lib.Ticket
class MutableTicket(UserList, metaclass=TicketType):
    
    def __init__(self, ticket=None):
        super().__init__(ticket[:8])        

    @property
    def total(self):
        result = self.price
        for option in \
                (opt for opt in self.selected_options
                if opt in self.options):
            result += self.options[option]
        return result

    def create_ticket(self, addon1, addon2):
        self.data.append(addon1)
        self.data.append(addon2)
        return self
        

class ItemEditorType(MenuWidget):

    null_item = MutableTicket(("", "", 0, {}, "", 0, [], {}))

    def item_names(self, category):
        try:
            return list(self.menu_items[category].keys())
        except KeyError:
            return list()
    
    def item_lookup(self, category, name):
        item = self.menu_items[category][name]
        return item["Price"], item["Options"]

    def __call__(self, parent, **kwargs):
        result = (super().__call__(parent, **kwargs),
                super().__call__(parent, **kwargs),
                super().__call__(parent, **kwargs))
        return result


class ItemEditor(metaclass=ItemEditorType, device="POS"):
    font = ("Courier", 14)
    style = None

    def __new__(cls, parent, **kwargs):
        if cls.style is None:
            cls.style = ttk.Style(parent)
            cls.style.configure("ItemEditor.TCombobox", 
                    font=cls.font)
            cls.style.map("ItemEditor.TCombobox",
                    fieldbackground=[("readonly", "white")],
                    selectbackground=[("readonly", "white")],
                    selectforeground=[("readonly","black")])
        return super().__new__(cls)
        
    def __init__(self, parent, **kwargs):
        self.removed = True
        self._ticket = None
        self.category = None
        self.item_selector = ttk.Combobox(parent,
                state="readonly",
                style="ItemEditor.TCombobox",
                font=self.font,
                width=type(self).longest_item,
                postcommand=self.post_command)
        
        self.edit_options = LabelButton(parent, "Options",
                width=7,
                font=self.font,
                command=self._options_callback(parent))
        
        self.remove_item = LabelButton(parent, "Remove",
                width=7,
                font=self.font,
                bg="red",
                command=self._remove_callback)

        self.add_item = LabelButton(parent, "",
                width=7,
                font=self.font,
                command=self._add_callback)

    def grid(self, row=None, column=None, isaddon=True):
        if isaddon:
            self.item_selector.grid(row=row, column=column +1, sticky="nswe", padx=2, pady=1)
            self.add_item.grid(row=row, column=column +1, sticky="nswe", padx=2, pady=1)
        else:
            self.item_selector.grid(row=row, column=column, columnspan=2, sticky="nswe", padx=2, pady=1)
            self.add_item.grid(row=row, column=column, columnspan=2, sticky="nswe", padx=2, pady=1)
        self.edit_options.grid(row=row, column=column +3, sticky="nswe", padx=2, pady=1)
        self.remove_item.grid(row=row, column=column +4, sticky="nswe", padx=2, pady=1)

    def send_alert(self):
        if self.ticket.parameters.get("register", False):
            alert(f"'{self.ticket.name}' may have been completed")
    
    def post_command(self):
        self.send_alert()
        self.ticket.selected_options.clear()
    
    @property
    def ticket(self):
        return self._ticket
    
    @ticket.setter
    def ticket(self, value):
        self.category = value.category
        self.item_selector["values"] = type(self).item_names(value.category)
        self.item_selector.set(value.name)
        self.add_item["text"] = value.category
        
        if value.name:
            self._add_callback()
        else:
            self._remove_callback()
        self._ticket = value

    def get(self):
        if self.removed or self.ticket is None:
            return type(self).null_item
        
        item_name = self.item_selector.get()
        if item_name:
            self.ticket.name = item_name
            self.ticket.price, self.ticket.options = \
                     type(self).item_lookup(self._ticket.category, item_name)
            self.ticket.parameters["status"] = None
        else:
            self.ticket.price = 0
            self.ticket.selected_options.clear()
            self.ticket.parameters.clear()
        return self.ticket

    def _remove_callback(self, *args):
        self.removed = True
        if self.ticket is not None:
            self.send_alert
        self.item_selector.lower()
        self.edit_options.grid_remove()
        self.remove_item.grid_remove()
        self.add_item.lift()
    
    def _add_callback(self, *args):
        self.removed = False
        self.item_selector.lift()
        self.edit_options.grid()
        self.remove_item.grid()
        self.add_item.lower()

    def _options_callback(self, parent):
        def inner(*args):
            EditOptions(parent, self.get())
        return inner
    
    def destroy(self, *args):
        self.item_selector.destroy()
        self.edit_options.destroy()
        self.remove_item.destroy()
        self.add_item.destroy()
    

class TicketEditor(tk.Frame, metaclass=MenuType):
    
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)
        self.grid_columnconfigure(1, weight=1)
        ttk.Separator(self).grid(row=0, column=0, columnspan=5, sticky="nswe", padx=2, pady=5)
        self.item_editors = item, addon1, addon2 = ItemEditor(self)
        
        # prevent frame from 
        tk.Label(self, width=2, font=ItemEditor.font).grid(row=2, column=0, sticky="nswe", padx=2)
        item.grid(row=1, column=0, isaddon=False)
        addon1.grid(row=2, column=0)
        addon2.grid(row=3, column=0)
        self.is_gridded = True
    
    def total(self):    
        items = (self.item_editors[0].get(),
                self.item_editors[1].get(),
                self.item_editors[2].get())
        return AsyncTk().forward("get_total", *items)
       
    def grid(self, **kwargs):
        super().grid(**kwargs)
        self.is_gridded = True
    
    def grid_remove(self):
        super().grid_remove()
        self.is_gridded = False

    @property
    def removed(self):
        return all(item.removed for item in self.item_editors)

    def set(self, *items):
        for i, editor in enumerate(self.item_editors):
            editor.ticket = items[i]
            
    def get(self):
        ticket = MutableTicket(self.item_editors[0].get())
        ticket.data.extend((self.item_editors[1].get(), self.item_editors[2].get()))
        return ticket


class EditorCalculator(ChangeCalculator, device="POS"):
    font = ("Courier", 14)

    def update(self, difference):
        change = difference - self.cash
        if change > 0 or difference == 0:
            self.change_due.set("- - -")
        else:
            self.change_due.set("{:.2f}".format(change / 100))


class TicketEditorFrame(tk.Frame):
            
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs) 
        self.widgets = WidgetCache(TicketEditor, self, initial_size=4)
        
        tk.Label(self, width=3, font=ItemEditor.font).grid(row=0, column=0, sticky="nswe", padx=2, ipadx=2)
        self.calculator_frame = tk.Frame(self, bd=2, relief=tk.RIDGE)
        self.calculator = EditorCalculator(self.calculator_frame)
        self._difference = tk.StringVar(self)

        self.difference_label = tk.Label(self.calculator_frame, text="Difference ", font=ItemEditor.font)
        self.difference_entry = tk.Entry(self.calculator_frame, 
                textvariable=self._difference,
                font=ItemEditor.font,
                state=tk.DISABLED,
                disabledforeground="black",
                disabledbackground="white",
                width=10)
        
        self.difference_label.grid(row=0, column=0, sticky="w")
        self.difference_entry.grid(row=0, column=1, sticky="w")
        self.calculator.grid(row=1, column=0, sticky="w", columnspan=2)

        self.calculator_frame.grid(row=0, column=1, sticky="nswe", columnspan=2, pady=2, padx=2)
        self.ticket_no = None
        self.orderprogress = None
        self.is_gridded = False
        self.original_total = None

    
    @property
    def difference(self):
        try:
            result = self._difference.get()
            result = decimal.Decimal(result).quantize(decimal.Decimal(".01"))
            return int(result * 100)
        except:
            return 0

    @difference.setter
    def difference(self, value):
        self._difference.set("{:.2f}".format(value / 100))

    def set(self, ticket_no, index, orderprogress):
        self.orderprogress = orderprogress
        
        self.ticket_no = int(ticket_no)
        self.original_total, order_list = AsyncTk().forward(
                "get_order_info", self.ticket_no, "total", "items")
        self.widgets.realloc(len(order_list))

        for i, ticket in enumerate(order_list):
            item, addon1, addon2 = (MutableTicket(ticket),
                    MutableTicket(ticket.addon1),
                    MutableTicket(ticket.addon2))
    
            # combobox needs to know what items to show by looking up
            # the item's category
            self.widgets[i].set(*self.set_category(item, addon1, addon2))
            self.widgets[i].grid(row=i + 1, column=1, columnspan=4, sticky="nswe")
    
        # remove excess cached widgets
        for ticket in self.widgets[len(order_list):len(self.widgets)]: 
            ticket.grid_remove()


    @staticmethod
    def set_category(item, addon1, addon2):
        sides = "Sides"
        drinks = "Drinks"
        if item.category in ItemEditor.two_sides:
            addon1.category = sides
            addon2.category = sides
        elif item.category in ItemEditor.no_addons:
            addon1.category = ""
            addon2.category = ""
        else:
            addon1.category = sides
            addon2.category = drinks
        return item, addon1, addon2
    
    def grid(self, **kwargs):
        self.is_gridded = True
        super().grid(**kwargs)

    def grid_remove(self):
        self.is_gridded = False
        super().grid_remove()

    def create_order(self):
        return [widget.get() for widget in self.widgets if widget.is_gridded]
      
    @update
    def update(self):
        # no point updating if no one can see it.
        if not self.is_gridded:
            return
    
        self.difference = sum(widget.total()
                for widget in self.widgets
                    if widget.is_gridded) - self.original_total
        self.calculator.update(self.difference)


class ConfirmationFrame(tk.Frame):
    
    def __init__(self, parent, first="Confirm", font=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.confirm_bt = LabelButton(self, "Confirm", width=7, font=font, bg="green")
        self.return_bt = LabelButton(self, "Return", width=7, font=font)
        confirm_grid_info = {"row":0, "column":1,  "sticky":"nswe", "padx":2}
        return_grid_info = {"row":0, "column": 1,  "sticky":"nswe", "padx":2}

        if first == "Confirm":
            confirm_grid_info["column"] = 0
        else:
            return_grid_info["column"] = 0
        
        self.confirm_bt.grid(**confirm_grid_info)
        self.return_bt.grid(**return_grid_info)

    def set_confirm(self, command):
        self.confirm_bt.command = command
        
    def set_return(self, command):
        self.return_bt.command = command

class ProgressBar(tk.Frame):

    def __init__(self, parent, **kwargs):
        super().__init__(parent)
        self._status = tk.IntVar(self)
        self._bar = ttk.Progressbar(parent, variable=self._status, **kwargs)
        
    def grid(self, cnf={}, **kwargs):
        super().grid(cnf, **kwargs)
        self._bar.grid(cnf, **kwargs)
        self._bar.lift()

    @property
    def status(self):
        return self._status.get()

    @status.setter
    def status(self, value):
        self._status.set(value)

class OrderProgress(tk.Frame, metaclass=MenuWidget, device="POS"):
    font=("Courier", 14)
    editor = None

    def __new__(cls, parent, **kwargs):
        if cls.editor is None:
            cls.editor = TicketEditorFrame(parent)
            cls.editor.update()
        return super().__new__(cls)


    @classmethod
    def set_keypress_bind(cls, *args, **kwargs):
        cls.editor.calculator.price_input.set_keypress_bind(*args, **kwargs)

    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)
        self.grid_columnconfigure(2, weight=1)
        self.original_order = None
        self.ticket_no = tk.Label(self, font=self.font, relief=tk.SUNKEN, bd=1, bg="white")
        self.index = None
        self.completed = False
        self.is_gridded = False
        
        self.progress = ProgressBar(self)
        self.cancel_button = LabelButton(self, "Cancel", width=7, font=self.font, bg="red", command=self.on_cancel)
        self.modify_button = LabelButton(self, "Modify", width=7, font=self.font, command=self.on_modify)
        
        self.confirm_cancel = ConfirmationFrame(self, font=self.font)
        self.confirm_cancel.set_confirm(self.on_cancel_confirm)
        self.confirm_cancel.set_return(self.on_cancel_return)
        
        self.confirm_modify = ConfirmationFrame(self, first="Return", font=self.font)
        self.confirm_modify.set_confirm(self.on_modify_confirm)
        self.confirm_modify.set_return(self.on_modify_return)

        ttk.Separator(self, orient=tk.HORIZONTAL).grid(
                row=0, column=0,
                columnspan=5,
                sticky="we",
                pady=2)

        self.ticket_no.grid(row=1, column=0,
                sticky="nswe",
                padx=2)
        self.progress.grid(row=1, column=1,
                sticky="nswe",
                columnspan=2,
                padx=2)
        
        self.modify_button.grid(row=1, column=3, 
                padx=2,
                sticky="nswe")
        
        self.cancel_button.grid(row=1, column=4,
                padx=2,
                sticky="nswe")
        
        self.confirm_cancel.grid(row=1, column=3,
                columnspan=2,
                sticky="nswe")
        
        self.modify_button.lift()
        self.cancel_button.lift()
    

    def _update(self, ticket_no):
        ticket_no = int(ticket_no)
        self.ticket_no["text"] = "{:03d}".format(ticket_no)
        self.progress.status = status = AsyncTk().forward("get_order_status", ticket_no)
        if status == 100:
            self.modify_button.deactivate()
            self.cancel_button.deactivate()

        else:
            self.modify_button.activate()
            self.cancel_button.activate()
        
        if self.editor.grid_info().get("in") != self:
            self.confirm_modify.lower()

    def on_cancel(self):
        items, = AsyncTk().forward("get_order_info", int(self.ticket_no["text"]), "items")
        names = []
        for ticket in items:
            for item in (ticket, ticket.addon1, ticket.addon2):
                if item.parameters.get("register", False):
                    names.append(f"'{item.name}'")
        alert(f"{', '.join(names)} may have been completed.")
        self.confirm_cancel.lift()

    def on_modify(self, *args):
        self.confirm_modify.grid(row=1,
                column=3,
                columnspan=2,
                sticky="nswe")
    
        self.confirm_modify.lift()
        self.editor.set(str(int(self.ticket_no["text"])), self.index + 1, self)
        self.editor.grid(row=2, column=0, columnspan=5, sticky="nswe", pady=2, in_=self)
        self.editor.lift()

    def on_cancel_confirm(self):
        self.cancel_button.lift()
        self.modify_button.lift()
        ticket_no = int(self.ticket_no["text"])

        #uncomment to prevent cancellation of completed items.
        #original, = AsyncTk().forward("get_order_info", ticket_no, "items")
        #modified_order = []
        #for ticket in original:
        #    items = ticket[:6], ticket[6], ticket[7]        
        #    for item in items:
        #        if item[5].get("status") == TICKET_COMPLETE:
        #            _item = MutableTicket(item)
        #            null_ticket = MutableTicket(("", "", 0, {}, [], {}))
        #            _item.data.extend((null_ticket, null_ticket))
        #            modified_order.append(_item)
        #
        #
        #if modified_order:
        #    names = ", ".join(f"'{ticket.name}'" for ticket in modified_order)
        #    logging.getLogger("main.POS.gui.stdout").info(
        #        f"{names} cannot be removed. Modifying ticket instead..."
        #    )
        #    return AsyncTk().forward("modify_order", ticket_no, modified_order)

        AsyncTk().forward("cancel_order", ticket_no)
        if self.editor.ticket_no == ticket_no:
            self.editor.grid_remove()
            self.confirm_modify.grid_remove()

    def on_cancel_return(self):
        self.cancel_button.lift()
        self.modify_button.lift()
    
    def on_modify_confirm(self, *args):
        AsyncTk().forward("modify_order",
                self.editor.ticket_no,
                self.editor.create_order(),
                self.editor.calculator.cash_given.get(),
                self.editor.calculator.change_due.get(),
                self.editor.difference)
     
        self.editor.grid_remove()
        self.confirm_modify.grid_remove()

    def on_modify_return(self):
        self.editor.grid_remove()
        self.editor.calculator.cash_given.set("0.00")
        self.confirm_modify.grid_remove()
        self.modify_button.lift()
        self.cancel_button.lift()

    def grid(self, **kwargs):
        self.is_gridded = True
        super().grid(**kwargs)
    
    def grid_remove(self):
        if self.editor.grid_info().get("in") == self:
            self.editor.grid_remove()
            self.confirm_modify.grid_remove()
        super().grid_remove()


class ProgressFrame(ScrollFrame):

    def __init__(self, parent, initial_size=10, show_completed_num=2, **kwargs):
        super().__init__(parent, **kwargs)
        self.interior.grid_columnconfigure(1, weight=1)
        self.widget_cache = WidgetCache(OrderProgress, self.interior, initial_size=initial_size)
        self.show_completed_num = show_completed_num
        self.set_keypress_bind = OrderProgress.set_keypress_bind
    
    def editor(self):
        return OrderProgress.editor

    def keybind_condition(self, notebook, tab):
        def func():
            return (notebook.current() == tab
                and OrderProgress.editor.is_gridded)
        return func
        
    def on_enter(self):
        editor = OrderProgress.editor
        assert editor.is_gridded
        frame = editor.grid_info()["in"]
        assert isinstance(frame, OrderProgress)
        frame.on_modify_confirm()

    @update
    def update_order_status(self):
        order_queue = AsyncTk().forward("order_queue")
        if order_queue is None:
            return

        queue_size = len(order_queue)
        self.widget_cache.realloc(queue_size)
        cache_size = len(self.widget_cache)

        for i, ticket in enumerate(order_queue):
            self.widget_cache[i]._update(ticket)
            self.widget_cache[i].grid(row=(cache_size - i) * 2, column=0, columnspan=5, sticky="we", padx=5)
            self.widget_cache[i].index = (cache_size - i) * 2

        # remove excess cached widgets
        for widget in self.widget_cache[queue_size:cache_size]:
            widget.grid_remove()