-- -----------------------------------------------------------------------------
-- SimNetwork
-- scene.lua
-- 3D scene
-- -----------------------------------------------------------------------------

SimNetwork_SCENE = true
if not SimNetwork_GLOBAL	then dofile("global.lua") 		end
if not SimNetwork_DATA		then dofile("data.lua") 		end
if not SimNetwork_TOOLBAR	then dofile("toolbar.lua") 		end
if not SimNetwork_STATUSBAR	then dofile("statusbar.lua") 		end

-- Contains 3D Models
SPRITE = {
	
}

OBJECTS = {	-- Contains all 3D Models
	floor = {
		e = E.create_object("data/floor.obj"),
		bounds = {200, 1, 200}
	},
	
	cable0 = {
		e = E.create_object("data/cables/cable0.obj"),
		bounds = nil,
	},
	
	cable1 = {
		e = E.create_object("data/cables/cable.obj"),
		bounds = nil,
	},
	
	cable10 = {
		e = E.create_object("data/cables/cable.obj"),
		bounds = nil,
	},
	
	cable100 = {
		e = E.create_object("data/cables/cable.obj"),
		bounds = nil,
	},
	
	university = {
		e = E.create_object("data/customers/university/university.obj"),
		bounds = nil,
	},
	
	offices = {
		e = E.create_object("data/customers/offices/building1.obj"),
		bounds = {2, 2 * 3.2, 2},
	},
		
}


WIDGETS = {	-- Contains all widgets
	customer = create_overlay("data/sample_widget.png")
}

-- Define state if scenes
SCENE_STATE = {
	normal			= 0,	-- initial, nothing selected
	entity_selected		= 1,	-- entity selected
	cable_draw		= 2,
	router_drop		= 4,
}

CATEGORY_SELECTABLE = 1
CATEGORY_FLOOR = 2
CATEGORY_MOUSERAY = CATEGORY_SELECTABLE + CATEGORY_FLOOR

Scene = {}
sceneMt = {__index = Scene}

function Scene:init()
	newScene = {
		
		map = {
			mouseRay = nil,		-- Mouse ray: used for mouse picking	
			
			scene3d = nil,		-- pivot for whole 3D scene
			headLight = nil,
			
			camera3d = nil,		-- root camera
			cameraLocation = {},	-- current camera coordinates
			cameraRotation = {},	-- current camera rotation
		
			floor = nil,		-- ground
			
			-- GObjects are here!
			cables = {},
			routers = {},
			customers = {},
			
			selected = nil,		-- currently selected GObject
			tempCableSegments = {},-- holds temporary points while	
						-- cable being drawn
		},
		
		gui = {
			scene2d = nil,		-- pivot for 2D scene (toolbar and widgets)
			camera2d = nil,
			widgets = {},		-- 2D widgets
			
			widgetsCamera = nil,
			activeWidget = nil,	-- only one widget can be displayed
						-- at a time
		},
		-- current state of scene
		state = SCENE_STATE.normal,
		toolType = nil			-- tool currently being applied to
						-- scene
	}
	
	setmetatable(newScene, sceneMt)
	return newScene
end

--------------------------------------------------------------------------------
-- Function Scene:retrieveObject()
-- Inputs:	eEntity: an Electro entity
-- Returns:	instance of the entity's game logic object
-- This function is useful for retreiving the actual game logic object given
-- an Electro entity. It is used in mouse picking where do_contact() returns
-- the Electro entity and we want to map it to the actual object
function Scene:retrieveObject(eEntity)
	
	-- search in customers
	for index, object in self.map.customers do
		if object.e == eEntity then
			return object
		end
	end
	
	-- search in routers
	for index, object in self.map.routers do
		if object.e == eEntity then
			return object
		end
	end
	
	-- search in cables
	for index, object in self.map.cables do
		if object.e == eEntity then
			return object
		end
	end
	
	-- not found
	return nil
end

function Scene:select(entity)
	local newSelect = self:retrieveObject(entity)
	if self.map.selected ~= newSelect then
		self:deselect()
		self.map.selected = newSelect
	end
	
	-- high light be changing alpha
	E.set_entity_alpha(self.map.selected.e, 0.5)
	self.state = SCENE_STATE.entity_selected
	
end

function Scene:deselect()
	if self.map.selected ~= nil then
		E.set_entity_alpha(self.map.selected.e, 1.0)
		self.map.selected = nil
	end
	self.state = SCENE_STATE.normal
end

function Scene:getSelected()
	return self.map.selected
end

