# -*- coding: utf-8 -*-
"""qibla

Automatically generated by Colab.

Original file is located at
    https://colab.research.google.com/drive/1UK-U7P-Ab71HAPQJe0c4NNwdZ0TKaGxG
"""

import math

from dataclasses import dataclass

@dataclass(frozen=True)
class QiblaDirection:
    decimal: float  # decimal degrees, 0..360
    degree: str

def direction (lat : float, long: float) -> QiblaDirection:


  φ_Location = lat
  λ_Location = long
  φ_Kaabah = 21.4225
  λ_Kaabah = 39.8262
  Difference_Longitude   = abs(λ_Location-λ_Kaabah )


  #Calculation of Qibla Direction
  import math

  A = math.sin(math.radians(abs(Difference_Longitude)))
  B = math.cos(math.radians(φ_Location))*math.tan(math.radians(φ_Kaabah))
  C = math.sin(math.radians(φ_Location))  * math.cos(math.radians(Difference_Longitude))
  D = A/(B-C)
  θ     = math.degrees(math.atan(D))

  #Determine the Azimuth of the Qibla
  if Difference_Longitude > 180:
      delta_λ = 360 - Difference_Longitude
  else:
      delta_λ = Difference_Longitude

  if θ > 0:
      if λ_Location > λ_Kaabah:
          quadrant = "UB"  # Utara Barat
      elif λ_Location <= λ_Kaabah:
          quadrant = "UT"  # Utara Timur
      elif λ_Location < 0:
          if c >= 180:
              quadrant = "UB"
          else:
              quadrant = "UT"
  elif θ < 0:
      if λ_Location > λ_Kaabah:
          quadrant = "SB"  # Selatan Barat
      elif λ_Location <= λ_Kaabah:
          quadrant = "ST"  # Selatan Timur
      elif λ_Location < 0:
          if c >= 180:
              quadrant = "SB"
          else:
              quadrant = "ST"

  if quadrant == "UB":
      azimuth_kiblat = 360 - θ
  elif quadrant == "SB":
      azimuth_kiblat = 180 - θ
  elif quadrant == "UT":
      azimuth_kiblat = θ
  elif quadrant == "ST":
      azimuth_kiblat = 180 + θ

  # To Convert in Degree Form

  degrees = int(azimuth_kiblat)
  decimal_part = azimuth_kiblat - degrees
  minutes_total = decimal_part = 60
  minutes = int(minutes_total)
  seconds = round((minutes_total - minutes) * 60)
  #print(f'The azimuth of the Qibla for Location with coordinate {φ_Location} Latitude, {λ_Location} Longitude, is {degrees}° {minutes}′ {seconds}″')
  outputdegree = f"{degrees}° {minutes}′ {seconds}″"

  return QiblaDirection(decimal=azimuth_kiblat, degree=outputdegree)



