# Student ID: 012498637
# Student Name: Zakaria Benmoulay
# Course: C950 - Data Structures and Algorithms II
# Algorithm  : Nearest-Neighbor (greedy self-adjusting)
# Data Structure: Custom Chaining Hash Table

# Let's import the required libraries and modules
import os
from datetime import datetime
from data_loader import load_packages, load_distances
from truck import Truck



# Let's define file paths for package and distance data (relative to this script)

BASE_DIR      = os.path.dirname(os.path.abspath(__file__))
PACKAGES_CSV  = os.path.join(BASE_DIR, "packages.csv")
DISTANCES_CSV = os.path.join(BASE_DIR, "distances.csv")


# Let's load all necessary data for the program
def initialize():
    package_table = load_packages(PACKAGES_CSV)
    address_list, distance_matrix = load_distances(DISTANCES_CSV)
    return package_table, address_list, distance_matrix




# Let's assign packages to trucks based on delivery constraints
# Manual truck loading (satisfies all constraints)
def load_trucks(package_table, address_list, distance_matrix):
    """Assign packages to trucks and run the delivery algorithm."""

    # Let's set the start of the delivery day (8:00 AM)
    start_of_day = datetime(2024, 1, 1, 8, 0, 0)  # 8:00 AM

    # ---- Truck 1 ----
    # Let's load priority packages and grouped deliveries
    truck1_packages = [
        1, 13, 14, 15, 16, 19, 20, 29, 30, 31, 34, 37, 40,
        2, 4, 5, 7, 10,
    ]

    # ---- Truck 2 ----
    # Let's load truck-specific and delayed packages
    truck2_packages = [
        3, 18, 36, 38,
        6, 28, 32,
        9,
        8, 11, 12, 17, 21, 23, 24,
    ]

    # ---- Truck 3 ----
    # Let's assign any remaining packages to Truck 3
    all_assigned = set(truck1_packages + truck2_packages)
    all_packages = [pid for pid, _ in package_table.get_all()]
    truck3_packages = [pid for pid in all_packages if pid not in all_assigned]

    # Let's initialize Truck 1 (departs at 8:00 AM)
    truck1 = Truck(
        truck_id=1,
        packages=truck1_packages,
        depart_time=start_of_day,
        package_table=package_table,
        address_list=address_list,
        distance_matrix=distance_matrix,
    )

    # Let's delay Truck 2 departure until 9:05 AM for late packages
    truck2_depart = datetime(2024, 1, 1, 9, 5, 0)
    truck2 = Truck(
        truck_id=2,
        packages=truck2_packages,
        depart_time=truck2_depart,
        package_table=package_table,
        address_list=address_list,
        distance_matrix=distance_matrix,
    )

    # Let's run deliveries for Truck 1 and Truck 2
    truck1.deliver_all(return_to_hub=True)
    truck2.deliver_all(return_to_hub=True)

    # Let's assign Truck 3 to depart when Truck 1 returns
    truck3_depart = truck1.current_time
    truck3 = Truck(
        truck_id=3,
        packages=truck3_packages,
        depart_time=truck3_depart,
        package_table=package_table,
        address_list=address_list,
        distance_matrix=distance_matrix,
    )

    # Let's complete deliveries for Truck 3
    truck3.deliver_all(return_to_hub=True)

    return truck1, truck2, truck3


# CLI INTERFACE
# Let's display the status of all packages at a given time
def print_all_status(package_table, query_time):
    """Print the delivery status of all 40 packages at a given time."""
    print(f"\n{'='*75}")
    print(f"  Package Status at {query_time.strftime('%I:%M %p')}")
    print(f"{'='*75}")
    print(f"{'ID':<4} {'Address':<38} {'Deadline':<10} {'Truck':<6} {'Status':<12} {'Delivered'}")
    print(f"{'-'*75}")

    # Sort packages by ID so output is easy to read
    all_packages = sorted(package_table.get_all(), key=lambda x: x[0])
    for pid, pkg in all_packages:
        # status_at() compares query_time to departure and delivery times
        status = pkg.status_at(query_time)
        delivered_str = (
            pkg.delivery_time.strftime("%I:%M %p")
            if status == "Delivered" and pkg.delivery_time
            else "—"
        )
        truck_str = str(pkg.truck_id) if pkg.truck_id else "—"
        print(
            f"{pid:<4} {pkg.address:<38} {pkg.deadline:<10} "
            f"{truck_str:<6} {status:<12} {delivered_str}"
        )


