# # Customizable Random Aircraft selector
from random import choice
import json

# Constants
AIRCRAFTTYPE = {1: "Airliner",2: "Freighter",3: "General Aviation",4: "Military",5: "Helicopter",6: "Glider",7: "Relics"}
ENGINETYPE = {1: "Jet", 2: "Turboprop", 3: "Piston", 4: "Electric"}
GEARTYPE = ["Wheels", "Skis", "Floats"]
GAMEVERSION = {1: "MSFS2020", 2: "MSFS2024"}
GAMEEDITION = {1:"Standard",2:"Deluxe",3: "Premium Deluxe",4: "Aviator (2024 only)"}
PRICEBRACKETS = ["$0-20", "$21-40", "$41-60", "$61-80", "$81+"]
PRICESTRUCTURE = {1: "Included", 2: "Freeware",3: "Payware"}
PAYWAREDEVS = {1: "PMDG", 2: "iniBuilds",3: "JustFlight", 4: "TFDi Designs", 5: "FlightFX", 6: "Contrail", 7: "Bravo Simulations", 8: "Fenix", 9: "A2A", 10: "Black Square", 
11: "Cows Studio",12: "Got Friends",13: "FlightSim Studio",14: "Taog",15: "FlightSim Labs",16: "iFly", 17:"CSS", 18: "Vector",19: "BlueBird Simulations"}
FREEWAREDEVS = {1: "Headwind", 2: "FlyByWire",3: "Horizon Simulations",4: "Kuro",5: "GotFriends"}
RANKING = ["1/5", "2/5", "3/5", "4/5", "5/5"]
MSFS2020PARTNERS = {
   1: "Aeroplane Heaven",
   2: "Asobo Studio",
   3: "ATSimulations",
   4: "BlackBird Simulations",
   5: "BlueMesh",
   6: "Carenado",
   7: "FlyInside",
   8: "Hans Hartmann",
   9: "iniBuilds",
   10: "Mike Johnson",
   11: "Nemeth Designs",
   12: "Oliver Moser",
   13: "Orbx"
}

MSFS2024PARTNERS = {
   1: "Asobo Studio",
   2: "BlackBird Simulations",
   3: "BlueMesh",
   4: "Carenado",
   5: "DC Designs",
   6: "FlightFX",
   7: "FlightSim Studio",
   8: "Got Friends",
   9: "Hans Hartmann",
   10: "Hype Performance Group",
   11: "iniBuilds",
   12: "Mike Johnson",
   13: "MilTech Simulations"
}
finalDetermination = [] # Final determination to fill list.

class StringCombinationParser:
    def __init__(self, item_pool, max_value):
        self.item_pool = item_pool
        self.max_value = max_value
        self.bundles_map = {}

    def register_bundle(self, number_list, bundle_data):
        self.bundles_map[frozenset(number_list)] = bundle_data

    def parse(self, input_str):
        selectionMod = 0
        #Parses a comma-separated string of numbers.
        #Ensures entries are separated by commas and strictly 1 or 2 digits.
    
        # Remove any accidental whitespace the user might have typed around commas
        cleaned_str = input_str.replace(" ", "")
        
        # Split the string by the comma delimiter
        chunks = cleaned_str.split(",")
        detected_numbers = []

        for chunk in chunks:
            # Error out if the user forgot a comma (e.g., "1112" makes a 4-digit chunk)
            if len(chunk) > 2:
                raise ValueError(f"Missing comma or entry too long: '{chunk}' is greater than 2 digits.")
            
            # Error out if an entry is completely empty (e.g., trailing comma like "1,2,")
            if len(chunk) == 0:
                raise ValueError("Invalid format: Found an empty entry between commas.")

            # Ensure the chunk consists only of digits (catches letters/symbols)
            if not chunk.isdigit():
                raise ValueError(f"Invalid characters found: '{chunk}' is not a valid number.")

            # Convert to integer and check our specific 1-16 limits
            num = int(chunk)
            if num < 1 or num > self.max_value:
                raise ValueError(f"Number out of range: {num} must be between 1 and {self.max_value}.")
            selectionMod += 1
            detected_numbers.append(num)

        # Map to items and check for bundles (Same logic as before)
        matched_items = [self.item_pool[n] for n in detected_numbers if n in self.item_pool]
        lookup_key = frozenset(detected_numbers)
        bundle_data = self.bundles_map.get(lookup_key, None)
        return matched_items, bundle_data

