--[[
	Licensed according to the included 'LICENSE' document
	Author: Thomas Harning Jr <harningt@gmail.com>
]]
local jsonutil = require("json.util")

local type = type
local pairs = pairs
local assert = assert

local table = require("table")
local math = require("math")
local table_concat = table.concat
local math_floor, math_modf = math.floor, math.modf

local jsonutil = require("json.util")
local util_IsArray = jsonutil.IsArray

local _ENV = nil

local defaultOptions = {
	isArray = util_IsArray
}

local modeOptions = {}

local function mergeOptions(options, mode)
	jsonutil.doOptionMerge(options, false, 'array', defaultOptions, mode and modeOptions[mode])
end

--[[
	Utility function to determine whether a table is an array or not.
	Criteria for it being an array:
		* ExternalIsArray returns true (or false directly reports not-array)
		* If the table has an 'n' value that is an integer >= 1 then it
		  is an array... may result in false positives (should check some values
		  before it)
		* It is a contiguous list of values with zero string-based keys
]]
local function isArray(val, options)
	local externalIsArray = options and options.isArray

	if externalIsArray then
		local ret = externalIsArray(val)
		if ret == true or ret == false then
			return ret
		end
	end
	-- Use the 'n' element if it's a number
	if type(val.n) == 'number' and math_floor(val.n) == val.n and val.n >= 1 then
		return true
	end
	local len = #val
	for k,v in pairs(val) do
		if type(k) ~= 'number' then
			return false
		end
		local _, decim = math_modf(k)
		if not (decim == 0 and 1<=k) then
			return false
		end
		if k > len then -- Use Lua's length as absolute determiner
			return false
		end
	end

	return true
end

--[[
	Cleanup function to unmark a value as in the encoding process and return
	trailing results
]]
local function unmarkAfterEncode(tab, state, ...)
	state.already_encoded[tab] = nil
	return ...
end
local function getEncoder(options)
	options = options and jsonutil.merge({}, defaultOptions, options) or defaultOptions
	local function encodeArray(tab,  state)
		if not isArray(tab, options) then
			return false
		end
		-- Make sure this value hasn't been encoded yet
		state.check_unique(tab)
		local encode = state.encode
		local compositeEncoder = state.outputEncoder.composite
		local valueEncoder = [[
		for i = 1, (composite.n or #composite) do
			local val = composite[i]
			PUTINNER(i ~= 1)
			val = encode(val, state)
			val = val or ''
			if val then
				PUTVALUE(val)
			end
		end
		]]
		return unmarkAfterEncode(tab, state, compositeEncoder(valueEncoder, '[', ']', ',', tab, encode, state))
	end
	return { table = encodeArray }
end

local array = {
	mergeOptions = mergeOptions,
	isArray = isArray,
	getEncoder = getEncoder
}

return array