import win32api
import win32con
import win32gui
import pygame
import ctypes
import json
from tkinter import Tk, filedialog
import numpy as np

# INITIALIZATION
pygame.init()
FPS = 60

MAIN_PATH = "C:/Users/User/Documents/Coding/Picture Puzzle/"
#School: MAIN_PATH = "C:/Users/RVKG/Documents/My Palettes/Picture Puzzle/"
IMAGES_PATH = "/images/albums/"

DATA_FILE = "main_data.json"
PUZZLE_FILE = "main_puzzle.json"
SETTINGS_FILE = "main_settings.json"
STATS_FILE = "main_stats.json"
SIZE_PATH = "images/assets/"

# Data loading and saving
def load_data(data):
    try:
        with open(data, "r") as file:
            current_data = json.load(file)
    except (FileNotFoundError, json.JSONDecodeError):
        print("Something went wrong with file loading")
    return current_data
        
storage = load_data(DATA_FILE)
puzzle_data = load_data(PUZZLE_FILE)
settings_data = load_data(SETTINGS_FILE)
stats_data = load_data(STATS_FILE)

# Configuration
SIZE_CHOICES = ["small", "medium", "big"]
STYLE_CHOICES = ["classic", "original", "dark"]
GRID_CHOICES = ["4x4", "3x3"]
MUSIC_CHOICES = ["funky-leap", "serenity", "sunny-day"]
SOUND_CHOICES = ["puzzle", "wood", "metal_pipe", "lego_breaking"]
DISPLAY_CHOICES = ["time", "moves"]
LANGUAGE_CHOICES = ["english", "russian", "latvian"]
FONTS = ["msreferencesansserif", "arial", "bahnschrift"]

if settings_data["style"] == STYLE_CHOICES[0]:
    FONT = FONTS[0]
    STYLE_PATH = "1_"
    text_color = (1, 35, 61)
    timer_color = (255, 255, 255)
elif settings_data["style"] == STYLE_CHOICES[1]:
    FONT = FONTS[1]
    STYLE_PATH = "2_"
    text_color = (255, 255, 255)
    timer_color = (70, 35, 0)
else:
    FONT = FONTS[2]
    STYLE_PATH = "3_"
    text_color = (255, 255, 255)
    timer_color = text_color

if settings_data["size"] == SIZE_CHOICES[0]:
    WIDTH, HEIGHT = 144, 154
    INT = 0
    if FONT == FONTS[0]:
        FONT_SIZE = 11
    elif FONT == FONTS[1]:
        FONT_SIZE = 12
    elif FONT == FONTS[2]:
        FONT_SIZE = 11
    non_movable_area = pygame.Rect(8, 19, 128, 128)
    timer_area = pygame.Rect(22, 1, 55, 12)
    puzzle_size = (128, 128)
    win_area = pygame.Rect(55, 70, 55, 12)
    y = 25
    size = [20, 25, 100, 15]
    cons = [15, 195, 12, 3, 9]
    scaled_size = (18, 21, 108, 108)
    
elif settings_data["size"] == SIZE_CHOICES[1]:
    WIDTH, HEIGHT = 202, 216
    INT = 1
    if FONT == FONTS[0]:
        FONT_SIZE = 15
    elif FONT == FONTS[1]:
        FONT_SIZE = 17
    elif FONT == FONTS[2]:
        FONT_SIZE = 16
    puzzle_size = (180, 180)
    win_area = pygame.Rect(71, 98, 55, 12)
    non_movable_area = pygame.Rect(11, 26, 180, 180)
    timer_area = pygame.Rect(27, 2, 86, 16)
    y = 35
    size = [30, 35, 110, 25]
    cons = [25, 247, 18, 3, 13]
    scaled_size = (22, 26, 158, 158)

else:
    WIDTH, HEIGHT = 202, 216
    INT = 2
    if FONT == FONTS[0]:
        FONT_SIZE = 15
    elif FONT == FONTS[1]:
        FONT_SIZE = 17
    elif FONT == FONTS[2]:
        FONT_SIZE = 16
    puzzle_size = (180, 180)
    win_area = pygame.Rect(71, 98, 55, 12)
    non_movable_area = pygame.Rect(11, 26, 180, 180)
    timer_area = pygame.Rect(27, 2, 86, 16)
    y = 45
    size = [40, 45, 120, 35]
    cons = [35, 270, 18, 3, 15]
    scaled_size = (22, 26, 158, 158)

puzzle_version = settings_data["grid"]        
        
