NodeGrid = {}
NodeGrid_mt = { __index = NodeGrid }
setmetatable(NodeGrid, Control_mt)

function NodeGrid.Create(root, parentObject, graph)
	-- Create object
	local nodeGrid = Control.Create(root, parentObject)
	setmetatable(nodeGrid, NodeGrid_mt)
	
	-- Set constants
	nodeGrid.kMargin = 30
	nodeGrid.kNodeSize = GPoint(70, 20)
	nodeGrid.kNodeSpacing = GPoint(1, 1)
	nodeGrid.kNodeSideGrabWidth = 6
	
	-- Set attribtues
	nodeGrid.graph = graph
	nodeGrid.scrollOffset = GPoint(0, 0)
	
	nodeGrid.hotNodeCoord = nil
	nodeGrid.hotNode = nil
	nodeGrid.hotLeftGrabNode = nil
	nodeGrid.hotRightGrabNode = nil
	
	nodeGrid.dragLeftGrabNode = nil
	nodeGrid.dragRightGrabNode = nil
	
	nodeGrid.isDragging = false
	nodeGrid.copyingNodes = false
	nodeGrid.dragStartPos = nil
	nodeGrid.dragRelativeOffset = nil
	nodeGrid.dragRelativeCoordOffset = nil
	nodeGrid.dragSuccess = false
	nodeGrid.lastCheckedRelativeCoordOffset = nil

	nodeGrid.copiedNodes = {}
	nodeGrid.cuttingNodes = false
	
	-- Context menus
	--[[nodeGrid.emptyNodeContextMenu = ContextMenu.Create()
	
	-- Loop over types in node factory
	for baseNodeType=0,g_pNodeFactory:GetNumBaseNodeTypes()-1 do
		for i=0,g_pNodeFactory:GetNumNodeTypes(baseNodeType)-1 do
			local nodeType = g_pNodeFactory:GetNodeType(baseNodeType, i)
			
			-- Add type creation item to context menu
			nodeGrid.emptyNodeContextMenu:AddItem(nodeType.name,
			function(pos)
				local newNode = retain(g_pNodeFactory:CreateNode(nodeGrid.currentPage, nodeType.type))
				newNode.name = nodeType.name
				nodeGrid.currentPage:SetNode(pos.x, pos.y, newNode)
				nodeGrid:SelectNode(newNode)
				nodeGrid.root:NotifyNodeInserted(newNode)
			end)
		end
	end]]
		
	nodeGrid.nodeContextMenu = ContextMenu.Create()
		nodeGrid.nodeContextMenu:AddItem("Remove Node",
			function(pos)
				local node = nodeGrid.currentPage:GetNode(pos.x, pos.y)
				
				-- Check if showing in parameter list
				if (nodeGrid.currentPage.parameterListNode==node) then
					nodeGrid:SelectNode(nil)
				end
				
				-- Check if showing in main demo view
				if (g_pDemo.mainViewportRoot==node) then
					g_pDemo.mainViewportRoot = nil
				end
				
				-- Check if showing in aux demo view
				if (g_pDemo.auxViewportRoot==node) then
					g_pDemo.auxViewportRoot = nil
				end
				
				nodeGrid.currentPage:SetNode(pos.x, pos.y, nil)
				nodeGrid.root:NotifyGridChanged(nodeGrid.currentPage)
				
				-- It may be a function node, so update function refs
				nodeGrid.root:NotifyFunctionNameChanged()
				
			end)
		nodeGrid.nodeContextMenu:AddItem("Cut Node(s)",
			function(pos)
				nodeGrid:CutNodes()		
			end)
		nodeGrid.nodeContextMenu:AddItem("Copy Node(s)",
			function(pos)
				nodeGrid:CopyNodes()		
			end)
		nodeGrid.nodeContextMenu:AddItem("Set Main View Root",
			function(pos)
				local node = nodeGrid.currentPage:GetNode(pos.x, pos.y)
				nodeGrid:SelectNode(node)
				g_pDemo.mainViewportRoot = node
			end)
		nodeGrid.nodeContextMenu:AddItem("Set Aux View Root",
			function(pos)
				local node = nodeGrid.currentPage:GetNode(pos.x, pos.y)
				nodeGrid:SelectNode(node)
				g_pDemo.auxViewportRoot = node
			end)
		nodeGrid.nodeContextMenu:AddItem("Set Precalc View Root",
			function(pos)
				local node = nodeGrid.currentPage:GetNode(pos.x, pos.y)
				nodeGrid:SelectNode(node)
				g_pDemo.precalcViewportRoot = node
			end)
		nodeGrid.nodeContextMenu:AddItem("Reset",
			function(pos)
				local node = nodeGrid.currentPage:GetNode(pos.x, pos.y)
				node:Reset()
			end)
		
	
	-- Surfaces
	nodeGrid.nodeTypeGradients = {}
	
	for type=0,intruder.kNumBaseNodeTypes-1 do
		local hue = type * 360 / intruder.kNumBaseNodeTypes 
		local saturation = 0.4
		local gradient = GGradient()
		
		gradient:InsertColor(0.000, GColorFromHSV(hue, saturation, 0.9))
		gradient:InsertColor(0.499, GColorFromHSV(hue, saturation, 0.7))
		gradient:InsertColor(0.500, GColorFromHSV(hue, saturation, 0.6))
		gradient:InsertColor(1.000, GColorFromHSV(hue, saturation, 0.5))
		
		nodeGrid.nodeTypeGradients[type] = gradient
	end
	
	nodeGrid.nodeSizeRect = GRect(0, 0, nodeGrid.kNodeSize.x, nodeGrid.kNodeSize.y)
	nodeGrid.outlineSurface = GSurface(nodeGrid.kNodeSize.x, nodeGrid.kNodeSize.y)
	nodeGrid.outlineSurface:Clear(GColor(0, 0, 0, 0))
	nodeGrid.outlineSurface:DrawRoundedRectOutlined(nodeGrid.nodeSizeRect:CloneWithInset(0.5), GColor(0, 0, 0, 60), 5, 0.5)
	nodeGrid.outlineSurface:UpdateTexture()
	
	nodeGrid.nodeSurfaces = {}
	nodeGrid.activeSurfaces = {} -- outlines for selected nodes
	nodeGrid.copySourceSurfaces = {} 
	nodeGrid.copyDestOkSurfaces = {} 
	nodeGrid.copyDestBadSurfaces = {} 
	nodeGrid.errorOutlineSurfaces = {}
	-- grid numbers
	nodeGrid.nodeNumberSurfacesLeft = {}
	nodeGrid.nodeNumberSurfacesTop = {}
	
		-- generate node grid ref textures
	-- left 
	for cnt = 0, 64 do		
		local nnSurface = GSurface(nodeGrid.kNodeSize.x/2, nodeGrid.kNodeSize.y)
		nnSurface:Clear(GColor(80, 80, 80, 255))
		nnSurface:DrawTextAligned(intruder.kFontFaceLucidaGrande, string.format("%d", cnt), GColor(255, 255, 255), 9, kGAlignRight, kGVerticalAlignCenter, GPoint(0,0));
		nnSurface:UpdateTexture()
		nodeGrid.nodeNumberSurfacesLeft[cnt] = nnSurface		
	end
	-- top
	for cnt = 0, 64 do		
		local nnSurface = GSurface(nodeGrid.kNodeSize.x, nodeGrid.kNodeSize.y)
		nnSurface:Clear(GColor(80, 80, 80, 255))
		nnSurface:DrawTextAligned(intruder.kFontFaceLucidaGrande, string.format("%d", cnt), GColor(255, 255, 255), 9, kGAlignCenter, kGVerticalAlignCenter, GPoint(0,0));
		nnSurface:UpdateTexture()
		nodeGrid.nodeNumberSurfacesTop[cnt] = nnSurface		
	end
	
	local surface
	
	for width=1,16 do
		local rectWidth = nodeGrid.kNodeSize.x * width + nodeGrid.kNodeSpacing.x*(width-1)
		local rectHeight = nodeGrid.kNodeSize.y
		
		local rect = GRect(0, 0, rectWidth, rectHeight)
		
		-- Create node gradient rects (one for each base type)
		nodeGrid.nodeSurfaces[width] = {}
		
		for type=0,intruder.kNumBaseNodeTypes-1 do
			surface = GSurface(rectWidth, rectHeight)
			surface:Clear(GColor(0, 0, 0, 0))
			surface:DrawRoundedRectVerticalGradient(rect:CloneWithInset(0.5), nodeGrid.nodeTypeGradients[type], 5)
			surface:UpdateTexture()
			nodeGrid.nodeSurfaces[width][type] = surface
		end
		
		-- Create outline surface
		surface = GSurface(rectWidth, rectHeight)
		surface:Clear(GColor(0, 0, 0, 0))
		surface:DrawRoundedRectOutlined(rect:CloneWithInset(1.0), GColor(0, 0, 0, 255), 4, 1.5)
		surface:UpdateTexture()
		nodeGrid.activeSurfaces[width] = surface

		-- Create copying outline surface
		surface = GSurface(rectWidth, rectHeight)
		surface:Clear(GColor(0, 0, 0, 0))
		surface:DrawRoundedRectOutlined(rect:CloneWithInset(1.0), GColor(0, 255, 0, 255), 4, 1.5)
		surface:UpdateTexture()
		nodeGrid.copySourceSurfaces[width] = surface
		
		-- Create error outline surface
		local errorRect = GRect(0, 0, rectWidth + 6, rectHeight + 6)
		surface = GSurface(rectWidth + 6, rectHeight + 6)
		surface:Clear(GColor(0, 0, 0, 0))
		surface:DrawRoundedRectOutlined(errorRect:CloneWithInset(1.0), GColor(255, 0, 0, 192), 6, 3.5)
		surface:UpdateTexture()
		nodeGrid.errorOutlineSurfaces[width] = surface
	end
	
	-- Misc surfaces
	nodeGrid.triangleSurface = GSurface(5, 10)
	nodeGrid.triangleSurface:Clear(GColor(0, 0, 0, 0))
	nodeGrid.triangleSurface:DrawTriangleRight(GRect(0, 0, 5, 10), GColor(0, 0, 0, 192))
	nodeGrid.triangleSurface:UpdateTexture()
	
	
	-- Dictionary for surfaces for names and errors
	nodeGrid.nameSurfaces = {}
	nodeGrid.errorSurfaces = {}
	
	return nodeGrid
