-- EFL LuaJIT bindings: Eina (file module)
-- For use with Elua

local ffi = require("ffi")
local C = ffi.C

local tonum = ffi.tonumber or tonumber

local iterator = require("eina.iterator")
require("eina.xattr")

ffi.cdef [[
    typedef unsigned char Eina_Bool;
    typedef long time_t;

    typedef struct _Eina_File_Direct_Info Eina_File_Direct_Info;
    typedef struct _Eina_Stat             Eina_Stat;
    typedef struct _Eina_File_Line        Eina_File_Line;

    typedef void (*Eina_File_Dir_List_Cb)(const char *name, const char *path,
        void *data);

    typedef Eina_Bool (*Eina_File_Copy_Progress)(void *data,
        unsigned long long done, unsigned long long total);

    typedef enum {
        EINA_FILE_UNKNOWN,
        EINA_FILE_FIFO,
        EINA_FILE_CHR,
        EINA_FILE_DIR,
        EINA_FILE_BLK,
        EINA_FILE_REG,
        EINA_FILE_LNK,
        EINA_FILE_SOCK,
        EINA_FILE_WHT
    } Eina_File_Type;

    typedef struct _Eina_File Eina_File;

    typedef enum {
        EINA_FILE_RANDOM,
        EINA_FILE_SEQUENTIAL,
        EINA_FILE_WILLNEED,
        EINA_FILE_POPULATE,
        EINA_FILE_DONTNEED,
        EINA_FILE_REMOVE
    } Eina_File_Populate;

    struct _Eina_File_Direct_Info {
        size_t               path_length;
        size_t               name_length;
        size_t               name_start;
        Eina_File_Type       type;
        char                 path[8192];
    };

    struct _Eina_Stat {
        unsigned long int    dev;
        unsigned long int    ino;
        unsigned int         mode;
        unsigned int         nlink;
        unsigned int         uid;
        unsigned int         gid;
        unsigned long int    rdev;
        unsigned long int    size;
        unsigned long int    blksize;
        unsigned long int    blocks;
        unsigned long int    atime;
        unsigned long int    atimensec;
        unsigned long int    mtime;
        unsigned long int    mtimensec;
        unsigned long int    ctime;
        unsigned long int    ctimensec;
    };

    struct _Eina_File_Line {
       const char *start;
       const char *end;
       unsigned int index;
       unsigned long long length;
    };

    Eina_Bool eina_file_dir_list(const char *dir, Eina_Bool recursive,
        Eina_File_Dir_List_Cb cb, void *data);
    Eina_Iterator *eina_file_ls(const char *dir);
    Eina_Iterator *eina_file_stat_ls(const char *dir);
    int eina_file_statat(void *container, Eina_File_Direct_Info *info,
        Eina_Stat *buf);
    Eina_Iterator *eina_file_direct_ls(const char *dir);
    char *eina_file_path_sanitize(const char *path);

    typedef enum {
        EINA_FILE_COPY_DATA       = 0,
        EINA_FILE_COPY_PERMISSION = (1 << 0),
        EINA_FILE_COPY_XATTR      = (1 << 1)
    } Eina_File_Copy_Flags;

    Eina_Bool eina_file_copy(const char *src, const char *dst,
        Eina_File_Copy_Flags flags, Eina_File_Copy_Progress cb,
        const void *cb_data);
    Eina_File *eina_file_open(const char *name, Eina_Bool shared);
    Eina_Bool eina_file_unlink(const char *pathname);
    Eina_File *eina_file_virtualize(const char *virtual_name,
        const void *data, unsigned long long length, Eina_Bool copy);
    Eina_Bool eina_file_virtual(Eina_File *file);
    Eina_Bool eina_file_refresh(Eina_File *file);
    Eina_File *eina_file_dup(const Eina_File *file);
    void eina_file_close(Eina_File *file);
    size_t eina_file_size_get(const Eina_File *file);
    time_t eina_file_mtime_get(const Eina_File *file);
    const char *eina_file_filename_get(const Eina_File *file);
    Eina_Iterator *eina_file_xattr_get(Eina_File *file);
    Eina_Iterator *eina_file_xattr_value_get(Eina_File *file);
    void *eina_file_map_all(Eina_File *file, Eina_File_Populate rule);
    void *eina_file_map_new(Eina_File *file, Eina_File_Populate rule,
        unsigned long int offset, unsigned long int length);
    void eina_file_map_free(Eina_File *file, void *map);
    void eina_file_map_populate(Eina_File *file, Eina_File_Populate rule,
        const void *map, unsigned long int offset, unsigned long int length);
    Eina_Iterator *eina_file_map_lines(Eina_File *file);
    Eina_Bool eina_file_map_faulted(Eina_File *file, void *map);

    void eina_stringshare_del   (const char *str);
    int  eina_stringshare_strlen(const char *str);

    char *strerror(int);
    void free(void*);
]]

