if not fs.exists("windont.lua") then
	print("'windont.lua' not found! Downloading...")
	local net = http.get("https://github.com/LDDestroier/CC/raw/master/windont/windont.lua")
	if net then
		local file = fs.open("windont.lua", "w")
		file.write(net.readAll())
		file.close()
		net.close()
	else
		error("Could not download Windon't.", 0)
	end
end
local windont = require "windont"

windont.default.alwaysRender = false
windont.useSetVisible = true

local scr_x, scr_y = term.getSize()
local keysDown = {}

local instances = {}
local knownNames = {
	["rom/programs/shell.lua"] = "CraftOS Shell",
	["rom/programs/edit.lua"] = "Edit",
	["rom/programs/gps.lua"] = "GPS",
	["rom/programs/list.lua"] = "List",
	["rom/programs/shutdown.lua"] = "Shutting down...",
	["rom/programs/reboot.lua"] = "Rebooting...",
	["rom/programs/monitor.lua"] = "Monitor Redirect",
	["rom/programs/emu.lua"] = "Emu (CCEmuX)",
	["rom/programs/exit.lua"] = "Goodbye!",
	["rom/programs/lua.lua"] = "Lua Interpreter",

	["rom/programs/fun/adventure.lua"] = "Adventure",
	["rom/programs/fun/worm.lua"] = "Worm",
	["rom/programs/fun/dj.lua"] = "DJ",
	["rom/programs/fun/hello.lua"] = "Hello world!",

	["rom/programs/turtle/dance.lua"] = "Dance!",
	["rom/programs/turtle/craft.lua"] = "Craft",
	["rom/programs/turtle/excavate.lua"] = "Excavate",
	["rom/programs/turtle/refuel.lua"] = "Refueling...",
	["rom/programs/turtle/go.lua"] = "Go (Turtle)",
	["rom/programs/turtle/turn.lua"] = "Turn (Turtle)",

	["rom/programs/http/pastebin.lua"] = "Pastebin",
	["rom/programs/http/wget.lua"] = "Wget",

	["rom/programs/pocket/falling.lua"] = "Falling",

	["rom/programs/rednet/chat.lua"] = "Chat",
	["rom/programs/rednet/repeat.lua"] = "Rednet Repeat",

	["rom/programs/command/exec.lua"] = "Exec (Command)",
	["rom/programs/command/commands.lua"] = "Commands",
}

-- events that can only be passed if the instance is focused
local FocusEvents = {
	["mouse_click"] = true,
	["mouse_drag"] = true,
	["mouse_up"] = true,
	["key"] = true,
	["key_up"] = true,
	["char"] = true,
	["monitor_touch"] = true,
	["paste"] = true,
	["terminate"] = true
	-- mouse_scroll is intentionally excluded
}

-- events that must have their XY values altered according to the position of the window
local CoordinateEvents = {
	["mouse_click"] = {3, 4},
	["mouse_drag"] = {3, 4},
	["mouse_up"] = {3, 4},
	["mouse_scroll"] = {3, 4}
}

local desktop = windont.newWindow(1, 1, scr_x, scr_y, {
	backColor = "9"
})
local overlay = windont.newWindow(1, 1, scr_x, scr_y, {
	backColor = "-"
})
local debugOverlay = windont.newWindow(1, 1, scr_x, scr_y, {
	backColor = "-"
})
desktop.redraw()

local instanceBoxCheck = function(i, x, y, useMain)
	assert(type(x) == "number", "x must be number")
	assert(type(y) == "number", "y must be number")
	return (
		x < 1 or x > (useMain and instances[i].mainWindow.meta.width or instances[i].termWindow.meta.width) or
		y < 1 or y > (useMain and instances[i].mainWindow.meta.height or instances[i].termWindow.meta.height)
	)
end

local focusInstance = function(i)
	if instances[i or false] then
		if i ~= 1 then
			local instance = instances[i]
			instances[1].focused = false
			table.remove(instances, i)
			table.insert(instances, 1, instance)
			instances[1].focused = true
		end
	end
end

