import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
from PIL import Image, ImageTk
import threading
import os
import platform
import subprocess
import configparser
import webbrowser
from math import log, tan, radians, pi, degrees, atan, sinh

from downloader import CopernicusDownloader

CONFIG_FILE = "config.ini"

class MapSelector:
    def __init__(self, root):
        self.root = root
        self.root.title("Surface Current Downloader")

        self.config = configparser.ConfigParser()
        self.load_config()

        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True)

        self.frame_download = ttk.Frame(self.notebook)
        self.frame_config = ttk.Frame(self.notebook)
        self.frame_about = ttk.Frame(self.notebook)

        self.notebook.add(self.frame_download, text="Download Manager")
        self.notebook.add(self.frame_config, text="Configuration")
        self.notebook.add(self.frame_about, text="About")

        self.downloader = CopernicusDownloader(
            username=self.config.get("AUTH", "username", fallback=""),
            password=self.config.get("AUTH", "password", fallback="")
        )

        self.initialized = False
        self.scale = 1.0  # Facteur de zoom initial
        self.min_scale = 0.3  # Zoom minimum
        self.max_scale = 2.0  # Zoom maximum
        self.offset_x = 0  # Décalage horizontal
        self.offset_y = 0  # Décalage vertical
        self.start_pan_x = 0  # Position initiale du clic pour le déplacement
        self.start_pan_y = 0
        self.setup_download_tab()
        self.setup_config_tab()

    def load_config(self):
        if not os.path.exists(CONFIG_FILE):
            self.config["AUTH"] = {"username": "", "password": ""}
            with open(CONFIG_FILE, "w") as f:
                self.config.write(f)
        else:
            self.config.read(CONFIG_FILE)

    def save_config(self):
        with open(CONFIG_FILE, "w") as f:
            self.config.write(f)

    def setup_download_tab(self):
        # Canvas et image
        self.canvas = tk.Canvas(self.frame_download, cursor="cross")
        self.canvas.pack(fill=tk.BOTH, expand=True)
        
        filename = self.config['map']['filename'].strip('"')

        self.original_img = Image.open(filename)
        self.display_img = self.original_img.copy()
        self.tk_img = ImageTk.PhotoImage(self.display_img)
        self.img_id = self.canvas.create_image(self.offset_x, self.offset_y, anchor="nw", image=self.tk_img)

        self.canvas.bind("<Configure>", self.on_canvas_configure)
               
        self.canvas.bind("<Button-1>", self.on_click)
        self.canvas.bind("<B1-Motion>", self.on_drag)
        self.canvas.bind("<ButtonRelease-1>", self.on_release)
        self.canvas.bind("<MouseWheel>", self.setzoom)  # Windows
        self.canvas.bind("<Button-4>", self.setzoom)    # Linux scroll up
        self.canvas.bind("<Button-5>", self.setzoom)    # Linux scroll down
        self.canvas.bind("<ButtonPress-3>", self.start_pan)
        self.canvas.bind("<B3-Motion>", self.pan_image)

        self.start_x = self.start_y = None
        self.rect = None

        params_frame = ttk.Frame(self.frame_download) #, borderwidth=1, relief="solid")
        
        params_frame.pack(pady=5)
        #params_frame.pack(pady=5)

        self.lat1_var = tk.StringVar()
        self.lon1_var = tk.StringVar()
        self.lat2_var = tk.StringVar()
        self.lon2_var = tk.StringVar()

        ttk.Label(params_frame, text="Lat Min:").grid(row=0, column=0)
        ttk.Entry(params_frame, textvariable=self.lat1_var, width=10).grid(row=0, column=1)
        params_frame.grid_columnconfigure(2, minsize=20)
        ttk.Label(params_frame, text="Lon Min:").grid(row=0, column=3)
        ttk.Entry(params_frame, textvariable=self.lon1_var, width=10).grid(row=0, column=4)
        params_frame.grid_columnconfigure(5, minsize=20)

        ttk.Label(params_frame, text="Lat Max:").grid(row=1, column=0)
        ttk.Entry(params_frame, textvariable=self.lat2_var, width=10).grid(row=1, column=1)
        ttk.Label(params_frame, text="Lon Max:").grid(row=1, column=3)
        ttk.Entry(params_frame, textvariable=self.lon2_var, width=10).grid(row=1, column=4)

        self.duration_var = tk.StringVar(value="3")
        ttk.Label(params_frame, text="Days").grid(row=0, column=6)
        ttk.Combobox(params_frame, textvariable=self.duration_var, values=["1", "2", "3", "4", "5", "6", "7", "8", "9"], width=5).grid(row=1, column=6)

        btn_frame = ttk.Frame(self.frame_download)
        btn_frame.pack(pady=5)
        ttk.Button(btn_frame, text="Download", command=self.start_download).grid(row=0, column=1, padx=5)
        ttk.Button(btn_frame, text="Open Folder", command=self.open_download_folder).grid(row=0, column=2, padx=5)
 
        self.status = ttk.Label(self.frame_download, text="")
        self.status.pack()

        self.progress = ttk.Progressbar(self.frame_download, orient="horizontal", length=300, mode="indeterminate")
        self.progress.pack(pady=5)

        self.display_image()

    def on_canvas_configure(self, event):
        if not self.initialized:
            self.canvas_width = event.width
            self.canvas_height = event.height
            #print(f"configure canvas {self.original_img.width}")
            
            factor = self.canvas_width / self.original_img.width
            
            self.zoom(factor,0,-round(self.canvas_height / 2))  # Affichage initial sans zoom
            self.initialized = True


    def start_pan(self, event):
        self.start_pan_x = event.x
        self.start_pan_y = event.y

    def pan_image(self, event):
        dx = event.x - self.start_pan_x
        dy = event.y - self.start_pan_y
        self.offset_x += dx
        self.offset_y += dy
        self.canvas.move(self.img_id, dx, dy)
        self.start_pan_x = event.x
        self.start_pan_y = event.y
        self.redraw_rectangle_from_coords()
     
    def setzoom(self, event):
        # Define the zoom direction
        if event.num == 5 or event.delta == -120:
            zoom_factor = 0.9
        elif event.num == 4 or event.delta == 120:
            zoom_factor = 1.1
        else:
            return
        self.zoom(zoom_factor,event.x,event.y)     

    def zoom(self, zoom_factor, zoom_x=200, zoom_y=200):
    
        # Calculer le nouveau facteur de zoom
        new_scale = self.scale * zoom_factor
        if self.min_scale <= new_scale <= self.max_scale:
            self.scale = new_scale
            # Redimensionner l'image
            width = int(self.original_img.width * self.scale)
            height = int(self.original_img.height * self.scale)
            self.display_img = self.original_img.resize((width, height), Image.Resampling.LANCZOS)
            self.tk_img = ImageTk.PhotoImage(self.display_img)
            # Mettre à jour l'image sur le canvas
            self.canvas.itemconfig(self.img_id, image=self.tk_img)
            # Ajuster la position de l'image pour centrer le zoom sur le pointeur de la souris
            canvas_coords = self.canvas.coords(self.img_id)
            mouse_x = self.canvas.canvasx(zoom_x)
            mouse_y = self.canvas.canvasy(zoom_y)
            self.offset_x = mouse_x - (mouse_x - canvas_coords[0]) * zoom_factor
            self.offset_y = mouse_y - (mouse_y - canvas_coords[1]) * zoom_factor
            self.canvas.coords(self.img_id, self.offset_x, self.offset_y)
        self.redraw_rectangle_from_coords()
            

    def open_link(self, event=None):
        webbrowser.open_new("https://data.marine.copernicus.eu/register")

    def setup_config_tab(self):
        frame = self.frame_config

        ttk.Label(frame, text="Use the credential of your account ").pack(pady=5)
        account_link = tk.Label(frame, text="register on https://data.marine.copernicus.eu/register",fg="blue",cursor="hand2")
        account_link.pack(pady=5)
        account_link.bind("<Button-1>", self.open_link)
        ttk.Label(frame, text="Copernicus Marine 'username'").pack(pady=5)
        self.username_var = tk.StringVar(value=self.config.get("AUTH", "username", fallback=""))
        ttk.Entry(frame, textvariable=self.username_var, width=30).pack()

        ttk.Label(frame, text="Copernicus Marine 'password'").pack(pady=5)
        self.password_var = tk.StringVar(value=self.config.get("AUTH", "password", fallback=""))
        ttk.Entry(frame, textvariable=self.password_var, show="*", width=30).pack()

        ttk.Button(frame, text="Sauvegarder", command=self.save_credentials).pack(pady=10)

    def save_credentials(self):
        self.config["AUTH"]["username"] = self.username_var.get()
        self.config["AUTH"]["password"] = self.password_var.get()
        self.save_config()
        messagebox.showinfo("Info", "Identifiants sauvegardés avec succès.")
        # Met à jour le downloader avec les nouveaux identifiants
        self.downloader.username = self.username_var.get()
        self.downloader.password = self.password_var.get()
        
    def redraw_rectangle_from_coords(self):
        try:
            lat1 = float(self.lat1_var.get())
            lat2 = float(self.lat2_var.get())
            lon1 = float(self.lon1_var.get())
            lon2 = float(self.lon2_var.get())
    
            # Convert lat/lon to canvas pixel coordinates
            x1, y1 = self.xy_from_latlon(lat1, lon1)
            x2, y2 = self.xy_from_latlon(lat2, lon2)
    
            # Supprimer l'ancien rectangle s'il existe
            if self.rect:
                self.canvas.delete(self.rect)
    
            self.rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline="red", width=2)
        except ValueError:
            # Ignore si les champs ne sont pas valides
            pass

    def xy_from_latlon(self, lat, lon):
        lat_min = self.config.getfloat('map', 'map_lat_start')
        lat_max = self.config.getfloat('map', 'map_lat_end')
        lon_min = self.config.getfloat('map', 'map_lon_start')
        lon_max = self.config.getfloat('map', 'map_lon_end')

        canvas_w = self.canvas.winfo_width()
        canvas_h = self.canvas.winfo_height()
        # print(f"canvas = {canvas_w} {canvas_h}  scale = {self.scale}  offset = {self.offset_x} {self.offset_y}")

        # Longitude (linéaire)
        x_norm = (lon - lon_min) / (lon_max - lon_min)
        x = x_norm * self.scale * self.original_img.width + self.offset_x
    
        # Latitude (projection Mercator)
        mercator_min = log(tan(pi / 4 + radians(lat_min) / 2))  # Mercator
        mercator_max = log(tan(pi / 4 + radians(lat_max) / 2))  # Mercator
        mercator_lat = log(tan(pi / 4 + radians(lat) / 2))      # Mercator

        y_norm = (mercator_max - mercator_lat) / (mercator_max - mercator_min)
        y = y_norm * self.scale * self.original_img.height + self.offset_y
    
        return x, y
    
    def latlon_from_xy(self, x, y):
        # Coordonnées géographiques de la carte
        lat_min = self.config.getfloat('map', 'map_lat_start')
        lat_max = self.config.getfloat('map', 'map_lat_end')
        lon_min = self.config.getfloat('map', 'map_lon_start')
        lon_max = self.config.getfloat('map', 'map_lon_end')
        # print(f"latlon_from_xy dbg coord = {lat_min}-{lat_max}-{lon_min}-{lon_max}")
        
        # Calculer la position relative dans l’image (sans offset/zoom)
        img_x = (x - self.offset_x) / self.scale
        img_y = (y - self.offset_y) / self.scale
    
        if 0 <= img_x <= self.original_img.width and 0 <= img_y <= self.original_img.height:
            rel_x = img_x / self.original_img.width
            rel_y = img_y / self.original_img.height
    
            # Longitude : linéaire
            lon = lon_min + rel_x * (lon_max - lon_min)
    
            # Latitude : projection inverse de Mercator
            merc_min = log(tan(pi / 4 + radians(lat_min) / 2))  # Mercator
            merc_max = log(tan(pi / 4 + radians(lat_max) / 2))  # Mercator
            merc_y = merc_max - rel_y * (merc_max - merc_min)
            lat = degrees(atan(sinh(merc_y)))                   # Mercator Inverse
            return lat, lon
        else:
            return None, None  # En dehors de l'image

    def on_click(self, event):
        self.start_x, self.start_y = event.x, event.y
        if self.rect:
            self.canvas.delete(self.rect)
        self.rect = self.canvas.create_rectangle(self.start_x, self.start_y, event.x, event.y, outline="red")

    def on_drag(self, event):
        if self.rect:
            self.canvas.coords(self.rect, self.start_x, self.start_y, event.x, event.y)

    def on_release(self, event):
        lat1, lon1 = self.latlon_from_xy(self.start_x, self.start_y)
        lat2, lon2 = self.latlon_from_xy(event.x, event.y)
        self.lat1_var.set(f"{min(lat1, lat2):.4f}")
        self.lat2_var.set(f"{max(lat1, lat2):.4f}")
        self.lon1_var.set(f"{min(lon1, lon2):.4f}")
        self.lon2_var.set(f"{max(lon1, lon2):.4f}")

    def display_image(self):
        self.canvas_width = self.canvas.winfo_width() or self.original_img.width
        self.canvas_height = self.canvas.winfo_height() or self.original_img.height
        resized_img = self.original_img.resize((self.canvas_width, self.canvas_height), Image.Resampling.LANCZOS)
        self.tk_img = ImageTk.PhotoImage(resized_img)
        self.canvas.itemconfig(self.img_id, image=self.tk_img)
       
    def start_download(self):
        try:
            lat_min = float(self.lat1_var.get())
            lat_max = float(self.lat2_var.get())
            lon_min = float(self.lon1_var.get())
            lon_max = float(self.lon2_var.get())
            days = int(self.duration_var.get())
        except ValueError:
            messagebox.showerror("❌ Erreur", "Veuillez entrer des coordonnées et durée valides.")
            return
        self.progress.start(10)
        self.status.config(text="✅ Téléchargement en cours...")
        threading.Thread(target=self.download, args=(lat_min, lat_max, lon_min, lon_max, days), daemon=True).start()

    def download(self, lat_min, lat_max, lon_min, lon_max, days):
        try:
            
            grib_path = self.downloader.retrieve_grib2(lat_min, lat_max, lon_min, lon_max, days)
            #self.status.config(text="Conversion en GRIB2 en cours...")
            #grib_path = self.downloader.convert_to_grib2()
            self.status.config(text=f"✅ Téléchargement et conversion terminés : {os.path.basename(grib_path)}")
        except Exception as e:
            self.status.config(text=f"❌ Erreur : {e}")
        finally:
            self.progress.stop()
    
    def open_download_folder(self):
        folder_path = os.path.join(os.getcwd(), "downloads")
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
        if platform.system() == "Windows":
            os.startfile(folder_path)
        elif platform.system() == "Darwin":  # macOS
            subprocess.run(["open", folder_path])
        else:  # Linux
            subprocess.run(["xdg-open", folder_path])


if __name__ == "__main__":
    root = tk.Tk()
    root.geometry("900x600")
    app = MapSelector(root)
    root.mainloop()