#!/usr/bin/python3

import os
import sys
import re
import datetime
import time
import xml.etree.ElementTree as ET
import gzip

import pyproj
from shapely.geometry import Point, Polygon
from shapely.strtree import STRtree
import functools
import shapely
from shapely.ops import transform

import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

notamList = []

def timestamp():
        return(time.strftime("%a %d/%m/%Y %H:%M:%S"))

def convertDDddddToDDMMSS(decimal, ll):
    dec = decimal
    nsew = ""
    if ll.upper() == "LAT":
       nsew = "N"
       if dec < 0:
          nsew = "S" 
    else: # LON
       nsew = "E"
       if dec < 0:
          nsew = "W" 
    if dec < 0:
       dec *= -1
    dd = int("%d" % (dec))
    dec = dec - dd
    dec *= 60
    mm = int("%d" % (dec))
    dec = dec - mm
    dec *= 60
    ss = int("%.0f" % (dec))
    
    if ss >= 60:
       ss = 0
       mm += 1
    if mm >= 60:
       mm = 0
       dd += 1

    #print("%f  %s    %02d:%02d:%02d%s" % (decimal, ll, dd, mm, ss, nsew))

    if ll.upper() == "LAT":
       return("%02d:%02d:%02d %s" % (dd, mm, ss, nsew))
    else:
       return("%03d:%02d:%02d %s" % (dd, mm, ss, nsew))
    

def parseAlt(text):
    text = text.upper()

    if "FL" in text:
        return float(re.findall(r"\d+", text)[0]) * 100
    if "FT" in text:
        return float(re.findall(r"\d+", text)[0])
    if "GND" in text or "SFC" in text:
        return 0
    if "UNL" in text:
        return 50000

    return float(re.findall(r"\d+", text)[0])


def skipNotam(notamData, dateStr):
    # Only interested in stuff for specified data
    # Not interested in Aerodrome NOTAMS
    if notamData["Section"] == "ADSection" and notamData["PIBSection"] == "AD":
       return(True)

    if "StartValidity" in notamData and "EndValidity" in notamData:
       start = notamData["StartValidity"][:6]
       if notamData["EndValidity"][:4] == "PERM":
           end = "999999"
       else:
           end = notamData["EndValidity"][:6]
       #print("%s   %s    %s" % (dateStr, start, end))
       if int(start) > int(dateStr):
          return(True)
       if int(end) < int(dateStr):
          return(True)

    # Not interested if not for VFR
    if "qLine" in notamData and "Traffic" in notamData["qLine"] and "V" not in notamData["qLine"]["Traffic"]:
       return(True)

    # Not interested if it is a checkList
    if "qLine" in notamData and "Purpose" in notamData["qLine"] and notamData["qLine"]["Purpose"] == "K":
       return(True)
    if "qLine" in notamData and "Scope" in notamData["qLine"] and notamData["qLine"]["Scope"] == "K":
       return(True)

    # Not interested in: Notification of Security Advice to UK Air Operators by Government to provide guidance/instructions 
    #                    on Airspace Security Risks. Volcanic Ash related information within En-Route Airspace...
    if "Series" in notamData and notamData["Series"] == "V":
       return(True)

    # Not interested in anything covering the whole country / world
    if "Radius" in notamData and int(notamData["Radius"]) >= 999:
       return(True)

    # Not interested in anything more than 25nm in diameter
    if "Radius" in notamData and int(notamData["Radius"]) > 25:
       return(True)

    # Not interested in anything lower than we should be flying
    if "qLine" in notamData and "Upper" in notamData["qLine"] and int(notamData["qLine"]["Upper"]) <= 5:
       return(True)
    if "ItemG" in notamData and len(notamData["ItemG"]) > 0 and int(parseAlt(notamData["ItemG"])) <= 5:
       return(True)

    # Not interested in Facilities, Movement and Landing Area, Lighting, Comms, Nav Facilites, Procedures or Services
    if notamData["qLine"]["Code23"][0] in "FMLCINPS":
       return(True)

    # Temp - look for AREA or BOUND in ItemE
    #if "ItemE" in notamData and "BOUNDED" in notamData["ItemE"]:
    #  return(False)


    return(False)

