/*
 * 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 <android-base/unique_fd.h>

#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace android::incfs {

//
// MountRegistry - a collection of mount points for a particular filesystem, with
//      live tracking of binds, mounts and unmounts on it
//

class MountRegistry final {
public:
    struct Bind {
        std::string subdir;
        int rootIndex;
    };
    // std::less<> enables heterogeneous lookups, e.g. by a string_view
    using BindMap = std::map<std::string, Bind, std::less<>>;

    class Mounts final {
        struct Root {
            std::string path;
            std::string backing;
            std::vector<BindMap::const_iterator> binds;

            bool empty() const { return path.empty(); }
            void clear() {
                decltype(path)().swap(path);
                decltype(binds)().swap(binds);
            }
        };

    public:
        struct Mount final {
            Mount(std::vector<Root>::const_iterator base) : mBase(base) {}

            std::string_view root() const { return mBase->path; }
            std::string_view backingDir() const { return mBase->backing; }
            std::vector<std::pair<std::string_view, std::string_view>> binds() const;

        private:
            std::vector<Root>::const_iterator mBase;
        };

        struct iterator final : public std::vector<Root>::const_iterator {
            using base = std::vector<Root>::const_iterator;
            using value_type = Mount;
            value_type operator*() const { return Mount(*this); }

            explicit iterator(base b) : base(b) {}
        };

        static Mounts load(base::borrowed_fd fd, std::string_view filesystem);
        bool loadFrom(base::borrowed_fd fd, std::string_view filesystem);

        iterator begin() const { return iterator(roots.begin()); }
        iterator end() const { return iterator(roots.end()); }
        size_t size() const { return roots.size(); }
        bool empty() const { return roots.empty(); }

        std::string_view rootFor(std::string_view path) const;
        std::pair<const Root*, std::string> rootAndSubpathFor(std::string_view path) const;

        void swap(Mounts& other);
        void clear();

        void addRoot(std::string_view root, std::string_view backingDir);
        void removeRoot(std::string_view root);
        void addBind(std::string_view what, std::string_view where);
        void removeBind(std::string_view what);

    private:
        std::pair<int, BindMap::const_iterator> rootIndex(std::string_view path) const;

        std::vector<Root> roots;
        BindMap rootByBindPoint;
    };

    MountRegistry(std::string_view filesystem = {});
    ~MountRegistry();

    std::string rootFor(std::string_view path);
    std::pair<std::string, std::string> rootAndSubpathFor(std::string_view path);

    struct Details {
        std::string root;
        std::string backing;
        std::string subpath;
    };
    Details detailsFor(std::string_view path);

    Mounts copyMounts();

    void reload();

private:
    [[nodiscard]] std::unique_lock<std::mutex> ensureUpToDate();

private:
    const std::string mFilesystem;
    base::unique_fd mMountInfo;
    Mounts mMounts;
    mutable std::mutex mDataMutex;
};

} // namespace android::incfs
