/*
 * 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.
 */

#ifndef FILE_OPEN_FILE_H
#define FILE_OPEN_FILE_H

#include <map>
#include <vector>

#include "common/container/list.h"
#include "common/mutex/mutex.h"
#include "common/tsfile_common.h"
#include "reader/bloom_filter.h"
#include "utils/storage_utils.h"

namespace storage {

/*
 * Data of a TSStore consist of:
 * - some tsfiles
 * - some immutable tvlists
 * - the active tvlist.
 *
 * When some immutable tvlists are flushed into tsfile, we
 * should guarantee that we have a consisteny view of TSStore
 * data. That means we should see one and only one of the
 * flushed immutable tvlist or the corresponding tsfile.
 * Otherwise, we may see duplicated data in the immutable tvlist
 * and the tsfile, or we may lose the data if we see neither
 * of them.
 * So we should do the following steps atomically:
 * - remove the immutable tvlist from tsstore
 * - make it visible in tsfile_mgr
 */

// opened tsfile
class OpenFile {
   public:
    // maybe use sorted array instead of map.
    typedef std::map<common::TsID, TimeRange> TsTimeRangeMap;
    typedef std::map<common::TsID, TimeRange>::iterator TsTimeRangeMapIterator;

   public:
    OpenFile()
        : file_id_(),
          file_path_(),
          fd_(-1),
          bloom_filter_(nullptr),
          ts_time_range_map_(nullptr),
          mutex_() {}
    int init();
    // reset the map to reclaim memory
    void reset();

    void set_file_id_and_path(const common::FileID& file_id,
                              const std::string& file_path);
    FORCE_INLINE common::FileID get_file_id() { return file_id_; }
    FORCE_INLINE std::string get_file_path() { return file_path_; }
    int build_from(const std::vector<TimeseriesTimeIndexEntry>& time_index_vec);
    int add(const common::TsID& ts_id, const TimeRange& time_range);

    bool contain_timeseries(const common::TsID& ts_id) const;
    int get_time_range(const common::TsID& ts_id,
                       TimeRange& ret_time_range) const;

#ifndef NDEBUG
    friend std::ostream& operator<<(std::ostream& out, OpenFile& open_file) {
        out << "file_id=" << open_file.file_id_
            << ", file_path=" << open_file.file_path_
            << ", fd=" << open_file.fd_ << ", ts_time_range_map=";
        if (open_file.ts_time_range_map_ == nullptr) {
            out << "nil" << std::endl;
        } else {
            TsTimeRangeMapIterator it;
            out << std::endl;
            for (it = open_file.ts_time_range_map_->begin();
                 it != open_file.ts_time_range_map_->end();
                 it++) {  // cppcheck-suppress postfixOperator
                out << "{ts_id=" << it->first.to_string()
                    << ", ts_time_range={start_time=" << it->second.start_time_
                    << ", end_time=" << it->second.end_time_ << "}}";
            }
        }
        return out;
    }
#endif

   private:
    common::FileID file_id_;
    std::string file_path_;
    int fd_;
    /*
     * Why use pointer instead of object:
     * we may want to reclaim the memory in case of memory overused.
     */
    BloomFilter* bloom_filter_;
    TsTimeRangeMap*
        ts_time_range_map_;  // TODO: use custom hashtable to monitor memory.
    mutable common::Mutex mutex_;
};

class OpenFileFactory {
   public:
    static OpenFile* alloc() {
        void* buf =
            common::mem_alloc(sizeof(OpenFile), common::MOD_OPEN_FILE_OBJ);
        if (IS_NULL(buf)) {
            return nullptr;
        }
        return new (buf) OpenFile;
    }

    static void free(OpenFile* of) {
        if (of != nullptr) {
            common::mem_free(of);
        }
    }
};

}  // end namespace storage
#endif  // FILE_OPEN_FILE_H
