currentFileFormatVersion = 1



function LoadFile(path)
	local file = assert(io.open(path, "r"))
	local data = file:read("*all")
	file:close()

	return data
end

function GetLayoutString()
	if (root.frameLayout) then
		return root.frameLayout:SaveToString()
	else
		return "";
	end
end

function SetLayoutString(str)
	if (root.frameLayout) then
		root.frameLayout:LoadFromString(str)
		--print('layout = ' .. str)
	end
end

function SaveCurrentProject()
	if (currentProjectPath) then
		SaveProjectToFile(currentProjectPath)
	else
		if (intruder.SpawnSaveFileDialog()==intruder.kDialogFileChosen) then
			local path = intruder.DialogResult()
			SaveProjectToFile(path)
			currentProjectPath = path
		end
	end
end 

function SaveFile(path, data)
	local file = assert(io.open(path, "w"))
	local data = file:write(data)
	file:close()
end

function SaveProjectToFile(path)
    print("Saving Project to "..path)

	local project = {}
	project.pages = {}

	for pageIndex=1,root.graph.numPages do
		local page = SavePageToTable(pageIndex-1)
		project.pages[pageIndex] = page
	end

	-- information about current page and view roots
	project.currentPage = root.pageList.currentPage

	if (g_pDemo.mainViewportRoot ~= nil) then
		project.mainViewportRoot = {}
		project.mainViewportRoot.page = g_pDemo.mainViewportRoot.page.index
		project.mainViewportRoot.xPos = g_pDemo.mainViewportRoot.xPos
		project.mainViewportRoot.yPos = g_pDemo.mainViewportRoot.yPos
	end

	if (g_pDemo.auxViewportRoot ~= nil) then
		project.auxViewportRoot = {}
		project.auxViewportRoot.page = g_pDemo.auxViewportRoot.page.index
		project.auxViewportRoot.xPos = g_pDemo.auxViewportRoot.xPos
		project.auxViewportRoot.yPos = g_pDemo.auxViewportRoot.yPos
	end

	if (g_pDemo.precalcViewportRoot ~= nil) then
		project.precalcViewportRoot = {}
		project.precalcViewportRoot.page = g_pDemo.precalcViewportRoot.page.index
		project.precalcViewportRoot.xPos = g_pDemo.precalcViewportRoot.xPos
		project.precalcViewportRoot.yPos = g_pDemo.precalcViewportRoot.yPos
	end

	-- version information
	project.readableDownToVersion = currentFileFormatVersion

    --PrettyPrintTableRecursive(page)

	-- return
	projectStr = "INTP,"..string.format("%04d", currentFileFormatVersion)..",return "..SerializeTableToString(project)

	SaveFile(path, projectStr)
	g_pDemo:HandleProjectLoad(path)
	root.SetProjectPath(path);
end

function ExportHeaderToFile(path)
	g_pDemo:WriteToHeaderFile(path..".h")
end

function LoadProjectFromFile(path)
	print("Load project from "..path)
	local data = LoadFile(path)

	local projectIdentStr = data:sub(1,4)
	if projectIdentStr~="INTP" then
		error("Wrong file indentifier.")
	end

	-- update intruder project path
	root.SetProjectPath(path);

	-- save file version
	local saveFileVersion = tonumber(data:sub(6,9))
	print("File format version "..saveFileVersion)

	-- create texture table from string
	local project = loadstring(data:sub(11))()

	-- reset graph
	root.pageList:SelectPage(nil)
	root.parameterList:ShowNode(nil)

	--root.graph.numPages = i
	root.graph.numPages = 0
	LoadPagesAtIndex(0, project.pages)
	--[[for i,page in ipairs(project.pages) do
		LoadPageFromTable(i-1, page)
	end]]

	-- set current page
	root.pageList:SelectPage(project.currentPage)

	-- set views
	if (project.mainViewportRoot) then
		local node = root.graph:GetPage(project.mainViewportRoot.page):GetNode(project.mainViewportRoot.xPos, project.mainViewportRoot.yPos)
		g_pDemo.mainViewportRoot = node
	else
		g_pDemo.mainViewportRoot = nil
	end

	if (project.auxViewportRoot) then
		local node = root.graph:GetPage(project.auxViewportRoot.page):GetNode(project.auxViewportRoot.xPos, project.auxViewportRoot.yPos)
		g_pDemo.auxViewportRoot = node
	else
		g_pDemo.auxViewportRoot = nil
	end

	if (project.precalcViewportRoot) then
		local node = root.graph:GetPage(project.precalcViewportRoot.page):GetNode(project.precalcViewportRoot.xPos, project.precalcViewportRoot.yPos)
		g_pDemo.precalcViewportRoot = node
	else
		g_pDemo.precalcViewportRoot = nil
	end
			
	g_pDemo:HandleProjectLoad(path)
