import json
import requests
from enum import Enum, IntFlag
import time
import threading

class VehicleType(IntFlag):
    ALL                  = 0
    NORMAL_BUS           = 1 << 0
    MAX                  = 1 << 1
    MAX_TYPE_1           = 1 << 2
    MAX_TYPE_2           = 1 << 3
    MAX_TYPE_3           = 1 << 4
    MAX_TYPE_4           = 1 << 5
    MAX_TYPE_5           = 1 << 6
    MAX_TYPE_6           = 1 << 7
    MAX_GREEN            = 1 << 8
    MAX_BLUE             = 1 << 9
    MAX_ORANGE           = 1 << 10
    MAX_RED              = 1 << 11
    MAX_YELLOW           = 1 << 12
    UNKNOWN_VEHICLE_TYPE = 1 << 13
    FX_BUS               = 1 << 14
    OTHER_BUS            = 1 << 15

class Garage(Enum):
    UNKNOWN = 0
    RUBY    = 1
    POWELL  = 2
    MERLO   = 3
    CENTER  = 4
    ELMO    = 5


class Position():
    lat: float
    lon: float
    def __init__(self, lat, lon):
        self.lat = lat
        self.lon = lon
    def __eq__(self, other):
        return (isinstance(other, Position) and self.lat == other.lat and self.lon == other.lon)


class Vehicle():
    type: VehicleType
    pos: Position
    line: int
    signMessage: str
    garage: Garage
    vehicleID: int
    hexColor: str
    
    def __eq__(self, other):
        return (isinstance(other, Vehicle) and self.type == other.type and self.pos == other.pos and self.line == other.line)

    def __init__(self, type: str, pos: Position, color: str, signMessage: str, garage: str, vehicleID: int, line: int):
        self.type = 0
        if type == "rail":
            self.type = self.type | VehicleType.MAX
            match color:
                case "008342": # Green Line
                    self.type = self.type | VehicleType.MAX_GREEN
                case "114C96": # Blue Line
                    self.type = self.type | VehicleType.MAX_BLUE
                case "FFC52F": # Yellow Line
                    self.type = self.type | VehicleType.MAX_YELLOW
                case "D05F27": # Orange Line
                    self.type = self.type | VehicleType.MAX_ORANGE
                case "C41F3E": # Red Line
                    self.type = self.type | VehicleType.MAX_RED
            match int(vehicleID / 100):
                case 1:
                    self.type = self.type | VehicleType.MAX_TYPE_1
                case 2:
                    self.type = self.type | VehicleType.MAX_TYPE_2
                case 3:
                    self.type = self.type | VehicleType.MAX_TYPE_3
                case 4:
                    self.type = self.type | VehicleType.MAX_TYPE_4
                case 5:
                    self.type = self.type | VehicleType.MAX_TYPE_5
                case 6:
                    self.type = self.type | VehicleType.MAX_TYPE_6
                case _:
                    self.type = self.type | VehicleType.UNKNOWN_VEHICLE_TYPE
        elif type == "bus":
            self.type = (self.type | VehicleType.NORMAL_BUS if color == "084C8D" else (self.type | VehicleType.FX_BUS if color == "61A744" else self.type | VehicleType.OTHER_BUS))
        else:
            print("Found vehicle with unknown type:", type)
            self.type = self.type | VehicleType.UNKNOWN_VEHICLE_TYPE
        match garage:
            case "RUBY":
                self.garage = Garage.RUBY
            case "POWELL":
                self.garage = Garage.POWELL
            case "MERLO":
                self.garage = Garage.MERLO
            case "CENTER":
                self.garage = Garage.CENTER
            case "ELMO":
                self.garage = Garage.ELMO
            case _:
                self.garage = Garage.UNKNOWN
        self.signMessage = signMessage
        self.pos = pos
        self.vehicleID = vehicleID
        self.line = line
        self.hexColor = color

class TriMet:
    filter: VehicleType
    appId: str
    allVehicles: list[Vehicle] = []
    lastAllVehicles: list[Vehicle] = []
    registered_callbacks: dict[int, list] = {}
    refreshInterval: float
    registered_fetch_callbacks: dict[int, list] = {}

    def track_vehicle(self, id):
        def decorator(func):
            if self.registered_callbacks.get(id, []):
                self.registered_callbacks[id].append(func)
            else:
                self.registered_callbacks[id] = [func]
            def wrapper(*args, **kwargs):
                return func(*args, **kwargs)
            return wrapper
        return decorator
    def fetch_vehicles(self) -> list[Vehicle]:
        response = requests.get(f"https://developer.trimet.org/ws/v2/vehicles?appID={self.appId}")
        if response.status_code != 200:
            print(f"Failed to fetch. Code: {response.status_code}. Response: {response.content}")
            return []
        data = json.loads(response.content)
        if not data:
            print("Invalid JSON response received.")
            return []
        if not data["resultSet"]:
            print("Invalid JSON response received.")
            return []
        if not data["resultSet"]["vehicle"]:
            print("Invalid JSON response received.")
            return []
        result = []
        for vehicle in data["resultSet"]["vehicle"]:
            result.append(Vehicle(vehicle["type"], Position(vehicle["latitude"], vehicle["longitude"]), vehicle["routeColor"], vehicle["signMessage"], vehicle["garage"], vehicle["vehicleID"], vehicle["routeNumber"]))
        return result
    def filterList(self, vehicles: list[Vehicle]):
        if self.filter == VehicleType.ALL:
            return vehicles
        result = []
        for vehicle in vehicles:
            if (vehicle.type & self.filter):
                result.append(vehicle)
        return result

            
    def __init__(self, filter: VehicleType, appId: str, refreshInterval: float = 1):
        response = requests.get(f"https://developer.trimet.org/ws/v2/vehicles?appID={appId}")
        if response.status_code != 200:
            print(f"Failed to initialize. Status code: {response.status_code}. Response: {str(response.content)}")
            return
        self.filter = filter
        self.appId = appId
        self.refreshInterval = refreshInterval
        self.allVehicles = self.filterList(self.fetch_vehicles())
        threading.Thread(target=self.start_tracking, daemon=True).start()
    def start_tracking(self):
        while True:
            self.lastAllVehicles = self.allVehicles
            self.allVehicles = self.filterList(self.fetch_vehicles())
            for vehicle in self.allVehicles:
                if vehicle.vehicleID in self.registered_callbacks and not vehicle in self.lastAllVehicles:
                    for func in self.registered_callbacks[vehicle.vehicleID]:
                        func()
            time.sleep(self.refreshInterval)

time.sleep(0xFFFFFFF)