function Scene:getMouseRay()
	return self.map.mouseRay
end

--------------------------------------------------------------------------------
-- Function: Scene:toolSelection(tool)
-- Inputs:	tool: newly selected tool
-- Returns:	none
-- Called whenever currently selected tool changes
function Scene:toolSelection(tool)
	
	if isCable(tool) then
		-- new cable type being selected		
		if self.state == SCENE_STATE.normal then
			
			self.toolType = tool
			self.state = SCENE_STATE.cable_draw
			
		elseif	self.state == SCENE_STATE.cable_draw then			
			if self.toolType ~= tool then
				-- some other cable type has been selected
				-- cancel drawing cable
				self:cancelCableDrawing()
				
				-- start drawing the new cable
				self.toolType = tool
				self.state = SCENE_STATE.cable_draw
			end
			
		end
	end	
end

--------------------------------------------------------------------------------
-- Function: Scene:mouseTrack()
-- Inputs:	none
-- Returns:	none
-- Called whenever mouse coordinates change within scene
function Scene:mouseTrack()

	if self.state == SCENE_STATE.cable_draw then
		-- if we're drawing a cable, then we need to adjust cable
		-- segment being drawn
		self:enableMouseRay(CATEGORY_MOUSERAY)
	end
end

--------------------------------------------------------------------------------
-- Function: Scene:mouseSceneHover(a, p)
-- Inputs:	a: Electro entity being hovered
--		p: Point of hovering
-- Called by Electro's do_contact() to report mouse hover over the scene
function Scene:mouseSceneHover(a, p)
	
	-- use retreiveObject(a)
	
	if self.state == SCENE_STATE.cable_draw then
		if table.getn(self.map.tempCableSegments) > 0 then
			-- we're currently drawing a cable segment
			-- adjust the current cable segment being drawn
			
			local cableSegment = self.map.tempCableSegments[
				table.getn(self.map.tempCableSegments)]
			
			local layout = calcSegmentLayout(cableSegment.point, p)
			
			E.set_entity_position(cableSegment.e, unpack(layout.midPoint))
			E.set_entity_rotation(cableSegment.e, unpack(layout.rotation))
			E.set_entity_scale(cableSegment.e, unpack(layout.scale))
			cableSegment.angle = layout.rotation[2]
			
		end
	end
end

--------------------------------------------------------------------------------
-- Function: Scene:mouseClick()
-- Inputs:	button: "left", or "right"
-- Returns:	none
function Scene:mouseClick(button)

	if button == "right" then
	
		if	self.state == SCENE_STATE.cable_draw and
			table.getn(self.map.tempCableSegments) < 2 then
			
			-- still less than 2 points,
			-- cancel cable drawing
			self:cancelCableDrawing()
			self.state = SCENE_STATE.normal
		
		elseif self.state == SCENE_STATE.cable_draw then
			-- commit cable drawing
			self:drawCable()
			
			-- create new cable object
			local object = Cable:new(CABLES.selectedTool)
			
			-- a cable objects is composed of multiple segments,and each
			-- segment is a seperate Electro entity
			local segments = {}
			for i = 1, table.getn(self.map.tempCableSegments) do
				table.insert(segments, self.map.tempCableSegments.e)
			end
			
			table.insert(self.map.cables, {e = segments, o = object})
			self.map.tempCableSegments = {}
			self.state = SCENE_STATE.cable_draw
		else
			-- deselect current entity
			self:deselect()
		end
	
	elseif button == "left" then
		self:enableMouseRay(CATEGORY_MOUSERAY)
	end
end
	
--------------------------------------------------------------------------------
-- Function: Scene:mousePick()
-- Inputs:	a, b: picked entity and mouse ray in no particular order
--		p: point of picking
-- Returns: none
-- Called by Electro's do_contact() call back to report mouse picking event to
-- handle entity selection in 3D scene
function Scene:mousePick(a, b, p)
	
	if a == self.map.floor or b == self.map.floor then
		-- collision with floor
		if self.state == SCENE_STATE.normal then
			-- ignore
			
		elseif self.state == SCENE_STATE.entity_selected then
			self:deselect()
			
		elseif self.state == SCENE_STATE.cable_draw then
			
			-- draw temp cable segment
			local newSegment = E.create_clone(OBJECTS.cable0.e)
			p[2] = 1
			E.set_entity_position(newSegment, unpack(p))
			E.parent_entity(newSegment, self.map.headLight)
			table.insert(self.map.tempCableSegments, {e = newSegment, point = p, angle = 0})

		end
		
		--[[
		print("Floor collision")
		if isSelectedRouter() then
			print("Router selected")
			-- display router at point of collision
			E.set_entity_position(self.dropRouter, p[1], p[2], 0)
			E.set_entity_flags(self.dropRouter, E.entity_flag_hidden, false)
		end
		--]]
	else
		--print("Element collision")
		-- Collision with some element other than floor
		-- Element selection
		-- change transparency of selected element to 0.5
		if a == self.map.mouseRay then
			self:select(b)
		else
			self:select(a)
		end
	end
	
