-- Developed by MAHN.quifi, 2010
local unused = "unused\024\0\0\0\0\0"

local print = print
local pairs = pairs
local tostring = tostring
local string = string
local type = type
local setmetatable = setmetatable
local getmetatable = getmetatable
local stdout = io.stdout
local stringrep = string.rep
local setfenv = setfenv
local assert = assert
local rawget = rawget
local pcall = pcall
local loadstring = loadstring
local loadfile = loadfile
local tableconcat = table.concat
local luad = luad
local ioopen = io.open

module "malut"
local here = _M
if luad then
	luad.register ("malut", here)
end


--
-- Pure Data Table
--
local DT_mark = {__copytype="all"}
DT_mark.__index = DT_mark
function DT(t)
	setmetatable(t, DT_mark)
	for _, v in pairs(t) do
		if type(v) == "table" then
			DT(v)
		end
	end
	return t
end

function isDT(t)
	return  t.__copytype=="all" and rawget(t,"__copytype")==nil
end

--
-- function printTable
--
local printTable_tablelist = {}

local function printTable_( t, level, maxLevel, name )
	printTable_tablelist[t] = name
	
	for k,v in pairs(t) do
		if( type(v)=="table" ) then
			local DT_str = isDT(v) and "(DT)" or ""
			
			if printTable_tablelist[v] then
				stdout:write( stringrep(' ',level*2), tostring(k), " = ", tostring(v), DT_str, " (", printTable_tablelist[v], ")" )
				print()
			elseif maxLevel and level+1 >= maxLevel then
				stdout:write( stringrep(' ',level*2), tostring(k), " = ", tostring(v), DT_str, " (...)" )
				print()
			else
				stdout:write( stringrep(' ',level*2), tostring(k), " = ", tostring(v), DT_str )
				print()
				printTable_( v, level + 1, maxLevel, name.."."..tostring(k) )
			end
		else
			if v == unused then
				stdout:write( stringrep(' ',level*2), tostring(k), " = unused" )
				print()
			elseif type(v)=="string" then
				stdout:write( stringrep(' ',level*2), tostring(k), ' = "', v, '"' )
				print()
			else
				stdout:write( stringrep(' ',level*2), tostring(k), " = ", tostring(v) )
				print()
			end
		end
	end
end

function printTable( t, maxLevel, name )
	if type(t) ~= "table" then
		if t == unused then
			stdout:write( "unused" )
			print()
		elseif type(t)=="string" then
			stdout:write( '"', t, '"' )
			print()
		else
			stdout:write( tostring(t) )
			print()
		end
	else
		if name then
			print( name.." =" )
		end
		local DT_str = isDT(t) and "(DT)" or ""
		stdout:write( "[", tostring(t), DT_str, "]" )
		print()
		printTable_( t, 0, maxLevel, type(name)=="string" and name or "t" )
		printTable_tablelist = {}
	end
end


--
--[[ function copy
	first check src.__copy, if it is a function, use it
	else if it is a table, if __copy[key] == true then copy src[key]
	if src[key].__copytype == "all", copy it
--]]
local emptyTable = {}
local function copy (dst, src)
	local __copy = rawget(src, "__copy")
	if type(__copy) == "function" then
		return __copy( dst, src )
	end

	if type(__copy) ~= "table" then
		__copy = emptyTable
	end
	
	for k,v in pairs(src) do
		local copyFlag = __copy[k]		--true: copy table, false: copy none, nil: copy ref
		if copyFlag ~= false then
			if type(v) == "table" and v.__copytype ~= "ref" then
				if copyFlag == true or v.__copytype == "all" then
					local newT = {}
					local metaT = getmetatable(v)
					if metaT ~= nil then
						if metaT == src then
							setmetatable(newT, newT)
						else
							setmetatable(newT, metaT)
						end
					end
					dst[k] = copy( newT, v )
				else
					dst[k] = v
				end
			else
				dst[k] = v
			end
		end
	end
	
	return dst
end
here.copy = copy

-- function clone, copy and add meta table
local function clone (t)
	local newT = {}
	local metaT = getmetatable(t)
	if metaT ~= nil then
		if metaT == src then
			setmetatable(newT, newT)
		else
			setmetatable(newT, metaT)
		end
	end
	return copy( newT, t )
end
here.clone = clone

