
local app_args = {...}
local appFrame = win.create_app_frame ()


local APP_TITLE      = "command"

local ID_MENUBTN     = 101

local IDM_QUITAPP    = 1001



local function create_env (wnd, parent_env)

	env = { }

	setmetatable (env, { __index = parent_env })
	env._G = env

	env.term = { }
	env.term.colors = { }

	for k, v in pairs (parent_env.term.colors) do
		env.term.colors[k] = v
	end


	local _buffer = { }
	local _cx = 0
	local _cy = 0
	local _blink = true
	local _fg = term.colors.black
	local _bg = term.colors.black
	local _sx, _sy

	do
		local gdi = wnd:get_GDI ()

		if not gdi then
			return nil
		end

		_sx, _sy = gdi:get_size ()

		for y = 1, _sy, 1 do
			_buffer[y] = { }

			for x = 1, _sx, 1 do
				_buffer[y][x] = { char = 0, fg = term.colors.black, bg = term.colors.black }
			end
		end

		wnd:release_GDI ()
	end


	env.draw = function (gdi, bounds)
		local sx, sy = wnd:get_client_size ()
		local fg, bg = gdi:get_colors ()

		for y = 1, sy, 1 do
			for x = 1, sx, 1 do
				local c = _buffer[y][x]

				gdi:set_colors (c.fg, c.bg)
				gdi:write (string.char (c.char), x - 1, y - 1)
			end
		end

		gdi:set_colors (fg, bg)
	end


	env.resize = function (width, height)
		local sx, sy = wnd:get_client_size ()

		if _cy >= height then
			local lines = _cy - height + 1

			env.term.scroll (-lines)
			env.term.set_cursor (_cx, height - 1)
			env.term.clear (0, term.colors.black, term.colors.black, 0, height, width, lines)
		end

		if _cx >= width then
			env.term.set_cursor (width - 1, _cy)
		end
	end


	env.term.get_cursor = function ()
		return _cx, _cy
	end


	env.term.set_cursor = function (x, y)
		local csx, csy = wnd:get_client_size ()

		if x < 0 then
			x = 0
		elseif x >= csx then
			x = csx - 1
		end

		if y < 0 then
			y = 0
		elseif y >= csy then
			y = csy - 1
		end

		_cx = x
		_cy = y

		wnd:set_cursor_pos (x, y)
	end


	env.term.set_blink = function (blink)
		_blink = blink
		wnd:set_cursor_blink  (blink)
	end


	env.term.get_blink = function ()
		return _blink
	end


	env.term.get_resolution = function ()
		return wnd:get_client_size ()
	end


	env.term.get_colors = function ()
		return _fg, _bg
	end


	env.term.set_colors = function (fg, bg)
		_fg = fg or _fg
		_bg = bg or _bg
	end


	env.term.set_char = function (x, y, char, fg, bg)
		local csx, csy = wnd:get_client_size ()

		x = tonumber (x or -1)
		y = tonumber (y or -1)

		if x >= 0 and x < csx and y >= 0 and y < csy then
			local c = _buffer[y + 1][x + 1]

			if char ~= nil then
				c.char = char % 256
			end

			if fg ~= nil then
				c.fg = fg % 16
			end

			if bg ~= nil then
				c.bg = bg % 16
			end
		end

		wnd:invalidate ()
	end


	env.term.get_char = function (x, y)
		local csx, csy = wnd:get_client_size ()

		x = tonumber (x or -1)
		y = tonumber (y or -1)

		if x >= 0 and x < csx and y >= 0 and y < csy then
			local c = _buffer[y + 1][x + 1]

			return c.char, c.fg, c.bg
		end

		return nil, nil, nil
	end


	env.term.write = function (str, fg, bg)
		local csx, csy = wnd:get_client_size ()

		local x, y = _cx, _cy
		str = tostring (str or "")
		fg = fg or _fg
		bg = bg or _bg

		for i = 1, str:len (), 1 do
			if x >= csx then
				x = 0
				y = y + 1
			end

			if y < csy then
				_buffer[y + 1][x + 1] =
				{
					char = str:byte (i) or 0,
					fg = fg % 16,
					bg = bg % 16
				}

				x = x + 1
			else
				break
			end
		end

		wnd:invalidate ()
	end


	env.term.blit = function (buff, x, y, w, h)
		local csx, csy = wnd:get_client_size ()

		x = tonumber (x or 0) or 0
		y = tonumber (y or 0) or 0
		w = tonumber (w or csx) or csx
		h = tonumber (h or csy) or csy

		if w < 1 or h < 1 then
			return false, "no size"
		end

		if type (buff) ~= "table" then
			return false, "buffer not a table"
		end

		if #buff < (w * h) then
			return false, "buffer too small"
		end

		for cy = 0, h - 1 do
			for cx = 0, w - 1 do
				local sx = x + cx
				local sy = y + cy

				if sx >= 0 and sx < csx and sy >= 0 and sy < csy then
					local b = buff[(cy * w) + cx + 1]

					if type (b) == "table" then
						_buffer[sy + 1][sx + 1] =
						{
							char = (b.char or 0) % 256,
							fg = (b.fg or env.term.colors.white) % 16,
							bg = (b.bg or env.term.colors.black) % 16
						}
					end
				end
			end
		end

		wnd:invalidate ()

		return true
	end


	env.term.cache = function (x, y, w, h)
		local csx, csy = wnd:get_client_size ()

		local buff = { }
		x = tonumber (x or 0) or 0
		y = tonumber (y or 0) or 0
		w = tonumber (w or csx) or csx
		h = tonumber (h or csy) or csy

		if w < 1 or h < 1 then
			return nil
		end

		for cy = 0, h - 1 do
			for cx = 0, w - 1 do
				local sx = x + cx
				local sy = y + cy

				if sx >= 0 and sx < csx and sy >= 0 and sy < csy then
					local c = _buffer[sy + 1][sx + 1]

					buff[(cy * w) + cx + 1] =
					{
						char = c.char,
						fg = c.fg,
						bg = c.bg
					}
				else
					-- if not defined leave transparent
					buff[(cy * w) + cx + 1] = 0
				end
			end
		end

		return buff
	end


	env.term.clear = function (char, fg, bg, x, y, w, h)
		local csx, csy = wnd:get_client_size ()

		char = ((char or 0) % 256)
		fg = fg or _fg
		bg = bg or _bg
		x = tonumber (x or 0) or 0
		y = tonumber (y or 0) or 0
		w = tonumber (w or csx) or csx
		h = tonumber (h or csy) or csy

		for cy = 0, h - 1 do
			for cx = 0, w - 1 do
				local sx = x + cx
				local sy = y + cy

				if sx >= 0 and sx < csx and sy >= 0 and sy < csy then
					_buffer[sy + 1][sx + 1] =
					{
						char = char,
						fg = fg,
						bg = bg
					}
				end
			end
		end

		wnd:invalidate ()
	end


	env.term.scroll = function (lines, x, y, w, h)
		local csx, csy = wnd:get_client_size ()

		lines = tonumber (lines or 0) or 0
		x = tonumber (x or 0) or 0
		y = tonumber (y or 0) or 0
		w = tonumber (w or csx) or csx
		h = tonumber (h or csy) or csy

		if x < 0 then
			w = w + x
			x = 0
		end

		if y < 0 then
			h = h + y
			y = 0
		end

		if (x + w) > csx then
			w = csx - x
		end

		if (y + h) > csy then
			h = csy - y
		end

		if w > 0 and h > 0 then
			if lines > 0 then
				for cy = (h - 1), 0, -1 do
					for cx = 0, w - 1 do
						local mx = x + cx
						local ry = y + cy
						local wy = y + cy + lines

						if wy < csy then
							_buffer[wy + 1][mx + 1] =_buffer[ry + 1][mx + 1]
						end
					end
				end

			elseif lines < 0 then
				for cy = 0, h - 1 do
					for cx = 0, w - 1 do
						local mx = x + cx
						local ry = y + cy
						local wy = y + cy + lines

						if wy >= 0 then
							_buffer[wy + 1][mx + 1] =_buffer[ry + 1][mx + 1]
						end
					end
				end
			end

			wnd:invalidate ()
		end
	end


	env.term.print = function (fmt, ... )
		local result, str = pcall (string.format, fmt, ... )

		if not result then
			error (str, 2)
		end

		local csx, csy = wnd:get_client_size ()

		local x = _cx
		local y = _cy

		if x >= csx then
			x = 0
			y = y + 1
		end

		while y >= csy do
			env.term.scroll (-1)
			y = y - 1
			env.term.clear (0, env.term.colors.white, env.term.colors.black,
								 0, csy - 1, csx, 1)
		end

		for p = 1, str:len () do
			local char = str:byte (p)

			if char == 10 then
				x = 0
				y = y + 1
			elseif char == 13 then
				x = 0
			else
				_buffer[y + 1][x + 1] =
				{
					char = char,
					fg = _fg,
					bg = _bg
				}
				x = x + 1
			end

			if x >= _sx then
				x = 0
				y = y + 1
			end

			while y >= csy do
				env.term.scroll (-1)
				y = y - 1
				env.term.clear (0, env.term.colors.white, env.term.colors.black,
									 0, csy - 1, csx, 1)
			end
		end

		_cx = x
		_cy = y
		env.term.set_cursor (x, y)
		wnd:invalidate ()
	end


	env.term.redraw = function (force)
		wnd:update (force)
	end


	env.term.invalidate = function ()
		wnd:invalidate ()
	end


	env.print = env.term.print


	env.load = function (func, chunkname)
		local fxn, msg = parent_env.load (func, chunkname)

		if not fxn then
			return fxn, msg
		end

		setfenv (fxn, env)

		return fxn
	end


	env.loadstring = function (str, chunkname)
		local fxn, msg = parent_env.loadstring (str, chunkname)

		if not fxn then
			return fxn, msg
		end

		setfenv (fxn, env)

		return fxn
	end


	env.loadfile = function (filename)
		filename = tostring (filename or "")

		if filename:len () < 1 then
			return nil, "no path"
		end

		local file = parent_env.io.open (filename, "r")

		if not file then
			return nil, "no open"
		end

		local src = file:read ("*a")
		file:close ()

		if not src then
			return nil, "no read"
		end

		return env.loadstring (src, filename)
	end


	env.dofile = function (filename)
		local fxn, msg = env.loadfile (filename)

		if not fxn then
			env.error (msg)
		end

		return fxn ()
	end


	env.string = { }
	for key, value in pairs (parent_env.string) do
		env.string[key] = value
	end
	setmetatable (env.string, getmetatable (parent_env.string))


	env.table = { }
	for key, value in pairs (parent_env.table) do
		env.table[key] = value
	end
	setmetatable (env.table, getmetatable (parent_env.table))


	env.math = { }
	for key, value in pairs (parent_env.math) do
		env.math[key] = value
	end
	setmetatable (env.math, getmetatable (parent_env.math))


	env.vector = { }
	for key, value in pairs (parent_env.vector) do
		env.vector[key] = value
	end
	setmetatable (env.vector, getmetatable (parent_env.vector))


	env.io = { }
	for key, value in pairs (parent_env.io) do
		env.io[key] = value
	end
	setmetatable (env.io, getmetatable (parent_env.io))


	env.os = { }
	for key, value in pairs (parent_env.os) do
		env.os[key] = value
	end
	setmetatable (env.os, getmetatable (parent_env.os))

	env.os.environs = { }


	env.os.start_timer = function (secs)
		return wnd:start_timer (secs)
	end


	env.os.kill_timer = function (timer_id)
		return wnd:kill_timer (timer_id)
	end


	env.os.exit = function ()
		wnd:get_app_frame ():send_event ("cmd_exit")
	end


	env.os.sleep = function (secs)
		wnd.cw__sleep_timer = wnd:start_timer (secs)

		parent_env.os.sleep (secs)
	end


	env.security = { }
	for key, value in pairs (parent_env.security) do
		env.security[key] = value
	end
	setmetatable (env.security, getmetatable (parent_env.security))


	env.fs = { }
	for key, value in pairs (parent_env.fs) do
		env.fs[key] = value
	end
	setmetatable (env.fs, getmetatable (parent_env.fs))


	env.utils = { }
	for key, value in pairs (parent_env.utils) do
		env.utils[key] = value
	end
	setmetatable (env.utils, getmetatable (parent_env.utils))


	env.wireless = { }
	for key, value in pairs (parent_env.wireless) do
		env.wireless[key] = value
	end
	setmetatable (env.wireless, getmetatable (parent_env.wireless))


	env.http = { }
	for key, value in pairs (parent_env.http) do
		env.http[key] = value
	end
	setmetatable (env.http, getmetatable (parent_env.http))


	env.mesecons = { }
	for key, value in pairs (parent_env.mesecons) do
		env.mesecons[key] = value
	end
	setmetatable (env.mesecons, getmetatable (parent_env.mesecons))


	env.digilines = { }
	for key, value in pairs (parent_env.digilines) do
		env.digilines[key] = value
	end
	setmetatable (env.digilines, getmetatable (parent_env.digilines))


	env.wires = { }
	for key, value in pairs (parent_env.wires) do
		env.wires[key] = value
	end
	setmetatable (env.wires, getmetatable (parent_env.wires))


	env.printer = { }
	for key, value in pairs (parent_env.printer) do
		env.printer[key] = value
	end
	setmetatable (env.printer, getmetatable (parent_env.printer))


	env.monitor = { }
	for key, value in pairs (parent_env.monitor) do
		env.monitor[key] = value
	end
	setmetatable (env.monitor, getmetatable (parent_env.monitor))


	if type (parent_env.robot) == "table" then
		env.robot = { }
		for key, value in pairs (parent_env.robot) do
			env.robot[key] = value
		end
		setmetatable (env.robot, getmetatable (parent_env.robot))
	end


	env.keys = { }
	for key, value in pairs (parent_env.keys) do
		env.keys[key] = value
	end
	setmetatable (env.keys, getmetatable (parent_env.keys))


	return env
