#!/usr/bin/env python3
"""
Bigg Adventure EQEmu Server - Client Patcher & Launcher
Features:
 - Sleek Dark UI (Tkinter)
 - Downloads patch_manifest.json via HTTP/HTTPS
 - Auto-configures eqhost.txt (Host=192.168.4.6:5998)
 - Validates file integrity via SHA-256
 - Downloads missing / updated patch files (spells_us.txt, dbstr_us.txt, maps, UI)
 - Launches EverQuest: eqgame.exe patchme
"""

import os
import sys
import json
import hashlib
import urllib.request
import subprocess
import threading
import tkinter as tk
from tkinter import ttk, messagebox

# Default Server Configuration
MANIFEST_URL = "http://192.168.4.78:8080/patch_manifest.json"
PATCH_BASE_URL = "http://192.168.4.78:8080/patch_files/"
DEFAULT_EQHOST = "Host=192.168.4.78:5998"

class HeroesJourneyPatcher(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Bigg Adventure - Client Patcher & Launcher")
        self.geometry("700x480")
        self.resizable(False, False)

        # Style configuration (Sleek Dark Theme)
        self.configure(bg="#121212")
        self.style = ttk.Style(self)
        self.style.theme_use("clam")
        self.style.configure("TFrame", background="#121212")
        self.style.configure("TLabel", background="#121212", foreground="#E0E0E0", font=("Segoe UI", 10))
        self.style.configure("Header.TLabel", background="#121212", foreground="#00E5FF", font=("Segoe UI", 16, "bold"))
        self.style.configure("SubHeader.TLabel", background="#121212", foreground="#FFB300", font=("Segoe UI", 11, "italic"))
        self.style.configure("TProgressbar", thickness=15, troughcolor="#222222", background="#00E5FF")

        # UI Components
        self.create_widgets()

    def create_widgets(self):
        # Header Banner
        header_frame = ttk.Frame(self, padding=15)
        header_frame.pack(fill="x")

        title_label = ttk.Label(header_frame, text="HEROES JOURNEY", style="Header.TLabel")
        title_label.pack(anchor="w")

        subtitle_label = ttk.Label(header_frame, text="Dragons of Norrath / DoDH Era • 3-Class Multiclass Server", style="SubHeader.TLabel")
        subtitle_label.pack(anchor="w", pady=(2, 0))

        # News & Status Box
        self.news_text = tk.Text(self, height=10, bg="#1E1E1E", fg="#D4D4D4", insertbackground="white", relief="flat", highlightthickness=1, highlightbackground="#333333", font=("Consolas", 9), padding=10)
        self.news_text.pack(fill="both", expand=True, padx=15, pady=5)
        self.log_msg("Welcome to Bigg Adventure Patcher!\nClick 'CHECK FOR UPDATES' to sync your client files.\n" + "="*65 + "\n")

        # Progress Bar & Status Text
        progress_frame = ttk.Frame(self, padding=(15, 5))
        progress_frame.pack(fill="x")

        self.status_label = ttk.Label(progress_frame, text="Ready to check client files.")
        self.status_label.pack(anchor="w", pady=(0, 5))

        self.progress_bar = ttk.Progressbar(progress_frame, orient="horizontal", mode="determinate")
        self.progress_bar.pack(fill="x")

        # Bottom Button Panel
        button_frame = ttk.Frame(self, padding=15)
        button_frame.pack(fill="x")

        self.check_btn = tk.Button(button_frame, text="CHECK FOR UPDATES", bg="#2979FF", fg="white", font=("Segoe UI", 10, "bold"), relief="flat", activebackground="#2962FF", activeforeground="white", padx=15, pady=8, command=self.start_update_thread)
        self.check_btn.pack(side="left")

        self.play_btn = tk.Button(button_frame, text="PLAY HEROES JOURNEY", bg="#00C853", fg="white", font=("Segoe UI", 11, "bold"), relief="flat", activebackground="#009688", activeforeground="white", padx=25, pady=8, command=self.launch_game, state="normal")
        self.play_btn.pack(side="right")

    def log_msg(self, text):
        self.news_text.insert(tk.END, text + "\n")
        self.news_text.see(tk.END)

    def start_update_thread(self):
        self.check_btn.config(state="disabled")
        threading.Thread(target=self.run_patch_process, daemon=True).start()

    def get_sha256(self, filepath):
        if not os.path.exists(filepath):
            return None
        sha256 = hashlib.sha256()
        with open(filepath, 'rb') as f:
            while chunk := f.read(65536):
                sha256.update(chunk)
        return sha256.hexdigest()

    def update_eqhost(self, host_string):
        eqhost_path = os.path.join(os.getcwd(), "eqhost.txt")
        content = f"[LoginServer]\n{host_string}\n"
        try:
            with open(eqhost_path, "w") as f:
                f.write(content)
            self.log_msg(f"[✓] Configured eqhost.txt -> {host_string}")
        except Exception as e:
            self.log_msg(f"[!] Warning: Could not write eqhost.txt ({e})")

    def run_patch_process(self):
        self.status_label.config(text="Fetching server manifest...")
        self.log_msg(f"Connecting to manifest server: {MANIFEST_URL}")
        
        try:
            req = urllib.request.Request(MANIFEST_URL, headers={'User-Agent': 'HeroesJourneyPatcher/1.0'})
            with urllib.request.urlopen(req, timeout=10) as response:
                manifest_data = json.loads(response.read().decode())
        except Exception as e:
            self.log_msg(f"[!] Failed to fetch manifest: {e}")
            self.log_msg("[!] Falling back to local eqhost configuration.")
            self.update_eqhost(DEFAULT_EQHOST)
            self.status_label.config(text="Offline mode ready.")
            self.check_btn.config(state="normal")
            return

        server_news = manifest_data.get("news", "")
        if server_news:
            self.log_msg(f"\nSERVER NEWS:\n{server_news}\n" + "-"*65)

        eqhost_setting = manifest_data.get("eqhost", DEFAULT_EQHOST)
        self.update_eqhost(eqhost_setting)

        files_to_patch = manifest_data.get("files", [])
        total_files = len(files_to_patch)

        if total_files == 0:
            self.log_msg("[✓] No custom patch files required.")
            self.status_label.config(text="Client is up to date!")
            self.progress_bar["value"] = 100
            self.check_btn.config(state="normal")
            return

        updated_count = 0
        for idx, file_info in enumerate(files_to_patch):
            rel_path = file_info["path"]
            expected_hash = file_info["sha256"]
            local_hash = self.get_sha256(rel_path)

            progress_pct = int(((idx + 1) / total_files) * 100)
            self.progress_bar["value"] = progress_pct
            self.status_label.config(text=f"Checking file ({idx+1}/{total_files}): {rel_path}")

            if local_hash != expected_hash:
                self.log_msg(f"[-->] Downloading updated asset: {rel_path}")
                download_url = PATCH_BASE_URL + rel_path
                dir_name = os.path.dirname(rel_path)
                if dir_name:
                    os.makedirs(dir_name, exist_ok=True)
                
                try:
                    urllib.request.urlretrieve(download_url, rel_path)
                    updated_count += 1
                except Exception as e:
                    self.log_msg(f"[!] Error downloading {rel_path}: {e}")

        self.log_msg(f"\n[✓] Patch check completed! ({updated_count} files updated)")
        self.status_label.config(text="Client is fully patched and ready!")
        self.check_btn.config(state="normal")

    def launch_game(self):
        eqgame_exe = os.path.join(os.getcwd(), "eqgame.exe")
        if not os.path.exists(eqgame_exe):
            messagebox.showerror("Launcher Error", f"eqgame.exe was not found in current directory:\n{os.getcwd()}\n\nPlease place this patcher in your EverQuest client directory.")
            return

        self.log_msg("Launching EverQuest (eqgame.exe patchme)...")
        try:
            subprocess.Popen([eqgame_exe, "patchme"], cwd=os.getcwd())
            self.after(2000, self.destroy)
        except Exception as e:
            messagebox.showerror("Launch Failed", f"Could not start eqgame.exe:\n{e}")

if __name__ == "__main__":
    app = HeroesJourneyPatcher()
    app.mainloop()