end

function NodeGrid:CutNodes()
	copiedNodes = {}
	for node in self:SelectedNodeIterator() do
		table.insert(self.copiedNodes, node)
	end
	self.cuttingNodes = true
end

function NodeGrid:CopyNodes()
	self.copiedNodes = {}
	for node in self:SelectedNodeIterator() do
		table.insert(self.copiedNodes, node)
	end
	self.cuttingNodes = false
end

function NodeGrid:PasteNodes(eventPos)
	if (table.getn(self.copiedNodes) > 0) then

		local posX = self.hotNodeCoord.x;
		local posY = self.hotNodeCoord.y;

		if (eventPos~=nil)	then
			--posX = eventPos.x
			--posY = eventPos.y
			--print(' p ' .. self.hotNodeCoord.x .. ' ' .. self.hotNodeCoord.y )
		end

		if (self.cuttingNodes) then
			-- cut/paste
			print('cut/paste')
			for i, node in pairs(self.copiedNodes) do
				node.page:SetNode(node.xPos, node.yPos, nil) -- remove
				self.currentPage:SetNode(posX, posY, node) -- re-add
			end
		else
			-- copy/paste
			print('copy/paste')
			for i, node in pairs(self.copiedNodes) do
				local newNode = retain(g_pNodeFactory:CopyNode(node))
				self.currentPage:SetNode(posX, posY, newNode) -- add copy
			end
		end
					
		self.root:NotifyGridChanged(node.page)
		self.root:NotifyGridChanged(self.currentPage)
	end

	self.copiedNodes = {}