end



local function create_thread (env, path)
	local fxn, msg = loadfile (path)

	if not fxn then
		return nil, msg
	end

	setfenv (fxn, env)

	return coroutine.create (fxn)
end



-- cmdWnd class --------------------------------------------------------
local cmdWnd = win.window:base ()


function cmdWnd:constructor (parent, id, x, y, width, height)
	if not win.window.constructor (self, parent, id, x, y, width, height) then
		return nil
	end

	self:set_color (term.colors.white)
	self:set_bg_color (term.colors.black)
	self:set_want_key_input (win.KEYINPUT_EDIT)

	self.cw__sleep_timer = 0
	self.cw__event_filter = ""

	self:want_event ("*")

	local app = self:get_app_frame ()

	if not app then
		win.syslog ("cmdWnd:constructor () could not get app.")
		return nil
	end

	local path = fs.path_folder (app:get_app_path ()).."/cmd_los"

	if not fs.file_exists (path, false) then
		win.syslog ("cmdWnd:constructor () could not find "..tostring (path))
		return nil
	end

	self.cw__env = create_env (self, _G)

	if not self.cw__env then
		win.syslog ("cmdWnd:constructor () could not create environment.")
		return nil
	end

	self.cw__yield_point = "booting"

	self.cw__thread = create_thread (self.cw__env, path)

	if not self.cw__thread then
		win.syslog ("cmdWnd:constructor () could not create thread.")
		return nil
	end

	return self
