local gettime = require "luatz.gettime".gettime
local timetable_mt = require "luatz.timetable".timetable_mt

local function to_timestamp ( o )
	if type ( o ) == "number" then
		return o
	elseif getmetatable ( o ) == timetable_mt then
		return o:timestamp ( )
	end
end

local tz_info_methods = { }
local tz_info_mt = {
	__index = tz_info_methods ;
}
local tt_info_mt = {
	__tostring = function ( self )
		return string.format ( "tt_info:%s=%d" , self.abbr , self.gmtoff )
	end ;
}

-- Binary search
local function _find_current ( tzinfo , target , i , j )
	if i >= j then return j end

	local half = math.ceil ( (j+i) / 2 )

	if target >= tzinfo [ half ].transition_time then
		return _find_current ( tzinfo , target , half , j )
	else
		return _find_current ( tzinfo , target , i , half-1 )
	end
end

local function find_current_local ( tzinfo , ts_local )
	-- Find two best possibilities by searching back and forward a day (assumes transition is never by more than 24 hours)
	local tz_first = _find_current ( tzinfo , ts_local-86400 , 0 , #tzinfo )
	local tz_last  = _find_current ( tzinfo , ts_local+86400 , 0 , #tzinfo )

	local n_candidates = tz_last - tz_first + 1

	if n_candidates == 1 then
		return tz_first
	elseif n_candidates == 2 then
		local tz_first_ob = tzinfo [ tz_first ]
		local tz_last_ob  = tzinfo [ tz_last ]

		local first_gmtoffset = tz_first_ob.info.gmtoff
		local last_gmtoffset  = tz_last_ob .info.gmtoff

		local t_start = tz_last_ob.transition_time + first_gmtoffset
		local t_end   = tz_last_ob.transition_time + last_gmtoffset

		-- If timestamp is before start or after end
		if ts_local < t_start then
			return tz_first
		elseif ts_local > t_end then
			return tz_last
		end

		-- If we get this far, the local time is ambiguous
		return tz_first , tz_last
	else
		error ( "Too many transitions in a 2 day period" )
	end
end

function tz_info_methods:find_current ( current )
	current = assert ( to_timestamp ( current ) , "invalid timestamp to :find_current" )
	return self [ _find_current ( self , current , 0 , #self ) ].info
end

function tz_info_methods:localise ( utc_ts )
	utc_ts = utc_ts or gettime ( )
	return utc_ts + self:find_current ( utc_ts ).gmtoff
end
tz_info_methods.localize = tz_info_methods.localise

function tz_info_methods:utctime ( ts_local )
	ts_local = assert ( to_timestamp ( ts_local ) , "invalid timestamp to :utctime" )
	local tz1 , tz2 = find_current_local ( self , ts_local )
	tz1 = self [ tz1 ].info
	if tz2 == nil then
		return ts_local - tz1.gmtoff
	else -- Local time is ambiguous
		tz2 = self [ tz2 ].info

		return ts_local - tz2.gmtoff , ts_local - tz2.gmtoff
	end
end

return {
	tz_info_mt = tz_info_mt ;
	tt_info_mt = tt_info_mt ;
}