-- checks if (x, y) is within a rectangle between (rx1, ry1), (rx2, ry2)
local rectangleCheck = function(x, y, rx1, ry1, rx2, ry2)
	return
		(x >= rx1 and x <= rx2) and
		(y >= ry1 and y <= ry2)
end

-- checks which instance should be selected if you were to click on (x, y)
local checkInstanceByPos = function(x, y, useTerm)
	for i = 1, #instances do
		if rectangleCheck(
			x,
			y,
			instances[i].mainWindow.meta.x + (useTerm and 1 or 0),
			instances[i].mainWindow.meta.y + (useTerm and 1 or 0),
			instances[i].mainWindow.meta.x + (useTerm and 1 or 0) + (useTerm and instances[i].termWindow or instances[i].mainWindow).meta.width - 1,
			instances[i].mainWindow.meta.y + (useTerm and 1 or 0) + (useTerm and instances[i].termWindow or instances[i].mainWindow).meta.height - 1
		) then
			return i
		end
	end
	return false
end

local resumeInstance = function(i, _evt, isCoordinateEvent)
	local evt = {}
	for k,v in pairs(_evt) do
		evt[k] = v
	end
	if isCoordinateEvent then
		evt[3] = evt[3] - instances[i].mainWindow.meta.x
		evt[4] = evt[4] - instances[i].mainWindow.meta.y
	end
	repeat
		if (isCoordinateEvent and instanceBoxCheck(i, evt[3], evt[4])) then
			break
		end
		oldTerm = term.redirect(instances[i].termWindow)
		success, result = coroutine.resume(instances[i].coroutine, table.unpack(evt))

		instances[i].program = shell.resolveProgram(instances[i].environment.multishell.getTitle(multishell.getCurrent()))
		instances[i].setTitle()

		term.redirect(oldTerm)
		if success and coroutine.status(instances[i].coroutine) ~= "dead" then
			instances[i].cFilter = result
		else
			instances[i].alive = false
		end
	until true
end

local newInstance = function(x, y, width, height, program, pName, addBorder)
	local output = {}
	if addBorder then
		output.mainWindow = windont.newWindow(x, y, width, height + 1, {
			baseTerm = desktop,
			alwaysRender = false,
			backColor = "7"
		})
		output.termWindow = windont.newWindow(1, 2, width, height, {
			baseTerm = output.mainWindow,
			alwaysRender = false,
			blink = false
		})
		output.oldTermPos = {1, 2, width, height}
		output.oldMainPos = {x, y, width, height + 1}
	else
		output.mainWindow = windont.newWindow(x, y, width + 2, height + 2, {
			baseTerm = desktop,
			alwaysRender = false,
			backColor = "7"
		})
		output.termWindow = windont.newWindow(2, 2, width, height, {
			baseTerm = output.mainWindow,
			alwaysRender = false,
		})
		output.oldTermPos = {2, 2, width, height}
		output.oldMainPos = {x, y, width + 2, height + 2}
	end

	local mw = output.mainWindow	-- contains the titlebar, and is the base terminal for termWindow
	local tw = output.termWindow	-- contains the program's terminal output

	tw.meta.transformation = function(x, y, char, text, back, meta)
		if x > mw.meta.width - 2 then
			return {x, y, " "}, {x, y, "-"}, {x, y, "-"}
		else
			return {x, y, char}, {x, y, text}, {x, y, back}
		end
	end

	output.refreshMainWindow = function()
		for y = 1, mw.meta.height do
			mw.setCursorPos(1, y)
			if y == 1 or y == mw.meta.height then
				mw.blit(
					(" "):rep(mw.meta.width),
					("7"):rep(mw.meta.width),
					("7"):rep(mw.meta.width)
				)
			else
				mw.blit(" ","7","7")
				mw.setCursorPos(mw.meta.width, y)
				mw.blit(" ","7","7")
			end
		end
	end
	output.refreshMainWindow()
	output.program = program or "rom/programs/shell.lua"
	output.setTitle = function()
		output.title = pName or (type(output.program) == "string" and (knownNames[fs.combine("", output.program)] or fs.getName(output.program))) or tostring(output.program)
	end
	output.setTitle()
	-- pausing will probably be implemented later
	output.paused = false
	output.timeMod = 0
	output.clockMod = 0
	output.timers = {}

	output.alive = true
	output.focused = true
	output.manipMode = 0

	output.writeTitleBar = function()
		mw.setCursorPos(1, 1)
		if output.focused then
			mw.setTextColor(colors.white)
		else
			mw.setTextColor(colors.lightGray)
		end
		mw.setBackgroundColor(colors.gray)
		mw.clearLine()

		if #output.title <= (mw.meta.width - 4) then
			mw.write(output.title)	-- write full title
		else
			mw.write(output.title:sub(1, mw.meta.width - 7) .. "...") -- draw abreviated title
		end