def dailyqibla(lat,long,ele,year,month,day,timezone,tolerance):

  from datetime import datetime, timedelta, date
  from skyfield.api import load, wgs84
  from skyfield import almanac
  import numpy as np

  # ---------- Inputs ----------
  lat_location = lat
  long_location = long
  #ele = elev
  #year, month, day = 2025, 10, 14
  #timezone = 8                 # UTC+8 (Malaysia)
  #tolerance = 5.0              # +/- degrees

  # ---------- Setup ----------




  qibla_direction = direction (lat, long)
  qibla_az = qibla_direction.decimal
  

  # Opposite azimuth (wrapped)
  opp_az = (qibla_az - 180.0) % 360.0
  min_alt_deg = 0.0            # >0: strictly above geometric horizon
  # min_alt_deg = -0.833       # common sunrise standard incl. refraction + solar radius


  ts = load.timescale()
  eph = load('de440s.bsp')
  earth, sun = eph['earth'], eph['sun']
  location = earth + wgs84.latlon(lat_location, long_location, elevation_m=ele)

  # Local-day window [00:00, 24:00) local
  t0 = ts.utc(year, month, day, 0 - timezone, 0, 0)
  t1 = ts.utc(year, month, day, 24 - timezone, 0, 0)

  def angular_diff_deg(a, b):
      """Smallest signed difference a-b on a circle (-180..+180]."""
      return ((a - b + 180.0) % 360.0) - 180.0

  def make_sector_fn(target_az_deg, tol_deg, min_alt):
      """Return a vectorized predicate for almanac.find_discrete()."""
      def in_sector(t):
          alt, az, _ = location.at(t).observe(sun).apparent().altaz()
          az_err = np.abs(angular_diff_deg(az.degrees, target_az_deg))
          return (alt.degrees >= min_alt) & (az_err <= tol_deg)
      # Hints for root finder
      in_sector.step_days = 0.001       # ~1.44 minutes
      in_sector.rough_period = 0.5      # ~12 hours between changes
      return in_sector

  def find_intervals(predicate, t0, t1):
      """Return list of (entry_time, exit_time) when predicate is True in [t0,t1]."""
      times, states = almanac.find_discrete(t0, t1, predicate)
      intervals = []

      # If already True at t0, prepend so we capture opening interval
      if predicate(t0):
          times = ts.tt_jd(np.insert(times.tt, 0, t0.tt))
          states = np.insert(states, 0, True)

      for i in range(len(times) - 1):
          if states[i]:
              intervals.append((times[i], times[i+1]))

      # If last state remains True up to t1, close it
      if len(times) > 0 and states[-1]:
          intervals.append((times[-1], t1))

      return intervals

  def to_local(dt_utc):  # UTC -> local (UTC+8)
      return dt_utc + timedelta(hours=timezone)

  # Build predicates
  fn_qibla = make_sector_fn(qibla_az, tolerance, min_alt_deg)
  fn_oppo  = make_sector_fn(opp_az,  tolerance, min_alt_deg)

  # Solve for both targets
  intervals_qibla = find_intervals(fn_qibla, t0, t1)
  intervals_oppo  = find_intervals(fn_oppo,  t0, t1)

  # ---------- Report ----------
  date_str = f"{year:04d}-{month:02d}-{day:02d}"

  def describe(label, target_az, intervals, predicate):
      """Return formatted string summary of the intervals for given azimuth."""
      output_lines = []
      if not intervals:
          msg = (f"[{label}] No Sun azimuth within {target_az:.2f}° ±{tolerance}° "
                f"on {date_str}.")
          #print(msg)
          return msg

      header = (f"[{label}] Sun azimuth ~ {target_az:.2f}° ±{tolerance}° "
                f"on {date_str}.")
      #print(header)
      output_lines.append(header)

      for entry, exit_ in intervals:
          dt_start_local = to_local(entry.utc_datetime())
          dt_end_local   = to_local(exit_.utc_datetime())
          alt_e, az_e, _ = location.at(entry).observe(sun).apparent().altaz()
          line = (f"  • Entry ~ {dt_start_local:%H:%M:%S} | Exit ~ {dt_end_local:%H:%M:%S}  ")
          #print(line)
          output_lines.append(line)

      return "\n".join(output_lines)

  # Example usage
  z = describe("QIBLA", qibla_az, intervals_qibla, fn_qibla)
  y = describe("OPPOSITE (Qibla - 180°)", opp_az, intervals_oppo, fn_oppo)

  return z, y