end


function cmdWnd:status ()
	local status = coroutine.status (self.cw__thread)

	if status == "suspended" then
		return self.cw__yield_point
	end

	return status
end


function cmdWnd:resume ( ... )
	if self.cw__sleep_timer ~= 0 then
		return false
	end

	local status = self:status ()
	local params

	if status == "dead" then
		self.cw__yield_point = "dead"
		return false

	elseif status == "get_event" then
		local event = { ... }

		if type (event[1]) ~= "string" or event[1]:len () < 1 then
			return false
		end

		-- apply filter
		if self.cw__event_filter:len () > 0 then
			if event[1] ~= self.cw__event_filter then
				return false
			end
		end

		params = { coroutine.resume (self.cw__thread, ... ) }
	else
		params = { coroutine.resume (self.cw__thread, ... ) }
	end

	local success = table.remove (params, 1)

	self.cw__yield_point = params[1]

	if not success then
		local app = self:get_app_frame ()

		if app then
			app:msgbox ("Error",
							"Terminated with error ("..tostring (self.cw__yield_point)..").",
							term.colors.red)

			app:send_event ("cmd_exit")
		end
	end

	if success then
		if self.cw__yield_point == "get_event" then
			-- filter
			self.cw__event_filter = tostring (params[2] or "")

			return true
		else
			return params
		end
	end

	return false
