/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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.
 */

#pragma once

#include <stdint.h>

#include <functional>
#include <memory>
#include <string>
#include <vector>

#include <android-base/unique_fd.h>

#include "fiemap_writer.h"

namespace android {
namespace fiemap_writer {

// Wrapper around FiemapWriter that is able to split images across files if
// necessary.
class SplitFiemap final {
  public:
    using ProgressCallback = std::function<bool(uint64_t, uint64_t)>;

    // Create a new split fiemap file. If |max_piece_size| is 0, the number of
    // pieces will be determined automatically by detecting the filesystem.
    // Otherwise, the file will be split evenly (with the remainder in the
    // final file).
    static std::unique_ptr<SplitFiemap> Create(const std::string& file_path, uint64_t file_size,
                                               uint64_t max_piece_size,
                                               ProgressCallback progress = {});

    // Open an existing split fiemap file.
    static std::unique_ptr<SplitFiemap> Open(const std::string& file_path);

    ~SplitFiemap();

    // Return a list of all files created for a split file.
    static bool GetSplitFileList(const std::string& file_path, std::vector<std::string>* list);

    // Destroy all components of a split file. If the root file does not exist,
    // this returns true and does not report an error.
    static bool RemoveSplitFiles(const std::string& file_path, std::string* message = nullptr);

    // Return whether all components of a split file still have pinned extents.
    bool HasPinnedExtents() const;

    // Helper method for writing data that spans files. Note there is no seek
    // method (yet); this starts at 0 and increments the position by |bytes|.
    bool Write(const void* data, uint64_t bytes);

    // Flush all writes to all split files.
    bool Flush();

    const std::vector<struct fiemap_extent>& extents();
    uint32_t block_size() const;
    uint64_t size() const { return total_size_; }
    const std::string& bdev_path() const;

    // Non-copyable & Non-movable
    SplitFiemap(const SplitFiemap&) = delete;
    SplitFiemap& operator=(const SplitFiemap&) = delete;
    SplitFiemap& operator=(SplitFiemap&&) = delete;
    SplitFiemap(SplitFiemap&&) = delete;

  private:
    SplitFiemap() = default;
    void AddFile(FiemapUniquePtr&& file);

    bool creating_ = false;
    std::string list_file_;
    std::vector<FiemapUniquePtr> files_;
    std::vector<struct fiemap_extent> extents_;
    uint64_t total_size_ = 0;

    // Most recently open file and position for Write().
    size_t cursor_index_ = 0;
    uint64_t cursor_file_pos_ = 0;
    android::base::unique_fd cursor_fd_;
};

}  // namespace fiemap_writer
}  // namespace android
