/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "meta_store.h"

#include <dsn/dist/fmt_logging.h>
#include <dsn/utility/flags.h>

namespace pegasus {
namespace server {

const std::string meta_store::DATA_VERSION = "pegasus_data_version";
const std::string meta_store::LAST_FLUSHED_DECREE = "pegasus_last_flushed_decree";
const std::string meta_store::LAST_MANUAL_COMPACT_FINISH_TIME =
    "pegasus_last_manual_compact_finish_time";

meta_store::meta_store(pegasus_server_impl *server,
                       rocksdb::DB *db,
                       rocksdb::ColumnFamilyHandle *meta_cf)
    : replica_base(server), _db(db), _meta_cf(meta_cf)
{
    // disable write ahead logging as replication handles logging instead now
    _wt_opts.disableWAL = true;
}

uint64_t meta_store::get_last_flushed_decree() const
{
    uint64_t last_flushed_decree = 0;
    auto ec = get_value_from_meta_cf(true, LAST_FLUSHED_DECREE, &last_flushed_decree);
    dcheck_eq_replica(::dsn::ERR_OK, ec);
    return last_flushed_decree;
}

uint32_t meta_store::get_data_version() const
{
    uint64_t pegasus_data_version = 0;
    auto ec = get_value_from_meta_cf(false, DATA_VERSION, &pegasus_data_version);
    dcheck_eq_replica(::dsn::ERR_OK, ec);
    return static_cast<uint32_t>(pegasus_data_version);
}

uint64_t meta_store::get_last_manual_compact_finish_time() const
{
    uint64_t last_manual_compact_finish_time = 0;
    auto ec = get_value_from_meta_cf(
        false, LAST_MANUAL_COMPACT_FINISH_TIME, &last_manual_compact_finish_time);
    dcheck_eq_replica(::dsn::ERR_OK, ec);
    return last_manual_compact_finish_time;
}

uint64_t meta_store::get_decree_from_readonly_db(rocksdb::DB *db,
                                                 rocksdb::ColumnFamilyHandle *meta_cf) const
{
    uint64_t last_flushed_decree = 0;
    auto ec = get_value_from_meta_cf(db, meta_cf, true, LAST_FLUSHED_DECREE, &last_flushed_decree);
    dcheck_eq_replica(::dsn::ERR_OK, ec);
    return last_flushed_decree;
}

std::string meta_store::get_usage_scenario() const
{
    // If couldn't find rocksdb usage scenario in meta column family, return normal in default.
    std::string usage_scenario = ROCKSDB_ENV_USAGE_SCENARIO_NORMAL;
    auto ec = get_string_value_from_meta_cf(false, ROCKSDB_ENV_USAGE_SCENARIO_KEY, &usage_scenario);
    dassert_replica(ec == ::dsn::ERR_OK || ec == ::dsn::ERR_OBJECT_NOT_FOUND,
                    "rocksdb {} get {} from meta column family failed: {}",
                    _db->GetName(),
                    ROCKSDB_ENV_USAGE_SCENARIO_KEY,
                    ec.to_string());
    return usage_scenario;
}

::dsn::error_code meta_store::get_value_from_meta_cf(bool read_flushed_data,
                                                     const std::string &key,
                                                     uint64_t *value) const
{
    return get_value_from_meta_cf(_db, _meta_cf, read_flushed_data, key, value);
}

::dsn::error_code meta_store::get_value_from_meta_cf(rocksdb::DB *db,
                                                     rocksdb::ColumnFamilyHandle *cf,
                                                     bool read_flushed_data,
                                                     const std::string &key,
                                                     uint64_t *value)
{
    std::string data;
    auto ec = get_string_value_from_meta_cf(db, cf, read_flushed_data, key, &data);
    if (ec != ::dsn::ERR_OK) {
        return ec;
    }
    dassert_f(dsn::buf2uint64(data, *value),
              "rocksdb {} get \"{}\" from meta column family failed to parse into uint64",
              db->GetName(),
              data);
    return ::dsn::ERR_OK;
}

::dsn::error_code meta_store::get_string_value_from_meta_cf(bool read_flushed_data,
                                                            const std::string &key,
                                                            std::string *value) const
{
    return get_string_value_from_meta_cf(_db, _meta_cf, read_flushed_data, key, value);
}

::dsn::error_code meta_store::get_string_value_from_meta_cf(rocksdb::DB *db,
                                                            rocksdb::ColumnFamilyHandle *cf,
                                                            bool read_flushed_data,
                                                            const std::string &key,
                                                            std::string *value)
{
    rocksdb::ReadOptions rd_opts;
    if (read_flushed_data) {
        // only read 'flushed' data, mainly to read 'last_flushed_decree'
        rd_opts.read_tier = rocksdb::kPersistedTier;
    }
    auto status = db->Get(rd_opts, cf, key, value);
    if (status.ok()) {
        return ::dsn::ERR_OK;
    }

    if (status.IsNotFound()) {
        return ::dsn::ERR_OBJECT_NOT_FOUND;
    }

    // TODO(yingchun): add a rocksdb io error.
    return ::dsn::ERR_LOCAL_APP_FAILURE;
}

::dsn::error_code meta_store::set_value_to_meta_cf(const std::string &key, uint64_t value) const
{
    return set_string_value_to_meta_cf(key, std::to_string(value));
}

::dsn::error_code meta_store::set_string_value_to_meta_cf(const std::string &key,
                                                          const std::string &value) const
{
    auto status = _db->Put(_wt_opts, _meta_cf, key, value);
    if (!status.ok()) {
        derror_replica(
            "Put {}={} to meta column family failed, status {}", key, value, status.ToString());
        // TODO(yingchun): add a rocksdb io error.
        return ::dsn::ERR_LOCAL_APP_FAILURE;
    }
    return ::dsn::ERR_OK;
}

void meta_store::set_last_flushed_decree(uint64_t decree) const
{
    dcheck_eq_replica(::dsn::ERR_OK, set_value_to_meta_cf(LAST_FLUSHED_DECREE, decree));
}

void meta_store::set_data_version(uint32_t version) const
{
    dcheck_eq_replica(::dsn::ERR_OK, set_value_to_meta_cf(DATA_VERSION, version));
}

void meta_store::set_last_manual_compact_finish_time(uint64_t last_manual_compact_finish_time) const
{
    dcheck_eq_replica(
        ::dsn::ERR_OK,
        set_value_to_meta_cf(LAST_MANUAL_COMPACT_FINISH_TIME, last_manual_compact_finish_time));
}

void meta_store::set_usage_scenario(const std::string &usage_scenario) const
{
    dcheck_eq_replica(::dsn::ERR_OK,
                      set_string_value_to_meta_cf(ROCKSDB_ENV_USAGE_SCENARIO_KEY, usage_scenario));
}

} // namespace server
} // namespace pegasus
