#include "Critical.h"

#include <iostream>
#include <time.h>

#include "Globals.h"
#include "Thread.h"

using namespace Framework;

// Content of the Critical class from Critical.h
// Konstructor
Critical::Critical()
{
    InitializeCriticalSection(&cs);
    lockCount = 0;
}

// Destructor
Critical::~Critical()
{
    DeleteCriticalSection(&cs);
}

// locks the object
void Critical::lock()
{
    currentThreadLockCount++;
    EnterCriticalSection(&cs);
    lockCount++;
}

// tries to lock the object
bool Critical::tryLock()
{
    if (lockCount > 0) return false;
    currentThreadLockCount++;
    EnterCriticalSection(&cs);
    lockCount++;
    return true;
}

// unlocks the object
void Critical::unlock()
{
    if (lockCount <= 0)
    {
        throw "A Thread tried to unlock a critical that was not locked before";
    }
    lockCount--;
    LeaveCriticalSection(&cs);
    currentThreadLockCount--;
}

// returns true if the object is locked
bool Critical::isLocked() const
{
    return lockCount > 0;
}

Synchronizer::Synchronizer()
    : numWaiting(0),
      skip(0)
{}

Synchronizer::~Synchronizer()
{
    skip = 1;
    std::unique_lock<std::mutex> lk(mutex);
    if (numWaiting > 0)
    {
        lk.unlock();
        block.notify_all();
    }
    else
        lk.unlock();
    while (numWaiting > 0)
        Sleep(10);
    lk.lock();
}

bool Synchronizer::wait()
{
    std::unique_lock<std::mutex> lk(mutex);
    if (skip) return false;
    numWaiting++;
    block.wait(lk);
    numWaiting--;
    return !skip;
}

bool Synchronizer::wait(int milisec)
{
    std::unique_lock<std::mutex> lk(mutex);
    if (skip) return false;
    numWaiting++;
    std::cv_status status
        = block.wait_for(lk, std::chrono::milliseconds(milisec));
    numWaiting--;
    return !skip && status == std::cv_status::no_timeout;
}

void Synchronizer::notify()
{
    block.notify_one();
}

void Synchronizer::notify(int amount)
{
    while (amount--)
        block.notify_one();
}

void Synchronizer::notifyAll()
{
    block.notify_all();
}

int Synchronizer::getNumberOfWaitingThreads() const
{
    return numWaiting;
}

Framework::StackLock::StackLock(std::initializer_list<Lock*> locks)
{
    this->locks = new Lock*[locks.size()];
    int i = 0;
    for (Lock* c : locks)
    {
        this->locks[i] = c;
        i++;
    }
    size = (int)locks.size();
    if (size > 0)
    {
        this->locks[0]->lock();
        int index = 1;
        while (index < size)
        {
            if (!this->locks[index]->tryLock())
            {
                for (int i = 0; i < index; i++)
                {
                    this->locks[i]->unlock();
                }
                this->locks[index]->lock();
                this->locks[index]->unlock();
                this->locks[0]->lock();
                index = 1;
            }
            else
                index++;
        }
    }
}

Framework::StackLock::~StackLock()
{
    for (int i = 0; i < size; i++)
    {
        locks[i]->unlock();
    }
    delete[] locks;
}

class Framework::InternalReadLock : public Lock
{
private:
    ReadWriteLock* rwLock;

public:
    InternalReadLock(ReadWriteLock* rwLock)
        : rwLock(rwLock)
    {}

    virtual ~InternalReadLock() = default;

    void lock() override
    {
        rwLock->lockRead();
    }

    void unlock() override
    {
        rwLock->unlockRead();
    }

    bool tryLock() override
    {
        return rwLock->tryLockRead();
    }
};

class Framework::InternalWriteLock : public Lock
{
private:
    ReadWriteLock* rwLock;

public:
    InternalWriteLock(ReadWriteLock* rwLock)
        : rwLock(rwLock)
    {}

    virtual ~InternalWriteLock() = default;

    void lock() override
    {
        rwLock->lockWrite();
    }

    void unlock() override
    {
        rwLock->unlockWrite();
    }

    bool tryLock() override
    {
        return rwLock->tryLockWrite();
    }
};

Framework::ReadWriteLock::ReadWriteLock(int initialMaxSize)
    : readerThreads(new int[initialMaxSize]),
      readCounters(new int[initialMaxSize]),
      maxSize(initialMaxSize),
      readerThreadCount(0),
      writerThread(0),
      writerCount(0),
      waitingReaders(0),
      waitingWriters(0),
      readLock(new InternalReadLock(this)),
      writeLock(new InternalWriteLock(this))
{
    memset(readerThreads, 0, sizeof(int) * initialMaxSize);
    memset(readCounters, 0, sizeof(int) * initialMaxSize);
}

