#!/usr/bin/python 

import sys
import os
import time
import io
import _thread 
import threading
import tempfile
import shutil


#************************ setting paths ****************************

appPath = os.path.dirname(os.path.abspath(sys.argv[0])) + os.sep

#release
cmplPath = os.path.dirname(os.path.abspath(sys.argv[0]))+os.sep+'..'+os.sep+'..'+os.sep+'Cmpl'
#debug
#cmplPath = os.path.dirname(os.path.abspath(sys.argv[0]))+os.sep+'..'+os.sep+'..'+os.sep+'..'+os.sep+'..'+os.sep+'LogisticsLab'+os.sep+'Cmpl'
#print "cmplBinPath="+cmplPath

cmplBinPath=cmplPath+os.sep+'bin'+os.sep+'cmpl.exe'
#print "cmplBinPath="+cmplBinPath
os.environ.update({'CMPLBINARY':cmplBinPath })

sys.path.append(cmplPath)
sys.path.append(cmplPath+os.sep+'bin')
from pyCmpl import *

tmpPath = tempfile.gettempdir()+os.sep
#print tmpPath
#************************ end setting paths ************************



#Big M used for the WLP model
bigM = 1000000000

# This exception is raised by this cmplTpp code.
class CmplRunnerException(Exception):
    pass


#*************** SourceElement *************************************
class Source(object):
	#*********** constructor **********
	def __init__(self):
		self.__idx = 0
		self.__name = ""
		self.__xPos = 0
		self.__yPos = 0
		self.__fixedCosts = 0
		self.__capacity = bigM
		self.__active = False
		self.__assignedDestinations = 0
	#*********** end constructor ******
	
	# getter and setter 
	@property
	def idx(self):
		return self.__idx
	
	def setIdx(self, idx):
		if type(idx)==int:
			self.__idx = idx
		else:
			if not CmplTools.strIsNumber(idx.strip()):
				raise CmplRunnerException("Wrong index <" +str( idx)+ "> "  )
			else:
				self.__idx=int(idx)
	@property
	def name(self):
		return self.__name
	
	def setName(self, name):
		self.__name = name
		
		
	@property
        def xPos(self):
                return self.__xPos
               
        def setXPos(self, pos):
            if type(pos)==float:
                    self.__xPos = pos
            else:
                    tmpPos=pos.strip().replace(',','.')
                    try:
                        self.__xPos=float(tmpPos)
                    except:
                        raise CmplRunnerException("Wrong xPosition <" +str( pos)+  "> "  )
        @property
        def yPos(self):
                return self.__yPos
           
        def setYPos(self, pos):
                if type(pos)==float:
                        self.__yPos = pos
                else:
                        tmpPos=pos.strip().replace(',','.')
                        try:
                            self.__yPos=float(tmpPos) 
                        except:
                            raise CmplRunnerException("Wrong yPosition <" +str( pos)+ "> "  )
		
	@property
	def fixedCosts(self):
		return self.__fixedCosts 
	
	def setFixedCosts(self, fc):	
		if type(fc)==int:
			self.__fixedCosts  = fc
		else:
			if fc.upper() == "M":
				self.__fixedCosts = bigM
			elif  CmplTools.strIsNumber(fc.strip()):
				self.__fixedCosts=int(fc)	
			else:
				raise CmplRunnerException("Wrong fixed costs <"+str( fc)+ "> "  )
			
					
	@property
	def capacity(self):
		return self.__capacity 
	
	def setCapacity(self, cap):
		if type(cap)==int:
			self.__capacity  = cap
		else:
			if cap.upper() == "M":
				self.__capacity = bigM
			elif CmplTools.strIsNumber(cap.strip()):
				self.__capacity=int(cap)
			else:
				raise CmplRunnerException("Wrong capacity <"+str( cap)+ "> "  )
		
	@property
	def active(self):
		return self.__active 
	
	def setActive(self, act=True):
		self.__active  = act
		
	@property
	def nrOfAssignedDestinations(self):
		return self.__assignedDestinations 
	
	def incNrOfAssignedDestinations(self):
		self.__assignedDestinations  += 1
#*************** end SourceElement **********************************



