"""
tree_app_zoom_pan.py
Lesnický kalkulátor s podporou zoomu, pan a přesnou referencí.
Podpora: stojící strom, DBH, ležící kmen, referenční objekt, kubatura, CSV export.
"""
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
import math, csv, os, datetime

# -------------------------
# Data: MADI tabulka (mm/rok) podle druhu a stanoviště
# -------------------------
MADI_TABLE = {
    "Smrk": {"živné": 5.0, "svěží": 4.0, "kyselé": 3.0, "suché": 2.5},
    "Borovice": {"živné": 4.0, "svěží": 3.5, "kyselé": 2.5, "suché": 2.0},
    "Buk": {"živné": 3.5, "svěží": 3.0, "kyselé": 2.5, "suché": 1.8},
    "Dub": {"živné": 3.0, "svěží": 2.5, "kyselé": 2.0, "suché": 1.5},
    "Jedle": {"živné": 4.5, "svěží": 3.5, "kyselé": 2.5, "suché": 2.0},
    "Listnaté_obecně": {"živné": 3.0, "svěží": 2.5, "kyselé": 2.0, "suché": 1.8},
    "Jehličnaté_obecně": {"živné": 4.5, "svěží": 3.5, "kyselé": 2.5, "suché": 2.0}
}

UHUL_CLASSES = [
    (1, 20, "I.", "1–20"),
    (21, 40, "II.", "21–40"),
    (41, 60, "III.", "41–60"),
    (61, 80, "IV.", "61–80"),
    (81, 100, "V.", "81–100"),
    (101, 120, "V.", "100+"),
    (121, 140, "V.", "100+"),
    (141, 9999, "V.", "100+"),
]

# -------------------------
# Pomocné výpočty
# -------------------------
def uhul_age_class(age):
    if age is None:
        return "bez určení"
    a = int(round(age))
    for lo, hi, code, label in UHUL_CLASSES:
        if lo <= a <= hi:
            return f"{code} ({label})"
    return "mimo rozsah"

def estimate_age_from_dbh(dbh_cm, species, site):
    if dbh_cm is None or dbh_cm <= 0:
        return (None, None)
    dbh_mm = dbh_cm * 10.0
    s = species
    if s not in MADI_TABLE:
        s = "Listnaté_obecně"
    site_dict = MADI_TABLE.get(s, {})
    madi = site_dict.get(site, None)
    if madi is None:
        madi = list(site_dict.values())[0] if site_dict else 2.0
    age = dbh_mm / madi
    return (age, madi)

def volume_cylinder(dbh_cm, length_m):
    r = (dbh_cm/100.0)/2.0
    return math.pi * r*r * length_m

def volume_cone(dbh_cm, length_m):
    r = (dbh_cm/100.0)/2.0
    return (1/3.0) * math.pi * r*r * length_m

