#pragma once

#include <condition_variable>
#include <initializer_list>

#include "OperatingSystem.h"

namespace Framework
{
    class Thread;

    class Lock
    {
    public:
        virtual void lock() = 0;
        virtual bool tryLock() = 0;
        virtual void unlock() = 0;
    };

    class Critical : public Lock
    {
    private:
        CRITICAL_SECTION cs;
        int lockCount;

    public:
        //! Constructor
        DLLEXPORT Critical();
        //! Destructor
        DLLEXPORT ~Critical();
        //! Locks the object
        DLLEXPORT void lock() override;
        //! Tries to lock the object
        DLLEXPORT bool tryLock() override;
        //! Unlocks the object
        DLLEXPORT void unlock() override;

        //! Returns true if the object is locked
        DLLEXPORT bool isLocked() const;
    };

    class StackLock
    {
    private:
        Lock** locks;
        int size;

    public:
        DLLEXPORT StackLock(std::initializer_list<Lock*> locks);
        DLLEXPORT ~StackLock();
    };

#define LOCK(x)     Framework::StackLock _lock(x)
#define LOCKN(x, i) Framework::StackLock _lock_##i(x)

    class Synchronizer
    {
    private:
        std::condition_variable block;
        std::mutex mutex;
        int numWaiting;
        bool skip;

    public:
        DLLEXPORT Synchronizer();
        DLLEXPORT ~Synchronizer();

        DLLEXPORT bool wait();
        DLLEXPORT bool wait(int milisec);
        DLLEXPORT void notify();
        DLLEXPORT void notify(int amount);
        DLLEXPORT void notifyAll();

        DLLEXPORT int getNumberOfWaitingThreads() const;
    };

    class InternalReadLock;
    class InternalWriteLock;

    class ReadWriteLock
    {
    private:
        int* readerThreads;
        int* readCounters;
        int maxSize;
        int readerThreadCount;
        int writerThread;
        int writerCount;
        int waitingReaders;
        int waitingWriters;
        Critical cs;
        Synchronizer readerBlock;
        Synchronizer writerBlock;
        InternalReadLock* readLock;
        InternalWriteLock* writeLock;

    public:
        DLLEXPORT ReadWriteLock(int initialMaxSize = 10);
        DLLEXPORT ~ReadWriteLock();
        DLLEXPORT void lockRead();
        DLLEXPORT void lockWrite();
        DLLEXPORT void unlockRead();
        DLLEXPORT void unlockWrite();
        DLLEXPORT bool tryLockRead();
        DLLEXPORT bool tryLockWrite();
        DLLEXPORT Lock& getReadLock() const;
        DLLEXPORT Lock& getWriteLock() const;
    };
} // namespace Framework