local cutil = require("cutil")
local util  = require("util")

local Object = util.Object

local M = {}

local eina

local init = function()
    eina = util.lib_load("eina")
end

local shutdown = function()
    util.lib_unload("eina")
end

cutil.init_module(init, shutdown)

M.dir_list = function(dir, recursive, cb)
    if not cb or not dir then return false end
    local cbp = ffi.cast("Eina_File_Dir_List_Cb", function(name, path, data)
        return cb(name, path)
    end)
    local v = eina.eina_file_dir_list(dir, recursive or false, cbp, nil)
    cbp:free()
    return v ~= 0
end

local Iterator = iterator.Iterator

local Ls_Iterator = Iterator:clone {
    __ctor = function(self, selfmt, dir)
        return Iterator.__ctor(self, selfmt, eina.eina_file_ls(dir))
    end,

    next = function(self)
        local  v = Iterator.next(self)
        if not v then return nil end
        local  r = ffi.string(v, eina.eina_stringshare_strlen(v))
        eina.eina_stringshare_del(v)
        return r
    end
}

M.ls = function(dir) return Ls_Iterator(dir) end

local file_type_map = {
    [C.EINA_FILE_UNKNOWN] = "unknown",
    [C.EINA_FILE_FIFO]    = "fifo",
    [C.EINA_FILE_CHR]     = "chr",
    [C.EINA_FILE_DIR]     = "dir",
    [C.EINA_FILE_BLK]     = "blk",
    [C.EINA_FILE_REG]     = "reg",
    [C.EINA_FILE_LNK]     = "lnk",
    [C.EINA_FILE_SOCK]    = "sock",
    [C.EINA_FILE_WHT]     = "wht",
}

local Direct_Info = Object:clone {
    __ctor = function(self, path, name_start, name_length, tp)
        self.path        = path
        self.name_start  = name_start
        self.name_length = name_length
        self.type        = tp
    end,

    statat = function(self, container)
        if not container then return false, "invalid container" end
        local info = ffi.new("Eina_File_Direct_Info", #self.path,
            self.name_length, self.name_start - 1,
            C["EINA_FILE_" .. self.type:upper()], self.path)
        local buf = ffi.new("Eina_Stat")
        if eina.eina_file_statat(container, info, buf) ~= 0 then
            return false, ffi.string(C.strerror(ffi.errno()))
        end
        return buf
    end
}

local direct_info_iterator_next = function(self)
    local  v = Iterator.next(self)
    if not v then return nil end
    local s = ffi.cast("Eina_File_Direct_Info*", v)
    local path = ffi.string(s.path, s.path_length)
    local ns = tonum(s.name_start)
    local nl = tonum(s.name_length)
    local tp = file_type_map[s.type]
    return Direct_Info(path, ns, nl, tp), self:container_get()
end

local Stat_Ls_Iterator = Iterator:clone {
    __ctor = function(self, selfmt, dir)
        return Iterator.__ctor(self, selfmt, eina.eina_file_stat_ls(dir))
    end,
    next = direct_info_iterator_next
}

M.stat_ls = function(dir) return Stat_Ls_Iterator(dir) end

local Direct_Ls_Iterator = Iterator:clone {
    __ctor = function(self, selfmt, dir)
        return Iterator.__ctor(self, selfmt, eina.eina_file_direct_ls(dir))
    end,
    next = direct_info_iterator_next
}

M.direct_ls = function(dir) return Direct_Ls_Iterator(dir) end

M.path_sanitize = function(path)
    local v = eina.eina_file_path_sanitize(path)
    if v == ffi.nullptr then return nil end
    local r = ffi.string(v)
    C.free(v)
    return r
