require(game.ReplicatedFirst.ReadyModule)("ProgramModule")
-- Need interpreter for graph
-- Need program graph for JSON export
-- Need compiler for graph
-- Need data structure links to gui

--[[
So I SHOULD definitely work backwards, starting from the interpreter, since it connects to the structure best
The graph is a bit funky... :/

So, where does execution even start? Do we have an endpoint and then call everything backwards to bubble up the stack?
Or do we just start from the inputs and try and flow through there? Both sound a bit stupid to be honest.
There are some inputs that just won't really appear.

There is a concern with loops however, but this probably shouldn't be handled with real physical loops but
more as a processor cycle. I could always increase the clock speed? I'm not sure.
In a way, that means we should iterate through every single object and provide hooks for each one. So lots of coroutines?
Is that sensible? No. Kind of.

This means every node should have a function to produce a given value associated with it,
an operation to I guess, make that producer? Or does that count. I guess not.
So, the structure should have a list of outputs with types,
a list of inputs with types.
There could also be blocks which have a flag saying that they produce some sort of tangible output. I guess.
So that would be a 'callback'? I guess so.

So each 'node' in the table structure would just be implicitly in the graph... Uh. Great. Now what.
Well, I guess, how is the JSON data reaaaalllly any different here?
Using IDs doesn't seem like an extraordinarily stupid idea except for adding new tables causes problems.
Ok so we can't use JSON lol.

]]

--[[
Every node would also have its own sort of global rules associated. Which does make me think more about the GUI stuff.
That is indeed quite interesting.
]]
local function class(name)
	return {name=name,[name]=true}
end

-- Language types for everything
local VECTOR = class("Vector")
local STRING = class("String")
local NUMBER = class("Number")
local BOOLEAN = class("Boolean")
local CONTROL = class("Control") -- Enum for vehicle controls
local PIN = class("Pin")
local ANY = class("Any")

local PINS_LIST = {}
for char in string.gmatch("12345ABCDEXY",".") do table.insert(PINS_LIST,char) end
local CONTROL_LIST = {}
for control in string.gmatch("throttle,yaw,pitch,roll,fire,action1,gear,action2,flaps,bomb,auto","[^,]+") do table.insert(CONTROL_LIST,control) end

local TYPE_COLOURS = {
	Vector = Color3.new(1,0.5,0),
	String = Color3.new(0,1,0.5),
	Number = Color3.new(0,0.5,1),
	Boolean = Color3.new(0.5,0,1),
	Control = Color3.new(1,0,0.5),
	Pin = Color3.new(0.5,1,0),
	Any = Color3.new(0.5,0.5,0.5)
}

-- Types of row for GUI
local GUI_ROW_TEXT = class("Text") -- Name
local GUI_ROW_INPUT = class("Input") -- Type Name
local GUI_ROW_OUTPUT = class("Output") -- Name Type
local GUI_ROW_LITERAL = class("Literal") -- Name Type Options

-- Components of the GUI

local gui

local programFrame
local programTopBar
local programsList
local programsSelectionBox
local programNameBox
local programUploadButton
local programCancelButton
local programContainer
local programInventory
local programViewport
local programViewportEmpty

local templateNode
local templateNodeIO
local templateNodeText
local templateNodeSelect
local templateNodeInput
local templateNodeButton
local templateNodeName
local templateProgramTab
local highlight

if _G.IsClient then
	gui = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui")

	programFrame = _G:WaitFor("ProgramFrame")
	programTopBar = programFrame:WaitForChild("TopBar")
	programsList = programTopBar:WaitForChild("Programs"):WaitForChild("ScrollingFrame")
	programsSelectionBox = programsList.Parent:WaitForChild("Template"):WaitForChild("UIStroke")
	programNameBox = programTopBar:WaitForChild("ProgramNameBox")
	programUploadButton = programTopBar:WaitForChild("ProgramUploadButton")
	programCancelButton = programTopBar:WaitForChild("ProgramCancelButton")
	programContainer = programFrame:WaitForChild("Container")
	programInventory = programContainer:WaitForChild("Inventory"):WaitForChild("ScrollingFrame")
	local programViewportFrame = programContainer:WaitForChild("Program")
	programViewport = programViewportFrame:WaitForChild("Viewport")
	programViewportEmpty = programViewportFrame:WaitForChild("Empty")

	templateNode = programContainer:WaitForChild("Template"):WaitForChild("Node")
	templateNodeIO = templateNode:WaitForChild("IO")
	templateNodeText = templateNode:WaitForChild("TextLabel")
	templateNodeSelect = templateNode:WaitForChild("LiteralSelect")
	templateNodeInput = templateNode:WaitForChild("LiteralInput")
	templateNodeButton = templateNode:WaitForChild("LiteralButton")
	templateNodeName = templateNode:WaitForChild("NodeLabel")
	templateProgramTab = programsList:WaitForChild("Template"):WaitForChild("Script")
	highlight = script.Highlight

	templateNode.Visible = false
	templateProgramTab.Visible = false
