-- TODO:
-- mousewheel zoom when modelling
-- 3d texture node
-- copy/paste nodes
-- +ctrl for big param value increments

-- MISC BUGS:
-- missing references - keep time node page at top for now
-- nodes incorrectly marked as 'errored' when loading project
-- resizing view windows locks up app (too many FBO objects?)

Root = {}
Root_mt = { __index = Root }

function Root.SetProjectPath(path)
	currentProjectPath = path
	local pathPart = getPath(path, '\\')
	print(pathPart)
	intruder.SetProjectDirectory(pathPart)
end

function Root.Create(window, graph)
	-- Create object
	local root = {}
	setmetatable(root, Root_mt)

	-- Set attributes
	root.window = window
	root.graph = graph
	
	root.functionMenuChanged = true -- has function menu changed since it was last constructed?
	root.menuExFunctions = nil

	-- Create buttons
	local buttonWidth = 80
	local buttonHeight = 21
	root.buttonSize = GRect(0, 0, buttonWidth, buttonHeight)
	root.buttons = {}
	
	root.buttons.load   = {["xPos"] = 5,   ["control"] = Button.Create(root, root, buttonWidth, buttonHeight, "Load",
		function()
			if (intruder.SpawnLoadFileDialog()==intruder.kDialogFileChosen) then
				local path = intruder.DialogResult()
				LoadProjectFromFile(path)
				currentProjectPath = path
			end
		end)}
	root.buttons.save   = {["xPos"] = 90,  ["control"] = Button.Create(root, root, buttonWidth, buttonHeight, "Save",
		function()
			SaveCurrentProject()
		end)}
	root.buttons.saveas = {["xPos"] = 175, ["control"] = Button.Create(root, root, buttonWidth, buttonHeight, "Save As",
		function()
			if (intruder.SpawnSaveFileDialog()==intruder.kDialogFileChosen) then
				local path = intruder.DialogResult()
				SaveProjectToFile(path)
				currentProjectPath = path
			end
		end)}
	root.buttons.export = {["xPos"] = 175 + 85, ["control"] = Button.Create(root, root, buttonWidth, buttonHeight, "Export .h",
		function()
			if (currentProjectPath) then
				ExportHeaderToFile(currentProjectPath)
			end
		end)}
	--[[root.buttons.undo   = {["xPos"] = 285, ["control"] = Button.Create(root, root, buttonWidth, buttonHeight, "Undo")}
	root.buttons.redo   = {["xPos"] = 370, ["control"] = Button.Create(root, root, buttonWidth, buttonHeight, "Redo")}
	
	root.buttons.undo.control.onClick =
		function()
			root.undoStack:Undo()
		end
	
	root.buttons.redo.control.onClick =
		function()
			root.undoStack:Redo()
		end]]
		
	root.gotoErrorButton = Button.Create(root, root, buttonWidth, buttonHeight, "Goto Error",
		function()
			print("goto error not implemented yet")
			
			local menu = ContextMenu.Create()
			for i,v in pairs(root.nodesWithErrors) do
				local str = v.errorString.." - "..v.page.name.." ("..v.xPos..", "..v.yPos..")"
				menu:AddItem(str, function() end)
			end
			menu:SelectAt(GPoint(root.frameLayout.width - 5 - buttonWidth, 5 + buttonHeight))
		end
	)
	
	-- Create controls
	root.frameLayout = FrameLayout.Create(root, root)
	
	root.parameterList = ParameterList.Create(root, root)
	root.scrolledParameterList = ScrollView.Create(root, root, root.parameterList)
	
	root.nodeGrid = NodeGrid.Create(root, root, graph)
	root.scrolledNodeGrid = ScrollView.Create(root, root, root.nodeGrid)
	
	root.pageList = PageList.Create(root, root, graph) -- (depends on nodeGrid + scrolledNodeGrid)
	
	-- Create other objects
	root.undoStack = UndoStack.Create(root)
	root.nodesWithErrors = {}
	
	root.functionList = {}
	root:UpdateFunctionList()
	root.functionMenuChanged = true -- has function menu changed since it was last constructed?

	return root
end

