#place this file in Temp Outputs and double-click to run it
#if placed somewhere else, update prefix

import glob
import re

#Set the trace state object names
traceObjects = ['BPBerth1','BPBerth2']

#Set the states that defines the berth as occupied
states = ['Wait to Handle','Handling delay - RampUp','Handling','Handling delay - RampDown','Wait for Conditions to Clear Berth','Handling delay - Weather','Pre-Handling Time','Post-Handling Time']

#Get the run file names using the .rep file
runFiles = glob.glob('*.rep')
runFiles = ['BP-116-10.rep']

#Change to true to output intermediate files
printIntermediateFile = False

#Set the location of where the output files (.rep, .trc) are relative to where this file is located.
#prefix = '../../02 - Temp Outputs' #py file is located in run files.
prefix = './' #py file is located in temp outputs

#Trace state end times is not always accurate. It doesn't always report the state of the berth at the end of the simulation.
#Set the simulation end time and
initialization = 1
simYears = 50   #does not include initialization
hoursPerYear = 8760.0

initHours = initialization*hoursPerYear
simEndTime = hoursPerYear*simYears

#Data must be parsed using getOccupiedState first. This functions sums up all the "True" states and returns the list below.
#[Start Time, Object, Duration, Start Time + Duration, End Time (extra for checking but not used)]
#when it reaches the end of the file the list is: [Start Time, Object, Duration, Start Time + Duration, 'end'] -- this is end of the list and gives us an easy stopping condition.
def getDurationTime(occupiedStateData):
    timeOccupied = 0.0
    startTime = 0.0
    endTime =0.0
    newEndTime = 0.0
    berthName = ''
    myList = []
    startTimeIndx = 0 #index from getOccupiedState()
    berthNameIndx = 1 #index from getOccupiedState()
    stateNameIndx = 2 #index from getOccupiedState() 
    durationIndx = 3  #index from getOccupiedState()
    endTimeIndx = 4   #index from getOccupiedState()
    OccupiedIndx = 5  #index from getOccupiedState()
    for i in range(len(occupiedStateData)):
        if i < len(occupiedStateData)-1:
            #4 combinations of True/False states
            #False->True
            #Beginning of a occupied group
                #Set the Start Time and Berth Name from the Next Line
            if not occupiedStateData[i][OccupiedIndx] and occupiedStateData[i+1][OccupiedIndx]:
                startTime = float(occupiedStateData[i+1][startTimeIndx])
                berthName = occupiedStateData[i+1][berthNameIndx]
            #False->False
                #DO NOTHING, Just incase - Reset Time to 0
            elif not occupiedStateData[i][OccupiedIndx] and not occupiedStateData[i+1][OccupiedIndx]:
                timeOccupied = 0
            #True->True
                #Add to time occupied
            elif occupiedStateData[i][OccupiedIndx] and occupiedStateData[i+1][OccupiedIndx]:
                timeOccupied += float(occupiedStateData[i][durationIndx])
            #True->False
            #End of occupied group
                #Set End Time, Add Current duration to time occupied
                #Append and reset time occupied
            elif occupiedStateData[i][OccupiedIndx] and not occupiedStateData[i+1][OccupiedIndx]:
                endTime = float(occupiedStateData[i][endTimeIndx])
                timeOccupied += float(occupiedStateData[i][durationIndx])
                myList.append([startTime,berthName,timeOccupied,startTime+timeOccupied,endTime])
                timeOccupied=0
            else:
                #This shouldn't happen from the data, error out
                raise Exception("Missing True or False.")
        #last line of the file
        else:
            #If it ends during an occupied state
            if occupiedStateData[i][OccupiedIndx] and occupiedStateData[i-1][OccupiedIndx]:
                timeOccupied += float(occupiedStateData[i][durationIndex])
                myList.append([startTime,berthName,timeOccupied,startTime+timeOccupied,occupiedStateData[i][endTimeIndx]])
                myList.append([occupiedStateData[i][startTimeIndx],occupiedStateData[i][berthNameIndx],occupiedStateData[i][OccupiedIndx],occupiedStateData[i][startTimeIndx]+occupiedStateData[i][durationIndx],'end'])
            #If it ends during an unoccupied state
            else:
                myList.append([occupiedStateData[i][startTimeIndx],occupiedStateData[i][berthNameIndx],occupiedStateData[i][OccupiedIndx],occupiedStateData[i][startTimeIndx]+occupiedStateData[i][durationIndx],'end'])
    return myList

#Gets the occupied percentage for an object. Data must be parsed using 1) getOccupiedState then 2) getDurationTime
#Before using this function
def getOccupiedTime(durationTimeData, objectName):
    sumOccupied = 0.0
    for i in durationTimeData:
        if (isinstance(i[0], float) and i[1] == objectName and (i[4] != 'end' and i[0]>=initHours)):
            sumOccupied += i[2]
    percentOccupied = sumOccupied/simEndTime*100
    return percentOccupied

