//#####################################################################
// Copyright 2004-2007, Ronald Fedkiw, Geoffrey Irving, Tamar Shinar, Eftychios Sifakis, Jerry Talton.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class STATIC_ARRAY
//#####################################################################
#ifndef __STATIC_ARRAY__
#define __STATIC_ARRAY__

#include <Core/Arrays/ARRAY.h>
#include <Core/Arrays/ARRAY_BASE.h>
#include <Core/Log/DEBUG_UTILITIES.h>
#include <Core/Math_Tools/exchange.h>
#include <Core/Math_Tools/minmax.h>
#include <Core/Utilities/EXCEPTIONS.h>
#include <Core/Utilities/PHYSBAM_ATTRIBUTE.h>
#include <Core/Utilities/TYPE_UTILITIES.h>
#include <initializer_list>
namespace PhysBAM{

template<class T,int d,class ID=int> class STATIC_ARRAY;
template<class T,int d,class ID> struct IS_ARRAY<STATIC_ARRAY<T,d,ID> > {static const bool value=true;};
template<class T,int d,class ID> struct IS_ARRAY_VIEW<STATIC_ARRAY<T,d,ID> > {static const bool value=false;};
template<class T,int d,class ID> struct HAS_POINTER<STATIC_ARRAY<T,d,ID> > {static const bool value=true;};

template<class T,int d,class ID> struct CANONICALIZE_CONST_ARRAY<STATIC_ARRAY<T,d,ID> >:public FIRST<ARRAY_VIEW<const T,ID> >{};

template<class T,int d,class ID>
class STATIC_ARRAY:public ARRAY_BASE<T,STATIC_ARRAY<T,d,ID>,ID>
{
    struct UNUSABLE{};
public:
    static constexpr int fixed_m=-1;
    template<class T2> struct REBIND{typedef ARRAY<T2,ID> TYPE;};
    typedef T ELEMENT;typedef ID INDEX;
    using ARRAY_BASE<T,STATIC_ARRAY<T,d,ID>,ID>::Same_Array;
    typedef T* iterator; // for stl
    typedef const T* const_iterator; // for stl
    typedef T& RESULT_TYPE;
    typedef const T& CONST_RESULT_TYPE;

    union
    {
        char space[sizeof(T)*d];
        T array[d];
    };
    ID m;

    STATIC_ARRAY()
        :m(0)
    {
    }

    explicit STATIC_ARRAY(ID m_input)
        :m(m_input)
    {
        assert(m<=d);
        for(int i=0,j=Value(m);i<j;i++) new(array+i)T();
    }

    STATIC_ARRAY(ID m_input,NO_INIT)
        :m(m_input)
    {
        assert(m<=d);
    }

    STATIC_ARRAY(ID m_input,INIT_NEW,const T& __restrict__ initialization_value)
        :m(m_input)
    {
        assert(m<=d);
        for(int i=0,j=Value(m);i<j;i++) new(array+i)T(initialization_value);
    }

    STATIC_ARRAY(const STATIC_ARRAY& a)
        :m(a.m)
    {
        for(int i=0,j=Value(m);i<j;i++) new(array+i)T(a.array[i]);
    }

    STATIC_ARRAY(STATIC_ARRAY&& a)
        :m(a.m)
    {
        for(int i=0,j=Value(m);i<j;i++) new(array+i)T(std::move(a.array[i]));
    }

    template<class T_ARRAY>
    explicit STATIC_ARRAY(const ARRAY_BASE<T,T_ARRAY,ID>& a)
        :m(a.Size())
    {
        for(int i=0,j=Value(m);i<j;i++) new(array+i)T(a(ID(i)));
    }

    STATIC_ARRAY(std::initializer_list<T> init)
        :m(init.size())
    {
        if(m<=ID()) return;
        T* p=array;
        for(const auto& a:init) new(p++)T(a);
    }

    ~STATIC_ARRAY()
    {
        Call_Destructors_And_Free();
    }