end

function NodeGrid:PageWasSelected(page)
	self.currentPage = page
	
	if (page ~= nil) then
		self.root.parameterList:ShowNode(page.parameterListNode)
	else
		self.root.parameterList:ShowNode(nil)
	end
end

function NodeGrid:GetCurrentPage()
	return self.currentPage
end

function NodeGrid:GetCanvasSize(rect)
	local size = GPoint(0, 0)
	
	if (self.currentPage) then
		size = GPoint(2*self.kMargin + self.currentPage.width*self.kNodeSize.x + (self.currentPage.width-1)*self.kNodeSpacing.x, 2*self.kMargin + self.currentPage.height*self.kNodeSize.y + (self.currentPage.height-1)*self.kNodeSpacing.y)
	end
	
	return size
end

function NodeGrid:SetScrollOffset(offset)
	self.scrollOffset = offset
end

function NodeGrid:SetScrollPos(normalizedScrollPos)
	if (self.currentPage) then
		self.currentPage.normalizedScrollPos = GPoint(normalizedScrollPos)
	end
end

function NodeGrid:GetNodeCoordFromPos(rect, pos)
	local canvasX = pos.x - rect.left - self.scrollOffset.x
	local nodeX = (canvasX - self.kMargin) / (self.kNodeSize.x + self.kNodeSpacing.x)
	
	local canvasY = pos.y - rect.top - self.scrollOffset.y
	local nodeY = (canvasY - self.kMargin) / (self.kNodeSize.y + self.kNodeSpacing.y)
	
	if (nodeY >= 0 and nodeX >= 0 and nodeX < self.currentPage.width and nodeY < self.currentPage.height) then
		return {["x"] = math.floor(nodeX), ["y"] = math.floor(nodeY)}
	end
		
	return nil
end

