local cassandra = require "lua_cassandra"
local json = require "json"
local crypto = require "crypto"
local m_allegroRepository = require "icc_repository.allegro"
local m_helpers = require "helpers"
local logger = {}
local m_repositoryNodes
local repositoryServiceIP
local repositoryServicePORT

package.preload["repository.logger"] = function()
	return logger
end

local _M = {}

local http = require("socket.http")
local socketUrl = require "socket.url"
---
-- Public
_M.CASSANDRA_COLUMN_SIZE = 40 * 1024 -- 40 KB by default
_M.CASSANDRA_MAX_COLUMNS_PER_ROW = 100

local model = {}

local columnFamilyByChannel = {
	FilesCall = true,
	FilesMail = true,
	FilesVoicemail = true,
	FilesChat = true,
	FilesWhatsapp = true,
	FilesFacebook = true,
	FilesTwitter = true,
	FilesInstagram = true,
	FilesYoutube = true,
	FilesScreenrecording = true,
	FilesOther = true
}

local m_helpers = require "helpers"
local m_hasRepositoryService = nil

local function HasRepositoryService()
	if m_hasRepositoryService == nil then
		m_hasRepositoryService = m_helpers.utils.HasRepositoryService()
	end
	return m_hasRepositoryService
end

function model:initialize ()
	-- Test if the needed keyspace exists
	local ok, connection = pcall(cassandra.CassandraConnection, cassandra, "Repository")
	if not ok then
		local err = ("apps.repository.SetCassandra - Error while retrieving connection to Cassandra: %s"):format(connection)
		return false, err
	else
		connection:Close()
	end

	return true
end

---
-- Returns the column family where a file is stored in Cassandra
--
function model:GetFileColumnFamily (file_id, repoKeySpace)
	local keyspace = repoKeySpace or cassandra:CassandraConnection("Repository")
	local columnFamily = keyspace:GetColumnValue(file_id, "Files", "CF")
	if not repoKeySpace then
		--LogDebug("3 ----> keyspace:Close()")
		keyspace:Close()
	end
	--Old files are stored in Files CF
	return columnFamilyByChannel[columnFamily] and columnFamily or "Files"
end

---
--
function model:Get (id, column_family, repoKeySpace)
	--By default, search on CF Files for metadata
	column_family = column_family or "Files"

	if type(id) ~= "string" or id:len() == 0 then
		return {}, false
	end

	local keyspace = repoKeySpace or cassandra:CassandraConnection("Repository")
	local fileColumns = keyspace:GetSliceRange(id, column_family, "",
							{
								column_names = {},
								slice_range = { start = "", finish = "", reversed = true, count = 200 }
							}
						)

	local file_exist = false
	local repositoryFile = {}
	if fileColumns then
		for k, v in ipairs(fileColumns) do
			file_exist = true
			repositoryFile[v.name] = v.value
		end
	end
	
	--Return CF where file data are stored
	local data_column_family = model:GetFileColumnFamily(id, keyspace)
	if not repoKeySpace then
		--LogDebug("4 ----> keyspace:Close()")
		keyspace:Close()
	end

	--LogDebug("[icc_repository.model:Get] 1 -id: '%s'", id)
	--LogDebug("[icc_repository.model:Get] 2 -column_family: '%s'", column_family)
	--LogDebug("[icc_repository.model:Get] 3 -file_exist: '%s'", file_exist)
	--LogDebug("[icc_repository.model:Get] 4 -id: '%s'", data_column_family)

	return repositoryFile, file_exist, data_column_family
end

---
--
function model:DeleteFile (row_id, num_rows, repoKeySpace)
	--Get file metadata
	local keyspace = repoKeySpace or cassandra:CassandraConnection("Repository")
	local _, file_exist, column_family = self:Get(row_id, nil, keyspace)

	if not file_exist then
		if not repoKeySpace then
			--LogDebug("1 ----> keyspace:Close()")
			keyspace:Close()
		end
		logger.warn("[icc_repository] File '%s' to be deleted does not exist.", row_id)
		return true
	end

	-- When we delete a file stored with the new schema, we need to remove all rows.
	-- If num_rows is not supplied, try to see if there is one.
	if not num_rows then
		local fileColumn = keyspace:GetSliceRange(row_id, "Files", "", {
			column_names = { "NumRows" },
			slice_range = { start = "", finish = "", reversed = true, count = 200 }
		})
		if #fileColumn > 0 and fileColumn[1].value then
			num_rows = tonumber(fileColumn[1].value)
		end
	end

	if num_rows then
		for i = 0, num_rows - 1 do
			-- Remove file data
			keyspace:RemoveColumn(row_id .. i, column_family)
		end
	end

	-- Remove file metadata
	keyspace:RemoveColumn(row_id, "Files")
	if not repoKeySpace then
		--LogDebug("2 ----> keyspace:Close()")
		keyspace:Close()
	end

	return true
