import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
from mutagen.mp3 import MP3, EasyMP3
import base64
import tempfile
import json
import os
import pygame

from mutagen.id3 import ID3, APIC


class LevelDesigner:
    def __init__(self, root):
        self.root = root
        self.root.title("Level Designer")
        initial_width = 600
        initial_height = 400

        self.root.geometry(
            f"{initial_width}x{initial_height}"
        )  # Set initial window size
        self.root.minsize(width=initial_width, height=initial_height)
        self.song_path = ""
        self.notes = []

        self.select_button = tk.Button(
            root, text="Select Song", command=self.select_song
        )
        self.select_button.pack()

        self.canvas_frame = tk.Frame(root)
        self.canvas_frame.pack(fill="both", expand=True)

        self.canvas = tk.Canvas(self.canvas_frame, bg="white")
        self.canvas.pack(side="top", fill="both", expand=True)

        self.h_scrollbar = tk.Scrollbar(
            self.canvas_frame, orient="horizontal", command=self.canvas.xview
        )
        self.h_scrollbar.pack(side="bottom", fill="x")
        self.canvas.configure(xscrollcommand=self.h_scrollbar.set)

        self.canvas.bind("<Button-1>", self.add_note)
        self.canvas.bind("<Button-3>", self.delete_note)

        self.root.bind("<space>", self.auto_add_note)

        self.save_button_visible = False
        self.save_button_height = 0

        self.arrows_visible = False

        self.save_button = tk.Button(root, text="Save Level", command=self.save_level)
        self.save_button.pack()
        self.save_button_height = self.save_button.winfo_height()
        self.save_button.pack_forget()

        self.arrow_buttons_frame = tk.Frame(root)
        self.arrow_buttons_frame.pack()

        self.song = None
        self.meta = None

        self.left_button = tk.Button(
            self.arrow_buttons_frame,
            text="←",
            command=lambda: self.select_arrow("left"),
            bg="red",
        )
        self.left_button.pack(side="left")

        self.up_button = tk.Button(
            self.arrow_buttons_frame,
            text="↑",
            command=lambda: self.select_arrow("up"),
            bg="green",
        )
        self.up_button.pack(side="left")

        self.down_button = tk.Button(
            self.arrow_buttons_frame,
            text="↓",
            command=lambda: self.select_arrow("down"),
            bg="blue",
        )
        self.down_button.pack(side="left")

        self.right_button = tk.Button(
            self.arrow_buttons_frame,
            text="→",
            command=lambda: self.select_arrow("right"),
            bg="yellow",
        )
        self.right_button.pack(side="left")

        self.canvas_frame.bind("<Configure>", self.update_canvas_height)
        self.canvas.bind("<Configure>", self.redraw_notes)

        self.add_controls()
        self.current_pos: float = 0.0

        pygame.mixer.init()
        self.song_playing = False

    def init_song(self):
        if self.song_path:
            pygame.mixer.music.load(self.song_path)
            self.song_playing = False
            self.cursor = None

    def update_cursor(self):
        self.canvas.delete("cursor")
        if self.song_playing:
            current_time = self.current_pos + pygame.mixer.music.get_pos() / 1000.0
        else:
            current_time = self.current_pos

        x = current_time / self.song_length * self.canvas_width

        canvas_width = self.canvas.winfo_width()  # Get current canvas width
        visible_x1 = self.canvas.canvasx(0)  # Get the leftmost visible x coordinate
        visible_x2 = visible_x1 + canvas_width  # Get the rightmost visible x coordinate

        # # Calculate the new scroll region based on the cursor's position
        if x < visible_x1 + 50:  # If cursor is about to leave from the left
            new_x1 = max(0, x - canvas_width / 2)
            self.canvas.xview_moveto(new_x1 / self.canvas_width)
        elif x > visible_x2 - 50:  # If cursor is about to leave from the right
            new_x1 = max(0, x - canvas_width / 2)
            self.canvas.xview_moveto(new_x1 / self.canvas_width)

        # Draw the cursor
        self.cursor = self.canvas.create_line(
            x, 0, x, self.canvas.winfo_height(), fill="red", tags="cursor"
        )

        if self.song_playing:
            self.root.after(100, self.update_cursor)

    def play_pause_song(self):
        if self.song_playing:
            print("Paused")
            pygame.mixer.music.pause()
            self.current_pos += pygame.mixer.music.get_pos() / 1000.0
            self.song_playing = False
        else:
            print("Unpaused")
            pygame.mixer.music.play(start=self.current_pos)
            self.song_playing = True
        self.update_cursor()

    def move_cursor(self, amount):
        if self.song_playing:
            self.current_pos += pygame.mixer.music.get_pos() / 1000.0
            self.current_pos = max(0, min(self.current_pos + amount, self.song_length))
            pygame.mixer.music.play(start=self.current_pos)
        else:
            self.current_pos = max(0, min(self.current_pos + amount, self.song_length))
        self.update_cursor()

    def backward_1_second(self):
        self.move_cursor(-1)

    def backward_half_second(self):
        self.move_cursor(-0.5)

    def forward_1_second(self):
        self.move_cursor(1)

    def forward_half_second(self):
        self.move_cursor(0.5)

    def backward_5_seconds(self):
        self.move_cursor(-5)

    def forward_5_seconds(self):
        self.move_cursor(5)

    def add_controls(self):
        control_frame = tk.Frame(self.root)
        control_frame.pack()

        backward_half_second_button = tk.Button(
            control_frame, text="<< 0.5s", command=self.backward_half_second
        )
        backward_half_second_button.pack(side="left")

        backward_1_second_button = tk.Button(
            control_frame, text="<< 1s", command=self.backward_1_second
        )
        backward_1_second_button.pack(side="left")

        backward_button = tk.Button(
            control_frame, text="<< 5s", command=self.backward_5_seconds
        )
        backward_button.pack(side="left")

        play_pause_button = tk.Button(
            control_frame, text="PP", command=self.play_pause_song
        )
        play_pause_button.pack(side="left")

        forward_button = tk.Button(
            control_frame, text=">> 5s", command=self.forward_5_seconds
        )
        forward_button.pack(side="left")

        forward_1_second_button = tk.Button(
            control_frame, text=">> 1s", command=self.forward_1_second
        )
        forward_1_second_button.pack(side="left")

        forward_half_second_button = tk.Button(
            control_frame, text=">> 0.5s", command=self.forward_half_second
        )
        forward_half_second_button.pack(side="left")

    def get_canvas_width(self):
        canvas_width = self.canvas.winfo_width()
        return canvas_width

    def redraw_notes(self, event):
        self.canvas.delete("notes")
        canvas_height = (
            self.canvas_frame.winfo_height() - self.arrow_buttons_frame.winfo_height()
        )
        self.canvas_height = canvas_height  # Store canvas height for future use
        for note_time, arrow in self.notes:
            x = (
                note_time
                / float(MP3(self.song_path).info.length)
                * self.get_canvas_width()
            )
            color = self.get_arrow_color(arrow)
            self.canvas.create_rectangle(
                x - 2, 0, x + 2, self.canvas_height, fill=color, tags="notes"
            )

    def update_canvas_height(self, event):
        canvas_height = (
            self.canvas_frame.winfo_height()
            - self.arrow_buttons_frame.winfo_height()
            - self.save_button_height
        )
        self.canvas.config(height=canvas_height)
        self.generate_visualizations()

    def select_song(self):
        file_path = filedialog.askopenfilename(
            filetypes=[("MP3 files", "*.mp3"), ("RHYS files", "*.rhys")]
        )
        if file_path:
            self.song_path = file_path  # Assign the song path here
            if file_path.endswith(".rhys"):
                self.load_rhys_file(file_path)
            self.init_song()
            self.generate_visualizations()
            self.save_button.pack()
            self.arrow_buttons_frame.pack()

    def generate_visualizations(self):
        audio = MP3(self.song_path)
        meta = EasyMP3(self.song_path)
        song_length = audio.info.length
        song_name = meta["title"][0]
        song_artist = meta["artist"][0]
        self.song_length = song_length

        self.canvas.delete("all")

        mins = int(song_length / 60)
        secs = song_length % 60
        self.canvas.create_text(
            20, 10, text=f"{song_name}", anchor="nw", font=("Arial", 12)
        )
        self.canvas.create_text(
            20, 25, text=f"By: {song_artist}", anchor="nw", font=("Arial", 12)
        )
        self.canvas.create_text(
            20,
            40,
            text=f"Song Length: {mins}m:{secs}:s",
            anchor="nw",
            font=("Arial", 12),
        )

        canvas_height = (
            self.canvas_frame.winfo_height() - self.arrow_buttons_frame.winfo_height()
        )
        self.canvas.config(height=canvas_height)

        self.canvas_width = song_length * 100
        self.canvas.config(scrollregion=(0, 0, self.canvas_width, canvas_height))

        for sec in range(int(song_length) + 1):
            x = sec * 100
            self.canvas.create_line(x, 0, x, canvas_height, fill="gray")
            if sec % 5 == 0:
                self.canvas.create_text(
                    x + 10,
                    canvas_height - 20,
                    text=f"{sec}s",
                    font=("Arial", 10),
                    anchor="nw",
                )
            elif sec % 1 == 0:
                self.canvas.create_line(x, 0, x, canvas_height, fill="lightgray")

        self.update_cursor()

    def load_rhys_file(self, file_path):
        with open(file_path, "r") as f:
            data = json.load(f)
            encoded_audio = data["audio"]
            self.notes = data["notes"]

            # Decode the base64 audio and save it temporarily
            decoded_audio = base64.b64decode(encoded_audio)
            temp_audio_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
            temp_audio_file.write(decoded_audio)
            temp_audio_file.close()

            self.song_path = (
                temp_audio_file.name
            )  # Update the song_path to the temporary file

            self.generate_visualizations()
            for note_time, arrow in self.notes:
                x = note_time * 100
                color = self.get_arrow_color(arrow)
                self.canvas.create_rectangle(x - 2, 0, x + 2, 200, fill=color)

    def auto_add_note(self, event):
        print("Auto add")
        
        if self.song_playing:
            current_time = self.current_pos + pygame.mixer.music.get_pos() / 1000.0
        else:
            current_time = self.current_pos

        x = current_time / self.song_length * self.canvas_width
        
        time = x / self.get_canvas_width() * float(MP3(self.song_path).info.length)
        self.notes.append((time, self.selected_arrow))
        color = self.get_arrow_color(self.selected_arrow)
        self.canvas.create_rectangle(
            x - 2,
            0,
            x + 2,
            self.canvas_frame.winfo_height(),
            fill=color,
            tags=f"{time}",
        )

    def add_note(self, event):
        x = self.canvas.canvasx(
            event.x
        )  # Adjust x coordinate based on canvas scroll position
        time = x / self.get_canvas_width() * float(MP3(self.song_path).info.length)
        self.notes.append((time, self.selected_arrow))
        color = self.get_arrow_color(self.selected_arrow)
        self.canvas.create_rectangle(
            x - 2,
            0,
            x + 2,
            self.canvas_frame.winfo_height(),
            fill=color,
            tags=f"{time}",
        )

    def delete_note(self, event):
        x = self.canvas.canvasx(
            event.x
        )  # Adjust x coordinate based on canvas scroll position
        canvas_width = self.get_canvas_width()
        closest_note = None
        closest_distance = float("inf")

        delta = 4

        # Find the closest note to the mouse click
        for note_time, _ in self.notes:
            note_x = note_time / float(MP3(self.song_path).info.length) * canvas_width
            distance = abs(note_x - x)
            if distance < closest_distance and distance <= delta:
                closest_note = (note_time, _)
                self.canvas.delete(f"{note_time}")
                closest_distance = distance

        if closest_note:
            self.notes.remove(closest_note)
            self.redraw_notes(event)

    def select_arrow(self, direction):
        self.selected_arrow = direction

    def get_arrow_color(self, direction):
        if direction == "up":
            return "green"
        elif direction == "down":
            return "blue"
        elif direction == "left":
            return "red"
        elif direction == "right":
            return "yellow"

    def save_from_mp3(self):
        encoded_audio = ""
        with open(self.song_path, "rb") as f:
            encoded_audio = base64.b64encode(f.read()).decode("utf-8")

        # Get metadata information
        audio = EasyMP3(self.song_path)
        artist = audio.get("artist", [""])[0]
        album = audio.get("album", [""])[0]
        title = audio.get("title", [""])[0]
        track_time = int(audio.info.length)

        # Fallback to MP3 and ID3 to get cover art
        mp3_audio = MP3(self.song_path, ID3=ID3)
        cover_art_data = ""
        cover_art_filename = ""

        # Extract cover art
        for tag in mp3_audio.tags.values():
            if isinstance(tag, APIC):
                cover_art_data = base64.b64encode(tag.data).decode("utf-8")
                cover_art_filename = (
                    f"{os.path.splitext(os.path.basename(self.song_path))[0]}_cover.jpg"
                )


        formatted_notes = [{"t": note[0], "a": note[1]} for note in self.notes]

        # Fail-safe for missing metadata
        if not artist:
            artist = "Unknown Artist"
        if not album:
            album = "Unknown Album"
        if not title:
            title = "Unknown Track"

        song_data = {
            "notes": formatted_notes,
            "artist": artist,
            "album": album,
            "title": title,
            "track_time": track_time,
            "audio": encoded_audio,
            "cover_art": cover_art_data,
        }

        file_name = filedialog.asksaveasfilename(
            parent=root, title="Save as rhys:", filetypes=[("RHYS files", "*.rhys")]
        )

        if file_name:
            with open(f"{file_name}", "w", encoding="utf-8") as f:
                json.dump(song_data, f)
            print(f"Level saved to {file_name}")

            messagebox.showinfo("Success", f"Level saved to {file_name}")
        else:
            messagebox.showerror("Error", "You must provide a filename")

    def update_rhys(self):
        with open(self.song_path, "r+", encoding="utf-8") as f:
            data = json.load(f)
            data["notes"] = self.notes
            f.seek(0)
            json.dump(data, f)
            f.truncate()
        print("Notes added to rhys file")

    def save_level(self):
        if self.song_path:
            file_extension = os.path.splitext(self.song_path)[1]
            if file_extension == ".mp3":
                self.save_from_mp3()
            elif file_extension == ".rhys":
                self.update_rhys()


root = tk.Tk()
app = LevelDesigner(root)

root.mainloop()