end

function SavePageToFile(pageIndex)
	if (intruder.SpawnSaveFileDialog()==intruder.kDialogFileChosen) then
		local path = intruder.DialogResult()

	    print("Saving Page to "..path)

		local page = SavePageToTable(pageIndex)

		-- version information
		page.readableDownToVersion = currentFileFormatVersion

	    --PrettyPrintTableRecursive(page)

		-- return
		pageStr = "INTS,"..string.format("%04d", currentFileFormatVersion)..",return "..SerializeTableToString(page)

		SaveFile(path, pageStr)
	end
end

function LoadPageFromFile(pageIndex)
	if (intruder.SpawnLoadFileDialog()==intruder.kDialogFileChosen) then
		local path = intruder.DialogResult()

		local data = LoadFile(path)

		local projectIdentStr = data:sub(1,4)
		if projectIdentStr~="INTS" then
			error("Wrong file indentifier.")
		end

		-- save file version
		local saveFileVersion = tonumber(data:sub(6,9))

		-- create texture table from string
		local page = loadstring(data:sub(11))()

		-- make room for page
		if (pageIndex > 0) then
			local startIndex = pageIndex + 1
			local endIndex = root.pageList.graph.numPages

			for i = endIndex,startIndex,-1 do
				root.pageList.graph:SetPage(i, root.pageList.graph:GetPage(i - 1))
			end
		end

		-- insert page
		--root.pageList.graph.numPages = root.pageList.graph.numPages + 1
		LoadPagesAtIndex(pageIndex, {page})

		-- select page
		root:NotifyPageListChanged()
		root.pageList:SelectPage(pageIndex)
	end
end

