from enum import Enum

from nicegui import ui
from nicegui.elements.html import Html
from nicegui.functions.timer import Timer

Direction = Enum('Direction', ['Up', 'Down', 'Right', 'Left'])

class Position():
    def __init__ (self, x, y):
        self.x = x
        self.y = y

class State():
    def __init__(self):
        self.iteration = 0
        self.robot_pos = Position(0, 0)
        self.ball_pos = Position(0, 0)
        self.grid_size = 0
        self.cell_size = 0
        self.solution_path = ''
        self.start_pos = Position(0, 0)
        self.gate = (Position(0,0), Position(0, 0))
        self.ball_color = 'black'
        self.robot_color = 'black'
        self.path_color = 'black'
        self.gate_color = 'black'

def build_svg( state: State) -> str:
    cell_size = state.cell_size
    grid_size = state.grid_size

    width = cell_size * grid_size + 1
    height = cell_size * grid_size + 1

    current_path = state.solution_path.split(' ')[0:state.iteration]

    # draw solution path
    path = draw_path(state.start_pos.x, state.start_pos.y, current_path, grid_size, cell_size, state.path_color)

    # draw gate
    if(state.gate[0].x == state.gate[1].x):
        gate_start_x = state.gate[0].x
        gate_start_y = min(state.gate[0].y, state.gate[1].y)
        gate_path = 'U' * abs(state.gate[0].y - state.gate[1].y)
    else:
        gate_start_x = min(state.gate[0].x, state.gate[1].x)
        gate_start_y = state.gate[0].y
        gate_path = 'R' * abs(state.gate[0].x - state.gate[1].x)

    gate = draw_path(gate_start_x, gate_start_y, gate_path, grid_size, cell_size, state.gate_color)

    # draw ball
    ball_x = get_x_position(state.ball_pos.x, grid_size, cell_size)
    ball_y = get_y_position(state.ball_pos.y, grid_size, cell_size)
    ball = f'<circle cx="{ball_x}" cy="{ball_y}" r="10" fill="{state.ball_color}" />'

    last_movement = state.solution_path.split(' ')[state.iteration]
    last_movement_direction = get_direction(last_movement)

    # draw robot
    robot_pos_x = get_x_position(state.robot_pos.x, grid_size, cell_size)
    robot_pos_y = get_y_position(state.robot_pos.y, grid_size, cell_size)
    robot = f'<circle cx="{robot_pos_x}" cy="{robot_pos_y}" r="20" fill="{state.robot_color}" />'

    robot_next_pos = calculate_next_position(state.robot_pos, last_movement_direction)
    state.robot_pos = robot_next_pos # update robot position

    if(state.robot_pos.x == state.ball_pos.x and state.robot_pos.y == state.ball_pos.y):
        ball_next_pos = calculate_next_position(state.ball_pos, last_movement_direction)
        state.ball_pos = ball_next_pos
    
    # draw game board
    return f'''
        <svg id='patternId' width='{width}' height='{height}' xmlns='http://www.w3.org/2000/svg'>
        <defs><pattern id='a' patternUnits='userSpaceOnUse' width='{cell_size}' height='{cell_size}' patternTransform='rotate(0)'>
        <rect x='0' y='0' width='100%' height='100%' fill='hsla(0,0%,100%,1)'/>
        <path d='M 0,0 V {cell_size} Z M 0,0 H {cell_size} Z'  stroke-width='1' stroke='hsla(133, 35%, 32%, 1)' fill='none'/>
        </pattern></defs>
        <rect width='100%' height='100%'  fill='url(#a)'/>
        
        {path}
        {gate}
        {ball}
        {robot}
        
        </svg>
    '''

def get_x_position(pos, grid_size, cell_size):
    return pos * cell_size

def get_y_position(pos, grid_size, cell_size):
    return (grid_size - pos) * cell_size

def draw_path(x, y, path, grid_size, cell_size, color):
    lines = []

    next_x = x
    next_y = y

    for dir in path:
        direction: Direction = get_direction(dir)
        lines.append(generate_line(next_x, next_y, direction, 1, grid_size, cell_size, color))
        next_x, next_y = calculate_next_position_xy(next_x, next_y, direction)

    return str.join(" ", lines)

def calculate_next_position(pos: Position, direction: Direction):
    (next_x, next_y) = calculate_next_position_xy(pos.x, pos.y, direction)
    return Position(next_x, next_y)

def calculate_next_position_xy(x, y, direction: Direction):
    match direction:
        case Direction.Down:
            return (x, y - 1)
        case Direction.Up:
            return (x, y + 1)
        case Direction.Left:
            return (x - 1, y)
        case Direction.Right:
            return (x + 1, y)

def get_direction(dir):
    match dir:
        case 'D':
            return Direction.Down
        case 'U':
            return Direction.Up
        case 'L':
            return Direction.Left
        case 'R':
            return Direction.Right

def generate_line(x, y, direction: Direction, length, grid_size, cell_size, color):
    target_x = x
    target_y = (grid_size) - y

    match direction:
        case Direction.Down:
            target_y = target_y + length # SVG grid is top to bottom
        case Direction.Up:
            target_y = target_y - length # SVG grid is top to bottom
        case Direction.Right:
            target_x = target_x + length
        case Direction.Left:
            target_x = target_x - length

    scaled_x = x * cell_size
    scaled_y = ((grid_size) - y) * cell_size

    scaled_target_x = target_x * cell_size
    scaled_target_y = target_y * cell_size
    return f'''
    <path d='M {scaled_x} {scaled_y} L {scaled_target_x} {scaled_target_y}'  stroke-width='3' stroke='{color}'/>
    '''

def next_frame(body: Html, state: State):
    body.set_content(build_svg(state))
    state.iteration = state.iteration + 1

def toggle_timer(timer: Timer):
    if(timer.active):
        timer.deactivate()
    else:
        timer.activate()

def reset(state: State):
    state.ball_pos = Position(2, 1)
    state.robot_pos = Position(7, 4)
    state.grid_size = 10
    state.cell_size = 50
    state.iteration = 0
    state.solution_path = 'D D D D L L L L L U U U U U U L U R R R R R R R R R'
    state.start_pos = state.robot_pos
    state.gate = (Position(10, 5), Position(10, 8))
    state.ball_color = 'red'
    state.robot_color = 'green'
    state.path_color = 'orange'
    state.gate_color = 'blue'

interval = 1.0 # seconds
timer = ui.timer(interval, lambda: next_frame(body, state))
timer.deactivate()

state = State()

with ui.row():
    ui.button('Play / Pause', on_click= lambda: toggle_timer(timer))
    ui.button('Reset', on_click= lambda: reset(state))

body = ui.html().classes('self-center')

reset(state)

ui.run()