class AircraftSelector:
    def __init__(self):
        try:
            with open('aircraft_data.json', 'r', encoding='utf-8') as file:
                self.aircraft_database = json.load(file)
                # This line proves the file was found and read correctly
                print(f"--- SYSTEM: Loaded {len(self.aircraft_database)} aircraft from JSON ---") 
        except FileNotFoundError:
            raise FileNotFoundError ("CRITICAL ERROR: aircraft_data.json not found! Make sure it is in the exact same folder.")
            self.aircraft_database = []
        except json.decoder.JSONDecodeError as e:
            # Catches invisible spaces or missing brackets in the JSON
            print(f"CRITICAL ERROR: The JSON file has a formatting error: {e}")
            self.aircraft_database = []
    def get_random_aircraft(self, user_selections):
        matching_planes = []

        for plane in self.aircraft_database:
            is_match = True

            # We loop through every category the user had options for
            for category, allowed_values in user_selections.items():
                
                # If you didn't pick anything for this category, skip checking it
                # (e.g., if you left developers blank, any developer is fine)
                if not allowed_values:
                    continue  

                plane_val = plane.get(category)

                # --- The "OR within, AND across" Magic ---
                
                # Case A: For single-string traits (like category, propulsion, pricing)
                # It checks if the plane's trait is IN your list of allowed choices (OR logic)
                if isinstance(plane_val, str):
                    if plane_val not in allowed_values:
                        is_match = False
                        break  # Fails this category check, drop the plane instantly

                # Case B: For list-based traits (like developers, sim_versions)
                # It uses intersection to see if it shares AT LEAST ONE item with your choices (OR logic)
                elif isinstance(plane_val, list):
                    if not set(plane_val).intersection(allowed_values):
                        is_match = False
                        break  # Fails this category check, drop the plane instantly

            # If it survived ALL category checks without breaking, it's a perfect match
            if is_match:
                matching_planes.append(plane)

        if not matching_planes:
            return "No aircraft match your exact combination."

        return choice(matching_planes)["name"]
def mainMenu():
    selector = AircraftSelector()
    print("Welcome to the random aircraft generator program.")
    print("This program will ask you a few questions and generate a random aircraft for you to fly in MSFS based on your selections.")
    print("This program contains a small database of the most popular payware planes. Use web app to add your own database.") 
    while True:
        continueInputVar = input("Would you like to continue (y/n)?: ")
        if continueInputVar.lower() == "y" or continueInputVar.lower() == "yes":

            finalDetermination = aircraftDeterminator()
            chosen_plane = selector.get_random_aircraft(finalDetermination)
            print(f"Here is an aircraft to fly: {chosen_plane}! Fly safe!")
            break
        elif continueInputVar.lower() == "n" or continueInputVar.lower() == "no":
            print("Very well. Program ending.")
            exit()
        else:
            print("Invalid entry. Try again.")

