from logic import *
import micro_widget as mw
from itertools import accumulate

NEXT_VAR_PREFIX = "next_"
def make_next_state(old_state, state):
    next_state = []
    for [var1, val1] in state:
        if var1.get_name().startswith(NEXT_VAR_PREFIX):
            real_var_name = var1.get_name()[len(NEXT_VAR_PREFIX):]
            real_var = Var(real_var_name)
            next_state = extend_sub(real_var, val1, next_state)
    for [var1, val1] in old_state:
        if not sub_get(next_state, var1):
            next_state = extend_sub(var1, val1, next_state)
    return next_state
            

def display(content_pattern, content_goal, init_goal, trace=False):
    states = list(init_goal(EMPTY_SUB))
    return [Application(content_pattern, content_goal, states[i], i, trace=trace) for i in range(len(states))]

def button_click_callback(button):
    action, window = mw.get_button_data(button)
    state = window.get_application().process_action(action)

def entry_change_callback(mw_entry):
    action, window = mw.get_entry_data(mw_entry)
    value = mw.get_entry_value(mw_entry)
    window.get_application().process_change_action(action, value)

def checkbox_change_callback(mw_checkbox):
    action, window = mw.get_checkbox_data(mw_checkbox)
    value = mw.get_checkbox_value(mw_checkbox)
    window.get_application().process_change_action(action, value)

def radiobutton_change_callback(mw_radiobutton):
    action, window = mw.get_radiobutton_data(mw_radiobutton)
    value = mw.get_radiobutton_value(mw_radiobutton)
    window.get_application().process_change_action(action, value)

def make_mw_widget(mw_window, widget):
    widget_type = widget[0]
    if widget_type == "button":
        return make_mw_button(mw_window, widget)
    elif widget_type == "label":
        return make_mw_label(mw_window, widget)
    elif widget_type == "entry":
        return make_mw_entry(mw_window, widget)
    elif widget_type == "checkbox":
        return make_mw_checkbox(mw_window, widget)
    elif widget_type == "radiobutton":
        return make_mw_radiobutton(mw_window, widget)
    else:
        raise RuntimeError(f"Unknown widget type {widget_type}.")
    
def make_mw_button(mw_window, button):
    mw_button = mw.make_button(mw_window)
    mw.set_button_click_callback(mw_button, button_click_callback)
    return mw_button

def make_mw_entry(mw_window, entry):
    mw_entry = mw.make_entry(mw_window)
    mw.set_entry_change_callback(mw_entry, entry_change_callback)
    return mw_entry

def make_mw_checkbox(mw_window, entry):
    mw_checkbox = mw.make_checkbox(mw_window)
    mw.set_checkbox_change_callback(mw_checkbox, checkbox_change_callback)
    return mw_checkbox

def make_mw_radiobutton(mw_window, entry):
    mw_radiobutton = mw.make_radiobutton(mw_window)
    mw.set_radiobutton_change_callback(mw_radiobutton, radiobutton_change_callback)
    return mw_radiobutton

def make_mw_label(mw_window, button):
    mw_label = mw.make_label(mw_window)
    return mw_label

def destroy_mw_widget(mw_widget):
    if mw.is_label(mw_widget):
        mw.destroy_label(mw_widget)
    elif mw.is_button(mw_widget):
        mw.destroy_button(mw_widget)
    elif mw.is_entry(mw_widget):
        mw.destroy_entry(mw_widget)
    elif mw.is_checkbox(mw_widget):
        mw.destroy_checkboy(mw_widget)
    elif mw.is_radiobutton(mw_widget):
        mw.destroy_radiobutton(mw_widget)
    else:
        raise RuntimeError(f"Unknown widget {mw_widget}.")


def update_mw_widget(window, mw_widget, widget):
    widget_type = widget[0] 
    if widget_type == "button":
        update_mw_button(window, widget, mw_widget)
    elif widget_type == "label":
        update_mw_label(window, widget, mw_widget)
    elif widget_type == "entry":
        update_mw_entry(window, widget, mw_widget)
    elif widget_type == "checkbox":
        update_mw_checkbox(window, widget, mw_widget)
    elif widget_type == "radiobutton":
        update_mw_radiobutton(window, widget, mw_widget)
    else:
        raise RuntimeError(f"Unknown widget type {widget_type}.")

def update_mw_button(window, button, mw_button): 
    x = button[1]
    y = button[2]
    text = button[3]
    action = button[4]
    mw.set_button_x(mw_button, x)
    mw.set_button_y(mw_button, y)
    mw.set_button_data(mw_button, [action, window])
    if mw.get_button_text(mw_button) != str(text):
        mw.set_button_text(mw_button, str(text))