# Seting screen and other stuff
programIcon = pygame.image.load('icon.png')
pygame.display.set_icon(programIcon)
pygame.display.set_caption('Picture Puzzle')
screen_info = pygame.display.Info()
window_coords = [(screen_info.current_w - WIDTH) // 2, (screen_info.current_h - HEIGHT) // 2]
screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.NOFRAME)
clock = pygame.time.Clock()
hwnd = pygame.display.get_wm_info()["window"]

# Sprites generation
class SpriteSheet:
    def __init__(self, sheet_path):
        self.sheet = pygame.image.load(sheet_path).convert_alpha()

    def get_sprite(self, position, width, height, color_key=None):
        image = pygame.Surface((width, height), pygame.SRCALPHA).convert_alpha()
        image.blit(self.sheet, (0, 0), (position[0], position[1], width, height))

        if color_key:
            for x in range(width):
                for y in range(height):
                    if image.get_at((x, y))[:3] == color_key:
                        image.set_at((x, y), (0, 0, 0, 0))

        return image

    def set_hue(self, hue):
        for x in range(self.sheet.get_width()):
            for y in range(self.sheet.get_height()):
                color = self.sheet.get_at((x, y))

                h, s, v, a = pygame.Color(color)
                h = (h + hue) % 360
                new_color = pygame.Color(0, 0, 0, 0)
                new_color.hsva = (h, s, v, a)

                self.sheet.set_at((x, y), new_color)
    
sprite_sheet = SpriteSheet(SIZE_PATH + str(INT) + STYLE_PATH + "sprites.png")
    
class Image:
    def __init__(self, sprite_position, window_position, size):
        self.sprite_sheet = sprite_sheet
        self.sprite_position = sprite_position
        self.window_position = window_position
        self.size = size
        self.update_image()

    def update_image(self):
        self.img = self.sprite_sheet.get_sprite(self.sprite_position, *self.size)
        self.rect = self.img.get_rect(topleft=self.window_position)

    def draw(self, screen):
        screen.blit(self.img, self.rect)