# -------------------------
# GUI aplikace
# -------------------------
class TreeApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Tauberova pomsta")
        # layout
        left = tk.Frame(root)
        left.pack(side=tk.LEFT, padx=5, pady=5)
        right = tk.Frame(root)
        right.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)

        # Canvas
        self.canvas_w = 1000
        self.canvas_h = 700
        self.canvas = tk.Canvas(left, width=self.canvas_w, height=self.canvas_h, bg="lightgray")
        self.canvas.pack()
        self.canvas.bind("<Button-1>", self.on_click)
        self.canvas.bind("<ButtonPress-2>", self.start_pan)
        self.canvas.bind("<B2-Motion>", self.do_pan)
        self.canvas.bind("<MouseWheel>", self.do_zoom)  # Windows
        self.canvas.bind("<Button-4>", self.do_zoom)    # Linux scroll up
        self.canvas.bind("<Button-5>", self.do_zoom)    # Linux scroll down

        # ovládací panely
        self.setup_controls(right)

        # stav
        self.image = None
        self.display_image = None
        self.tk_img = None
        self.image_path = None
        self.points = []  # ukládá body v originálních pixelech (img_x,img_y)
        self.result = {}
        self.scale_factor = 1.0
        self.ref_pixels = None
        self.offset_x = 0
        self.offset_y = 0
        self.pan_start = None
        self.zoom_level = 1.0

    # -------------------------
    # Ovládací prvky
    # -------------------------
    def setup_controls(self, right):
        btn_frame = tk.Frame(right)
        btn_frame.pack(fill=tk.X)
        tk.Button(btn_frame, text="Načti fotku", command=self.load_image).pack(fill=tk.X)
        tk.Button(btn_frame, text="Vyčistit body", command=self.clear_points).pack(fill=tk.X, pady=2)

        # režim
        mode_frame = tk.LabelFrame(right, text="Režim měření")
        mode_frame.pack(fill=tk.X, pady=4)
        self.mode_var = tk.StringVar(value="standing")
        tk.Radiobutton(mode_frame, text="Stojící strom (pata+vrchol)", variable=self.mode_var, value="standing").pack(anchor="w")
        tk.Radiobutton(mode_frame, text="DBH (levý+pravý okraj)", variable=self.mode_var, value="dbh").pack(anchor="w")
        tk.Radiobutton(mode_frame, text="Referenční objekt (2 body)", variable=self.mode_var, value="ref").pack(anchor="w")
        tk.Radiobutton(mode_frame, text="Ležící kmen (zadání DBH)", variable=self.mode_var, value="lying").pack(anchor="w")

        # měřítko
        scale_frame = tk.LabelFrame(right, text="Měřítko (pixel -> m)")
        scale_frame.pack(fill=tk.X, pady=4)
        self.scale_method = tk.StringVar(value="reference")
        tk.Radiobutton(scale_frame, text="Použít referenci", variable=self.scale_method, value="reference").pack(anchor="w")
        tk.Radiobutton(scale_frame, text="Vzdálenost + FOV", variable=self.scale_method, value="distance").pack(anchor="w")

        # reference
        ref_frame = tk.Frame(right)
        ref_frame.pack(fill=tk.X, pady=2)
        tk.Label(ref_frame, text="Výška reference [m]:").grid(row=0, column=0, sticky="w")
        self.ref_height_entry = tk.Entry(ref_frame, width=8)
        self.ref_height_entry.grid(row=0, column=1, sticky="w")

        # distance
        dist_frame = tk.Frame(right)
        dist_frame.pack(fill=tk.X, pady=2)
        tk.Label(dist_frame, text="Vzdálenost ke stromu [m]:").grid(row=0, column=0, sticky="w")
        self.dist_entry = tk.Entry(dist_frame, width=8)
        self.dist_entry.grid(row=0, column=1, sticky="w")
        tk.Label(dist_frame, text="Vertikální FOV [deg]:").grid(row=1, column=0, sticky="w")
        self.fov_entry = tk.Entry(dist_frame, width=8)
        self.fov_entry.insert(0, "60")
        self.fov_entry.grid(row=1, column=1, sticky="w")

        # ležící kmen DBH
        lying_frame = tk.Frame(right)
        lying_frame.pack(fill=tk.X, pady=2)
        tk.Label(lying_frame, text="DBH [cm] pro ležící kmen:").grid(row=0, column=0, sticky="w")
        self.dbh_entry = tk.Entry(lying_frame, width=8)
        self.dbh_entry.grid(row=0, column=1, sticky="w")

        # species & site
        species_frame = tk.LabelFrame(right, text="Dřevina a stanoviště (MADI)")
        species_frame.pack(fill=tk.X, pady=4)
        tk.Label(species_frame, text="Dřevina:").grid(row=0, column=0, sticky="w")
        self.species_cb = ttk.Combobox(species_frame, values=list(MADI_TABLE.keys()), state="readonly")
        self.species_cb.set("Smrk")
        self.species_cb.grid(row=0, column=1, sticky="w")
        tk.Label(species_frame, text="Stanoviště:").grid(row=1, column=0, sticky="w")
        self.site_cb = ttk.Combobox(species_frame, values=["živné", "svěží", "kyselé", "suché"], state="readonly")
        self.site_cb.set("svěží")
        self.site_cb.grid(row=1, column=1, sticky="w")

        # kubatura
        vol_frame = tk.LabelFrame(right, text="Kubatura")
        vol_frame.pack(fill=tk.X, pady=4)
        tk.Label(vol_frame, text="Délka kmene [m]:").grid(row=0, column=0, sticky="w")
        self.length_entry = tk.Entry(vol_frame, width=8)
        self.length_entry.grid(row=0, column=1, sticky="w")
        self.vol_method = tk.StringVar(value="cylinder")
        tk.Radiobutton(vol_frame, text="Válec", variable=self.vol_method, value="cylinder").grid(row=1, column=0, sticky="w")
        tk.Radiobutton(vol_frame, text="Kužel", variable=self.vol_method, value="cone").grid(row=1, column=1, sticky="w")

        # akce
        action_frame = tk.Frame(right)
        action_frame.pack(fill=tk.X, pady=6)
        tk.Button(action_frame, text="Spočítat", command=self.calculate_all).pack(fill=tk.X)
        tk.Button(action_frame, text="Uložit CSV", command=self.save_csv).pack(fill=tk.X, pady=3)

    # -------------------------
    # Image utils
    # -------------------------
    def load_image(self):
        path = filedialog.askopenfilename(filetypes=[("Obrázky", "*.jpg *.jpeg *.png *.tif")])
        if not path: return
        img = Image.open(path).convert("RGB")
        self.image = img
        self.image_path = path
        self.zoom_level = 1.0
        self.offset_x = 0
        self.offset_y = 0
        self.points = []
        self.ref_pixels = None
        self.result = {}
        self.display_image_on_canvas()

    def display_image_on_canvas(self):
        if self.image is None: return
        iw, ih = self.image.size
        disp_w = int(iw * self.zoom_level)
        disp_h = int(ih * self.zoom_level)
        self.display_image = self.image.resize((disp_w, disp_h), Image.LANCZOS)
        self.tk_img = ImageTk.PhotoImage(self.display_image)
        self.canvas.delete("all")
        self.canvas.create_image(self.offset_x, self.offset_y, anchor="nw", image=self.tk_img, tags="IMG")
        self._draw_points()
        self._draw_instructions()

    def _image_to_display_coords(self, ix, iy):
        """Transform originální pixely na pozici na canvas"""
        x = ix * self.zoom_level + self.offset_x
        y = iy * self.zoom_level + self.offset_y
        return x, y

    def _display_to_image_coords(self, x, y):
        if self.display_image is None: return None
        ix = (x - self.offset_x)/self.zoom_level
        iy = (y - self.offset_y)/self.zoom_level
        ix = max(0, min(self.image.width-1, ix))
        iy = max(0, min(self.image.height-1, iy))
        return (ix, iy)

    # -------------------------
    # Kliky
    # -------------------------
    def on_click(self, event):
        if self.display_image is None: return
        img_coords = self._display_to_image_coords(event.x, event.y)
        if img_coords is None: return
        # uložíme jen originální pixely
        self.points.append((img_coords[0], img_coords[1]))
        self._draw_points()
        # reference
        if self.mode_var.get() == "ref" and len(self.points) >=2:
            p1 = self.points[-2]; p2=self.points[-1]
            dx = p2[0]-p1[0]; dy=p2[1]-p1[1]
            self.ref_pixels = math.hypot(dx, dy)
            cx, cy = self._image_to_display_coords((p1[0]+p2[0])/2, (p1[1]+p2[1])/2)
            self.canvas.create_text(cx, cy-12, text=f"{int(self.ref_pixels)} px", fill="blue", tags="pt")

    def _draw_points(self):
        self.canvas.delete("pt")
        for ix, iy in self.points:
            x, y = self._image_to_display_coords(ix, iy)
            r=4
            self.canvas.create_line(x-r, y, x+r, y, fill="red", width=2, tags="pt")
            self.canvas.create_line(x, y-r, x, y+r, fill="red", width=2, tags="pt")

    def clear_points(self):
        self.points = []
        self.ref_pixels = None
        self.canvas.delete("pt")
        self._draw_instructions()

    def _draw_instructions(self):
        self.canvas.delete("instr")
        txt = "1) Načti fotku\n2) Zvol režim\n3) Klikni body podle režimu\n4) Zvol měřítko\n5) Spočítej"
        self.canvas.create_text(10,10, anchor="nw", text=txt, fill="black", tags="instr", font=("Arial",10))

    # -------------------------
    # Pan & Zoom
    # -------------------------
    def start_pan(self, event):
        self.pan_start = (event.x, event.y)

    def do_pan(self, event):
        if self.pan_start is None: return
        dx = event.x - self.pan_start[0]
        dy = event.y - self.pan_start[1]
        self.offset_x += dx
        self.offset_y += dy
        self.pan_start = (event.x, event.y)
        self.display_image_on_canvas()

    def do_zoom(self, event):
        factor = 1.1 if getattr(event,'delta',0)>0 or getattr(event,'num',0)==4 else 0.9
        # zoom kolem středu canvasu
        cx = self.canvas_w/2
        cy = self.canvas_h/2
        ix, iy = self._display_to_image_coords(cx, cy)
        self.zoom_level *= factor
        # posun tak, aby bod (ix,iy) zůstal na stejném místě
        self.offset_x = cx - ix*self.zoom_level
        self.offset_y = cy - iy*self.zoom_level
        self.display_image_on_canvas()

    # -------------------------
    # Výpočty
    # -------------------------
    def calculate_all(self):
        if self.image is None:
            messagebox.showerror("Chyba", "Nejprve načti obrázek.")
            return

        mode = self.mode_var.get()
        scale_method = self.scale_method.get()

        # ----------------- pixel->m -----------------
        pixel_to_m = None
        used_method = None
        if scale_method=="reference":
            try: ref_h_m = float(self.ref_height_entry.get())
            except:
                messagebox.showerror("Chyba", "Zadej výšku referenčního objektu (m)."); return
            if self.ref_pixels is None:
                messagebox.showerror("Chyba", "Klikni 2 body referenčního objektu."); return
            pixel_to_m = ref_h_m / self.ref_pixels
            used_method = f"reference (ref_h={ref_h_m} m)"
        else:
            try:
                d = float(self.dist_entry.get())
                fov_deg = float(self.fov_entry.get())
            except:
                messagebox.showerror("Chyba", "Zadej vzdálenost a FOV."); return
            img_h = self.image.height
            fov_rad = math.radians(fov_deg)
            ang_one = fov_rad / img_h
            pixel_to_m = 2.0*d*math.tan(ang_one/2.0)
            used_method = f"distance (d={d} m, FOV={fov_deg}°)"

        result_lines = []

        # ----------------- Režimy -----------------
        if mode=="standing":
            if len(self.points)<2:
                messagebox.showwarning("Chyba","Klikni 2 body: pata+vrchol."); return
            p1=self.points[0]; p2=self.points[1]
            pix_h=abs(p2[1]-p1[1])
            tree_h_m = pix_h*pixel_to_m
            result_lines.append(f"Výška stromu: {tree_h_m:.2f} m ({used_method})")
            self.result['height_m']=tree_h_m

        elif mode=="dbh":
            if len(self.points)<2:
                messagebox.showwarning("Chyba","Klikni 2 body: levý+pravý okraj kmene."); return
            p1=self.points[0]; p2=self.points[1]
            pix_w=abs(p2[0]-p1[0])
            dbh_m = pix_w*pixel_to_m
            dbh_cm = dbh_m*100.0
            result_lines.append(f"DBH: {dbh_cm:.1f} cm ({used_method})")
            self.result['dbh_cm']=dbh_cm
            species = self.species_cb.get(); site=self.site_cb.get()
            age,madi=estimate_age_from_dbh(dbh_cm,species,site)
            result_lines.append(f"Věk: {age:.1f} let (MADI={madi:.2f}, {species},{site})")
            result_lines.append(f"ÚHÚL: {uhul_age_class(age)}")
            self.result.update({'age_years':age,'madi_mm_per_year':madi,'species':species,'site':site})

        elif mode=="lying":
            try:
                dbh_cm = float(self.dbh_entry.get())
            except:
                messagebox.showerror("Chyba","Zadej DBH ležícího kmene."); return
            species = self.species_cb.get(); site=self.site_cb.get()
            age,madi=estimate_age_from_dbh(dbh_cm,species,site)
            result_lines.append(f"Ležící kmen – DBH: {dbh_cm:.1f} cm, věk: {age:.1f} let ({uhul_age_class(age)})")
            self.result.update({'dbh_cm':dbh_cm,'age_years':age,'madi_mm_per_year':madi,'species':species,'site':site})

        # ----------------- kubatura -----------------
        length_text=self.length_entry.get().strip()
        if length_text:
            try:
                length_m=float(length_text)
                dbh_cm=self.result.get('dbh_cm',None)
                if dbh_cm is None: result_lines.append("Kubatura: nelze spočítat (chybí DBH).")
                else:
                    vol = volume_cylinder(dbh_cm,length_m) if self.vol_method.get()=="cylinder" else volume_cone(dbh_cm,length_m)
                    result_lines.append(f"Kubatura ({self.vol_method.get()}): {vol:.3f} m³ (DBH {dbh_cm:.1f} cm, délka {length_m} m)")
                    self.result.update({'volume_m3':vol,'length_m':length_m})
            except:
                result_lines.append("Kubatura: chyba vstupu délky kmene.")

        # ----------------- zobraz výsledky -----------------
        self.result['image_path']=self.image_path
        self.result['calc_time']=datetime.datetime.now().isoformat(sep=' ', timespec='seconds')
        if result_lines: messagebox.showinfo("Výsledky","\n".join(result_lines))
        self._show_result_on_canvas()

    def _show_result_on_canvas(self):
        self.canvas.delete("res")
        lines=[]
        if 'height_m' in self.result: lines.append(f"Výška: {self.result['height_m']:.2f} m")
        if 'dbh_cm' in self.result: lines.append(f"DBH: {self.result['dbh_cm']:.1f} cm")
        if 'age_years' in self.result: lines.append(f"Věk: {self.result['age_years']:.1f} let ({uhul_age_class(self.result['age_years'])})")
        if 'volume_m3' in self.result: lines.append(f"Kubatura: {self.result['volume_m3']:.3f} m³")
        if lines: self.canvas.create_text(10,self.canvas_h-20, anchor="w", text=" | ".join(lines), fill="darkgreen", font=("Arial",12), tags="res")

    # ----------------- CSV -----------------
    def save_csv(self):
        if not self.result:
            messagebox.showwarning("Žádné výsledky","Nejdříve spočítej výsledky."); return
        path=filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV","*.csv")])
        if not path: return
        row={k:self.result.get(k,'') for k in ['image_path','calc_time','height_m','dbh_cm','age_years','madi_mm_per_year','species','site','volume_m3','length_m']}
        file_exists=os.path.exists(path)
        with open(path,'a',newline='',encoding='utf-8') as f:
            writer = csv.DictWriter(f,row.keys())
            if not file_exists: writer.writeheader()
            writer.writerow(row)
        messagebox.showinfo("Uloženo",f"Výsledky uloženy do {path}")

# ------------------------- Spuštění -------------------------
if __name__=="__main__":
    root=tk.Tk()
    app=TreeApp(root)
    root.mainloop()