end


function cmdWnd:startup ( ... )
	if self.cw__yield_point == "booting" then
		return self:resume ( ... )
	end

	return false
end


function cmdWnd:ready()
	local status = self:status ()

	return status ~= "booting" and status ~= "dead"
end


function cmdWnd:draw (gdi, bounds)
	if self.cw__env then
		self.cw__env.draw (gdi, bounds)
	end
end


function cmdWnd:resize (width, height)
	if self.cw__env then
		self.cw__env.resize (width, height)
	end
end


function cmdWnd:on_focus (blurred)
	self:show_cursor ()

	return false
end


function cmdWnd:pump_event ( ... )
	if self:status () ~= "booting" then
		local params = self:resume ( ... )

		if type (params) == "table" then
			while true do
				if #params < 1 then
					return false
				end

				params = self:resume (coroutine.yield (unpack (params)))

				if type (params) ~= "table" then
					break
				end
			end
		end

		return params
	end

	return false
end


function cmdWnd:on_key (key, ctrl, alt, shift)
	self:pump_event ("key", key, ctrl, alt, shift)

	return true
end


function cmdWnd:on_char (char, ascii)
	self:pump_event ("char", char, ascii)

	return true
end


function cmdWnd:on_left_click (x, y, count)
	if win.window.on_left_click (self, x, y, count) then
		return true
	end

	self:pump_event ("click", x, y, count)

	return true