class Slider:
    def __init__(self, line_size, button_size, position):
        self.line_size = line_size
        self.button_size = button_size
        self.position = position
        self.value = 0
        self.button_rect = pygame.Rect(self.position[0], self.position[1] - (self.button_size[1] - self.line_size[1]) // 2,
                                       self.button_size[0], self.button_size[1])
        self.slider_rect = pygame.Rect(self.position[0], self.position[1], self.line_size[0], self.line_size[1])

    def update(self):
        button_x = self.position[0] + (self.value / 360) * (self.line_size[0] - self.button_size[0])
        self.button_rect.topleft = (button_x, self.button_rect.y)

    def draw(self, screen):
        pygame.draw.rect(screen, (200, 200, 200), self.slider_rect)
        pygame.draw.rect(screen, (0, 0, 255), self.button_rect)

    def handle_event(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            if self.button_rect.collidepoint(event.pos):
                self.dragging = True
        elif event.type == pygame.MOUSEBUTTONUP and event.button == 1:
            self.dragging = False
        elif event.type == pygame.MOUSEMOTION and self.dragging:
            mouse_x = max(self.position[0], min(event.pos[0], self.position[0] + self.line_size[0]))
            self.value = int(((mouse_x - self.position[0]) / (self.line_size[0] - self.button_size[0])) * 360)
            self.update()

class Button:
    def __init__(self, sprite_position, window_position, size):
        self.sprite_sheet = sprite_sheet
        self.sprite_position = sprite_position
        self.window_position = window_position
        self.size = size
        self.hovered = False
        self.disabled = False
        self.update_images()

    def update_images(self):
        self.img = self.sprite_sheet.get_sprite(self.sprite_position, *self.size)
        hover_position = (
            self.sprite_position[0],
            self.sprite_position[1] + 3 + self.size[1]
        )
        self.hover_image = self.sprite_sheet.get_sprite(hover_position, *self.size)

        self.rect = self.img.get_rect(topleft=self.window_position)
        self.mask = pygame.mask.from_surface(self.img)

    def is_hovered(self, mouse_pos):
        return self.rect.collidepoint(mouse_pos) and self.mask.get_at(
            (mouse_pos[0] - self.rect.x, mouse_pos[1] - self.rect.y)
        )      
    
    def disable(self):
        self.disabled = True
        
    def enable(self):
        self.disabled = False
    
    def update(self, mouse_pos):
        self.hovered = self.is_hovered(mouse_pos)

    def draw(self, screen):
        if not self.disabled:
            screen.blit(self.hover_image if self.hovered else self.img, self.rect)
        else:
            screen.blit(self.img, self.rect)

# Ability to move window
def move_win(coordinates):
    hwnd = pygame.display.get_wm_info()["window"]
    w, h = pygame.display.get_surface().get_size()
    win32gui.MoveWindow(hwnd, coordinates[0], coordinates[1], w, h, True)
    set_shadow_style(hwnd)

# Generating shadow
def set_shadow_style(hwnd):
    try:
        DWMWA_NCRENDERING_POLICY = 2
        DWMNCRP_ENABLED = 2

        ctypes.windll.dwmapi.DwmSetWindowAttribute(
            hwnd,
            DWMWA_NCRENDERING_POLICY,
            ctypes.byref(ctypes.c_uint(DWMNCRP_ENABLED)),
            ctypes.sizeof(ctypes.c_uint),
        )
        MARGINS = (ctypes.c_int * 4)(-1, -1, -1, -1)
        ctypes.windll.dwmapi.DwmExtendFrameIntoClientArea(hwnd, ctypes.byref(MARGINS))

    except Exception as e:
        print(e)
        
# Text render
def render_text(text, position, screen, color=text_color):
    font = pygame.font.SysFont(FONT, FONT_SIZE)
    text_render = font.render(text, True, color)
    screen.blit(text_render, position)   

# MENU MODE
def browse():
    root = Tk()
    root.withdraw()
    file_path = filedialog.askopenfilename(filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.gif")])
    root.destroy()
    return file_path

quick_game_activated = False
selected_file = None

def quick_game():
    global selected_file, puzzle_image, puzzle_pieces, shuffled_pieces, empty_position, quick_game_activated, original_image
    quick_game_activated = True
    try:
        selected_file = browse()
        if selected_file is not None:
            puzzle_image = pygame.image.load(selected_file)
            original_image = pygame.transform.scale(puzzle_image, puzzle_size)
            puzzle_pieces = cut_image(original_image)
            shuffled_pieces = shuffle_pieces(puzzle_pieces)
            empty_position = (len(puzzle_matrix) - 1, len(puzzle_matrix[0]) - 1)
            quick_game_activated = False
    except FileNotFoundError:
        print("No file selected")

def render_albums():
    global current_mode, album_name
    current_hovered_index = -1

    for hover_index, album in enumerate(storage["albums"]):
        album_rect = pygame.Rect(size[0], y, size[2], size[3])
        album_hover = album_rect.collidepoint(pygame.mouse.get_pos())

        if album_hover:
            current_hovered_index = hover_index

        render_text(album, (size[1], y), screen)
        y += cons[0]

        if album_hover and event.type == pygame.MOUSEBUTTONDOWN:
            album_name = album
            if album_name != album:
                album_name = None
            
            if album_name is not None:
                current_mode = "select"
            
            return album
    
    if current_hovered_index == -1:
        current_hovered_index = 0
    arrow_image = Image((cons[1], 0), (cons[2], size[1] + cons[0] * current_hovered_index + cons[3]), (cons[4], cons[4]))
    arrow_image.draw(screen)
        
def album_deletion():
    # Not finished yet
    pass

def album_creation():
    # Not finished yet
    pass
            
# SELECT MODE
class ImageRender:
    def __init__(self, album_name, initial_index=0):
        self.album_name = album_name
        self.index = initial_index
        self.image_load()
        
    def image_load(self):
        global current_img, current_album
        if settings_data["size"] == SIZE_CHOICES[0]:
            self.scaled_size = (18, 21, 108, 108)
        else:
            self.scaled_size = (22, 26, 158, 158)
            
        image_list = storage["albums"][self.album_name]
        image_names = list(image_list.keys())
        
        if not image_names:
            return
        
        self.current = image_names[self.index]
        selected_image = pygame.image.load(MAIN_PATH + IMAGES_PATH + self.album_name + "/" + self.current)
        self.scaled_image = pygame.transform.scale(selected_image, (self.scaled_size[2], self.scaled_size[3]))
        current_img = self.current
        current_album = self.album_name

    def render(self, screen):
        screen.blit(self.scaled_image, (self.scaled_size[0], self.scaled_size[1]))
        pygame.display.flip()
        
    def update_index(self, increment):
        self.index += increment
        num_images = len(storage["albums"][self.album_name])
        if self.index >= num_images:
            self.index = 0
        elif self.index < 0:
            self.index = num_images - 1 
        self.image_load()
        
    @staticmethod
    def image_save(selected_album, current_img):
        for album in storage["albums"].values():
            for image in album.values():
                image[0] = 0
        chosen = storage["albums"][selected_album][current_img]
        chosen[0] = 1
        with open(DATA_FILE, 'w') as data_file:
            data_file.write(json.dumps(storage, indent=3))
        image_rect = pygame.Rect(scaled_size)
        hover = image_rect.collidepoint(pygame.mouse.get_pos())
        if hover and pygame.mouse.get_pressed()[0]:
            puzzle_data[f'current {puzzle_version}']["selected_image"] = IMAGES_PATH + selected_album + "/" + current_img
            with open(PUZZLE_FILE, 'w') as puzzle_file:
                puzzle_file.write(json.dumps(puzzle_data))
                load_puzzle()
        return image_rect
    
    def image_deletion(self):
        # Not finished yet
        pass

    def image_creation(self):
        # Not finished yet
        pass
        
def reload_chosen():
    for album in storage["albums"].values():
        for image in album.values():
            image[0] = 0
    with open(DATA_FILE, 'w') as data_file:
        data_file.write(json.dumps(storage, indent=3))

# GAME MODE
def timer(elapsed_time):
    hours = elapsed_time // 3600
    minutes = (elapsed_time % 3600) // 60
    seconds = elapsed_time % 60
    timer_text = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
    return timer_text

# Puzzle loading and running logic
def cut_image(image):
    piece_width = puzzle_size[0] // len(puzzle_matrix[0])
    piece_height = puzzle_size[1] // len(puzzle_matrix)
    pieces = []
    for row in range(len(puzzle_matrix)):
        for col in range(len(puzzle_matrix[0])):
            left, top = col * piece_width, row * piece_height
            piece_rect = pygame.Rect(left, top, piece_width, piece_height)
            piece_image = image.subsurface(piece_rect)

            piece_layer = pygame.Surface((piece_width, piece_height), pygame.SRCALPHA)
            
            if puzzle_version == GRID_CHOICES[0]:
                layer_path = pygame.image.load("images/assets/piece32.png").convert_alpha()
            else:
                layer_path = pygame.image.load("images/assets/piece45.png" if settings_data["size"] == SIZE_CHOICES[0] else "images/assets/piece60.png").convert_alpha()
            layer_path = pygame.transform.scale(layer_path, (piece_width, piece_height))
            layer_path.set_colorkey((0, 0, 0))

            piece_layer.blit(piece_image, (0, 0))
            piece_layer.blit(layer_path, (0, 0))

            pieces.append((piece_layer, (left, top)))

    return pieces

def load_puzzle():
    global puzzle_matrix, original_matrix, empty_position, puzzle_pieces, shuffled_pieces, puzzle_size, directions, cell_size, number_of_rows, original_image

    puzzle_version = settings_data["grid"]
    image_path = puzzle_data[f'current {puzzle_version}']['selected_image']
    puzzle_image = pygame.image.load(MAIN_PATH + image_path)

    directions = {"up": [4, 8, 12],
                  "down": [],
                  "left": [13, 14, 15],
                  "right": []}

    puzzle_matrix = np.array(puzzle_data[f'current {puzzle_version}']['matrix'])
    original_matrix = np.array(puzzle_data[f'puzzle {puzzle_version}'])
    empty_position = np.nonzero(puzzle_matrix == 0)
    number_of_rows = int(puzzle_version[0])
    cell_size = puzzle_size[0] // number_of_rows
    original_image = pygame.transform.scale(puzzle_image, puzzle_size)

    puzzle_pieces = cut_image(original_image)
    if np.max(puzzle_matrix) == 0:
        shuffled_pieces = shuffle_pieces(puzzle_pieces)
    empty_position = (len(puzzle_matrix) - 1, len(puzzle_matrix[0]) - 1)

load_puzzle()

def shuffle_pieces(puzzle_pieces):
    global puzzle_matrix
    numbers = list(range(len(puzzle_pieces)))
    np.random.shuffle(numbers)
    puzzle_matrix = np.array(numbers).reshape(puzzle_matrix.shape)
    save_puzzle_state()
    update_directions()

    for i, (_, (left, top)) in enumerate(puzzle_pieces):
        row, col = divmod(numbers[i], len(puzzle_matrix[0]))
        puzzle_pieces[i] = (puzzle_pieces[i][0], (col * cell_size, row * cell_size))

    return puzzle_pieces

def move_pieces(clicked_row, clicked_col):
    global puzzle_matrix

    zero_positions = np.nonzero(puzzle_matrix == 0)
    zero_row, zero_col = zero_positions[0][0], zero_positions[1][0]

    if clicked_row == zero_row:
        if clicked_col < zero_col:
            direction = "right"
        else:
            direction = "left"
    elif clicked_col == zero_col:
        if clicked_row < zero_row:
            direction = "down"
        else:
            direction = "up"
    else:
        return

    if direction in ["up", "down"]:
        pieces_to_move = puzzle_matrix[min(clicked_row, zero_row):max(clicked_row, zero_row) + 1, clicked_col]
    else:
        pieces_to_move = puzzle_matrix[clicked_row, min(clicked_col, zero_col):max(clicked_col, zero_col) + 1]
        
    if direction == "right":
        puzzle_matrix[clicked_row, clicked_col:zero_col + 1] = np.roll(puzzle_matrix[clicked_row, clicked_col:zero_col + 1], 1)
    elif direction == "left":
        puzzle_matrix[clicked_row, zero_col:clicked_col + 1] = np.roll(puzzle_matrix[clicked_row, zero_col:clicked_col + 1], -1)
    elif direction == "down":
        puzzle_matrix[min(clicked_row, zero_row):max(clicked_row, zero_row) + 1, clicked_col] = np.roll(pieces_to_move, 1)
    elif direction == "up":
        puzzle_matrix[min(clicked_row, zero_row):max(clicked_row, zero_row) + 1, clicked_col] = np.roll(pieces_to_move, -1)[:pieces_to_move.shape[0]]

    save_puzzle_state()
    update_directions()

def update_directions():
    global directions, puzzle_matrix
    zero_positions = np.nonzero(puzzle_matrix == 0)
    zero_row, zero_col = zero_positions[0][0], zero_positions[1][0]

    directions = {
        "up": puzzle_matrix[zero_row + 1:, zero_col].flatten().tolist() if zero_row < number_of_rows - 1 else [],
        "down": puzzle_matrix[:zero_row, zero_col].flatten().tolist() if zero_row > 0 else [],
        "left": puzzle_matrix[zero_row, zero_col + 1:].flatten().tolist() if zero_col < number_of_rows - 1 else [],
        "right": puzzle_matrix[zero_row, :zero_col].flatten().tolist() if zero_col > 0 else [],
    }
    
update_directions()

def save_puzzle_state():
    puzzle_matrix_list = puzzle_matrix.tolist()
    puzzle_data[f'current {puzzle_version}']['matrix'] = puzzle_matrix_list
    with open(PUZZLE_FILE, 'w') as puzzle_file:
        json.dump(puzzle_data, puzzle_file)
    
def update_puzzle_data(completed, sorted, current_time, current_moves, real_time):
    path_components = puzzle_data[f'current {puzzle_version}']['selected_image'].split('/')[2:]
    current_data = storage[path_components[0]]
    for component in path_components[1:]:
        if component in current_data:
            current_data = current_data[component]
        else:
            print("Something went wrong while updating puzzle data")
            return
    
    print(current_data)
    initial_chosen, initial_completed, initial_sorted, initial_moves, initial_time, _, _ = current_data
    initial_completed = int(completed)
    initial_sorted = int(sorted)
    initial_moves += current_moves
    
    initial_time_seconds = sum(int(x) * 60**i for i, x in enumerate(reversed(initial_time.split(":"))))
    total_time_seconds = initial_time_seconds + real_time
    hours, remainder = divmod(total_time_seconds, 3600)
    minutes, seconds = divmod(remainder, 60)
    formatted_time = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
    
    current_data = [initial_chosen, initial_completed, initial_sorted, initial_moves, formatted_time, current_time, current_moves]
    print(current_data)
    storage[path_components[0]][path_components[1]][path_components[2]] = current_data
    with open(DATA_FILE, 'w') as data_file:
        data_file.write(json.dumps(storage, indent=3))
        
# SETTINGS MODE
def play_music():
    music_file = settings_data["music"]["version"]
    volume = settings_data["music_volume"]
    pygame.mixer.music.load("misc/" + music_file + ".mp3")
    pygame.mixer.music.set_volume(volume)
    pygame.mixer.music.play(-1)
    
if settings_data["music"]["on"] is True:
    play_music()

def play_sound():
    sound_file = settings_data["sound"]["version"]
    volume = settings_data["sound"]["volume"]
    sound = pygame.mixer.Sound("misc/" + sound_file + ".ogg")
    sound.set_volume(volume)
    pygame.mixer.Sound.play(sound)

def set_hue():
    global hue_value
    if settings_data["style"] == STYLE_CHOICES[0]:
        hue_value = settings_data["gui color"][0]
        x = 0
    elif settings_data["style"] == STYLE_CHOICES[2]:
        hue_value = settings_data["gui color"][1]
        x = 1
        
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        hue_value = (hue_value - 1) % 360
    if keys[pygame.K_RIGHT]:
        hue_value = (hue_value + 1) % 360

    sprite_sheet.set_hue(hue_value)

    pygame.image.save(sprite_sheet.sheet, MAIN_PATH + SIZE_PATH + str(INT) + STYLE_PATH + "spritesheet.png")

    settings_data["gui color"][x] = hue_value
    with open(SETTINGS_FILE, "w") as settings_file:
        settings_file.write(json.dumps(settings_data))

def change_hue(image, hue):
    new_image = image.copy()

    for x in range(new_image.get_width()):
        for y in range(new_image.get_height()):
            color = new_image.get_at((x, y))

            h, s, v, a = pygame.Color(color)
            h = (h + hue) % 360
            new_color = pygame.Color(0, 0, 0, 0)
            new_color.hsva = (h, s, v, a)

            new_image.set_at((x, y), new_color)

    return new_image
        
def export():
    # Export data function, not finished yet
    pass

def reset():
    # Reset all data function, not finished yet
    pass

def info():
    # Not finished yet
    pass

# Stats
def stats():
    # Not finished yet
    pass

# Buttons
if settings_data["size"] == SIZE_CHOICES[0]:
    px12 = (12, 12)
    px14 = (14, 14)
    timer_button = Button((0, 0), (7, 3), px12)
    menu_button = Button((15, 0), (80, 3), px12)
    hint_button = Button((30, 0), (95, 3), px12)
    shuffle_button = Button((45, 0), (110, 3), px12)
    close_button = Button((60, 0), (125, 3), px12)
    finished_button = Button((75, 0), (7, 3), px12)
    not_finished_button = Button((90, 0), (7, 3), px12)
    delete_button = Button((105, 0), (95, 3), px12)
    continue_button = Button((120, 0), (110, 3), px12)
    settings_button = Button((135, 0), (7, 3), px12)
    quick_add_button = Button((34, 30), (22, 3), (55, 12))
    new_button = Button((150, 0), (80, 3), px12)
    info_button = Button((165, 0), (7, 3), px12)
    stats_button = Button((180, 0), (95, 3), px12)
    to_left_button = Button((0, 30), (44, 136), px14)
    to_right_button = Button((17, 30), (86, 136), px14)
    BACKGROUND_IMAGE = Image((0, 64), (0, 0), (WIDTH, HEIGHT))
    ver1_layer = sprite_sheet.get_sprite((147, 64), WIDTH, HEIGHT, color_key=(255, 0, 128))
    ver2_layer = sprite_sheet.get_sprite((294, 64), WIDTH, HEIGHT, color_key=(255, 0, 128))
else:
    px16 = (16, 16)
    px20 = (20, 20)
    timer_button = Button((0, 0), (8, 4), px16)
    menu_button = Button((19, 0), (118, 4), px16)
    hint_button = Button((38, 0), (138, 4), px16)
    shuffle_button = Button((57, 0), (158, 4), px16)
    close_button = Button((76, 0), (178, 4), px16)
    finished_button = Button((95, 0), (8, 4), px16)
    not_finished_button = Button((114, 0), (8, 4), px16)
    delete_button = Button((133, 0), (138, 4), px16)
    continue_button = Button((152, 0), (158, 4), px16)
    settings_button = Button((171, 0), (8, 4), px16)
    quick_add_button = Button((46, 38), (28, 4), (86, 16))
    new_button = Button((190, 0), (118, 4), px16)
    info_button = Button((209, 0), (8, 4), px16)
    stats_button = Button((228, 0), (138, 4), px16)
    to_left_button = Button((0, 38), (66, 191), px20)
    to_right_button = Button((23, 38), (116, 191), px20)
    BACKGROUND_IMAGE = Image((0, 84), (0, 0), (WIDTH, HEIGHT))
    ver1_layer = sprite_sheet.get_sprite((205, 84), WIDTH, HEIGHT, color_key=(255, 0, 128))
    ver2_layer = sprite_sheet.get_sprite((410, 84), WIDTH, HEIGHT, color_key=(255, 0, 128))

path_components = puzzle_data[f'current {puzzle_version}']['selected_image'].split('/')[2:]
current_data = storage[path_components[0]]
for component in path_components[1:]:
    current_data = current_data[component]
    
completed = current_data[1]
not_shuffled = current_data[2]
timer_text = current_data[5]
current_timer_text = timer_text
current_moves = current_data[6]

if completed:
    state_button = finished_button
else:
    state_button = not_finished_button

game_buttons = [timer_button, menu_button, hint_button, shuffle_button, close_button]
menu_buttons = [settings_button, quick_add_button, new_button, delete_button, continue_button, close_button]
select_buttons = [state_button, menu_button, delete_button, continue_button, close_button, to_left_button, to_right_button]
settings_buttons = [info_button, menu_button, stats_button, continue_button, close_button]

do_not_save = False

def button_check(pressed_button, run, current_mode, timer_running):
    global completed, not_shuffled, current_timer_text, current_moves, real_time
    do_not_save = False
    if pressed_button == close_button:
        run = False
        save_puzzle_state()
        update_puzzle_data(completed, not_shuffled, current_timer_text, current_moves, real_time)
        reload_chosen()
    elif pressed_button == settings_button:
        current_mode = "settings"
    elif pressed_button == menu_button:
        current_mode = "menu"
        timer_running = False
    elif pressed_button == continue_button:
        current_mode = "game"
    elif pressed_button == delete_button:
        if current_mode == "menu":
            album_deletion()
        else:
            # Not finished yet
            pass
    elif pressed_button == new_button:
        album_creation()
    elif pressed_button == quick_add_button:
        quick_game()
        current_mode = "game"
        not_shuffled = 1
        current_timer_text = "00:00:00"
        current_moves = 0
        do_not_save = True
    elif pressed_button in [not_finished_button, finished_button]:
        # Not finished yet
        pass
    elif pressed_button == info_button:
        info()
    elif pressed_button == stats_button:
        stats()

    return run, current_mode, timer_running, do_not_save

win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE,
                       win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) | win32con.WS_EX_LAYERED)