function NodeGrid:GetRectForNodeCoord(rect, coord)
	local node = self.currentPage:GetNode(coord.x, coord.y)
	local width = 1
	
	if (node) then
		width = node.width
	end
	
	local nodeRect = GRect(0, 0, self.kNodeSize.x * width + self.kNodeSpacing.x * (width-1), self.kNodeSize.y)
	
	local xPos = rect.left + self.scrollOffset.x + self.kMargin + coord.x * (self.kNodeSize.x + self.kNodeSpacing.x)
	local yPos = rect.top + self.scrollOffset.y + self.kMargin + coord.y * (self.kNodeSize.y + self.kNodeSpacing.y)
	
	nodeRect:MoveTo(xPos, yPos)
	
	return nodeRect
end

function NodeGrid:GetNodeAtPos(rect, pos)
	local coord = self:GetNodeCoordFromPos(rect, pos)
	
	if (coord) then
		return self.currentPage:GetNode(coord.x, coord.y);
	else
		return nil
	end
end

function NodeGrid:SelectNode(selectedNode, event)
	
	-- clear all nodes		
	for node in self:NodeIterator() do
		if (event == nil or event:HasModifier(kGEventModifierControl) == false) then -- if CTRL held, we can multi-select nodes
			node.isSelected = false
		end
		node.isShowingParameters = false
	end

	-- set current node
	if (selectedNode) then
		if (event ~= nil and event:HasModifier(kGEventModifierControl)) == true then
			selectedNode.isSelected = not selectedNode.isSelected
		else
			selectedNode.isSelected = true
		end		
	end
	
	self.currentPage.parameterListNode = selectedNode
	self.root.parameterList:ShowNode(selectedNode)
end

function NodeGrid:ClosestCoordFromPos(rect, pos)
	local xPos = pos.x - rect.left - self.scrollOffset.x - self.kMargin
	local xCoord = math.floor((xPos + (self.kNodeSize.x/2)) / (self.kNodeSize.x + self.kNodeSpacing.x))
	
	local yPos = pos.y - rect.top - self.scrollOffset.y - self.kMargin
	local yCoord = math.floor((yPos + (self.kNodeSize.y/2)) / (self.kNodeSize.y + self.kNodeSpacing.y))
	
	return GPoint(xCoord, yCoord)
end

function NodeGrid:GetNodeAtCoord(coord)
	-- look for covering node to the left
	local testCoordX = coord.x
	
	while (testCoordX >= 0) do
		node = self.currentPage:GetNode(testCoordX, coord.y)
		
		if (node) then
			if ((testCoordX + node.width) <= coord.x) then
				return nil -- doesn't cover
			end
			
			return node,testCoordX
		end
		
		testCoordX = testCoordX - 1
	end
	
	return nil
end

function NodeGrid:CloneNodeParams(newNodeObj, oldNodeObj)	
	--UpdateNodeParameters(newNodeObj, oldNodeObj.parameters)
end