# Let's display the status of a single package
def print_single_status(package_table, pkg_id, query_time):
    """Print full details for a single package at a given time."""
    # Use the hash table lookup function to retrieve the package by ID
    pkg = package_table.lookup(pkg_id)
    if pkg is None:
        print(f"Package {pkg_id} not found.")
        return

    status = pkg.status_at(query_time)
    delivered_str = (
        pkg.delivery_time.strftime("%I:%M %p")
        if status == "Delivered" and pkg.delivery_time
        else "Not yet delivered"
    )

    # Display all data components associated with this package ID
    print(f"\n{'='*50}")
    print(f"  Package {pkg.package_id} — Status at {query_time.strftime('%I:%M %p')}")
    print(f"{'='*50}")
    print(f"  Address  : {pkg.address}, {pkg.city}, {pkg.state} {pkg.zip_code}")
    print(f"  Deadline : {pkg.deadline}")
    print(f"  Weight   : {pkg.weight} kg")
    print(f"  Truck    : {pkg.truck_id if pkg.truck_id else '—'}")
    print(f"  Notes    : {pkg.notes if pkg.notes else 'None'}")
    print(f"  Status   : {status}")
    print(f"  Delivered: {delivered_str}")


# Let's convert user input into a valid time format
def parse_time(time_str):
    """Parse a user-entered time string into a datetime object."""
    reference_date = datetime(2024, 1, 1)
    for fmt in ["%I:%M %p", "%H:%M", "%I:%M%p"]:
        try:
            t = datetime.strptime(time_str.strip(), fmt)
            return reference_date.replace(hour=t.hour, minute=t.minute, second=0)
        except ValueError:
            continue
    return None  # Return None if no format matched


# Let's create the main user interface loop
def main(package_table, truck1, truck2, truck3):
    # Calculate and display total combined mileage at startup
    total_miles = truck1.total_miles + truck2.total_miles + truck3.total_miles

    print("\n" + "="*60)
    print("  WGUPS Package Routing Program")
    print("="*60)
    print(f"  Truck 1 mileage : {truck1.total_miles:.2f} miles")
    print(f"  Truck 2 mileage : {truck2.total_miles:.2f} miles")
    print(f"  Truck 3 mileage : {truck3.total_miles:.2f} miles")
    print(f"  Total mileage   : {total_miles:.2f} miles")
    print("="*60)

    # Menu loop — continues until the user enters 4 to exit
    while True:
        print("\nOptions:")
        print("  1 - View status of ALL packages at a specific time")
        print("  2 - View status of a SINGLE package at a specific time")
        print("  3 - Print all truck routes")
        print("  4 - Exit")
        choice = input("\nEnter choice (1/2/3/4): ").strip()

        if choice == "1":
            # Prompt for a time and display all 40 package statuses
            time_str = input("Enter time (e.g. 9:00 AM or 13:00): ")
            query_time = parse_time(time_str)
            if query_time is None:
                print("Invalid time format. Try '9:00 AM' or '09:00'.")
            else:
                print_all_status(package_table, query_time)

        elif choice == "2":
            # Prompt for a package ID and time, then display that package
            try:
                pkg_id = int(input("Enter package ID (1-40): "))
            except ValueError:
                print("Invalid package ID.")
                continue
            time_str = input("Enter time (e.g. 9:00 AM or 13:00): ")
            query_time = parse_time(time_str)
            if query_time is None:
                print("Invalid time format.")
            else:
                print_single_status(package_table, pkg_id, query_time)

        elif choice == "3":
            # Print the full ordered route for each truck with delivery times
            truck1.print_route()
            truck2.print_route()
            truck3.print_route()
            total = truck1.total_miles + truck2.total_miles + truck3.total_miles
            print(f"\nCombined total: {total:.2f} miles")

        elif choice == "4":
            print("Thank you for using WGUPS Routing Program, Goodbye.")
            break

        else:
            print("Invalid choice. Please enter 1, 2, 3, or 4.")


# Let's start the program execution
if __name__ == "__main__":
    package_table, address_list, distance_matrix = initialize()
    truck1, truck2, truck3 = load_trucks(package_table, address_list, distance_matrix)
    main(package_table, truck1, truck2, truck3)
