

-- htmlDoc -------------------------------------------------------------


local html_colors =
{
	["black"] = 0,
	["0"] = 0,
	["orange"] = 1,
	["1"] = 1,
	["magenta"] = 2,
	["2"] = 2,
	["sky"] = 3,
	["3"] = 3,
	["yellow"] = 4,
	["4"] = 4,
	["pink"] = 5,
	["5"] = 5,
	["cyan"] = 6,
	["6"] = 6,
	["gray"] = 7,
	["7"] = 7,
	["silver"] = 8,
	["8"] = 8,
	["red"] = 9,
	["9"] = 9,
	["green"] = 10,
	["10"] = 10,
	["blue"] = 11,
	["11"] = 11,
	["brown"] = 12,
	["12"] = 12,
	["lime"] = 13,
	["13"] = 13,
	["purple"] = 14,
	["14"] = 14,
	["white"] = 15,
	["15"] = 15
}


local html_color_names =
{
	["0"] = "black",
	["1"] = "orange",
	["2"] = "magenta",
	["3"] = "sky",
	["4"] = "yellow",
	["5"] = "pink",
	["6"] = "cyan",
	["7"] = "gray",
	["8"] = "silver",
	["9"] = "red",
	["10"] = "green",
	["11"] = "blue",
	["12"] = "brown",
	["13"] = "lime",
	["14"] = "purple",
	["15"] = "white",
}


htmlDoc = win.__classBase:base ()


function htmlDoc:constructor (doc)
	self.html__type = "text"
	self:parse (doc)

	return self
end


function htmlDoc:color (color)
	return html_colors[tostring (color or "none")]
end


function htmlDoc:color_name (color)
	return html_color_names[tostring (htmlDoc:color (color) or "none")]
end


function htmlDoc:type ()
	return self.html__type
end


function htmlDoc:is_html (doc)
	if type (doc) == "table" then
		if type (doc.html) == "table" then
			return doc
		end
	end

	local success, new_doc
	local raw = tostring (doc)
	if string.trim_left (raw):sub (1, 6) ~= "return" then
		raw = "return "..raw

		success, new_doc = pcall (utils.deserialize, raw)

		if success and type (new_doc) == "table" then
			if type (new_doc.html) == "table" then
				return new_doc
			end
		end

		raw = tostring (doc)
	end

	success, new_doc = pcall (utils.deserialize, raw)

	if success and type (new_doc) == "table" then
		if type (new_doc.html) == "table" then
			return new_doc
		end
	end

	return nil
end


