//----------------------------------------------------------------------------
//Yume Engine
//Copyright (C) 2015  arkenthera
//This program is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2 of the License, or
//(at your option) any later version.
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License along
//with this program; if not, write to the Free Software Foundation, Inc.,
//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.*/
//----------------------------------------------------------------------------
//
// File : <Filename>
// Date : <Date>
// Comments :
//
//----------------------------------------------------------------------------
#ifndef __YumeArrayPtr_h__
#define __YumeArrayPtr_h__
//----------------------------------------------------------------------------

#pragma once
#include "YumeRequired.h"
#include "HashBase.h"
#include "Core/RefCounter.h"

#include <cassert>

namespace YumeEngine
{

/// Shared array pointer template class. Uses non-intrusive reference counting.
template <class T> class SharedArrayPtr
{
public:
    /// Construct a null shared array pointer.
    SharedArrayPtr() :
        ptr_(0),
        refCount_(0)
    {
    }

    /// Copy-construct from another shared array pointer.
    SharedArrayPtr(const SharedArrayPtr<T>& rhs) :
        ptr_(rhs.ptr_),
        refCount_(rhs.refCount_)
    {
        AddRef();
    }

    /// Construct from a raw pointer.
    explicit SharedArrayPtr(T* ptr) :
        ptr_(ptr),
        refCount_(new RefCount())
    {
        AddRef();
    }

    /// Destruct. Release the array reference.
    ~SharedArrayPtr()
    {
        ReleaseRef();
    }

    /// Assign from another shared array pointer.
    SharedArrayPtr<T>& operator =(const SharedArrayPtr<T>& rhs)
    {
        if (ptr_ == rhs.ptr_)
            return *this;

        ReleaseRef();
        ptr_ = rhs.ptr_;
        refCount_ = rhs.refCount_;
        AddRef();

        return *this;
    }

    /// Assign from a raw pointer.
    SharedArrayPtr<T>& operator =(T* ptr)
    {
        if (ptr_ == ptr)
            return *this;

        ReleaseRef();

        if (ptr)
        {
            ptr_ = ptr;
            refCount_ = new RefCount();
            AddRef();
        }

        return *this;
    }

    /// Point to the array.
    T* operator ->() const
    {
        assert(ptr_);
        return ptr_;
    }

    /// Dereference the array.
    T& operator *() const
    {
        assert(ptr_);
        return *ptr_;
    }

    /// Subscript the array.
    T& operator [](const int index)
    {
        assert(ptr_);
        return ptr_[index];
    }

    /// Test for equality with another shared array pointer.
    bool operator ==(const SharedArrayPtr<T>& rhs) const { return ptr_ == rhs.ptr_; }

    /// Test for inequality with another shared array pointer.
    bool operator !=(const SharedArrayPtr<T>& rhs) const { return ptr_ != rhs.ptr_; }

    /// Test for less than with another array pointer.
    bool operator <(const SharedArrayPtr<T>& rhs) const { return ptr_ < rhs.ptr_; }

    /// Convert to a raw pointer.
    operator T*() const { return ptr_; }

    /// Reset to null and release the array reference.
    void Reset() { ReleaseRef(); }

    /// Perform a static cast from a shared array pointer of another type.
    template <class U> void StaticCast(const SharedArrayPtr<U>& rhs)
    {
        ReleaseRef();
        ptr_ = static_cast<T*>(rhs.Get());
        refCount_ = rhs.RefCountPtr();
        AddRef();
    }

    /// Perform a reinterpret cast from a shared array pointer of another type.
    template <class U> void ReinterpretCast(const SharedArrayPtr<U>& rhs)
    {
        ReleaseRef();
        ptr_ = reinterpret_cast<T*>(rhs.Get());
        refCount_ = rhs.RefCountPtr();
        AddRef();
    }

    /// Check if the pointer is null.
    bool Null() const { return ptr_ == 0; }

    /// Check if the pointer is not null.
    bool NotNull() const { return ptr_ != 0; }

    /// Return the raw pointer.
    T* Get() const { return ptr_; }

    /// Return the array's reference count, or 0 if the pointer is null.
    int Refs() const { return refCount_ ? refCount_->refs_ : 0; }

    /// Return the array's weak reference count, or 0 if the pointer is null.
    int WeakRefs() const { return refCount_ ? refCount_->weakRefs_ : 0; }

    /// Return pointer to the RefCount structure.
    RefCount* RefCountPtr() const { return refCount_; }

    /// Return hash value for HashSet & HashMap.
    unsigned ToHash() const { return (unsigned)((size_t)ptr_ / sizeof(T)); }

private:
    /// Prevent direct assignment from a shared array pointer of different type.
    template <class U> SharedArrayPtr<T>& operator =(const SharedArrayPtr<U>& rhs);

    /// Add a reference to the array pointed to.
    void AddRef()
    {
        if (refCount_)
        {
            assert(refCount_->refs_ >= 0);
            ++(refCount_->refs_);
        }
    }

    /// Release the array reference and delete it and the RefCount structure if necessary.
    void ReleaseRef()
    {
        if (refCount_)
        {
            assert(refCount_->refs_ > 0);
            --(refCount_->refs_);
            if (!refCount_->refs_)
            {
                refCount_->refs_ = -1;
                delete[] ptr_;
            }

            if (refCount_->refs_ < 0 && !refCount_->weakRefs_)
                delete refCount_;
        }

        ptr_ = 0;
        refCount_ = 0;
    }

    /// Pointer to the array.
    T* ptr_;
    /// Pointer to the RefCount structure.
    RefCount* refCount_;
};

/// Perform a static cast from one shared array pointer type to another.
template <class T, class U> SharedArrayPtr<T> StaticCast(const SharedArrayPtr<U>& ptr)
{
    SharedArrayPtr<T> ret;
    ret.StaticCast(ptr);
    return ret;
}

/// Perform a reinterpret cast from one shared array pointer type to another.
template <class T, class U> SharedArrayPtr<T> ReinterpretCast(const SharedArrayPtr<U>& ptr)
{
    SharedArrayPtr<T> ret;
    ret.ReinterpretCast(ptr);
    return ret;
}

/// Weak array pointer template class. Uses non-intrusive reference counting.
template <class T> class WeakArrayPtr
{
public:
    /// Construct a null weak array pointer.
    WeakArrayPtr() :
        ptr_(0),
        refCount_(0)
    {
    }

    /// Copy-construct from another weak array pointer.
    WeakArrayPtr(const WeakArrayPtr<T>& rhs) :
        ptr_(rhs.ptr_),
        refCount_(rhs.refCount_)
    {
        AddRef();
    }

    /// Construct from a shared array pointer.
    WeakArrayPtr(const SharedArrayPtr<T>& rhs) :
        ptr_(rhs.Get()),
        refCount_(rhs.RefCountPtr())
    {
        AddRef();
    }

    /// Destruct. Release the weak reference to the array.
    ~WeakArrayPtr()
    {
        ReleaseRef();
    }

    /// Assign from a shared array pointer.
    WeakArrayPtr<T>& operator =(const SharedArrayPtr<T>& rhs)
    {
        if (ptr_ == rhs.Get() && refCount_ == rhs.RefCountPtr())
            return *this;

        ReleaseRef();
        ptr_ = rhs.Get();
        refCount_ = rhs.RefCountPtr();
        AddRef();

        return *this;
    }

    /// Assign from another weak array pointer.
    WeakArrayPtr<T>& operator =(const WeakArrayPtr<T>& rhs)
    {
        if (ptr_ == rhs.ptr_ && refCount_ == rhs.refCount_)
            return *this;

        ReleaseRef();
        ptr_ = rhs.ptr_;
        refCount_ = rhs.refCount_;
        AddRef();

        return *this;
    }

    /// Convert to shared array pointer. If expired, return a null shared array pointer.
    SharedArrayPtr<T> Lock() const
    {
        if (Expired())
            return SharedArrayPtr<T>();
        else
            return SharedArrayPtr<T>(ptr_, refCount_);
    }

    /// Return raw pointer. If expired, return null.
    T* Get() const
    {
        if (Expired())
            return 0;
        else
            return ptr_;
    }

    /// Point to the array.
    T* operator ->() const
    {
        T* rawPtr = Get();
        assert(rawPtr);
        return rawPtr;
    }

    /// Dereference the array.
    T& operator *() const
    {
        T* rawPtr = Get();
        assert(rawPtr);
        return *rawPtr;
    }

    /// Subscript the array.
    T& operator [](const int index)
    {
        T* rawPtr = Get();
        assert(rawPtr);
        return (*rawPtr)[index];
    }

    /// Test for equality with another weak array pointer.
    bool operator ==(const WeakArrayPtr<T>& rhs) const { return ptr_ == rhs.ptr_ && refCount_ == rhs.refCount_; }

    /// Test for inequality with another weak array pointer.
    bool operator !=(const WeakArrayPtr<T>& rhs) const { return ptr_ != rhs.ptr_ || refCount_ != rhs.refCount_; }

    /// Test for less than with another weak array pointer.
    bool operator <(const WeakArrayPtr<T>& rhs) const { return ptr_ < rhs.ptr_; }

    /// Convert to a raw pointer, null if array is expired.
    operator T*() const { return Get(); }

    /// Reset to null and release the weak reference.
    void Reset() { ReleaseRef(); }

    /// Perform a static cast from a weak array pointer of another type.
    template <class U> void StaticCast(const WeakArrayPtr<U>& rhs)
    {
        ReleaseRef();
        ptr_ = static_cast<T*>(rhs.Get());
        refCount_ = rhs.refCount_;
        AddRef();
    }

    /// Perform a dynamic cast from a weak array pointer of another type.
    template <class U> void DynamicCast(const WeakArrayPtr<U>& rhs)
    {
        ReleaseRef();
        ptr_ = dynamic_cast<T*>(rhs.Get());

        if (ptr_)
        {
            refCount_ = rhs.refCount_;
            AddRef();
        }
        else
            refCount_ = 0;
    }

    /// Check if the pointer is null.
    bool Null() const { return refCount_ == 0; }

    /// Check if the pointer is not null.
    bool NotNull() const { return refCount_ != 0; }

    /// Return the array's reference count, or 0 if null pointer or if array has expired.
    int Refs() const { return (refCount_ && refCount_->refs_ >= 0) ? refCount_->refs_ : 0; }

    /// Return the array's weak reference count.
    int WeakRefs() const { return refCount_ ? refCount_->weakRefs_ : 0; }

    /// Return whether the array has expired. If null pointer, always return true.
    bool Expired() const { return refCount_ ? refCount_->refs_ < 0 : true; }

    /// Return pointer to RefCount structure.
    RefCount* RefCountPtr() const { return refCount_; }

    /// Return hash value for HashSet & HashMap.
    unsigned ToHash() const { return (unsigned)((size_t)ptr_ / sizeof(T)); }

private:
    /// Prevent direct assignment from a weak array pointer of different type.
    template <class U> WeakArrayPtr<T>& operator =(const WeakArrayPtr<U>& rhs);

    /// Add a weak reference to the array pointed to.
    void AddRef()
    {
        if (refCount_)
        {
            assert(refCount_->weakRefs_ >= 0);
            ++(refCount_->weakRefs_);
        }
    }

    /// Release the weak reference. Delete the Refcount structure if necessary.
    void ReleaseRef()
    {
        if (refCount_)
        {
            assert(refCount_->weakRefs_ >= 0);

            if (refCount_->weakRefs_ > 0)
                --(refCount_->weakRefs_);

            if (Expired() && !refCount_->weakRefs_)
                delete refCount_;
        }

        ptr_ = 0;
        refCount_ = 0;
    }

    /// Pointer to the array.
    T* ptr_;
    /// Pointer to the RefCount structure.
    RefCount* refCount_;
};

/// Perform a static cast from one weak array pointer type to another.
template <class T, class U> WeakArrayPtr<T> StaticCast(const WeakArrayPtr<U>& ptr)
{
    WeakArrayPtr<T> ret;
    ret.StaticCast(ptr);
    return ret;
}

/// Perform a reinterpret cast from one weak pointer type to another.
template <class T, class U> WeakArrayPtr<T> ReinterpretCast(const WeakArrayPtr<U>& ptr)
{
    WeakArrayPtr<T> ret;
    ret.ReinterpretCast(ptr);
    return ret;
}

}
#endif