end


---
-- Stores the metadata of the file in Cassandra.
-- Usually: id, section, format, content type, filename, filesize
--
function model:SaveMetadata (file_id, metadata)
	local keyspace = cassandra:CassandraConnection("Repository")

	local t = {}
	for k, v in pairs(metadata) do
		table.insert(t,
		{
			column_family = "Files",
			column_key    = file_id,
			column_name   = k,
			column_value  = tostring(v)
		})
	end
	keyspace:BatchInsert(t)

	keyspace:Close()
	return true
end

---
-- Stores a single bucket of data in Cassandra, along with the current number of rows
--
function model:SaveBucket (column_family, file_id, row_number, column_number, data)
	local row_id = file_id .. row_number
	local keyspace = cassandra:CassandraConnection("Repository")

	local t = {
		{
			column_family = column_family,
			column_key    = row_id,
			column_name   = "bucket_"..column_number,
			column_value  = tostring(data)
		},
		{
			column_family = column_family,
			column_key    = file_id,
			column_name   = "NumRows",
			column_value  = tostring(row_number)
		}
	}

	keyspace:BatchInsert(t)
	keyspace:Close()

	return true
end


---------------------------------------------------------------------------
-- Helpers

---
-- Given a file id and a section, this function creates the file id in cassandra
--
local function make_file_id (id, section)
	id = id or ""
	section = section or ""
	if tostring(section) == "1" then
		return id -- widely used for prompts
	end
	-- mostly used for recording
	return string.format("%s[%s]", id, section)
end