end

local nodeLibrary = {}

do -- General nodes library
	-- Node constructor functions:
	
	-- Node input dragged out from the right as a point
	local function input(name,type)
		return function(node)
			table.insert(node.rows,{name = name, text = name, input = type})
		end
	end

	-- Node output dragged in from the left as a point
	local function output(name,type,producer)
		return function(node)
			table.insert(node.rows,{name = name, text = name, output = type, producer = producer})
		end
	end
	
	-- Node needs to call a function when done
	local function callback(callback)
		return function(node)
			node.callback = callback
		end
	end

	-- Node literal value typed in by user
	local function literal(name,type,options)
		return function(node)
			local row = {name = name, text = name, literal = type}
			table.insert(node.rows,row)
		end
	end

	-- Just a row of text on the node
	local function text(text)
		return function(node)
			table.insert(node.rows,{text = text})
		end
	end
	
	-- The name tag at the top of the node
	local function name(name)
		return function(node)
			node.name = name
		end
	end

	-- The overall node
	local function node(...)
		local node = {
			inputs = {},
			outputs = {},
			rows = {},
			callback = nil,
			name = ""
		}
		for _,item in pairs({...}) do
			item(node)
		end
		return node
	end

	-- Raw constructors for nodes in the instruction set
	local library = {
		node(
			name("Control"),
			literal("Control",CONTROL),
			output("Value",NUMBER,function(node,program)
				return program.context.control(node.Control)
			end)
		),
		node(
			name("Input"),
			literal("Pin",PIN),
			output("Value",ANY,function(node,program)
				return program.context.input(node.Pin)
			end)
		),
		node(
			name("Output"),
			literal("Pin",PIN),
			literal("Header",STRING),
			input("Value",ANY),
			callback(function(node,program)
				program.context.output(node.Pin,node.Header,node.Value())
			end)
		),
		node(
			name("Print"),
			input("Text",ANY),
			callback(function(node)
				print(node.Text() or "nil")
			end)
		),
		node(
			name("Notify"),
			input("Title",ANY),
			input("Text",ANY),
			callback(function(node,program)
				program.context.message(node.Title or "Program Message",node.Text or "Nothing")
			end)
		),
		node(
			name("Number"),
			literal("Number",NUMBER),
			output("Value",NUMBER,function(node)
				return node.Number or 0 
			end)
		),
		node(
			name("Boolean"),
			literal("Truth",BOOLEAN),
			output("Value",BOOLEAN,function(node)
				return node.Truth or false
			end)
		),
		node(
			name("String"),
			literal("String",STRING),
			output("Value",STRING,function(node)
				return node.String or ""
			end)
		),
		node(
			name("Neg"),
			input("A",NUMBER),
			output("-A",NUMBER,function(node)
				return -(tonumber(node.A() or 0) or 0)
			end)
		),
		node(
			name("Con"),
			input("A",STRING),
			output("A .. B",STRING,function(node)
				return (tostring(node.A() or "") or "") .. (tostring(node.B() or "") or "")
			end),
			input("B",STRING)
		),
		node(
			name("Add"),
			input("A",NUMBER),
			output("A + B",NUMBER,function(node)
				return (tonumber(node.A() or 0) or 0) + (tonumber(node.B() or 0) or 0)
			end),
			input("B",NUMBER)
		),
		node(
			name("Sub"),
			input("A",NUMBER),
			output("A - B",NUMBER,function(node)
				return (tonumber(node.A() or 0) or 0) - (tonumber(node.B() or 0) or 0)
			end),
			input("B",NUMBER)
		),
		node(
			name("Div"),
			input("A",NUMBER),
			output("A / B",NUMBER,function(node)
				return (tonumber(node.A() or 1) or 1) / (tonumber(node.B() or 1) or 1)
			end),
			input("B",NUMBER)
		),
		node(
			name("Mul"),
			input("A",NUMBER),
			output("A * B",NUMBER,function(node)
				return (tonumber(node.A() or 1) or 1) * (tonumber(node.B() or 1) or 1)
			end),
			input("B",NUMBER)
		),
		node(
			name("Comp"),
			input("A",NUMBER),
			output("A < B",BOOLEAN,function(node)
				return (tonumber(node.A() or math.huge) or math.huge) < (tonumber(node.B() or -math.huge) or -math.huge)
			end),
			input("B",NUMBER)
		),
		node(
			name("Comp Eq"),
			input("A",NUMBER),
			output("A <= B",BOOLEAN,function(node)
				return (tonumber(node.A() or math.huge) or math.huge) <= (tonumber(node.B() or -math.huge) or -math.huge)
			end),
			input("B",NUMBER)
		),
		node(
			name("Cos"),
			input("Value",NUMBER),
			output("Cosine",NUMBER,function(node)
				return math.cos(tonumber(node.Value() or 0) or 0)
			end)
		),
		node(
			name("Sin"),
			input("Value",NUMBER),
			output("Sin",NUMBER,function(node)
				return math.sin(tonumber(node.Value() or 0) or 0)
			end)
		),
		node(
			name("Equal"),
			input("A",ANY),
			output("A == B",BOOLEAN,function(node)
				return node.A() == node.B() 
			end),
			input("B",ANY)
		),
		node(
			name("Gate"),
			input("Continue",BOOLEAN),
			output("Output",ANY,function(node)
				if node.Continue() then
					return node.Input()
				else
					coroutine.yield()
				end
			end),
			input("Input",ANY)
		),
		node(
			name("Set"),
			literal("Name",STRING),
			input("Value",ANY),
			callback(function(node,this)
				this.variables[node.Name()] = node.Value
			end)
		),
		node(
			name("Get"),
			literal("Name",STRING),
			output("Value",ANY,function(node,this)
				return this.variables[node.Name()]
			end)
		),
		node(
			name("Time"),
			output("Value",ANY,function(node,this)
				return tick()
			end)
		),
		node(
			name("Frequency"),
			literal("Number",NUMBER,{min = 1, max = 60}),
			callback(function(node,program)
				program.frequency = node.Number or 30
			end)
		)
	}
	
	-- Create all node frames for the library to select nodes from
	local function init()
		programFrame.Visible = false
		for index,node in ipairs(library) do
			local frame = templateNode:Clone()
			-- Remove all things already in the template, they may not be needed.
			for _,item in pairs(frame:GetChildren()) do
				if item:IsA("GuiObject") then item:Destroy() end
			end
			-- Node name tag at the top
			local name = templateNodeName:Clone()
			name.Text = node.name or error("Node missing a name")
			name.Parent = frame
			-- Massive if thingy to make all the different types of row in a node
			for index,row in ipairs(node.rows) do
				local item
				if row.input or row.output then -- Input/Output
					local io = templateNodeIO:Clone()
					io.Point.BackgroundColor3 = TYPE_COLOURS[(row.input or row.output).name]
					if row.input then
						io.Label.TextXAlignment = Enum.TextXAlignment.Left
					elseif row.output then
						io.Point.Position = UDim2.new(1,6,0.5,0)
						io.Label.TextXAlignment = Enum.TextXAlignment.Right
					end
					io.Label.Text = row.text
					item = io
				elseif row.literal then -- Literal
					local literal
					local type = row.literal
					if row.literal == NUMBER or row.literal == STRING then
						literal = templateNodeInput:Clone()
						literal.Text = ""
						if row.literal == NUMBER then
							literal.PlaceholderText = "0"
						elseif row.literal == STRING then
							literal.PlaceholderText = "Text"
						end
					elseif row.literal == BOOLEAN then
						literal = templateNodeButton:Clone()
						literal.Text = "False"
					elseif row.literal == PIN or row.literal == CONTROL then
						literal = templateNodeSelect:Clone()
						if row.literal == PIN then
							literal.Value.Text = "1"
						elseif row.literal == CONTROL then
							literal.Value.Text = "throttle"
						end
					else
						error("No row type for "..tostring(row.literal))
					end
					item = literal
				elseif row.text then -- Text
					local text = templateNodeText:Clone()
					text.Text = row.text
					item = text
				else
					error("Row doesn't satisfy any template rows")
				end
				item.Parent = frame
				item.LayoutOrder = index
				if row.name then item.Name = row.name end
			end
			-- Darker nodes indicate where the rest of the nodes get originally called from!
			if node.callback then frame.UIStroke.Enabled = true end
			
			frame.LayoutOrder = index
			frame.Parent = programInventory
			frame.Visible = true
			
			node.frame = frame
			library[frame] = node
		end
	end
	
	if _G.IsClient then init() end
	
	for index,node in pairs(library) do
		library[node.name] = node
	end
	
	nodeLibrary = library
