import sys
import AssignmentData
import datetime
from MyHashTable import DerekHashTable
from DeliveryObject import DeliveryObject
from DeliveryObject import deliveryStatus

class Truck:
    def __init__(self, truckId: str):
        self.id = truckId                                           # This trucks ID
        self.packages: list[DeliveryObject] = []                    # The packages on the truck
        self.hasLoad: bool = False                                  # Status of if the truck was loaded
        self.mileage = 0.0                                          # Mileage counter
        self.deliveryCounter = 0                                    # Keep track of the delivery counter
        self.current_location = "HUB"                               # Current location of the truck
        self.speed = 18                                             # Set from the project instructions
        self.startTime: datetime.time | None = None                 # The trucks start time

    def addPkg(self, pkg: DeliveryObject):                          # Adds a package to the truck
        if len(self.packages) < 16:                                     # Check if we can fit another package
            pkg.timeStart = self.getTimeOfDay()                         # Set the start time for the pkg
            pkg.truckId = self.id                                       # Set the truck ID for the end menu
            pkg.status = deliveryStatus.IN_ROUTE                        # The package is on the truck
            self.packages.append(pkg)                                   # Add the package
        else:
            print("to many packages loaded on the truck")               # If there are too many packages
            sys.exit()                                                  # Crash the program

    def getDriveTime(self) -> int:                                  # Usefull outside this class
        hourPercent = self.mileage / self.speed                         # Calculate the hour percentage
        rounededMin = int(60 * hourPercent)                             # Average that into minutes
        return rounededMin                                        

    def getTimeOfDay(self) -> datetime.time:                        # Get the time of day using offsets
        truckStartMins = (self.startTime.hour * 60) + self.startTime.minute # Get the minutes past midnight when the truck left
        offsetMinutes =  truckStartMins + self.getDriveTime()           # Add on the number of minutes the truck as been driving
        pkgHours = (offsetMinutes // 60) % 24                           # pull hours back out
        pkgMins = offsetMinutes % 60                                    # pull minutes back out
        return datetime.time(pkgHours, pkgMins)                         # return the time of day

    def deliverPkgs(self):                                              # Delivers any packeges that match the current location
        for i in range(len(self.packages) - 1, -1, -1):                 # Loop backwards so removals dont index shift
            pkg = self.packages[i]
            if pkg.address in self.current_location:                    # Check if the package is at the correct address
                if ("Wrong address listed" not in pkg.notes or
                self.getTimeOfDay() >= datetime.time(10, 30)):          # Wait for corrected shipping address
                    pkg.status = deliveryStatus.DELIVERED               # Mark as delivered
                    pkg.timeEnd = self.getTimeOfDay()                   # Mark the delivered time
                    self.deliveryCounter += 1                           # Incriment the delivery counter
                    pkg.deliveryNumber = self.deliveryCounter           # Mark the package number
                    self.packages.pop(i)

    def getMatrixIndex(self, address_string: str) -> int:           # Helper function to get x or y offset in the distance table
        for i, full_name in enumerate(AssignmentData.xyNames2):         # Loop through the names in the list
            if address_string in full_name:                             # Check if the name matches
                return i                                                # Return the index
        for i, full_name in enumerate(AssignmentData.xyNames):          # Loop through the names in the list
            if address_string in full_name:                             # Check if the name matches
                return i                                                # Return the index
        print(f"Address {address_string} is not in Full List")          # Report items not found in the list
        return 0                                                        # Default to Hub if not found

    def driveNextClosest(self, priority: DerekHashTable) -> datetime.time: # The bread and butter of the algorithm
        # deal with priority        
        priorityPkgs = []                                               # set the packages we currently want to look for
        for cur in self.packages:                                       # loop thorugh looking for priority matches
            if priority.getItem(cur.id):
                priorityPkgs.append(cur)                                # if a priority matches add it to the list
        if not priorityPkgs:                                            # if no priority matches just use the normal
            for v in self.packages:
                if ("Wrong address listed" not in v.notes or            # If the package is marked wrong address
                self.getTimeOfDay() >= datetime.time(10, 30)):          # Wait for corrected shipping address
                    priorityPkgs.append(v)                              # add to the list

        # get the next closest
        shortest_dist = float('inf')                                    # Start with a very long distance to work backwards from
        next_pkg = None                                                 # Keep track of the closest pkg

        if len(priorityPkgs) == 0:
            priorityPkgs.append(DeliveryObject(-1, "HUB", "", "", 0, "10:30 AM", 21),)  # Add a blank package in order to get the truck to drive back to HUB

        current_idx = self.getMatrixIndex(self.current_location)        # Get the trucks xyIndex
        for pkg in priorityPkgs:                                        # Loop through the packages
            dest_idx = self.getMatrixIndex(pkg.address)                 # Get the package xyIndex
            
            row = max(current_idx, dest_idx)                            # Becuase the distance table is a triange
            col = min(current_idx, dest_idx)                            # Keep on the buttom left side
            dist = AssignmentData.distanceChart[row][col]               # Get the distance from the grid
            
            if dist < shortest_dist:                                    # If the distance is shorter than the one found
                shortest_dist = dist                                    # Update the distance and package
                next_pkg = pkg
                
        # drive
        self.mileage += shortest_dist                                   # Add this shortest distance to the truck
        self.current_location = next_pkg.address                        # update the location
        self.deliverPkgs()                                              # deliver packages at this location
        if len(self.packages) == 0:                                     # if there is no packages
            self.hasLoad = False                                        # Set the load to empty