ParameterList = {}
ParameterList_mt = { __index = ParameterList }
setmetatable(ParameterList, Control_mt)

-- Set constants
ParameterList.kMargin = 10
ParameterList.kMarginBetween = 10
ParameterList.kParameterHeight = 30
ParameterList.kNameWidth = 150
ParameterList.kNameHeight = 30
	
-- Create constant highlight surfaces
ParameterList.kValueOutlineSmall = GSurface(100, ParameterList.kParameterHeight)
ParameterList.kValueOutlineSmall:Clear(GColor(0, 0, 0, 0))
ParameterList.kValueOutlineSmall:DrawRoundedRectOutlined(GRect(0, 0, 100, ParameterList.kParameterHeight):CloneWithInset(4.5), GColor(0, 0, 0), 5, 1.5)
ParameterList.kValueOutlineSmall:UpdateTexture()

ParameterList.kValueOutlineBig = GSurface(400, ParameterList.kParameterHeight)
ParameterList.kValueOutlineBig:Clear(GColor(0, 0, 0, 0))
ParameterList.kValueOutlineBig:DrawRoundedRectOutlined(GRect(0, 0, 400, ParameterList.kParameterHeight):CloneWithInset(4.5), GColor(0, 0, 0), 5, 1.5)
ParameterList.kValueOutlineBig:UpdateTexture()


function ParameterList.Create(root, parentObject)
	-- Create object
	local parameterList = Control.Create(root, parentObject)
	setmetatable(parameterList, ParameterList_mt)
	
	-- Set attributes
	parameterList.scrollOffset = GPoint(0, 0)
	parameterList.currentNode = nil
	
	parameterList.parameters = {}
	
	parameterList.parameterNameSurfaces = {}
	
	
	return parameterList
end

function ParameterList:GetCanvasSize(rect)
	if (self.currentNode) then
		if (self.currentNode.numParameters > 0) then
			-- find widest parameter
			local maxWidth = 0
			for i,par in ipairsFromZero(self.parameters) do
				local width = self.kMargin + self.kNameWidth + self.kMarginBetween + par.kWidth + self.kMargin 
				
				if (self.modulations[i]) then
					width = width + OptionMenuParameter.kWidth + self.kMarginBetween + ScalarParameter.kWidth + self.kMarginBetween
				end
				
				if (width > maxWidth) then
					maxWidth = width
				end
			end
			local height = self.currentNode.numParameters * self.kParameterHeight + (self.currentNode.numParameters - 1)*self.kMarginBetween + 2*self.kMargin
			return GPoint(maxWidth, height)
		end
	end
	
	return GPoint(0, 0)
end

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

function ParameterList:SetScrollPos(normalizedScrollPos)
	if (self.currentNode) then
		self.currentNode.normalizedScrollPos = GPoint(normalizedScrollPos)
	end
end

