import display
import main
import utils
import random
import custom_shapes
import time
import buttons
import easydraw

STATUS_BAR_HEIGHT = 40
PROGRESS_BAR_HEIGHT = 10
TEXT_BLOCK_HEIGHT = 55
CONTROL_TYPES = ["VERTICAL_SLIDER", "ROTARY_SWITCH", "BINARY_SWITCH"]

INTRO_STATE = {
    "totalTime": 5,
    "remainingTime": 5,
    "assignment": "Starting the game in 5 seconds",
}
INTRO_HEALTH_INFO = {
    "death_limit": 0.0,
    "health": 50.0
}


class GameState:
    def __init__(self, grid):
        self.state = INTRO_STATE
        self.health_info = INTRO_HEALTH_INFO
        self.grid = grid
        self.control_mapping = {}
        seen_switch = False
        seen_joystick = False
        self.intro = True
        self.first_command_received = False
        for i, control in enumerate(grid):
            control_type = control["type"]
            value = {"id": i, "name": control["name"], "type": control_type}
            if control_type == CONTROL_TYPES[2]:
                if seen_switch:
                    self.control_mapping[buttons.BTN_A] = value
                else:
                    seen_switch = True
                    self.control_mapping[buttons.BTN_B] = value
            elif control_type in CONTROL_TYPES[:2]:
                if seen_joystick:
                    self.control_mapping[buttons.BTN_LEFT] = value
                    self.control_mapping[buttons.BTN_RIGHT] = value
                else:
                    seen_joystick = True
                    self.control_mapping[buttons.BTN_UP] = value
                    self.control_mapping[buttons.BTN_DOWN] = value
        print("control mapping:", self.control_mapping)

    def update_grid(self, grid):
        self.grid = grid
        self.draw_game_screen()

    def update_assignment(self, assignment):
        if self.first_command_received:
            self.intro = False
        else:
            self.first_command_received = True
        self.state["assignment"] = assignment["text"]
        self.state["totalTime"] = assignment["time"]
        self.state["remainingTime"] = int(assignment["time"])
        self.trigger_counter()

    def update_health_info(self, health_info):
        self.health_info = health_info
        self.draw_game_screen()


    def handle_button_press(self, button):
        if not self.intro and self.control_mapping:
            command = self.control_mapping[button]
            index = command["id"]
            if button == buttons.BTN_A or button == buttons.BTN_B:
                self.toggle_button(index)
            else:
                self.change_control_value(index, button == buttons.BTN_DOWN or button == buttons.BTN_RIGHT)

            self.draw_game_screen()
            return {"name": command["name"], "value": self.grid[index]["value"]}
        else:
            print("Ignoring button press, game not started yet")

    def toggle_button(self, index):
        self.grid[index]["value"] = not self.grid[index]["value"]

    def change_control_value(self, index, higher):
        control = self.grid[index]
        if higher:
            self.grid[index]["value"] = min(control["value"] + 1, control["max"])
        else:
            self.grid[index]["value"] = max(control["value"] - 1, control["min"])

    def draw_game_screen(self):
        display.drawFill(utils.BG_DARK)

        draw_status_bar(self.state["remainingTime"], self.state["assignment"])

        draw_health_bar(self.health_info["death_limit"], self.health_info["health"])

        def get_intro_name(control_type, seen_switch, seen_joystick):
            if control_type == CONTROL_TYPES[2]:
                return "Toggle with A" if seen_switch else "Toggle with B"
            elif control_type in CONTROL_TYPES[:2]:
                return "Use Joystick Right/Left" if seen_joystick else "Use Joystick Up/Down"
            else:
                raise Exception("Unknown control_type '%s' for get_intro_name(%s, %s, %s)" % (control_type, control_type, seen_switch, seen_joystick))

        seen_switch = False
        seen_joystick = False
        for i, control in enumerate(self.grid):
            draw_control(control, i, get_intro_name(control["type"], seen_switch, seen_joystick) if self.intro else control["name"])
            if control["type"] == CONTROL_TYPES[2]:
                seen_switch = True
            if control["type"] in CONTROL_TYPES[:2]:
                seen_joystick = True

        display.flush()

    def trigger_counter(self):
        if self.first_command_received:
            self.state["remainingTime"] = max(self.state["remainingTime"] - 1, 0)
            self.draw_game_screen()


