
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import sys
import os
import traceback
import pickle


class VrpException(Exception):
    pass



#*************** CmplCPP  ************************************************
class VrpRunner(object):
        
    #*********** constructor **********
    def __init__(self, fName):

        #logName = fName+'.log'
        #self.lf = open(logName, 'w')

        #self.lf.write('start\n')

        self.fName = fName
        self.data = {}    
  
        self.isMaxTime = False
        self.isMaxDist = False

        self.maxTime = 0
        self.maxDist = 0
        self.maxWaitingTime = 30

        self.maxSolvingTime = 300

        self.routeIndices = []
        self.routeLoads = []
        self.routeTimes = [] 
        self.routeDistances = []

        self.solutionStatus = False
        self.message = ''
        self.objValue = 0

        self.manager = None
        self.routing = None
        self.solution = None

        #self.lf.write('vor lesen\n')

        self.readData()

        #self.lf.write('nach lesen\n')

         


    #*********** end constructor ******

    def readData(self):
        try:
            _dumpFile = open(self.fName, 'rb')
            self.data = pickle.load(_dumpFile)
            self.maxWaitingTime = pickle.load(_dumpFile)
            self.maxTime = pickle.load(_dumpFile)
            self.maxDist = pickle.load(_dumpFile)
            self.isTimeWindow = pickle.load(_dumpFile)
            self.maxSolvingTime = pickle.load(_dumpFile)
            _dumpFile.close()
               
            if self.data['time_matrix']:
                self.isTimeMatrix = True
            else:
                self.isTimeMatrix = False 

            os.remove(self.fName)

            if self.maxDist>0:
                self.isMaxDist = True
            else:
                for dRow in self.data['distance_matrix']:
                    self.maxDist += max(dRow)
            
            if self.maxTime>0:
                self.isMaxTime = True  
            else:
                if self.isTimeMatrix:
                    for tRow in self.data['time_matrix']:
                        self.maxTime += max(tRow)

            self.maxWaitingTime = round(self.maxTime / len(self.data['demands']))
            
            print(self.data)
            print(self.maxTime, self.isMaxTime)
            print(self.maxDist, self.isMaxDist)
            print(self.isTimeMatrix, self.isTimeWindow)

        except:
            raise VrpException("Internal error, cannot read dump: " + str(sys.exc_info()))

    def writeData(self):
        try:
            _dumpFile = open(self.fName, 'wb')
            pickle.dump(self.solutionStatus, _dumpFile)
            pickle.dump(self.message, _dumpFile)
            pickle.dump(self.objValue, _dumpFile)
            pickle.dump(self.routeIndices, _dumpFile)
            pickle.dump(self.routeLoads, _dumpFile)
            pickle.dump(self.routeTimes, _dumpFile)
            pickle.dump(self.routeDistances, _dumpFile)
            _dumpFile.close()
        except:
            raise VrpException("Internal error, cannot write dump: " + str(sys.exc_info()[1]))

    
    def distance_callback(self, from_index, to_index):
        from_node = self.manager.IndexToNode(from_index)
        to_node = self.manager.IndexToNode(to_index)
        return self.data['distance_matrix'][from_node][to_node]

    def time_callback(self, from_index, to_index):
        from_node = self.manager.IndexToNode(from_index)
        to_node = self.manager.IndexToNode(to_index)
        return (self.data['time_matrix'][from_node][to_node] + self.data['service_time'][from_node] )

    def demand_callback(self, from_index):
        from_node = self.manager.IndexToNode(from_index)
        return self.data['demands'][from_node]

    def getSolutionData(self):
        
        self.objValue = self.solution.ObjectiveValue()

        if self.isTimeWindow or self.isMaxTime:
            time_dimension = self.routing.GetDimensionOrDie('Time')

        route_distance = 0
        for vehicle_id in range(self.data['num_vehicles']):
            index = self.routing.Start(vehicle_id)

            rIndices = []
            rLoads = []
            rTimes = [] 
            rDists = []
            
            route_distance = 0
            if self.isTimeWindow or self.isMaxTime:
                time_var = time_dimension.CumulVar(index)
                rTimes.append(self.solution.Min(time_var))
                #rTimes.append([self.solution.Min(time_var),self.solution.Max(time_var)])
                rDists.append(0)

            while not self.routing.IsEnd(index):
                node_index = self.manager.IndexToNode(index)

                previous_index = index
                index = self.solution.Value(self.routing.NextVar(index))

                
                route_distance += self.routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)

                #print(previous_index, index, vehicle_id, self.routing.GetArcCostForVehicle(
                #previous_index, index, vehicle_id))

                if self.isTimeWindow or self.isMaxTime:
                    time_var = time_dimension.CumulVar(index)

                rIndices.append(node_index)
                rLoads.append(self.data['demands'][node_index])
                rDists.append(route_distance)
                
                if self.isTimeWindow or self.isMaxTime:
                    rTimes.append(self.solution.Min(time_var))
                    #rTimes.append([self.solution.Min(time_var),self.solution.Max(time_var)])

            node_index = self.manager.IndexToNode(index)
            rIndices.append(node_index)
            rLoads.append(self.data['demands'][node_index])

            self.routeIndices.append(rIndices)
            self.routeLoads.append(rLoads)
            self.routeTimes.append(rTimes)
            self.routeDistances.append(rDists)
            #print(route_distance)


    def solve(self):
        print('VRPrunner has been started ')
        try:
            self.manager = pywrapcp.RoutingIndexManager(len(self.data['distance_matrix']),
                                           self.data['num_vehicles'], self.data['depot'])

            self.routing = pywrapcp.RoutingModel(self.manager)

            dist_callback_index = self.routing.RegisterTransitCallback(self.distance_callback)
            self.routing.SetArcCostEvaluatorOfAllVehicles(dist_callback_index)

            if self.isMaxDist:
                dist = 'Dist'
                self.routing.AddDimension( dist_callback_index,  0,  self.maxDist , 
                                    False, dist)

            if self.isTimeWindow or self.isMaxTime:

                if not self.isTimeMatrix:
                    raise VrpException('Specify time matrix before using time windows or max time')

                time_callback_index = self.routing.RegisterTransitCallback(self.time_callback)

                time = 'Time'
                self.routing.AddDimension( time_callback_index, self.maxWaitingTime, 
                                                self.maxTime, False, time)
                                                
                time_dimension = self.routing.GetDimensionOrDie(time)
               
                if self.isTimeWindow:
                    # Add time window constraints for each location except depot.
                    for location_idx, time_window in enumerate(self.data['time_windows']):
                        if location_idx == self.data['depot']:
                            continue
                        index = self.manager.NodeToIndex(location_idx)
                        time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])

                    # Add time window constraints for each vehicle start node.
                    depot_idx = self.data['depot']
                    for vehicle_id in range(self.data['num_vehicles']):
                        index = self.routing.Start(vehicle_id)
                        time_dimension.CumulVar(index).SetRange(
                                        self.data['time_windows'][depot_idx][0],
                                        self.data['time_windows'][depot_idx][1])

                    # Instantiate route start and end times to produce feasible times.
                    for i in range(self.data['num_vehicles']):
                        self.routing.AddVariableMinimizedByFinalizer(
                                time_dimension.CumulVar(self.routing.Start(i)))
                        self.routing.AddVariableMinimizedByFinalizer(
                                time_dimension.CumulVar(self.routing.End(i)))
          
            demand_callback_index = self.routing.RegisterUnaryTransitCallback(self.demand_callback)

            self.routing.AddDimensionWithVehicleCapacity(demand_callback_index,
                                                        0,  self.data['vehicle_capacities'],  
                                                        True, 'Capacity')
            
            
            search_parameters = pywrapcp.DefaultRoutingSearchParameters()
            search_parameters.first_solution_strategy = (
                        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
            
            search_parameters.local_search_metaheuristic = (
                       routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)

            #search_parameters.first_solution_strategy = (
            #            routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
            #search_parameters.local_search_metaheuristic = (
            #            routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
            #search_parameters.time_limit.FromSeconds(1)
           
            search_parameters.time_limit.seconds = self.maxSolvingTime

      
            self.solution = self.routing.SolveWithParameters(search_parameters)

            if self.solution:
                self.solutionStatus = True
                self.getSolutionData()
                print(' ... solution has been found ')
                print(' ... Writing results ')
                self.writeData()
            else:
                self.solutionStatus = False
                print(' ... no solution has been found ')
   
            print('VRPrunner has been finished ')
      
            #self.lf.close()
        except:
            traceback.print_exc()
            print('Error :'+str(sys.exc_info()) )
            #self.lf.close()
            raise VrpException('VRPrunner: Something went wrong while solving VRP: '+ str(sys.exc_info()) )




def main():
    #fName = 'C:\\Users\\MikeSteglich\\AppData\\Local\\Temp\\tmpnl0zinfy'
    fName = sys.argv[1]
    try:
        m = VrpRunner(fName)
        m.solve()
    except VrpException as e: 
        vrpOK = False
        vrpMsg = e
        traceback.print_exc()
    except:
        vrpOK = False
        vrpMsg = str(sys.exc_info())
        traceback.print_exc()
    else:
        vrpOK = True
        vrpMsg = "OK" 

    #input("Hit Enter to exit")


if __name__ == "__main__":
    main()        
    