def __init__(self, controllers, bgeCars, racingLine, lap=2):
     self.controllers = controllers
     self.bgeCars = bgeCars
     self.registerCars()
     self.racingLine = racingLine
     self.lap = lap
     self.raceLapManager = RaceLapManager(bgeCars)
     self.raceRankingManager = RaceRankingManager(bgeCars, racingLine, self.raceLapManager)
     self.logger = LoggerHandler(appname=self.__class__.__name__)
 def __init__(self,bgeCar):
     self.bgeCar = bgeCar
     self.logger = LoggerHandler(appname=self.bgeCar.carname + self.__class__.__name__)
class BGECarPositionHandler:

    def __init__(self,bgeCar):
        self.bgeCar = bgeCar
        self.logger = LoggerHandler(appname=self.bgeCar.carname + self.__class__.__name__)

    #update internal state based on recent position
    def update(self):

        self.logger.info(str(self.bgeCar))

        idx_inf = self.bgeCar.racingLinePointInf
        idx_sup = self.bgeCar.racingLinePointSup
        iprev = self.bgeCar.racingLine.getPreviousIndex(idx_inf)
        inext = self.bgeCar.racingLine.getNextIndex(idx_sup)

        carobj_position = self.bgeCar.getPosition()
        vecSupInf = self.bgeCar.racingLine.getCoord(idx_sup) - self.bgeCar.racingLine.getCoord(idx_inf)
        vecSupCar = self.bgeCar.racingLine.getCoord(idx_sup) - carobj_position
        vecInfCar = self.bgeCar.racingLine.getCoord(idx_inf) - carobj_position
        vecInfSup = self.bgeCar.racingLine.getCoord(idx_inf) - self.bgeCar.racingLine.getCoord(idx_sup)
        
        #get previous/next interface point index
        next_interface_idx = self.bgeCar.next_interface_idx
        next_interface_point_idx = self.bgeCar.tiManager.getInterface(next_interface_idx).racingLinePointIdx
        prev_interface_point_idx = self.bgeCar.tiManager.getPreviousInterface(next_interface_idx).racingLinePointIdx

        #check angle at point_idx_inf (inf to car and inf to sup): if superior to 90 degree, need to increment idx
        #ie: car is moving forward as expected
        if tools3d.angleFormated(vecSupInf,vecSupCar)>90.0:

            
            #print('going forward: ' + str(vecSupInf.angle(vecSupCar)))
            self.bgeCar.racingLinePointInf = idx_sup
            self.bgeCar.racingLinePointSup = inext
            
            #check if need to increment interface (idx_sup is then the new 'point_idx_inf')
            if idx_sup==next_interface_point_idx:
                self.shiftInterfaceByOne(1)
            
            #check for new lap (idx_sup is then the new 'point_idx_inf')
            if idx_sup == 0:
                self.bgeCar.notifyEndOfLap()
                self.bgeCar.start_race = False
                #self.logger.info(str(self.bgeCar))

        #check angle at point_idx_sup (sup to car and sup to inf): if superior to 90 degree, need to decrement idx
        #ie: car is somehow moving backward for some reason
        elif tools3d.angleFormated(vecInfSup,vecInfCar)>90.0:

            #print('going backward: ' + str(vecSupInf.angle(vecInfCar)))
            self.bgeCar.racingLinePointInf = iprev
            self.bgeCar.racingLinePointSup = idx_inf
            
            #check if need to decrement interface (idx_inf is then the new 'point_idx_sup')
            if idx_inf==next_interface_point_idx:
                self.shiftInterfaceByOne(-1)

        #project car position orthogonally on the racing line
        orth_pos = self.bgeCar.getOrthPosition()
                        
        #distance to next interface
        self.bgeCar.dist_next_interface = self.getDistanceToNextInterface(orth_pos)
        
        #distance in current lap
        self.bgeCar.dist_lap = self.getDistanceLap(orth_pos)
    
    #increment/decrement car object's next interface by one
    def shiftInterfaceByOne(self,iShift):
        if iShift==1:
            self.bgeCar.next_interface_idx = self.bgeCar.tiManager.getNextInterfaceIdx(self.bgeCar.next_interface_idx)
        elif iShift==-1:
            self.bgeCar.next_interface_idx = self.bgeCar.tiManager.getPreviousInterfaceIdx(self.bgeCar.next_interface_idx)
    
    #get distance between car position (orthogonally projected on the spline)
    #and the next interface
    def getDistanceToNextInterface(self,carobj_position_orth):
        
        idx_inf = self.bgeCar.racingLinePointInf
        idx_sup = self.bgeCar.racingLinePointSup

        #distance between proj orth and next point
        dist = (carobj_position_orth - self.bgeCar.racingLine.getCoord(idx_sup)).length
        
        #remaining distance to next interface
        remaining_dist = self.bgeCar.racingLine.getRelativeDistance(
            idx_sup,self.bgeCar.tiManager.getInterfaceRLIdx(self.bgeCar.next_interface_idx))
        
        #case when AI are thrown far from the track
        #the interface count may then be screwed up
        #in that case we just need to reset next interface idx
        if dist + remaining_dist > max(self.bgeCar.tiManager.interfaceDistances):
            #TO DO
            print('issue')
        
        return dist + remaining_dist
    
    #compute distance in current lap
    #-> used by RaceManager to get ranking
    def getDistanceLap(self,carobj_position_orth):

        #special case - start of the race
        if self.bgeCar.start_race:
            return 0.0
        
        #get distance between pos orth and previous point
        dist_orth_prev = (carobj_position_orth - self.bgeCar.racingLine.getCoord(self.bgeCar.racingLinePointInf)).length

        #get distance between previous point and racing line starting point (point[0])
        dist_prev_start = self.bgeCar.racingLine.getDistanceToStart(self.bgeCar.racingLinePointInf)
        
        return dist_orth_prev + dist_prev_start