#Reads state and adds a boolean column. True = Occupied, False = Not Occupied
#Returns: [Start Time, Object, State, Duration, End Time, True/False]
#The start of when a berth is occupied is the pre-handling time state. If a vessel does not enter this state, berth occupancy will be 0
#States Occupied is everything between Pre-Handling to Post-Handling (and Wait for Conditions to Clear Berth).
def getOccupiedState(traceStateData):
    occupiedStateData = []
    startTime = 0.0 
    duration = 0.0
    endTime = 0.0
    berthName = ''
    stateName = ''
    for i in range(len(traceStateData)):
        #TLS outputs in this format
        #To generalize this
        searchStates = re.match('([0-9.]+)  (BPBerth[12]).setState\( "([a-zA-Z -0-9]+)" \) dt = ([0-9.]+)',traceStateData[i])
        if searchStates and float(searchStates.group(1))>=initHours:
            startTime = float(searchStates.group(1))
            berthName = searchStates.group(2)
            stateName = searchStates.group(3)
            duration = float(searchStates.group(4))
            endTime = startTime+duration
            if (searchStates.group(3) in states):
                traceStateData.append([startTime,berthName,stateName,duration,endTime,True])
            else:
                traceStateData.append([startTime,berthName,stateName,duration,endTime,False])
    return traceStateData

#Write a list to a file defined by writerObject
def writeOccupiedState(parsedData ,writerObject):
    for line in parsedData:
        for items in line:
            writerObject.write(str(items)+'\t')
        writerObject.write('\n')


def main(printIntermediateFile):
    #Index is based after the data runs through getDurationTime 
    startTimeIndex = 0
    berthNameIndex = 1
    durationIndex =  2
    endTimeIndex = 3

    for run in runFiles:
        print run
        overlapOccupied = 0.0
        preProcessedTraceStateData = []
        data = [[],[]]
        for i in range(len(traceObjects)):
            with open(prefix+run[:-4]+'-'+traceObjects[i]+'.trc') as fread:
                alllines = fread.readlines()
                for lines in alllines:
                    data[i].append(lines)
        Berth1States = getOccupiedState(data[0])
        Berth2States = getOccupiedState(data[1])
        preProcessedTraceStateData = getDurationTime(Berth1States) + getDurationTime(Berth2States)
        sortedData = sorted(preProcessedTraceStateData)
        berth1Occupied = getOccupiedTime(parsedTraceFileData,traceObjects[0])
        berth2Occupied = getOccupiedTime(parsedTraceFileData,traceObjects[1])

        if printIntermediateFile:
            #Print intermediate steps for checking in excel
            writerBerth1 = open(prefix+run[:-4]+'-Berth1occupied'+'.occ','w')
            writerBerth2 = open(prefix+run[:-4]+'-Berth2occupied'+'.occ','w')            
            writeOccupiedState(Berth1States,writerBerth1)
            writeOccupiedState(Berth2States,writerBerth2)                    
            writerBerthSorted = open(run[:-4]+'-sorted.occ','w')
            writerBerthUnsorted = open(run[:-4]+'-unsorted.occ','w')
            writeOccupiedState(sortedData,writerBerthSorted)
            writeOccupiedState(preProcessedTraceStateData,writerBerthUnsorted)
        
        #Loop: Calculate Both Berth Occupancy Percentage
        for i in range(len(sortedData)):
            numOfLinesAfterThatOverlap = 0
            #If it is the end, or it is during initialization, it cannot overlap
            if (sortedData[i][4]=='end' or sortedData[i][0]<initHours):
                pass
            #If this line and the next line have the same berth name, it cannot overlap
            elif (sortedData[i][1]==sortedData[i+1][1]):
                pass
            else:
                
                #Check how many lines after overlap this time
                for j in range(1,len(sortedData[i:])):
                    if sortedData[i][endTimeIndex] < sortedData[i+j][startTimeIndex]:
                           numOfLinesAfterThatOverlap = j-1
                           break;
                #For each of those lines, calculate the overlap and add it to accumulator
                for k in range(1,numOfLinesAfterThatOverlap+1):
                    #If the berth name is the same, it cannot overlap
                    #Also, because the data is sorted, it shouldn't ever get in here because for berths to be occupied again,
                    #a vessel would need to leave and come back, which means it cannot overlap
                    if sortedData[i][berthNameIndex] == sortedData[i+k][berthNameIndex]:
                        #This shouldn't happen from the data, error out
                        raise Exception("A Berth Overlapped itself.")
                    elif sortedData[i+k][endTimeIndex] > sortedData[i][endTimeIndex]:
                        overlapOccupied += sortedData[i][endTimeIndex] - sortedData[i+k][startTimeIndex]
                    elif sortedData[i][endTimeIndex] >= sortedData[i+k][endTimeIndex]:
                        overlapOccupied += sortedData[i+k][durationIndex]
                    else:
                        #This shouldn't happen from the data, error out
                        raise Exception("Error: Unknown Source. Please Investigate.")
        
        writer = open(prefix+run[:-4]+'.occ','w')
        writer.write('Percentage Occupied')
        writer.write('\n'+traceObjects[0]+' Occupied\t'+str(berth1Occupied)+'%')
        writer.write('\n'+traceObjects[1]+' Occupied\t'+str(berth2Occupied)+'%')
        writer.write('\n'+'Both Berths Occupied\t'+str(overlapOccupied/simEndTime*100)+'%')

main(printIntermediateFile)