---
-- Returns a function that receives chunks of data. When enough data to fill a bucket of size 'bucket_size' a callback
-- is called.
-- Bucket numbering starts at 0
--
local function make_bucket_sink (bucket_size, context, callback, final)
	local read_total_bytes = 0
	local read_bytes = 0
	local num_buckets = 0
	local chunks = {}

	context.data = context.data or {}

	local function sink (chunk)
		--logger.debug("[icc_repository:sink]      chunk %s.", chunk)

		if chunk then
			read_bytes = read_bytes + #chunk
			read_total_bytes = read_total_bytes + #chunk
			chunks[#chunks + 1] = chunk
		end

		if read_bytes >= bucket_size then
			-- we've enough data to fill at least a bucket

			local trailing
			local data = table.concat(chunks)

			if #data > 0 then
				table.insert(context.data, data)
			end

			while read_bytes >= bucket_size do
				data, trailing = data:sub(1, bucket_size), data:sub(bucket_size + 1)
				callback(num_buckets, data)
				num_buckets = num_buckets + 1
				data, read_bytes = trailing, #trailing
			end

			chunks = { trailing }

		elseif not chunk then
			-- a nil chunk means EOF

			-- process trailing data
			local data = table.concat(chunks)
			if #data > 0 then
				callback(num_buckets, data)
				num_buckets = num_buckets + 1
				table.insert(context.data, data)
			end

			-- no more data, we're done
			-- store how many columns in cassandra
			-- store the rest of the metadata
			context.read_total_bytes = read_total_bytes
			context.num_buckets = num_buckets

			final()
		end

		return true
	end

	return sink
end


---
-- Given a bucket_number ordinal, convert it into a [row, col] pair
--
local function make_row_col (bucket_number, max_cols)
	local row = math.floor(bucket_number / max_cols)
	local col = bucket_number % max_cols

	return row, col
end


-- Helpers
---------------------------------------------------------------------------
--
-- Returns repositoryservice nodes.
--
local function get_repository_service_nodes()

	if type(m_repositoryNodes) == "table" and next(m_repositoryNodes) ~= nil then
		return m_repositoryNodes
	end

	repositoryServiceIP   = repositoryServiceIP   or os.getenv("LOCAL_IP")
	repositoryServicePORT = repositoryServicePORT or os.getenv("REPOSITORYSERVICE_HTTP_PORT")

	return {{ ip = repositoryServiceIP, port = repositoryServicePORT }}
end

---
-- Returns a function that can be called until all data is received.
--
local function make_store_sink_repository_service (newFile, size_is_known, sink_context, callback)
	-- Create a new md5 "digestor". As each chunk of data arrives, we will update the md5 hash.

	logger.debug("[icc_repository:make_store_sink_repository_service] newFile.Id: %s", newFile.Id or newFile.PartialId)

	local md5_ctx = crypto.digest.new("md5")
	local num_rows = 0

	newFile.Id = newFile.PartialId
	newFile.PartialId = nil

	local sink = make_bucket_sink(
		_M.CASSANDRA_COLUMN_SIZE,
		sink_context,

		function(bucket_number, data)
			--logger.debug("[icc_repository:make_store_sink]  1-callback - bucket_number: %s, data: %s", bucket_number, data)
			assert(#data <= _M.CASSANDRA_COLUMN_SIZE)	-- invariant: data is always less or equal than bucket_size
			md5_ctx:update(data)
			local row, col = make_row_col(bucket_number, _M.CASSANDRA_MAX_COLUMNS_PER_ROW)
			num_rows = row + 1
		end,

		function()
			-- All data has been received. Now we need to send a request to repositoryservice
			logger.debug("[icc_repository:make_store_sink]  2-callback")

			local read_bytes = sink_context.read_total_bytes
			-- En sink_context.data tengo una tabla con los chunks. Lo paso a string
			local data = table.concat(sink_context.data)

			-- Do this check *only* if we know the size in advance (ie, NOT when dealing with a multipart http request)
			if not read_bytes or (size_is_known and read_bytes < newFile.FileSize) then
				callback(false, { code = "E_INCOMPLETE_FILE", msg = "Incomplete file received",
					expected = newFile.FileSize, read_bytes or "unknown"
				})
				return
			end

			-- When the request is multipart/form-data, I need to override the Content-Length header
			newFile.FileSize = read_bytes

			--Make the repositoryservice request url
			local result, code, headers, httpStatus
			local url          = ""
			local responseBody = {}
			local nodes        = get_repository_service_nodes()

			for k, v in pairs(newFile) do
				url = url .. (url == "" and "" or "&") .. k .. "=" .. socketUrl.escape(v)
			end

			for _, node in ipairs(nodes) do
				url = "http://" .. node.ip .. ":" .. node.port .. "/inconcert/apps/repository/add/?" .. url
				result, code, headers, httpStatus = socket.http.request{
					url = url,
					method = "POST",
					headers = {
						["Content-Type"] = newFile.ContentType == "agentrecording" and "video/webm" or newFile.ContentType,
						["Content-Length"] = #data,
					},
					sink = ltn12.sink.table(responseBody),
					source = ltn12.source.string(data)
				}

				if code == 200 then
					logger.info("[icc_repository:make_store_sink] File: '%s' successfully saved for ID '%s' [on section %s]", newFile.FileName, newFile.Id, newFile.Section)
					callback(true)
					return
				end
			end

			logger.warn("[icc_repository:make_store_sink] Failed to save file ID '%s' - section: %s", newFile.Id, newFile.Section)
			callback(false, { code = "E_SAVE_FAILED", msg = "Failed to save file" })
		end
	)

	return sink
end

---
-- public:
-- Returns a function that can be called until all data is received.
--
local function make_store_sink (newFile, size_is_known, sink_context, callback)
	if HasRepositoryService() then
		return make_store_sink_repository_service(newFile, size_is_known, sink_context, callback)
	end

	logger.debug("[icc_repository:make_store_sink] newFile.Id: %s", newFile.Id or newFile.PartialId)

	local md5_ctx = crypto.digest.new("md5")
	local num_rows = 0

	-- Make sure we create a unique file id
	if not newFile.Id then
		newFile.Id = make_file_id(newFile.PartialId, newFile.Section)
	end

	local cfSuffixName = newFile.proxyType or "FilesOther"
	local column_family_str = "Files" .. cfSuffixName:sub(1,1):upper() .. cfSuffixName:sub(2):lower()

	-- New file data will be stored in a specific CF or in a FilesOther CF by default
	local column_family = columnFamilyByChannel[column_family_str] and column_family_str or "FilesOther"
	newFile.CF = column_family
	
	local sink = make_bucket_sink(_M.CASSANDRA_COLUMN_SIZE, sink_context, function(bucket_number, data)
		assert(#data <= _M.CASSANDRA_COLUMN_SIZE)	-- invariant: data is always less or equal than bucket_size
		md5_ctx:update(data)

		local row, col = make_row_col(bucket_number, _M.CASSANDRA_MAX_COLUMNS_PER_ROW)
		model:SaveBucket(column_family, newFile.Id, row, col, data)
		logger.info("[icc_repository] Saved bucket '%d' at [%s,%s] for file id: '%s' on column family '%s'", bucket_number, row, col, newFile.Id, column_family)
		num_rows = row + 1
	end,

	function()
		-- All data has been received and stored in columns in Cassandra. Now we need to perform some bookkeeping, such
		-- as storing metadata, nomber of chunks, md5 hash, etc.

		local read_bytes = sink_context.read_total_bytes

		-- Do this check *only* if we know the size in advance (ie, NOT when dealing with a multipart http request)
		if not read_bytes or (size_is_known and read_bytes < newFile.FileSize) then
			model:DeleteFile(newFile.Id)
			-- local msg = LogError("Incomplete file received - (Filename: '%s', id: '%s', size: '%d' bytes, received size: '%s' bytes)",
			-- 			newFile.FileName, newFile.Id, newFile.FileSize, read_bytes or "unknown"
			-- 		)
			callback(false, { code = "E_INCOMPLETE_FILE", msg = "Incomplete file received",
				expected = newFile.FileSize, read_bytes or "unknown"
			 })
		end

		-- When the request is multipart/form-data, I need to override the Content-Length header
		newFile.FileSize = read_bytes

		newFile.NumBuckets = sink_context.num_buckets
		newFile.NumRows = num_rows
		newFile.MaxColsPerRow = _M.CASSANDRA_MAX_COLUMNS_PER_ROW

		logger.info("[icc_repository] Saving file metadata id:%s name:%s section:%s %d buckets and %d rows",
			newFile.Id, newFile.FileName, newFile.Section, newFile.NumBuckets, newFile.NumRows
		)

		if newFile.ContentType ~= "agentrecording" then
			newFile.MD5 = md5_ctx:final()
			logger.debug("[icc_repository] Hashed file id:%s to:%s", newFile.Id, newFile.MD5)
		end

		if model:SaveMetadata(newFile.Id, newFile) then
			logger.info("[icc_repository] File: '%s' successfully saved for ID '%s' [on section %s]", newFile.FileName, newFile.Id, newFile.Section)
			callback(true)
		else
			logger.warn("[icc_repository] Failed to save file ID '%s' - section: %s", newFile.Id, newFile.Section)
			model:DeleteFile(newFile.Id)
			callback(false, { code = "E_SAVE_FAILED", msg = "Failed to save file" })
		end
	end)

	return sink
end


local function erase_file_repository_service (partial_id, section, num_rows, repoKeySpace)

	local url, result, code, headers, httpStatus
	local responseBody = {}
	local nodes        = get_repository_service_nodes()

	for _, node in ipairs(nodes) do

		url = "http://" .. node.ip .. ":" .. node.port .. "/inconcert/apps/repository/delete/?id=" .. partial_id .. "&section=" .. section 
		
		result, code, headers, httpStatus = socket.http.request{
			url = url,
			method = "DELETE",
			sink = ltn12.sink.table(responseBody)
		}

		if code == 200 then
			logger.info("[icc_repository] File with Id '%s' and section '%s' successfully deleted", partial_id, section)
			return true
		end
	end

	logger.warn("[icc_repository] Error deleting file with Id '%s' and section '%s'", partial_id, section)
	return false
end

---
-- public:
-- Erase file of the repository
--
local function erase_file (partial_id, section, num_rows, repoKeySpace)
	if HasRepositoryService() then
		return erase_file_repository_service(partial_id, section, num_rows, repoKeySpace)
	end	
	local id =  make_file_id(partial_id, section)
	logger.info("[repository:erase_file] Deleting file id:'%s', num rows: '%s'. repoKeySpace: %s", id, num_rows, repoKeySpace)
	local success = model:DeleteFile(id, num_rows, repoKeySpace)
	if success then
		logger.info("[icc_repository] Succesufully deleted file '%s'.", id)
		return true
	end
	logger.warn("[icc_repository] Error deleting file '%s'.", id)
	return false
end

---
-- public:
-- Erase batchs files of the repository
--
local function erase_batch_files (files)
	assert(type(files) == "table")
	local totalFiles = #files
	logger.info("[repository:erase_batch_files] Deleting batch %d files.", totalFiles)

	if totalFiles < 1 then
		logger.warn("[repository:erase_batch_files] Error deleting batch files. The file list must contain at least one item. ")
		return false
	end

	local result = {removedFiles = {}, notRemovedFiles  = {}}
	local repoKeySpace = cassandra:CassandraConnection("Repository")

	for _, file in ipairs(files) do
		local num_rows = nil -- TODO: Revisar si esto tiene que ir "file.numRows"
		local success = erase_file(file.FileId, file.Section, num_rows, repoKeySpace)
		if success then
			table.insert(result.removedFiles, file)
		else
			table.insert(result.notRemovedFiles, file)
		end
	end
	repoKeySpace:Close()

	if #result.notRemovedFiles == totalFiles then
		logger.warn("[repository:erase_batch_files] Error deleting batch files. No file could be deleted.")
		return false, result
	elseif #result.removedFiles == totalFiles then
		logger.info("[repository:erase_batch_files] All files have been deleted. Total %d.", totalFiles)
	else
		logger.info("[repository:erase_batch_files] %d files have been deleted, %d have not been deleted.", #result.removedFiles, #result.notRemovedFiles)
	end

	return true, result
end

---
-- public:
-- returns true or false, plus metadata
--
local function file_exists (id, section, repoKeySpace)
	assert(id and section)

	logger.info("[icc_repository] Looking for file: id: %s - section: %s", id, section)
	-- make sure we download the correct file
	local full_id = make_file_id(id, section)
	local file, file_exist = model:Get(full_id, nil, repoKeySpace)

	if not file_exist or not file.FileSize or not file.FileName then
		logger.warn("[icc_repository] File '%s' not found on cassandra (or not complete yet) (missing metadata)", id)
		return false
	end
	return true, file
end

local function retrieve_file_repository_service (id, section, part, stream, repoKeySpace)
	assert(id and section)

	local url, result, code, header
	local response_body = {}
	local nodes         = get_repository_service_nodes()

	for _, node in ipairs(nodes) do
		url = "http://" .. node.ip .. ":" .. node.port .. "/inconcert/apps/repository/get/?id=" .. id .. "&section=" .. section
		
		result, code, header = socket.http.request {
			url = url,
			method = "GET",
			sink = ltn12.sink.table(response_body)
		}

		if code == 200 then
			break
		end
	end

	local generator = coroutine.wrap(function()
			for i = 1, #response_body do
				local data = response_body[i]
				if not data then
					return
				end
				coroutine.yield(data)
			end
		end
	)	
	
	if code == 200 then
		if stream then
			return true, generator
		else
			local res = {}
			while true do
				local data = generator()
				if data then
					table.insert(res, data)
				else
					return true, table.concat(res)
				end
			end
		end
	else
		logger.warn("[icc_repository] File '%s' and section '%s' not found (or not complete yet)", id, section)
		return false
	end
end

---
-- public:
-- Retrieves a file from the repository.
--
-- This needs to accomodate two ways that files could have been stored in the repository.
-- The old way, were the entire file was stored in Cassandra in a column named 'blob' and the new way, were
-- the file is split in N columns.
--
-- The function is a raw wsapi handler since we need to control the way we deliver the response body (piece by piece
--	instead of all at once).
--
local function retrieve_file (id, section, part, stream, repoKeySpace)
	if HasRepositoryService() then
		return retrieve_file_repository_service(id, section, part, stream, repoKeySpace)
	end
	assert(id and section and part)

	--logger.info("[icc_repository] Retrieving file: id: %s - section: %s - part: %s - stream: %s - repoKeySpace: %s", id, section, part, stream, repoKeySpace)

	-- make sure we download the correct file
	local full_id = make_file_id(id, section)

	-- (perfomance) Se obtiene la conexion a cassandra que sera utilizada en toda la funcion y en las funciones invocadas que necesiten conexion a cassandra
	local keyspace = repoKeySpace or cassandra:CassandraConnection("Repository")

	local function CloseConnection ()
		--LogDebug("----> (CloseConnection) keyspace:Close() %s ", repoKeySpace ~= nil and "NO CIERRO LA CONEXION !!!!!!!!" or "CIERRO LA CONEXION")
		if not repoKeySpace then
			keyspace:Close()
		end
	end

	--Get metadata and CF where file data is
	local file, file_exist, data_column_family = model:Get(full_id, nil, keyspace)

	-- Poor man's ACID: We make sure that the columns we need no matter what are present.
	-- FileName, Format, MD5 are optional
	-- 'blob' has to be present, but only for files that had been uploaded with the old scheme
	--
	if not file_exist or not file.FileSize or not file.FileName then
		CloseConnection()
		logger.warn("[icc_repository] File '%s' not found on cassandra (or not complete yet) (missing metadata)", full_id)
		return false
	end

	if file.blob then
		--LogDebug("[icc_repository.model:retrieve_file] 1 retrive from BLOB'")
		-- ok, an old scheme file, all data is here, merrily stream away
		CloseConnection()
		if not stream then
			return true, file.blob
		end
		return true, coroutine.wrap(function()
				coroutine.yield(file.blob)
			end
		)

	elseif file.NumBuckets and file.NumRows and file.MaxColsPerRow then
		-- ok, a new scheme file, check that all buckets are here
		--LogDebug("[icc_repository.model:retrieve_file] 2 retrive from BUCKETS'")
		local file_rows = {}

		for i = 0, tonumber(file.NumBuckets) - 1 do
			local row, col = make_row_col(i, file.MaxColsPerRow)
			--LogDebug("[icc_repository.model:retrieve_file]   --> row '%s' col '%s'.", row, col)
			local current_row = file_rows[row]

			if not current_row then
				current_row = model:Get(full_id .. row, data_column_family, keyspace)
				file_rows[row] = current_row
			end

			local bucket_id = "bucket_" .. col
			if not current_row[bucket_id] then
				CloseConnection()
				-- fuckings, a missing bucket, try again later?
				logger.warn("[icc_repository] File '%s' not found on cassandra (or not complete yet) (missing bucket '%s')", full_id, bucket_id)
				return false
			end
		end
		CloseConnection()

		local generator = coroutine.wrap(function()
				for i = 0, tonumber(file.NumBuckets) - 1 do
					local row, col = make_row_col(i, file.MaxColsPerRow)
					local current_row = file_rows[row]
					local bucket_id = "bucket_" .. col
					local data = current_row[bucket_id]
					if not data then
						return
					end
					coroutine.yield(data)
					current_row[bucket_id] = nil
				end
			end
		)

		if stream then
			return true, generator
		else
			local res = {}
			while true do
				local data = generator()
				if data then
					table.insert(res, data)
				else
					return true, table.concat(res)
				end
			end
		end
	else
		-- no blob field nor NumBuckets or NumRows field. Maybe those fields will eventually exist by grace of Cassandra
		CloseConnection()
		logger.warn("[icc_repository] File '%s' not found on cassandra (or not complete yet) (missing bucket metadata)", full_id)
		return false
	end
end

---
-- public:
--
local function init (options)
	assert(type(options) == "table", "options must be a table")
	assert(options.cassandra, "options.cassandra is missing")
	assert(options.cassandra.host, "missing options.cassandra.host field")
	assert(options.cassandra.port, "missing options.cassandra.port field")

	for k,v in pairs(options.logger) do
		logger[k] = v
	end
	setmetatable(logger, getmetatable(options.logger))

	m_allegroRepository:Initialize(options)
	cassandra.seed(options.cassandra.host, options.cassandra.port)

	m_repositoryNodes = options.repositoryNodes and m_helpers.table:DeepCopy(options.repositoryNodes) or nil

	return model:initialize()
end

return {
	init = init,
	make_store_sink = make_store_sink,
	file_exists = file_exists,
	retrieve_file = retrieve_file,
	erase_file = erase_file,
	erase_batch_files = erase_batch_files,
	make_file_id = make_file_id,
	allegro = {
		-- #18610 getHeaders param added. If true then retrieve the headers from request.
		-- If all ok the response will have a table with the headers and the file: true, { headers : {}, file : binarydata }
		retrieve_file = function(vcc, id, section, part, stream, getHeaders)
			return m_allegroRepository:Retrieve(vcc, id, section, part, stream, getHeaders)
		end
	}
}