def stripControlChars(input):
    stage1 = str(re.sub(r'[\x00-\x1f]', ' ', input))
    stage2 = str(re.sub(r'  ', ' ', stage1))
    return(stage2)
    

def parseNotamList(l2Tag, l3, FIR_ICAO, FIR_NAME, AD_CODE, AD_NAME, notamDate):
    for l4 in l3:
       if l4.tag != "Notam":
          print("parseNotamList(): UNEXPECTED: %s" % (l4.tag), file=sys.stderr)
          continue

       # If we get here it is a NOTAM
       notamData = { }
       notamData["Section"] = l2Tag
       notamData["PIBSection"] = l4.attrib["PIBSection"]
       notamData["FIR_ICAO"] = FIR_ICAO
       notamData["FIR_NAME"] = FIR_NAME
       notamData["AD_CODE"] = AD_CODE
       notamData["AD_NAME"] = AD_NAME
       notamData["qLine"] = {}
    
       #print("\t\t\t%s   PIBSection: %s" % (l4.tag, l4.attrib["PIBSection"]))

       for l5 in l4:
          if l5.tag == "QLine":
             #print("\t\t\t\t%s" % (l5.tag))
             for l6 in l5:
                notamData["qLine"][l6.tag] = stripControlChars(l6.text)
                #print(">>\t\t\t\t\t%-20s  %s" % (l6.tag, l6.text))
          else:
             notamData[l5.tag] = stripControlChars(l5.text)
             #print(">>\t\t\t\t%-20s  %s" % (l5.tag, l5.text))
                
       #print("NOTAM: %s" % (notamData))
       if not skipNotam(notamData, notamDate):
          notamList.append(notamData)

    return

##########################################################################################################
# Start Here
# Needs to be input
CLASS="B"

if len(sys.argv) != 2:
   print("Usage: %s date" % (sys.argv[0]), file=sys.stderr)
   print("       date format: YYYYMMDD", file=sys.stderr)
   sys.exit(-1)

fileDateStr = sys.argv[1]
notamDtTm = datetime.datetime.strptime(fileDateStr, "%Y%m%d")
notamDate = notamDtTm.strftime("%y%m%d")
fileDate = notamDtTm.strftime("%Y%m%d")
print("\n%s: Processing for date: %s" % (timestamp(), notamDate), file=sys.stderr)

NOTAM_FILE="NOTAMs/notams-%s.txt.gz" % (fileDate)
PIB_ZIP="PIBs/PIB-%s.xml.gz" % (fileDate)
PIB_FILE="tmpPIB.xml"

# Check PIB file exists
if not os.path.exists(PIB_ZIP):
   print("%s: PIB File: %s does not exist" % (sys.argv[0], PIB_ZIP))
   sys.exit(-1)

# Unzip PIB file into local directory
with gzip.open(PIB_ZIP, "r") as pibZip:
   content = pibZip.read()
   with open(PIB_FILE, "w") as pibFile:
      print(content.decode(), file=pibFile)

tree = ET.parse(PIB_FILE)

root = tree.getroot()

for l1 in root:
    if l1.tag != "FIRSection":
       continue

    FIR_ICAO = ""
    FIR_NAME = ""

    # ADSection == Aerodrome
    # En-route == En Route
    # Warnings == Warnings
    for l2 in l1:
       AD_CODE = ""
       AD_NAME = ""

       if l2.tag == "ICAO":
          FIR_ICAO = l2.text
          #print("FIR ICAO: %s" % (FIR_ICAO))
       elif l2.tag == "Name":
          FIR_NAME = l2.text
          #print("FIR NAME: %s" % (FIR_NAME))

       elif l2.tag == "ADSection":
          #print("\tAerodrome")
          for l3 in l2:
             if l3.tag == "Code":
                AD_CODE = l3.text
                #print("\t\tAD_CODE: %s" % (AD_CODE))
             elif l3.tag == "Name":
                AD_NAME = l3.text
                #print("\t\tAD_NAME: %s" % (AD_NAME))
             elif l3.tag == "NotamList":
                #print("\t\tNotamList")
                parseNotamList(l2.tag, l3, FIR_ICAO, FIR_NAME, AD_CODE, AD_NAME, notamDate)
             elif l3.tag != "ObsoleteNotamList":
                print("Aerodrome: Level 3: UNEXPECTED: %s" % (l3.tag), file=sys.stderr)

       elif l2.tag == "En-route":
          #print("\tEn-route")
          for l3 in l2:
             if l3.tag == "NotamList":
                #print("\t\tNotamList")
                parseNotamList(l2.tag, l3, FIR_ICAO, FIR_NAME, AD_CODE, AD_NAME, notamDate)
             elif l3.tag != "ObsoleteNotamList":
                print("En-route: Level 3: UNEXPECTED: %s" % (l3.tag), file=sys.stderr)

       elif l2.tag == "Warnings":
          #print("\tWarnings")
          for l3 in l2:
             if l3.tag == "NotamList":
                #print("\t\tNotamList")
                parseNotamList(l2.tag, l3, FIR_ICAO, FIR_NAME, AD_CODE, AD_NAME, notamDate)
             elif l3.tag != "ObsoleteNotamList":
                print("Warnings: Level 3: UNEXPECTED: %s" % (l3.tag), file=sys.stderr)
       
       else: # Don't know where this came from
          print("Level 2: UNEXPECTED: %s" % (l2.tag), file=sys.stderr)

