#ifndef GC_H
#define GC_H

#include <vector>

const int INCREMENTATION_NB_ID = 20;

class ID
{
public:
    explicit ID(int iID=0);

    int GetIDValue();
    bool GetInUse();
    void SetInUse(bool b);
private:
    int m_iID;
    bool m_bInUse;
};

ID::ID(int iID)
{
    m_bInUse = false;
    m_iID = iID;
}

int ID::GetIDValue()
{
    return m_iID;
}

bool ID::GetInUse()
{
    return m_bInUse;
}


void ID::SetInUse(bool b)
{
    m_bInUse = b;
}



class IDGenerator
{
public:
    IDGenerator(unsigned int iIncrementationNbID = INCREMENTATION_NB_ID);

    unsigned int GetMaxNbID();
    unsigned int GetNbCheckedOutID();
    unsigned int CheckOutID();
    bool CheckInID(unsigned int iID);

private:
    unsigned int m_iIncrementationNbID;
    unsigned int m_iMaxNbID;
    unsigned int m_iNbCheckedOutID;

    unsigned int m_iMinFreeID;
    unsigned int m_iNextMinFreeID;

    std::vector<ID> m_IDS;

    bool CanBeResizedDown();
    void SetNextAvailableIDs();
    void SetNextAvailableIDs(unsigned int iCheckedInID);
};

IDGenerator::IDGenerator(unsigned int iIncrementationNbID)
{
    m_iNbCheckedOutID = 0;
    m_iMaxNbID = 0;
    m_iIncrementationNbID = iIncrementationNbID;
    m_iMinFreeID = 0;
    m_iNextMinFreeID = 1;
}

unsigned int IDGenerator::GetMaxNbID()
{
    return m_iMaxNbID;
}

unsigned int IDGenerator::GetNbCheckedOutID()
{
    return m_iNbCheckedOutID;
}

unsigned int IDGenerator::CheckOutID()
{
    int iTempID = m_iMinFreeID;
    if (m_iNbCheckedOutID == m_iMaxNbID)
    {
        m_IDS.push_back(ID(m_iMaxNbID));
        m_iMaxNbID++;
    }
    m_iNbCheckedOutID++;
    m_IDS[iTempID].SetInUse(true);

    SetNextAvailableIDs();

    return m_IDS[iTempID].GetIDValue();
}

bool IDGenerator::CheckInID(unsigned int iID)
{
    bool bResult = true;
    if (iID >= m_IDS.size() || !m_IDS[iID].GetInUse())
    {
        bResult = false;
    }
    else
    {
        m_IDS[iID].SetInUse(false);
        m_iNbCheckedOutID--;
        if ((int)m_iNbCheckedOutID <= (int)m_iMaxNbID - (int)m_iIncrementationNbID*2)
        {
            if (CanBeResizedDown())
            {
                m_IDS.resize(m_iMaxNbID - m_iIncrementationNbID);
                m_iMaxNbID -= m_iIncrementationNbID;
            }
        }
        SetNextAvailableIDs(iID);
    }
    return bResult;
}

bool IDGenerator::CanBeResizedDown()
{
    unsigned int i;
    bool bCanBeResized = true;
    for (i=m_iMaxNbID - m_iIncrementationNbID; i<m_iMaxNbID; i++)
    {
        bCanBeResized &= !m_IDS[i].GetInUse();
    }
    return bCanBeResized;
}

void IDGenerator::SetNextAvailableIDs()
{
    unsigned int i;
    m_iMinFreeID = m_iNextMinFreeID;
    for (i=m_iNextMinFreeID+1; i<m_iMaxNbID && m_IDS[i].GetInUse(); i++);
    m_iNextMinFreeID = i;
}

void IDGenerator::SetNextAvailableIDs(unsigned int iCheckedInID)
{
    if (iCheckedInID <= m_iMinFreeID)
    {
        m_iNextMinFreeID = m_iMinFreeID;
        m_iMinFreeID = iCheckedInID;
    }
    else if (iCheckedInID < m_iNextMinFreeID)
    {
        m_iNextMinFreeID = iCheckedInID;
    }
}