-- copy without overwrite
local emptyTable = {}
local function copynew (dst, src)
	local retFlag = true
	local __copy = rawget(src, "__copy")

	if type(__copy) ~= "table" then
		__copy = emptyTable
	end
	
	for k,v in pairs(src) do
		local copyFlag = __copy[k]		--true: copy table, false: copy none, nil: copy ref
		if copyFlag ~= false then
			if type(v) == "table" and v.__copytype ~= "ref" then
				local dstV = dst[k]
				if dstV == nil then
					if copyFlag == true or v.__copytype == "all" then
						local newT = {}
						local metaT = getmetatable(v)
						if metaT ~= nil then
							if metaT == src then
								setmetatable(newT, newT)
							else
								setmetatable(newT, metaT)
							end
						end
						dst[k] = copy( newT, v )
					else
						dst[k] = v
					end
				elseif type(dstV)=="table" and (copyFlag == true or v.__copytype == "all") then
					local ret = copynew( dstV, v )
					retFlag = retFlag and ret
				end
			elseif dst[k] == nil then
				dst[k] = v
			else
				retFlag = false
			end
		end
	end
	
	return retFlag
end
here.copynew = copynew


--
-- Object Orient
--

local function baseof (proto)
	return proto.__base
end
here.baseof = baseof

local function protoof (obj)
	local __base = getmetatable(obj)
	return type(__base)=="table" and __base.__proto or nil
end
here.protoof = protoof

Object = 
{
	-- class part
	__base =
	{
		clone = function (proto)	-- copy both class part and instance part
			assert( proto.__base and proto.__base.__proto == proto, "Param must be a prototype" )
			local newProto = {}
			
			local newProto = clone( proto )
			newProto.__base = clone( proto.__base )
			
			newProto.__base.__index = newProto.__base
			newProto.__base.__proto = newProto
			
			return setmetatable( newProto, newProto.__base )
		end;
		
		new = function (proto)		-- copy instance part
			assert( proto.__base and proto.__base.__proto == proto, "Param must be a prototype" )
			local obj = clone( proto )
			obj.__base = nil
			
			return setmetatable( obj, proto.__base )
		end;
		
		addTable = function (proto, name, t )		-- add to instance part
			local __copy = rawget(proto, "__copy" )
			if __copy == nil then
				__copy = DT{[name] = true}
				proto.__copy = __copy
			elseif type(__copy) == "table" then
				__copy[name] = true
			end
			proto[name] = t
		end;
		
		addTableToBase = function (proto, name, t )		-- add to class part
			local __base = proto.__base
			local __copy = __base.__copy
			if __copy == nil then
				__copy = DT{[name] = true}
				__base.__copy = __copy
			elseif type(__copy) == "table" then
				__copy[name] = true
			end
			__base[name] = t
		end;
	};
	
	-- instance part
}

setmetatable( Object, Object.__base )
Object.__base.__index = Object.__base
Object.__base.__proto = Object

--
-- class QuList, quick queue like list
-- number can not be added
--

do
	local super = Object
	local proto = super:clone() local Class = baseof(proto)
	QuList = proto
	
	proto.head = 1
	proto.rear = 0
	proto.size_ = 0
		
	function Class:add (obj)
		if obj == nil or type(obj)=="number" then
			error( "Number or Nil can not be added", 2 )
		end
		
		local rear = self.rear
		self[rear+1] = obj
		self.rear = rear + 1
		self.size_ = self.size_ + 1
		
		return rear+1
	end

	function Class:size ()
		return self.size_
	end

	function Class:erase (index)
		local cur = self[index]
		if cur == nil or type(cur) == "number" then
			error( "No item at the given index", 2 )
		end
		
		local rear = self.rear
		local head = self.head
		
		if index == head then
			if index == rear then
				self[index] = nil		-- erase cur
				
				self.head = 1
				self.rear = 0
				self.size_ = 0
			else
				self[index] = nil		-- erase cur
				self.size_ = self.size_-1
				
				local next = self[index+1]
				
				if type(next) == "number" then	-- next is index
					self[index+1] = nil			-- erase next index
					self[index+next-1] = nil	-- erase prev index of next item
					self.head = index+next
				else							-- next is valid
					self.head = index + 1
				end
			end
		else	-- index ~= head
			self.size_ = self.size_-1
			
			if index == rear then
				self[index] = nil		-- erase cur
				
				local prev = self[index-1]
				if type(prev) == "number" then	-- prev is index
					self[index-1] = nil			-- erase prev index
					self[index-prev+1] = nil	-- erase next index of prev item
					self.rear = index+prev
				else							-- prev is valid
					self.rear = index - 1
				end
			else	-- index is not head or rear
				local prev = self[index-1]
				local next = self[index+1]
				
				if type(next) == "number" then
					if next ~= 2 then
						self[index+1] = nil
					end
					
					if type(prev) == "number" then
						self[index] = nil
						if prev ~= 2 then
							self[index-1] = nil
						end
						
						self[index+next-1] = self[index+next-1] + prev
						self[index-prev+1] = self[index-prev+1] + next
					else
						self[index+next-1] = self[index+next-1] + 1
						self[index] = next + 1
					end
				else	-- next is valid
					if type(prev) == "number" then
						if prev ~= 2 then
							self[index-1] = nil
						end
						
						self[index-prev+1] = self[index-prev+1] + 1
						self[index] = prev + 1
					else
						self[index] = 2
					end					
				end
			end
		end
	end

	function Class:eraseAll ()
		for k,v in pairs(self) do
			if type(k) == "number" then
				self[k] = nil
			end
		end
		
		self.head = 1
		self.rear = 0
		self.size_ = 0
	end

	-- for it, val in qulist:iter() do
	function Class:iter ()
		local index = self.head
		
		return function ()
			local oldIndex = index
			if self[oldIndex] == nil then
				return nil
			end
			
			local next = self[index+1]
			if type(next) == "number" then
				index = index + next
			else
				index = index + 1
			end
			
			return oldIndex, self[oldIndex]
		end
	end

	function Class:riter ()
		local index = self.rear
		
		return function ()
			local oldIndex = index
			if self[oldIndex] == nil then
				return nil
			end
			
			local prev = self[index-1]
			if type(prev) == "number" then
				index = index - prev
			else
				index = index - 1
			end
			
			return oldIndex, self[oldIndex]
		end
	end