def aircraftDeterminator():
    # Initialize as a dictionary mapped to the exact keys in your database
    finalDetermination = {
        "sim_versions": [],
        "editions": [],
        "category": [],
        "pricing": [],
        "propulsion": [],
        "developers": []
    }
    
    # Helper function to print selections cleanly from the dictionary
    def get_current_selections():
        flat_list = [item for sublist in finalDetermination.values() for item in sublist]
        return ", ".join(flat_list)

    print("Which version of Microsoft Flight Simulator are you using?")
    simVersion = list(GAMEVERSION.items())
    for index, (key, value) in enumerate(simVersion, start=1):
        print(f"{index}. {value}")
    simVersionSelect = input("Please choose a selection (1-2): ")
    stringParser = StringCombinationParser(item_pool=GAMEVERSION, max_value=2)
    items, bundle = stringParser.parse(simVersionSelect)
    finalDetermination["sim_versions"].extend(items)
    print(f"Current selections: {get_current_selections()}")
            
    if "MSFS2020" in finalDetermination["sim_versions"]:
            print("Which edition of MSFS2020 do have?")
            simEdition = list(GAMEEDITION.items())
            for index, (key, value) in enumerate(simEdition[:-1], start=1):
                print(f"{index}. {value}")
            simEditionSelect = input("Please choose a selection (1-3): ")
            stringParser = StringCombinationParser(item_pool=GAMEEDITION, max_value=3)
            items, bundle = stringParser.parse(simEditionSelect)
            finalDetermination["editions"].extend(items)
            print(f"Current selections: {get_current_selections()}")

    elif "MSFS2024" in finalDetermination["sim_versions"]:
            print("Which edition of MSFS2024 do have?")
            simEdition = list(GAMEEDITION.items())
            for index, (key, value) in enumerate(simEdition, start=1):
                print(f"{index}. {value}")
            simEditionSelect = input("Please choose a selection (1-4): ")
            stringParser = StringCombinationParser(item_pool=GAMEEDITION, max_value=4)
            items, bundle = stringParser.parse(simEditionSelect)
            finalDetermination["editions"].extend(items)
            print(f"Current selections: {get_current_selections()}")

    print("Which type of aircraft?")
    print("Example of valid multiple selections: 1,3 for airliner and general aviation. Order does not matter.")
    print("Example of invalid entry: 13 (No comma)")
    aircraftList = list(AIRCRAFTTYPE.items())
    for index, (key, value) in enumerate(aircraftList, start=1):
        print(f"{index}. {value}")
    aircraftTypeSelect = input("Please enter a value. Combination values are valid. (1-7): ")
    stringParser = StringCombinationParser(item_pool=AIRCRAFTTYPE, max_value=7)
    items, bundle = stringParser.parse(aircraftTypeSelect)
    finalDetermination["category"].extend(items)
    print(f"Current selections: {get_current_selections()}")

    print("Which price structure are you interested in? Freeware includes aircraft included with the sim.")
    print("Example of valid multiple selections: 1,3 for included and payware. Order does not matter.")
    print("Example of invalid entry: 13 (No comma)")
    priceList = list(PRICESTRUCTURE.items())
    for index, (key, value) in enumerate(priceList, start=1):
        print(f"{index}. {value}")
    priceSelect = input("Please choose a selection. Combination selections are valid. (1-3): ")
    stringParser = StringCombinationParser(item_pool=PRICESTRUCTURE, max_value=3)
    items, bundle = stringParser.parse(priceSelect)
    finalDetermination["pricing"].extend(items)
    print(f"Current selections: {get_current_selections()}")
    print("Which engine type? You can select multiple by typing in each number, separated by commas.")
    print("Example of valid multiple selections: 1,2,3 for Jet, turboprop, piston. Order does not matter.")
    print("Example of invalid entry: 123 (No comma)")
    engineList = list(ENGINETYPE.items())
    for index, (key, value) in enumerate(engineList, start=1):
        print(f"{index}. {value}")
    engineSelect = input("Enter a value. Combination values are valid. (1-4): ")
    stringParser = StringCombinationParser(item_pool=ENGINETYPE, max_value=4)
    items, bundle = stringParser.parse(engineSelect)
    finalDetermination["propulsion"].extend(items)
    print(f"Current selections: {get_current_selections()}")
    
    if "Payware" in finalDetermination["pricing"]:
        print("Which developer? You can select multiple by typing in each number, separated by commas.")
        print("Example of valid multiple selections: 1,2,3 for PMDG, iniBuilds, and JustFlight. Order does not matter.")
        print("Example of invalid entry: 123 (No comma)")
        devList = list(PAYWAREDEVS.items())
        for index, (key, value) in enumerate(devList[:-2], start=1):
            print(f"{index}. {value}")
        devSelection = input("Select an option. Combination entries are valid. (1-17): ")
        stringParser = StringCombinationParser(item_pool=PAYWAREDEVS, max_value=17)
        items, bundle = stringParser.parse(devSelection)
        finalDetermination["developers"].extend(items)
        print(f"Current selections: {get_current_selections()}")

    if "Freeware" in finalDetermination["pricing"]:
        print("Which developer? You can select multiple by typing in each number, separated by commas.")
        print("Example of valid multiple selections: 1,2 for FlyByWire and Headwind. Order does not matter.")
        print("Example of invalid entry: 123 (No comma)")
        devList = list(FREEWAREDEVS.items())
        for index, (key, value) in enumerate(devList, start=1):
            print(f"{index}. {value}")
        devSelection = input("Select an option. Combination entries are valid. (1-5): ")
        stringParser = StringCombinationParser(item_pool=FREEWAREDEVS, max_value=5)
        items, bundle = stringParser.parse(devSelection)
        finalDetermination["developers"].extend(items)
        print(f"Current selections: {get_current_selections()}")
        
        
    if "Included" in finalDetermination["pricing"] and "MSFS2020" in finalDetermination["sim_versions"]:
        print("Which developer? You can select multiple by typing in each number, separated by commas.")
        print("Example of valid multiple selections: 1,2 for Aeroplane Heaven and Asobo Studio")
        print("Example of invalid entry: 123 (No comma)")
        devList = list(MSFS2020PARTNERS.items())
        for index, (key, value) in enumerate(devList, start=1):
            print(f"{index}. {value}")
        devSelection = input("Select an option. Combination entries are valid. (1-13): ")
        stringParser = StringCombinationParser(item_pool=MSFS2020PARTNERS, max_value=13)
        items, bundle = stringParser.parse(devSelection)
        finalDetermination["developers"].extend(items)
        print(f"Current selections: {get_current_selections()}")

    if "Included" in finalDetermination["pricing"] and "MSFS2024" in finalDetermination["sim_versions"]:
        print("Which developer? You can select multiple by typing in each number, separated by commas.")
        print("Example of valid multiple selections: 1,2 for Blackbird and Asobo Studio")
        print("Example of invalid entry: 123 (No comma)")
        devList = list(MSFS2024PARTNERS.items())
        for index, (key, value) in enumerate(devList, start=1):
            print(f"{index}. {value}")
        devSelection = input("Select an option. Combination entries are valid. (1-13): ")
        stringParser = StringCombinationParser(item_pool=MSFS2024PARTNERS, max_value=13)
        items, bundle = stringParser.parse(devSelection)
        finalDetermination["developers"].extend(items)
        print(f"Current selections: {get_current_selections()}")
    return finalDetermination

def main():
    mainMenu()

if __name__ == "__main__":
    main()