end

function Scene:cancelCableDrawing()
	
	-- remove temporary cable elements
	if self.map.tempCableSegments ~= nil and table.getn(self.map.tempCableSegments) > 0 then
		for key, value in self.map.tempCableSegments do
			if value.e ~= nil then
				E.delete_entity(value.e)
			end
		end
	end
	
	self.map.tempCableSegments = {}
end

function Scene:createElement(elementType, coordinates)

	local o = GObject:new()

	-- clone and position electro entity and enable geometry for mouse
	-- picking
	o.e = E.create_clone(OBJECTS[elementType].e)		
	E.set_entity_position(o.e, coordinates[1], 0, coordinates[2])
	E.parent_entity(o.e, self.map.headLight)
	
	if OBJECTS[elementType].bounds ~= nil then
		self:addSceneGeom(o.e, E.geom_type_box, 
			OBJECTS[elementType].bounds,
			CATEGORY_SELECTABLE)
	end
		
	
	-- E.set_entity_flags(o.e, E.entity_flag_visible_geom, true)

	
	if	elementType == "university"	or	
		elementType == "lab"		then
		o.o = Customer:new(CUSTOMERS.educational[elementType])
		table.insert(self.map.customers, o)
		
	elseif	elementType == "offices"	or
		elementType == "corporate"	then
		o.o = Customer:new(CUSTOMERS.commercial[elementType])
		table.insert(self.map.customers, o)
	end
	
end

--------------------------------------------------------------------------------
-- Function: Scene:deleteElement()
-- Inputs:	e: electro entity
-- Returns:	none
-- Deletes the element from scene, and reports entity removal to simulation
-- engine. This makes sure that if a cable is removed, for example, both ends
-- connected to the cable are updated.
function Scene:deleteElement(e)
	local o = self:retrieveObject(e)
	
	-- delete entity from scene
	if type(e) == "table" then
		for key, value in e do
			E.delete_entity(value)
			value = nil
		end
	else
		E.delete_entity(e)
		e = nil
	end
	
	-- report cable or router removal to simulation engine, because 
	-- cable and router removal are initiated by scene, rather 
	-- than simulation engine
	if o:getElementType() == "cable" or o:getElementType() == "router" then
		-- simEngine:elementRemoved(o.o)
	end
end

--------------------------------------------------------------------------------
-- Function:	Scene:calcSegmentLayout()
-- Inputs:	pointA, pointB: ends of cable
-- Return:	layout: a table describing the layout of a cable segment on scene
-- Given the two ends of the cable, function calculates midPoint, rotation,
-- and scale of a cable segment so that the caller can position and scale
-- it correctly on the screen
function calcSegmentLayout(pointA, pointB)
	local layout = {}
	pointA[2] = 1
	pointB[2] = 1
	layout.midPoint = {
		(pointA[1] + pointB[1]) / 2,
		1,
		(pointA[3] + pointB[3]) / 2,
	}
	
	layout.scale = {1, 1, distance(pointA, pointB)}
	layout.rotation = {
		0, 
		(-1 * math.deg(math.atan((pointA[3] - pointB[3]) / (pointA[1] - pointB[1])))) - 90,
		0
	}
	
	return layout
end

--------------------------------------------------------------------------------
-- Function: Scene:drawCable()
-- Inputs:	pointA, pointB: source and destination
-- Returns:	none
-- Draws a cable between pointA and pointB on (X, Z) floor (will ignore Ys)
function Scene:drawCable()

	local pointA = nil
	local pointB = nil
	
	-- loop through temporary segments
	for i = 1, table.getn(self.map.tempCableSegments) do
		local tempSegment = self.map.tempCableSegments[i]
		local midX, midY, midZ = E.get_entity_position(tempSegment.e)
		local sX, sY, sZ = E.get_entity_scale(tempSegment.e)
		
		-- replace temporary segments with permanent ones
		local segment = E.create_clone(OBJECTS[self.toolType].e)
		
		E.set_entity_scale(segment, sX, sY, sZ)
		E.set_entity_position(segment, midX, midY, midZ)
		E.set_entity_rotation(segment, 0, tempSegment.angle, 0)
		E.parent_entity(segment, self.map.headLight)
		
		-- delete temp segment
		E.delete_entity(tempSegment.e)
		tempSegment.e = segment
	end