function NodeGrid:HandleEvent(window, rect, event)
	-- Return if there is no page
	if (self.currentPage==nil) then
		return
	end

	if (event.type==kGEventKeyDown) then
		if (event.key=="C" and event:HasModifier(kGEventModifierControl)) then
			print("COPY")
			self:CopyNodes()
		end

		if (event.key=="X" and event:HasModifier(kGEventModifierControl)) then
			print("CUT")
			self:CutNodes()
		end

		if (event.key=="V" and event:HasModifier(kGEventModifierControl)) then
			print("PASTE")
			self:PasteNodes(self:ClosestCoordFromPos(rect, event.pos))
		end
	end
		
	-- Determine hotness
	if (not self.isActive and not self:IsOtherControlActive() and event.type==kGEventMouseMoved) then
		-- Reset hotness
		self.hotNode = nil
		self.hotLeftGrabNode = nil
		self.hotRightGrabNode = nil
		self.hotNodeCoord = self:GetNodeCoordFromPos(rect, event.pos)
		
		-- Update hotness, if applicable
		if (not self.isActive and not self:IsOtherControlActive() and rect:IsPointInside(event.pos)) then
			if (self.hotNodeCoord) then
				local node,xCoord = self:GetNodeAtCoord(self.hotNodeCoord)
				
				if (node) then
					self.hotNodeCoord.x = xCoord
					
					local nodeRect = self:GetRectForNodeCoord(rect, self.hotNodeCoord)
					local hotRect = GRect(nodeRect)
					
					-- Check if node is hot (has mouse over it)
					if (hotRect:IsPointInside(event.pos)) then
						self.hotNode = node
					end
					
					-- Check if left side is hot
					hotRect.left = nodeRect.left
					hotRect.right = nodeRect.left + self.kNodeSideGrabWidth
					
					if (hotRect:IsPointInside(event.pos)) then
						self.hotLeftGrabNode = node
					end
					
					-- Check if right side is hot
					hotRect.left = nodeRect.right - self.kNodeSideGrabWidth
					hotRect.right = nodeRect.right
					
					if (hotRect:IsPointInside(event.pos)) then
						self.hotRightGrabNode = node
					end
				end
			end
		end
	end

	if (self.isActive==false) then

		if (event.type==kGEventMouseDown and rect:IsPointInside(event.pos)) then
			self:SetActive(true)
			
			if (event.button==kGEventMouseButtonRight) then
				if (self.hotNode) then
					-- node exists at grid position
					self.nodeContextMenu:SelectAt(event.pos, self.hotNodeCoord)
					self.hotNode = nil
				else
					-- no node at grid position
					if (self.hotNodeCoord) then
						-- Create menu with submenus
						local menu = intruder.GContextMenuEx()

						--nodeGrid.nodeContextMenu:AddItem("Paste Node(s)",
						--function(pos)
						--	nodeGrid:PasteNodes()				
						--end)
						print(table.getn(self.copiedNodes))
						if (table.getn(self.copiedNodes) > 0) then
							menu:AddItem(-1, 'Paste Node(s)')
						end
							
						for baseNodeType=0,g_pNodeFactory:GetNumBaseNodeTypes()-1 do
							local subMenu = intruder.GContextMenuEx()
							
							for i=0,g_pNodeFactory:GetNumNodeTypes(baseNodeType)-1 do
								local nodeType = g_pNodeFactory:GetNodeType(baseNodeType, i)
								
								subMenu:AddItem((baseNodeType+1)*1000 + i + 1, nodeType.name)
							end
								
							menu:AddMenu(subMenu, "Insert "..g_pNodeFactory:GetBaseNodeTypeName(baseNodeType))
						end
												
						-- Spawn menu
						local result = (menu:SelectAt(event.pos))
						
						if (result~=0) then
							if (result == -1) then
								self:PasteNodes()
							else
								-- Get node type information
								local baseNodeType = math.floor(result/1000) - 1
								local nodeTypeIndex = result - (baseNodeType+1)*1000 - 1
							
								local nodeType = g_pNodeFactory:GetNodeType(baseNodeType, nodeTypeIndex)
							
								-- Create node
								local newNode = retain(g_pNodeFactory:CreateNode(self.currentPage, nodeType.type))
								if (not newNode.name) then
									newNode.name = nodeType.name
								end
								self.currentPage:SetNode(self.hotNodeCoord.x, self.hotNodeCoord.y, newNode)
								self:SelectNode(newNode)
								self.root:NotifyGridChanged(self.currentPage)
							end
						end
						
						self.hotNode = nil
					end
				end
			elseif (event.button==kGEventMouseButtonLeft) then
				if (self.hotLeftGrabNode) then
					self.dragLeftGrabNode = self.hotLeftGrabNode
				elseif (self.hotRightGrabNode) then
					self.dragRightGrabNode = self.hotRightGrabNode
				elseif (self.hotNode) then
					if (not self.hotNode.isSelected or event:HasModifier(kGEventModifierControl)) then
						-- select node
						self:SelectNode(self.hotNode, event)
					end
										
					if (event.clickCount==2) then
						g_pDemo.auxViewportRoot = node
					end
					
					-- start drag
					self.isDragging = true
					self.copyingNodes = event:HasModifier(kGEventModifierShift)
					self.startDragPos = GPoint(event.pos)
					self.dragRelativeOffset = GPoint(0, 0)
					self.dragRelativeCoordOffset = GPoint(0, 0)
					self.lastCheckedRelativeCoordOffset = GPoint(0, 0)
					self.dragSuccess = true
				end
			end
		end
	elseif (self.isActive) then
		if (event.type==kGEventMouseMoved) then
			if (self.dragLeftGrabNode) then	
				local closestCoord = self:ClosestCoordFromPos(rect, event.pos)
			elseif (self.dragRightGrabNode) then	
				local closestCoord = self:ClosestCoordFromPos(rect, event.pos)
				local potentialWidth = closestCoord.x - self.hotNodeCoord.x
				
				if (potentialWidth~=self.hotNode.width and potentialWidth >= self.hotNode.minWidth and potentialWidth <= self.hotNode.maxWidth) then
					local overlap = false
					
					for x=self.hotNodeCoord.x+1,self.hotNodeCoord.x+potentialWidth-1 do
						if (self.currentPage:GetNode(x, self.hotNodeCoord.y)) then
							overlap = true
						end
					end
					
					if (overlap==false) then
						self.hotNode:SetWidth(potentialWidth)
						self.root:NotifyGridChanged(self.currentPage)
					end
				end
			elseif (self.isDragging) then
				self.dragRelativeOffset = GPoint(event.pos):Minus(self.startDragPos)
				
				local xCoord = math.floor((self.dragRelativeOffset.x + (self.kNodeSize.x/2)) / (self.kNodeSize.x + self.kNodeSpacing.x))
				local yCoord = math.floor((self.dragRelativeOffset.y + (self.kNodeSize.y/2)) / (self.kNodeSize.y + self.kNodeSpacing.y))
				self.dragRelativeCoordOffset = GPoint(xCoord, yCoord)
					
				-- check if drag is possible
				-- maybe there's overlap with existing node
				-- maybe a node is out of bounds
				
				if (self.dragRelativeCoordOffset.x~=0 or self.dragRelativeCoordOffset.y~=0) then -- if we didn't change we're automatically good
					if (self.dragRelativeCoordOffset.x~=self.lastCheckedRelativeCoordOffset.x or self.dragRelativeCoordOffset.y~=self.lastCheckedRelativeCoordOffset.y) then -- it's a relatively expensive operation, so only do if neccesary
						-- assume were good, and fail later
						self.dragSuccess = true
					
						for node in self:SelectedNodeIterator() do
							for i=0,node.width-1 do
								-- check coord in bounds
								local xCoord = node.xPos + self.dragRelativeCoordOffset.x + i
								local yCoord = node.yPos + self.dragRelativeCoordOffset.y
								
								if (xCoord<0 or xCoord>=self.currentPage.width or yCoord<0 or yCoord>=self.currentPage.height) then
									self.dragSuccess = false
									break
								end
								
								-- check node overlap
								local coveringNode = self:GetNodeAtCoord(GPoint(xCoord, yCoord))
								
								if (coveringNode and coveringNode.isSelected==false) then
									self.dragSuccess = false
									break
								end
							end
						end
					
						self.lastCheckedRelativeCoordOffset = GPoint(self.dragRelativeCoordOffset)
					end
				end
			end
		elseif (event.type==kGEventMouseUp) then
			self:SetActive(false)
			
			if (self.isDragging) then
				-- drag completed
				if (self.dragSuccess) then
					local nodes = {}

					if (self.copyingNodes) then
						-- copying nodes
						-- move nodes to temprorary table (one node may be moved onto another selected node)
						for node in self:SelectedNodeIterator() do
							table.insert(nodes, {["coord"] = GPoint(node.xPos, node.yPos), ["node"] = node})
						end	
					
						-- reinsert nodes
						for i,node in pairs(nodes) do
							local oldNode = node.node
							local nType = node.node.GetType(oldNode)
							local nBaseType = node.node.GetBaseType(oldNode)
							-- create and insert copy of node
							local newNode = retain(g_pNodeFactory:CopyNode(oldNode))
							self.currentPage:SetNode(node.coord.x + self.dragRelativeCoordOffset.x, node.coord.y + self.dragRelativeCoordOffset.y, newNode)
						end
					else
						-- moving nodes
						-- move nodes to temprorary table (one node may be moved onto another selected node)
						for node in self:SelectedNodeIterator() do
							table.insert(nodes, {["coord"] = GPoint(node.xPos, node.yPos), ["node"] = node})
							self.currentPage:SetNode(node.xPos, node.yPos, nil)
						end	
					
						-- reinsert nodes
						for i,node in pairs(nodes) do
							self.currentPage:SetNode(node.coord.x + self.dragRelativeCoordOffset.x, node.coord.y + self.dragRelativeCoordOffset.y, node.node)
						end
					end
					
					self.root:NotifyGridChanged(self.currentPage)
				end
				
				self.isDragging = false
				self.copyingNodes = false
				self.startDragPos = nil
				self.dragRelativeOffset = nil
				self.dragRelativeCoordOffset = nil
				self.lastCheckedRelativeCoordOffset = nil
			else
				self.dragLeftGrabNode = nil
				self.dragRightGrabNode = nil
				self.hotLeftGrabNode = nil
				self.hotRightGrabNode = nil
			end
			
		end
	end
	
	-- Set cursor
	-- left grab not supported atm: if (self.hotLeftGrabNode or self.hotRightGrabNode or self.dragLeftGrabNode or self.dragRightGrabNode) then
	if (self.hotRightGrabNode or self.dragRightGrabNode) then
		window:SetCursor(intruder.GWindow_kGCursorArrowWE)
	end