    ID Size() const
    {return m;}

    T& operator()(ID i)
    {assert((unsigned)Value(i)<(unsigned)Value(m));return array[Value(i)];}

    const T& operator()(ID i) const
    {assert((unsigned)Value(i)<(unsigned)Value(m));return array[Value(i)];}

    STATIC_ARRAY& operator=(const STATIC_ARRAY& a)
    {
        if(Same_Array(*this,array)) return *this;
        unsigned n=Value(m),p=Value(a.m);
        for(unsigned i=0,j=min(n,p);i<j;i++) array[i]=a.array[i];
        for(unsigned i=n;i<p;i++) new(array+i)T(a.array[i]);
        for(unsigned i=p;i<n;i++) array[i].~T();
        m=a.m;
        return *this;
    }

    STATIC_ARRAY& operator=(STATIC_ARRAY&& a)
    {
        ID array_m=a.Size();
        unsigned n=Value(m),p=Value(array_m);
        for(unsigned i=0,j=min(n,p);i<j;i++) array[i]=std::move(a(ID(i)));
        for(unsigned i=n;i<p;i++) new(array+i)T(std::move(a(ID(i))));
        for(unsigned i=p;i<n;i++) array[i].~T();
        m=array_m;
        return *this;
    }

    template<class T_ARRAY>
    STATIC_ARRAY& operator=(const ARRAY_BASE<T,T_ARRAY,ID>& a)
    {
        ID array_m=a.Size();
        unsigned n=Value(m),p=Value(array_m);
        for(unsigned i=0,j=min(n,p);i<j;i++) array[i]=a(ID(i));
        for(unsigned i=n;i<p;i++) new(array+i)T(a(ID(i)));
        for(unsigned i=p;i<n;i++) array[i].~T();
        m=array_m;
        return *this;
    }

    bool Valid_Index(ID i) const
    {return (unsigned)Value(i)<(unsigned)Value(m);}

    T* Get_Array_Pointer()
    {return array;}

    const T* Get_Array_Pointer() const
    {return array;}

    ID Max_Size() const
    {return d;}

private:
    void Call_Destructors_And_Free() PHYSBAM_ALWAYS_INLINE
    {
        for(int i=Value(m)-1;i>=0;i--) array[i].~T();
    }

    void Resize_Helper_Fill(int n) PHYSBAM_ALWAYS_INLINE
    {
        for(int i=0;i<n;i++) array[i]=T();
    }

    void Resize_Helper_Fill(int n,const T& initialization_value) PHYSBAM_ALWAYS_INLINE
    {
        for(int i=0;i<n;i++) array[i]=initialization_value;
    }

    template<bool exact,bool copy,bool init,bool fill,class... Args>
    void Resize_Helper(ID m_new,Args&&... initialization_value)
    {
        int n=Value(m),p=Value(m_new);
        assert(m_new<=d);
        if constexpr(fill) Resize_Helper_Fill(min(n,p),initialization_value...);
        if constexpr(init) for(int i=n;i<p;i++) new(array+i)T(initialization_value...);
        else for(int i=n;i<p;i++) new(array+i)T;
        for(int i=p;i<n;i++) array[i].~T();
        m=m_new;
    }

public:

    void Resize(ID m_new)
    {Resize_Helper<false,true,true,false>(m_new);}
    
    void Resize(ID m_new,NO_INIT)
    {Resize_Helper<false,false,false,false>(m_new);}

    // Aliasing with initialization_value is okay.
    void Resize(ID m_new,INIT_NEW,const T& initialization_value)
    {Resize_Helper<false,true,true,false>(m_new,initialization_value);}

    void Resize(ID m_new,INIT_ALL)
    {Resize_Helper<false,false,true,true>(m_new);}

    // Aliasing with initialization_value is okay.
    void Resize(ID m_new,INIT_ALL,const T& initialization_value)
    {Resize_Helper<false,false,true,true>(m_new,initialization_value);}