#*************** DestinationElement *************************************
class Destination(object):
	#*********** constructor **********
	def __init__(self):
		self.__idx = 0
		self.__name = ""
		self.__xPos = 0
		self.__yPos = 0
		self.__demand = 0
		self.__source = -1
	#*********** end constructor ******
	
	# getter and setter 
	@property
	def idx(self):
		return self.__idx
	
	def setIdx(self, idx):
		if type(idx)==int:
			self.__idx = idx
		else:
			if not CmplTools.strIsNumber(idx.strip()):
				raise CmplRunnerException("Wrong index <" +str( idx)+ "> "  )
			else:
				self.__idx=int(idx)
		
	@property
	def name(self):
		return self.__name
	
	def setName(self, name):
		self.__name = name
		
	@property
        def xPos(self):
                return self.__xPos
               
        def setXPos(self, pos):
            if type(pos)==float:
                    self.__xPos = pos
            else:
                    tmpPos=pos.strip().replace(',','.')
                    try:
                        self.__xPos=float(tmpPos)
                    except:
                        raise CmplRunnerException("Wrong xPosition <" +str( pos)+  "> "  )
                
        @property
        def yPos(self):
                return self.__yPos
           
        def setYPos(self, pos):
                if type(pos)==float:
                        self.__yPos = pos
                else:
                        tmpPos=pos.strip().replace(',','.')
                        try:
                            self.__yPos=float(tmpPos) 
                        except:
                            raise CmplRunnerException("Wrong yPosition <" +str( pos)+ "> "  )	
		
	@property
	def demand(self):
		return self.__demand 
	
	def setDemand(self, dem):
		if type(dem)==int:
			self.__demand = dem
		else:
			if not CmplTools.strIsNumber(dem.strip()):
				raise CmplRunnerException("Wrong demand <" +str(dem)+  "> "  )
			else:
				self.__demand=int(dem)
			
	@property
	def source(self):
		return self.__source 
	
	def setSource(self, src):
		if type(src)==int:
			self.__source = src
		else:
			if not CmplTools.strIsNumber(src.strip()):
				raise CmplRunnerException("Wrong demand <" +str(src)+ "> "  )
			else:
				self.__source=int(src)
#*************** end DestinationElement **********************************


