-- -------------------------------------------------------------------
-- SimNetwork
-- data.lua
-- Defines data structures of game
-- -------------------------------------------------------------------

SimNetwork_DATA = true

-- constant: Elements
-- This constant describes cables, routers, and types of customers available
-- in the game
-- Cables have 3 properties: bandwidth (b), cost per mile (c), maintenance cost per mile (m)
-- Customers have 2 properties: number of ports (p), maximum bandwidth
-- they can handle (b)
-- Customers have 4 properties: (minB
ELEMENTS = 
{
	cable = 
	{ 
		cable1 =	{b = 1, c = 1, m = 0.01},
		cable10 =	{b = 10, c = 2, m = 0.02},
		cable100 =	{b = 100, c = 3, m = 0.03}, 
	},
		
	router = {
				-- # of ports (p), maximum bandwidth (b), cost (c)
		router4 =	{p = 4, b = 30, c = 10, m = 0.1},
		router6 =	{p = 6, b = 100 , c = 70, m = 0.7},
		router8 =	{p = 8, b = 512, c = 200, m = 5},
	},
		
	customer = 
	{ 
		offices = {baseB = 1, bIncrement = 2, maxBIncrement = 10, baseFee = 20, fIncrement = 5, maxFIncrement = 6},
		corporate = {baseB = 10, bIncrement = 5, maxBIncrement = 10, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		factory1 = {baseB = 5, bIncrement = 5, maxBIncrement = 10, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		factory2 = {baseB = 5, bIncrement = 5, maxBIncrement = 10, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		lab = {baseB = 10, bIncrement = 10, maxBIncrement = 10, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		library = {baseB = 5, bIncrement = 5, maxBIncrement = 10, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		residential = {baseB = 1, bIncrement = 1, maxBIncrement = 1, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		university = {baseB = 30, bIncrement = 10, maxBIncrement = 7, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		church = {baseB = 1, bIncrement = 1, maxBIncrement = 1, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
		art_college = {baseB = 30, bIncrement = 10, maxBIncrement = 7, baseFee = 30, fIncrement = 5, maxFIncrement = 10},
	}
		
}

CUSTOMER_COUNT = 10
CUSTOMER_TYPE_MAP = {
	
	"offices",
	"corporate",
	"factory1",
	"factory2",
	"lab",
	"library",
	"residential",
	"university",
	"church",
	"art_college"
}

GAME_LOOSE_SAT = 20		-- player looses if satisfaction rate falls below this
GAME_WIN_SAT = 50

STATE = {
	CUSTOMER = {
		justAppeared = 0,	-- not used
		unInterested = 1,	-- not used now
		interested = 2,	
		contracted = 3,
	},
	
}
		
-- shortcuts (i.e. say 'CABLE.cable1' instead of 'Elements.cable.cable1')
CABLES		= ELEMENTS.cable
ROUTERS		= ELEMENTS.router
CUSTOMERS	= ELEMENTS.customer
INFINITY	= 4294967295

-- Time and pacing constants
TIME_STEP = 0.5				-- time unit is 0.5 day
MONTH = 60				-- month is 60 time units
SAT_HISTORY_SIZE = 60			-- how many previous satisfaction rates
					-- should we remember.
-- Global helper functions
--------------------------------------------------------------------------------
-- Function:	monthPassed()
-- Inputs:	period
-- Returns true if the period is a month or more, otherwise returns less
function monthPassed(period)
	if period > MONTH then
		return true
	else
		return false
	end
end

-- class GObject
-- A base class for game objects: cables, routers, and customers
-- -------------------------------------------------------------
GObject = {}	
GObjectMt = {__index = GObject}

function GObject:new()
	newObject = {
		e = nil,	-- electro's entity
		o = nil,	-- game logic instance
		anim = nil,	-- animation object
	}
	
	setmetatable(newObject, GObjectMt)
	return newObject
end

function GObject:animate()
	if self.o.animate ~= nil then
		self.o:animate()
	end
end

function GObject:timer()
	if self.o.timer ~= nil then
		self.o:timer()
	end
end

-- class: Customer
-- Represents a customer
-- ---------------------
Customer = {}
customerMt = {__index = Customer}
function Customer:new(cusType, cusName, customerE)

	local newCustomer = {
		customerType = cusType,
		name = cusName,

		initialBandwidth = 5,		-- initial bandwidth requirement
		currentBandwidth = 0,		-- current bandwidth requirement

		suppliedBandwidth = 0,		-- how much bandwidth is supplied
						-- to the customer
		initialFee = 0,			-- initial max fee requirement
		currentFee = 0,			-- current fee requirement
		
		age = 0,
		time = 0,			-- incremented from 0 to month
						-- then reset to 0 again
						
		satisfaction = 0,		-- current satisfaction rate, should
						-- be set to averate of satHistory
			
		peers = {},			-- adjacency list of pears
		links = {},			-- adjacency list of directly
						-- connected elements (routers or
						-- other customers)
		
		-- delivered bandwidth: how much bandwidth can the customer
		-- deliver to his peers, indexed by peer number
		deliveredBandwidth = {},
		
		-- this table is filled with entries of paths to peers
		peerPaths = {},
		
		-- current state of customer
		state = STATE.CUSTOMER.interested,

		-- flow graph of customer
		graph = nil,
		
		-- Electro entity
		e = customerE,
		
		satHistory = {}		-- history of satisfaction rate history

		
	}
	
	-- generate random initial bandwidth and initial fee
	local fee =		math.random(CUSTOMERS[cusType].maxFIncrement) *
			CUSTOMERS[cusType].fIncrement + CUSTOMERS[cusType].baseFee
			
	local bandwidth =	math.random(CUSTOMERS[cusType].maxBIncrement) *
			CUSTOMERS[cusType].bIncrement + CUSTOMERS[cusType].baseB
	
	--print("New customer: ", cusType, fee, bandwidth)
	--print("baseFee:", CUSTOMERS[cusType].baseFee, ",maxFee:", CUSTOMERS[curType].maxFIncrement)
	--print("NewCustomer: ", fee, bandwidth)
	newCustomer.initialBandwidth = bandwidth
	newCustomer.initialFee = fee
	
	setmetatable(newCustomer, customerMt)
	return newCustomer
end

function Customer:signContract(fee)
	
	self.state = STATE.CUSTOMER.contracted
	
	self.currentFee = fee
	self.currentBandwidth = self.initialBandwidth
	self.satHistory = {}

	for i=1, math.floor(SAT_HISTORY_SIZE / 2) do
		table.insert(self.satHistory, 100)
	end
	self.satisfaction = 100
end


function Customer:setInitialBandwidth(bnd)
	self.initialBandwidth = bnd
end

function Customer:getInitialFee()
	return self.initialFee
end

function Customer:getInitialBandwidth()
	return self.initialBandwidth
end

function Customer:getAge()
	return self.age
end

function Customer:getTime()
	return self.time
end

function Customer:timeTick()
	self.time = self.time + 1
	self.age = self.age + 1
	
	local instantSat = nil
	if self:getCurrentBandwidth() == 0 then
		instantSat = 100
	else
		instantSat = math.floor(self:getSuppliedBandwidth() * 100 / self:getCurrentBandwidth())
	end
	table.insert(self.satHistory, instantSat)
	
	if table.getn(self.satHistory) > SAT_HISTORY_SIZE then
		table.remove(self.satHistory, 1)
	end
	
	-- calculate satisfaction rate
	local sat = 0
	for i=1, table.getn(self.satHistory) do
		sat = sat + self.satHistory[i]
	end
	self.satisfaction = math.floor(sat / table.getn(self.satHistory)) 
end

function Customer:resetTime()
	self.time = 0
end

function Customer:getSatisfactionRate()
	return self.satisfaction
end

function Customer:getState()
	return self.state
end

function Customer:resetFlow()
	self.graph = nil
	self.deliveredBandwidth = {}
	self.peerPaths = {}
end

function Customer:getE()
	return self.e
end

function Customer:addPeer(anotherCustomer)

	table.insert(self.peers, anotherCustomer)
	-- relation ship is asymmertric
	-- table.insert(anotherCustomer.peers, self)
	
	table.insert(self.deliveredBandwidth, 0)
	table.insert(self.peerPaths, {})
	
end

function Customer:getName()
	return self.name
end

function Customer:getLinks()
	return self.links
end

function Customer:getPeers()
	return self.peers
end

function Customer:getPeer(index)
	return self.peers[index]
end

function Customer:getPeerCount()
	return table.getn(self.peers)
end

function Customer:setDeliveredBandwidth(peerIndex, bandwidth)
	self.deliveredBandwidth[peerIndex] = bandwidth
end

function Customer:getDeliveredBandwidth(peerIndex)
	return self.deliveredBandwidth[peerIndex]
end

function Customer:getDesiredPeerBandwidth()
	return math.floor(self:getCurrentBandwidth() / self:getPeerCount())
end

function Customer:getStatusBar()
	return 100 * self.suppliedBandwidth / self:getCurrentBandwidth()
end

function Customer:getDeliveredPercentage(peerIndex)
	return self:getDeliveredBandwidth(peerIndex) * 100 / self:getDesiredPeerBandwidth()
end

function Customer:getSuppliedBandwidth()
	return self.suppliedBandwidth
end

function Customer:setSuppliedBandwidth(bandwidth)
	self.suppliedBandwidth = bandwidth
end

function Customer:getCurrentBandwidth()
	return self.currentBandwidth
end

function Customer:setCurrentBandwidth(bandwidth)
	self.currentBandwidth = bandwidth
end

function Customer:getMonthlyFee()
	return self.currentFee
end

function Customer:setCurrentFee(montlyFee)
	self.currentFee = montlyFee
end

function Customer:getPeerConnection(index)
	
end

function Customer:getLink()
	if table.getn(self.links) > 0 then
		return self.links[1]
	else
		return nil
	end
end

function Customer:asymAddConnection(someElement, theCable)
	if self:getLink() ~= nil then
		-- this element is already linked
		return "link_max"
	end
	table.insert(self.links, {destination = someElement, cable = theCable})
	return "success"
end

function Customer:addConnection(someElement, theCable)
	
	-- only one connection per customer
	if not self:canAddNewConnection() then
		return "link_max"
	end
	
	local res = someElement:asymAddConnection(self, theCable)
	if res ~= "success" then
		return res
	end
	table.insert(self.links, {destination = someElement, cable = theCable})
	return "success"
end

function Customer:getElementType()
	return "customer"
end

function Customer:getSubtype()
	return self.customerType
end

function Customer:getProperties()
	return CUSTOMERS[self.customerType]
end

function Customer:canAddNewConnection()
	if table.getn(self.links) > 0 then
		return false
	else
		return true
	end
end

function Customer:canDemolish()
	return false
end

function Customer:removeCable(theCable)
	if table.getn(self.links) == 1 then
		table.remove(self.links)
	else
		-- we shouldn't be here
		E.print_console(" ** WARNING: trying to remove a link from customer that has no cables!\n")
	end
end

function Customer:traceTrafficFlow()
	if self.state ~= STATE.CUSTOMER.contracted then
		return nil
	elseif self.state == STATE.CUSTOMER.contracted and self.graph == nil then
		E.print_console(" ** WARNING: trying to retrieve contracted customer traffic flow with NIL flow graph!\n")
		return nil
	else
		return self.graph:traceFlow()
	end
end

function Customer:getPeerPath(peerIndex)

end
--------------------------------------------------------------------------------
-- Function:		Customer:resetFlow()
-- Inputs/Outputs:	none
-- Removes all flow information from previous simulation iterations
function Customer:resetFlow()
	for i = 1, table.getn(self.deliveredBandwidth) do
		self.deliveredBandwidth[i] = 0
		self.peerPaths[i] = {}
	end
end

-- class: Router
-- Router's meta table
-- --------------------
Router = {}
routerMt = {__index = Router}
function Router:new(rtrType, rtrName, routerE)

	local newRouter = {
		routerType = rtrType,
		name = rtrName,
		age = 0,
		time = 0,
		currentLoad = 0,		-- number of traffic units (Mbps)
						-- router currently handling
		links = {},			-- adjacency list of connections
		
		e = routerE,
	}
	
	setmetatable(newRouter, routerMt)
	return newRouter
end

function Router:getAge()
	return self.age
end

function Router:getTime()
	return self.time
end

function Router:timeTick()
	self.time = self.time + 1
	self.age = self.age + 1
end

function Router:resetTime()
	self.time = 0
end

function Router:getE()
	return self.e
end

function Router:getName()
	return self.name
end

function Router:asymAddConnection(someElement, theCable)
	if self:getUsedPorts() == self:getPortCount() then
		-- will exceed number of available ports
		return "link_max"
	elseif self:getLink(someElement) ~= nil then
		-- this element is already linked
		return "already_linked"
	end
	table.insert(self.links, {destination = someElement, cable = theCable})
	return "success"
end

function Router:addConnection(someElement, theCable)

	if not self:canAddNewConnection() then
		-- will exceed number of available ports
		return "link_max"
	elseif self:getLink(someElement) ~= nil then
		-- this element is already linked
		return "already_linked"
	end
	
	local res = someElement:asymAddConnection(self, theCable)
	if res ~= "success" then
		return res
	end
	table.insert(self.links, {destination = someElement, cable = theCable})
	
	return "sucess"
end

function Router:getLinks()
	return self.links
end

function Router:getLink(someElement)
	for x, link in self:getLinks() do
		if link.destination == someElement then
			return link
		end
	end
	return nil
end

function Router:getUsedPorts()
	return table.getn(self.links)
end

function Router:getElementType()
	return "router"
end

function Router:getSubtype()
	return self.routerType
end

function Router:getProperties()
	return ROUTERS[self.routerType]
end

function Router:getPortCount()
	return ROUTERS[self.routerType].p
end

function Router:getMaxBandwidth()
	return ROUTERS[self.routerType].b
end

function Router:getCost()
	return ROUTERS[self.routerType].c
end

function Router:canAddNewConnection()
	if self:getUsedPorts() == self:getPortCount() then
		return false
	else
		return true
	end
end

function Router:canDemolish()
	if self:getUsedPorts() > 0 then
		return false
	else
		return true
	end
end

function Router:removeCable(cable)
	local index = searchTable(self.links, "cable", cable)
	table.remove(self.links, index)
end

function Router:getMaintenanceCost()
	return ROUTERS[self.routerType].m
end

-- class: Cable
-- ------------
Cable = {}
cableMt = {}
cableMt.__index = Cable

function Cable:new(cblType, cableLength, cableE)

	local newCable = {
		cableType = cblType,	-- string; "cable1", "cable10", etc...
		age = 0,		-- age of cable
		time = 0,
			
		currentLoad = 0,	-- current load (bandwidth on cable)
		length = cableLength,	-- length of cable
		flow = {},		-- traffic flowing in this cable. Each entry
					-- is a pair of:
					--	- customer:	the customer contributing to this flow
					--	- amount:	the amount of flow
		angles = nil,		-- rotational angle of segements about Y in degrees
		e = cableE,
	}
	
	setmetatable(newCable, cableMt)
	return newCable
end

function Cable:getAge()
	return self.age
end

function Cable:getTime()
	return self.time
end

function Cable:timeTick()
	self.time = self.time + 1
	self.age = self.age + 1
end

function Cable:resetTime()
	self.time = 0
end


function Cable:getE()
	return self.e
end

function Cable:getElementType()
	return "cable"
end

function Cable:getSubtype()
	return cableType
end

function Cable:getProperties()
	return CABLES[self.cableType]
end

function Cable:getBandwidth()
	return CABLES[self.cableType].b
end

function Cable:getCurrentLoad()
	return self.currentLoad
end

function Cable:getUtilization()
	return math.floor(100 * self:getCurrentLoad() / self:getBandwidth())
end

function Cable:getMileCost()
	return CABLES[self.cableType].c
end

function Cable:getCost()
	return (CABLES[self.cableType].c) * self.length
end

function Cable:setSegments(segments)
	self.e = segments
end

function Cable:getLength()
	return self.length
end

function Cable:setLength(len)
	self.length = len
end

function Cable:canDemolish()
	return true
end

function Cable:getAngles()
	return self.angles
end

function Cable:setAngles(angs)
	self.angles = angs
end

function Cable:getSegmentCount()
	return table.getn(self.angles)
end

function Cable:resetFlow()
	self.flow = {}
	self.currentLoad = 0
end

function Cable:getFlow(customerO)
	for x, cons in self.flow do
		if customerO == cons.customer then
			return cons
		end
	end
	
	return nil
end

function Cable:addConsumer(customerO, bandwidthC)
	local index = table.insert(self.flow, {customer = customerO, bandwidth = bandwidthC})
	self.currentLoad = self.currentLoad + bandwidthC
	
	return self.flow[index]
end

function Cable:adjustConsumption(customerO, newBandwidth)
	local theFlow = self:getFlow(customerO)
	if theFlow == nil then
		theFlow = self:addConsumer(customerO, newBandwidth)
		--E.print_console("** WARNING: adjustConsumption(): argument customer object does not have flow on cable.\n")
	else
		self.currentLoad = self.currentLoad - theFlow.bandwidth + newBandwidth
		theFlow.bandwidth = newBandwidth
	end	
end


function Cable:getConsumption(customerO)
	local theFlow = self:getFlow(customerO)
	if theFlow == nil then
		return 0
	else
		return theFlow.bandwidth
	end
end

function Cable:getMaintenanceCost()
	return (CABLES[self.cableType].m * self.length)
end

Demolish = {}
DemolishMt = {__index = Demolish}
setmetatable(Demolish, DemolishMt)

function Demolish:getCost()
	return 10
end