end

function Scene:moveCamera(transV)

	-- Rotate camera
	self.map.cameraRotation[2] = self.map.cameraRotation[2] + transV[4]
	E.set_entity_rotation(self.map.camera3d, unpack(self.map.cameraRotation))

	-- Reset camera position according to current scroll vector and rotation.
	local s = math.sin(math.rad(self.map.cameraRotation[2]))
	local c = math.cos(math.rad(self.map.cameraRotation[2]))
	local x, y, z = unpack(self.map.cameraLocation)
	
	x = x + transV[1]*c + transV[2]*s
	z = z + transV[1]*(-s) + transV[2]*c
	y = y + transV[3]
	
	if (x>=-100 and x <=100 and z >= -100 and z <=100 and y>=30 and y <=160) then
		self.map.cameraLocation = {x, y, z}
		E.set_entity_position(self.map.camera3d, x + s*y, y, z + c*y)
	end
	-- E.set_entity_position(self.map.headLight, 0, 0, 0)
	
end

function Scene:addSceneGeom(theEntity, geomType, geomData, geomCategory)
	E.set_entity_geom_type(theEntity, geomType, unpack(geomData))       
	E.set_entity_geom_attr(theEntity, E.geom_attr_category, geomCategory)

end

--------------------------------------------------------------------------------
-- Function: Scene:enableMouseRay()
-- Inputs:	category: geometry category to assign to mouse ray
-- Returns:	none
-- Enables mouse ray, this function is called whenever we want to detect
-- coll ision between mouse pointer and any other entity in 3D scene. Note that
-- collisions are reported to Scene:mousePick
function Scene:enableMouseRay(category)
	local mouseV = {E.get_camera_vector(self.map.camera3d, input.MouseX, input.MouseY)}
	local cameraP = {E.get_entity_position(self.map.camera3d)}
					
	-- Enable collision on floor
	self:addSceneGeom(self.map.floor,
		E.geom_type_box,
		OBJECTS.floor.bounds,
		CATEGORY_FLOOR)	
		
	-- Enable the mouse ray.
	E.set_entity_geom_type(
		self.map.mouseRay, 
		E.geom_type_ray, 
		cameraP[1], cameraP[2], cameraP[3],
		unpack(v_mult(mouseV, 100)))
				
	-- enable mouse ray collision
	E.set_entity_geom_attr(self.map.mouseRay, E.geom_attr_callback, category)
end

function Scene:disableMouseRay()
	
	-- Disable mouse ray
	E.set_entity_geom_attr(self.map.mouseRay, E.geom_attr_callback, 0)
	E.set_entity_geom_type(
		self.map.mouseRay,
		E.geom_type_ray,
		0, 0, 0, 0, 0, 0)
		
	-- Disable collision on floor
	E.set_entity_geom_attr(self.map.floor, E.geom_type_none)

end

--------------------------------------------------------------------------------
-- Function: Scene:toggleWidget()
-- Inputs/Returns: none
-- Toggles widget for currently selected element
function Scene:toggleWidget()
	if self.gui.activeWidget ~= nil then
		print("Destroy widget")
		-- destroy active widget
		E.delete_entity(self.gui.activeWidget)
		self.gui.activeWidget = nil
		
	else
		if self.map.selected == nil then
			-- no element selected, ignore
			print("No selected element, skipping.")
			return
		end
		print("Creating customer's widget.")
		-- create a clone for the new widget
		-- for now, do a customer widget
		self.gui.activeWidget = E.create_clone(WIDGETS.customer)
		--[[
		if self.map.selected.o:getType() == CUSTOMERS then
			self.gui.activeWidget = E.create_clone(WIDGETS.customer)
		elseif self.map.selected.o:getType() == CABLES then
			self.gui.activeWidget = E.create_clone(WIDGETS.cable)
		elseif self.map.selected.o:getType() == ROUTERS	 then
			self.gui.activeWidget = E.create_clone(WIDGETS.router)
		end
		--]]
		
		-- position widget
		E.set_entity_position(self.gui.activeWidget, input.MouseX, -input.MouseY, 0)
		-- add it to scene2d heirarchy
		E.parent_entity(self.gui.activeWidget, self.gui.widgetsCamera)
	end		
end