end

-- Neat way of making classes in Lua
local function factory()
	local meta = {}
	meta.__index = meta
	setmetatable(meta,{__call = function(meta,...)
		return setmetatable(meta.new(...),meta)
	end})
	return meta
end

local currentProgram
local editSource
local openProgram

-- The infamous dragger
local dragger = function(a,b) return require(_G.Modules.DraggingModule).dragger(a,b,programFrame) end

do -- General nodes programming
	local programs = {} -- Live program data being edited. programs[program{options...=...,node{}...}]
	
	-- Make a line shape to be going from the two AbsolutePositions given
	local function position_line(line,start,finish)
		local diff = start-finish
		local rotation = math.atan2(diff.y,diff.x)
		local mid = finish:Lerp(start,0.5)
		line.Position = UDim2.new(0,mid.X-line.Parent.AbsolutePosition.X,0,mid.Y-line.Parent.AbsolutePosition.Y)
		line.Rotation = math.deg(rotation)
		line.Size = UDim2.new(0,(diff.Magnitude)+line.Size.Y.Offset,0,line.Size.Y.Offset)
	end
	
	local row = factory()
	row.new = function(template,node)
		local this = {}
		this.parent = node
		this.parent.rows[template.name] = this
		this.template = template
		this.frame = nil
		if this.template.input then
			this.link = nil
			this.line = nil
		end
		if this.template.output then
			this.links = {}
		end
		return this
	end
	local function matches(this,other)
		local function types(input,output)
			if not (input.template.input and output.template.output) then return nil end
			if input.template.input == output.template.output then return input.template.input,input,output
			elseif input.template.input == ANY then return output.template.output,input,output
			elseif output.template.output == ANY then return input.template.input,input,output
			else return ANY,input,output end
		end
		local typed,i,o = types(this,other)
		if typed then return typed,i,o end
		local typed,i,o = types(other,this)
		if typed then return typed,i,o end
		return nil
	end
	row.adopt = function(this,frame)
		this.frame = frame
		if this.template.output or this.template.input then
			local point = frame.Point
			this.parent.parent.points[point] = this
			dragger(point,function(initial)
				-- Create a line from the point we are dragging from
				local line = frame.Point:Clone()
				line.AnchorPoint = Vector2.new(0.5,0.5)
				line.Parent = gui
				local color = line.BackgroundColor3
				-- Offset of point, something to do with AnchorPoint I think...
				local offset = Vector2.new(frame.Point.Size.X.Offset,frame.Point.Size.Y.Offset)/2
				-- Other point connected to
				local connected
				return {
					drag = function(position)
						local position = Vector2.new(position.X,position.Y)
						local start = frame.Point.AbsolutePosition + offset
						position_line(line,start,position)
						line.BackgroundColor3 = color
						connected = nil
						for _,object in _G.Gui:GetGuiObjectsAtPosition(position.X,position.Y) do
							local row = this.parent.parent.points[object] 
							if row then
								local typed = matches(this,row)
								if typed then
									local point = row.frame.Point
									connected = row
									position_line(line,start,point.AbsolutePosition + offset)
									line.BackgroundColor3 = TYPE_COLOURS[typed.name]
								end
							end
						end
					end,
					ended = function()
						if connected then
							local typed,input,output = matches(this,connected)
							input:connect(output)
						end
						line:Destroy()
					end,
				}
			end)
		elseif this.template.literal then
			local typed = this.template.literal
			if typed == NUMBER or typed == STRING then
				if typed == NUMBER then this.value = this.value or 0
				elseif typed == STRING then this.value = this.value or ""
				end
				this.frame.Text = tostring(this.value)
				this.frame.FocusLost:Connect(function(enter)
					if enter then
						if typed == NUMBER then
							this.value = tonumber(this.frame.Text) or 0
						elseif typed == STRING then
							this.value = string.sub(this.frame.Text or "",1,25565)
						end
						this.frame.Text = tostring(this.value)
					end
				end)
			elseif typed == BOOLEAN then
				if this.value then this.frame.Text = "True" else this.frame.Text = "False" end
				this.frame.Activated:Connect(function()
					this.value = not this.value
					if this.value then this.frame.Text = "True" else this.frame.Text = "False" end
				end)
			elseif typed == PIN or typed == CONTROL then
				local tab
				if typed == PIN then 
					tab = PINS_LIST 
				elseif typed == CONTROL then
					tab = CONTROL_LIST 
				end
				this.value = this.value or tab[1]
				this.frame.Value.Text = this.value
				this.frame.Next.Activated:Connect(function()
					local index = table.find(tab,this.value) + 1
					if index > #tab then index = 1 end
					this.value = tab[index]
					this.frame.Value.Text = this.value
				end)
				this.frame.Previous.Activated:Connect(function()
					local index = table.find(tab,this.value) - 1
					if index < 1 then index = #tab end
					this.value = tab[index]
					this.frame.Value.Text = this.value
				end)
			else
				error("No row type for "..tostring(row.literal))
			end
		end
	end
	-- Make a row's line position correctly if it exists
	row.reconnect = function(this)
		if this.template.input and this.link and this.line then
			local offset = Vector2.new(this.frame.Point.Size.X.Offset,this.frame.Point.Size.Y.Offset)/2
			position_line(
				this.line,
				this.frame.Point.AbsolutePosition + offset,
				this.link.frame.Point.AbsolutePosition + offset
			)
		elseif this.template.output then
			-- As an output we have multiple links, inputs have one, let's just let the inputs do it.
			for _,input in pairs(this.links) do
				input:reconnect()
			end
		end
	end
	-- Disconnect a node from it's incoming link
	row.disconnect = function(this)
		if this.template.output then
			-- Just pass it to the input to handle this.
			local links = {}
			for _,row in pairs(this.links) do
				table.insert(links,row)
			end
			for _,row in pairs(links) do
				row:disconnect()
			end
			--error("Can't disconnect from output, do it on input.",2)
		elseif this.template.input then
			if this.link then -- Check if it is linked
				--print("disc",debug.traceback())
				-- Remove that link from whatever it was linked to as well
				table.remove(this.link.links,table.find(this.link.links,this))
				-- Remove it here
				this.link = nil
				-- Remove the GUI
				if this.line then this.line:Destroy() this.line = nil end
			end
		end
	end
	row.close = function(this)
		if this.template.output or this.template.input then
			if this.line then this.line:Destroy() this.line = nil end
			this.parent.parent.points[this.frame.Point] = nil
			this.frame = nil
			-- No need for this.frame:Destroy() since a row can only be destroyed when a node is, and a row is in the node's frame
		end
	end
	row.connect = function(this,other)
		if this.template.output then
			-- It's not really good to do this either. Input's only have one, simple.
			error("Can't connect from output, do it on input.",2)
		elseif this.template.input then
			this:disconnect()
			this.link = other -- Link up here
			table.insert(other.links,this) -- Link up on the output too
			--print(this,debug.traceback())
			if this.frame then -- Create a line if we have a frame
				local line = this.frame.Point:Clone()
				line.AnchorPoint = Vector2.new(0.5,0.5)
				line.Parent = programViewport
				line.ZIndex = line.ZIndex - 1
				line.InputEnded:Connect(function(input)
					if input.UserInputType == Enum.UserInputType.MouseButton1 or 
						input.UserInputType == Enum.UserInputType.Touch then
						this:disconnect()
					end
				end)
				line.BackgroundColor3 = TYPE_COLOURS[matches(this,other).name]
				this.line = line
			end
			this:reconnect()
		end
	end
	
	local node = factory()
	node.new = function(template,parent)
		local this = {}
		this.template = template
		-- Row: {frame, template, }
		-- Node: {...,rows[int] = {},[producer_name] = function() end...,[literal_name] = value...}
		this.rows = {}
		for _,template in ipairs(template.rows) do
			row(template,this)
		end
		this.parent = parent
		this.position = Vector2.new(0,0)
		this.frame = nil
		return this
	end
	-- Adopt a frame that this node will respect
	node.adopt = function(this,frame)
		this.position = frame.AbsolutePosition - programViewport.AbsolutePosition
		this.frame = frame
		dragger(frame.NodeLabel,function(initial)
			local offset = frame.AbsolutePosition - initial - programViewport.AbsolutePosition
			return {
				drag = function(position)
					frame.Position = UDim2.new(0,position.X + offset.X,0,position.Y + offset.Y)
					this.position = frame.AbsolutePosition - programViewport.AbsolutePosition
					for _,row in pairs(this.rows) do
						row:reconnect()
					end
				end,
				ended = function(position)
					-- TODO: Add the stupid out of bounds check
				end,
			}
		end)
		frame.NodeLabel.CloseButton.Activated:Connect(function()
			this:destroy()
			this:close()
		end)
		for _,row in pairs(this.rows) do
			-- For every I/O row in the node
			row:adopt(this.frame:FindFirstChild(row.template.name))
		end
	end
	-- Remove all associated GUI
	node.close = function(this)
		if this.frame then
			this.position = this.frame.AbsolutePosition - programViewport.AbsolutePosition
			for _,row in pairs(this.rows) do row:close() end
			this.frame:Destroy()
			this.frame = nil
		end
	end
	-- Remove all links to other structures
	node.destroy = function(this)
		for _,row in pairs(this.rows) do
			row:disconnect()
		end
		table.remove(this.parent.nodes,table.find(this.parent.nodes,this))
	end
	-- Program data that can be executed and has GUI instances bound to it
	local program = factory()
	-- Literally write 'program()' to do program.new
	program.new = function(value,upload)
		local this = {}
		if upload then
			if programs[value] then
				return programs[value]
			else
				programs[value] = this -- Add it to the programs list end
			end
		end
		this.upload_callback = upload
		this.value = value
		this.name = "Unnamed"
		this.frequency = 30
		this.nodes = {}
		this.points = {} -- point to row
		this.tab = nil
		this.variables = {}
		-- Hooks for the value changing in game, the whole program needs to be updated in this case.
		if value.Changed then
			value.Changed:Connect(function()
				if currentProgram == this then this:close() end
				this:load()
				this:compile()
				if currentProgram == this then this:open() end
				print("reloaded")
				-- TODO: Add a program.new here so that users don't lose their program data!
			end)
		end
		if value.Destroying then
			value.Destroying:Connect(function()
				this:close() -- ... So why are they still there then?
				if this.tab then this.tab:Destroy() end
				programs[this.value] = nil
			end)
		end
		return this
	end
	-- Make program adopt a tab
	program.adopt = function(this,tab)
		this.tab = tab
		this.tab.Text = this.name
		this.tab.Close.Activated:Connect(function()
			wait()
			this:close()
			this.tab:Destroy()
			this.tab = nil
		end)
		this.tab.Activated:Connect(function()
			this:open()
		end)
	end
	-- Load into live program data from JSON
	NAME_INDEX = 1 -- For each row and each node too!
	POSITION_INDEX = 2
	ROWS_INDEX = 3
	VALUE_INDEX = 2
	LINK_ID_INDEX = 2
	LINK_ROW_INDEX = 3
	INDEX_TO_NAME = {}
	NAME_TO_INDEX = {}
	for word in ("Value,Input,Output,Text,Add,Div,Mul,Neg,Boolean,Number,Print,Message,Output,Input"):gmatch("[^,%s]+") do -- This string CANNOT change. 
		table.insert(INDEX_TO_NAME,word)		
	end
	for index,item in pairs(INDEX_TO_NAME) do 
		NAME_TO_INDEX[item] = index 
	end
	--print(NAME_TRANSLATION)
	program.load = function(this)
		local current = currentProgram == this
		xpcall(function() -- Protection on saves
			local text = this.value.Value
			
			if not text then return end
			if text == "" then return end
			
			local data = _G.Http:JSONDecode(text)

			this.name = data.name
			this.version = data.version
			if data.position then this.position = Vector2.new(data.position[1],data.position[2]) end
			
			this.nodes = {}
			for index,node in ipairs(data.nodes) do
				local name = node[NAME_INDEX]
				local template = nodeLibrary[INDEX_TO_NAME[name] or name]
				if not template then warn("Could not find node of name: "..INDEX_TO_NAME[name] or name) print(nodeLibrary) continue end
				this.nodes[index] = this:node(template)
				this.nodes[index].position = Vector2.new(node[POSITION_INDEX][1],node[POSITION_INDEX][2])
			end
			
			for index,data_node in ipairs(data.nodes) do
				local node = this.nodes[index]
				for _,data_row in pairs(data_node[ROWS_INDEX]) do
					local name = data_row[NAME_INDEX]
					local row = node.rows[INDEX_TO_NAME[name] or name]
					if not row then warn("Could not find row of name: "..(INDEX_TO_NAME[name] or name)) continue end
					if row.template.input then
						row:connect(this.nodes[data_row[LINK_ID_INDEX]].rows[INDEX_TO_NAME[data_row[LINK_ROW_INDEX]] or data_row[LINK_ROW_INDEX]])
					elseif row.template.literal then
						row.value = data_row[VALUE_INDEX]
					end
				end
			end
			
			if this.tab then this.tab.Text = this.name end
			this:compile()
		end,function(err)
			warn(debug.traceback(err,2))
		end)
	end
	-- Serialises the live program data
	program.save = function(this)
		local data = {nodes = {}, name = this.name, version = (this.version or 0) + 1}
		
		if this.position then data.position = {this.position.X,this.position.Y} end
		
		local ids = {}
		-- Make a table where we can get the id of a node using it as a key, ordered.
		for index,node in ipairs(this.nodes) do
			ids[node] = index
			if node.frame then node.position = node.frame.AbsolutePosition - programViewport.AbsolutePosition end
		end
		
		for _,node in ipairs(this.nodes) do
			local rows = {}
			for _,row in pairs(node.rows) do
				local name = row.template.name
				if row.template.input and row.link then
					table.insert(rows,{NAME_TO_INDEX[name] or name,ids[row.link.parent],NAME_TO_INDEX[row.link.template.name] or row.link.template.name})
				elseif row.template.literal then
					table.insert(rows,{NAME_TO_INDEX[name] or name,row.value})
				end
			end
			data.nodes[ids[node]] = {
				[NAME_INDEX] = NAME_TO_INDEX[node.template.name] or node.template.name,
				[POSITION_INDEX] = {node.position.X,node.position.Y},
				[ROWS_INDEX] = rows
			}
		end
		
		return _G.Http:JSONEncode(data)
	end
	program.upload = function(this)
		this.upload_callback(this:save())
	end
	-- Connects everything together in a program so it is accessible from the node
	program.compile = function(this)
		for _,node in pairs(this.nodes) do
			for _,row in pairs(node.rows) do
				if row.template.input then
					if row.link then
						node[row.template.name] = function() return row.link.template.producer(row.link.parent,this) end
					else
						node[row.template.name] = function() end
					end
				end
				if row.template.literal then
					node[row.template.name] = row.value
				end
			end
		end
	end
	-- Run a program from its data
	program.run = function(this,context)
		program.context = context
		for _,node in pairs(this.nodes) do
			if node.template.callback then coroutine.wrap(_G.Protect(node.template.callback))(node,this) end
		end
	end
	-- Open a program into the editor/viewport
	program.open = function(this)
		-- TODO: Add a selection box around the currently operating tab
		-- TODO: Add a background tint if the script has not been saved yet
		if currentProgram then currentProgram:close() end

		currentProgram = this
		programViewportEmpty.Visible = false
		highlight.Adornee = this.value.Parent
		programsSelectionBox.Parent = this.tab
		for _,node in ipairs(this.nodes) do
			local frame = node.template.frame:Clone()
			frame.Parent = programViewport
			frame.Position = UDim2.new(0,node.position.X,0,node.position.Y)
			node:adopt(frame)
		end
		if this.position then
			local pos = programViewport.Position
			programViewport.Position = UDim2.new(pos.X.Scale,this.position.X,pos.Y.Scale,this.position.Y)
		end
		-- This is quite bad:
		for _,node in ipairs(this.nodes) do
			for _,row in pairs(node.rows) do
				if row.link then row:connect(row.link) end -- To get links to regenerate after everything is adopted
			end
		end
	end
	-- Remove all GUI hooks in data
	program.close = function(this)
		for _,node in pairs(this.nodes) do
			node:close()
		end
		if _G.IsClient then
			programViewportEmpty.Visible = true
			programsSelectionBox.Parent = nil
			currentProgram = nil
			highlight.Adornee = nil
		end
	end
	-- Remove a node, resolve connections and remove GUI
	program.remove = function(this,node)
		node:close()
		node:destroy()
		table.remove(this.nodes,table.find(this.nodes,node))
	end
	-- Add a node, candidly on the ScreenGui (lol)
	program.node = function(this,...)
		local node = node(...,this)
		table.insert(this.nodes,node)
		return node
	end
	-- Make a new program, add it to the list, read into it and open it out.
	local function edit(value,upload)
		-- Value is a StringValue instance or a table binding to this, so return if it's already here!
		
		local this = program(value,upload)
		
		if not this.tab then
			local tab = templateProgramTab:Clone()
			tab.Parent = programsList
			tab.Visible = true
			this:adopt(tab)
		end
		
		if currentProgram then currentProgram:close() end
		this:load()
		this:open()
	end
	
	editSource = edit
	openProgram = program