def multiday_qibla(lat, lon, ele, timezone, y, m, d_start, num_days, tolerance,
                   csv_filename=None):
    """
    Run dailyqibla() for a sequence of days and (optionally) save a CSV.
    - lat, lon, ele, timezone: observer
    - y, m, d_start: start date
    - num_days: how many days to compute (integer)
    - tolerance: azimuth tolerance (degrees)
    - csv_filename: if provided, save a CSV of windows per day

    Returns: list of dicts, one per day:
      {"date": "YYYY-MM-DD", "qibla": <str>, "opposite": <str>}
    """
    from datetime import date, timedelta
    import csv

    out = []

    # Prepare CSV if requested
    if csv_filename:
        with open(csv_filename, "w", newline="") as f:
            w = csv.writer(f)
            w.writerow(["Date", "Label", "Entry (Local)", "Exit (Local)"])

    # Helper to parse the lines from dailyqibla() “describe(...)” strings
    def extract_rows(label, block_text):
        """
        From a describe() block, yield (label, entry, exit) tuples.
        Lines look like: "  • Entry ~ 14:23:11 | Exit ~ 14:39:45"
        """
        if not block_text or "No Sun azimuth within" in block_text:
            return []
        rows = []
        for line in block_text.splitlines():
            line = line.strip()
            if line.startswith("• Entry ~") or line.startswith("•"):
                # normalize bullets that may be "•" or "- •"
                parts = line.replace("•", "").replace("Entry ~", "").split("|")
                if len(parts) >= 2:
                    entry = parts[0].strip()
                    exit_ = parts[1].replace("Exit ~", "").strip()
                    rows.append((label, entry, exit_))
        return rows

    start_date = date(y, m, d_start)
    for i in range(num_days):
        this_date = start_date + timedelta(days=i)
        yy, mm, dd = this_date.year, this_date.month, this_date.day

        qibla_txt, opp_txt = dailyqibla(
            lat, lon, ele, yy, mm, dd, timezone, tolerance
        )

        out.append({
            "date": this_date.strftime("%Y-%m-%d"),
            "qibla": qibla_txt,
            "opposite": opp_txt
        })

        # Print compact console summary
        print(f"\n=== {this_date:%Y-%m-%d} ===")
        print(qibla_txt)
        print(opp_txt)

        # Append to CSV if requested
        if csv_filename:
            rows_q = extract_rows("QIBLA", qibla_txt)
            rows_o = extract_rows("OPPOSITE", opp_txt)
            with open(csv_filename, "a", newline="") as f:
                w = csv.writer(f)
                if rows_q:
                    for _, e, x in rows_q:
                        w.writerow([this_date.strftime("%Y-%m-%d"), "QIBLA", e, x])
                else:
                    w.writerow([this_date.strftime("%Y-%m-%d"), "QIBLA", "-", "-"])
                if rows_o:
                    for _, e, x in rows_o:
                        w.writerow([this_date.strftime("%Y-%m-%d"), "OPPOSITE", e, x])
                else:
                    w.writerow([this_date.strftime("%Y-%m-%d"), "OPPOSITE", "-", "-"])

    if csv_filename:
        print(f"\n✅ Saved multi-day Qibla windows to {csv_filename}")

    return out


# Import required libraries
import math
from skyfield.api import load, N, E, wgs84  # Skyfield for astronomical calculations
import calendar  # For handling dates and times
import matplotlib.pyplot as plt
import numpy as np


# Load planetary ephemeris data (precise astronomical positions)
eph = load('de440s.bsp')
planets = load('de440s.bsp')

# Get Earth and Sun objects from the ephemeris
earth = planets['earth']
sun = planets['sun']

def qiblacompass(latitude, longitude, year,month,day, timezone,time_hour,time_minute):

  #azimuth kiblat
  qibla_direction = direction(latitude, longitude)
  location = earth + wgs84.latlon(latitude, longitude, elevation_m=0)


  # Create timescale object and set observation time
  ts = load.timescale()
  t0 = ts.utc(year, month, day)
  t1 = ts.utc(year, month, day + 1)

  sun_astro = location.at(ts.utc(year, month, day, time_hour-timezone, time_minute)).observe(sun)
  sun_app = sun_astro.apparent()
  sun_alt, sun_az, distance = sun_app.altaz()

  fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(8, 8))
  ax.set_theta_direction(-1)
  ax.set_theta_zero_location('N')

  # Plot Qibla Direction (blue line)
  qibla_direction_rad = np.deg2rad(qibla_direction.decimal)
  ax.plot([0, qibla_direction_rad], [0, 1], label= f'Qibla: { qibla_direction.decimal:.2f}°', color='blue',linestyle='--', linewidth=2)

  # Plot Sun Azimuth (orange line)
  sun_direction_rad = np.deg2rad(sun_az.degrees)
  # Plot a line from center (0,0) to edge (1) in sun's direction
  ax.plot(
      [0, sun_direction_rad],[0, 1],label=f'Sun Az: {sun_az.degrees:.2f}°', color='orange',linestyle='-.', linewidth=2
  )

  plt.title(f'Visualization of Qibla and Sun Azimuth\nLat: {latitude}°  | Long: {longitude}° \n{day} {month} {year}, {time_hour}:{time_minute} Local Time\n', pad=20)
  plt.legend(loc='upper right')
  plt.show()



