module(..., package.seeall)
local m_string = require "helpers.string"
local m_utils = require "helpers.utils"

local function GetElapsedTimeSec (start)
	return m_utils.GetElapsedTimeSec(start)
end

function ParseCSVLine (csvLine, fieldSeparator)
	assert(type(csvLine) == "string")
	fieldSeparator = fieldSeparator or ","

	--LogDebug("[HELPERS.CSV:ParseCSVLine] 1 ===> fieldSeparator: '%s', csvLine: '%s'.", fieldSeparator, csvLine)

	csvLine = m_string.alltrim(csvLine)
	local LN = "\n"
	local CR = "\r"

	local parsedLine = {}
	local function InsertColumn (column)
		column = string.gsub(column, LN, "")
		column = string.gsub(column, CR, "")
		column = m_string.alltrim(column)
		--LogDebug("[HELPERS.CSV:ParseCSVLine]   2 ===> column: '%s'.", column)
		table.insert(parsedLine, column)
	end

	local fieldSeparatorLength = #fieldSeparator
	local startPosition = 1
	local endPosition = nil
	while true do
		endPosition = string.find(csvLine, fieldSeparator, startPosition, endPosition, true)
		if not endPosition then
			local column = string.sub(csvLine, startPosition)
			InsertColumn(column)
			break
		end
		local column = string.sub(csvLine, startPosition, endPosition -1)
		InsertColumn(column)
		startPosition = endPosition + fieldSeparatorLength
		endPosition = nil
	end

	return parsedLine
end

function ParseCSVHeader (csv)
	assert(type(csv) == "string")

	csv = m_string.alltrim(csv)
	local LN = "\n"

	local position = string.find(csv, LN, nil, true)
	if not position then
		return
	end
	local headerLine = string.sub(csv, 1, position, nil, true)
	if not headerLine then
		return
	end

	-- obtengo el separador (los posibles son "," y ";")
	local fieldSeparator
	local allowedSeparators = {",", ";"}

	for _, separator in ipairs(allowedSeparators) do
		local separatorPosition = string.find(headerLine, separator, nil, true)
		if separatorPosition then
			local sep = string.sub(headerLine, separatorPosition, separatorPosition, nil, true)
			fieldSeparator = sep
			break
		end
	end
	--LogDebug("---> fieldSeparator: %s", fieldSeparator)

	local header = ParseCSVLine(headerLine, fieldSeparator)
	return header, position, fieldSeparator
end

function Csv2Table (csv, ProcessColumn, OnCompletedLine)
	assert(type(csv) == "string")

	ProcessColumn = ProcessColumn or function(columnName) return columnName end
	OnCompletedLine = OnCompletedLine or function() end

	csv = m_string.alltrim(csv)
	local LN = "\n"
	local csvHeader, headerEndPosition, fieldSeparator = ParseCSVHeader(csv)
	local parsedCSV = {}
	local parsedRawCSV = {}
	local rowIndex = 0

	local function AddCSVLine (csvLine)
		rowIndex = rowIndex + 1
		local parsedLine = ParseCSVLine(csvLine, fieldSeparator)
		local row = {}
		local rawRow  = {}

		for columnIndex, column in ipairs(csvHeader) do
			local value = parsedLine[columnIndex]
			rawRow[column] = value
		end

		for i, column in ipairs(csvHeader) do
			local value = parsedLine[i]
			local columnName = ProcessColumn(column, value, row, rowIndex, columnIndex, rawRow)
			if columnName then
				row[columnName] = value
			end
		end

		OnCompletedLine(row)
		table.insert(parsedCSV, row)
		table.insert(parsedRawCSV, rawRow)
	end

	local startPosition = headerEndPosition + 1

	while true do
		local position = string.find(csv, LN, startPosition, true)
		local csvLine = string.sub(csv, startPosition, position, nil, true)
		if not csvLine then
			break
		end
		AddCSVLine(csvLine)
		startPosition = startPosition + #csvLine
		if not position then
			break
		end
	end

	return parsedCSV, parsedRawCSV