end

M.copy_flags = {
    DATA = 0, PERMISION = 1, XATTR = 2
}

M.copy = function(source, destination, flags, cb)
    if not source or not destination then return false end
    flags = flags or 0
    local cbp
    if cb then
        cbp = ffi.cast("Eina_File_Copy_Progress", function(data, done, total)
            return not not cb(done, total)
        end)
    end
    local v = eina.eina_file_copy(source, destination, flags, cbp, nil)
    if cbp then cbp:free() end
    return v ~= 0
end

local Xattr_Iterator = Iterator:clone {
    __ctor = function(self, selfmt, file)
        return Iterator.__ctor(self, selfmt, eina.eina_file_xattr_get(file))
    end,
    next = function(self)
        local  v = Iterator.next(self)
        if not v then return nil end
        return ffi.string(v)
    end
}

local Xattr_Value_Iterator = Iterator:clone {
    __ctor = function(self, selfmt, file)
        return Iterator.__ctor(self, selfmt,
            eina.eina_file_xattr_value_get(file))
    end,
    next = function(self)
        local  v = Iterator.next(self)
        if not v then return nil end
        v = ffi.cast(v, "Eina_Xattr*")
        return ffi.string(v.name), ffi.string(v.value, v.length)
    end
}

M.populate     = {
    RANDOM     = 0,
    SEQUENTIAL = 1,
    WILLNEED   = 2,
    POPULATE   = 3,
    DONTNEED   = 4,
    REMOVE     = 5
}

local Line_Iterator = Iterator:clone {
    __ctor = function(self, selfmt, file)
        return Iterator.__ctor(self, selfmt, eina.eina_file_map_lines(file))
    end,
    next = function(self)
        local  v = Iterator.next(self)
        if not v then return nil end
        v = ffi.cast(v, "Eina_File_Line*")
        return ffi.string(v.start, v.length), tonum(v.index)
    end
}

M.File = ffi.metatype("Eina_File", {
    __new = function(self, name, shared)
        return self.open(name, shared)
    end,
    __len = function(self)
        return self:size_get()
    end,
    __index = {
        open = function(name, shared)
            return eina.eina_file_open(name, shared)
        end,

        unlink = function(self)
            return eina.eina_file_unlink(pathname)
        end,

        virtualize = function(vname, data, length, copy)
            return eina.eina_file_virtualize(vname, data, length,
                copy or false)
        end,

        close = function(self)
            return eina.eina_file_close(self)
        end,

        dup = function(self)
            return eina.eina_file_dup(self)
        end,

        is_virtual = function(self)
            return eina.eina_file_virtual(self) ~= 0
        end,
        refresh = function(self)
            return eina.eina_file_refresh(self) ~= 0
        end,

        size_get = function(self)
            return tonum(eina.eina_file_size_get(self))
        end,

        mtime_get = function(self)
            return tonum(eina.eina_file_mtime_get(self))
        end,

        filename_get = function(self)
            return ffi.string(eina.eina_file_filename_get(self))
        end,

        xattr_get = function(self) return Xattr_Iterator(self) end,
        xattr_value_get = function(self) Xattr_Value_Iterator(self) end,

        map_all = function(self, rule, raw)
            local v = ffi.cast("char*", eina.eina_file_map_all(self, rule or 0))
            if v == ffi.nullptr then return nil end
            if not raw then
                local r = ffi.string(v)
                self:map_free(v)
                return r
            end
            return v
        end,

        map_new = function(self, rule, offset, length, raw)
            local v = ffi.cast("char*", eina.eina_file_map_new(self, rule or 0,
                offset or 0, length))
            if v == ffi.nullptr then return nil end
            if not raw then
                local r = ffi.string(v, length)
                self:map_free(v)
                return r
            end
            return v
        end,

        map_free = function(self, map)
            return eina.eina_file_map_free(self, map)
        end,

        map_populate = function(self, rule, map, offset, length)
            return eina.eina_file_map_populate(self, rule or 0, offset or 0,
                length)
        end,

        map_faulted = function(self, map)
            return eina.eina_file_map_faulted(self, map) ~= 0
        end,

        lines = function(self) return Line_Iterator(self) end
    }
})

return M