--		mw.setCursorPos(mw.meta.width - 3, 1)
--		mw.write(" \22\94\215")	-- minimize / maximize / close
		mw.setCursorPos(mw.meta.width - 1, 1)
		mw.write(" \215")	-- close
	end

	if type(program) == "string" then
		output.main = function()
			tw.clear()
			return shell.run(program)
		end
	elseif type(program) == "function" then
		output.main = program
	end

	output.environment = {}
	setmetatable(output.environment, {__index = _ENV})
	setfenv(output.main, output.environment)

	output.coroutine = coroutine.create(output.main)
	output.cFilter = nil

	for i = 1, #instances do
		instances[i].focused = false
	end

	table.insert(instances, 1, output)

	resumeInstance(1, {}, false)

	return output
end

local moveInstance = function(i, x, y, newWidth, newHeight, relative)
	desktop.clear()
	newWidth = math.max(newWidth or instances[i].mainWindow.meta.width, 3)
	newHeight = math.max(newHeight or instances[i].mainWindow.meta.height, 3)
	if relative then
		if x == 0 and y == 0 then
			if (not newWidth or newWidth == instances[i].mainWindow.meta.width) and (not newHeight or newHeight == instances[i].mainWindow.meta.height) then
				return
			end
		end
		instances[i].mainWindow.reposition(instances[i].mainWindow.meta.x + x, instances[i].mainWindow.meta.y + y, newWidth, newHeight)
	else
		if x == instances[i].mainWindow.meta.x and y == instances[i].mainWindow.meta.y then
			if (not newWidth or newWidth == instances[i].mainWindow.meta.width) and (not newHeight or newHeight == instances[i].mainWindow.meta.height) then
				return
			end
		end
		instances[i].mainWindow.reposition(x, y, newWidth, newHeight)
	end
	instances[i].termWindow.reposition(
		2, 2,
		math.max(instances[i].oldTermPos[3], instances[i].mainWindow.meta.width - 2),
		math.max(instances[i].oldTermPos[4], instances[i].mainWindow.meta.height - 2)
	)
	instances[i].termWindow.redraw(nil, nil, nil, {force = true})
	instances[i].refreshMainWindow()
end

local render = function()
	local wins = {}
	for i = 1, #instances do
		wins[i] = instances[i].mainWindow
		instances[i].termWindow.redraw()
	end
	windont.render({force = true}, table.unpack(wins))
	windont.render({}, debugOverlay, overlay, desktop)
end

local makeNewWindow = function(program)
	newInstance(2, 2, math.max(scr_x - 16, 8), math.max(scr_y - 6, 5), program or "rom/programs/shell.lua")
	local good = false
	while not good do
		good = true
		if #instances == 1 then
			return
		else
			for i = 2, #instances do
				if instances[1].mainWindow.meta.x == instances[i].mainWindow.meta.x and instances[1].mainWindow.meta.y == instances[i].mainWindow.meta.y then
					instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x + 2, instances[1].mainWindow.meta.y + 2)
					good = false
					break
				end
			end
		end
	end
end

local cleanExit = function()
	term.setBackgroundColor(colors.black)
	term.clear()
	term.setCursorPos(1, 1)
	print("Thanks for using Windon't Shell!")
end