class RaceManager:
    def __init__(self, controllers, bgeCars, racingLine, lap=2):
        self.controllers = controllers
        self.bgeCars = bgeCars
        self.registerCars()
        self.racingLine = racingLine
        self.lap = lap
        self.raceLapManager = RaceLapManager(bgeCars)
        self.raceRankingManager = RaceRankingManager(bgeCars, racingLine, self.raceLapManager)
        self.logger = LoggerHandler(appname=self.__class__.__name__)

    # observer pattern - need to register each car to race manager
    def registerCars(self):
        for bgeCar in self.bgeCars:
            bgeCar.registerRaceManager(self)

    # return list of carname ordered by crossed distance DESC
    def getRanking(self):
        return self.raceRankingManager.getRanking()

    # log current ranking
    def logCurrentRanking(self):
        self.logger.info(self.raceRankingManager.printCurrentRankingNicely())

    # log final ranking
    def logFinalRanking(self):
        self.logger.info(self.raceRankingManager.printFinalRankingNicely())

    # log best lap times
    def logBestLapTimes(self):
        self.logger.info(self.raceLapManager.printBestLapTimesNicely())

    # kind of observer pattern:
    # bgeCar will call this method and pass itself as argument
    # to notify raceManager that it has finished a lap
    # raceManager then handles all necessary action
    def notifyEndOfLap(self, bgeCar):

        # update list of lap time
        self.raceLapManager.notifyEndOfLap(bgeCar)

        # test race completion for given car
        if self.raceCompletedForCar(bgeCar):

            self.raceRankingManager.addToFinalRanking(bgeCar)

            # update controller to end of race state - if AI
            if bgeCar.carname[:3] == "cpu":
                controller = self.controllers[bgeCar.carname]
                controller.setControl("eor")

            # test race completion
            if self.raceCompleted():
                self.logger.info(
                    "\n"
                    + "############################################\n"
                    + "######### RACE COMPLETED ###################\n"
                    + "############################################\n"
                )
                self.logFinalRanking()
                self.logBestLapTimes()

    # test race completion
    def raceCompleted(self):
        return self.raceLapManager.raceCompleted(self.lap)

    # test race completion
    def raceCompletedForCar(self, bgeCar):
        return self.raceLapManager.raceCompletedForCar(self.lap, bgeCar)