def update_mw_checkbox(window, checkbox, mw_checkbox): 
    x = checkbox[1]
    y = checkbox[2]
    value = checkbox[3]
    action = checkbox[4]
    mw.set_checkbox_x(mw_checkbox, x)
    mw.set_checkbox_y(mw_checkbox, y)
    mw.set_checkbox_data(mw_checkbox, [action, window])
    if mw.get_checkbox_value(mw_checkbox) != str(value):
        mw.set_checkbox_value(mw_checkbox, str(value))

def update_mw_radiobutton(window, radiobutton, mw_radiobutton): 
    x = radiobutton[1]
    y = radiobutton[2]
    value = radiobutton[3]
    action = radiobutton[4]
    mw.set_radiobutton_x(mw_radiobutton, x)
    mw.set_radiobutton_y(mw_radiobutton, y)
    mw.set_radiobutton_data(mw_radiobutton, [action, window])
    if mw.get_radiobutton_value(mw_radiobutton) != str(value):
        mw.set_radiobutton_value(mw_radiobutton, str(value))

def update_mw_entry(window, entry, mw_entry): 
    x = entry[1]
    y = entry[2]
    value = entry[3]
    action = entry[4]
    mw.set_entry_x(mw_entry, x)
    mw.set_entry_y(mw_entry, y)
    mw.set_entry_data(mw_entry, [action, window])
    if mw.get_entry_value(mw_entry) != str(value):
        mw.set_entry_value(mw_entry, str(value))

def update_mw_label(window, label, mw_label): 
    x = label[1]
    y = label[2]
    text = label[3]
    mw.set_label_x(mw_label, x)
    mw.set_label_y(mw_label, y)
    if mw.get_label_text(mw_label) != str(text):
        mw.set_label_text(mw_label, str(text))
        
class Window:
    def __init__(self, application):
        self.application = application
        self.old_mw_widgets = []
        self.old_content = []
        self.mw_window = mw.make_window()

    def get_application(self):
        return self.application

    def get_mw_window(self):
        return self.mw_window

    def ensure_mw_widget_types(self, widgets):
        old_mw_widgets = self.old_mw_widgets
        old_widgets = self.old_content
        mw_widgets = []
        for i in range(max(len(widgets), len(old_widgets))):
            widget = widgets[i] if i < len(widgets) else None 
            old_widget = old_widgets[i] if i < len(old_widgets) else None
            if widget == None:
                destroy_mw_widget(old_mw_widgets[i])
            elif old_widget == None:
                mw_widgets.append(make_mw_widget(self.mw_window, widget))
            else:
                widget_type = widget[0]
                old_widget_type = old_widget[0]
                if widget_type != old_widget_type:
                    self.destroy_mw_widget(old_mw_widgets[i])
                    mw_widgets.append(self.make_mw_widget(widget))
                else:
                    mw_widgets.append(old_mw_widgets[i])
        self.old_mw_widgets = mw_widgets
                    
                   
    def update(self, content):
        self.ensure_mw_widget_types(content)
        old_content = self.old_content
        old_mw_widgets = self.old_mw_widgets
        for i in range(len(content)):
            widget = content[i]
            mw_widget = old_mw_widgets[i]
            update_mw_widget(self, mw_widget, widget)
        self.old_content = content

    def destroy(self):
        mw.destroy_window(self.mw_window)


def get_window_id_string(app_id, win_id, win_count):
    string = ""
    if app_id != None:
        string += f" {app_id}"
    if win_count > 1:
        string += f" {win_id}"
    return string
        
        
