#pragma once

#include <fcntl.h>
#include <functional>
#include <stdio.h>
#include <stdexcept>
#include <thread>
#include <unistd.h>

#include "aqsi-commons-global.h"

namespace aqsi {
class AQSICOMMONS_EXPORT FileLocker
{
public:
    /**  Конструктор
    \param file_path - путь к файлу, который будет блокироваться.
    */
    FileLocker(char const* file_path);

    ~FileLocker();

    /**  Пытается получить блокировку записи в файл. Блокирует поток на время ожидания
    \param callback_function - функция, вызываемая при получении блокировки.
    \throw std::runtime_error - в случае, если файл не удалось открыть либо передан неверный путь/имя файла
    \retval 0 При получении блокировки
    \retval errno https://man7.org/linux/man-pages/man3/errno.3.html при неуспешной операции блокировки для чтения
    */
    int waitForWriteLock(std::function<void()> const callback_function = nullptr);

    /**  Пытается получить блокировку записи в файл.
    \param millisec_timeout - таймаут, в течение которого пытается заблокировать файл. Если
    указан таймаут 0, то вернет результат попытки первой блокировки
    \param callback_function - функция, вызываемая при получении блокировки.
    \throw std::runtime_error - в случае, если файл не удалось открыть либо передан неверный путь/имя файла
    \retval 0 При получении блокировки
    \retval errno https://man7.org/linux/man-pages/man3/errno.3.html при неуспешной операции блокировки для записи
    */
    int tryToWriteLock(
        std::chrono::milliseconds const millisec_timeout = std::chrono::seconds(3),
        std::function<void()> const callback_function    = nullptr);

    /**  Пытается получить блокировку чтения файла. Блокирует поток на время ожидания
    \param callback_function - функция, вызываемая при получении блокировки.
    \throw std::runtime_error - в случае, если файл не удалось открыть либо передан неверный путь/имя файла
    \retval 0 При получении блокировки
    \retval errno https://man7.org/linux/man-pages/man3/errno.3.html при неуспешной операции блокировки для чтения
    */
    int waitForReadLock(std::function<void()> const callback_function = nullptr);

    /**  Пытается получить блокировку чтения файла.
    \param millisec_timeout - таймаут, в течение которого пытается заблокировать файл. Если
    указан таймаут 0, то вернет результат попытки первой блокировки
    \param callback_function - функция, вызываемая при получении блокировки.
    \throw std::runtime_error - в случае, если файл не удалось открыть либо передан неверный путь/имя файла
    \retval 0 При получении блокировки
    \retval errno https://man7.org/linux/man-pages/man3/errno.3.html при неуспешной операции блокировки для чтения
    */
    int tryToReadLock(
        std::chrono::milliseconds const millisec_timeout = std::chrono::seconds(3),
        std::function<void()> const callback_function    = nullptr);

    /**  Пытается снять свою блокировку с файла.
    \retval 0 При успешном снятии блокировки
    \retval errno https://man7.org/linux/man-pages/man3/errno.3.html при неуспешной операции снятия блокировки
    */
    int tryToUnlock();

private:
    struct flock m_lock;
    static constexpr int kInvalidFileDescriptor{-1};
    int m_file_descriptor;
    char const* m_file_path;
    int m_current_lock;
    int m_current_open_file_mode;

    /**  Пытается установить блокировку
    \param lock_type - тип блокировки (F_UNLCK, F_RDLCK, F_WRLCK).
    \param lock_mode - режим блокировки F_OFD_SETLK, F_OFD_SETLKW (блокирующий поток)
    \param callback_function - вызываемая функция в случае получения блокировки
    \retval 0 При получении блокировки
    \retval errno https://man7.org/linux/man-pages/man3/errno.3.html при неуспешной операции блокировки
    */
    int _setLock(int const lock_type, int const lock_mode, std::function<void()> const callback_function = nullptr);

    /**  Пытается установить блокировку
    \param millisec_timeout - таймаут, в течение которого пытается получить блокировку
    \param lock_type - тип блокировки (F_UNLCK, F_RDLCK, F_WRLCK).
    \param lock_mode - режим блокировки F_OFD_SETLK, F_OFD_SETLKW (блокирующий поток)
    \param callback_function - вызываемая функция в случае получения блокировки
    \retval 0 При получении блокировки
    \retval errno https://man7.org/linux/man-pages/man3/errno.3.html при неуспешной операции блокировки
    */
    int _tryToSetLock(
        std::chrono::milliseconds const millisec_timeout, int const lock_type, int const lock_mode,
        std::function<void()> const f = nullptr);

    /**  Возвращает идентификатор самых старых данных в очереди.
    \param open_mode режим чтения либо записи.
    \return Возвращает int дескриптор открытого файла
    */
    int _openFile(int const open_mode);

    bool _resetLock();  ///  Сбрасывает блокировку, если она установлена

    bool _alreadyLocked(int const lock_mode) const;

    void _prepareToLock(int const open_file_mode);
};
}  // namespace aqsi