end


function cmdWnd:on_clipboard (text)
	self:pump_event ("clipboard", text)

	return true
end


function cmdWnd:on_timer (timer_id)
	if timer_id == self.cw__sleep_timer then
		self.cw__sleep_timer = 0
		self:pump_event ()
	else
		self:pump_event ("timer", timer_id)
	end

	return false
end
-- end cmdWnd class ----------------------------------------------------





function appFrame:on_event (event, p1, p2, p3, p4, p5, ...)
	if event == "btn_click" then
		if p1:get_id () == ID_MENUBTN then
			self:on_menu ()
			return true
		end

	elseif event == "menu_cmd" then
		return self:on_command (p1)

	elseif event == "cmd_exit" then
		self.app__exit_timer = self:start_timer (0.1)
		return true

	elseif event == "cmd_error" then
		self:msg_box ("Error", p2, term.colors.red)
		return true

	end

	return false
end


function appFrame:on_timer (id)
	if id == self.app__startup_timer then
		self.app__cmd_wnd:startup (unpack (app_args))
		self.app__startup_timer = 0
	elseif id == self.app__exit_timer then
		self:quit_app ()
	end

	return false
end


function appFrame:on_menu ()
	local menu = win.menuWindow:new (self)

	menu:add_string ("Quit App   ",       IDM_QUITAPP)

	menu:track (0, 1)
end


function appFrame:on_command (cmd_id)
	if cmd_id == IDM_QUITAPP then
		self:quit_app ()
		return true
	end

	return false
end


function appFrame:on_move ()
	self.app__cmd_wnd:resize (self.width, self.height - 1)
	self.app__cmd_wnd:move (0, 1, self.width, self.height - 1)

	return false
end


function appFrame:on_child_key (wnd, key, ctrl, alt, shift)
	if key == keys.KEY_TAB then
		if not ctrl and not alt and not shift and wnd == self.app__cmd_wnd then
			return false
		end
	end

	return win.applicationFrame.on_child_key (self, wnd, key, ctrl, alt, shift)
end


function appFrame:on_create ()
	self:dress (APP_TITLE)

	local menuBtn = win.buttonWindow:new (self, ID_MENUBTN, 0, 0, "Menu")
	menuBtn:set_colors (menuBtn:get_colors ().frame_text,
							  menuBtn:get_colors ().title_back,
							  menuBtn:get_colors ().frame_back)
	menuBtn:move (nil, nil, nil, nil, win.WND_TOP)

	self.app__cmd_wnd = cmdWnd:new (self, 0, 0, 1, self.width, self.height - 1)

	if not self.app__cmd_wnd then
		return false
	end

	self.app__cmd_wnd:set_focus ()

	self.app__exit_timer = 0
	self.app__startup_timer = self:start_timer (0.1)

	self:set_active_top_frame ()

	return true
end


appFrame:run_app ()