def text_centered(x, y, width, text, font, color):
    if font:
        text_width = display.getTextWidth(text, font)
    else:
        text_width = display.getTextWidth(text)
    pos_x = int((width - text_width) / 2)
    if font:
        display.drawText(x + pos_x, y, text, color, font)
    else:
        display.drawText(pos_x, y, text, color)


def draw_assignment(assignment, x, y, width, height):
    ASSIGNMENT_FONT = utils.ARCADE_FONT[12]
    HORIZONTAL_PADDING = 10
    LINE_SPACING = 4

    split_assignment = assignment.upper()

    assignment_lines = easydraw.lineSplit(
        split_assignment, width=width, font=ASSIGNMENT_FONT
    )

    line_heights = sum(
        [display.getTextHeight(line, ASSIGNMENT_FONT) for line in assignment_lines]
    )

    total_height = line_heights + (len(assignment_lines) - 1) * LINE_SPACING

    for i, line in enumerate(assignment_lines):
        text_centered(
            x + HORIZONTAL_PADDING,
            y
            + (height - total_height) // 2
            + (i * (line_heights // len(assignment_lines) + LINE_SPACING)),
            width - 2 * HORIZONTAL_PADDING,
            line,
            ASSIGNMENT_FONT,
            utils.BG_LIGHT,
        )

    # Save some bucks on font licensing
    # Draw horizontal lines through assignment text
    for i in range(0, height, 2):
        display.drawLine(x, i, x + width, i, utils.BG_DARK)


def draw_status_bar(remaining_time, assignment):
    TIME_REMAINING_WIDTH = WIFI_INDICATOR_WIDTH = 40
    ASSIGNMENT_WIDTH = (
        utils.DISPLAY_SIZE[0] - TIME_REMAINING_WIDTH - WIFI_INDICATOR_WIDTH
    )

    # Background
    display.drawRect(
        0, 0, utils.DISPLAY_SIZE[0], STATUS_BAR_HEIGHT, True, utils.BG_DARK
    )

    # Time Remaining
    timer_height = display.getTextHeight(str(remaining_time), utils.ARCADE_FONT[9])
    text_centered(0, STATUS_BAR_HEIGHT - ((STATUS_BAR_HEIGHT + timer_height) // 2), TIME_REMAINING_WIDTH, str(remaining_time), utils.ARCADE_FONT[9], utils.TEXT_MAIN)

    # Assignment
    draw_assignment(
        assignment, TIME_REMAINING_WIDTH, 0, ASSIGNMENT_WIDTH, STATUS_BAR_HEIGHT
    )

    custom_shapes.draw_wifi_indicator(
        utils.DISPLAY_SIZE[0] - WIFI_INDICATOR_WIDTH,
        5,
        utils.COLOR_SECONDARY,
        utils.BG_DARK,
    )


def draw_health_bar(death_limit, health):
    progress = health / 100.0
    display.drawRect(
        0,
        STATUS_BAR_HEIGHT,
        utils.DISPLAY_SIZE[0],
        PROGRESS_BAR_HEIGHT,
        True,
        utils.COLOR_SECONDARY,
    )

    arrow_data = display.pngInfo(utils.PROGRESS_ARROW_RED)
    ARROW_SPACING = 3.0

    death_limit_width = round((death_limit / 100.0) * utils.DISPLAY_SIZE[0])
    arrow_amount = 1 + int(death_limit_width / (arrow_data[0] + ARROW_SPACING))
    display.drawRect(
        0,
        STATUS_BAR_HEIGHT,
        death_limit_width,
        PROGRESS_BAR_HEIGHT,
        True,
        utils.PROGRESS_GREEN,
    )

    for i in range(arrow_amount):
        current_x = int(i * (arrow_data[0] + ARROW_SPACING))
        if current_x >= 0:
            display.drawPng(
                current_x,
                STATUS_BAR_HEIGHT,
                utils.PROGRESS_ARROW_RED,
            )

    health_bar_width = round(progress * utils.DISPLAY_SIZE[0])
    if health_bar_width > 0:
        display.drawRect(
            death_limit_width,
            STATUS_BAR_HEIGHT,
            health_bar_width - death_limit_width,
            PROGRESS_BAR_HEIGHT,
            True,
            utils.PROGRESS_GREEN,
        )



def draw_control_text(text, x, y, width, height, is_light):
    TEXT_VERTICAL_SPACING = 3
    FONT = utils.ARCADE_FONT[6]

    lines = [t.upper() for t in text.split(" ")]
    line_heights = [display.getTextHeight(line, FONT) for line in lines]
    total_height = sum(line_heights, (len(lines) - 1) * TEXT_VERTICAL_SPACING)

    if total_height > height:
        raise Exception(
            f"Cannot render text '{text}' using given height constraint {height}px, need {total_height}px"
        )

    base_y = y + (height - total_height) // 2

    for i, line in enumerate(lines):
        text_centered(
            x,
            base_y + sum(line_heights[0:i]) + i * TEXT_VERTICAL_SPACING,
            width,
            line,
            FONT,
            utils.BG_DARK if is_light else utils.BG_LIGHT,
        )


def draw_binary_control_indicator(text, enabled, x, y, width, is_light):
    INDICATOR_HEIGHT = 20
    BORDER_WIDTH = 2

    # Center fill
    if enabled:
        display.drawRect(
            x + BORDER_WIDTH,
            y + BORDER_WIDTH,
            width - 2 * BORDER_WIDTH,
            INDICATOR_HEIGHT - 2 * BORDER_WIDTH,
            True,
            utils.BG_DARK if is_light else utils.BG_LIGHT,
        )

    # Top bar
    display.drawRect(
        x + BORDER_WIDTH,
        y,
        width - 2 * BORDER_WIDTH,
        BORDER_WIDTH,
        True,
        utils.BG_DARK if is_light else utils.BG_LIGHT,
    )

    # Bottom bar
    display.drawRect(
        x + BORDER_WIDTH,
        y + INDICATOR_HEIGHT - BORDER_WIDTH,
        width - 2 * BORDER_WIDTH,
        2,
        True,
        utils.BG_DARK if is_light else utils.BG_LIGHT,
    )

    # Left bar
    display.drawRect(
        x,
        y + BORDER_WIDTH,
        BORDER_WIDTH,
        INDICATOR_HEIGHT - 2 * BORDER_WIDTH,
        True,
        utils.BG_DARK if is_light else utils.BG_LIGHT,
    )

    # Right bar
    display.drawRect(
        x + width - BORDER_WIDTH,
        y + BORDER_WIDTH,
        BORDER_WIDTH,
        INDICATOR_HEIGHT - 2 * BORDER_WIDTH,
        True,
        utils.BG_DARK if is_light else utils.BG_LIGHT,
    )

    text_centered(
        x,
        y + 7,
        width,
        text,
        utils.ARCADE_FONT[8],
        utils.BG_LIGHT if is_light == enabled else utils.BG_DARK,
    )


def draw_binary_switch(value, x, y, width, height, is_light):
    center_x = x + width // 2

    sprite_file = (
        (utils.JOYSTICK_DARK_OFF if is_light else utils.JOYSTICK_LIGHT_OFF)
        if value == 0
        else (utils.JOYSTICK_DARK_ON if is_light else utils.JOYSTICK_LIGHT_ON)
    )

    sprite_info = display.pngInfo(sprite_file)

    display.drawPng(
        center_x - sprite_info[0] // 2, y + 25 + (11 if value == 0 else 0), sprite_file
    )

    draw_binary_control_indicator(
        "ON", value == 1, center_x - sprite_info[0] // 2, y, sprite_info[0], is_light
    )

    draw_binary_control_indicator(
        "OFF",
        value == 0,
        center_x - sprite_info[0] // 2,
        y + height - 25,
        sprite_info[0],
        is_light,
    )


def draw_vertical_slider(value, min_val, max_val, x, y, width, height, is_light):
    VERTICAL_PADDING = 5
    LEFT_PADDING = 7
    FONT_SIZE = utils.ARCADE_FONT[8]
    SLIDER_BAR_WIDTH = 3
    tick_amount = 1 + max_val - min_val

    total_text_height = sum(
        [display.getTextHeight(str(i), FONT_SIZE) for i in range(min_val, max_val + 1)]
    )
    text_spacing = (height - 2 * VERTICAL_PADDING - total_text_height) // (
        tick_amount - 1
    )

    # Draw slider legend
    for i in range(min_val, max_val + 1):
        display.drawText(
            x + LEFT_PADDING,
            y
            + VERTICAL_PADDING
            + (i - min_val) * ((total_text_height // tick_amount) + text_spacing),
            str(i),
            utils.BG_DARK if is_light else utils.BG_LIGHT,
            FONT_SIZE,
        )

    total_text_width = LEFT_PADDING + display.getTextWidth(str(max_val), FONT_SIZE)
    slider_bar_x = (
        x + total_text_width + (width - total_text_width) // 2 - SLIDER_BAR_WIDTH // 2
    )

    # Draw slider bar
    display.drawRect(
        slider_bar_x,
        y + VERTICAL_PADDING,
        SLIDER_BAR_WIDTH,
        height - 2 * VERTICAL_PADDING,
        True,
        utils.SLIDER_PURPLE,
    )

    # Draw slider handle
    slider_handle_data = display.pngInfo(utils.SLIDER_HANDLE_DARK)
    display.drawPng(
        slider_bar_x + (SLIDER_BAR_WIDTH // 2) - (slider_handle_data[0] // 2),
        y
        + (value - min_val)
        * ((height - slider_handle_data[1] - VERTICAL_PADDING) // (tick_amount - 1)),
        utils.SLIDER_HANDLE_DARK if is_light else utils.SLIDER_HANDLE_LIGHT,
    )


def draw_rotary_switch(value, x, y, min_val, max_val, width, height, is_light):
    FONT_SIZE = utils.ARCADE_FONT[8]
    HORIZONTAL_PADDING = 5
    MAX_TICK_COUNT = 5
    TICK_COUNT = 1 + max_val - min_val
    if TICK_COUNT not in [3, 5]:
        raise Exception("Invalid rotary switch provided with value range: [%s, %s]" % (min_val, max_val))

    total_text_width = sum(
        [display.getTextWidth(str(i), FONT_SIZE) for i in range(MAX_TICK_COUNT)]
    )
    text_spacing = (width - 2 * HORIZONTAL_PADDING - total_text_width) // (
        MAX_TICK_COUNT - 1
    )

    for i in range(min_val, max_val + 1):
        index = i - min_val if TICK_COUNT == 5 else [0, 2, 4][i - min_val]

        y_pos = y + 15
        if index == 0 or index == 4:
            y_pos += 2 * 12
        elif index == 1 or index == 3:
            y_pos += 12

        display.drawText(
            x
            + HORIZONTAL_PADDING
            + index * ((total_text_width // MAX_TICK_COUNT) + text_spacing),
            y_pos,
            str(i),
            utils.BG_DARK if is_light else utils.BG_LIGHT,
            FONT_SIZE,
        )

    control_sprite = utils.ROTARY_CONTROL(value if TICK_COUNT == 5 else [1, 3, 5][value - min_val], is_light)

    # Draw control handle
    rotary_control_data = display.pngInfo(control_sprite)
    display.drawPng(
        x + (width // 2 - rotary_control_data[0] // 2),
        y + 45,
        control_sprite,
    )


def draw_control(control, i, control_name):
    is_light = i % 2 == 0

    block_width = utils.DISPLAY_SIZE[0] // 4
    x0 = block_width * i
    y0 = STATUS_BAR_HEIGHT + PROGRESS_BAR_HEIGHT
    block_height = utils.DISPLAY_SIZE[1] - y0

    display.drawRect(
        x0,
        y0,
        block_width,
        block_height,
        True,
        utils.BG_LIGHT if is_light else utils.BG_DARK,
    )

    draw_control_text(
        control_name, x0, y0, block_width, TEXT_BLOCK_HEIGHT, is_light
    )

    if control["type"] == CONTROL_TYPES[0]:
        draw_vertical_slider(
            control["value"],
            control["min"],
            control["max"],
            x0,
            y0 + TEXT_BLOCK_HEIGHT,
            block_width,
            block_height - TEXT_BLOCK_HEIGHT,
            is_light,
        )
    elif control["type"] == CONTROL_TYPES[1]:
        draw_rotary_switch(
            control["value"],
            x0,
            y0 + TEXT_BLOCK_HEIGHT,
            control["min"],
            control["max"],
            block_width,
            block_height - TEXT_BLOCK_HEIGHT,
            is_light,
        )
    elif control["type"] == CONTROL_TYPES[2]:
        draw_binary_switch(
            control["value"],
            x0,
            y0 + TEXT_BLOCK_HEIGHT,
            block_width,
            block_height - TEXT_BLOCK_HEIGHT,
            is_light,
        )


# Below can be removed once all game elements are integrated - this is only for testing/demo purposes
# state = {
#     "totalTime": 8,
#     "remainingTime": 8,
#     "assignment": "Set power plant level to 5",
#     "controls": [
#         {
#             "type": CONTROL_TYPES[0],
#             "tickAmount": 6,
#             "value": 3,
#             "title": "Money Supply",
#         },
#         {"type": CONTROL_TYPES[1], "value": 2, "title": "Power Plant Level"},
#         {
#             "type": CONTROL_TYPES[2],
#             "value": 0,
#             "title": "Traffic Light Control",
#         },
#         {
#             "type": CONTROL_TYPES[2],
#             "value": 1,
#             "title": "Flood Control",
#         },
#     ],
# }
#
#
# def set_vertical_switch(up):
#     vertical_switch = state["controls"][0]
#
#     if not (
#         ((not up) and vertical_switch["value"] == vertical_switch["tickAmount"] - 1)
#         or (up and vertical_switch["value"] == 0)
#     ):
#         vertical_switch["value"] += -1 if up else 1
#
#     state["controls"][0] = vertical_switch
#
#     draw_game_screen(state)
#
#
# def set_rotary_switch(left):
#     rotary_switch = state["controls"][1]
#
#     if not (
#         ((not left) and rotary_switch["value"] == 4)
#         or (left and rotary_switch["value"] == 0)
#     ):
#         rotary_switch["value"] += -1 if left else 1
#
#     state["controls"][1] = rotary_switch
#
#     draw_game_screen(state)
#
#
# def toggle_binary_switch(left):
#     binary_switch = state["controls"][2 if left else 3]
#
#     binary_switch["value"] = int(not binary_switch["value"])
#
#     state["controls"][2 if left else 3] = binary_switch
#
#     draw_game_screen(state)
#
#
#
# def draw_demo_game():
#     buttons.attach(
#         buttons.BTN_UP,
#         lambda pressed: set_vertical_switch(True) if pressed else 0,
#     )
#     buttons.attach(
#         buttons.BTN_DOWN,
#         lambda pressed: set_vertical_switch(False) if pressed else 0,
#     )
#     buttons.attach(
#         buttons.BTN_LEFT, lambda pressed: set_rotary_switch(True) if pressed else 0
#     )
#     buttons.attach(
#         buttons.BTN_RIGHT, lambda pressed: set_rotary_switch(False) if pressed else 0
#     )
#     buttons.attach(
#         buttons.BTN_A,
#         lambda pressed: toggle_binary_switch(False) if pressed else 0,
#     )
#     buttons.attach(
#         buttons.BTN_B,
#         lambda pressed: toggle_binary_switch(True) if pressed else 0,
#     )
#
#     buttons.attach(buttons.BTN_HOME, lambda pressed: main.main_menu() if pressed else 0)
#
#     draw_game_screen(state)
