import threading
import queue
import time
import smbus
import sys
from ble_utils.bluetooth_uart_server import ble_gatt_uart_loop

# LCD Configuration
I2C_ADDR = 0x27  # Verify with i2cdetect -y 1
LCD_WIDTH = 16
ENABLE_BIT = 0x04
LINE_ADDR = [0x80, 0xC0]  # Addresses for line 1 and 2

# Initialize I2C bus
i2c = smbus.SMBus(1)

# Game state
current_player = "Player 1"
player_scores = {"Player 1": 501, "Player 2": 501}
last_prediction = None    # Store the last prediction
prediction_active = False # Flag to track if we're waiting to apply a prediction
connection_active = True  # Flag to track BLE connection status

def lcd_send_byte(bits, mode):
    """Send byte to LCD in 4-bit mode with proper timing"""
    # High nibble
    i2c.write_byte(I2C_ADDR, (bits & 0xF0) | mode | 0x08)
    i2c.write_byte(I2C_ADDR, (bits & 0xF0) | mode | 0x08 | ENABLE_BIT)
    i2c.write_byte(I2C_ADDR, (bits & 0xF0) | mode | 0x08)
    # Low nibble
    i2c.write_byte(I2C_ADDR, ((bits << 4) & 0xF0) | mode | 0x08)
    i2c.write_byte(I2C_ADDR, ((bits << 4) & 0xF0) | mode | 0x08 | ENABLE_BIT)
    i2c.write_byte(I2C_ADDR, ((bits << 4) & 0xF0) | mode | 0x08)
    time.sleep(0.0002)

def lcd_init():
    """Initialize LCD with proper 4-bit sequence"""
    cmds = [0x33, 0x32, 0x28, 0x0C, 0x06, 0x01]
    for cmd in cmds:
        lcd_send_byte(cmd, 0)
        time.sleep(0.0045 if cmd in [0x33, 0x32] else 0.002)

def lcd_write(line_num, text):
    """Write text to specified LCD line"""
    text = text.ljust(LCD_WIDTH)[:LCD_WIDTH]
    lcd_send_byte(LINE_ADDR[line_num], 0)
    for char in text:
        lcd_send_byte(ord(char), 1)

def update_score_display(message=None):
    """Update LCD with current player, score, and prediction or message"""
    global last_prediction
    lcd_write(0, f"{current_player}: {player_scores[current_player]}")
    if message:
        lcd_write(1, message[:LCD_WIDTH])
    elif last_prediction:
        if prediction_active:
            prediction_text = f"Pred: {last_prediction} (NEW)"
        else:
            prediction_text = f"Last: {last_prediction}"
        lcd_write(1, prediction_text)
    else:
        lcd_write(1, "Waiting for data...")

def show_winner():
    """Display the winner on the LCD and reset after a pause"""
    winner = current_player
    lcd_write(0, "Game Over!")
    lcd_write(1, f"Winner: {winner}")
    print(f"\nGame over! Winner: {winner}")
    time.sleep(3)
    reset_scores()

def apply_prediction(prediction):
    """Apply the prediction to the current player's score with double-out logic and no negative scores or score of 1"""
    global player_scores, current_player, last_prediction, prediction_active
    try:
        score = 0
        is_double = False
        if prediction.startswith("T"):
            base_score = int(prediction[1:])
            score = base_score * 3
        elif prediction.startswith("D"):
            base_score = int(prediction[1:])
            score = base_score * 2
            is_double = True
        elif prediction.isdigit():
            score = int(prediction)
        new_score = player_scores[current_player] - score
        if new_score == 0 and is_double:
            player_scores[current_player] = 0
            update_score_display("Double out! WIN")
            show_winner()
        elif new_score < 0:
            update_score_display("Score can't go <0!")
            print("Bust: Score unchanged.")
        elif new_score == 0 and not is_double:
            update_score_display("Need double to win!")
            print("Not double out. Score unchanged.")
        elif new_score == 1:
            update_score_display("Score 1 not allowed!")
            print("Score of 1 is not finishable. Turn does not count.")
        else:
            player_scores[current_player] = new_score
            update_score_display()
        last_prediction = prediction
        prediction_active = False
    except (ValueError, KeyError) as e:
        print(f"Error applying prediction: {e}")

def process_prediction_command(command):
    global last_prediction, prediction_active
    try:
        if command.startswith("PRED "):
            prediction = command[5:].strip()
            if prediction_active and last_prediction:
                apply_prediction(last_prediction)
            last_prediction = prediction
            prediction_active = True
            update_score_display()
    except Exception as e:
        print(f"Error processing prediction: {e}")

def process_score_command(command):
    global current_player, player_scores
    try:
        if command.startswith(("P1 ", "P2 ")):
            player = "Player 1" if command.startswith("P1") else "Player 2"
            score_part = command[3:]
            if score_part.isdigit():
                player_scores[player] = int(score_part)
            elif score_part.startswith(("T", "D")):
                base_score = int(score_part[1:])
                multiplier = 3 if score_part.startswith("T") else 2
                player_scores[player] -= base_score * multiplier
            if player == current_player:
                update_score_display()
    except (ValueError, KeyError) as e:
        print(f"Error processing command: {e}")

def switch_player():
    global current_player, prediction_active
    if prediction_active and last_prediction:
        apply_prediction(last_prediction)
    current_player = "Player 2" if current_player == "Player 1" else "Player 1"
    prediction_active = False
    update_score_display()

def reset_scores():
    global player_scores, current_player, last_prediction, prediction_active
    player_scores = {"Player 1": 501, "Player 2": 501}
    current_player = "Player 1"
    last_prediction = None
    prediction_active = False
    update_score_display()
    print("Scores reset to 501 for both players.")

def process_incoming_command(command, tx_q):
    if command.startswith("PRED"):
        process_prediction_command(command)
    elif command == "SWITCH":
        switch_player()
    elif command.startswith(("P1 ", "P2 ")):
        process_score_command(command)
    elif command == "GET_SCORE":
        tx_q.put(f"{current_player[-1]} {player_scores[current_player]}")
    elif command == "RESET":
        reset_scores()
    elif command == "WIN":
        show_winner()

def check_connection(evt_q):
    global connection_active
    try:
        event = evt_q.get_nowait()
        if event == "DISCONNECTED":
            print("PC disconnected - ending program")
            connection_active = False
    except queue.Empty:
        pass

def cleanup():
    if prediction_active and last_prediction:
        apply_prediction(last_prediction)
    show_winner()
    lcd_send_byte(0x01, 0)
    sys.exit(0)

def main():
    global connection_active
    lcd_init()
    update_score_display()
    time.sleep(2)
    rx_q = queue.Queue()
    tx_q = queue.Queue()
    evt_q = queue.Queue()
    ble_thread = threading.Thread(
        target=ble_gatt_uart_loop,
        args=(rx_q, tx_q, "Darts Score", evt_q),
        daemon=True
    )
    ble_thread.start()
    try:
        while connection_active:
            check_connection(evt_q)
            try:
                incoming = rx_q.get_nowait()
                print(f"Received: {incoming}")
                process_incoming_command(incoming, tx_q)
            except queue.Empty:
                pass
            time.sleep(0.1)
    except KeyboardInterrupt:
        print("\nKeyboard interrupt received")
    finally:
        cleanup()

if __name__ == '__main__':
    main()
