local mod_name = (...):match ( "^(.*)%..-$" )

local setmetatable = setmetatable
local strbyte = string.byte
local strformat = string.format
local t_insert = table.insert
local t_concat = table.concat

local hasposix , posix = pcall ( require , "posix" )

local ll = require ( mod_name .. ".ll" )
local num_to_le_uint = ll.num_to_le_uint
local num_to_be_uint = ll.num_to_be_uint

local function _tostring(ob)
    local t = {}
    for i = 1 , 12 do
        t_insert(t, strformat("%02x", strbyte(ob.id, i, i))) 
    end
    return t_concat(t)
end

local function _get_ts(ob)
    return ll.be_uint_to_num(ob.id, 1, 4)
end

local function _get_hostname(ob)
    local t = {}
    for i = 5, 7 do
        t_insert(t, strformat("%02x", strbyte(ob.id, i, i))) 
    end
    return t_concat(t)
end

local function _get_pid(ob)
    return ll.be_uint_to_num(ob.id, 8, 9)
end

local function _get_inc(ob)
    return ll.be_uint_to_num(ob.id, 10, 12)
end

local object_id_mt = {
    __tostring = _tostring;
    __eq = function ( a , b ) return a.id == b.id end ;
}

local machineid
if hasposix then
    machineid = posix.uname("%n")
else
    machineid = assert(io.popen("uname -n")):read("*l")
end
machineid = ngx.md5_bin(machineid):sub(1, 3)

local pid = num_to_le_uint(ngx.var.pid, 2)

local inc = 0
local function generate_id ( )
    inc = inc + 1
    -- "A BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp (seconds since epoch), a 3-byte machine id, a 2-byte process id, and a 3-byte counter. Note that the timestamp and counter fields must be stored big endian unlike the rest of BSON"
    return num_to_be_uint ( os.time ( ) , 4 ) .. machineid .. pid .. num_to_be_uint ( inc , 3 )
end

local function new_object_id(str)
    if str then
        assert(#str == 12)
    else
        str = generate_id()
    end
    return setmetatable({id = str,
                        tostring = _tostring,
                        get_ts = _get_ts,
                        get_pid = _get_pid,
                        get_hostname = _get_hostname,
                        get_inc = _get_inc,
                        } , object_id_mt)
end

return {
    new = new_object_id ;
    metatable = object_id_mt ;
}
