/*
    SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef BTCACHEFILE_H
#define BTCACHEFILE_H

#include <QFile>
#include <QHash>
#include <QRecursiveMutex>
#include <QSharedPointer>
#include <util/constants.h>

namespace bt
{
class PreallocationThread;

/*!
 * \headerfile diskio/cachefile.h
 * \brief Interface which must be implemented to be able to map something from a CacheFile.
 *
 * It will also be used to notify when things get unmapped or remapped
 */
class MMappeable
{
public:
    virtual ~MMappeable()
    {
    }

    /*!
     * When a CacheFile is closed, this will be called on all existing mappings.
     */
    virtual void unmapped() = 0;
};

/*!
    \headerfile diskio/cachefile.h
    \author Joris Guisson <joris.guisson@gmail.com>

    \brief Used by SingleFileCache and MultiFileCache to write to disk.
*/
class CacheFile : public QObject
{
    Q_OBJECT
public:
    CacheFile();
    ~CacheFile() override;

    /*!
     * \enum Mode
     *
     * Specifies how the cache file will be opened.
     *
     * \var READ
     * Opened for read-only.
     *
     * \var WRITE
     * Opened for write-only.
     *
     * \var RW
     * Opened for read and write.
     */
    enum Mode {
        READ,
        WRITE,
        RW,
    };

    /*!
     * Open the file.
     * \param path Path of the file
     * \param size Max size of the file
     * \throw Error when something goes wrong
     */
    void open(const QString &path, Uint64 size);

    //! Change the path of the file
    void changePath(const QString &npath);

    /*!
     * Map a part of the file into memory, will expand the file
     * if it is to small, but will not go past the limit set in open.
     * \param thing The thing that wishes to map the mmapping
     * \param off Offset into the file
     * \param size Size of the region to map
     * \param mode How the region will be mapped
     * \return A ptr to the mmaped region, or nullptr if something goes wrong
     */
    void *map(MMappeable *thing, Uint64 off, Uint32 size, Mode mode);

    /*!
     * Unmap a previously mapped region.
     * \param ptr Ptr to the region
     */
    void unmap(void *ptr);

    /*!
     * Close the file, everything will be unmapped.
     */
    void close();

    /*!
     * Read from the file.
     * \param buf Buffer to store data
     * \param size Size to read
     * \param off Offset to read from in file
     */
    void read(Uint8 *buf, Uint32 size, Uint64 off);

    /*!
     * Write to the file.
     * \param buf Buffer to write
     * \param size Size to read
     * \param off Offset to read from in file
     */
    void write(const Uint8 *buf, Uint32 size, Uint64 off);

    /*!
     * Preallocate disk space
     */
    void preallocate(PreallocationThread *prealloc);

    //! Get the number of bytes this cache file is taking up
    Uint64 diskUsage();

    using Ptr = QSharedPointer<CacheFile>;

private:
    void growFile(Uint64 to_write);
    void closeTemporary();
    void openFile(Mode mode);
    void unmapAll();
    bool allocateBytes(bt::Uint64 off, bt::Uint64 size);

private:
    QFile fptr;
    bool read_only;
    Uint64 max_size, file_size;
    QString path;
    struct Entry {
        MMappeable *thing;
        void *ptr;
        Uint64 offset;
        Uint32 size;
        Mode mode;
    };
    QHash<void *, Entry> mappings;
    mutable QRecursiveMutex mutex;

#ifndef Q_OS_WIN
    static const Uint64 page_size;
#endif
};

}

#endif
