#include "gridfs.h"
#include "bson.h"
#include "exception.h"
#include "connection.h"

#include <library/cpp/logger/global/global.h>
#include <library/cpp/json/json_value.h>
#include <library/cpp/json/json_writer.h>

#include <util/memory/blob.h>
#include <util/generic/buffer.h>
#include <util/generic/string.h>


namespace NUtil {

    TMongoGridFs::TMongoGridFs(TMongoConnection* connection, const TString& db, const TString& fs) {
        CHECK_WITH_LOG(!!connection);
        bson_error_t error;
        GridFs = mongoc_client_get_gridfs(connection->GetRawConnection(), db.data(), fs.data(), &error);
        VERIFY_WITH_LOG(!!GridFs, "Can't create gridfs %s", error.message);
    }

    TMongoGridFs::~TMongoGridFs() {
        mongoc_gridfs_destroy(GridFs);
    }

    TBlob TMongoGridFs::GetFile(const NJson::TJsonValue& query) const {
        bson_error_t error;
        mongoc_gridfs_file_t* mongoFile = mongoc_gridfs_find_one(GridFs, NPrivate::TBson(query).Get(), &error);
        if (!mongoFile) {
            return TBlob();
        }

        ui64 fileSize = mongoc_gridfs_file_get_length(mongoFile);
        TBuffer data;
        data.Resize(fileSize);
        CHECK_WITH_LOG(data.Size() == fileSize);

        mongoc_iovec_t iov;
        iov.iov_base = (void*) data.Data();
        iov.iov_len = data.Size();

        ui64 read = mongoc_gridfs_file_readv(mongoFile, &iov, 1, 0, MONGOC_DEFAULT_SOCKETTIMEOUTMS);
        if (read != fileSize) {
            ythrow TMongoException() << "Can't read file " << query.GetStringRobust() << " from gridfs" << Endl;
        }

        mongoc_gridfs_file_destroy(mongoFile);
        return TBlob::FromBuffer(data);
    }

    void TMongoGridFs::PutFile(const TString& filename, const TBlob& fileData, const NJson::TJsonValue& metadata) {
        mongoc_gridfs_file_opt_t opt = {nullptr, nullptr, nullptr, nullptr, nullptr, 0};
        opt.filename = filename.data();
        NPrivate::TBson bson(metadata);
        opt.metadata = bson.Get();

        mongoc_gridfs_file_t* mongoFile = mongoc_gridfs_create_file(GridFs, &opt);

        mongoc_iovec_t iov;
        iov.iov_base = (void*) fileData.Data();
        iov.iov_len = fileData.Size();

        ui64 written = mongoc_gridfs_file_writev(mongoFile, &iov, 1, MONGOC_DEFAULT_SOCKETTIMEOUTMS);

        if (written != fileData.Size()) {
            ythrow TMongoException() << "Can't write file " << filename << " to gridfs" << Endl;
        }

        mongoc_gridfs_file_save(mongoFile);
        mongoc_gridfs_file_destroy(mongoFile);
    }

    void TMongoGridFs::RemoveFile(const TString& filename) {
        bson_error_t error;
        if (!mongoc_gridfs_remove_by_filename(GridFs, filename.data(), &error)) {
            ythrow TMongoException() << "Can't remove file " << filename << " from gridfs" << Endl;
        }
    }

    TMongoCollection::TIterator::TPtr TMongoGridFs::FindFileInfo(const NJson::TJsonValue& query) {
        if (!FilesMeta) {
            FilesMeta.Reset(new TMongoCollection(this));
        }
        return FilesMeta->Find(query);
    }

    mongoc_gridfs_t* TMongoGridFs::GetRawFs() {
        return GridFs;
    }
}