class Application:
    def __init__(self, content_def, content_goal, init_state, identifier, trace=False):
        self.content_def = content_def
        self.content_goal = content_goal
        self.state = init_state
        self.ignore_changes = False
        self.trace = trace
        self.windows = []
        self.identifier = identifier
        self.update()


    def start_ignore_changes(self):
        self.ignore_changes = True

    def stop_ignore_changes(self):
        self.ignore_changes = False

    def should_ignore_changes(self):
        return self.ignore_changes

    def get_state(self):
        return self.state

    def set_state(self, state):
        self.state = state
        self.update()

    def create_windows(self, win_contents):
        self.windows = [Window(self) for content in win_contents]

    def get_contents(self):
        content_def = self.content_def
        content_goal = self.content_goal
        state = self.state
        contents = [reify(content_def, content_sub) for content_sub in content_goal(state)]
        if self.trace:
            print(f"State{" " + self.identifier if self.identifier != None else ""}:", state)
        return contents
         
    def update_windows(self, win_contents):
        old_windows = self.windows
        
        new_windows = []
        for i in range(max(len(win_contents), len(old_windows))):
            if i >= len(win_contents):
                window = old_windows[i]
                window.destroy()
            else:
                if i >= len(old_windows):
                    window = Window(self)
                else:
                    window = old_windows[i]
                new_windows.append(window)
                content = win_contents[i]
                if self.trace:
                    print(f"Window{get_window_id_string(self.identifier, i, len(win_contents))}:", content)
                window.update(content)
        self.windows = new_windows

        
    def update(self):
        self.start_ignore_changes()
        try:
            win_contents = self.get_contents()
            self.update_windows(win_contents)
        finally:
            self.stop_ignore_changes()

    def process_action(self, action, sub=None):
        if not self.should_ignore_changes():
            if sub == None:
                sub = self.get_state()          
            next_state_sub = list(action(sub))
            assert len(next_state_sub) == 1
            next_state = make_next_state(self.get_state(), next_state_sub[0])
            self.set_state(next_state)

    def process_change_action(self, action, value):
        if not self.should_ignore_changes():
            state = self.get_state()
            state_with_value = extend_sub(changed_value, value, state)
            self.process_action(action, state_with_value)


def display(content_pattern, content_goal=taut, init_goal=taut, trace=False):
    states = list(init_goal(EMPTY_SUB))
    for i in range(len(states)):
        state = states[i]
        Application(content_pattern, content_goal, state, i if len(states) > 1 else None, trace=trace)
        
  

# Popisek
def label_props(widget, x, y, text):
    return eq(widget, ["label", x, y, text])
                
def label(widget):
    return label_props(widget, Var(), Var(), Var())

def label_x(widget, x):
    return label_props(widget, x, Var(), Var())

def label_y(widget, y):
    return label_props(widget, Var(), y, Var())

def label_text(widget, text):
    return label_props(widget, Var(), Var(), text)


# Tlačítko
def button_props(widget, x, y, text, goal):
    return eq(widget, ["button", x, y, text, goal])
                
def button(widget):
    return button_props(widget, Var(), Var(), Var(), Var())

def button_x(widget, x):
    return button_props(widget, x, Var(), Var(), Var())

def button_y(widget, y):
    return button_props(widget, Var(), y, Var(), Var())

def button_text(widget, text):
    return button_props(widget, Var(), Var(), text, Var())

def button_action(widget, action):
    return  button_props(widget, Var(), Var(), Var(), action)


# Textové pole
def entry_props(widget, x, y, value, action):
    return eq(widget, ["entry", x, y, value, action])
                
def entry(widget):
    return entry_props(widget, Var(), Var(), Var(), Var())

def entry_x(widget, x):
    return entry_props(widget, x, Var(), Var(), Var())

def entry_y(widget, y):
    return entry_props(widget, Var(), y, Var(), Var())

def entry_value(widget, value):
    return entry_props(widget, Var(), Var(), value, Var())

def entry_action(widget, action):
    return  entry_props(widget, Var(), Var(), Var(), action)


# Zaškrtávací pole
def checkbox_props(widget, x, y, value, action):
    return eq(widget, ["checkbox", x, y, value, action])
                
def checkbox(widget):
    return checkbox_props(widget, Var(), Var(), Var(), Var())

def checkbox_x(widget, x):
    return checkbox_props(widget, x, Var(), Var(), Var())

def checkbox_y(widget, y):
    return checkbox_props(widget, Var(), y, Var(), Var())

def checkbox_value(widget, value):
    return checkbox_props(widget, Var(), Var(), value, Var())

def checkbox_action(widget, action):
    return  checkbox_props(widget, Var(), Var(), Var(), action)


# Přepínač
def radiobutton_props(widget, x, y, value, action):
    return eq(widget, ["radiobutton", x, y, value, action])
                
def radiobutton(widget):
    return radiobutton_props(widget, Var(), Var(), Var(), Var())

def radiobutton_x(widget, x):
    return radiobutton_props(widget, x, Var(), Var(), Var())

def radiobutton_y(widget, y):
    return radiobutton_props(widget, Var(), y, Var(), Var())

def radiobutton_value(widget, value):
    return radiobutton_props(widget, Var(), Var(), value, Var())

def radiobutton_action(widget, action):
    return radiobutton_props(widget, Var(), Var(), Var(), action)

# Ovládací prvek
def widget_x(widget, x):
    return disj(button_x(widget, x),
                label_x(widget, x),
                entry_x(widget, x),
                checkbox_x(widget, x),
                radiobutton_x(widget, x))