local main = function()

	makeNewWindow()

	local evt, success, result, oldTerm
	local cx, cy
	local isCoordinateEvent
	local usedCoordinateEvent
	local isManipulatingInstance = false
	-- handles input system
	local keyTimer = os.startTimer(0.05)

	while true do
		evt = {coroutine.yield()}
		cx, cy = term.getCursorBlink()
		if evt[1] == "key" and not evt[3] then
			keysDown[evt[2]] = 0
		elseif evt[1] == "key_up" then
			keysDown[evt[2]] = nil
		end
		if evt[1] == "term_resize" then
			scr_x, scr_y = term.getSize()
			desktop.reposition(1, 1, scr_x, scr_y)
			overlay.reposition(1, 1, scr_x, scr_y)
		end
		if evt[1] == "mouse_click" then
			focusInstance(checkInstanceByPos(evt[3], evt[4]))
			if not checkInstanceByPos(evt[3], evt[4]) then
				if evt[2] == 2 then
					makeNewWindow()
				end
			elseif evt[2] == 1 then
				if evt[4] == instances[1].mainWindow.meta.y and evt[3] == (instances[1].mainWindow.meta.x + instances[1].mainWindow.meta.width - 1) then
					instances[1].alive = false
				end
			end
		end
		if evt[1] == "terminate" and not instances[1] then
			cleanExit()
			return true
		end
		if evt[1] == "timer" and evt[2] == keyTimer then
			keyTimer = os.startTimer(0)
			for k,v in pairs(keysDown) do
				keysDown[k] = v + 0.05
			end

			if instances[1] then
				term.setCursorPos(
					instances[1].termWindow.meta.cursorX + instances[1].mainWindow.meta.x,
					instances[1].termWindow.meta.cursorY + instances[1].mainWindow.meta.y
				)
				term.setCursorBlink(instances[1].termWindow.meta.blink)
				overlay.clear()
			else
				local msg = "Right click to open shell."
				term.setCursorBlink(false)
				overlay.setCursorPos(scr_x / 2 - #msg / 2, scr_y / 2)
				overlay.write("Right click to open shell.")
			end
			render()

			-- move windows with arrow keys (for now)
			if false then
				if keysDown[keys.right] then
					desktop.clear()
					instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x + 1, instances[1].mainWindow.meta.y)
				end
				if keysDown[keys.left] then
					desktop.clear()
					instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x - 1, instances[1].mainWindow.meta.y)
				end
				if keysDown[keys.up] then
					desktop.clear()
					instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x,     instances[1].mainWindow.meta.y - 1)
				end
				if keysDown[keys.down] then
					desktop.clear()
					instances[1].mainWindow.reposition(instances[1].mainWindow.meta.x,     instances[1].mainWindow.meta.y + 1)
				end
			end
		else
			usedCoordinateEvent = false
			isCoordinateEvent = false
			for i = 1, #instances do
				if (not isManipulatingInstance) and evt[1] == "mouse_click" and (keysDown[keys.leftAlt] or (
					(checkInstanceByPos(evt[3], evt[4], false) == i) and not (checkInstanceByPos(evt[3], evt[4], true) == i)
				)) then
					if evt[2] == 1 then
						-- dragging a window
						if instances[i].manipMode == 0 then
							instances[i].manipMode = 1
							isManipulatingInstance = true
							instances[i].dragging = {instances[i].mainWindow.meta.x - evt[3], instances[i].mainWindow.meta.y - evt[4]}
						end
					elseif evt[2] == 2 then
						-- resizing a window
						if instances[i].manipMode == 0 then
							instances[i].manipMode = 2
							isManipulatingInstance = true
							instances[i].oldTermPos = {
								instances[i].termWindow.meta.x,
								instances[i].termWindow.meta.y,
								instances[i].termWindow.meta.width,
								instances[i].termWindow.meta.height
							}
							instances[i].oldMainPos = {
								instances[i].mainWindow.meta.x,
								instances[i].mainWindow.meta.y,
								instances[i].mainWindow.meta.width,
								instances[i].mainWindow.meta.height
							}
							if evt[3] > (instances[i].mainWindow.meta.x + (instances[i].mainWindow.meta.width) - 1) - 2 then
								instances[i].resizingRight = (instances[i].mainWindow.meta.x + instances[i].mainWindow.meta.width - 1) - evt[3]
							elseif evt[3] < (instances[i].mainWindow.meta.x + 2) then
								instances[i].resizingLeft = (instances[i].mainWindow.meta.x - 1) - evt[3]
							end
							if evt[4] > (instances[i].mainWindow.meta.y + (instances[i].mainWindow.meta.height) - 1) - 2 then
								instances[i].resizingBottom = (instances[i].mainWindow.meta.y + instances[i].mainWindow.meta.height - 1) - evt[4]
							elseif evt[4] < (instances[i].mainWindow.meta.y + 2) then
								instances[i].resizingTop = (instances[i].mainWindow.meta.y - 1) - evt[4]
							end
						end
					end
				else
					if evt[1] == "mouse_up" then
						if instances[i].manipMode == 2 then
							instances[i].mainWindow.clear()
							instances[i].termWindow.reposition(2, 2, instances[i].mainWindow.meta.width - 2, instances[i].mainWindow.meta.height - 2)
							instances[i].termWindow.redraw(nil, nil, nil, {force = true})
						end
						instances[i].manipMode = 0
						isManipulatingInstance = false
						instances[i].dragging = nil
						instances[i].resizingRight = nil
						instances[i].resizingLeft = nil
						instances[i].resizingBottom = nil
						instances[i].resizingTop = nil
					elseif evt[1] == "mouse_drag" then
						if isManipulatingInstance then
							if instances[i].manipMode == 1 then
								moveInstance(
									i,
									instances[i].dragging[1] + evt[3],
									instances[i].dragging[2] + evt[4]
								)
							elseif instances[i].manipMode == 2 then
								local newX, newY = instances[i].mainWindow.meta.x, instances[i].mainWindow.meta.y
								local oriX, oriY = instances[i].mainWindow.meta.x, instances[i].mainWindow.meta.y
								local newWidth, newHeight = instances[i].mainWindow.meta.width, instances[i].mainWindow.meta.height
								local oriWidth, oriHeight = instances[i].mainWindow.meta.width, instances[i].mainWindow.meta.height
								if instances[i].resizingRight then
									newWidth = instances[i].resizingRight + (evt[3] - instances[i].oldMainPos[1] + 1)
								end
								if instances[i].resizingLeft then
									newX = instances[i].resizingLeft + evt[3] + 1
									newWidth = instances[i].oldMainPos[3] + (instances[i].oldMainPos[1] - newX)
								end
								if instances[i].resizingBottom then
									newHeight = instances[i].resizingBottom + (evt[4] - instances[i].oldMainPos[2] + 1)
								end
								if instances[i].resizingTop then
									newY = instances[i].resizingTop + evt[4] + 1
									newHeight = instances[i].oldMainPos[4] + (instances[i].oldMainPos[2] - newY)
								end
								moveInstance(
									i,
									newX,
									newY,
									newWidth,
									newHeight
								)
								if newWidth ~= oriWidth or newHeight ~= oriHeight then
									resumeInstance(i, {"term_resize"}, false)
								end
							end
						end
					end
					if (instances[i].cFilter == evt[1] or instances[i].cFilter == "terminate" or (not instances[i].cFilter)) then
						if (instances[i].focused or not FocusEvents[evt[1]]) then
							resumeInstance(i, evt, CoordinateEvents[evt[1]])
							if CoordinateEvents[evt[1]] then
								usedCoordinateEvent = true
							end
						end
					end
				end
				instances[i].writeTitleBar()
			end
		end

		-- check for dead instances
		for i = #instances, 1, -1 do
			if not instances[i].alive then
				if instances[i].focused then
					if instances[i - 1] then
						instances[i - 1].focused = true
					elseif instances[i + 1] then
						instances[i + 1].focused = true
					end
				end
				table.remove(instances, i)
				desktop.clear()
			end
		end

		-- make sure the focused window is on top
		for i = 1, #instances do
			if instances[i].focused then
				instances[i], instances[1] = instances[1], instances[i]
				break
			end
		end
	end
end

main()