function Root:HandleEvent(event)
	
	-- Check for material
	if (event.type==kGEventKeyDown) then
		if (self.activeControl==nil) then
			if (event.key=="M") then
				self.graph:UpdateMaterials()
				if (self.graph:UpdateErrors()) then
					self.graph:UpdateErrorList()
					self:UpdateErrors()
				end
				self.parameterList:ShowNode(self.parameterList.currentNode) -- this is neccesary because if we are showing a material node, it's parameters may be updated
		
				return
			end
		end
	end
	
	-- Handle controls
	self.frameLayout:HandleEvent(self.window, nil, event)
	self.pageList:HandleEvent(self.window, self.frameLayout:GetPageListViewRect(), event)
	self.scrolledNodeGrid:HandleEvent(self.window, self.frameLayout:GetNodeGridViewRect(), event)
	self.scrolledParameterList:HandleEvent(self.window, self.frameLayout:GetParameterListViewRect(), event)
	
	-- Handle buttons
	for i,button in pairs(self.buttons) do
		root.buttonSize:MoveTo(button.xPos, 5)
		button.control:HandleEvent(self.window, root.buttonSize, event)
	end
	
	if (#self.nodesWithErrors > 0) then
		root.buttonSize:MoveTo(self.frameLayout.width - self.buttonSize:GetWidth() - 5, 5)
		self.gotoErrorButton:HandleEvent(self.window, root.buttonSize, event)
	end
	
	-- Send event to C++ side
	--if (root.parameterList.currentNode) then
	--	root.parameterList.currentNode:HandleEvent(event)
	--end
	if (g_pDemo.auxViewportRoot) then
		g_pDemo.auxViewportRoot:HandleEvent(event)
	end
end

function Root:Draw()
	-- Draw controls
	self.frameLayout:Draw(self.window, nil)
	self.pageList:Draw(self.window, self.frameLayout:GetPageListViewRect())
	
	self.window:DrawRect(self.frameLayout:GetNodeGridViewRect(), GColor(80, 80, 80)) -- draw background for nodegrid (can't draw behind scrollbars from inner nodegrid view)
	self.scrolledNodeGrid:Draw(self.window, self.frameLayout:GetNodeGridViewRect())
	
	self.window:DrawRect(self.frameLayout:GetParameterListViewRect(), GColor(80, 80, 80)) -- draw background for parameterlist (can't draw behind scrollbars from inner nodegrid view)
	self.scrolledParameterList:Draw(self.window, self.frameLayout:GetParameterListViewRect())
	
	-- Draw buttons
	for i,button in pairs(self.buttons) do
		root.buttonSize:MoveTo(button.xPos, 5)
		button.control:Draw(self.window, root.buttonSize)
	end
	
	if (#self.nodesWithErrors > 0) then
		root.buttonSize:MoveTo(self.frameLayout.width - self.buttonSize:GetWidth() - 5, 5)
		self.gotoErrorButton:Draw(self.window, root.buttonSize)
	end
end

function Root:NotifyPageListChanged()
	self.graph:UpdatePageIndices()
end

function Root:NotifyGridChanged(page)
	page:UpdateNodePositions()
	page:UpdateInputs()
	if (page:UpdateErrors()) then
		self.graph:UpdateErrorList()
		self:UpdateErrors()
	end
end

function Root:NotifyNodeInserted(node)
	local hasFunctionName = false
	
	for i=0,node.numParameters-1 do
		local parameter = node:GetParameter(i)	
		if (parameter.type==intruder.ZParameter_kTypeFunctionName) then
			hasFunctionName = true
		end
	end	
	
	if (hasFunctionName) then
		self.graph:UpdateFunctionNameSet()
		if (self.graph:UpdateErrors()) then
			self.graph:UpdateErrorList()
			self:UpdateErrors()
		end
	end
	
	self:NotifyGridChanged(node.page)
end

function Root:NotifyFunctionNameChanged()
	self.graph:UpdateFunctionNameSet()
	
	if (self.graph:UpdateErrors()) then
		self.graph:UpdateErrorList()
		self:UpdateErrors()
	end
	
	self:UpdateFunctionList()
end

function Root:NotifyFunctionReferencesChanged(page)
	page:UpdateFunctionReferences()
	if (page:UpdateErrors()) then
		self.graph:UpdateErrorList()
		self:UpdateErrors()
	end
end

function Root:UpdateFunctionList()
	self.functionList = {{0, "No Reference"}}	
	
	for i=0,self.graph:GetNumFunctionNames()-1 do
		table.insert(self.functionList, {i+1, self.graph:GetFunctionName(i)})
	end	
	functionMenuChanged = true
end

function Root:UpdateErrors()
	if (#self.nodesWithErrors > 0 or self.graph:GetNumErrorNodes() > 0) then
		self.nodesWithErrors = {}
		
		for i=0,self.graph:GetNumErrorNodes()-1 do
			local node = self.graph:GetErrorNode(i)
			table.insert(self.nodesWithErrors, node)
		end
	end
end



UndoStack = {}
UndoStack_mt = { __index = UndoStack }

function UndoStack.Create(root)
	local undoStack = {}
	setmetatable(undoStack, UndoStack_mt)
	
	undoStack.root = root
	
	undoStack.undoStack = {}
	undoStack.redoStack = {}

	return undoStack
end

function UndoStack:Execute(command)
	command:Execute(true)
	
	table.insert(self.undoStack, command)
	
	-- Clear redo stack
	local length = #self.redoStack
	for i=1,length do
		self.redoStack[i] = nil
	end
end

function UndoStack:Undo()
	if (#self.undoStack > 0) then
		-- Pop from undo stack
		local command = table.remove(self.undoStack)
		
		-- Execute command
		command:UnExecute(false)
		
		-- Push to redo stack
		table.insert(self.redoStack, command)
	end
end

function UndoStack:Redo()
	if (#self.redoStack > 0) then
		-- Pop from redo stack
		local command = table.remove(self.redoStack)
		
		-- Execute command
		command:Execute(false)
		
		-- Push to undo stack
		table.insert(self.undoStack, command)
	end
end