--------------------------------------------------------------------------------
-- Function: Scene:initGUI()
-- Inputs: none
-- Returns: none
-- Sets up 2D scene to be used for game's toolbar interface (and maybe 
-- other things in the future)
function Scene:initGUI()
		
	-- create 2D camera, place it in the center of screen
	self.gui.camera2d = E.create_camera(E.camera_type_orthogonal)
	E.set_entity_position(self.gui.camera2d, -viewport.k * viewport.w/2, 
				-viewport.k * viewport.h/2, 0)
	E.set_entity_scale(self.gui.scene2d, viewport.k, viewport.k, viewport.k)
	
	-- create 2D scene
	self.gui.scene2d = E.create_pivot()

	-- add toolbar to 2D scene
	E.parent_entity(create_toolbar(), self.gui.scene2d)
	E.parent_entity(create_balancebar(), self.gui.scene2d)
	E.parent_entity(self.gui.scene2d, self.gui.camera2d)

	-- create a camera for displaying widgets
	self.gui.widgetsCamera = E.create_camera(E.camera_type_orthogonal)
	E.set_entity_position(self.gui.widgetsCamera, 0, -viewport.T*2, 0)
	
end

function Scene:loadDemoScene()
	-- create entities
	self.map.scene3d = E.create_pivot()
	self.map.camera3d = E.create_camera(E.camera_type_perspective)
	
	--E.set_camera_range(camera3d, 1, 100)
	local lightL = E.create_light(E.light_type_positional)
	local light3d  = E.create_light(E.light_type_positional)
	local lightR = E.create_light(E.light_type_positional)
	self.map.headLight = E.create_light(E.light_type_positional)		
	self.map.floor = E.create_clone(OBJECTS.floor.e)
		
	-- Create SKY 
	local sky = E.create_object("data/sky.obj")

	local envmap = E.create_image("data/sky_nx.png",
                            "data/sky_px.png",
                            "data/sky_ny.png",
                            "data/sky_py.png",
                            "data/sky_nz.png",
                            "data/sky_pz.png")

	-- scale sky
	E.set_entity_scale(sky, 200, 200, 200) 

	-- group all 3D entities under scene3d
	E.parent_entity(lightL, self.map.camera3d)
	E.parent_entity(lightR, lightL)
	E.parent_entity(light3d, lightR)
	E.parent_entity(self.map.scene3d, light3d)
	E.parent_entity(sky,self.map.headLight)
	E.parent_entity(self.map.floor, self.map.headLight)
	E.parent_entity(self.map.headLight, self.map.scene3d)
	E.set_light_color(self.map.headLight, 1.0, 1.0, 1.0)
	
	-- initial camera position               
	self.map.cameraLocation = {0, 60, 0}
	self.map.cameraRotation = {-45, 0, 0}
	self:moveCamera({0, 0, 0, 0})
	
	-- position light sources
	E.set_entity_position(light3d, 0, 30, 30)
	E.set_entity_position(lightL, -10, 0, 3)
	E.set_entity_position(lightR, 10, 0, 3)
	E.set_entity_position(self.map.headLight, 0, 20, 100)
	
	-- position sky
	E.set_brush_image(E.get_mesh(sky, 0), envmap)
	E.set_brush_flags(E.get_mesh(sky, 0), E.brush_flag_unlit,     true)
	E.set_brush_flags(E.get_mesh(sky, 0), E.brush_flag_sky_map_0, true)
			
	-- create two customers of type commercial.offices
	self:createElement("offices", {20, 0})
	self:createElement("offices", {-30, 16})
	
	-- create mouse ray for mouse picking
	self.map.mouseRay = E.create_pivot()
	E.parent_entity(self.map.mouseRay, self.map.camera3d)
	E.set_entity_geom_attr(self.map.mouseRay, E.geom_attr_category, CATEGORY_MOUSERAY)

	-- enable collision on floor
	--[[
	self:addSceneGeom(self.map.floor,
		E.geom_type_box,
		OBJECTS.floor.bounds,
		CATEGORY_FLOOR)	
	--]]
	
end

function do_contact(a, b, px, py, pz, xn, ny, nz, d)

	local p = {px, py, pz}
	
	-- disable mouse ray collision
	gameScene:disableMouseRay()
	
	-- report collision
	if input.MouseLDown then
		gameScene:mousePick(a, b, {px, py, pz})
	else
		if a == gameScene:getMouseRay() then
			gameScene:mouseSceneHover(b, p)
		else
			gameScene:mouseSceneHover(a, p)
		end
	end
		
end
