import time
import random
import numpy as np
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

URL_LOGIN = "http://localhost/Kelompok06_2048/Homepage.html" 

SEARCH_DEPTH = 4

DELAY_ANIMATION = 0.1 

class GameAI:
    def __init__(self, url):
        print(">>> Menyiapkan Browser Mode HARDCORE (Depth 4)...")
        chrome_options = Options()
        chrome_options.add_argument("--log-level=3") 
        
        chrome_options.add_experimental_option("detach", True)

        self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        self.driver.set_window_size(1000, 1000)
        
        self.driver.get(url)
        print("\n" + "="*50)
        print("  SILAKAN LOGIN MANUAL -> KLIK PLAY  ")
        print("  Bot Depth 4 siap menunggu di arena...")
        print("="*50 + "\n")

        try:
            WebDriverWait(self.driver, 3600).until(
                EC.presence_of_element_located((By.ID, "board"))
            )
            print(">>> GAME START! Mengaktifkan Otak Jenius...")
            time.sleep(1) 
        except:
            print("Gagal deteksi game, tapi browser tetap terbuka.")
            
        self.body = self.driver.find_element(By.TAG_NAME, 'body')

    def get_grid(self):
        matrix = np.zeros((4, 4), dtype=int)
        try:
            for r in range(4):
                for c in range(4):
                    element_id = f"{r}-{c}"
                    try:
                        tile = self.driver.find_element(By.ID, element_id)
                        text = tile.text.strip()
                        if text.isdigit():
                            matrix[r][c] = int(text)
                        else:
                            matrix[r][c] = 0
                    except:
                        matrix[r][c] = 0
        except:
            pass
        return matrix

    def heuristic(self, grid):
        weights = np.array([
            [0,   1,   10,  30],
            [150, 100, 80,  50],
            [200, 300, 500, 800],
            [20000, 8000, 3000, 1500] 
        ]) 
        score = np.sum(grid * weights)
        empty_count = len(np.where(grid == 0)[0])
        score += empty_count * 30000
        penalty = 0
        for x in range(4):
            # Baris
            current = 0
            next_val = 0
            for y in range(3):
                current = grid[x][y]
                next_val = grid[x][y+1]
                if current > next_val:
                    score += current - next_val
                else:
                    score -= (next_val - current) * 5
            
            for y in range(3):
                current = grid[y][x]
                next_val = grid[y+1][x]
                if current > next_val:
                    score += current - next_val
                else:
                    score -= (next_val - current) * 5

        return score

    def simulate_move(self, grid, direction):
        temp_grid = grid.copy()
        if direction == 'UP': temp_grid = np.rot90(temp_grid, 1)
        elif direction == 'RIGHT': temp_grid = np.rot90(temp_grid, 2)
        elif direction == 'DOWN': temp_grid = np.rot90(temp_grid, 3)
        
        changed = False
        new_grid_vals = []
        for row in temp_grid:
            new_row = [x for x in row if x != 0]
            for i in range(len(new_row)-1):
                if new_row[i] == new_row[i+1] and new_row[i] != 0:
                    new_row[i] *= 2
                    new_row[i+1] = 0
            new_row = [x for x in new_row if x != 0]
            new_row += [0] * (4 - len(new_row))
            new_grid_vals.append(new_row)
            if new_row != list(row): changed = True
            
        temp_grid = np.array(new_grid_vals)
        if direction == 'UP': temp_grid = np.rot90(temp_grid, 3)
        elif direction == 'RIGHT': temp_grid = np.rot90(temp_grid, 2)
        elif direction == 'DOWN': temp_grid = np.rot90(temp_grid, 1)
        return temp_grid, changed

    def expectimax(self, grid, depth, is_player_turn):
        if depth == 0: return self.heuristic(grid)
        
        if is_player_turn:
            best_score = -float('inf')
            for move in ['DOWN', 'RIGHT', 'LEFT', 'UP']:
                new_grid, changed = self.simulate_move(grid, move)
                if changed:
                    score = self.expectimax(new_grid, depth - 1, False)
                    best_score = max(best_score, score)
            return best_score if best_score != -float('inf') else -10000000
        else:
            empty_cells = list(zip(*np.where(grid == 0)))
            if not empty_cells: return self.heuristic(grid)
            
            sample_size = 4 
            if len(empty_cells) > sample_size: 
                empty_cells = random.sample(empty_cells, sample_size)
            
            total_score = 0
            for r, c in empty_cells:
                grid[r][c] = 2
                total_score += self.expectimax(grid, depth - 1, True)
                grid[r][c] = 0
            return total_score / len(empty_cells)

    def run(self):
        print(">>> Bot Mulai! (Mode: FULL DEPTH 4)")
        print(">>> Tips: Jangan minimize window browser biar lancar.")
        
        while True:
            try:
                grid = self.get_grid()
                
                if np.sum(grid) == 0:
                    time.sleep(0.5)
                    continue

                best_move = None
                max_val = -float('inf')
                valid_found = False
                
                empty = len(np.where(grid == 0)[0])
                print(f"\rMikir... (Kosong: {empty})", end="")

                for move in ['DOWN', 'RIGHT', 'LEFT', 'UP']:
                    sim_grid, changed = self.simulate_move(grid, move)
                    if changed:
                        valid_found = True
                        val = self.expectimax(sim_grid, SEARCH_DEPTH, False)
                        if val > max_val: max_val = val; best_move = move
                
                if best_move:
                    self.body.send_keys(getattr(Keys, best_move))
                    time.sleep(DELAY_ANIMATION)
                else:
                    if not valid_found:
                        print("\nGAME OVER!")
                        break
                    else:
                        self.body.send_keys(Keys.DOWN)
            except KeyboardInterrupt:
                break
            except Exception:
                time.sleep(1)

if __name__ == "__main__":
    ai = GameAI(URL_LOGIN)
    ai.run()