local function is_number (v) return type(v) == "number" end
local function is_nil (v) return type(v) == "nil" end
local function is_string (v) return type(v) == "string" end
local function is_table (v) return type(v) == "table" end

local logger = {}
package.preload["ticketek.logger"] = function()
	return logger
end

local helpers = {}
package.preload["ticketek.helpers"] = function()
	return helpers
end

---
-- Returns the vcc (that's anything from the rightmost @)
--
function helpers.extract_vcc (value)
	return value:match("@(.-)$")
end

---
-- Clones a table, recursively.
--
function helpers.clone (t)
	local new = {}
	for k,v in pairs(t) do
		if type(k) == "table" then
			k = clone(k)
		end
		if type(v) == "table" then
			v = clone(v)
		end
		new[k] = v
	end
	return new
end

local Emitter = require "ticketek.emitter"
local DataAccess = require "ticketek.data_access"
local Binds = require "ticketek.binds"

---
-- Takes a configuration table and sets up Cassandra and Redis
--
local function init (options)
	assert(is_table(options), "options must be a table")
	assert(is_table(options.cassandra), "options.cassandra is missing")
	assert(is_string(options.cassandra.host), "missing options.cassandra.host field")
	assert(is_number( tonumber(options.cassandra.port) ), "missing options.cassandra.port field")
	assert(is_table(options.redis), "options.redis is missing")
	assert(is_string(options.redis.host), "missing options.redis.host field")
	assert(is_number( tonumber(options.redis.port) ), "missing options.redis.port field")

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

	DataAccess.init(options.cassandra, options.redis)

	return true
end

---
-- Input validation
local function create_valid_data (vcc, options)
	assert(is_string(vcc), "a 'vcc' must be provided")
	assert(is_table(options) and next(options))	-- non-empty table

	assert(is_string(options.description), "a 'description' must be provided")

	local data = {}
	for k,v in pairs(options) do
		data[k] = v
	end

	assert(is_string(data.prefix) or is_nil(data.prefix))
	assert(is_string(data.suffix) or is_nil(data.suffix))
	assert(is_string(data.number_type) and (data.number_type == "autoincrement" or data.number_type == "date"))
	-- data.base_number is either a number or nil (and it defaults to 0)
	assert(is_number(data.base_number) or is_nil(data.base_number))
	-- data.number_width is either a number or nil (and it defaults to 0)
	assert(is_number(data.number_width) or is_nil(data.number_width))
	data.number_width = data.number_width or 0
	assert(data.number_width <= 99) -- this is the maximum value we can use in a formatter

	if data.number_type == "autoincrement" then
		data.base_number = data.base_number or 0
	elseif data.number_type == "date" then
		data.base_number = data.base_number or 0
		data.date_format = "%Y%m%d" -- year, month, day
	end

	return data
end

---
-- Creates a new ticket emitter
--
local function new (vcc, options)
	local data = create_valid_data(vcc, options)
	local emitter = Emitter.new(vcc, data)
	return emitter
end

---
-- Updates the data of an existing emitter
--
local function update (vcc, emitter_id, options)
	local data = create_valid_data(vcc, options)
	local emitter = Emitter.update(emitter_id, data)
	return emitter
end

---
-- Retrieve available ticket emitters for a given vcc.
-- We only retrieve the id and its description
--
local function get_by_vcc (vcc)
	assert(is_string(vcc), "a 'vcc' must be provided")
	return DataAccess.get_by_vcc(vcc)
end

---
-- Retrieves an emitter with a given id.
--
local function get (id)
	assert(is_string(id), "an 'id' must be provided")
	local emitter = Emitter.get(id)
	if not emitter then
		return nil, { code = "E_NOT_FOUND", msg = "Emitter not found", emitter_id = id }
	end
	return emitter
end

---
-- Deletes an emitter given its id.
--
local function delete (id)
	return DataAccess.delete(id)
end

---
-- Emits a new ticket, using a given emitter.
--
local function emit (id)
	assert(is_string(id), "an 'id' must be provided")
	local emitter = Emitter.get(id)
	if not emitter then
		return nil, { code = "E_NOT_FOUND", msg = "Emitter not found", emitter_id = id }
	end
	return emitter:emit()
end

---
--
local function preview (data)
	local vcc = "foo" -- whatever, we don't care about this data when previewing
	data.description = "foo"
	local data = create_valid_data(vcc, data)
	local emitter = Emitter.create(vcc, data)
	return emitter:preview()
end

--
-- Exported functions
--
return {
	init = init,
	new = new,
	update = update,
	get_by_vcc = get_by_vcc,
	get = get,
	emit = emit,
	delete = delete,
	preview = preview,
	binds = Binds  -- link, unlink and lookup
}