Framework::ReadWriteLock::~ReadWriteLock()
{
    delete[] readerThreads;
    delete[] readCounters;
    delete readLock;
    delete writeLock;
}

void Framework::ReadWriteLock::lockRead()
{
    cs.lock();
    while (writerCount > 0 && writerThread != currentThreadId)
    {
        waitingReaders++;
        cs.unlock();
        readerBlock.wait();
        cs.lock();
        waitingReaders--;
    }
    currentThreadLockCount++;
    int index = -1;
    for (int i = 0; i < readerThreadCount; i++)
    {
        if (readerThreads[i] == currentThreadId)
        {
            index = i;
            break;
        }
    }
    if (index == -1)
    {
        if (readerThreadCount == maxSize)
        {
            int* newReaderThreads = new int[maxSize * 2];
            int* newReadCounters = new int[maxSize * 2];
            for (int i = 0; i < maxSize; i++)
            {
                newReaderThreads[i] = readerThreads[i];
                newReadCounters[i] = readCounters[i];
            }
            delete[] readerThreads;
            delete[] readCounters;
            readerThreads = newReaderThreads;
            readCounters = newReadCounters;
            maxSize *= 2;
        }
        index = readerThreadCount++;
        readerThreads[index] = currentThreadId;
    }
    readCounters[index]++;
    cs.unlock();
}

void Framework::ReadWriteLock::lockWrite()
{
    cs.lock();
    while (
        (writerCount > 0 && writerThread != currentThreadId)
        || (readerThreadCount > 0
            && (readerThreadCount != 1 || readerThreads[0] != currentThreadId)))
    {
        waitingWriters++;
        cs.unlock();
        writerBlock.wait();
        cs.lock();
        waitingWriters--;
    }
    currentThreadLockCount++;
    writerThread = currentThreadId;
    writerCount++;
    cs.unlock();
}

void Framework::ReadWriteLock::unlockRead()
{
    cs.lock();
    int index = -1;
    for (int i = 0; i < readerThreadCount; i++)
    {
        if (readerThreads[i] == currentThreadId)
        {
            index = i;
            break;
        }
    }
    if (index == -1 || readCounters[index] == 0)
    {
        cs.unlock();
        throw "A Thread that does not hold a read lock tries to unlock it";
    }
    readCounters[index]--;
    if (readCounters[index] == 0)
    {
        readerThreads[index] = readerThreads[readerThreadCount - 1];
        readCounters[index] = readCounters[readerThreadCount - 1];
        readerThreadCount--;
    }
    if (readerThreadCount == 0 && waitingWriters > 0)
    {
        writerBlock.notify();
    }
    currentThreadLockCount--;
    cs.unlock();
}

void Framework::ReadWriteLock::unlockWrite()
{
    cs.lock();
    if (writerThread != currentThreadId || writerCount == 0)
    {
        cs.unlock();
        throw "A Thread that does not hold the write lock tries to unlock it";
    }
    writerCount--;
    if (writerCount == 0)
    {
        if (waitingWriters > 0)
        {
            writerBlock.notify();
        }
        else if (waitingReaders > 0)
        {
            readerBlock.notify(waitingReaders);
        }
    }
    currentThreadLockCount--;
    cs.unlock();
}

bool Framework::ReadWriteLock::tryLockRead()
{
    cs.lock();
    if (writerCount > 0 && writerThread != currentThreadId)
    {
        cs.unlock();
        return false;
    }
    currentThreadLockCount++;
    int index = -1;
    for (int i = 0; i < readerThreadCount; i++)
    {
        if (readerThreads[i] == currentThreadId)
        {
            index = i;
            break;
        }
    }
    if (index == -1)
    {
        if (readerThreadCount == maxSize)
        {
            int* newReaderThreads = new int[maxSize * 2];
            int* newReadCounters = new int[maxSize * 2];
            for (int i = 0; i < maxSize; i++)
            {
                newReaderThreads[i] = readerThreads[i];
                newReadCounters[i] = readCounters[i];
            }
            delete[] readerThreads;
            delete[] readCounters;
            readerThreads = newReaderThreads;
            readCounters = newReadCounters;
            maxSize *= 2;
        }
        index = readerThreadCount++;
        readerThreads[index] = currentThreadId;
    }
    readCounters[index]++;
    cs.unlock();
    return true;
}

bool Framework::ReadWriteLock::tryLockWrite()
{
    cs.lock();
    if ((writerCount > 0 && writerThread != currentThreadId)
        || (readerThreadCount > 0
            && (readerThreadCount != 1 || readerThreads[0] != currentThreadId)))
    {
        cs.unlock();
        return false;
    }
    currentThreadLockCount++;
    writerThread = currentThreadId;
    writerCount++;
    cs.unlock();
    return true;
}

Lock& Framework::ReadWriteLock::getReadLock() const
{
    return *readLock;
}

Lock& Framework::ReadWriteLock::getWriteLock() const
{
    return *writeLock;
}