win32gui.SetLayeredWindowAttributes(hwnd, win32api.RGB(*(255, 0, 128)), 0, win32con.LWA_COLORKEY)
set_shadow_style(hwnd)

def data_check(current_mode):   
    if current_mode == "select":
        background_image = ver2_layer
        current_buttons = select_buttons

    elif current_mode == "game":
        current_buttons = game_buttons
        background_image = ver1_layer

    elif current_mode == "menu":
        current_buttons = menu_buttons
        background_image = ver1_layer

    elif current_mode == "settings":
        current_buttons = settings_buttons
        background_image = ver1_layer

    return background_image, current_buttons

# Game state
run = True
current_mode = "menu"
# Dragging and mouse interaction
dragging = False
can_move = False
start_pos = (0, 0)
pressed_button = None
clicked_piece = None
# Select mode
clicked_image = False
image_display = None
# Puzzle-related stuff
last_shuffle_time = 0
user_win = False
# Timer-related stuff
timer_running = False
timer_start_time = 0
elapsed_time = 0
real_time = elapsed_time
total_paused_time = 0
timer_button.disable()

while run:
    background_image, current_buttons = data_check(current_mode)
    current_time = pygame.time.get_ticks()
    
    screen.fill((255,0,128))
    screen.blit(BACKGROUND_IMAGE.img, (0, 0))
    screen.blit(background_image, (0, 0))
    
    mouse_pos = pygame.mouse.get_pos()

    for button in current_buttons:
        button.update(mouse_pos)

    for button in current_buttons:
        button.draw(screen)

    clock.tick(FPS)
    
    if current_mode == "menu":
        selected_album = render_albums()
    
    if current_mode == "select":
        if image_display is None or current_album != selected_album:
            selected_image = ImageRender(selected_album)
            image_display = True

        selected_image.render(screen)
        
    for event in pygame.event.get():
        if event.type == pygame.QUIT or event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            if current_mode == "game": #is this needed?
                save_puzzle_state()
            update_puzzle_data(completed, not_shuffled, current_timer_text, current_moves, real_time)
            reload_chosen()
            run = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if not non_movable_area.collidepoint(pygame.mouse.get_pos()):
                can_move = True
            if current_mode == "select" and event.button == 1:
                clicked_image_rect = ImageRender.image_save(selected_album, current_img)
                clicked_image = clicked_image_rect.collidepoint(event.pos)
                if clicked_image:
                    current_mode = "game"
                    shuffle_pieces(puzzle_pieces)
                last_click_time = pygame.time.get_ticks()
                
                if to_left_button.is_hovered(event.pos):
                    selected_image.update_index(-1)
                    last_image_switch_time = current_time

                if to_right_button.is_hovered(event.pos):
                    selected_image.update_index(1)
                    last_image_switch_time = current_time
            
            elif current_mode == "game":
                if non_movable_area.collidepoint(event.pos) and not timer_running:
                    timer_running = True
                    timer_button.enable()
                    timer_start_time = pygame.time.get_ticks()
                elif timer_button.rect.collidepoint(event.pos) and timer_running:
                    timer_running = False
                    timer_button.disable()
                    total_paused_time += pygame.time.get_ticks() - timer_start_time
                        
            if event.button == 1:
                dragging = True
                start_pos = pygame.mouse.get_pos()
                for button in current_buttons:
                    if button.rect.collidepoint(start_pos):
                        pressed_button = button
                        run, current_mode, timer_running, do_not_save = button_check(pressed_button, run, current_mode, timer_running)
                    if settings_data["sound"]["on"] is True and current_mode == "game" and non_movable_area.collidepoint(event.pos):
                        play_sound()

        elif event.type == pygame.MOUSEMOTION and can_move:
            new_pos = pygame.mouse.get_pos()
            offset_x = new_pos[0] - start_pos[0]
            offset_y = new_pos[1] - start_pos[1]
            window_coords[0] += offset_x
            window_coords[1] += offset_y
            move_win(window_coords)
            
        elif event.type == pygame.MOUSEBUTTONUP:
            dragging = False
            pressed_button = None
            can_move = False

    if current_mode == "game":
        if timer_running:
            current_time = pygame.time.get_ticks() - timer_start_time
            elapsed_time = (current_time + total_paused_time) // 1000
            real_time = elapsed_time
            hours, minutes, seconds = map(int, timer_text.split(':'))
            total_seconds = hours * 3600 + minutes * 60 + seconds
            elapsed_time += total_seconds
            current_timer_text = timer(elapsed_time)
        render_text(current_timer_text, (timer_area.x, timer_area.y), screen, timer_color)

        # Puzzle
        if pressed_button == shuffle_button and current_time - last_shuffle_time > 500:
            if not_shuffled:
                np.copyto(puzzle_matrix, original_matrix)
                user_win = False
                not_shuffled = 0
            else:
                shuffle_pieces(puzzle_pieces)
                not_shuffled = 1
            save_puzzle_state()
            update_directions()
            last_shuffle_time = current_time
            
        for row in range(len(puzzle_matrix)):
            for col in range(len(puzzle_matrix[0])):
                if puzzle_matrix[row][col] != 0:

                    piece_x = col * puzzle_size[0] // len(puzzle_matrix[0]) + non_movable_area.x
                    piece_y = row * puzzle_size[1] // len(puzzle_matrix) + non_movable_area.y
                    screen.blit(puzzle_pieces[puzzle_matrix[row][col] - 1][0], (piece_x, piece_y))

            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                mouse_x, mouse_y = pygame.mouse.get_pos()

                clicked_row, clicked_col = (mouse_y - non_movable_area.y) // cell_size, (mouse_x - non_movable_area.x) // cell_size

                if 0 <= clicked_row < len(puzzle_matrix) and 0 <= clicked_col < len(puzzle_matrix[0]):
                    if puzzle_matrix[clicked_row, clicked_col] != 0:
                        user_win = True
                        current_moves += 1
                        move_pieces(clicked_row, clicked_col)

        if pressed_button == hint_button:
            screen.blit(original_image, non_movable_area)
        if number_of_rows == 4 and puzzle_data[f'current {puzzle_version}']['matrix'] == puzzle_data[f'puzzle {puzzle_version}'] and user_win is True:
            completed = 1
            not_shuffled = 1
            user_win = False
            timer_running = False
            start_time = pygame.time.get_ticks()
            fade_in_end_time = start_time + 2 * 1000
            fade_out_start_time = start_time + 4 * 1000
            end_time = start_time + 6 * 1000
            alpha = 0
            text_alpha = 0
            fade_in_speed = 100 / (fade_in_end_time - start_time)
            fade_out_speed = 100 / (fade_out_start_time - fade_in_end_time)

            while pygame.time.get_ticks() < end_time:

                surface = pygame.Surface((non_movable_area.width, non_movable_area.height), pygame.SRCALPHA)
                surface.fill((0, 0, 0, alpha))
                print(alpha)
                screen.blit(surface, (non_movable_area.x, non_movable_area.y))

                render_text("You win !!", (win_area.x, win_area.y), screen, (255, 255, 255, text_alpha))

                if pygame.time.get_ticks() < fade_in_end_time:
                    alpha += fade_in_speed
                    text_alpha += fade_in_speed
                elif fade_in_end_time <= pygame.time.get_ticks() < fade_out_start_time:
                    alpha = max(0, alpha)
                    text_alpha = max(0, text_alpha)
                elif pygame.time.get_ticks() >= fade_out_start_time:

                    alpha -= fade_out_speed
                    text_alpha -= fade_out_speed

                alpha = max(0, alpha)
                text_alpha = max(0, text_alpha)
                pygame.display.flip()
                pygame.time.delay(16)
   
    pygame.display.flip()

pygame.quit()