template <class T>
class Allocator
{
public:
    Allocator();
    Allocator(const T& Object);
    Allocator(T* ptrObject);
    ~Allocator();

    T* GetPointer();
private:
    T* m_Data;
};

template <class T>
Allocator<T>::Allocator()
{
    m_Data = new T;
}

template <class T>
Allocator<T>::Allocator(const T& Object)
{
    m_Data = new T(Object);
}

template <class T>
Allocator<T>::Allocator(T* ptrObject)
{
    m_Data = ptrObject;
}

template <class T>
Allocator<T>::~Allocator()
{
    delete m_Data;
    m_Data = NULL;
}

template <class T>
T* Allocator<T>::GetPointer()
{
    return m_Data;
}



template <class T>
class GC
{
public:
    GC();
    GC(const T& Object);
    GC(T* ptrObject);
    GC(const GC<T> &oGC);
    ~GC();
    GC<T> operator= (const GC<T> &oGC);
    GC<T> operator= (const T& Object);
    GC<T> operator= (T* ptrObject);
    operator T();
    void operator& ();
    T& operator* ();
    T* operator-> ();

    void Free();

private:
    int m_iID;
    static IDGenerator m_IDGen;
    static std::vector<int> m_References;
    Allocator<T>* m_Data;

    void InitTasks();
};

template <class T>
IDGenerator GC<T>::m_IDGen = IDGenerator();

template <class T>
std::vector<int> GC<T>::m_References;

template <class T>
GC<T>::GC()
{
    InitTasks();
    m_Data = new Allocator<T>;
}

template <class T>
GC<T>::GC(const T& Object)
{
    InitTasks();
    m_Data = new Allocator<T>(Object);
}

template <class T>
GC<T>::GC(T* ptrObject)
{
    InitTasks();
    m_Data = new Allocator<T>(ptrObject);
}

template <class T>
GC<T>::GC(const GC<T> &oGC)
{
    m_iID = oGC.m_iID;
    m_Data = oGC.m_Data;
    m_References[m_iID]++;
}

template <class T>
GC<T>::~GC()
{
    if (m_References[m_iID] == 1)
    {
        m_IDGen.CheckInID(m_iID);
        if (m_Data != NULL) 
        {
            delete m_Data;
            m_Data = NULL;
        }
    }
    else
    {
        m_References[m_iID]--;
    }
}

template <class T>
GC<T>::operator T()
{
    return *m_Data->GetPointer();
}

template <class T>
GC<T> GC<T>::operator= (const GC<T> &oGC)
{
    if (this != &oGC)
    {
        if (m_References[m_iID] == 1)
        {
            m_IDGen.CheckInID(m_iID);
            delete m_Data;
        }
        else
        {
            m_References[m_iID]--;
        }
        m_iID = oGC.m_iID;
        m_Data = oGC.m_Data;
        m_References[m_iID]++;
    }
    return *this;
}

template <class T>
GC<T> GC<T>::operator= (const T& Object)
{
    *m_Data->GetPointer() = Object;
    return *this;
}

template <class T>
GC<T> GC<T>::operator= (T* ptrObject)
{
    m_Data = new Allocator<T>(ptrObject);
    return *this;
}

template <class T>
T* GC<T>::operator-> ()
{
    return m_Data->GetPointer();
}

template <class T>
void GC<T>::operator &()
{
}

template <class T>
T& GC<T>::operator *()
{
    return *m_Data->GetPointer();
}

template <class T>
void GC<T>::Free()
{
    delete m_Data;
    m_Data = NULL;
    m_IDGen.CheckInID(m_iID);
}

template <class T>
void GC<T>::InitTasks()
{
    m_iID = m_IDGen.CheckOutID();
    if (m_References.size() != m_IDGen.GetMaxNbID())
    {
        m_References.resize(m_IDGen.GetMaxNbID());
    }
    m_References[m_iID] = 1;
}

#endif