local LuaOrm_Date = require("LuaOrm.Date")

local tonumber = tonumber
local tostring = tostring
local pairs = pairs
local ipairs = ipairs
local type = type
local string = require "string"
local error = error
local assert = assert

module("LuaOrm.Utils")

local convert = {}

function convert.integer(v)
	return tonumber(v)
end

function convert.decimal(v)
	return tonumber(v)
end

function convert.int(v)
	return tonumber(v)
end

function convert.tinyint(v)
	return tonumber(v)
end

function convert.number(v)
	return tonumber(v)
end

function convert.float(v)
	return tonumber(v)
end

function convert.numeric(v)
	return tonumber(v)
end

function convert.bit(v, driver)
	if driver == "mysql" then
		return v:byte(1)
	else
		return v and 1 or 0
	end
end

function convert.smallint(v)
	return tonumber(v)
end

function convert.varchar(v)
	return tostring(v)
end

function convert.nvarchar(v)
	return tostring(v)
end

function convert.string(v)
	return tostring(v)
end

function convert.text(v)
	return tostring(v)
end

function convert.char(v)
	return tostring(v)
end

function convert.boolean(v, driver)
	if driver == "sqlite3" then
		return v == "t"
	elseif driver == "mysql" then
		return tonumber(v) == 1
	else
		return v == "t"
	end
end

function convert.binary(v)
	return convert.text(v)
end

function convert.none(v, driver)
	assert(driver == "sqlite3", "columns with type 'none' are only supported by Sqlite3")
	local t = type(v)
	if t == "number" then
		return convert.number(v, driver)
	elseif t == "string" then
		-- hacemos una salvedad, si el valor es 't' o 'f' entonces lo tratamos como boolean
		if v == 't' or v == 'f' then return convert.boolean(v, driver) end
		return convert.text(v, driver)
	elseif t == "boolean" then
		return convert.boolean(v, driver)
	else
		error("unsupported type (" .. t .. ") for a column of type none")
	end
end

--[=[
esta es la implementacion de orbit
function convert.datetime(v)
  local year, month, day, hour, min, sec = 
    string.match(v, "(%d+)%-(%d+)%-(%d+) (%d+):(%d+):(%d+)")
  return os.time({ year = tonumber(year), month = tonumber(month),
		   day = tonumber(day), hour = tonumber(hour),
		   min = tonumber(min), sec = tonumber(sec) })
end
--]=]
function convert.datetime(v)
	local dataType = type(v)
	local newDate
	if dataType == "string" then
		newDate = LuaOrm_Date(v)
	elseif dataType == "table" then
		-- expects a Date returned from LuaCOM
		newDate = LuaOrm_Date{ year = v.Year, month = v.Month, day = v.Day, hour = v.Hour, min = v.Minute, sec = v.Second, millisec = v.Milliseconds }
	else
		error("convert.datetime - parameter should be a string or a table")
	end
	return newDate
end

convert.date = convert.datetime
convert.timestamp = convert.datetime

function ConvertTypes(row, meta, driver)
	for k, v in pairs(row) do
		if meta[k] then
			local conv = convert[meta[k].type]
			if conv then
				row[k] = conv(v, driver)
			else
				error("no conversion for type " .. meta[k].type)
			end
		end
	end
end

local escape = {}

function escape.integer(v)
	return tostring(v)
end

function escape.decimal(v)
	return tostring(v)
end

function escape.int(v)
	return tostring(v)
end

function escape.tinyint(v)
	return tostring(v)
end

function escape.float(v)
	return tostring(v)
end

function escape.smallint(v)
	return tostring(v)
end

function escape.bit(v)
	return tostring(v)
end

function escape.numeric(v)
	return tostring(v)
end

function escape.number(v)
	return escape.integer(v)
end

function escape.varchar(v, driver, conn)
	-- asi esta en Orbit nuevo, pero conn no tiene funciones escape, aunque estan en discusion en la mailing list de Kepler
	--return "'" .. conn:escape(v) .. "'"
	return "'" .. string.gsub(v, "'", "''") .. "'"
end

function escape.nvarchar(v, driver, conn)
	-- asi esta en Orbit nuevo, pero conn no tiene funciones escape, aunque estan en discusion en la mailing list de Kepler
	--return "'" .. conn:escape(v) .. "'"
	return "'" .. string.gsub(v, "'", "''") .. "'"