end

function Table2Csv (tableRows, allowedColumns, excludeColumns, separator, orderHeaders)
	assert(type(tableRows) == "table")
	separator = separator or ","
	excludeColumns = excludeColumns or {}

	local startProcessTime = os.time()
	--local startSubProcessTime = startProcessTime
	--LogDebug("[HELPERS.CSV:Table2Csv] 1 - Source Table: '%s'.", json.encode(tableRows))
	LogInfo("[HELPERS.CSV:Table2Csv] (START) MAKING CSV FROM Table: '%s' elements.", #tableRows)

	local function CanAddColumn (columnName)
		if excludeColumns[columnName] then
			--LogDebug("[HELPERS.CSV:Table2Csv]    -> 1 - NO (CanAddColumn) - excludeColumns.columnName: '%s'.", columnName)
			return false
		end
		if allowedColumns then
			if not allowedColumns[columnName] then
				--LogDebug("[HELPERS.CSV:Table2Csv]    -> 2 - NO (CanAddColumn) - not allowedColumns.columnName: '%s'.", columnName)
				return false
			end
		end
		--LogDebug("[HELPERS.CSV:Table2Csv]    --> 3 - SI (CanAddColumn) - add columnName: '%s'.", columnName)
		return true
	end

	local csvHeader
	local headerColumns = {}

	local  function AddColumns (columnName)
		if CanAddColumn(columnName) then
			--LogDebug("[HELPERS.CSV:Table2Csv]   Adding -> header columnName: '%s'.'", columnName)
			if not csvHeader then
				csvHeader = columnName
			else
				csvHeader = csvHeader .. separator .. columnName
			end
			table.insert(headerColumns, columnName)
		end
	end
	if orderHeaders then
		for _, columnName in ipairs(orderHeaders) do
			AddColumns(columnName)
		end
	else
		for _, item in pairs(tableRows) do
			for columnName, value in pairs(item) do
				AddColumns(columnName)
			end
			break
		end
	end
	--LogDebug("[HELPERS.CSV:Table2Csv] 2 - Target csvHeader: %s'. Elpased time %s seconds.", csvHeader, GetElapsedTimeSec(startSubProcessTime))

	--startSubProcessTime = os.time()
	local LN = "\n"
	local tableContent
	local rowNumber = 0
	for _, item in pairs(tableRows) do
		rowNumber = rowNumber + 1
		local isFirstColumn = true
		--LogDebug("------------------------------------")
		--LogDebug("[HELPERS.CSV:Table2Csv] Row %d'", rowNumber)
		for _, columnName in ipairs(headerColumns) do
			local value = tostring(item[columnName])
			--LogDebug("[HELPERS.CSV:Table2Csv]   Adding -> columnName: '%s'.value: '%s'", columnName, value)
			local content
			if isFirstColumn then
				tableContent = tableContent or {}
				content =  "" .. LN .. value
			else
				content =  separator .. value
			end
			table.insert(tableContent, content)
			isFirstColumn = false
		end
	end
	--LogDebug("[HELPERS.CSV:Table2Csv] 3 - tableContent: '%s' --> Elpased time %s seconds.", #tableContent, GetElapsedTimeSec(startSubProcessTime))

	local csv
	if csvHeader and tableContent then
		--startSubProcessTime = os.time()
		local csvContent = table.concat(tableContent, "")
		--LogDebug("[HELPERS.CSV:Table2Csv] 4 - --> Elpased time %s seconds.", GetElapsedTimeSec(startSubProcessTime))
		csv = csvHeader .. csvContent
	end

	LogInfo("[HELPERS.CSV:Table2Csv] --> (END) MAKING CSV FROM %d elements. Elpased time %s seconds.", #tableRows, GetElapsedTimeSec(startProcessTime))
	--LogDebug("[HELPERS.CSV:Table2Csv] 4 - Target CSV:\n%s", csv)
	return csv
end