#!/usr/bin/python3

import re
import xml.etree.ElementTree as ET
from urllib.request import urlopen

PIB_URL = "https://pibs.nats.co.uk/operational/pibs/PIB.xml"


# ----------------------------
# Coordinate parsing
# ----------------------------
def dms_to_decimal(coord):
    """
    Convert DMS string to decimal degrees.
    Handles:
      DDMMSSN
      DDMMN
      DDDMMSSW
      DDDMMW
    """
    coord = coord.strip().upper()

    match = re.match(r"(\d+)([NSEW])", coord)
    if not match:
        return None

    value, hemi = match.groups()

    try:
        if hemi in ("N", "S"):
            if len(value) == 6:  # DDMMSS
                deg = int(value[0:2])
                minutes = int(value[2:4])
                seconds = int(value[4:6])
            elif len(value) == 4:  # DDMM
                deg = int(value[0:2])
                minutes = int(value[2:4])
                seconds = 0
            else:
                return None
        else:
            if len(value) == 7:  # DDDMMSS
                deg = int(value[0:3])
                minutes = int(value[3:5])
                seconds = int(value[5:7])
            elif len(value) == 5:  # DDDMM
                deg = int(value[0:3])
                minutes = int(value[3:5])
                seconds = 0
            else:
                return None

        decimal = deg + minutes / 60 + seconds / 3600

        if hemi in ("S", "W"):
            decimal *= -1

        return decimal

    except Exception:
        return None


# ----------------------------
# Text helpers
# ----------------------------
def normalize_text(text):
    return re.sub(r"\s+", " ", text.replace("\n", " ")).strip()


def extract_sentence(text, keyword):
    """
    Extract sentence starting at keyword until a safe terminator.
    """
    pattern = rf"{keyword}[:\s]*(.*?)(?:\.|$|LOWER:|UPPER:|SFC|FL\d+)"
    match = re.search(pattern, text, re.IGNORECASE)
    return match.group(1).strip() if match else ""


# ----------------------------
# Area parsing
# ----------------------------
def parse_area_bounded(text):
    text = normalize_text(text)

    area_text = extract_sentence(text, r"(?:WI\s+)?AREA BOUNDED BY")
    if not area_text:
        return []

    tokens = re.split(r"\s*-\s*", area_text)

    points = []
    for token in tokens:
        parts = token.split()

        if len(parts) >= 2:
            lat = dms_to_decimal(parts[0])
            lon = dms_to_decimal(parts[1])

            if lat is not None and lon is not None:
                points.append((lat, lon))

    # Remove duplicate closing point
    if len(points) > 2 and points[0] == points[-1]:
        points.pop()

    return points


def parse_circle(text):
    """
    Parse circular areas:
    e.g. "RADIUS 5NM CENTRED ON 512345N 0012345W"
    """
    text = normalize_text(text)

    match = re.search(
        r"RADIUS\s+(\d+(?:\.\d+)?)\s*NM.*?(?:CENTRE|CENTER|CENTRED ON|CENTERED ON)\s+(\d+[NS])\s+(\d+[EW])",
        text,
        re.IGNORECASE,
    )

    if not match:
        return None

    radius_nm = float(match.group(1))
    lat = dms_to_decimal(match.group(2))
    lon = dms_to_decimal(match.group(3))

    if lat is None or lon is None:
        return None

    return {
        "type": "circle",
        "center": (lat, lon),
        "radius_nm": radius_nm,
    }


# ----------------------------
# Altitude parsing
# ----------------------------
def extract_altitudes(text):
    text = normalize_text(text)

    lower = None
    upper = None

    lower_match = re.search(r"LOWER:\s*([^ ]+)", text, re.IGNORECASE)
    upper_match = re.search(r"UPPER:\s*([^ ]+)", text, re.IGNORECASE)

    if lower_match:
        lower = lower_match.group(1)
    elif "SFC" in text:
        lower = "SFC"

    if upper_match:
        upper = upper_match.group(1)

    return lower, upper


# ----------------------------
# Name extraction
# ----------------------------
def extract_name(text, index):
    text = normalize_text(text)

    # Try NOTAM ID
    match = re.search(r"\b([A-Z]\d{4}/\d{2})\b", text)
    if match:
        return match.group(1)

    # Fallback: first few words
    return f"PIB_{index}"


# ----------------------------
# Main parsing
# ----------------------------
def parse_pib(xml_data):
    root = ET.fromstring(xml_data)

    airspaces = []

    idx = 0

    for item in root.iter():
        if item.tag.endswith("ItemE"):
            text = normalize_text("".join(item.itertext()))

            if "AREA BOUNDED" in text or "RADIUS" in text:
                name = extract_name(text, idx)
                lower, upper = extract_altitudes(text)

                # Polygon
                points = parse_area_bounded(text)

                if points:
                    airspaces.append({
                        "type": "polygon",
                        "name": name,
                        "points": points,
                        "lower": lower,
                        "upper": upper,
                    })
                    idx += 1
                    continue

                # Circle fallback
                circle = parse_circle(text)
                if circle:
                    airspaces.append({
                        "type": "circle",
                        "name": name,
                        "center": circle["center"],
                        "radius_nm": circle["radius_nm"],
                        "lower": lower,
                        "upper": upper,
                    })
                    idx += 1

    return airspaces


# ----------------------------
# OpenAir writing
# ----------------------------
def write_openair(airspaces, filename="output.txt"):
    with open(filename, "w") as f:
        for asp in airspaces:
            f.write("AC R\n")  # Default restricted (can refine later)
            f.write(f"AN {asp['name']}\n")

            if asp["lower"]:
                f.write(f"AL {asp['lower']}\n")
            if asp["upper"]:
                f.write(f"AH {asp['upper']}\n")

            if asp["type"] == "polygon":
                for lat, lon in asp["points"]:
                    f.write(f"DP {lat:.6f} {lon:.6f}\n")

            elif asp["type"] == "circle":
                lat, lon = asp["center"]
                f.write(f"V X={lat:.6f} {lon:.6f}\n")
                f.write(f"DC {asp['radius_nm']}\n")

            f.write("\n")


# ----------------------------
# Main
# ----------------------------
def main():
    #print("Downloading PIB XML...")
    #xml_data = urlopen(PIB_URL).read()
    with open("PIB.xml") as f:
       xml_data = f.read()

    print("Parsing...")
    airspaces = parse_pib(xml_data)

    print(f"Found {len(airspaces)} airspaces")

    print("Writing OpenAir...")
    write_openair(airspaces)

    print("Done.")


if __name__ == "__main__":
    main()

