#pragma once

#include <library/cpp/logger/global/global.h>

#include <util/system/mutex.h>
#include <util/system/condvar.h>
#include <util/system/guard.h>

class ITransaction: public TNonCopyable {
public:
    enum TTransactionPolicy {tpIAPriority, tpEqualPriorities};
private:
    TMutex MutexConnections;
    TMutex MutexTransaction;
    TCondVar CondVar;
    int CountIncompatibleActions, ActiveTransactions;
    TTransactionPolicy Policy;
    TString Message;
public:
    ITransaction(TTransactionPolicy policy = tpIAPriority): CountIncompatibleActions(0), ActiveTransactions(0), Policy(policy) {
    }

    ~ITransaction() {
        if (!!Message) {
            DEBUG_LOG << "transaction " << Message << " destroy (ia:" << CountIncompatibleActions << " t:" << ActiveTransactions << ")" << Endl;
        }
        VERIFY_WITH_LOG(!CountIncompatibleActions, "Incorrect transaction destructor usage (%s)", Message.data());
        VERIFY_WITH_LOG(!ActiveTransactions, "Incorrect transaction destructor usage (%s)", Message.data());
    }

    bool StartIncompatibleAction(bool tryIt = false) {
        if (!!Message)
            DEBUG_LOG << "Incompatible action " << Message << " starting... (" << CountIncompatibleActions << ". Try=" << tryIt <<  ")" << Endl;
        if (Policy == tpEqualPriorities) {
            if (tryIt) {
                if (!MutexTransaction.TryAcquire()) {
                    if (!!Message)
                        DEBUG_LOG << "Incompatible action " << Message << " starting failed... (" << CountIncompatibleActions << ". Try=" << tryIt <<  ")" << Endl;
                    return false;
                }
            } else
                MutexTransaction.Acquire();
        }
        TGuard<TMutex> g(MutexConnections);
        ++CountIncompatibleActions;
        if (Policy == tpEqualPriorities)
            MutexTransaction.Release();
        if (!!Message)
            DEBUG_LOG << "Incompatible action " << Message << " start OK (" << CountIncompatibleActions << ". Try=" << tryIt <<  ")" << Endl;
        return true;
    }

    void FinishIncompatibleAction() {
        if (!!Message)
            DEBUG_LOG << "Incompatible action " << Message << " finish... (" << CountIncompatibleActions << ")" << Endl;
        TGuard<TMutex> g(MutexConnections);
        --CountIncompatibleActions;
        CondVar.Signal();
        if (!!Message)
            DEBUG_LOG << "Incompatible action " << Message << " finish OK (" << CountIncompatibleActions << ")" << Endl;
    }

    void StartTransaction() {
        MutexTransaction.Acquire();
        ++ActiveTransactions;
        MutexConnections.Acquire();
        while (CountIncompatibleActions) {
            CondVar.Wait(MutexConnections);
        }
        if (!!Message)
            DEBUG_LOG << "Transaction " << Message << " started OK (" << ActiveTransactions << ")" << Endl;
    }

    void FinishTransaction() {
        --ActiveTransactions;
        MutexConnections.Release();
        MutexTransaction.Release();
        if (!!Message)
            DEBUG_LOG << "Transaction " << Message << " finished OK (" << ActiveTransactions << ")" << Endl;
    }

    void SetMessage(const TString& message) {
        Message = message;
    }

};

class ITransactionEQ : public ITransaction {
public:
    ITransactionEQ()
        : ITransaction(ITransaction::tpEqualPriorities)
    {

    }
};

class TLockTransaction: public TCommonLockOps<ITransaction> {
public:
    static inline void Acquire(ITransaction* t) noexcept {
        t->StartTransaction();
    }

    static inline void Release(ITransaction* t) noexcept {
        t->FinishTransaction();
    }
};

class TLockIncompatibleAction: public TCommonLockOps<ITransaction> {
public:
    static inline void Acquire(ITransaction* t) noexcept {
        t->StartIncompatibleAction();
    }

    static inline void Release(ITransaction* t) noexcept {
        t->FinishIncompatibleAction();
    }
};

typedef TGuard<ITransaction, TLockTransaction> TGuardTransaction;
typedef TGuard<ITransaction, TLockIncompatibleAction> TGuardIncompatibleAction;

class TIncompatibleActionTryLocker {
public:
    static inline bool TryAcquire(ITransaction* t) noexcept {
        return t->StartIncompatibleAction(true);
    }

    static inline void Release(ITransaction* t) noexcept {
        t->FinishIncompatibleAction();
    }
};

typedef TTryGuard<ITransaction, TIncompatibleActionTryLocker> TTryGuardIncompatibleAction;

class TTryMultiGuardIncompatibleAction {
private:
    ITransaction* Transactions;
    ui32 Size;
    bool Status;
public:
    TTryMultiGuardIncompatibleAction(ITransaction* transactions, ui32 size) {
        Transactions = transactions;
        Size = size;
        Status = true;
        for (ui32 i = 0; i < Size; i++) {
            if (!Transactions[i].StartIncompatibleAction(true)) {
                Status = false;
                for (ui32 back = 0; back < i; back++)
                    Transactions[back].FinishIncompatibleAction();
            }
        }
    }

    bool Locked() const {
        return Status;
    }

    ~TTryMultiGuardIncompatibleAction() {
        if (Status)
            for (ui32 i = 0; i < Size; i++)
                Transactions[i].FinishIncompatibleAction();
    }

};

class TMultiGuardIncompatibleAction {
private:
    ITransaction* Transactions;
    ui32 Size;
public:
    TMultiGuardIncompatibleAction(ITransaction* transactions, ui32 size) {
        Transactions = transactions;
        Size = size;
        for (ui32 i = 0; i < Size; i++)
            Transactions[i].StartIncompatibleAction();
    }
    ~TMultiGuardIncompatibleAction() {
        for (ui32 i = 0; i < Size; i++)
            Transactions[i].FinishIncompatibleAction();
    }

};

template <class CMutex, class CGuard>
class TDeferredGuard {
private:
    CMutex& Mutex;
    THolder<CGuard> Holder;
    TString Message;
    TInstant StartTime;
public:
    TDeferredGuard(CMutex& mutex, TString message = ""): Mutex(mutex), Message(message) {
    }

    ~TDeferredGuard() {
        if (!!Message) {
            INFO_LOG << Message << "... Unlocked. Start=" << StartTime << ". Finish=" << Now() << ". Duration=" << Now() - StartTime << Endl;
        }
    }

    void Start() {
        Holder.Reset(new CGuard(Mutex));
        if (!!Message) {
            StartTime = Now();
            INFO_LOG << Message << "... Locked" << Endl;
        }
    }
};

typedef TDeferredGuard<ITransaction, TGuardTransaction> TDeferredGuardTransaction;