-- TODO: Mangler at gemme selected node, og root/auxView nodes (isn't it nice with some danish comments here and there?)
-- Crash

function LoadPagesAtIndex(pageIndex, pages)

	for i,page in ipairs(pages) do
		local pageObject = retain(intruder.ZPage(root.graph, page.size.width, page.size.height))

		pageObject.name = retain(page.name)
		pageObject.nameSurface = retain(root.pageList:CreatePageNameSurface(page.name))
		pageObject.normalizedScrollPos.x = page.normalizedScrollPos.x
		pageObject.normalizedScrollPos.y = page.normalizedScrollPos.y

		root.graph:SetPage(pageIndex+i-1, pageObject)
		root.graph.numPages = root.graph.numPages + 1
	end

	-- Create nodes and set parameter (with exceptions)
	for i,page in ipairs(pages) do
		local pageObject = root.graph:GetPage(pageIndex+i-1)

		for j,node in ipairs(page.nodes) do
			if (node.type == intruder.kNodeTypeFunction) then -- functions first
				local nodeObject = retain(g_pNodeFactory:CreateNode(pageObject, node.type))
				nodeObject.name = retain(node.name)
				nodeObject.width = node.width
				pageObject:SetNode(node.pos.x, node.pos.y, nodeObject)

				UpdateNodeParameters(nodeObject, node.parameters)
			end
		end

		pageObject:UpdateNodePositions()
	end

	root.graph:UpdateFunctionNameSet() -- this should not be neccesary per page

	for i,page in ipairs(pages) do
		local pageObject = root.graph:GetPage(pageIndex+i-1)

		for j,node in ipairs(page.nodes) do
			if (node.type ~= intruder.kNodeTypeFunction and node.type ~= intruder.kNodeTypeMaterial and not intruder.IsPPEffect(node.type)) then -- skip material and ppeffect and call classes
				local nodeObject = retain(g_pNodeFactory:CreateNode(pageObject, node.type))
				nodeObject.name = retain(node.name)
				nodeObject.width = node.width
				pageObject:SetNode(node.pos.x, node.pos.y, nodeObject)

				UpdateNodeParameters(nodeObject, node.parameters)
			end
		end

		-- Not sure if all of these must be done per page
		pageObject:UpdateNodePositions()
		pageObject:UpdateInputs()
	end

	g_pNodeFactory:UpdatePostProcessEffects()
	root.graph:UpdateMaterials()

	-- Create materials and postprocess effects
	for i,page in ipairs(pages) do
		local pageObject = root.graph:GetPage(pageIndex+i-1)

		for j,node in ipairs(page.nodes) do
			if (node.type == intruder.kNodeTypeMaterial or intruder.IsPPEffect(node.type)) then
				local nodeType = node.type

				-- node types for PP effects may have changed (if new effect types were added), so look up node type id based on name
				if (intruder.IsPPEffect(node.type)) then
					nodeType = intruder.g_pNodeFactory:GetNodeTypeForPPEffectName(node.name)
				end

				if (nodeType ~= -1) then
					local nodeObject = retain(g_pNodeFactory:CreateNode(pageObject, nodeType))
					if (not nodeObject.name) then -- this is neccesary because PPEffect has a name pointing to a char array in the object. Lua will try to delete this array when we overwrite the name variable
						nodeObject.name = retain(node.name)
					end
					nodeObject.width = node.width
					pageObject:SetNode(node.pos.x, node.pos.y, nodeObject)
				else
					print("ERROR: Couldn't find NodeType for PP effect name "..node.name.."!")
				end
			end
		end

		pageObject:UpdateNodePositions()
		pageObject:UpdateInputs()
	end

	root.graph:UpdateMaterials()

	-- Set parameters for materials and postprocess effects
	for i,page in ipairs(pages) do
		local pageObject = root.graph:GetPage(pageIndex+i-1)

		for j,node in ipairs(page.nodes) do
			if (node.type == intruder.kNodeTypeMaterial or intruder.IsPPEffect(node.type)) then
				local nodeObject = pageObject:GetNode(node.pos.x, node.pos.y)

				if (nodeObject) then
					UpdateNodeParameters(nodeObject, node.parameters)
				end
			end
		end

		root:NotifyGridChanged(pageObject)
	end

	root.graph:UpdateMaterials()

	root:UpdateFunctionList()
	root.graph:UpdateErrors()
	root.graph:UpdateErrorList()
	root.graph:UpdatePageIndices()
	root:UpdateErrors()

end

function UpdateNodeParameters(nodeObject, parameters)
	for i=0,nodeObject.numParameters-1 do
		local parameterObject = nodeObject:GetParameter(i)

		for j,parameter in ipairs(parameters) do

			if (parameter.name == parameterObject.name) then
				parameterObject.floatValue = parameter.floatValue
				parameterObject.intValue = parameter.intValue
				parameterObject.stringValue = parameter.stringValue
				parameterObject.editableStringValue = parameter.editableStringValue
				if (parameter.rawDataSize and parameter.rawDataSize > 0) then
					parameterObject:SetRawData(intruder.luafriendly_decode_base64(parameter.rawDataSize, parameter.rawData), parameter.rawDataSize)
				end
				if (parameter.colorValue) then -- remove this later
					parameterObject.vectorValue:SetX(parameter.colorValue.x)
					parameterObject.vectorValue:SetY(parameter.colorValue.y)
					parameterObject.vectorValue:SetZ(parameter.colorValue.z)
					parameterObject.vectorValue:SetW(parameter.colorValue.w)
				end
				if (parameter.vectorValue) then -- remove this later
					parameterObject.vectorValue:SetX(parameter.vectorValue.x)
					parameterObject.vectorValue:SetY(parameter.vectorValue.y)
					parameterObject.vectorValue:SetZ(parameter.vectorValue.z)
					parameterObject.vectorValue:SetW(parameter.vectorValue.w)
				end

				-- TODO: cause of the bug requiring referenced value to appear higher on page than caller??
				if (parameter.nodeValue) then
					parameterObject.nodeValue = root.graph:GetFunctionNodeWithName(parameterObject.stringValue)
				end

				if (parameter.modulationAmount) then -- remove this later
					parameterObject.modulationAmount = parameter.modulationAmount
				end

				if (parameter.modulationSourceNode) then
					parameterObject.modulationSourceNode = root.graph:GetFunctionNodeWithName(parameterObject.stringValue)
				end

				nodeObject:ParameterUpdated(i)
			end
		end
	end
end

function SavePageToTable(pageIndex)
	local graphObject = g_pGraph
	local pageObject = graphObject:GetPage(pageIndex)

	local page = {}
	page.name = pageObject.name
	page.size = {}
	page.size.width = pageObject.width
	page.size.height = pageObject.height
	page.normalizedScrollPos = {}
	page.normalizedScrollPos.x = pageObject.normalizedScrollPos.x
	page.normalizedScrollPos.y = pageObject.normalizedScrollPos.y

	page.nodes = {}

	for y=0,pageObject.height-1 do
		for x=0,pageObject.width-1 do
			local nodeObject = pageObject:GetNode(x, y)

			if (nodeObject) then
				local node = {}

				node.type = nodeObject:GetActualType()
				node.name = nodeObject.name
				node.width = nodeObject.width

				node.pos = {}
				node.pos.x = x
				node.pos.y = y

				node.parameters = {}

				for i=0,nodeObject.numParameters-1 do
					local parameter = {}
					local parameterObject = nodeObject:GetParameter(i)

					parameter.name = parameterObject.name

					parameter.floatValue = parameterObject.floatValue
					parameter.intValue = parameterObject.intValue
					parameter.stringValue = parameterObject.stringValue
					parameter.editableStringValue = parameterObject.editableStringValue
					parameter.vectorValue = {}
					parameter.vectorValue.x = parameterObject.vectorValue:GetX()
					parameter.vectorValue.y = parameterObject.vectorValue:GetY()
					parameter.vectorValue.z = parameterObject.vectorValue:GetZ()
					parameter.vectorValue.w = parameterObject.vectorValue:GetW()

					if (parameterObject.nodeValue) then
						parameter.nodeValue = parameterObject.stringValue -- we save the reference in the stringValue field, and later recall it from the function list by name
					end

					if (parameterObject.rawDataSize > 0) then
						parameter.rawDataSize = parameterObject.rawDataSize
						parameter.rawData = intruder.luafriendly_encode_base64(parameterObject.rawDataSize, parameterObject.rawData)
					end

					-- modulation
					parameter.modulationAmount = parameterObject.modulationAmount

					if (parameterObject.modulationSourceNode) then
						parameter.modulationSourceNode = parameterObject.stringValue -- we save the reference in the stringValue field, and later recall it from the function list by name
					end

					table.insert(node.parameters, parameter)
				end

				table.insert(page.nodes, node)
			end
		end
	end

	return page
end

function SerializeTableToString(t, indent)
	if indent == nil then
		indent = ""
	end

	local commaNewLine= "\n"
	local str = ""
	str = str.."\n"..indent.."{"
		for i,v in pairs(t) do
			local iStr=""
			if type(i)=="number" then
				iStr="["..i.."]"
			elseif type(i)=="string" then
				iStr="[\""..i.."\"]"
			end

			if type(v)=="number" then
				str = str..commaNewLine..indent.."\t"..iStr.."="..v..""
			elseif type(v)=="string" then
				str = str..commaNewLine..indent.."\t"..iStr.."=\""..stringescape(v).."\""
			elseif type(v)=="table" then
				str = str..commaNewLine..indent.."\t"..iStr.."="..SerializeTableToString(v, indent.."\t")
			end
			commaNewLine= ",\n"
		end
	str = str.."\n"..indent.."}"
	return str
end

function PrettyPrintTableRecursive(t, indent)
	if indent==nil then
		indent = ""
	end

	local commaNewline = "\n"
	io.write("\n"..indent.."{")
		for i,v in pairs(t) do
			if type(v)=="number" then
				io.write(commaNewline..indent.."\t"..i.." = "..v.."")
			elseif type(v)=="string" then
				io.write(commaNewline..indent.."\t"..i.." = \""..stringescape(v).."\"")
			elseif type(v)=="table" then
				io.write(commaNewline..indent.."\t"..i.." =")
				PrettyPrintTableRecursive(v, indent.."\t")
			end
			commaNewline = ",\n"
		end
	io.write("\n"..indent.."}")
end

function stringescape(str)
	return str:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\r", "\\r"):gsub("\n", "\\n")
end