function ParameterList:ShowNode(node)
	self.currentNode = node
	
	if (node) then
		self.root.scrolledParameterList:SetScrollPos(node.normalizedScrollPos)
	else
		self.root.scrolledParameterList:SetScrollPos(GPoint(0, 0))
	end
		
	-- Reset parameters
	self.parameters = {}
	self.modulations = {}

	if (node) then
		-- Create parameters	
		for i=0,node.numParameters-1 do
			local parameter = node:GetParameter(i)
			
			-- instatiate parameter
			if (parameter.type==intruder.ZParameter_kTypeFloat) then
				if (parameter.hasLimits) then
					self.parameters[i] = ScalarParameter.Create(self.root, self, parameter.type, parameter.floatValue, parameter.floatValueMin, parameter.floatValueMax, parameter.dragScale, function(value) parameter.floatValue = value; node:ParameterUpdated(i); end)
				else
					self.parameters[i] = ScalarParameter.Create(self.root, self, parameter.type, parameter.floatValue, nil, nil, parameter.dragScale, function(value) parameter.floatValue = value; node:ParameterUpdated(i); end)
				end
			elseif (parameter.type==intruder.ZParameter_kTypeInt) then
				if (parameter.hasLimits) then
					self.parameters[i] = ScalarParameter.Create(self.root, self, parameter.type, parameter.intValue, parameter.intValueMin, parameter.intValueMax, parameter.dragScale, function(value) parameter.intValue = value; node:ParameterUpdated(i); end)
				else
					self.parameters[i] = ScalarParameter.Create(self.root, self, parameter.type, parameter.intValue, nil, nil, parameter.dragScale, function(value) parameter.intValue = value; node:ParameterUpdated(i); end)
				end
			elseif (parameter.type==intruder.ZParameter_kTypeFunctionName) then
				self.parameters[i] = StringParameter.Create(self.root, self, parameter.type, parameter.stringValue, function(value) parameter.stringValue = value; self.root:NotifyFunctionNameChanged() if (node:GetActualType()==intruder.kNodeTypeFunction) then node.name="'"..value.."'"; root.functionMenuChanged = true; end; node:ParameterUpdated(i); end)
			elseif (parameter.type==intruder.ZParameter_kTypeFunctionReference) then
				local currentIndex = 0
				if (parameter.nodeValue) then
					currentIndex = parameter.nodeValue:GetParameter(0).intValue + 1
				end
				
				-- function reference for all node

				self.parameters[i] = OptionMenuParameter.Create(self.root, self, parameter.type, currentIndex, self.root.functionList,
					function(value)
						parameter.stringValue = self.root.functionList[value+1][2]
						parameter.nodeValue = self.root.graph:GetFunctionNodeWithName(self.root.functionList[value+1][2])
						if (node:GetActualType()==intruder.kNodeTypeCall) then
							node.name=""..parameter.stringValue..""
						end

						node:ParameterUpdated(i)
					end)
			elseif (parameter.type==intruder.ZParameter_kTypeFile) then
				self.parameters[i] = FileParameter.Create(self.root, self, parameter.type, parameter.stringValue,
					function(value) 
						parameter.stringValue = value
						
						node:ParameterUpdated(i)
					end)
			elseif (parameter.type==intruder.ZParameter_kTypeOption) then
				-- Create value name pair table
				local vp = {}
				for i=0,parameter.numOptions-1 do
					table.insert(vp, {parameter:GetOptionValue(i), parameter:GetOptionName(i)})
				end
				
				-- create parameters
				self.parameters[i] = OptionMenuParameter.Create(self.root, self, parameter.type, parameter.intValue, vp,
					function(value) 
						parameter.intValue = value
						
						node:ParameterUpdated(i)
					end)
			elseif (parameter.type==intruder.ZParameter_kTypeString) then
				self.parameters[i] = StringParameter.Create(self.root, self, parameter.type, parameter.stringValue,
					function(value)
						parameter.stringValue = value;
						
						if (node:GetActualType()==intruder.kNodeTypeCreatePPEffect) then
							node.name="'"..value.."'"
						end
						
						node:ParameterUpdated(i)
 					end)
			elseif (parameter.type==intruder.ZParameter_kTypeColor) then
				self.parameters[i] = VectorParameter.Create(self.root, self, parameter.type, {parameter.vectorValue:GetX(), parameter.vectorValue:GetY(), parameter.vectorValue:GetZ(), parameter.vectorValue:GetW()}, nil, nil, parameter.dragScale, 4,
					function(value)
						parameter.vectorValue:SetX(value[1]);
						parameter.vectorValue:SetY(value[2]);
						parameter.vectorValue:SetZ(value[3]);
						parameter.vectorValue:SetW(value[4]);
						
						node:ParameterUpdated(i)
					end)
			elseif (parameter.type==intruder.ZParameter_kTypeVector) then
				self.parameters[i] = VectorParameter.Create(self.root, self, parameter.type, {parameter.vectorValue:GetX(), parameter.vectorValue:GetY(), parameter.vectorValue:GetZ()}, nil, nil, parameter.dragScale, 3,
					function(value)
						parameter.vectorValue:SetX(value[1]);
						parameter.vectorValue:SetY(value[2]);
						parameter.vectorValue:SetZ(value[3]);
						
						node:ParameterUpdated(i)
					end)
			elseif (parameter.type==intruder.ZParameter_kTypeEditableString) then
				self.parameters[i] = EditableStringParameter.Create(self.root, self, parameter.type, i, parameter, node,
					function()
						-- NOTE: this code is kinda specific to shader changes
						intruder.g_pDemo:Render()
						
						if (root.graph:UpdateErrors()) then
							root.graph:UpdateErrorList()
							root:UpdateErrors()
						end
						root.parameterList:ShowNode(root.parameterList.currentNode) -- this is neccesary because if we are showing a material node, it's parameters may be updated
					end )	
			elseif (parameter.type==intruder.ZParameter_kTypeAsset) then
				self.parameters[i] = AssetParameter.Create(self.root, self, parameter.type, i, parameter, node,
					function(path)
						print(path)
						print('change\n')
						-- NOTE: this code is kinda specific to shader changes
						intruder.g_pDemo:Render()
						
						if (root.graph:UpdateErrors()) then
							root.graph:UpdateErrorList()
							root:UpdateErrors()
						end
						parameter.stringValue =  path
						node:ParameterUpdated(i)
						root.parameterList:ShowNode(root.parameterList.currentNode) -- this is neccesary because if we are showing a material node, it's parameters may be updated
					end )	
			elseif (parameter.type==intruder.ZParameter_kTypeCompilableString) then
				self.parameters[i] = EditableStringParameter.Create(self.root, self, parameter.type, i, parameter, node,
					function()
						-- Changes to parameter + ParameterUpdated done from C++ side
					end )	
			end
			
			-- instatiate modulation, if applicable
			if (parameter.modulationEnabled) then
				local amountParameter = ScalarParameter.Create(self.root, self, intruder.ZParameter_kTypeFloat, parameter.modulationAmount, nil, nil, 0.5, function(value) parameter.modulationAmount = value; node:ParameterUpdated(-1); end)
				
				local currentIndex = 0
				if (parameter.modulationSourceNode) then
					currentIndex = parameter.modulationSourceNode:GetParameter(0).intValue + 1
				end

				local refParameter = OptionMenuParameter.Create(self.root, self, intruder.ZParameter_kTypeFunctionReference, currentIndex, self.root.functionList,
					function(value)
						--parameter.stringValue = self.root.functionList[value+1][2]
						parameter.modulationSourceNode = self.root.graph:GetFunctionNodeWithName(self.root.functionList[value+1][2])
						parameter.stringValue = self.root.functionList[value+1][2]
						
						node:ParameterUpdated(-1)
					end)
				self.modulations[i] = {[1] = refParameter, [2] = amountParameter}
			end
		end

	end
