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

local md5 = require "resty.md5"
local str = require "resty.string"
local bson = require ( mod_name .. ".bson" )
local object_id = require ( mod_name .. ".object_id" )
local gridfs_file= require ( mod_name .. ".gridfs_file" )

local gridfs_mt = { }
local gridfs = { __index = gridfs_mt }
local get_bin_data = bson.get_bin_data
local get_utc_date = bson.get_utc_date

function gridfs_mt:find_one(fields)
    local r = self.file_col:find_one(fields)
    if not r then return nil end

    return setmetatable({
                    file_col = self.file_col;
                    chunk_col = self.chunk_col;
                    chunk_size = r.chunkSize;
                    files_id = r._id;
                    file_size = r.length;
                    file_md5 = r.md5;
                    file_name = r.filename;
                    }, gridfs_file)
end

function gridfs_mt:get(fh, fields)
    local f = self:find_one(fields)
    if not f then return nil, "file not found" end
    local r = fh:write(f:read())
    return r
end

function gridfs_mt:remove(fields, continue_on_err, safe)
    local r, err 
    local n = 0
    if fields == {} then
        r,err = self.chunk_col:delete({}, continue_on_err, safe) 
        if not r then return nil, "remove chunks failed: "..err end
        r,err = self.file_col:delete({}, continue_on_err, safe) 
        if not r then return nil, "remove files failed: "..err end
        return r
    end
        
    local cursor = self.file_col:find(fields, {_id=1})
    for k,v in cursor:pairs() do
        r,err = self.chunk_col:delete({files_id=v._id}, continue_on_err, safe) 
        if not r then return nil, "remove chunks failed: "..err end
        r,err = self.file_col:delete({_id=v._id}, continue_on_err, safe) 
        if not r then return nil, "remove files failed: "..err end
        n = n + 1
    end
    return n
end

function gridfs_mt:new(meta)
    meta = meta or {}
    meta._id = meta._id or object_id.new()
    meta.chunkSize = meta.chunkSize or 256*1024
    meta.filename = meta.filename or meta._id:tostring()

    meta.md5 = 0
    meta.uploadDate = get_utc_date(ngx.time() * 1000)
    meta.length = 0
    local r, err = self.file_col:insert({meta}, nil, true)
    if not r then return nil, err end

    return setmetatable({
                    file_col = self.file_col;
                    chunk_col = self.chunk_col;
                    chunk_size = meta.chunkSize;
                    files_id = meta._id;
                    file_size = 0;
                    file_md5 = 0;
                    file_name = meta.filename;
                    }, gridfs_file)
end

function gridfs_mt:insert(fh, meta, safe)
    meta = meta or {}
    meta.chunkSize = meta.chunkSize or 256*1024
    meta._id = meta._id or object_id.new()
    meta.filename = meta.filename or meta._id:tostring()

    local n = 0
    local length = 0
    local r, err 
    local md5_obj = md5:new()
    while true do
        local bytes = fh:read(meta.chunkSize)
        if not bytes then break end

        md5_obj:update(bytes)
        r, err = self.chunk_col:insert({{ files_id = meta._id,
                                n = n,
                                data = get_bin_data(bytes),
                                }}, nil, safe)
        if safe and not r then return nil, err end

        n = n + 1
        length = length + string.len(bytes)
    end
    local md5hex = str.to_hex(md5_obj:final())

    meta.md5 = md5hex
    meta.uploadDate = get_utc_date(ngx.time() * 1000)
    meta.length = length
    r, err = self.file_col:insert({meta}, nil, safe)
    if safe and not r then return nil, err end
    return r
end

return gridfs
