#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')

#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

#Index is based after the data runs through getDurationTime 
startTimeIndex = 0
berthNameIndex = 1
durationIndex =  2
endTimeIndex = 3


#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 = []
    startTimeIndex = 0 #index from getOccupiedState()
    berthNameIndex = 1 #index from getOccupiedState()
    stateNameIndex = 2 #index from getOccupiedState() 
    durationIndex = 3  #index from getOccupiedState()
    endTimeIndex = 4   #index from getOccupiedState()
    OccupiedIndex = 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][OccupiedIndex] and occupiedStateData[i+1][OccupiedIndex]:
                startTime = float(occupiedStateData[i+1][startTimeIndex])
                berthName = occupiedStateData[i+1][berthNameIndex]
            #False->False
                #DO NOTHING, Just incase - Reset Time to 0
            elif not occupiedStateData[i][OccupiedIndex] and not occupiedStateData[i+1][OccupiedIndex]:
                timeOccupied = 0
            #True->True
                #Add to time occupied
            elif occupiedStateData[i][OccupiedIndex] and occupiedStateData[i+1][OccupiedIndex]:
                timeOccupied += float(occupiedStateData[i][durationIndex])
            #True->False
            #End of occupied group
                #Set End Time, Add Current duration to time occupied
                #Append and reset time occupied
            elif occupiedStateData[i][OccupiedIndex] and not occupiedStateData[i+1][OccupiedIndex]:
                endTime = float(occupiedStateData[i][endTimeIndex])
                timeOccupied += float(occupiedStateData[i][durationIndex])
                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][OccupiedIndex] and occupiedStateData[i-1][OccupiedIndex]:
                timeOccupied += float(occupiedStateData[i][durationIndex])
                myList.append([startTime,berthName,timeOccupied,startTime+timeOccupied,occupiedStateData[i][endTimeIndex]])
                myList.append([occupiedStateData[i][startTimeIndex],occupiedStateData[i][berthNameIndex],occupiedStateData[i][OccupiedIndex],occupiedStateData[i][startTimeIndex]+occupiedStateData[i][durationIndex],'end'])
            #If it ends during an unoccupied state
            else:
                myList.append([occupiedStateData[i][startTimeIndex],occupiedStateData[i][berthNameIndex],occupiedStateData[i][OccupiedIndex],occupiedStateData[i][startTimeIndex]+occupiedStateData[i][durationIndex],'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:
            startTime = float(searchStates.group(1))
            berthName = searchStates.group(2)
            stateName = searchStates.group(3)
            duration = float(searchStates.group(4))
            endTime = startTime+duration
            if startTime<initHours and ((startTime+duration)>initHours):
                occupiedStateData.append([startTime,berthName,stateName,initHours-startTime,initHours,False])
                occupiedStateData.append([initHours,berthName,stateName,endTime-initHours,endTime,True])
            if startTime>=initHours:
                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')




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)+'%')