end

function ParameterList:HandleEvent(window, rect, event)
	-- Return if there is no node selected
	if (self.currentNode==nil) then
		return
	end
	
	window:PushScissor(rect)
		local parRect = GRect(0, 0, self.kNameWidth, self.kNameHeight)  
		parRect:MoveTo(rect:GetTopLeft():Plus(self.scrollOffset):Plus(self.kMargin + self.kNameWidth + self.kMarginBetween, self.kMargin))
		
		for i,par in ipairsFromZero(self.parameters) do
			parRect:SetSize(par.kWidth, par.kHeight)
			par:HandleEvent(window, parRect, event)
			
			if (self.modulations[i]) then
				local mParRect = parRect:CloneWithOffset(par.kWidth + self.kMarginBetween, 0)
				
				local mPar = self.modulations[i][1]
				mParRect:SetSize(mPar.kWidth, mPar.kHeight)
				mPar:HandleEvent(window, mParRect, event)
				
				mParRect:Offset(mPar.kWidth + self.kMarginBetween, 0)
				
				local mPar = self.modulations[i][2]
				mParRect:SetSize(mPar.kWidth, mPar.kHeight)
				mPar:HandleEvent(window, mParRect, event)
			end
			
			parRect:Offset(0, par.kHeight + self.kMarginBetween)
		end
	window:PopScissor()
end

function ParameterList:Draw(window, rect)
	-- Return if there is no page
	if (self.currentNode==nil) then
		return
	end
	
	window:PushScissor(rect)
		local parRect = GRect(0, 0, self.kNameWidth, self.kNameHeight)  
		parRect:MoveTo(rect:GetTopLeft():Plus(self.scrollOffset):Plus(self.kMargin + self.kNameWidth + self.kMarginBetween, self.kMargin))
		
		local xPos = rect.left + self.scrollOffset.x + self.kMargin
		local yPos = rect.top + self.scrollOffset.y + self.kMargin
		
		for i,par in ipairsFromZero(self.parameters) do
			-- Draw name
			local surface = self:GetSurfaceForParameterName(self.currentNode:GetParameter(i).name)
			surface:DrawTexture(GPoint(xPos, yPos))	
			
			-- Draw parameter
			parRect:SetSize(par.kWidth, par.kHeight)
			par:Draw(window, parRect)
			
			-- Draw modulation parameters
			if (self.modulations[i]) then
				local mParRect = parRect:CloneWithOffset(par.kWidth + self.kMarginBetween, 0)
				
				local mPar = self.modulations[i][1]
				mParRect:SetSize(mPar.kWidth, mPar.kHeight)
				mPar:Draw(window, mParRect)
				
				mParRect:Offset(mPar.kWidth + self.kMarginBetween, 0)
				
				local mPar = self.modulations[i][2]
				mParRect:SetSize(mPar.kWidth, mPar.kHeight)
				mPar:Draw(window, mParRect)
			end
			
			-- Move positions
			parRect:Offset(0, par.kHeight + self.kMarginBetween)
			yPos = yPos + par.kHeight + self.kMarginBetween
		end
	window:PopScissor()
end

function ParameterList:GetSurfaceForParameterName(name)
	if (self.parameterNameSurfaces[name]) then
		return self.parameterNameSurfaces[name]
	else
		local surface = GSurface(self.kNameWidth, self.kNameHeight)
		surface:Clear(GColor(0, 0, 0, 0))
		surface:DrawTextAligned(intruder.kFontFaceEnvyBold, name, GColor(0, 0, 0), 13, kGAlignLeft, kGVerticalAlignCenter, GPoint(1,1));
		surface:DrawTextAligned(intruder.kFontFaceEnvyBold, name, GColor(255, 255, 255), 13, kGAlignLeft, kGVerticalAlignCenter, GPoint(0,0));
		surface:UpdateTexture()
		
		self.parameterNameSurfaces[name] = surface
		
		return surface
	end
end