with gzip.open(NOTAM_FILE, "wt") as notamFile:
    for notam in notamList:
        itemE = itemF = itemG = ""
        if "ItemE" in notam:
            itemE = notam["ItemE"]
        if "ItemF" in notam:
            itemF = notam["ItemF"]
        if "ItemG" in notam:
            itemG = notam["ItemG"]
    
        radius = notam["Radius"]
        centre = notam["Coordinates"]
        if notam["qLine"]["Lower"] != "0":
            lower = "%s00" % (notam["qLine"]["Lower"])
        else:
            lower = "0"
        upper = "%s00" % (notam["qLine"]["Upper"])
    
        if len(itemF) > 0 and "FL" not in lower:
            lower = notam["ItemF"].replace("FT", "").replace("AMSL", "").strip()
        if len(itemG) > 0 and "FL" not in upper:
            upper = notam["ItemG"].replace("FT", "").replace("AMSL", "").strip()
            if lower == "UNL":
               lower = "999"
    
        if "FL" not in lower and lower != "SFC":
           lower = "%sALT" % (lower)
        if "FL" not in upper:
           upper = "%sALT" % (upper)
        if not "NM" in radius: 
           radius = "%sNM" % (radius)
    
        lat = lon = ""
        if len(itemE) > 0 and "RADIUS OF" in itemE:
            itemEParts = itemE.split(" ")   
            for indx in range(len(itemEParts)):
               if itemEParts[indx] == "RADIUS" and itemEParts[indx + 1] == "OF":
                   break
            if itemEParts[indx - 2] == "WI":
                radius = itemEParts[indx - 1]
                if itemEParts[indx + 2] == "PSN":
                   indx += 1
                lat = itemEParts[indx + 2]
                lon = itemEParts[indx + 3]
                centre = "%s%s" % (lat, lon)
    
        if len(centre) < 15:
            centre = "%s00%s00%s" % (centre[0:4], centre[4:10], centre[10])
          
        # Convert circles into DPs
        centre_lat = float(centre[0:2]) + (float(centre[2:4]) / 60) + (float(centre[4:6]) / 3600)
        if centre[6] == "S":
           centre_lat *= -1
        centre_lon = float(centre[7:10]) + (float(centre[10:12]) / 60) + (float(centre[12:14]) / 3600)
        if centre[14] == "W":
           centre_lon *= -1
        local_azimuthal_projection = "+proj=aeqd +R=6371000 +units=m +lat_0={} +lon_0={}".format(centre_lat, centre_lon)
        wgs84_to_aeqd = functools.partial(pyproj.transform, pyproj.Proj("+proj=longlat +datum=WGS84 +no_defs"), pyproj.Proj(local_azimuthal_projection),)
        aeqd_to_wgs84 = functools.partial(pyproj.transform, pyproj.Proj(local_azimuthal_projection), pyproj.Proj("+proj=longlat +datum=WGS84 +no_defs"),)
        center = Point(float(centre_lon), float(centre_lat))
        point_transformed = transform(wgs84_to_aeqd, center)
        rad = float(radius.replace("NM", "")) * 1852
        buffer = point_transformed.buffer(rad)
        coordPoly = transform(aeqd_to_wgs84, buffer)
    
        coords = []
        polyCoords = list(coordPoly.exterior.coords)
        for coord in polyCoords:
           lat = convertDDddddToDDMMSS(coord[1], "LAT")
           lon = convertDDddddToDDMMSS(coord[0], "LON")
           coords.append([lat, lon])

        # This next section is pretty nasty - but so is the format of the NOTAMs
        # Now cnotam["Item"] if it looks like there are a list of coords in ItemE
        if "ItemE" in notam and "BOUNDED" in notam["ItemE"]:
           if "ARC" not in notam["ItemE"] and "CIRCLE" not in notam["ItemE"]: # This secrtion won't parse these
              #print("%s\n" % (notam["ItemE"]))
              itemEParts = notam["ItemE"].split(" ")
              # Find "BY" - they all seem to have this
              for indx in range(len(itemEParts)):
                 if itemEParts[indx][0:2] == "BY":
                    indx += 1
                    break
              # Skip anything that doesn't look like a lat-lon pair
              for startIndx in range(indx, len(itemEParts)):
                 if len(itemEParts[startIndx]) == 7 and itemEParts[startIndx][6] in "NSns" and itemEParts[startIndx][0:6].isnumeric():
                    break
              # Now startIndx should point to first number
              # Find first '.' = assume that is the end of the coords
              for endIndx in range(startIndx, len(itemEParts)):
                 if "." in itemEParts[endIndx]:
                     break
              # Work through rebuilding coords
              coords = []
              for indx in range(startIndx, endIndx):
                 if len(itemEParts[indx]) == 7 and itemEParts[indx][6] in "NSns" and itemEParts[indx][0:6].isnumeric():
                    if len(itemEParts[indx + 1]) == 8 and itemEParts[indx + 1][7] in "EWew" and itemEParts[indx + 1][0:7].isnumeric():
                       # We have what looks like a lat/lon
                       lat = "%s:%s:%s %s" % (itemEParts[indx][0:2], itemEParts[indx][2:4], itemEParts[indx][4:6], itemEParts[indx][6])
                       lon = "%s:%s:%s %s" % (itemEParts[indx + 1][0:3], itemEParts[indx + 1][3:5], itemEParts[indx + 1][5:7], itemEParts[indx + 1][7])
                       coords.append([lat, lon])

        # Can't process these yet
        if "ItemE" in notam and "BOUNDED" in notam["ItemE"]:
           if "ARC" in notam["ItemE"] or "CIRCLE" in notam["ItemE"]: # This secrtion won't parse these
              print("NOT UPDATING COORDS for: %s\n" % (notam["ItemE"]))
        
        starts = ""
        if "StartValidity" in notam:
           startDtTm = datetime.datetime.strptime(notam["StartValidity"], "%y%m%d%H%M")
           starts = startDtTm.strftime("%d-%b-%Y %H:%M")
        ends = ""
        if "EndValidity" in notam:
           if notam["EndValidity"] == "PERM":
              ends = "PERM"
           else:
              endDtTm = datetime.datetime.strptime(notam["EndValidity"], "%y%m%d%H%M")
              ends = endDtTm.strftime("%d-%b-%Y %H:%M")

        print("*\t%s%s/%s begins: %s, ends: %s" % (notam["Series"], notam["Number"], notam["Year"], starts, ends), file=notamFile)
        print("AC %s" % (CLASS), file=notamFile)
        print("AN %s" % (notam["ItemE"]), file=notamFile)
        print("AL %s" % (lower), file=notamFile)
        print("AH %s" % (upper), file=notamFile)

        for coord in coords:
           print("DP %s %s" % (coord[0], coord[1]), file=notamFile)
 
        print("*", file=notamFile)

        
        #print("\nNOTAM: %s" % notam)
        #print("\tCoords: %-15s   Radius: %-3s   ItemF: %s   ItemG: %s   Lower: %s   Upper: %s" % (notam["Coordinates"], notam["Radius"], itemF, itemG, notam["qLine"]["Lower"], notam["qLine"]["Upper"]))
        #print("\tCoords: %-15s   Radius: %-3s    Lower: %s    Upper: %s" % (centre, radius, lower, upper))

os.remove(PIB_FILE)