end

function NodeGrid:GetCanvasPosForNode(node)
	return GPoint(self.kMargin + node.xPos*(self.kNodeSize.x + self.kNodeSpacing.x), self.kMargin + node.yPos*(self.kNodeSize.y + self.kNodeSpacing.y)) 
end

function NodeGrid:Draw(window, rect)
	-- Return if there is no page
	if (self.currentPage==nil) then
		return
	end
	
	window:PushScissor(rect)
		local gridRect = GRect(rect) -- this excludes the node row/col num area
		gridRect.left = gridRect.left + (self.kNodeSize.x)/2 - 5;
		gridRect.top = gridRect.top + self.kNodeSize.y + self.kNodeSpacing.y

		window:PushScissor(gridRect)
			-- draw background outlines
			local minNodeX = math.max(0, math.floor((-self.scrollOffset.x - self.kMargin) / (self.kNodeSize.x + self.kNodeSpacing.x)))
			local maxNodeX = math.min(self.currentPage.width-1, math.floor((-self.scrollOffset.x - self.kMargin + rect:GetWidth()) / (self.kNodeSize.x + self.kNodeSpacing.x)))
			local minNodeY = math.max(0, math.floor((-self.scrollOffset.y - self.kMargin) / (self.kNodeSize.y + self.kNodeSpacing.y)))
			local maxNodeY = math.min(self.currentPage.height-1, math.floor((-self.scrollOffset.y - self.kMargin + rect:GetHeight()) / (self.kNodeSize.y + self.kNodeSpacing.y)))
			
			for y=minNodeY,maxNodeY do
				for x=minNodeX,maxNodeX do
					local x = self.kMargin + rect.left + self.scrollOffset.x + x*(self.kNodeSize.x + self.kNodeSpacing.x)
					local y = self.kMargin + rect.top + self.scrollOffset.y + y*(self.kNodeSize.y + self.kNodeSpacing.y)
					
					self.outlineSurface:DrawTexture(GPoint(x, y))
				end
			end
			
			local it = intruder.ZPageLimitedNodeIterator(self.currentPage, minNodeX, maxNodeX, minNodeY, maxNodeY)

			-- draw nodes
			it:Rewind()
			local node = it:Next()
			while (node) do
				local canvasPos = self:GetCanvasPosForNode(node)
				local xPos = rect.left + self.scrollOffset.x + canvasPos.x
				local yPos = rect.top + self.scrollOffset.y + canvasPos.y
				
				--if (not (self.isDragging and node.isSelected)) then
				if (self.copyingNodes or not self.isDragging or not node.isSelected) then
					-- draw all nodes that are not being dragged
					self.nodeSurfaces[node.width][node:GetActualBaseType()]:DrawTexture(GPoint(xPos, yPos))
					
					if (node.isSelected or node==self.hotNode) then
						-- draw selected bounding box
						if (self.copyingNodes) then	
							self.copySourceSurfaces[node.width]:DrawTexture(GPoint(xPos, yPos))
						else
							self.activeSurfaces[node.width]:DrawTexture(GPoint(xPos, yPos))
						end
					end
					
					if (node.errorString) then
						self.errorOutlineSurfaces[node.width]:DrawTexture(GPoint(xPos - 3, yPos - 3))
					end
					
					self:GetSurfaceForName(node.name):DrawTexture(GPoint(xPos + (node.width-1)*(self.kNodeSize.x + self.kNodeSpacing.x)/2, yPos))
					
					if (g_pDemo.auxViewportRoot == node) then
						self.triangleSurface:DrawTexture(GPoint(xPos + 3, yPos + 5))
					end
				end
				
				node = it:Next()
			end
			
			-- draw error overlays
			it:Rewind()
			local node = it:Next()
			while (node) do
				if (not (self.isDragging)) then
					local canvasPos = self:GetCanvasPosForNode(node)
					local xPos = rect.left + self.scrollOffset.x + canvasPos.x
					local yPos = rect.top + self.scrollOffset.y + canvasPos.y
					
					if (node==self.hotNode and node.errorString) then
						local errorSurface = self:GetSurfaceForError(node.errorString)
						local overlayX = xPos + (node.width * (self.kNodeSize.x + self.kNodeSpacing.x) - errorSurface:GetRect():GetWidth())/2
						errorSurface:DrawTexture(GPoint(overlayX, yPos + self.kNodeSize.y + 3))
					end
					
					if (node==self.hotNode and node.errorString) then
						local errorSurface = self:GetSurfaceForError(node.errorString)
						local overlayX = xPos + (node.width * (self.kNodeSize.x + self.kNodeSpacing.x) - errorSurface:GetRect():GetWidth())/2
						errorSurface:DrawTexture(GPoint(overlayX, yPos + self.kNodeSize.y + 3))
					end
				end
				
				node = it:Next()
			end
			
			-- draw dragging nodes
			if (self.isDragging) then
				it:Rewind()
				local node = it:Next()
				while (node) do
					if (node==self.hotNode or node.isSelected) then
						local canvasPos = self:GetCanvasPosForNode(node)
						local xPos = rect.left + self.scrollOffset.x + canvasPos.x
						local yPos = rect.top + self.scrollOffset.y + canvasPos.y
					
						if (self.dragSuccess) then
							-- if succes draw outlines to show snap position
							local xCoord = node.xPos + self.dragRelativeCoordOffset.x
							local yCoord = node.yPos + self.dragRelativeCoordOffset.y
							
							local pos = self:GetRectForNodeCoord(rect, GPoint(xCoord, yCoord)):GetTopLeft()
							
							self.activeSurfaces[node.width]:DrawTexture(pos)
						end
						
						-- draw overlay
						xPos = xPos + self.dragRelativeOffset.x
						yPos = yPos + self.dragRelativeOffset.y
						
						-- draw all nodes that are not being dragged
						self.nodeSurfaces[node.width][node:GetActualBaseType()]:DrawTexture(GPoint(xPos, yPos))
						
						self:GetSurfaceForName(node.name):DrawTexture(GPoint(xPos + (node.width-1)*(self.kNodeSize.x + self.kNodeSpacing.x)/2, yPos))
					end
					
					node = it:Next()
				end
			end
			
		window:PopScissor() -- be able to draw in node row/col area

		-- draw grid refs
		for y=minNodeY,maxNodeY do						
			local py = self.kMargin + rect.top + self.scrollOffset.y + y*(self.kNodeSize.y + self.kNodeSpacing.y)
			self.nodeNumberSurfacesLeft[y]:DrawTexture(GPoint(self.kMargin + rect.left - 40, py))					
			for x=minNodeX,maxNodeX do			
				local px = self.kMargin + rect.left + self.scrollOffset.x + x*(self.kNodeSize.x + self.kNodeSpacing.x)
				
				if (y == minNodeY) then
					self.nodeNumberSurfacesTop[x]:DrawTexture(GPoint(px, self.kMargin + rect.top - 28))
				end								
			end
		end
	window:PopScissor()
