local uuid = require "uuid"
local data_access = require "ticketek.data_access"
local helpers = require "ticketek.helpers"
local date = require "os".date

---
-- Computes a new sequential value
--
local function get_new_sequential_value (emitter)
	local new_value = data_access.increment(emitter.id)
	return new_value
end

---
-- Converts a number to a string using a custom precision to avoid scientific notation on big numbers
--
local function safe_tostring (value)
	assert(type(value) == "number")
	return ("%.17g"):format(value)
end

---
-- Produces a proper value. It converts it to string to avoid scientific notation issues caused by tostring.
-- It also adds a date when needed.
--
local function produce_value (seq_value, emitter)
	if type(seq_value) == "number" then
		seq_value = safe_tostring(seq_value)
	end
	if emitter.number_width > 0 then
		seq_value = ("%"..emitter.number_width.."s"):format(seq_value)  -- i.e. ("%010s"):format(value)
		seq_value = seq_value:gsub(" ", "0")
	end

	if emitter.number_type == "autoincrement" then
		return seq_value
	end
	-- the emitter is of type 'date'
	-- this just allows us to "freeze time". It is only used in the tests.
	local date_fn = emitter.date_fn or date
	return date_fn(emitter.date_format)..seq_value
end

---
-- Formats a given value using the provided emitter.
-- Takes care of padding, prefixes, suffixes and all that jazz.
--
local function format_value (value, emitter)
	-- padding ? -- note: I use %s instead of %d to avoid numeric precision issues
	-- I also can't use ("%010s"):format(value) because libc is trolling me (it ignores the 0)
	-- And finally, the zero padding works with %i or %d but then I run into representation issues and overflows.
	-- fuck
	if type(value) == "number" then
		value = safe_tostring(value)
	end
	if emitter.prefix or emitter.suffix then
		value = ("%s%s%s"):format(emitter.prefix or "", value, emitter.suffix or "")
	end

	return tostring(value)
end

---
-- Emitter class
--
local Emitter_mt = {}
Emitter_mt.__index = Emitter_mt

---
-- Produces a new ticket.
--
function Emitter_mt:emit ()
	-- fucking lisp
	return format_value( produce_value( get_new_sequential_value(self), self ), self )
end

---
-- Resets the emitter to its base number. Must be used with care (IE, don't, if you already emitted tickets)
--
function Emitter_mt:reset ()
	data_access.reset(self.id, self.base_number)
end

---
--
function Emitter_mt:preview ()
	local warnings = {}

	local min_ticket = format_value( produce_value( self.base_number, self ), self )

	-- produce a value using the max integer available in Lua
	local max_value = produce_value( 2 ^ 53, self )

	local max_ticket = format_value( max_value, self )

	if self.number_width > 0 and #max_value > self.number_width then
		warnings.overflow = true
		warnings.suggested_width = #max_value
		if self.number_type == "autoincrement" then
			-- i.e. width = 5 -> 10 ^ 5 - 1 = 99999
			warnings.max_value_no_overflow = ("9"):rep(self.number_width)--format_value( 10 ^ number_width - 1, emitter )
		else
			-- dates are more involved. we must compute how many digits the autonumbering part can have so we don't
			-- exceed date_digits + autonumber_digits <= number_width
			
			local autonumber_digits = #safe_tostring(self.base_number)
			local date_digits = #produce_value(self.base_number, self):sub(1, -(autonumber_digits + 1))
			local width = self.number_width - (date_digits + autonumber_digits)
			if width < 0 then
				-- the date takes the whole space, I can't even put the autonumber
				-- leave the max number suggestion
				warnings.max_value_no_overflow = 0
			else
				-- we can fit a number in there
				warnings.max_value_no_overflow = ("9"):rep(width)
			end
			--print("***", date_digits, autonumber_digits, number_width, width)
			--print(warnings.suggested_width, warnings.max_value_no_overflow)
		end
	end
	return min_ticket, max_ticket, warnings
end

---
-- Emitter module
--
local Emitter = {}

---
--
function Emitter.get (id)
	local data, err = data_access.get_by_id(id)
	if not data then
		-- emitter not found
		return nil
	end
	return setmetatable(data, Emitter_mt)
end

---
-- Creates a new emitter
--
function Emitter.create (vcc, data)
	local new_id = uuid.new():gsub("%-", "")
	new_id = new_id .. "@" .. vcc

	local emitter = {}
	emitter.id = new_id
	for k,v in pairs(data) do emitter[k] = v end

	return setmetatable(emitter, Emitter_mt)
end

---
-- Creates a new emitter and saves it
--
function Emitter.new (vcc, data)
	local emitter = Emitter.create(vcc, data)
	local ok, err = data_access.save_new(emitter)
	if ok then
		return emitter
	end
	-- TODO: deal with errors
end

function Emitter.update (emitter_id, new_data)
	local ok, err = data_access.update(emitter_id, new_data)
	if not ok then
		return nil, err
	end

	local emitter = { id = emitter_id }
	for k,v in pairs(new_data) do emitter[k] = v end

	return setmetatable(emitter, Emitter_mt)
end

return Emitter