function htmlDoc:parse_node (node, def_tag)
	if type (node) == "table" then
		local tag = { }

		for k, v in pairs (node) do
			if type (k) == "string" then
				if type (v) == "string" or type (v) == "number" then
					if k == "color" or k == "bgcolor" or k == "linkcolor" then
						tag[k] = htmlDoc:color (v)
					elseif k == "width" then
						tag[k] = tonumber (v) or 0
					else
						if k == "protocol" then
							v = tostring (v)

							if v:sub (-1, -1) ~= ":" then
								v = v ..":"
							end
						end

						tag[k] = v
					end
				end
			end
		end

		if def_tag and not tag.tag then
			tag.tag = def_tag
		end

		for i = 1, #node, 1 do
			tag[#tag + 1] = self:parse_node (node[i])
		end

		for i = 1, #tag, 1 do
			if type (tag[i]) == "string" then
				while (i + 1) <= #tag and type (tag[i + 1]) == "string" do
					tag[i] = tag[i]..tag[i + 1]
					table.remove (tag, i + 1)
				end
			end
		end

		return tag
	end

	return tostring (node or "")
end


function htmlDoc:parse (content)
	local doc = htmlDoc:is_html (content)
	self.html =
	{
		head = { },
		body = { tostring (content or "") }
	}

	self.html__type = "text"

	if doc then
		self.html__type = "html"

		if type (doc.html.head) == "table" then
			self.html.head = self:parse_node (doc.html.head)
		end

		if type (doc.html.body) == "table" then
			self.html.body = self:parse_node (doc.html.body)
		end

		return
	end
end



local function fix_string_for_file (str)
	return "\""..str:gsub ("\r", "\\r"):gsub ("\n", "\\n"):gsub ("\"", "\\\"").."\""
end


function htmlDoc:serialize_node (node, indent)
	local did_break = false
	local str = "{"

	if node.tag then
		str = string.format("%s tag = %s,", str, fix_string_for_file (node.tag))
	end

	for k, v in pairs (node) do
		if type (k) == "string" and k ~= "tag"then
			if k == "color" or k == "bgcolor" or k == "linkcolor" then
				v = htmlDoc:color_name (v)
			end

			if type (v) == "number" then
				str = string.format ("%s %s = %s,", str, k, tostring (v))
			elseif type(v) == "string" then
				str = string.format ("%s %s = %s,", str, k, fix_string_for_file (v))
			end
		end
	end

	if node.tag == "p" and #node > 0 then
		str = str.."\n "..indent
		did_break = true
	end

	for i = 1, #node, 1 do
		if type (node[i]) == "table" then
			if node[i].tag == "p" then
				str = string.format ("%s\n  %s%s", str, indent,
											self:serialize_node (node[i], indent.."  "))
				did_break = true
			else
				str = str.." "..self:serialize_node (node[i], indent.."  ")
			end
		elseif type (node[i]) == "string" then
			str = string.format ("%s %s,", str, fix_string_for_file (node[i]))
		end
	end

	if did_break then
		return str:sub (1, -2).."\n"..indent.."},"
	end

	return str:sub (1, -2).." },"
end


function htmlDoc:get_html ()
	return string.format ("return {\n  html = {\n    head = %s\n    body = %s\n  }\n}",
								 self:serialize_node (self.html.head, "    "),
								 self:serialize_node (self.html.body, "    "))
end


local function html_node_to_text (node)
	local str = ""

	for i = 1, #node, 1 do
		if type (node[i]) == "table" then
			str = str..html_node_to_text (node[i])
		elseif type (node[i]) == "string" then
			str = str..node[i]
		end
	end

	if node.tag == "p" then
		str = str.."\n\n"
	end

	return str
end


function htmlDoc:get_text ()
	if self:type () == "text" then
		return self.html.body[1]
	end

	return html_node_to_text (self.html.body)
end


function htmlDoc:serialize ()
	if self:type () == "text" then
		return self:get_text ()
	end

	return self:get_html ()
end


function htmlDoc:get_width ()
	if type(self.html) == "table"  then
		if type(self.html.body) == "table"  then
			if self.html.body.width  then
				return tonumber (self.html.body.width) or 0
			end
		end
	end

	return 0
end

-- end htmlDoc ---------------------------------------------------------



-- htmlMap -------------------------------------------------------

local htmlMap = win.__classBase:base ()


function htmlMap:constructor (width, color, bgcolor)
	self.line = 0
	self.column = 0
	self.width = width
	self.color = color
	self.bgcolor = bgcolor

	return self
end


function htmlMap:add (color, bgcolor, align, erase, node, text)
	local current, new_text, new_line, mid_word
	text = tostring (text or "")

	repeat
		current, new_text, new_line, mid_word = string.splice (text, self.width - self.column)

		if mid_word and self.column > 0 then
			self:advance (color, bgcolor, align, erase, node)

			current, new_text, new_line, mid_word = string.splice (text, self.width)
		end

		text = new_text

		if current:len () > 0 then
				self[#self + 1] =
				{
					node = node,
					color = color,
					bgcolor = bgcolor,
					align = align,
					erase = erase or bgcolor,
					line = self.line,
					column = self.column,
					text = current
				}

				self.column = self.column + current:len ()
		end

		if new_line then
			self:advance (color, bgcolor, align, erase, node)
		end
	until not text
end


function htmlMap:last_line  ()
	if #self > 0 then
		return self[#self].line
	end

	return -1
end


function htmlMap:lines ()
	return self:last_line () + 1
end


function htmlMap:advance (color, bgcolor, align, erase, node)
	if self:last_line () < self.line then
		self[#self + 1] =
		{
			node = node,
			color = color,
			bgcolor = bgcolor,
			align = align,
			erase = erase or bgcolor,
			line = self.line,
			column = self.column,
			text = ""
		}
	end

	self.line = self.line + 1
	self.column = 0
end


function htmlMap:start_block (color, bgcolor, align, erase, node)
	if self.column > 0 then
		self:advance (color, bgcolor, align, erase, node)
	end
end


function htmlMap:set_alignment ()
	local first = 1

	while first <= #self do
		if self[first].align == "right" or self[first].align == "center" then
			local lead = self.width
			local last = first

			for i = first + 1, #self, 1 do
				if self[i].line == self[first].line then
					last = i
				else
					break
				end
			end

			for i = first, last, 1 do
				lead = lead - self[i].text:len ()
			end

			if self[first].align == "center" then
				lead = math.modf (lead / 2)
			end

			for i = first, last, 1 do
				self[i].column = self[i].column + lead
			end

			first = last
		end

		first = first + 1
	end
end


function htmlMap:get_range (first_line, last_line)
	local first_map
	local last_map

	last_line = last_line or first_line

	for i = 1, #self, 1 do
		if self[i].line >= first_line then
			first_map = i

			break
		end
	end

	if first_map then
		if self[first_map].line > last_line then
			first_map = nil
		else
			for i = first_map + 1, #self, 1 do
				if self[i].line > last_line then
					last_map = i - 1

					break
				end
			end

			if not last_map then
				last_map = #self
			end
		end
	end

	return first_map, last_map
end


function htmlMap:node_from_point (x, y)
	local first, last = self:get_range (y)

	if first then
		for i = first, last, 1 do
			if x >= self[i].column and x < (self[i].column + self[i].text:len ()) then
				return self[i].node
			end
		end
	end

	return nil
end


function htmlMap:get_width ()
	return self.width
end

-- end htmlMap ---------------------------------------------------------



-- browserWindow -------------------------------------------------------

browserWindow = win.window:base ()


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

	self.brws__doc = nil
	self.brws__map = nil

	self:set_want_focus (false)

	self:set_text ()

	return self
end


function browserWindow:get_title (title)
	if self.brws__doc.html.head.title then
		return tostring (self.brws__doc.html.head.title)
	end

	return title
end


function browserWindow:map_node (map, node, color, bgcolor, linkcolor, align, erase)
	if node.tag == "a" then
		color = node.color or linkcolor
	else
		color = node.color or color
	end

	bgcolor = node.bgcolor or bgcolor

	if node.tag == "l" then
		local lwidth = math.max (math.modf ((map.width / 100) *
								math.min (math.abs (node.width or 100), 100)), 1)
		local lchar = node.char or "-"
		align = node.align or align

		if lchar:len () < 1 then
			lchar = "-"
		else
			lchar = lchar:sub (1, 1)
		end

		map:start_block (color, bgcolor, align, erase, node)
		map:add (color, bgcolor, align, erase, node, string.rep (lchar, lwidth))
		map:start_block (color, bgcolor, align, erase, node)

		return
	end


	if node.tag == "p" or node.tag == "d" then
		align = node.align or align

		if node.bgcolor then
			erase = node.bgcolor
		end

		map:start_block (color, bgcolor, align, erase, node)
	end

	for i = 1, #node, 1 do
		if type (node[i]) == "table" then
			self:map_node (map, node[i], color, bgcolor, linkcolor, align, erase)

			if node[i].tag == "p" then
				map:start_block (color, bgcolor, align, erase, node)
				map:advance (color, bgcolor, align, erase, node)
			elseif node[i].tag == "d" then
				map:start_block (color, bgcolor, align, erase, node)
			end

		elseif type (node[i]) == "string" then
			map:add (color, bgcolor, align, erase, node, node[i])
		end
	end
end


function browserWindow:map (width, body, color, bgcolor, linkcolor, align)
	local map = htmlMap:new (width, color, bgcolor)--, align)

	if width >= 1 then
		self:map_node (map, body, color, bgcolor, linkcolor, align, bgcolor)
	end

	map:set_alignment ()

	return map
end


function browserWindow:get_map (width)
	return self:map (width, self.brws__doc.html.body,
						  (self.brws__doc.html.body.color or self:get_colors ().wnd_text),
						  (self.brws__doc.html.body.bgcolor or self:get_colors ().wnd_back),
						  (self.brws__doc.html.body.linkcolor or term.colors.blue),
						  (self.brws__doc.html.body.align or "left"))
end


function browserWindow:remap ()
	local width = self.brws__doc:get_width ()

	if width >= self.width then
		self.brws__map = self:get_map (width)

		self:set_scroll_size (width, self.brws__map:last_line () + 1)

		return
	end

	self.brws__map = self:get_map (self.width)

	if self.brws__map:last_line () >= self.height then
		self.brws__map = self:get_map (self.width - 1)
	end

	self:set_scroll_size (0, self.brws__map:last_line () + 1)
end


function browserWindow:lines ()
	return self.brws__map:lines ()
end


function browserWindow:node_from_point (x, y)
	return self.brws__map:node_from_point (x, y)
end


function browserWindow:get_html ()
	return self.brws__doc:get_html ()
end


function browserWindow:get_text ()
	return self.brws__doc:get_text ()
end


function browserWindow:serialize ()
	return self.brws__doc:serialize ()
end


function browserWindow:doc_type ()
	return self.brws__doc:type ()
end


function browserWindow:merge_address (link, ref, protocol)
	local address;

	link = tostring (link or "")
	ref = tostring (ref or "")

	if link:find (":", 1) then
		address = link
	else
		local links = { }
		local current = { }

		if ref:sub (1, 5) == "file:" or ref:sub (1, 1) == "/" then
			address = "file:"
		elseif protocol then
			address = protocol
		else
			address = "http:"
		end

		if ref:find (":", 1) then
			ref = ref:sub (ref:find (":", 1) + 1)
		end

		for part in link:gmatch ("[^%/]+") do
			links[#links + 1] = part
		end

		if links[1] == "" then
			table.remove (links, 1)
		end

		for part in ref:gmatch ("[^%/]+") do
			current[#current + 1] = part
		end

		if current[1] == "" then
			table.remove (current, 1)
		end

		if #current > 0 then
			table.remove (current, #current)
		end

		while links[1] == ".." do
			if #current > 0 then
				table.remove (current, #current)
			end

			table.remove (links, 1)
		end

		if #current > 0 then
			if address == "file:" then
				address = address.."/"
			end

			address = address..current[1]

			for i = 2, #current, 1 do
				address = address.."/"..current[i]
			end
		end

		for i = 1, #links, 1 do
			address = address.."/"..links[i]
		end
	end

	return address
end


function browserWindow:split_address (address)
	local path, domain, protocol;

	address = tostring (address or "")

	if address:sub (1, 1) == "/" then
		protocol = "file"
		domain = ""
		path = address:sub (2)
	else
		local addr = ""

		if address:find (":", 1) then
			protocol = address:sub (1, address:find (":", 1) - 1)
			address = address:sub(address:find(":", 1, true) + 1)
		else
			protocol = "http"
		end

		if address:find ("/", 1) then
			domain = address:sub (1, address:find ("/", 1, true) - 1)
			path = address:sub(address:find ("/", 1, true) + 1)
		else
			domain = address
			path = ""
		end
	end

	return path, domain, protocol
end


function browserWindow:server_not_found (address)
	local path, domain, protocol = self:split_address (address)
	local fmt = "\n\nThe server \"%s\" was not found. It may be temporarily down or the address was entered incorrectly."
	self:set_text (string.format (fmt, domain))
end


function browserWindow:draw (gdi, bounds)
	local first, last =
		self.brws__map:get_range (self.wnd__scroll_y, self.wnd__scroll_y + self.height)

	gdi:set_colors (nil, self.brws__map.bgcolor)
	gdi:clear (0, 0, self:get_client_size ())

	if first then
		local erase_line = -1
		local erase = ""

		if self.width > 0 then
			erase = string.rep (" ", self.brws__map:get_width ())
		end

		for index = first, last, 1 do
			local map = self.brws__map[index]

			if map.erase and map.line ~= erase_line then
				gdi:set_colors (nil, map.erase)
				gdi:write (erase, 0, map.line)
				erase_line = map.line
			end

			if map.text:len () > 0 then
				gdi:set_colors (map.color, map.bgcolor)
				gdi:write (map.text, map.column, map.line)
			end
		end
	end
end


function browserWindow:on_move ()
	self:remap ()

	return false
end


function browserWindow:set_text (doc)
	self.brws__doc = htmlDoc:new (doc)
	self:remap ()
	self:set_color (self.brws__map.color)
	self:set_bg_color (self.brws__map.bgcolor)
	self:set_scroll_org (0, 0)
	self:invalidate ()
end


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

	local node = self:node_from_point (self:wnd_to_scroll (x, y))

	if type (node) == "table" then
		if node.tag == "a" and self:get_parent () then
			self:get_parent ():send_event ("link_click", self, node.href, node.protocol)
		end
	end

	return true
end


function browserWindow:on_touch (x, y)
	if win.window.on_touch (self, x, y) then
		return true
	end

	local node = self:node_from_point (self:wnd_to_scroll (x, y))

	if type(node) == "table" then
		if node.tag == "a" and self:get_parent () then
			self:get_parent ():send_event ("link_click", self, node.href, node.protocol)
		end
	end

	return true
end

-- end browserWindow ---------------------------------------------------