end

function NodeGrid:SelectedNodeIterator()
	local it = intruder.ZPageLimitedNodeIterator(self.currentPage, 0, self.currentPage.width-1, 0, self.currentPage.height-1)

	return function()
		local node = it:Next()
		while (node) do
			if (node==self.hotNode or node.isSelected) then
				return node
			end
			
			node = it:Next()
		end
		
		return nil
	end
end

function NodeGrid:NodeIterator()
	local it = intruder.ZPageLimitedNodeIterator(self.currentPage, 0, self.currentPage.width-1, 0, self.currentPage.height-1)

	return function()
		local node = it:Next()
		return node
	end
end

function NodeGrid:GetSurfaceForName(name)
	if (self.nameSurfaces[name]) then
		return self.nameSurfaces[name]
	else
		local surface = GSurface(self.kNodeSize.x, self.kNodeSize.y)
		surface:Clear(GColor(0, 0, 0, 0))
		surface:DrawTextAligned(intruder.kFontFaceLucidaGrande, name, GColor(0, 0, 0), 9, kGAlignCenter, kGVerticalAlignCenter);
		surface:UpdateTexture()
		
		self.nameSurfaces[name] = surface
		
		return surface
	end
end

function NodeGrid:GetSurfaceForError(error)
	if (self.errorSurfaces[error]) then
		return self.errorSurfaces[error]
	else
		local textHeight = 11
		local textWidth = intruder.g_pFontManager:GetTextWidth(intruder.kFontFaceLucidaGrande, error, textHeight)
		local margin = 10
		local width = textWidth + 2*margin
		local height = textHeight + 2*margin
		local surface = GSurface(width, height)
		local gradient = GGradient()
		gradient:InsertColor(0, GColor(0, 0, 0, 200))
		gradient:InsertColor(1, GColor(0, 0, 0, 200))
		surface:Clear(GColor(0, 0, 0, 0))
		surface:DrawRoundedRectVerticalGradient(GRect(0, 0, width, height):CloneWithInset(0.5), gradient, 5)
		surface:DrawTextAligned(intruder.kFontFaceLucidaGrande, error, GColor(255, 255, 255), textHeight, kGAlignCenter, kGVerticalAlignCenter);
		surface:UpdateTexture()
		
		self.errorSurfaces[error] = surface
		
		return surface
	end
end