def widget_y(widget, y):
    return disj(button_y(widget, y),
                label_y(widget, y),
                entry_y(widget, y),
                checkbox_y(widget, y),
                radiobutton_y(widget, y))

def widget_position(widget, x, y):
    return conj(widget_x(widget, x),
                widget_y(widget, y))

def widget_text(widget, text):
    return disj(button_text(widget, text),
                label_text(widget, text))

def widget_value(widget, value):
    return disj(entry_value(widget, value),
                checkbox_value(widget, value),
                radiobutton_value(widget, value))

def widget_action(widget, action):
    return disj(entry_action(widget, action),
                checkbox_action(widget, action),
                radiobutton_action(widget, action),
                button_action(widget, action))

def widget_below(widget1, widget2):
    x = Var()
    y = Var()
    y2 = Var()
    return conj(widget_position(widget2, x, y),
                add(y, 40, y2),
                widget_position(widget1, x, y2))

changed_value = Var("changed_value")


content = Var("content")
widget = Var("widget")
widget1 = Var("widget1")
widget2 = Var("widget2")
widget3 = Var("widget3")
value = Var("value")


def make_next_var(var):
    return Var("next_" + var.name)

next_x = make_next_var(x)
next_y = make_next_var(y)
next_z = make_next_var(z)
next_value = make_next_var(value)


# Jednoduché počítadlo
"""
display([widget],
        conj(button(widget),
             widget_position(widget, 0, 0),
             widget_text(widget, x),
             widget_action(widget, add(x, 1, next_x))),
        eq(x, 0),
        trace=True)
"""

# Textové pole:
"""
display([widget],
        conj(entry(widget),
             widget_position(widget, 0, 0),
             widget_value(widget, value),
             widget_action(widget, eq(changed_value, next_value))),
        eq(value, ""),
        trace=True)
"""

# Počítadlo
"""
display([widget1, widget2],
        conj(label(widget1),
             widget_position(widget1, 0, 0),
             widget_text(widget1, x),
             button(widget2),
             widget_below(widget2, widget1),
             widget_text(widget2, "+"),
             button_action(widget2, add(x, 1, next_x))),
        eq(x, 0))
"""


# Přepínače
"""
display([widget1, widget2],
        conj(radiobutton_props(widget1, 0, 0, x, conj(eq(True, next_x), eq(False, next_y))),
             radiobutton_props(widget2, 20, 0, y, conj(eq(False, next_x), eq(True, next_y)))),
        conj(eq(x, True), eq(y, False)))
"""


# Zaškrtávací pole
"""
display([widget1, widget2],
        conj(checkbox_props(widget1, 0, 0, x, eq(changed_value, next_x)),
             checkbox_props(widget2, 20, 0, y, eq(changed_value, next_y))),
        conj(eq(x, True), eq(y, False)))
"""

# Popisek opakující zadaný text a tlačítko pro reset:
"""
display([widget1, widget2, widget3],
        conj(entry(widget1),
             widget_position(widget1, 0, 0),
             widget_value(widget1, x),
             entry_action(widget1, eq(changed_value, next_x)),
             label(widget2),
             widget_below(widget2, widget1),
             widget_text(widget2, x),
             button(widget3),
             widget_below(widget3, widget2),
             widget_text(widget3, "Reset"),
             button_action(widget3, eq("", next_x))),
        disj(eq(x, "")))
"""
# Skrývání prvku
"""
display(content,
        conj(button(widget1),
             widget_position(widget1, 0, 0),
             disj(conj(eq(x, 0), widget_text(widget1, "show"), button_action(widget1, eq(1, next_x)), eq(content, [widget1])),
                  conj(eq(x, 1),
                       widget_text(widget1, "hide"),
                       button_action(widget1, eq(0, next_x)),
                       eq(content, [widget1, widget2]),
                       label(widget2),
                       widget_below(widget2, widget1),
                       label_text(widget2, "Ahoj")))),
        eq(x, 0),
        trace=True)
"""
# Více aplikací i oken:
"""
display([widget1],
        conj(widget_position(widget1, 0, 0),
             widget_text(widget1, x),
             disj(conj(button(widget1), button_action(widget1, add(x, 1, next_x))),
                  label(widget1))),
        disj(eq(x, 0), eq(x, 1)),
        trace=True)
"""

# Otevírání a zavírání pomocného okna
"""
display([widget1],
        conj(button(widget1),
             widget_position(widget1, 0, 0),
             disj(conj(widget_text(widget1, "open"),
                       button_action(widget1, eq(next_x, 1))),
                  conj(eq(x, 1),
                       conj(widget_text(widget1, "close"),
                       button_action(widget1, eq(next_x, 0)))))),
        eq(x, 0))
"""