    void Exact_Resize(ID m_new)
    {Resize_Helper<true,true,true,false>(m_new);}
    
    void Exact_Resize(ID m_new,NO_INIT)
    {Resize_Helper<true,false,false,false>(m_new);}

    // Aliasing with initialization_value is okay.
    void Exact_Resize(ID m_new,INIT_NEW,const T& initialization_value)
    {Resize_Helper<true,true,true,false>(m_new,initialization_value);}

    void Exact_Resize(ID m_new,INIT_ALL)
    {Resize_Helper<true,false,true,true>(m_new);}

    // Aliasing with initialization_value is okay.
    void Exact_Resize(ID m_new,INIT_ALL,const T& initialization_value)
    {Resize_Helper<true,false,true,true>(m_new,initialization_value);}

    // Aliasing with element is okay.
    ID Append(const T& element) PHYSBAM_ALWAYS_INLINE
    {assert(m<d);new(array+Value(m))T(element);return m++;}

    // Aliasing with element is okay.
    ID Append(T&& element) PHYSBAM_ALWAYS_INLINE
    {assert(m<d);new(array+Value(m))T(std::move(element));return m++;}

    // Aliasing is not allowed
    template<class T_ARRAY>
    void Append(const ARRAY_BASE<T,T_ARRAY,ID>& append_array)
    {ID n=append_array.Size(),p=Value(m);Resize_Helper<false,true,false,false>(m+n);for(ID i(0);i<n;i++) (*this)(i+p)=append_array(i);}

    // Note: not very efficient
    void Append_Unique(const T& element)
    {for(ID i(0);i<m;i++) if((*this)(i)==element) return;Append(element);}

    // Note: not efficient
    template<class T_ARRAY>
    void Append_Unique(const ARRAY_BASE<T,T_ARRAY,ID>& append_array)
    {ID n=append_array.Size();for(ID i(0);i<n;i++) Append_Unique(append_array(i));}

    ID Add_End() PHYSBAM_ALWAYS_INLINE
    {assert(m<d);new(array+Value(m))T();return m++;}

    void Remove_End()
    {assert(m>ID());array[Value(--m)].~T();}

    void Remove_Index(ID index) // preserves ordering of remaining elements
    {assert((unsigned)Value(index)<(unsigned)Value(m));for(ID i=index;i<m-1;i++) (*this)(i)=std::move((*this)(i+1));Remove_End();}

    void Remove_Index_Lazy(ID index)
    {assert((unsigned)Value(index)<(unsigned)Value(m));if(index<m-1) (*this)(index)=std::move((*this)(m-1));Remove_End();}

    void Remove_All() // if elements are non-primitive this may waste memory
    {for(int i=Value(m)-1;i>=0;i--) array[i].~T();m=ID();}

    void Clean_Memory()
    {Call_Destructors_And_Free();m=ID();}

    void Delete_Pointers_And_Clean_Memory() // only valid if T is a pointer type
    {for(ID i(0);i<m;i++) delete (*this)(i);Clean_Memory();}

    // Aliasing is not allowed
    void Insert(const T& element,ID index)
    {Add_End();for(ID i=m-1;i>index;i--) (*this)(i)=std::move((*this)(i-1));(*this)(index)=element;}

    T Pop_Value()
    {T copy=array[Value(m)-1];Remove_End();return copy;}

    void Pop()
    {Remove_End();}

    void Pop(const int count) // return value should be copied immediately, not kept around
    {int n=Value(m);assert(n-count>=0);for(int i=n-1;i>=n-count;i--) array[i].~T();m-=count;}

    const STATIC_ARRAY& operator+() const
    {return *this;};

    STATIC_ARRAY operator-() const
    {STATIC_ARRAY r(m);
    for(int i=0;i<m;i++) r.array[i]=-array[i];
    return r;}

    STATIC_ARRAY operator+(const T& a) const
    {STATIC_ARRAY r(m);
    for(int i=0;i<m;i++) r.array[i]=array[i]+a;
    return r;}