#*************** CmplWLP  ************************************************
class CmplWLP(object):
	
	#*********** constructor **********
	def __init__(self, wlpFile, cap, mipGap, ver):
		
		self.__sources = []
		self.__destinations = []
		
		self.__comment = 0
		
		self.__fixedCosts = 0
		self.__varCosts = 0
		self.__totalCosts = 0
		self.__activeSources = 0
		
		self.__nrOfDestinationsInHeader = 0
		self.__nrOfSourcesInHeader = 0
		
		self.__totalDemand = 0
		self.__totalCapa = 0
		
		self.__srcIdx=0
		self.__destIdx=0
		
		self.__costs = []
		
		self.__wlpFile = wlpFile
		
		self.__solFile = tmpPath + os.path.splitext(os.path.basename(wlpFile))[0]+".SOL"
		#self.__solFile = os.path.splitext(wlpFile)[0]+".LSG"
		
		self.__solutionStatus = ""
		self.__solutionFound = False
				
		self.__model = None
		
		if cap == "cap":
			self.__isCap = True
		else:
			self.__isCap = False
		
		self.__mipGap = mipGap.replace(',','.')
		
		self.__LogLabVersion = ver
		
				
	#*********** end constructor ******
	
	#*********** __readWlp ************
	def readWlpFile(self):
				
		sourcesSection=False
		destSection = False
		costSection = False
		
		if not os.path.isfile(self.__wlpFile):
			raise CmplRunnerException("File does not exist <" + self.__wlpFile +">" )
					
		try:	
			f = open(self.__wlpFile, "r")
			
			lineNr = 0
			secLineNr=0
			for line in f:
				lineNr+=1
				if lineNr == 1:
					if line.lstrip().upper()[0:3] != "WLP" :
						raise CmplRunnerException("File is not a WLP file <" + self.__wlpFile +">" )
					continue
				elif lineNr==2:
					self.__comment=line
					continue
				else:
					if line.strip().upper().startswith("SOURCES") or line.strip().upper().startswith("STANDORTE") or line.strip().upper().startswith("SITES"):
						sourcesSection=True
						destSection=False
						costSection = False
						secLineNr=0
						self.__srcIdx=0
						continue
					elif line.strip().upper().startswith("DESTINATIONS") or line.strip().upper().startswith("KUNDEN") or line.strip().upper().startswith("CUSTOMERS"):
						sourcesSection=False
						destSection=True
						costSection = False
						secLineNr=0
						self.__destIdx=0
						continue
					elif line.strip().upper().startswith("COSTS") or line.strip().upper().startswith("KOSTEN"):
						sourcesSection=False
						destSection=False
						costSection = True
						continue
					elif line.strip().upper().startswith("EEE"):
						sourcesSection=False
						destSection=False
						costSection = False
						continue
					
				if sourcesSection:
					secLineNr+=1
					if secLineNr==1:
						if not CmplTools.strIsNumber(line.strip()):
							raise CmplRunnerException("Wrong number of sources <"+ line + "> in file <" + self.__wlpFile +"> in line <"+str(lineNr)+ ">" )
						
						self.__nrOfSourcesInHeader=int(line.strip())
						continue
					elif secLineNr==2:
						continue
					else:
						e=line.split()
						self.__srcIdx+=1
						if len(e)<4:
							raise CmplRunnerException("Wrong entry for a source <"+ line + "> in file <" + self.__wlpFile +"> in line <"+str(lineNr)+ ">" )
						else:
							try:
								s = Source()
								s.setIdx(self.__srcIdx)
								s.setName(e[0])
								s.setXPos(e[1])
								s.setYPos(e[2])
								s.setFixedCosts(e[3])
								if len(e)==5:
									if e[4].strip().upper()=='M':
										s.setCapacity("M")
									else:
										s.setCapacity(e[4])
								else:
									s.setCapacity("M")
							except CmplException as e:
								raise CmplRunnerException("Wrong entry for a source <"  +str( e)+  "> in file <" + self.__wlpFile +"> in line <"+str(lineNr)+ ">" )
							
							self.__sources.append(s)	
				
				if destSection:
					secLineNr+=1
					if secLineNr==1:
						if not CmplTools.strIsNumber(line.strip()):
							raise CmplRunnerException("Wrong number of destinations <"+ line + "> in file <" + self.__wlpFile +"> in line <"+str(lineNr)+ ">" )
						
						self.__nrOfDestinationsInHeader=int(line.strip())
						continue
					elif secLineNr==2:
						continue
					else:
						e=line.split()
						self.__destIdx+=1
						if len(e)<3:
							raise CmplRunnerException("Wrong entry for a destination <"+ line + "> in file <" + self.__wlpFile +"> in line <"+str(lineNr)+ ">" )
						else:
							try:
								d = Destination()
								d.setIdx(self.__destIdx)
								d.setName(e[0])
								d.setXPos(e[1])
								d.setYPos(e[2])
								if len(e)==4:
									d.setDemand(e[3])
								else:
									d.setDemand("0")
								
								self.__totalDemand += d.demand
								
							except CmplException as e:
								raise CmplRunnerException("Wrong entry for a destination <" +str( e) + "> in file <" + self.__wlpFile+ "> in line <" +str(lineNr)+ ">" )
							
							self.__destinations.append(d)
							
				if costSection:
					costStringList = line.strip().split()
					if len(costStringList) != self.__destIdx:
						raise CmplRunnerException("Wrong costs for a source in file <" + self.__wlpFile +"> in line <", lineNr,">" )
					
					costList = []
					for c in costStringList:
						if not CmplTools.strIsNumber(c.strip()):
							raise CmplRunnerException("Wrong cost <" + str(c)+ "> in file <" +self.__wlpFile+ "> in line <" +str( lineNr) + ">" )
						
						costList.append(int(c))
					
					self.__costs.append(costList)
							
			f.close()
			
		
			if self.__nrOfDestinationsInHeader != self.__destIdx:
				raise CmplRunnerException("Wrong number of destinations " )
		
			if self.__nrOfSourcesInHeader != self.__srcIdx:
				raise CmplRunnerException("Wrong number of sources " )
							
				
			
		except IOError as e:
			raise CmplRunnerException("Can't read WLP File <" + self.__wlpFile +">  <" +str( e)+">" )
		
	#*********** end __readWlp ************	


	#*********** solve ************
	def solve(self):
		
		
		'''try:	
			os.remove( self.__solFile)
		except:
			pass
		'''
		
		w = CmplSet("W")
		w.setValues(1,self.__srcIdx)
		
		c = CmplSet("C")
		c.setValues(1,self.__destIdx)
		
		costs = CmplParameter("c",w,c )
		costs.setValues(self.__costs)
		
		
		fcVals=[]
		capVals=[]
		demandVals=[]
			
		for s in self.__sources:
			fcVals.append(s.fixedCosts)
			if self.__isCap:
				capVals.append(s.capacity)
			
		fc =  CmplParameter("F",w)	
		fc.setValues(fcVals)
		
	
		for d in self.__destinations:
			demandVals.append(d.demand)
		
		if self.__isCap:
			cap =  CmplParameter("s",w)	
			cap.setValues(capVals)
			
			dem =  CmplParameter("d",c)	
			dem.setValues(demandVals)
			
			shutil.copyfile(appPath+"warehouse-cap.cmpl", tmpPath+"warehouse-cap.cmpl") 
			self.__model = Cmpl( tmpPath+"warehouse-cap.cmpl")
		else:
			shutil.copyfile(appPath+"warehouse.cmpl", tmpPath+"warehouse.cmpl") 
			self.__model = Cmpl(tmpPath+"warehouse.cmpl")
			
		self.__model.setSets(w,c)
			
		if self.__isCap:
			self.__model.setParameters(costs, fc, cap, dem)
		else:
			self.__model.setParameters(costs, fc)
			
		#self.__model.debug()
		self.__model.setOutput(True)
		
		#print self.__mipGap , float(self.__mipGap)
		if float(self.__mipGap)>0:
			optStr = '%opt cbc ratio ' + self.__mipGap
			self.__model.setOption(optStr)
			print('... set mipGap to '+self.__mipGap)
		
		self.__model.solve()
		
		self.__solutionStatus = 'no solution found'
		
		if self.__model.solverStatus == SOLVER_OK:
			
			self.__totalCosts = self.__model.solution.value
			self.__solutionStatus = self.__model.solution.status
			self.__solutionFound = True
			
			self.__model.varByName()
			
			for i in w.values:
				try:
					if self.__model.y[i].activity == 1:
						self.__sources[i-1].setActive()
						self.__activeSources +=1
						self.__fixedCosts+=self.__sources[i-1].fixedCosts
						
					for j in c.values:
						try:
							if self.__model.x[(i,j)].activity > 0:
								self.__sources[i-1].incNrOfAssignedDestinations()
								self.__destinations[j-1].setSource(i)
								self.__varCosts += self.__costs[i-1][j-1]*self.__model.x[(i,j)].activity 
						except:
							pass
				except:
					pass
			
			#print 	self.__fixedCosts
			#print self.__varCosts
			#print self.__totalCosts
						
			'''if (self.__fixedCosts + self.__varCosts) != self.__totalCosts:
				raise CmplRunnerException("Internal error by calculating costs" )'''
				
		try:	
			os.remove( tmpPath+"warehouse-cap.cmpl")
			os.remove( tmpPath+"warehouse.cmpl")
		except:
			pass
		
		
	#*********** end solve **************
	
	
	#*********** writeSolFile ************
	def writeSolFile(self):
					
		try:
			f = open(self.__solFile, "w")
			
			now = time.localtime()
			
			f.write("WLP %s SOLUTION       %g.%g.%g    %g:%g:%g\n" % (self.__LogLabVersion,now.tm_mday , now.tm_mon, now.tm_year , now.tm_hour, now.tm_min, now.tm_sec) ) 
			
			f.write(self.__comment.strip()+"\n")
			if self.__isCap:
				f.write("Algorithm:\tMIP capacitated"+"\n")
			else:
				f.write("Algorithm:\tMIP uncapacitated"+"\n")
			f.write("Status: "+self.__solutionStatus+ " \n")
			
			if self.__solutionFound:
				
				f.write("FixedCosts:\t%d\n" % (self.__fixedCosts))
				f.write("VariableCosts:\t%d\n" % (self.__varCosts))
				f.write("TotalCosts:\t%d\n" % (self.__totalCosts))
				
				f.write("SOURCES:\t" +str(self.__activeSources) + "\tof\t" + str(self.__srcIdx) + "\tactive\n")
				f.write("%4s\t%-15s\t%15s\t%15s\n" % ("Nr.","Name","FixedCosts", "NrOfDests"))
				for s in self.__sources:
					if s.active:
						f.write("%4d\t%-15s\t%15d\t%15d\n" % (s.idx, s.name, s.fixedCosts, s.nrOfAssignedDestinations))
					
				f.write("DESTINATIONS:\t" +str(self.__destIdx) +"\n")
				f.write("%4s\t%-15s\t%15s\t%15s\n" % ("Nr.","Name", "Source", "VariableCosts"))
				for d in self.__destinations:
					f.write("%4d\t%-15s\t%15d\t%15d\n" % (d.idx, d.name, d.source, self.__costs[d.source-1][d.idx-1]))
			
			f.write("EEE")
			
			
			f.close()
			
		except IOError as e:
			raise CmplRunnerException("Can't write SOL File <" + self.__solFile +">  <" +str( e)+">" )
	
	#*************** end __writeSolFile  *********************************************
	
		
	
#************** end CmplWLP  **********************************************



#*************** main  *****************************************************
try:
	
	if len(sys.argv) < 5:
		print("ERROR: Wrong argument\nUsage: cmplWlp.py <file.wlp> <cap|uncap> <mipGap> <LogisticLabVersion>")
		input("Hit Enter to exit")
		
	else:
		print("LogisticLab has started CMPL ")
		wlp = CmplWLP(sys.argv[1],sys.argv[2], sys.argv[3], sys.argv[4] )

		print("... reading WLP file ")
		wlp.readWlpFile()
		print("... solving problem")
		wlp.solve()
		print("... done ")
		print("... writing solution to LogisticLab ")
		wlp.writeSolFile()
				
		
except CmplException as e:
	print(e.msg)
	input("Hit Enter to exit ")
	
except CmplRunnerException as e:
	print("cmplTpp error: ", e)
	#traceback.print_exc(file=sys.stdout)
	input("Hit Enter to exit ")
	
except:	
	print("ERROR: " + str(sys.exc_info()[1]))
	#traceback.print_exc(file=sys.stdout)
	input("Hit Enter to exit ")
	
#************** end main  **************************************************


	