require "json"
module(..., package.seeall)

function DeepCopy (self, t)
	--LogDebug("[HELPERS.TABLE:DeepCopy()] --> t: %s", t)
	if type(t) == "table" then
		local result = {}
		for k, v in pairs (t) do
			result[k] = DeepCopy(self, v)
		end
		return result
	end
	return t
end

function Log (self, t)
	local log = ""
	if type(t) ~= "table" then
		return log
	end
	local i = 0
	for n, v in pairs(t) do
		i = i + 1
		local val = type(v) == "table" and json.encode(v) or v
		log = log .. LogDebug("[HELPERS.table.Log] %d - name: '%s', type: '%s', value: '%s'.", i, n, type(val), val)
	end
	return log
end

function IsArray (self, t)
	--LogDebug("[HELPERS.table.IsArray] t: '%s'.", tostring(t))
	if type(t) ~= "table" then
		return false
	end
	return #t > 0
end

function JSON_Decode (self, value)
	--LogDebug("[HELPERS.table.JSON_Decode] value: '%s'.", tostring(value))
	local ok, result = pcall(function ()
		local result = json.decode(value)
		if type (result) == "table" then
			return result
		else
			return value
		end
	end)
	return ok and result or value
end

function JSON_Encode (self, value)
	--LogDebug("[HELPERS.table.JSON_Encode] value: '%s'.", tostring(value))
	local ok, result = pcall(function (param)
		local luaJson = require "json"
		local resultJson = luaJson.encode(param)
		if type (resultJson) == "string" then
			return resultJson
		else
			return param
		end
	end, value)
	if ok and result then
		--LogDebug("[HELPERS.table.JSON_Encode] 1 - Encode with 'LUA json' result '%s'", result)
		if result:lower() == "null" then
			return ""
		end
		return result
	end

	ok, result = pcall(function (param)
		local cjson = require "cjson"
		local resultJson = cjson.encode(param)
		if type (resultJson) == "string" then
			return resultJson
		else
			return false
		end
	end, value)

	result = ok and result or ""
	--LogDebug("[HELPERS.table.JSON_Encode] 2 - Encode with 'C++ json' result '%s'", result)
	return result
end

function TableIsEmpty (self, t)
	if type(t) ~= "table" then
		--LogDebug("[HELPERS.table.TableIsEmpty] 1 Table %s is empty: %s.", t, true)
		return true
	end
	local id, item = next(t)
	local isEmpty = not id
	--LogDebug("[HELPERS.table.TableIsEmpty] 1 Table %s is empty: %s, id: %s, item: %s.", t, isEmpty, id, item)
	return isEmpty, item
end

--[[
	recursively compare two tables.
	returns true if they are equal
	if different returns false.
]]
function StructuralCompare (self, v1, v2, CanValidateFn, current_attr)
	if type(CanValidateFn) == "function" and not CanValidateFn(current_attr) then
		--LogDebug("[HELPERS.table.StructuralCompare] Cannot validate attribute - attr: '%s': '%s' '%s'", current_attr, v1, v2)
		return true
	end
	-- differents types
	if type(v1) ~= type(v2) then
		--LogDebug("[HELPERS.table.StructuralCompare] Diff types - attr: '%s': '%s' '%s' -> '%s' '%s'", current_attr, v1, type(v1), v2, type(v2))
		return false
	end
	-- equals
	if v1 == v2 then return true end
	-- differents values, not a table
	if type(v1) ~= "table" then
		--LogDebug("[HELPERS.table.StructuralCompare] Diff values - attr: '%s' ->", current_attr, v1, v2)
		return false
	end
	local seen = {}
	for attr, value in pairs(v1) do
		seen[attr] = true
		if not StructuralCompare(self, value, v2[attr], CanValidateFn, attr) then return false end
	end
	for attr, value in pairs(v2) do
		if not seen[attr] then
			seen[attr] = true
			if not StructuralCompare(self, value, v1[attr], CanValidateFn, attr) then return false end
		end
	end
	return true
end

--[[

]]--
function Merge (self, source, target)
	source = source or {}
	target = target or {}
	--LogDebug("")
	--LogDebug("[HELPERS.table.Merge] ===> 1 -  source: '%s'.", JSON_Encode(self, source))
	--LogDebug("[HELPERS.table.Merge] ===> 2 -  target: '%s'.", JSON_Encode(self, target))
	local destination = DeepCopy(self, source)

	local function Start (list)
		for _, targetItem in ipairs(list or {}) do
			--LogDebug("")
			--LogDebug("           %s - targetItem.Value: %s", _, targetItem)
			local canAdd = true
			for _, sourceItem in ipairs(destination or {}) do
				local isDifferente = not StructuralCompare(self, targetItem, sourceItem)
				--LogDebug("              %s - sourceItem: %s", _, JSON_Encode(self, sourceItem))
				--LogDebug("              %s - isDifferente: %s", _, isDifferente)
				if not isDifferente then
					canAdd = false
					break
				end
			end
			--LogDebug("                 %s - canAdd: %s", _, canAdd)
			if canAdd then
				table.insert(destination, targetItem)
			end
		end
	end

	Start(target)
	--LogDebug("[HELPERS.table.Merge] ===> 3 -  destination: '%s'.", JSON_Encode(self, destination))
	--LogDebug("")
	return destination
end