    STATIC_ARRAY operator-(const T& a) const
    {STATIC_ARRAY r(m);
    for(int i=0;i<m;i++) r.array[i]=array[i]-a;
    return r;}

    STATIC_ARRAY operator*(const T& a) const
    {STATIC_ARRAY r(m);
    for(int i=0;i<m;i++) r.array[i]=array[i]*a;
    return r;}

    STATIC_ARRAY operator/(const T& a) const
    {return *this*Inverse(a);}

    STATIC_ARRAY operator+(const STATIC_ARRAY& v) const
    {STATIC_ARRAY r(m);assert(m==v.m);
    for(int i=0;i<m;i++) r.array[i]=array[i]+v.array[i];
    return r;}

    STATIC_ARRAY operator-(const STATIC_ARRAY& v) const
    {STATIC_ARRAY r(m);assert(m==v.m);
    for(int i=0;i<m;i++) r.array[i]=array[i]-v.array[i];
    return r;}

    STATIC_ARRAY operator*(const STATIC_ARRAY& v) const
    {STATIC_ARRAY r(m);assert(m==v.m);
    for(int i=0;i<m;i++) r.array[i]=array[i]*v.array[i];
    return r;}

    STATIC_ARRAY operator/(const STATIC_ARRAY& v) const
    {STATIC_ARRAY r(m);assert(m==v.m);
    for(int i=0;i<m;i++) r.array[i]=array[i]/v.array[i];
    return r;}
    
    T* begin() // for stl
    {return Get_Array_Pointer();}

    const T* begin() const // for stl
    {return Get_Array_Pointer();}

    T* end() // for stl
    {return Get_Array_Pointer()+Value(m);}

    const T* end() const // for stl
    {return Get_Array_Pointer()+Value(m);}

    void Exchange(STATIC_ARRAY& other)
    {
        std::swap(other,*this);
    }

    template<class RW> void Read(UNTYPED_ISTREAM<RW>& input)
    {ARRAY_VIEW<T,ID>(*this).Read(input);}

    template<class RW> void Write(UNTYPED_OSTREAM<RW>& output) const
    {ARRAY_VIEW<const T,ID>(*this).Write(output);}
//#####################################################################
};

template<class T,int d,class ID> inline VECTOR<T,d>
operator+(const typename STATIC_ARRAY<T,d,ID>::ELEMENT& a,const STATIC_ARRAY<T,d,ID>& v)
{
    STATIC_ARRAY<T,d,ID> r(v.m);
    for(int i=0;i<d;i++) r.array[i]=a+v.array[i];
    return r;
}

template<class T,int d,class ID> inline STATIC_ARRAY<T,d,ID>
operator*(const typename STATIC_ARRAY<T,d,ID>::ELEMENT& a,const STATIC_ARRAY<T,d,ID>& v)
{return v*a;}

template<class T,int d,class ID> inline STATIC_ARRAY<T,d,ID>
operator-(const typename STATIC_ARRAY<T,d,ID>::ELEMENT& a,const STATIC_ARRAY<T,d,ID>& v)
{
    STATIC_ARRAY<T,d,ID> r(v.m);
    for(int i=0;i<d;i++) r.array[i]=a-v.array[i];
    return r;
}

template<class T,int d,class ID> inline STATIC_ARRAY<T,d,ID>
operator/(const typename STATIC_ARRAY<T,d,ID>::ELEMENT& a,const STATIC_ARRAY<T,d,ID>& v)
{
    STATIC_ARRAY<T,d,ID> r(v.m);
    for(int i=0;i<d;i++) r.array[i]=a/v.array[i];
    return r;
}

template<class T,int d,class ID> inline STATIC_ARRAY<T,d,ID>
Inverse(const STATIC_ARRAY<T,d,ID>& v)
{
    STATIC_ARRAY<T,d,ID> r(v.m);
    for(int i=0;i<d;i++) r.array[i]=1/v.array[i];
    return r;
}

}
#endif
