#! /usr/bin/env python
''' Traffic Engineering Tools.'''

from math import ceil

__metaclass__ = type

#########################
# traffic signals
#########################

class Volume:
    '''Class to represent volumes with varied vehicule types '''
    def __init__(self, volume, types = ['pc'], proportions = [1], equivalents = [1], nLanes = 1):
        '''mvtEquivalent is the equivalent if the movement is right of left turn'''

        # check the sizes of the lists
        if sum(proportions) == 1:
            self.volume = volume
            self.types = types
            self.proportions = proportions
            self.equivalents = equivalents
            self.nLanes = nLanes
        else:
            pass

    def getPCEVolume(self):
        '''Returns the passenger-car equivalent for the input volume'''
        v = 0
        for p, e in zip(self.proportions, self.equivalents):
            v += p*e
        return v*self.volume

class IntersectionMovement:
    '''Represents an intersection movement
    with a volume, a type (through, left or right)
    and an equivalent for movement type'''
    def __init__(self, volume, type, mvtEquivalent = 1):
        self.volume = volume
        self.type = type
        self.mvtEquivalent = mvtEquivalent

    def getTVUVolume(self):
        return self.mvtEquivalent*self.volume.getPCEVolume()    

class IntersectionApproach:
    def __init__(self, leftTurnVolume, throughVolume, rightTurnVolume):
        self.leftTurnVolume = leftTurnVolume
        self.throughVolume = throughVolume
        self.rightTurnVolume = rightTurnVolume

    def getTVUVolume(self, leftTurnEquivalent = 1, throughEquivalent = 1, rightTurnEquivalent = 1):
        return self.leftTurnVolume.getPCEVolume()*leftTurnEquivalent+self.throughVolume.getPCEVolume()*throughEquivalent+self.rightTurnVolume.getPCEVolume()*rightTurnEquivalent

class LaneGroup:
    '''Class that represents a group of mouvements'''

    def __init__(self, movements, nLanes):
        self.movements = movements
        self.nLanes = nLanes

    def getTVUVolume(self):
        return sum([mvt.getTVUVolume() for mvt in self.movements])

def checkProtectedLeftTurn(leftMvt, opposedThroughMvt):
    '''Checks if one of the main two conditions on left turn is verified
    The lane groups should contain left and through movement'''
    return leftMvt.volume >= 200 or leftMvt.volume*opposedThroughMvt.volume/opposedThroughMvt.nLanes > 50000

def optimalCycle(lostTime, criticalCharge, rounding=True):
    if rounding:
        return ceil((1.5*lostTime+5)/(1-criticalCharge))
    else:
        return (1.5*lostTime+5)/(1-criticalCharge)

class Cycle:
    '''Class to compute optimal cycle and the split of effective green times'''
    def __init__(self, phases, lostTime, saturationVolume):
        '''phases is a list of phases
        a phase is a list of lanegroups'''
        self.phases = phases
        self.lostTime = lostTime
        self.saturationVolume = saturationVolume

    def computeCycle(self):
        self.criticalCharges = []
        for phase in self.phases:
            self.criticalCharges.append(max([lg.getTVUVolume() for lg in phase])/(lg.nLanes*self.saturationVolume))

        self.criticalCharge = sum(self.criticalCharges)
        self.C0 = optimalCycle(self.lostTime, self.criticalCharge)
        return self.C0

    def computeEffectiveGreen(self):
        from numpy import round
        self.computeCycle() # in case it was not done before
        effectiveGreenTime = self.C0-self.lostTime
        self.effectiveGreens = [round(c*effectiveGreenTime/self.criticalCharge,1) for c in self.criticalCharges]
        return self.effectiveGreens


def computeInterGreen(perceptionReactionTime, initialSpeed, intersectionLength, vehicleAverageLength = 6, deceleration = 3):
    '''Computes the intergreen time (yellow/amber plus all red time)
    Deceleration is positive
    All variables should be in the same units'''
    if deceleration > 0:
        return [perceptionReactionTime+float(initialSpeed)/(2*deceleration), float(intersectionLength+vehicleAverageLength)/initialSpeed]
    else:
        print 'Issue deceleration should be strictly positive'
        return None

def uniformDelay(cycleLength, effectiveGreen, saturationDegree):
    '''Computes the uniform delay'''
    return 0.5*cycleLength*(1-float(effectiveGreen)/cycleLength)/(1-float(effectiveGreen*saturationDegree)/cycleLength)