--[[

]]--
function ArrayToHash (self, array, fieldId, transformKeyFn, transformValueFn)
	--LogDebug("[HELPERS.table.ArrayToHash] 1-> source -> fieldId: '%s', transformKeyFn: '%s', transformValueFn: '%s', array: %s", fieldId, transformKeyFn, transformValueFn, json.encode(array))
	transformKeyFn = transformKeyFn or function(item, key) return key end
	--[[
		Funcion que valida si para la columna ya existen valores.
		En cuyo caso toma la decision de crear datos multiples o simples.
			- (1 a 1)      Las lineas simples son utilizadas cuando se ha mapeado una columna del CSV a una columna del formato de importacion .
			- (1 a varios) Las lineas multiples son utilizadas cuando se ha mapeado una misma columna del CSV a varios columnas del formato de importacion.
	]]--
	transformValueFn = transformValueFn or function(item, key, currentItemValue)
		if not currentItemValue then
			-- columna simple
			return item
		end
		-- columna multiple
		if currentItemValue.isMulti then
			-- si ya existe, caso del tercer valor o superior
			table.insert(currentItemValue.list, item)
			return currentItemValue
		end
		-- caso el segundo valor, la columna simple se convierte en un array de 2 elementos.
		return {
			isMulti = true,
			list    = {currentItemValue, item}
		}
	end
	--[[
		Comienza a recorrer el array y convertirlo en un hash
	]]--
	local hash = {}
	for _, item in ipairs(array) do
		local key
		if type(item) == "table" and fieldId then
			key = item[fieldId]
		elseif type(item) == "string" or type(item) == "number" then
			key = item
		end
		key = transformKeyFn(item, key)
		local currentItemValue = hash[key]
		hash[key] = transformValueFn(item, key, currentItemValue)
		--LogDebug("[HELPERS.table.ArrayToHash]    1.1-> key: '%s', value: '%s'. ", key, JSON_Encode(self, hash[key]))
	end
	--LogDebug("[HELPERS.table.ArrayToHash] 2-> target -> hash: %s", json.encode(hash))
	return hash
end

--[[
	params
		hash        : (requerido)    lista origen, con la cual se creara un array.
		pFieldId    : (NO requerido) nombre del atributo en el cual se almacenara el indice de la lista origen en el array.
		pResponeType: (NO requerido) (DEFAULT/RAW) el tipo de respuesta en el array.
			DEFAULT     : Por defecto cada item del array es un objeto.
			RAW: Cada item de un array se devuelve sin ninguna modificacion.
]]--
function HashToArray (self, hash, pFieldId, pResponeType)
	assert(hash)
	local responeType = (pResponeType or "DEFAULT"):upper()
	local array = {}
	local fieldId = pFieldId or "__id"
	--LogDebug("[HELPERS.table.HashToArray] 1-> fieldId: %s, responeType: %s, source -> hash: %s", fieldId, responeType, json.encode(hash))
	for id, item in pairs(hash) do
		if responeType == "RAW" then
			table.insert(array, item)
		elseif responeType == "DEFAULT" then
			local arrayItem
			if type(item) == "table" then
				arrayItem = DeepCopy(self, item)
			else
				arrayItem = {}
				local fieldItem = "Item"
				fieldItem = fieldItem .. (fieldItem == fieldId and "1" or "")
				arrayItem[fieldItem] = item
			end
			arrayItem[fieldId] = id
			table.insert(array, arrayItem)
		else
			assert(false, "[HELPERS.table.HashToArray] Unrecognized responeType: " .. responeType)
		end
	end
	--LogDebug("[HELPERS.table.HashToArray] 2-> target -> array: %s", json.encode(array))
	return array
end

--[[
]]--
function GetRowCount (self, rows)
	local count = 0
	for _, row in pairs(rows or {}) do
		count = count + 1
	end
	return count
end

function SortArray (self, array, MakeSortingValue, desc)
	assert(type(array) == "table")
	assert(type(MakeSortingValue) == "function")

	table.sort(array, function(a, b)
		local val1, dataType = MakeSortingValue(a)
		local val2  = MakeSortingValue(b)
		--LogDebug("[HELPERS.table.SortArray]---------> dataType: '%s', val1: '%s', val2: '%s'", dataType, val1, val2)
		if val1 and val2 then
			if (dataType or "") == "date" then
				-- LuaOrm.Date
				if desc then
					return val2:compare(val1)
				else
					return val1:compare(val2)
				end
			else
				if desc then
					return val1 > val2
				else
					return val1 < val2
				end
			end
		end
	end)

	return array
end

function TruncateArray (self, sourceArray, from, to, onProcessRow)
	assert(type(sourceArray) == "table")
	onProcessRow = onProcessRow or function(row) return row end

	local totalRows = #sourceArray
	from = tonumber(from or 1)
	to = tonumber(to or totalRows)

	--LogDebug("[HELPERS.table.TruncateArray] Total rows: '%d', from: '%d' to: '%d'.", totalRows, from, to)

	if from > totalRows then
		return {} --sourceArray
	end

	local truncatedArray = {}
	local i = from

	while(i <= to) do
		local item = sourceArray[i]
		--LogDebug("[HELPERS.table.TruncateArray]  --> ROW i: '%d', item: '%s'.", i, item)
		if not item then
			break
		end
		item = onProcessRow(item, i)
		table.insert(truncatedArray, item)
		i = i + 1
	end

	return truncatedArray
end