end

function escape.string(v, driver, conn)
	return escape.varchar(v, driver, conn)
end

function escape.text(v, driver, conn)
	-- asi esta en Orbit nuevo, pero conn no tiene funciones escape, aunque estan en discusion en la mailing list de Kepler
	--return "'" .. conn:escape(v) .. "'"
	return "'" .. string.gsub(v, "'", "''") .. "'"
end

function escape.char(v, driver, conn)
	-- asi esta en Orbit nuevo, pero conn no tiene funciones escape, aunque estan en discusion en la mailing list de Kepler
	--return "'" .. conn:escape(v) .. "'"
	return "'" .. string.gsub(v, "'", "''") .. "'"
end

function escape.datetime(v)
	local date
	if (type(v) == "string") then
		date = LuaOrm_Date(v)
	elseif (type(v) == "table") then
		date = LuaOrm_Date(v)
	elseif type(v) == "number" then
		date = LuaOrm_Date(v)
	else
		date = nil
	end
	if (date) then
		return "'" .. date:toString() .. "'"
	else
		return "''"
	end
end

escape.date = escape.datetime
escape.timestamp = escape.datetime

function escape.boolean(v, driver, conn)
	if driver == "sqlite3" then
		return v and "'t'" or "'f'"
	elseif driver == "mysql" then
		return tostring(v)
	else
		return v and "'t'" or "'f'"
	end
end

function escape.binary(v, driver, conn)
	return escape.text(v, driver, conn)
end

function escape.none(v, driver, conn)
	assert(driver == "sqlite3", "columns with type 'none' are only supported by Sqlite3")
	local t = type(v)
	if t == "number" then
		return escape.number(v, driver, conn)
	elseif t == "string" then
		return escape.text(v, driver, conn)
	elseif t == "boolean" then
		return escape.boolean(v, driver, conn)
	else
		error("unsupported type (" .. t .. ") for a column of type none")
	end
end


--
-- Escapes a value using the given datasource rules
-- @param datasource is an instance of LuaOrm.DBMS.DataSource
-- @param columnInfo is a table containing column metadata
-- @param value is the value to escape
-- @return the escaped value or an error if no escape function is available
function EscapeValue(datasource, columnInfo, value)
	return EscapeTypedValue(datasource, columnInfo.type, value)
end


--
-- Escapes a value using the given datasource rules. It receives an explicit column type instead of inferring it.
-- @param datasource is an instance of LuaOrm.DBMS.DataSource
-- @param columnType is a string with the value's type (i.e. string, date, etc)
-- @param value is the value to escape
-- @return the escaped value or an error if no escape function is available
function EscapeTypedValue(datasource, columnType, value)
	local esc = escape[columnType]
	if esc then
		return esc(value, datasource.m_driver, datasource.m_rawConnection)
	else
		error("no escape function for type " .. columnType)
	end
end

--
-- Escapes all values in a given row.
-- @param model is the model the row belongs to (an instance of LuaOrm.Model)
-- @param row the row to be escaped
-- @return a new row with the escaped values
function EscapeValues(model, row)
	local row_escaped = {}
	for i, m in ipairs(model.columnInfo) do
		if row[m.name] == nil then
			row_escaped[m.name] = "NULL"
		else
			row_escaped[m.name] = EscapeValue(model.dataSource, m, row[m.name])
		end
	end
	return row_escaped
end

-- DEPRECATED in #4084
-- function FillValues(row, meta, driver)
	-- for i, m in ipairs(meta) do
		-- if not row[m.name] then
			-- if (m.type == "datetime" or m.type == "date") then
				-- row[m.name] = LuaOrm_Date()
			-- end
		-- end
	-- end
-- end

--
-- Initializes the values of a given entity, according to the each column's metadata
-- @param entity the entity whose values we're initializing
-- @param columnInfo the metadata of the columns
-- @param initialData a table with the initial data (if any) for this new entity
function InitValues(entity, columnInfo, initialData)
	for i, m in ipairs(columnInfo) do
		if (m.type == "datetime" or m.type == "date") then
			if initialData[m.name] then
				entity[m.name] = LuaOrm_Date(initialData[m.name])
			else
				entity[m.name] = LuaOrm_Date()
			end
		else
			entity[m.name] = initialData[m.name]
		end
	end
end