end


--
-- class Stack, a basic stack
--
do
	local super = Object
	local proto = super:clone() local Class = baseof(proto)
	Stack = proto

	function Class:push (obj)
		self[#self+1] = obj
	end

	function Class:pop (n)
		n = n or 1
		for i = 1, n  do
			self[#self - i + 1] = nil
		end
	end

	function Class:back ()
		return self[#self]
	end

	function Class:size ()
		return #self
	end

	function Class:empty ()
		return #self == 0
	end

	function Class:clear ()
		for i = 1, #self  do
			self[i] = nil
		end
	end
end


--
-- function toCode, convert table to code, return codeStr, errMsg
--
local toCode_codes = {}
local toCode_tables = {}

function toCode_ (t, level)		-- recursion function
	toCode_tables[t] = true
	
	local indent = string.rep(' ',level*3)
	local indent2 = string.rep(' ',(level+1)*3)
	
	toCode_codes[#toCode_codes+1] = indent
	toCode_codes[#toCode_codes+1] = "{\n"
		
	for k,v in pairs(t) do
		toCode_codes[#toCode_codes+1] = indent2
		
		local typeK = type(k)

		if typeK == "number" or typeK == "boolean" then
			toCode_codes[#toCode_codes+1] = "["
			toCode_codes[#toCode_codes+1] = tostring(k)
			toCode_codes[#toCode_codes+1] = "] = "
		elseif typeK == "string" then
			toCode_codes[#toCode_codes+1] = string.format( "[%q] = ", k )
		else
			error( "unsupported key type: "..typeK..", for "..tostring(k), 2 )
		end
		
		local typeV = type(v)
		if typeV == "number" or typeV == "boolean" then
			toCode_codes[#toCode_codes+1] = tostring(v)
			toCode_codes[#toCode_codes+1] = ";\n"
		elseif typeV == "string" then
			toCode_codes[#toCode_codes+1] = string.format( "%q;\n", v )
		elseif typeV == "table" then
			if toCode_tables[v] then
				error( "table has ring: "..tostring(v), 2 )
			end
			
			toCode_codes[#toCode_codes+1] = "\n"
			toCode_( v, level + 1 )
			toCode_codes[#toCode_codes+1] = ";\n"
		else
			error( "unsupported value type: "..typeV..", for "..tostring(v), 2 )
		end
		
	end
	
	toCode_codes[#toCode_codes+1] = indent
	toCode_codes[#toCode_codes+1] = "}"
	
	return true
end

function toCode (t)
	toCode_(t, 0)

	local result = tableconcat(toCode_codes)
	toCode_codes = {}
	toCode_tables = {}
	
	return result
end

function toCodeToFile (t, fileName)
	local fout, err = ioopen(fileName, "w")
	if fout then
		fout:write("return\n", toCode(t))
		fout:close()
		return true
	else
		return nil, err
	end
end

function fromCode (str)
	local func, err = loadstring(str)
	if func then
		local s, ret = pcall( setfenv( func, {} ) )
		if s then
			return ret
		else
			return nil, ret
		end
	else
		return nil, err
	end
end

function fromCodeInFile (fileName)
	local func, err = loadfile(fileName)
	if func then
		local s, ret = pcall( setfenv( func, {} ) )
		if s then
			return ret
		else
			return nil, ret
		end
	else
		return nil, err
	end
end

