/*
 * Copyright 2024-present Alibaba Cloud.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "JdoFileSystem.hpp"

#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>
#include <cstdlib>
#include <pwd.h>
#include <unistd.h>

#include "JdoStatus.hpp"
#include "JdoFileInfo.hpp"
#include "JdoListResult.hpp"
#include "JdoContentSummary.hpp"

#include "jdo_common.h"
#include "jdo_api.h"
#include "jdo_defines.h"
#include "jdo_error.h"
#include "jdo_options.h"
#include "jdo_file_status.h"
#include "jdo_list_dir_result.h"
#include "jdo_content_summary.h"
#include "jdo_file_buffers.h"
#include "jdo_longs.h"

#define START_CALL(fs) \
    JdoHandleCtx_t ctx = jdo_createHandleCtx1(fs);

#define START_CALL2(fs, stream) \
    JdoHandleCtx_t ctx = jdo_createHandleCtx2(fs, stream);

#define END_CALL() \
    auto errorCode = jdo_getHandleCtxErrorCode(ctx); \
    std::string errorMsg;                            \
    const char* msg = jdo_getHandleCtxErrorMsg(ctx); \
    if (msg != nullptr) {                            \
        errorMsg.assign(msg);                        \
    }                                                \
    jdo_freeHandleCtx(ctx);

static void covertToFileInfo(JdoFileStatus_t fileStatus,
                             JdoFileInfo* info) {
    if (!fileStatus) return;
    auto path = jdo_getFileStatusPath(fileStatus);
    if (path) {
        info->path = path;
    }
    auto user = jdo_getFileStatusUser(fileStatus);
    if (user) {
        info->user = user;
    }
    auto group = jdo_getFileStatusGroup(fileStatus);
    if (group) {
        info->group = group;
    }
    info->type = jdo_getFileStatusType(fileStatus);
    info->perm = jdo_getFileStatusPerm(fileStatus);
    info->length = jdo_getFileStatusSize(fileStatus);
    info->mtime = jdo_getFileStatusMtime(fileStatus);
    info->atime = jdo_getFileStatusAtime(fileStatus);
    if (info->isDir() && info->path.back() != '/') {
        info->path.push_back('/');
    }
}

class JindoOperation : public JdoTask {
public:
    JindoOperation(JdoStore_t store, JdoOperationCall_t call)
            : JdoTask(), _store(store), _stream(nullptr), _call(call) {}

    JindoOperation(JdoStore_t store, JdoIOContext_t stream, JdoOperationCall_t call)
            : JdoTask(), _store(store), _stream(stream), _call(call) {}

    ~JindoOperation() override {
        if (_call) {
            jdo_freeOperationCall(_call);
            _call = nullptr;
        }
    }

    JdoStatus perform() override {
        START_CALL2(_store, _stream)
        jdo_perform(ctx, _call);
        END_CALL();
        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "failed to perform, errmsg: ", errorMsg);
        }
        return JdoStatus::OK();
    }

    JdoStatus wait() override {
        START_CALL2(_store, _stream)
        jdo_wait(ctx, _call);
        END_CALL();
        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "failed to wait, errmsg: ", errorMsg);
        }
        return JdoStatus::OK();
    }

    JdoStatus cancel() override {
        START_CALL2(_store, _stream)
        jdo_cancel(ctx, _call);
        END_CALL();
        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "failed to cancel, errmsg: ", errorMsg);
        }
        return JdoStatus::OK();
    }

private:
    JdoStore_t _store = nullptr;
    JdoIOContext_t _stream = nullptr;
    JdoOperationCall_t _call = nullptr;
};

struct ReadArgs {
    JdoStore_t store = nullptr;
    JdoIOContext_t reader = nullptr;
    std::string name;
    size_t n = 0;
    std::string_view *result;
    char *scratch;
    std::function<void(JdoStatus)> callback;
};

static void readCallback(JdoHandleCtx_t ctx, int64_t num_read, void *userdata) {
    ReadArgs *args = (ReadArgs *) userdata;
    auto store = args->store;
    auto reader = args->reader;
    auto name = args->name;
    auto n = args->n;
    auto result = args->result;
    auto scratch = args->scratch;
    auto total_read = num_read;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    if (errorCode != 0) {
        auto status = JdoStatus::InternalError(errorCode, "failed to read ", name,
                                          " size ", n - total_read, " readed ", num_read,
                                          ", errmsg: ", errorMsg);
        if (callback) {
            callback(status);
        }
        return;
    }

    JdoStatus status;
    if (total_read >= 0) {
        while (n - total_read > 0) {
            START_CALL2(store, reader)
            num_read = jdo_read(ctx, scratch + total_read, n - total_read, nullptr);
            END_CALL()
            if (errorCode != 0) {
                status = JdoStatus::InternalError(errorCode, "failed to read ", name,
                                                  " size ", n - total_read, " readed ", num_read,
                                                  ", errmsg: ", errorMsg);
                if (callback) {
                    callback(status);
                }
                return;
            }
            if (num_read < 0) {
                // isEOF
                break;
            }
            total_read += num_read;
        }
    }

    if ((total_read < 0 || total_read < n) && num_read != -1) {
        // if there was an error before satisfying the current read, this logic declares it an error
        // and does not try to return any of the bytes read. Don't think it matters, so the code
        // is just being conservative.
        status = JdoStatus::IOError("Failed to read, ", name,
                                    ", expect read ", n, " bytes"
                                    ", but ", total_read, " bytes readed"
                                    ", last read ", num_read, " bytes");
        if (callback) {
            callback(status);
        }
        return;
    }

    if (total_read < 0 || total_read < n) {
        // This is not an error per se. The RandomAccessFile interface expects
        // that Read returns OutOfRange if fewer bytes were read than requested.
        status = JdoStatus::IOError("EOF reached, ", result->size(),
                                    " bytes were read out of ", n,
                                    " bytes requested.");
        if (callback) {
            callback(status);
        }
        return;
    }

    if (callback) {
        callback(JdoStatus::OK());
    }
}

struct PreadArgs {
    JdoStore_t store = nullptr;
    JdoIOContext_t reader = nullptr;
    std::string name;
    int64_t offset = 0;
    size_t n = 0;
    std::string_view *result;
    char *scratch;
    std::function<void(JdoStatus)> callback;
};

static void preadCallback(JdoHandleCtx_t ctx, int64_t num_read, void *userdata) {
    PreadArgs *args = (PreadArgs *) userdata;
    auto store = args->store;
    auto reader = args->reader;
    auto name = args->name;
    auto offset = args->offset;
    auto n = args->n;
    auto result = args->result;
    auto scratch = args->scratch;
    auto total_read = num_read;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    if (errorCode != 0) {
        auto status = JdoStatus::InternalError(errorCode, "failed to read ", name, " offset ", offset + total_read,
                                    " size ", n - total_read, " readed ", num_read,
                                    ", errmsg: ", errorMsg);
        if (callback) {
            callback(status);
        }
        return;
    }

    JdoStatus status;
    if (total_read >= 0) {
        while (n - total_read > 0) {
            START_CALL2(store, reader)
            num_read = jdo_pread(ctx, scratch + total_read, n - total_read, offset + total_read, nullptr);
            END_CALL()
            if (errorCode != 0) {
                status = JdoStatus::InternalError(errorCode, "failed to read ", name, " offset ", offset + total_read,
                                            " size ", n - total_read, " readed ", num_read,
                                            ", errmsg: ", errorMsg);
                if (callback) {
                    callback(status);
                }
                return;
            }
            if (num_read < 0) {
                // isEOF
                break;
            }
            total_read += num_read;
        }


        // Set the results.
        *result = std::string_view(scratch, total_read);
    }

    if ((total_read < 0 || total_read < n) && num_read != -1) {
        // if there was an error before satisfying the current read, this logic declares it an error
        // and does not try to return any of the bytes read. Don't think it matters, so the code
        // is just being conservative.
        status = JdoStatus::IOError("Failed to read, ", name,
                                    ", expect read ", n, " bytes"
                                    ", but ", total_read, " bytes readed"
                                    ", last read ", num_read, " bytes");
        if (callback) {
            callback(status);
        }
        return;
    }

    if (total_read < 0 || total_read < n) {
        // This is not an error per se. The RandomAccessFile interface expects
        // that Read returns OutOfRange if fewer bytes were read than requested.
        status = JdoStatus::IOError("EOF reached, ", result->size(),
                                    " bytes were read out of ", n,
                                    " bytes requested.");
        if (callback) {
            callback(status);
        }
        return;
    }

    if (callback) {
        callback(JdoStatus::OK());
    }
}

struct PreadvArgs {
    JdoStore_t store = nullptr;
    JdoIOContext_t reader = nullptr;
    std::string name;
    JdoLongs_t lengths;
    JdoLongs_t offsets;
    JdoFileBuffers_t buffers;
    std::function<void(JdoStatus)> callback;
};

static void preadvCallback(JdoHandleCtx_t ctx, int64_t ret, void *userdata) {
    PreadvArgs *args = (PreadvArgs *) userdata;
    auto store = args->store;
    auto reader = args->reader;
    auto name = args->name;
    auto offsets = args->offsets;
    auto lengths = args->lengths;
    auto buffers = args->buffers;
    auto rangeCnt = jdo_getFileBuffersSize(buffers);
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    jdo_freeFileBuffers(buffers);
    jdo_freeLongs(lengths);
    jdo_freeLongs(offsets);

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "failed to preadv ", name,
                                  " ranges ", std::to_string(rangeCnt),
                                  ", errmsg: ", errorMsg);
    }

    if (callback) {
        callback(status);
    }
}

struct Int64Args {
    JdoStore_t store = nullptr;
    JdoIOContext_t reader = nullptr;
    JdoIOContext_t writer = nullptr;
    std::function<void(JdoStatus, int64_t)> callback;
};

static void tellCallback(JdoHandleCtx_t ctx, int64_t result, void *userdata) {
    Int64Args* args = (Int64Args*) userdata;
    auto store = args->store;
    auto reader = args->reader;
    std::function<void(JdoStatus, int64_t)> callback = args->callback;
    delete args;

    END_CALL()

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "file tell failed: ", errorMsg);
    }

    if (callback) {
        callback(status, result);
    }
}

static void seekCallback(JdoHandleCtx_t ctx, int64_t result, void *userdata) {
    Int64Args* args = (Int64Args*) userdata;
    auto store = args->store;
    auto reader = args->reader;
    std::function<void(JdoStatus, int64_t)> callback = args->callback;
    delete args;

    END_CALL()

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "file seek failed: ", errorMsg);
    }

    if (callback) {
        callback(status, result);
    }
}

static void getFileLengthCallback(JdoHandleCtx_t ctx, int64_t result, void *userdata) {
    Int64Args* args = (Int64Args*) userdata;
    auto store = args->store;
    auto reader = args->reader;
    std::function<void(JdoStatus, int64_t)> callback = args->callback;
    delete args;

    END_CALL()

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "get file length failed: ", errorMsg);
    }

    if (callback) {
        callback(status, result);
    }
}

struct CloseReaderArgs {
    JdoStore_t store = nullptr;
    JdoIOContext_t reader = nullptr;
    std::string name;
    std::atomic<bool>* isClosed = nullptr;
    std::function<void(JdoStatus)> callback;
};

static void closeReaderCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    CloseReaderArgs* args = (CloseReaderArgs*) userdata;
    auto store = args->store;
    auto reader = args->reader;
    auto name = args->name;
    std::atomic<bool>* isClosed = args->isClosed;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()

    *isClosed = true;

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "close file failed: ", errorMsg);
    }

    if (callback) {
        callback(status);
    }
}

class JindoReader : public JdoReader {
public:
    JindoReader(const std::string &fname,
                JdoStore_t store,
                JdoIOContext_t reader)
            : _name(fname),
              _store(store),
              _reader(reader),
              _is_closed(false) {
    }

    ~JindoReader() override {
        if (_reader) {
            if (!_is_closed) {
                JdoHandleCtx_t ctx = jdo_createHandleCtx2(_store, _reader);
                jdo_close(ctx, nullptr);
                jdo_freeHandleCtx(ctx);
            }
            jdo_freeIOContext(_reader);
            _reader = nullptr;
        }
    }

    bool closed() const override {
        return _is_closed;
    }

    JdoStatus name(std::string_view *result) const override {
        *result = _name;
        return JdoStatus::OK();
    }

    JdoStatus read(size_t n, std::string_view *result, char *scratch) override {
        std::lock_guard<std::mutex> lock(_lock);
        size_t total_read = 0;
        int64_t num_read = 0;
        while (n - total_read > 0) {
            START_CALL2(_store, _reader)
            num_read = jdo_read(ctx, scratch + total_read, n - total_read, nullptr);
            END_CALL()
            if (errorCode != 0) {
                return JdoStatus::InternalError(errorCode, "failed to read ", _name, " size ", n - total_read, " readed ", num_read,
                                          ", errmsg: ", errorMsg);
            }
            if (num_read < 0) {
                // isEOF
                break;
            }
            total_read += num_read;
        }
        // Set the results.
        *result = std::string_view(scratch, total_read);

        if (total_read < n && num_read != -1) {
            // if there was an error before satisfying the current read, this logic declares it an error
            // and does not try to return any of the bytes read. Don't think it matters, so the code
            // is just being conservative.
            return JdoStatus::IOError("Failed to read, ", _name,
                                      ", expect read ", n, " bytes"
                                      ", but ", total_read, " bytes readed"
                                       ", last read ", num_read, " bytes");
        }

        if (total_read < n) {
            // This is not an error per se. The RandomAccessFile interface expects
            // that Read returns OutOfRange if fewer bytes were read than requested.
            return JdoStatus::IOError("EOF reached, ", result->size(),
                                      " bytes were read out of ", n,
                                      " bytes requested.");
        }

        return JdoStatus::OK();
    }

    JdoTaskPtr readAsync(size_t n, std::string_view *result, char *scratch, std::function<void(JdoStatus)> callback) override {
        size_t total_read = 0;
        int64_t num_read = 0;
        ReadArgs *args = new ReadArgs();
        args->store = _store;
        args->reader = _reader;
        args->name = _name;
        args->n = n;
        args->result = result;
        args->scratch = scratch;
        args->callback = callback;
        START_CALL2(_store, _reader)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, readCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_readAsync(ctx, scratch + total_read, n - total_read, options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _reader, operationCall);
    }

    JdoStatus pread(int64_t offset, size_t n, std::string_view *result, char *scratch) override {
        size_t total_read = 0;
        int64_t num_read = 0;
        while (n - total_read > 0) {
            START_CALL2(_store, _reader)
            num_read = jdo_pread(ctx, scratch + total_read, n - total_read, offset + total_read, nullptr);
            END_CALL()
            if (errorCode != 0) {
                return JdoStatus::InternalError(errorCode, "failed to read ", _name, " offset ", offset + total_read,
                                          " size ", n - total_read, " readed ", num_read,
                                          ", errmsg: ", errorMsg);
            }
            if (num_read < 0) {
                // isEOF
                break;
            }
            total_read += num_read;
        }
        // Set the results.
        *result = std::string_view(scratch, total_read);

        if (total_read < n && num_read != -1) {
            // if there was an error before satisfying the current read, this logic declares it an error
            // and does not try to return any of the bytes read. Don't think it matters, so the code
            // is just being conservative.
            return JdoStatus::IOError("Failed to read, ", _name,
                                      ", expect read ", n, " bytes"
                                      ", but ", total_read, " bytes readed"
                                       ", last read ", num_read, " bytes");
        }

        if (total_read < n) {
            // This is not an error per se. The RandomAccessFile interface expects
            // that Read returns OutOfRange if fewer bytes were read than requested.
            return JdoStatus::IOError("EOF reached, ", result->size(),
                                      " bytes were read out of ", n,
                                      " bytes requested.");
        }

        return JdoStatus::OK();
    }

    JdoTaskPtr preadAsync(int64_t offset, size_t n, std::string_view *result, char *scratch,
                              std::function<void(JdoStatus)> callback) override {
        size_t total_read = 0;
        int64_t num_read = 0;
        PreadArgs *args = new PreadArgs();
        args->store = _store;
        args->reader = _reader;
        args->name = _name;
        args->offset = offset;
        args->n = n;
        args->result = result;
        args->scratch = scratch;
        args->callback = callback;
        START_CALL2(_store, _reader)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, preadCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_preadAsync(ctx, scratch + total_read, n - total_read, offset + total_read, options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _reader, operationCall);
    }

    JdoStatus preadv(std::vector<int64_t>& offsets, std::vector<size_t>& lengths, std::vector<char*>& scratches) override {
        START_CALL2(_store, _reader)
        JdoLongs_t offs = jdo_createLongs();
        for (auto& offset : offsets) {
            jdo_appendLong(offs, offset);
        }
        JdoLongs_t lens = jdo_createLongs();
        for (auto& len: lengths) {
            jdo_appendLong(lens, len);
        }
        JdoFileBuffers_t fileBuffers = jdo_createFileBuffers();
        for (auto& scratch : scratches) {
            jdo_appendFileBuffer(fileBuffers, scratch);
        }
        jdo_preadv(ctx, fileBuffers, lens, offs, nullptr);
        END_CALL()
        jdo_freeFileBuffers(fileBuffers);
        jdo_freeLongs(lens);
        jdo_freeLongs(offs);
        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "failed to preadv ", _name,
                                      " ranges ", scratches.size(),
                                      ", errmsg: ", errorMsg);
        }
        return JdoStatus::OK();
    }

    JdoTaskPtr preadvAsync(std::vector<int64_t>& offsets, std::vector<size_t>& lengths, std::vector<char*>& scratches,
                          std::function<void(JdoStatus)> callback) override {
        JdoLongs_t offs = jdo_createLongs();
        for (auto& offset : offsets) {
            jdo_appendLong(offs, offset);
        }
        JdoLongs_t lens = jdo_createLongs();
        for (auto& len: lengths) {
            jdo_appendLong(lens, len);
        }
        JdoFileBuffers_t fileBuffers = jdo_createFileBuffers();
        for (auto& scratch : scratches) {
            jdo_appendFileBuffer(fileBuffers, scratch);
        }
        PreadvArgs *args = new PreadvArgs();
        args->store = _store;
        args->reader = _reader;
        args->name = _name;
        args->offsets = offs;
        args->lengths = lens;
        args->buffers = fileBuffers;
        args->callback = callback;
        START_CALL2(_store, _reader)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, preadvCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_preadvAsync(ctx, fileBuffers, lens, offs, options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _reader, operationCall);
    }

    JdoStatus tell(int64_t& offset) override {
        START_CALL2(_store, _reader)
        offset = jdo_tell(ctx, nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "tell file failed: ", errorMsg);
        }

        return JdoStatus::OK();
    }

    JdoTaskPtr tellAsync(std::function<void(JdoStatus, int64_t)> callback) override {
        Int64Args *args = new Int64Args();
        args->store = _store;
        args->reader = _reader;
        args->callback = callback;
        START_CALL2(_store, _reader)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, tellCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_tellAsync(ctx,options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _reader, operationCall);
    }


    JdoStatus seek(int64_t offset) override {
        START_CALL2(_store, _reader)
        jdo_seek(ctx, offset, nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "seek file failed: ", errorMsg);
        }

        return JdoStatus::OK();
    }

    JdoTaskPtr seekAsync(int64_t offset, std::function<void(JdoStatus, int64_t)> callback) override {
        Int64Args *args = new Int64Args();
        args->store = _store;
        args->reader = _reader;
        args->callback = callback;
        START_CALL2(_store, _reader)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, seekCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_seekAsync(ctx, offset, options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _reader, operationCall);
    }

    JdoStatus getFileLength(int64_t& length) override {
        START_CALL2(_store, _reader)
        length = jdo_getFileLength(ctx, nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "tell file failed: ", errorMsg);
        }

        return JdoStatus::OK();
    }

    JdoTaskPtr getFileLengthAsync(std::function<void(JdoStatus, int64_t)> callback) override {
        Int64Args *args = new Int64Args();
        args->store = _store;
        args->reader = _reader;
        args->callback = callback;
        START_CALL2(_store, _reader)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, getFileLengthCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_getFileLengthAsync(ctx,options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _reader, operationCall);
    }

    JdoStatus close() override {
        START_CALL2(_store, _reader)
        jdo_close(ctx, nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "close file failed: ", errorMsg);
        }

        _is_closed = true;
        return JdoStatus::OK();
    }

    JdoTaskPtr closeAsync(std::function<void(JdoStatus)> callback) override {
        CloseReaderArgs* args = new CloseReaderArgs();
        args->store = _store;
        args->reader = _reader;
        args->name = _name;
        args->isClosed = &_is_closed;
        args->callback = callback;

        START_CALL2(_store, _reader)
        auto options = jdo_createOptions();
        jdo_setBoolCallback(options, closeReaderCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_closeAsync(ctx, options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _reader, operationCall);
    }

private:
    std::mutex  _lock;
    std::string _name;
    JdoStore_t  _store;
    JdoIOContext_t _reader = nullptr;
    std::atomic<bool> _is_closed = false;
};

struct WriteArgs {
    JdoStore_t store = nullptr;
    JdoIOContext_t writer = nullptr;
    std::string name;
    std::function<void(JdoStatus)> callback;
};

static void writeCallback(JdoHandleCtx_t ctx, int64_t result, void* userdata) {
    WriteArgs* args = (WriteArgs*) userdata;
    auto store = args->store;
    auto writer = args->writer;
    auto name = args->name;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "write file failed: ", errorMsg);
    }

    if (callback) {
        callback(status);
    }
}

struct FlushArgs {
    JdoStore_t store = nullptr;
    JdoIOContext_t writer = nullptr;
    std::string name;
    std::function<void(JdoStatus)> callback;
};

static void flushCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    FlushArgs* args = (FlushArgs*) userdata;
    auto store = args->store;
    auto writer = args->writer;
    auto name = args->name;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "flush file failed: ", errorMsg);
    }

    if (callback) {
        callback(status);
    }
}

struct CloseWriterArgs {
    JdoStore_t store = nullptr;
    JdoIOContext_t writer = nullptr;
    std::string name;
    std::atomic<bool>* isClosed = nullptr;
    std::function<void(JdoStatus)> callback;
};

static void closeWriterCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    CloseWriterArgs* args = (CloseWriterArgs*) userdata;
    auto store = args->store;
    auto writer = args->writer;
    auto name = args->name;
    std::atomic<bool>* isClosed = args->isClosed;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()

    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "close file failed: ", errorMsg);
    }

    *isClosed = true;

    if (callback) {
        callback(status);
    }
}

class JindoWriter : public JdoWriter {
public:
    JindoWriter(const std::string& fname,
                JdoStore_t store,
                JdoIOContext_t writer)
            : _name(fname),
              _store(store),
              _writer(writer),
              _is_closed(false) {
    }

    ~JindoWriter() override {
        if (_writer) {
            if (!_is_closed) {
                JdoHandleCtx_t ctx = jdo_createHandleCtx2(_store, _writer);
                jdo_close(ctx, nullptr);
                jdo_freeHandleCtx(ctx);
            }
            jdo_freeIOContext(_writer);
            _writer = nullptr;
        }
    }

    bool closed() const override {
        return _is_closed;
    }

    JdoStatus name(std::string_view* result) const override {
        *result = _name;
        return JdoStatus::OK();
    }

    JdoStatus write(std::string_view data) override {
        JDO_RETURN_IF_ERROR(_CheckClosed());

        START_CALL2(_store, _writer)
        jdo_write(ctx, data.data(), data.size(), nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "write file failed: ", errorMsg);
        }

        return JdoStatus::OK();
    }

    JdoTaskPtr writeAsync(std::string_view data, std::function<void(JdoStatus)> callback) override {
        WriteArgs* args = new WriteArgs();
        args->store = _store;
        args->writer = _writer;
        args->name = _name;
        args->callback = callback;

        START_CALL2(_store, _writer)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, writeCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_writeAsync(ctx, data.data(), data.size(), options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _writer, operationCall);
    }

    JdoStatus flush() override {
        JDO_RETURN_IF_ERROR(_CheckClosed());

        START_CALL2(_store, _writer)
        jdo_flush(ctx, nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "flush file failed: ", errorMsg);
        }

        return JdoStatus::OK();
    }

    JdoStatus tell(int64_t& offset) override {
        JDO_RETURN_IF_ERROR(_CheckClosed());

        START_CALL2(_store, _writer)
        offset = jdo_tell(ctx, nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "tell file failed: ", errorMsg);
        }

        return JdoStatus::OK();
    }


    JdoTaskPtr tellAsync(std::function<void(JdoStatus, int64_t)> callback) override {
        Int64Args *args = new Int64Args();
        args->store = _store;
        args->writer = _writer;
        args->callback = callback;
        START_CALL2(_store, _writer)
        auto options = jdo_createOptions();
        jdo_setInt64Callback(options, tellCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_tellAsync(ctx,options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _writer, operationCall);
    }

    JdoTaskPtr flushAsync(std::function<void(JdoStatus)> callback) override {
        FlushArgs* args = new FlushArgs();
        args->store = _store;
        args->writer = _writer;
        args->name = _name;
        args->callback = callback;

        START_CALL2(_store, _writer)
        auto options = jdo_createOptions();
        jdo_setBoolCallback(options, flushCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_flushAsync(ctx, options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _writer, operationCall);
    }

    JdoStatus close() override {
        JDO_RETURN_IF_ERROR(_CheckClosed());

        START_CALL2(_store, _writer)
        jdo_close(ctx, nullptr);
        END_CALL()

        if (errorCode != 0) {
            return JdoStatus::InternalError(errorCode, "close file failed: ", errorMsg);
        }
        _is_closed = true;
        return JdoStatus::OK();
    }

    JdoTaskPtr closeAsync(std::function<void(JdoStatus)> callback) override {
        CloseWriterArgs* args = new CloseWriterArgs();
        args->store = _store;
        args->writer = _writer;
        args->name = _name;
        args->isClosed = &_is_closed;
        args->callback = callback;

        START_CALL2(_store, _writer)
        auto options = jdo_createOptions();
        jdo_setBoolCallback(options, closeWriterCallback);
        jdo_setCallbackContext(options, args);
        auto operationCall = jdo_closeAsync(ctx, options);
        END_CALL()
        jdo_freeOptions(options);

        return std::make_shared<JindoOperation>(_store, _writer, operationCall);
    }

private:
    JdoStatus _CheckClosed() {
        if (_is_closed) {
            return JdoStatus::IOError("Already closed.");
        }

        return JdoStatus::OK();
    }

    std::string _name;
    JdoStore_t  _store;
    JdoIOContext_t _writer = nullptr;

    std::atomic<bool> _is_closed = false;
};

JdoFileSystem::JdoFileSystem() {
}

JdoFileSystem::~JdoFileSystem() {
    if (_store != nullptr) {
        jdo_freeStore(_store);
        _store = nullptr;
    }
    if (_options != nullptr) {
        jdo_freeOptions(_options);
        _options = nullptr;
    }
}

JdoStatus JdoFileSystem::init(const std::string &uri, const std::string &user, const std::shared_ptr<JdoConfig>& config) {
    if (_inited) {
        return JdoStatus::InitError("already inited");
    }

    _options = jdo_createOptions();
    auto configMap = config->getAll();
    for (auto& kv : configMap) {
        jdo_setOption(_options, kv.first.c_str(), kv.second.c_str());
    }
    if (getenv("OSS_ENDPOINT")) {
        jdo_setOption(_options, "fs.oss.endpoint", getenv("OSS_ENDPOINT"));
    }
    if (getenv("OSS_ACCESS_ID")) {
        jdo_setOption(_options, "fs.oss.accessKeyId", getenv("OSS_ACCESS_ID"));
    }
    if (getenv("OSS_ACCESS_KEY")) {
        jdo_setOption(_options, "fs.oss.accessKeySecret", getenv("OSS_ACCESS_KEY"));
    }

    jdo_setOption(_options, "logger.appender", "file");

    _store = jdo_createStore(_options, uri.c_str());

    START_CALL(_store)
    jdo_init(ctx, user.c_str());
    END_CALL()

    if (errorCode != 0) {
        destroy();
        return  JdoStatus::InitError("create jdo filesystem failed"
                                     ", uri: ", uri,
                                     ", user: ", user,
                                     ", error:  ", errorMsg);
    }

    _inited = true;
    return JdoStatus::OK();
}

JdoStatus JdoFileSystem::destroy() {
    if (!_inited) {
        return JdoStatus::OK();
    }

    if (_store != nullptr) {
        START_CALL(_store)
        jdo_destroyStore(_store);
        END_CALL()
        jdo_freeStore(_store);
        _store = nullptr;
    }
    if (_options != nullptr) {
        jdo_freeOptions(_options);
        _options = nullptr;
    }
    _inited = false;

	return JdoStatus::OK();
}

JdoStatus JdoFileSystem::openReader(const std::string &path,
                                    std::unique_ptr<JdoReader> *result) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    auto stream = jdo_open(ctx, path.c_str(), JDO_OPEN_FLAG_READ_ONLY, 0777, nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "open file reader failed: ", errorMsg);
    }

    result->reset(new JindoReader(path, store, stream));

    return JdoStatus::OK();
}

JdoStatus JdoFileSystem::openWriter(const std::string &path,
                                    std::unique_ptr<JdoWriter> *result) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    auto flag = JDO_OPEN_FLAG_CREATE | JDO_OPEN_FLAG_OVERWRITE;
    auto handle = jdo_open(ctx, path.c_str(), flag, 0777, nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "open file writer failed: ", errorMsg);
    }

    result->reset(new JindoWriter(path, store, handle));
    return JdoStatus::OK();
}

JdoStatus JdoFileSystem::mkdir(const std::string& path, bool recursive) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    bool result = jdo_mkdir(ctx, path.c_str(), recursive, 0777, nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "mkdir failed: ", errorMsg);
    } else if (!result) {
        return JdoStatus::IOError("mkdir failed: ", path);
    }

    return JdoStatus::OK();
}

struct MkdirArgs {
    std::string path;
    bool recursive;
    std::function<void(JdoStatus)> callback;
};

static void mkdirCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    MkdirArgs* args = (MkdirArgs*) userdata;
    auto path = args->path;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "mkdir failed: ", errorMsg);
    } else if (!result) {
        status = JdoStatus::IOError("mkdir failed: ", path);
    }
    if (callback) {
        callback(status);
    }
}

JdoTaskPtr JdoFileSystem::mkdirAsync(const std::string& path, bool recursive, std::function<void(JdoStatus)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    MkdirArgs* args = new MkdirArgs();
    args->path = path;
    args->recursive = recursive;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setBoolCallback(options, mkdirCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_mkdirAsync(ctx, path.c_str(), recursive, 0777, options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::rename(const std::string &oldpath, const std::string &newpath) {
    auto [store, err] = GetJdoStore(oldpath);
    if (!err.ok()) {
        return err;
    }

    if (exists(newpath).ok()) {
        auto status = remove(newpath, false);
        if (!status.ok()) {
            return status;
        }
    }

    START_CALL(store)
    bool result = jdo_rename(ctx, oldpath.c_str(), newpath.c_str(), nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "rename failed: ", errorMsg);
    } else if (!result) {
        return JdoStatus::IOError("rename failed,"
                                  " from ", oldpath,
                                  " to ", newpath);
    }
    return JdoStatus::OK();
}

struct RenameArgs {
    std::string oldpath;
    std::string newpath;
    std::function<void(JdoStatus)> callback;
};

static void renameCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    RenameArgs* args = (RenameArgs*) userdata;
    auto oldpath = args->oldpath;
    auto newpath = args->newpath;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "rename failed: ", errorMsg);
    } else if (!result) {
        status = JdoStatus::IOError("rename failed,"
                                  " from ", oldpath,
                                  " to ", newpath);
    }
    if (callback) {
        callback(status);
    }
}

JdoTaskPtr JdoFileSystem::renameAsync(const std::string &oldpath, const std::string &newpath, std::function<void(JdoStatus)> callback) {
    auto [store, err] = GetJdoStore(oldpath);
    if (!err.ok()) {
        return nullptr;
    }

    if (exists(newpath).ok()) {
        auto status = remove(newpath, false);
        if (!status.ok()) {
            return nullptr;
        }
    }

    RenameArgs* args = new RenameArgs();
    args->oldpath = oldpath;
    args->newpath = newpath;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setBoolCallback(options, renameCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_renameAsync(ctx, oldpath.c_str(), newpath.c_str(), options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::remove(const std::string& path, bool recursive) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    bool result = jdo_remove(ctx, path.c_str(), recursive, nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "delete failed: ", errorMsg);
    } else if (!result) {
        return JdoStatus::IOError("delete failed: ", path, " recursive ", recursive);
    }
    return JdoStatus::OK();
}

struct RemoveArgs {
    std::string path;
    bool recursive;
    std::function<void(JdoStatus)> callback;
};

static void removeCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    RemoveArgs* args = (RemoveArgs*) userdata;
    auto path = args->path;
    auto recursive = args->recursive;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "delete failed: ", errorMsg);
    } else if (!result) {
        status = JdoStatus::IOError("delete failed: ", path, " recursive ", recursive);
    }
    if (callback) {
        callback(status);
    }
}

JdoTaskPtr JdoFileSystem::removeAsync(const std::string& path, bool recursive, std::function<void(JdoStatus)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    RemoveArgs* args = new RemoveArgs();
    args->path = path;
    args->recursive = recursive;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setBoolCallback(options, removeCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_removeAsync(ctx, path.c_str(), recursive, options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::exists(const std::string& path) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return JdoStatus::IOError(err);
    }

    START_CALL(store)
    bool result = jdo_exists(ctx, path.c_str(), nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "exists failed: ", errorMsg);
    } else if (!result) {
        return JdoStatus::NotFound(path, " doesn't exist.");
    }

    return JdoStatus::OK();
}

struct ExistsArgs {
    std::string path;
    std::function<void(JdoStatus)> callback;
};

static void existsCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    ExistsArgs* args = (ExistsArgs*) userdata;
    auto path = args->path;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "exists failed: ", errorMsg);
    } else if (!result) {
        status = JdoStatus::NotFound(path, " doesn't exist.");
    }
    if (callback) {
        callback(status);
    }
}

JdoTaskPtr JdoFileSystem::existsAsync(const std::string &path, std::function<void(JdoStatus)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    ExistsArgs* args = new ExistsArgs();
    args->path = path;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setBoolCallback(options, existsCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_existsAsync(ctx, path.c_str(), options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::getFileInfo(const std::string& path, JdoFileInfo* stat) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return err;
    }

    return getFileInfoInternal(store, path, stat);
}

struct GetFileInfoArgs {
    std::string path;
    std::function<void(JdoStatus, JdoFileInfoPtr)> callback;
};

static void getFileInfoCallback(JdoHandleCtx_t ctx, JdoFileStatus_t fileStatus, void* userdata) {
    GetFileInfoArgs* args = (GetFileInfoArgs*) userdata;
    auto path = args->path;
    std::function<void(JdoStatus, JdoFileInfoPtr)> callback = args->callback;
    delete args;

    auto info = std::make_shared<JdoFileInfo>();
    covertToFileInfo(fileStatus, info.get());
    jdo_freeFileStatus(fileStatus);

    END_CALL()
    JdoStatus status;
    if (errorCode != 0) {
        if (errorCode == JDO_FILE_NOT_FOUND_ERROR) {
            status = JdoStatus::NotFound("cant not found ", path);
        } else {
            status = JdoStatus::InternalError(errorCode, "get status failed: ", errorMsg);
        }
    }

    if (callback) {
        callback(status, info);
    }
}

JdoTaskPtr JdoFileSystem::getFileInfoAsync(const std::string &path, std::function<void(JdoStatus, JdoFileInfoPtr)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    GetFileInfoArgs* args = new GetFileInfoArgs();
    args->path = path;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setFileStatusCallback(options, getFileInfoCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_getFileStatusAsync(ctx, path.c_str(), options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::listDir(const std::string &dir, bool recursive, JdoListResult *result) {
    auto [store, err] = GetJdoStore(dir);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    JdoListDirResult_t listResult = jdo_listDir(ctx, dir.c_str(), recursive, nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "get children failed: ", errorMsg);
    }

    auto numEntries = jdo_getListDirResultSize(listResult);
    for (int i = 0; i < numEntries; i++) {
        auto fileStatus = jdo_getListDirFileStatus(listResult, i);
        if (fileStatus == nullptr) {
            continue;
        }
        JdoFileInfo info;
        covertToFileInfo(fileStatus, &info);
        result->infos.push_back(info);
    }
    result->truncated = jdo_isListDirResultTruncated(listResult);
    auto nextMarker = jdo_getListDirResultNextMarker(listResult);
    if (nextMarker) {
        result->nextMarker = nextMarker;
    }

    jdo_freeListDirResult(listResult);
    return JdoStatus::OK();
}

struct listDirArgs {
    std::string path;
    std::function<void(JdoStatus, JdoListResultPtr)> callback;
};

static void listDirCallback(JdoHandleCtx_t ctx, JdoListDirResult_t listResult, void* userdata) {
    listDirArgs* args = (listDirArgs*) userdata;
    auto path = args->path;
    std::function<void(JdoStatus, JdoListResultPtr result)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    auto result = std::make_shared<JdoListResult>();
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "get children failed: ", errorMsg);
        callback(status, result);
        return;
    }

    auto numEntries = jdo_getListDirResultSize(listResult);
    for (int i = 0; i < numEntries; i++) {
        auto fileStatus = jdo_getListDirFileStatus(listResult, i);
        if (fileStatus == nullptr) {
            continue;
        }
        JdoFileInfo info;
        covertToFileInfo(fileStatus, &info);
        result->infos.push_back(info);
    }
    result->truncated = jdo_isListDirResultTruncated(listResult);
    auto nextMarker = jdo_getListDirResultNextMarker(listResult);
    if (nextMarker) {
        result->nextMarker = nextMarker;
    }

    jdo_freeListDirResult(listResult);

    if (callback) {
        callback(status, result);
    }
}

JdoTaskPtr JdoFileSystem::listDirAsync(const std::string &path, bool recursive, std::function<void(JdoStatus, JdoListResultPtr)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    listDirArgs* args = new listDirArgs();
    args->path = path;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setListDirResultCallback(options, listDirCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_listDirAsync(ctx, path.c_str(), false, options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::getContentSummary(const std::string &dir, bool recursive, JdoContentSummary *result) {
    auto [store, err] = GetJdoStore(dir);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    JdoContentSummary_t contentSummary = jdo_getContentSummary(ctx, dir.c_str(), recursive, nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "get content summary failed: ", errorMsg);
    }

    result->fileSize = jdo_getContentSummaryFileSize(contentSummary);
    result->fileCount = jdo_getContentSummaryFileCount(contentSummary);
    result->dirCount = jdo_getContentSummaryDirectoryCount(contentSummary);

    jdo_freeContentSummary(contentSummary);
    return JdoStatus::OK();
}

struct GetContentSummaryArgs {
    std::string path;
    std::function<void(JdoStatus, JdoContentSummaryPtr)> callback;
};

static void getContentSummaryCallback(JdoHandleCtx_t ctx, JdoContentSummary_t contentSummary, void* userdata) {
    GetContentSummaryArgs* args = (GetContentSummaryArgs*) userdata;
    auto path = args->path;
    std::function<void(JdoStatus, JdoContentSummaryPtr result)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    auto result = std::make_shared<JdoContentSummary>();
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "get content summary failed: ", errorMsg);
        callback(status, result);
        return;
    }

    result->fileSize = jdo_getContentSummaryFileSize(contentSummary);
    result->fileCount = jdo_getContentSummaryFileCount(contentSummary);
    result->dirCount = jdo_getContentSummaryDirectoryCount(contentSummary);

    jdo_freeContentSummary(contentSummary);

    if (callback) {
        callback(status, result);
    }
}

JdoTaskPtr JdoFileSystem::getContentSummaryAsync(const std::string &path, bool recursive, std::function<void(JdoStatus, JdoContentSummaryPtr)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    GetContentSummaryArgs* args = new GetContentSummaryArgs();
    args->path = path;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setContentSummaryCallback(options, getContentSummaryCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_getContentSummaryAsync(ctx, path.c_str(), false, options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::setPermission(const std::string& path, int16_t perm) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    bool result = jdo_setPermission(ctx, path.c_str(), perm, nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "setPermission failed: ", errorMsg);
    } else if (!result) {
        return JdoStatus::IOError("setPermission failed: ", path);
    }

    return JdoStatus::OK();
}

struct SetPermissionArgs {
    std::string path;
    int16_t perm;
    std::function<void(JdoStatus)> callback;
};

static void setPermissionCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    SetPermissionArgs* args = (SetPermissionArgs*) userdata;
    auto path = args->path;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "setPermission failed: ", errorMsg);
    } else if (!result) {
        status = JdoStatus::IOError("setPermission failed: ", path);
    }
    if (callback) {
        callback(status);
    }
}

JdoTaskPtr JdoFileSystem::setPermissionAsync(const std::string& path, int16_t perm, std::function<void(JdoStatus)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    SetPermissionArgs* args = new SetPermissionArgs();
    args->path = path;
    args->perm = perm;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setBoolCallback(options, setPermissionCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_setPermissionAsync(ctx, path.c_str(), perm, options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}


JdoStatus JdoFileSystem::setOwner(const std::string& path, const std::string& user, const std::string& group) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return err;
    }

    START_CALL(store)
    bool result = jdo_setOwner(ctx, path.c_str(), user.c_str(), group.c_str(), nullptr);
    END_CALL()

    if (errorCode != 0) {
        return JdoStatus::InternalError(errorCode, "setOwner failed: ", errorMsg);
    } else if (!result) {
        return JdoStatus::IOError("setOwner failed: ", path);
    }

    return JdoStatus::OK();
}

struct SetOwnerArgs {
    std::string path;
    std::string user;
    std::string group;
    std::function<void(JdoStatus)> callback;
};

static void setOwnerCallback(JdoHandleCtx_t ctx, bool result, void* userdata) {
    SetOwnerArgs* args = (SetOwnerArgs*) userdata;
    auto path = args->path;
    std::function<void(JdoStatus)> callback = args->callback;
    delete args;

    END_CALL()
    JdoStatus status;
    if (errorCode != 0) {
        status = JdoStatus::InternalError(errorCode, "setOwner failed: ", errorMsg);
    } else if (!result) {
        status = JdoStatus::IOError("setOwner failed: ", path);
    }
    if (callback) {
        callback(status);
    }
}

JdoTaskPtr JdoFileSystem::setOwnerAsync(const std::string& path,  const std::string& user, const std::string& group, std::function<void(JdoStatus)> callback) {
    auto [store, err] = GetJdoStore(path);
    if (!err.ok()) {
        return nullptr;
    }

    SetOwnerArgs* args = new SetOwnerArgs();
    args->path = path;
    args->user = user;
    args->group = group;
    args->callback = callback;

    START_CALL(store)
    auto options = jdo_createOptions();
    jdo_setBoolCallback(options, setOwnerCallback);
    jdo_setCallbackContext(options, args);
    auto operationCall = jdo_setOwnerAsync(ctx, path.c_str(), user.c_str(), group.c_str(), options);
    END_CALL()
    jdo_freeOptions(options);

    return std::make_shared<JindoOperation>(_store, operationCall);
}

JdoStatus JdoFileSystem::getFileInfoInternal(JdoStore_t store,
                                             const std::string &path,
                                             JdoFileInfo *info) {
    START_CALL(store)
    JdoFileStatus_t fileStatus = jdo_getFileStatus(ctx, path.c_str(), nullptr);
    END_CALL()

    JdoStatus ret;
    if (errorCode != 0) {
        if (errorCode == JDO_FILE_NOT_FOUND_ERROR) {
            return JdoStatus::NotFound("cant not found ", path);
        }
        return JdoStatus::InternalError(errorCode, "get status failed: ", errorMsg);
    }

    covertToFileInfo(fileStatus, info);

    jdo_freeFileStatus(fileStatus);
    return JdoStatus::OK();
}

std::tuple<JdoStore_t, JdoStatus> JdoFileSystem::GetJdoStore(const std::string &uri) {
    if (!_inited) {
        return {nullptr, JdoStatus::InitError()};
    }
    return {_store, JdoStatus::OK()};
}