end

if _G.IsClient then -- General GUI interaction
	programCancelButton.Activated:Connect(function()
		programFrame.Visible = false
		if currentProgram then currentProgram:close() end
	end)
	programUploadButton.Activated:Connect(function()
		if currentProgram then
			local program = currentProgram
			_G.Events.Notification:Fire("Program Upload","Attempting program upload.")
			program:upload()
			-- Optional, this should always work but it's not necessary
			program:close()
			program:load()
			program:open()
		else
			_G.Events.Notification:Fire("Program Upload","No program is currently loaded.")
		end
	end)
	dragger(programViewport,function(last)
		local lower = Vector2.new(-math.huge,-math.huge)
		local upper = Vector2.new(math.huge,math.huge)
		if currentProgram then
			for _,node in pairs(currentProgram.nodes) do
				node = node.frame
				lower = Vector2.max(lower,node.AbsolutePosition - programViewport.AbsolutePosition)
				upper = Vector2.min(upper,node.AbsolutePosition - programViewport.AbsolutePosition + node.AbsoluteSize - programViewport.AbsoluteSize)
			end
		end
		local original = programViewport.Position
		local positions = {}
		return {
			drag = function(position)
				local offset = position - last
				programViewport.Position = UDim2.new(original.X.Scale,original.X.Offset + offset.X,original.Y.Scale,original.Y.Offset + offset.Y)
				currentProgram.position = Vector2.new(original.X.Offset + offset.X,original.Y.Offset + offset.Y)
				--[[if not currentProgram then return end
				for _,node in pairs(currentProgram.nodes) do
					if not positions[node] then positions[node] = node.position end 
					node.position = positions[node] + Vector2.min(Vector2.max(position - last,-lower),-upper)
					node.frame.Position = UDim2.new(0,node.position.X,0,node.position.Y)
					for _,row in pairs(node.rows) do
						row:reconnect()
					end
				end]]
			end
		}
	end)
	--[[local pinchState
	local pinchScale
	programViewport.TouchPinch:Connect(function(_,scale,_,state)
		pinchState = state
		pinchScale
	end)
	]]
	programViewport.MouseWheelBackward:Connect(function()
		programViewport.UIScale.Scale = math.clamp(programViewport.UIScale.Scale - 0.1,0.2,2)
	end)
	programViewport.MouseWheelForward:Connect(function()
		programViewport.UIScale.Scale = math.clamp(programViewport.UIScale.Scale + 0.1,0.2,2)
	end)
	programNameBox.FocusLost:Connect(function(enter)
		if enter and currentProgram then
			currentProgram.name = string.sub(programNameBox.Text,1,128)
			currentProgram.tab.Text = currentProgram.name
		end
	end)
	-- GIVEN 'init()' on inventory
	for _,frame in pairs(programInventory:GetChildren()) do
		if nodeLibrary[frame] then -- This is a real node frame we can place
			frame.ZIndex = -1 
			dragger(frame.NodeLabel,function(initial)
				local offset = frame.AbsolutePosition - initial
				frame.Parent = gui
				return {
					drag = function(position)
						frame.Position = UDim2.new(0,position.X + offset.X,0,position.Y + offset.Y)
					end,
					ended = function(position)
						local lowX = programViewport.AbsolutePosition.X
						local highX = lowX + programViewport.AbsoluteSize.X
						local lowY = programViewport.AbsolutePosition.Y
						local highY = lowY + programViewport.AbsoluteSize.Y
						if position.X > lowX and
							position.X < highX and 
							position.Y > lowY and 
							position.Y < highY and
							currentProgram
						then
							-- Create a new node frame
							local template = frame
							local frame = template:Clone()
							local position = template.AbsolutePosition - programViewport.AbsolutePosition
							template.Parent = programInventory
							frame.Parent = programViewport
							frame.Position = UDim2.new(0,position.X,0,position.Y)
							-- Create new node data
							local node = currentProgram:node(nodeLibrary[template])
							node:adopt(frame) -- Make the data adopt the frame
						end
						frame.Parent = programInventory
					end,
				}
			end)
		end
	end
end

return {edit_source = editSource, open_program = openProgram}