import os, datetime, math
import numpy as np
import pytz
import customtkinter as ctk
from tkinter import messagebox
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import ephem
from skyfield.api import Loader, wgs84
from skyfield import almanac

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

def get_safe_events(t_obj, y_obj):
    y_arr = np.atleast_1d(y_obj)
    if len(y_arr) == 0: return []
    events = []
    for k in range(len(y_arr)):
        try: t_val = t_obj[k]
        except Exception: t_val = t_obj
        events.append((t_val, int(y_arr[k])))
    return events

class Menu29App(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("Kalkulator Mizwala & Simulasi Visual (Modul 29)")
        self.geometry("1200x750")
        ctk.set_appearance_mode("Dark")
        
        # --- PERBAIKAN SKYFIELD LOADER ---
        self.load_obj = Loader(BASE_DIR)
        self.eph = self.load_obj('de421.bsp')
        self.ts = self.load_obj.timescale()
        # ---------------------------------
        
        self.setup_ui()

    def get_header(self, width):
        return "\n".join(line.center(width) for line in ["By the Name of Allah", "KHGT Times 7.2 - Mizwala Simulator"])

    def create_input_row(self, parent, label_text, default_val):
        row = ctk.CTkFrame(parent, fg_color="transparent")
        row.pack(fill="x", padx=10, pady=2)
        ctk.CTkLabel(row, text=label_text, font=("Segoe UI", 12)).pack(side="left")
        entry = ctk.CTkEntry(row, width=80, justify="right")
        entry.insert(0, default_val)
        entry.pack(side="right")
        return entry

    def create_ymd_row(self, parent, dy, dm, dd):
        grid_frame = ctk.CTkFrame(parent, fg_color="transparent")
        grid_frame.pack(fill="x", padx=10, pady=(0, 10))

        ctk.CTkLabel(grid_frame, text="dd", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=0, pady=(0, 2))
        ctk.CTkLabel(grid_frame, text="mm", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=1, pady=(0, 2))
        ctk.CTkLabel(grid_frame, text="yyyy", font=("Segoe UI", 10), text_color="#9E9E9E").grid(row=0, column=2, pady=(0, 2))

        d = ctk.CTkEntry(grid_frame, width=45, justify="center"); d.insert(0, dd); d.grid(row=1, column=0, padx=(0, 5))
        m = ctk.CTkEntry(grid_frame, width=45, justify="center"); m.insert(0, dm); m.grid(row=1, column=1, padx=5)
        y = ctk.CTkEntry(grid_frame, width=70, justify="center"); y.insert(0, dy); y.grid(row=1, column=2, padx=(5, 0))
        return y, m, d

    def setup_ui(self):
        self.grid_columnconfigure(0, weight=0)
        self.grid_columnconfigure(1, weight=1)
        self.grid_rowconfigure(0, weight=1)

        self.sidebar = ctk.CTkFrame(self, width=320)
        self.sidebar.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)

        ctk.CTkLabel(self.sidebar, text="MIZWALA CALCULATOR", font=("Segoe UI", 18, "bold"), text_color="#00E5FF").pack(pady=20)
        
        now = datetime.datetime.now()
        
        frame_date = ctk.CTkFrame(self.sidebar, fg_color="#212121")
        frame_date.pack(fill="x", padx=15, pady=5)
        ctk.CTkLabel(frame_date, text="TANGGAL OBSERVASI", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=10, pady=(8, 2))
        self.entry_miz_year, self.entry_miz_month, self.entry_miz_day = self.create_ymd_row(frame_date, str(now.year), f"{now.month:02d}", f"{now.day:02d}")
        
        frame_loc = ctk.CTkFrame(self.sidebar, fg_color="#212121")
        frame_loc.pack(fill="x", padx=15, pady=5)
        ctk.CTkLabel(frame_loc, text="LOKASI OBSERVASI", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=10, pady=(8, 2))
        self.entry_miz_lat = self.create_input_row(frame_loc, "Lat (Lintang):", "-7.0667")
        self.entry_miz_lon = self.create_input_row(frame_loc, "Lon (Bujur):", "110.4100")
        self.entry_miz_elev = self.create_input_row(frame_loc, "Elevasi (m):", "230.0")
        self.entry_miz_tz = self.create_input_row(frame_loc, "Timezone:", "7.0")
        
        frame_opt = ctk.CTkFrame(self.sidebar, fg_color="#212121")
        frame_opt.pack(fill="x", padx=15, pady=5)
        ctk.CTkLabel(frame_opt, text="PARAMETER TONGKAT", font=("Segoe UI", 12, "bold")).pack(anchor="w", padx=10, pady=(8, 2))
        self.entry_miz_tinggi = self.create_input_row(frame_opt, "Tinggi (cm):", "100.0")
        self.entry_miz_waktu = self.create_input_row(frame_opt, "Waktu Awal:", "LIVE")
        self.entry_miz_step = self.create_input_row(frame_opt, "Interval (Mnt):", "10")

        self.btn_hitung = ctk.CTkButton(self.sidebar, text="▶ BUAT TABEL", font=("Segoe UI", 12, "bold"), command=self.calculate_mizwala)
        self.btn_hitung.pack(fill="x", padx=15, pady=10)
        
        self.btn_simulasi_miz = ctk.CTkButton(self.sidebar, text="👁️ BUKA SIMULATOR VISUAL", font=("Segoe UI", 12, "bold"), fg_color="#F57C00", hover_color="#E65100", command=self.buka_simulasi_mizwala)
        self.btn_simulasi_miz.pack(fill="x", padx=15, pady=5)

        self.main_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.main_frame.grid(row=0, column=1, sticky="nsew", padx=10, pady=10)
        self.textbox = ctk.CTkTextbox(self.main_frame, font=("Consolas", 13), wrap="none")
        self.textbox.pack(fill="both", expand=True)

    def calculate_mizwala(self):
        try:
            year, month, day = int(self.entry_miz_year.get()), int(self.entry_miz_month.get()), int(self.entry_miz_day.get())
            lat, lon, elev, tz = float(self.entry_miz_lat.get()), float(self.entry_miz_lon.get()), float(self.entry_miz_elev.get()), float(self.entry_miz_tz.get())
            tinggi_tongkat = float(self.entry_miz_tinggi.get())
            step_mnt = int(self.entry_miz_step.get())

            earth, sun = self.eph['earth'], self.eph['sun']
            loc = wgs84.latlon(lat, lon, elevation_m=elev)
            observer = earth + loc
            
            t0 = self.ts.utc(year, month, day, -int(tz))
            t1 = self.ts.utc(year, month, day, 24 - int(tz))
            
            f_rs = almanac.sunrise_sunset(self.eph, loc)
            t_rs, y_rs = almanac.find_discrete(t0, t1, f_rs)
            
            t_rise, t_set = None, None
            for t_ev, y_ev in get_safe_events(t_rs, y_rs):
                if y_ev == 1: t_rise = t_ev
                else: t_set = t_ev
                
            if t_rise is None or t_set is None:
                messagebox.showerror("Error", "Matahari tidak terbit/terbenam di lokasi ini pada tanggal tersebut (Anomali Lintang).")
                return

            tt_array = np.linspace(t_rise.tt, t_set.tt, 1440)
            t_search = self.ts.tt_jd(tt_array)
            alt_arr = observer.at(t_search).observe(sun).apparent().altaz()[0].degrees
            
            idx_noon = np.argmax(alt_arr)
            alt_noon = alt_arr[idx_noon]
            t_noon = self.ts.tt_jd(tt_array[idx_noon])
            noon_dt = t_noon.utc_datetime() + datetime.timedelta(hours=tz)
            
            zenith_noon = 90.0 - alt_noon
            bayangan_dzuhur = tinggi_tongkat * math.tan(math.radians(max(0, zenith_noon)))
            target_shadow_asr = tinggi_tongkat + bayangan_dzuhur
            
            alt_am, tt_am = alt_arr[:idx_noon], tt_array[:idx_noon]
            tt_dhuha = None
            diffs = alt_am - 4.5
            for i in range(len(diffs)-1):
                if diffs[i] <= 0 and diffs[i+1] > 0:
                    frac = abs(diffs[i]) / (abs(diffs[i]) + abs(diffs[i+1]) + 1e-9)
                    tt_dhuha = tt_am[i] + frac * (tt_am[i+1] - tt_am[i])
                    break
                    
            dhuha_str = "----"
            if tt_dhuha is not None:
                dhuha_dt_exact = self.ts.tt_jd(tt_dhuha).utc_datetime() + datetime.timedelta(hours=tz)
                dhuha_str = dhuha_dt_exact.strftime("%H:%M:%S")

            output_lines = []
            output_lines.append(self.get_header(90))
            output_lines.append("[ Tabel Bayangan Tongkat Istiwa / Mizwala ]".center(90))
            output_lines.append("")
            output_lines.append(f"* Tanggal    : {day:02d}/{month:02d}/{year}")
            output_lines.append(f"* Lokasi     : Lat {lat}, Lon {lon}, Elev {elev}m, TZ {tz}")
            output_lines.append(f"* Tongkat    : {tinggi_tongkat} cm")
            output_lines.append(f"* Waktu Dhuha: {dhuha_str} (Matahari Naik 4.5°)")
            output_lines.append(f"* Byg Dzuhur : {bayangan_dzuhur:.2f} cm (Bayangan Terpendek)")
            output_lines.append(f"* Target Ashr: {target_shadow_asr:.2f} cm (Tongkat + Byg Dzuhur)")
            output_lines.append("="*90)
            output_lines.append(f"{'Waktu (LT)':<12} | {'Alt Matahari':<14} | {'Arah Bayangan':<15} | {'Panjang Bayangan':<20}")
            output_lines.append("-" * 90)
            
            start_dt = t_rise.utc_datetime() + datetime.timedelta(hours=tz)
            end_dt = t_set.utc_datetime() + datetime.timedelta(hours=tz)
            
            menit_awal = (start_dt.minute // step_mnt) * step_mnt + step_mnt
            curr_dt = start_dt.replace(minute=0, second=0, microsecond=0) + datetime.timedelta(minutes=menit_awal)
            
            dhuha_marked, dzuhur_marked, ashar_marked = False, False, False
            
            while curr_dt <= end_dt:
                t_calc = self.ts.from_datetime((curr_dt - datetime.timedelta(hours=tz)).replace(tzinfo=pytz.utc))
                app = observer.at(t_calc).observe(sun).apparent()
                alt, az, _ = app.altaz()
                
                alt_deg, az_deg = alt.degrees, az.degrees
                
                if alt_deg > 0:
                    zenith = 90.0 - alt_deg
                    shadow_len = tinggi_tongkat * math.tan(math.radians(zenith))
                    shadow_az = (az_deg + 180.0) % 360.0
                    
                    waktu_str = curr_dt.strftime("%H:%M")
                    alt_str = f"{alt_deg:.2f}°"
                    arah_str = f"{shadow_az:.2f}°"
                    panjang_str = f"{shadow_len:.2f} cm"
                    
                    marker = ""
                    if curr_dt < noon_dt and alt_deg >= 4.5 and not dhuha_marked:
                        marker = "   << MASUK DHUHA"
                        dhuha_marked = True
                    elif curr_dt >= noon_dt and not dzuhur_marked:
                        marker = "   << ZAWAL / DZUHUR"
                        dzuhur_marked = True
                    elif curr_dt > noon_dt and shadow_len >= target_shadow_asr and not ashar_marked:
                        marker = "   << MASUK ASHAR"
                        ashar_marked = True
                    
                    output_lines.append(f"{waktu_str:<12} | {alt_str:<14} | {arah_str:<15} | {panjang_str}{marker}")
                
                curr_dt += datetime.timedelta(minutes=step_mnt)
                
            output_lines.append("=" * 90)
            output_lines.append("* Keterangan: Arah bayangan dihitung dari Utara (0°) searah jarum jam.")
            
            self.textbox.delete("1.0", "end")
            self.textbox.insert("1.0", "\n".join(output_lines))
            
        except Exception as e:
            messagebox.showerror("Error", str(e))

    def buka_simulasi_mizwala(self):
        try:
            year, month, day = int(self.entry_miz_year.get()), int(self.entry_miz_month.get()), int(self.entry_miz_day.get())
            lat, lon, elev, tz = float(self.entry_miz_lat.get()), float(self.entry_miz_lon.get()), float(self.entry_miz_elev.get()), float(self.entry_miz_tz.get())
            tinggi_tongkat = float(self.entry_miz_tinggi.get())
            waktu_input = self.entry_miz_waktu.get().strip().upper()
            
            is_live_mode = False
            jam_awal_desimal = 12.0
            
            if waktu_input == "" or waktu_input == "LIVE" or waktu_input == "SEKARANG":
                is_live_mode = True
            else:
                try:
                    waktu_input = waktu_input.replace(".", ":")
                    if ":" in waktu_input:
                        h_str, m_str = waktu_input.split(":")
                        jam_awal_desimal = int(h_str) + (int(m_str) / 60.0)
                    else:
                        jam_awal_desimal = float(waktu_input)
                except Exception:
                    is_live_mode = True 
            
            earth, sun = self.eph['earth'], self.eph['sun']
            loc = wgs84.latlon(lat, lon, elevation_m=elev)
            observer = earth + loc
            
            t0 = self.ts.utc(year, month, day, -int(tz))
            t1 = self.ts.utc(year, month, day, 24 - int(tz))
            f_rs = almanac.sunrise_sunset(self.eph, loc)
            t_rs, y_rs = almanac.find_discrete(t0, t1, f_rs)
            
            t_rise, t_set = None, None
            for t_ev, y_ev in get_safe_events(t_rs, y_rs):
                if y_ev == 1: t_rise = t_ev
                else: t_set = t_ev
                
            if t_rise is None or t_set is None:
                return messagebox.showerror("Error", "Matahari tidak terbit/terbenam pada tanggal ini.")
                
            dt_rise = t_rise.utc_datetime() + datetime.timedelta(hours=tz)
            dt_set = t_set.utc_datetime() + datetime.timedelta(hours=tz)
            start_hr = dt_rise.hour + (dt_rise.minute / 60.0)
            end_hr = dt_set.hour + (dt_set.minute / 60.0)
            
            win_sim = ctk.CTkToplevel(self)
            win_sim.title("Simulasi Visual Kompas Mizwala")
            win_sim.geometry("650x800")
            win_sim.attributes("-topmost", True)
            win_sim.configure(fg_color="#0A0A0A")
            
            ctk.CTkLabel(win_sim, text="SIMULASI ARAH BAYANGAN MATAHARI", font=("Segoe UI", 16, "bold"), text_color="#FFD54F").pack(pady=10)
            
            fig = plt.figure(figsize=(6, 6), facecolor='#0A0A0A')
            ax = fig.add_subplot(111, projection='polar')
            
            canvas_sim = FigureCanvasTkAgg(fig, master=win_sim)
            canvas_sim.get_tk_widget().pack(fill="both", expand=True, padx=10, pady=5)
            
            ctrl_frame = ctk.CTkFrame(win_sim, fg_color="#1E1E1E", corner_radius=10)
            ctrl_frame.pack(fill="x", padx=20, pady=10)
            top_ctrl = ctk.CTkFrame(ctrl_frame, fg_color="transparent")
            top_ctrl.pack(fill="x", padx=10, pady=5)
            
            lbl_waktu = ctk.CTkLabel(top_ctrl, text="Waktu Lokal: --:--:--", font=("Consolas", 16, "bold"), text_color="#00E676")
            lbl_waktu.pack(side="left", padx=10)
            
            self.miz_is_live = is_live_mode
            
            def toggle_live():
                self.miz_is_live = True
                btn_live.configure(fg_color="#2E7D32", text="🔴 MODE LIVE")
            
            btn_live = ctk.CTkButton(top_ctrl, text="🔴 MODE LIVE" if is_live_mode else "⏸️ MODE KUSTOM", 
                                     fg_color="#2E7D32" if is_live_mode else "#555555", width=120, command=toggle_live)
            btn_live.pack(side="right", padx=10)
            
            lbl_info = ctk.CTkLabel(ctrl_frame, text="Mengkalkulasi...", font=("Consolas", 12))
            lbl_info.pack(pady=5)
            
            slider_var = ctk.DoubleVar()
            max_radius = tinggi_tongkat * 3.0 
            
            def update_plot(val, update_slider=False):
                try:
                    jam_desimal = float(val)
                    jam = int(jam_desimal)
                    sisa_menit = (jam_desimal - jam) * 60.0
                    menit = int(sisa_menit)
                    detik = int(round((sisa_menit - menit) * 60.0))
                    
                    if detik >= 60: menit += 1; detik -= 60
                    if menit >= 60: jam += 1; menit -= 60
                    
                    if update_slider: slider_var.set(jam_desimal)
                    lbl_waktu.configure(text=f"Waktu Lokal: {jam:02d}:{menit:02d}:{detik:02d}")
                    
                    jam_safe = min(23, max(0, jam))
                    curr_local = datetime.datetime(year, month, day) + datetime.timedelta(hours=jam_safe, minutes=menit, seconds=detik)
                    curr_utc = curr_local - datetime.timedelta(hours=tz)
                    
                    t_calc = self.ts.from_datetime(curr_utc.replace(tzinfo=pytz.utc))
                    app = observer.at(t_calc).observe(sun).apparent()
                    alt, az, _ = app.altaz()
                    
                    ax.clear()
                    ax.set_theta_zero_location('N')
                    ax.set_theta_direction(-1) 
                    ax.set_facecolor('#121212')
                    ax.tick_params(colors='white')
                    ax.grid(color='#333333', linestyle='--')
                    ax.set_ylim(0, max_radius)
                    
                    ax.set_xticks(np.radians([0, 45, 90, 135, 180, 225, 270, 315]))
                    ax.set_xticklabels(['U (0°)', 'TL', 'T (90°)', 'TG', 'S (180°)', 'BD', 'B (270°)', 'BL'])
                    ax.set_yticklabels([]) 
                    
                    if alt.degrees > 0:
                        zenith = 90.0 - alt.degrees
                        shadow_len = tinggi_tongkat * math.tan(math.radians(zenith))
                        shadow_az = (az.degrees + 180.0) % 360.0
                        
                        ax.plot(0, 0, marker='o', color='white', markersize=8, label=f"Tongkat ({tinggi_tongkat}cm)")
                        display_shadow_len = min(shadow_len, max_radius)
                        ax.plot([0, math.radians(shadow_az)], [0, display_shadow_len], color='#00E5FF', linewidth=3.5, label="Arah Bayangan")
                        ax.plot(math.radians(az.degrees), max_radius * 0.9, marker='o', color='#FFD54F', markersize=14, label="Matahari")
                        
                        lbl_info.configure(text=f"Alt Mth: {alt.degrees:.1f}° | Arah Byg: {shadow_az:.1f}° | Pjg Byg: {shadow_len:.1f} cm", text_color="white")
                        ax.legend(loc='lower left', bbox_to_anchor=(-0.1, -0.15), facecolor='#0A0A0A', edgecolor='#333', labelcolor='white')
                    else:
                        lbl_info.configure(text="Matahari di bawah Ufuk (Malam Hari)", text_color="#FF5252")
                    
                    canvas_sim.draw_idle()
                except Exception as ex: print("Error rendering:", ex)

            def on_slider_drag(val):
                self.miz_is_live = False
                btn_live.configure(fg_color="#555555", text="⏸️ MODE KUSTOM")
                update_plot(val, update_slider=False)

            slider = ctk.CTkSlider(ctrl_frame, from_=start_hr, to=end_hr, variable=slider_var, command=on_slider_drag, progress_color="#F57C00", button_color="#E65100")
            slider.pack(fill="x", padx=20, pady=(10, 20))
            
            def live_loop():
                if not win_sim.winfo_exists(): return 
                if getattr(self, 'miz_is_live', False):
                    now = datetime.datetime.now()
                    jam_sekarang_desimal = now.hour + (now.minute / 60.0) + (now.second / 3600.0)
                    if start_hr <= jam_sekarang_desimal <= end_hr:
                        update_plot(jam_sekarang_desimal, update_slider=True)
                    else:
                        lbl_info.configure(text="Matahari di bawah Ufuk (Malam Hari)", text_color="#FF5252")
                        lbl_waktu.configure(text=f"Waktu Lokal: {now.strftime('%H:%M:%S')}")
                        ax.clear()
                        ax.set_theta_zero_location('N')
                        ax.set_theta_direction(-1) 
                        ax.set_facecolor('#121212')
                        ax.set_ylim(0, max_radius)
                        ax.set_xticks(np.radians([0, 45, 90, 135, 180, 225, 270, 315]))
                        ax.set_xticklabels(['U (0°)', 'TL', 'T (90°)', 'TG', 'S (180°)', 'BD', 'B (270°)', 'BL'])
                        ax.set_yticklabels([]) 
                        canvas_sim.draw_idle()
                        slider_var.set(start_hr)
                win_sim.after(1000, live_loop) 
            
            live_loop()
            
            if not is_live_mode:
                if start_hr <= jam_awal_desimal <= end_hr:
                    update_plot(jam_awal_desimal, update_slider=True)
                else:
                    messagebox.showwarning("Peringatan Waktu", f"Waktu yang Anda input ({waktu_input}) terjadi saat malam hari.\nSimulasi dikembalikan ke mode Live.")
                    self.miz_is_live = True
                    btn_live.configure(fg_color="#2E7D32", text="🔴 MODE LIVE")
            else:
                now = datetime.datetime.now()
                jam_sekarang_desimal = now.hour + (now.minute / 60.0) + (now.second / 3600.0)
                if not (start_hr <= jam_sekarang_desimal <= end_hr):
                    tengah_hari = (start_hr + end_hr) / 2.0
                    self.miz_is_live = False
                    btn_live.configure(fg_color="#555555", text="⏸️ MODE KUSTOM")
                    update_plot(tengah_hari, update_slider=True)
            
        except Exception as e:
            messagebox.showerror("Error", f"Gagal memuat simulasi: {e}")

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