//#####################################################################
// Copyright 2002-2007, Robert Bridson, Ronald Fedkiw, Eran Guendelman, Geoffrey Irving, Nipun Kwatra, Frank Losasso, Duc Nguyen, Craig Schroeder, Andrew Selle, Jonathan Su.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class ARRAY
//#####################################################################
#ifndef __ARRAYS_ND__
#define __ARRAYS_ND__

#include <Core/Arrays/ARRAYS_ND_BASE.h>
#include <Core/Math_Tools/FIXED_NUMBER.h>
#include <Core/Math_Tools/RANGE.h>
#include <Core/Utilities/EXCEPTIONS.h>
#include <Core/Vectors/VECTOR.h>
namespace PhysBAM{

template<class T,int d>
class ARRAY<T,VECTOR<int,d> >:public ARRAYS_ND_BASE<T,VECTOR<int,d> >
{
public:
    typedef VECTOR<int,d> IV;
    enum WORKAROUND1 {dimension=d};
    template<class T2> struct REBIND{typedef ARRAY<T2,IV> TYPE;};
    typedef T ELEMENT;typedef IV INDEX;typedef T& RESULT_TYPE;typedef const T& CONST_RESULT_TYPE;
    typedef ARRAYS_ND_BASE<T,VECTOR<int,d> > BASE;

    using BASE::array; // one-dimensional data storage
    using BASE::domain;using BASE::Exchange;using BASE::stride;using BASE::offset;
    using BASE::Calculate_Acceleration_Constants;

    ARRAY() = default;

    ARRAY(const RANGE<IV>& domain_input)
    {Initialize(domain_input,true,T());}

    ARRAY(const RANGE<IV>& domain_input,NO_INIT)
    {Initialize(domain_input,false,T());}

    ARRAY(const RANGE<IV>& domain_input,INIT_NEW,const T& initialization_value=T())
    {Initialize(domain_input,true,initialization_value);}

    ARRAY(const IV& size_input)
        :ARRAY(RANGE<IV>(IV(),size_input))
    {}

    ARRAY(const IV& size_input,NO_INIT)
        :ARRAY(RANGE<IV>(IV(),size_input),no_init)
    {}

    ARRAY(const IV& size_input,INIT_NEW,const T& initialization_value=T())
        :ARRAY(RANGE<IV>(IV(),size_input),init_new,initialization_value)
    {}

    ARRAY(const ARRAY& old_array)
    {Initialize(old_array.domain,false,T());array=old_array.array;}

    ARRAY(const ARRAY& old_array,NO_INIT)
    {Initialize(old_array.domain,false,T());}

    ARRAY(ARRAY&&) = default;

    ~ARRAY()
    {delete[] array.Get_Array_Pointer();}

protected:
    void Initialize(const RANGE<IV>& box,const bool initialize_using_initialization_value,const T& initialization_value)
    {
        if(box.Empty()) return;
        IV counts_new=box.Edge_Lengths();
        assert(counts_new.Min()>=0);
        int size_new=counts_new.Product();
        Calculate_Acceleration_Constants(box);
        array.Set(new T[size_new],size_new);
        if(initialize_using_initialization_value) array.Fill(initialization_value);
    }
public:

    void Clean_Memory()
    {Resize(RANGE<IV>::Empty_Box(),no_init);}

    void Delete_Pointers_And_Clean_Memory() // only valid if T is a pointer type
    {for(int i=0;i<array.Size();i++) delete array(i);Clean_Memory();}

    ARRAY& operator=(const ARRAY& source)
    {if(this==&source) return *this;
    Resize_In_Place(source.Domain_Indices());
    array=source.array;return *this;}

    ARRAY& operator=(ARRAY&& a)
    {domain=a.domain;stride=a.stride;offset=a.offset;array.Set(a.array);a.array.Set(0,0);return *this;}

    template<class T_ARRAY1>
    ARRAY& operator=(const T_ARRAY1& source)
    {static_assert(is_same<ELEMENT,typename T_ARRAY1::ELEMENT>::value);
    Resize_In_Place(source.Domain_Indices());
    ARRAY_BASE<T,BASE,IV>::operator=(source);return *this;}

    void Resize(const RANGE<IV>& box)
    {if(box==domain) return;
    ARRAY new_array(box);
    if(!domain.Empty()) this->Put(*this,new_array,domain.Intersect(box));
    Exchange(new_array);}

    void Resize(const RANGE<IV>& box,NO_INIT)
    {if(box==domain) return;
    ARRAY new_array(box,no_init);
    Exchange(new_array);}

    void Resize(const RANGE<IV>& box,INIT_NEW,const T& initialization_value)
    {if(box==domain) return;
    ARRAY new_array(box,init_new,initialization_value);
    if(!domain.Empty()) this->Put(*this,new_array,domain.Intersect(box));
    Exchange(new_array);}

    void Resize(const RANGE<IV>& box,INIT_ALL,const T& initialization_value)
    {Resize(box,no_init);array.Fill(initialization_value);}

    void Resize(const IV& corner)
    {Resize(RANGE<IV>(IV(),corner));}

    void Resize(const IV& corner,NO_INIT)
    {Resize(RANGE<IV>(IV(),corner),no_init);}

    void Resize(const IV& corner,INIT_NEW,const T& initialization_value)
    {Resize(RANGE<IV>(IV(),corner),init_new,initialization_value);}

    void Resize(const IV& corner,INIT_ALL,const T& initialization_value)
    {Resize(RANGE<IV>(IV(),corner),init_all,initialization_value);}

    int Number_Of_Ghost_Cells() const
    {return -domain.min_corner.x;}

    void Reallocate_In_Place(const RANGE<IV>& box)
    {IV counts_new(box.Edge_Lengths());int size_new=counts_new.Product();Calculate_Acceleration_Constants(box);delete [] array.Get_Array_Pointer();array.Set(new T[size_new],size_new);}

    void Resize_In_Place(const RANGE<IV>& box)
    {IV counts_new(box.Edge_Lengths());
    if(array.Size()>=counts_new.Product()) Calculate_Acceleration_Constants(box);
    else Reallocate_In_Place(box);}

    template<class RW>
    void Read(UNTYPED_ISTREAM<RW>& input)
    {
        Get(input,'(');
        int read_length;
        if constexpr(!is_same<RW,char>::value)
            Read_Binary(input,read_length);
        RANGE<IV> domain_temp;
        Read_Binary(input,domain_temp);
        Resize(domain_temp,no_init);
        Read_Binary_Array_Group(input,array.Get_Array_Pointer(),array.Size());
        Get(input,')');
    }
//#####################################################################
};

template<class T> inline std::ostream& operator<<(std::ostream& output_stream,const ARRAYS_ND_BASE<T,VECTOR<int,1> >& a)
{
    for(int i=a.domain.min_corner.x;i<a.domain.max_corner.x;i++) output_stream<<a(i)<<" ";
    output_stream<<std::endl;
    return output_stream;
}

template<class T> inline std::ostream& operator<<(std::ostream& output,const ARRAYS_ND_BASE<T,VECTOR<int,2> >& a)
{
    for(int i=a.domain.min_corner.x;i<a.domain.max_corner.x;i++){
        for(int j=a.domain.min_corner.y;j<a.domain.max_corner.y;j++)
            output<<a(i,j)<<" ";
        output<<std::endl;}
    return output;
}

template<class T> inline std::ostream& operator<<(std::ostream& output,const ARRAYS_ND_BASE<T,VECTOR<int,3> >& a)
{
    for(int i=a.domain.min_corner.x;i<a.domain.max_corner.x;i++){
        for(int j=a.domain.min_corner.y;j<a.domain.max_corner.y;j++){
            for(int k=a.domain.min_corner.z;k<a.domain.max_corner.z;k++)
                output<<a(i,j,k)<<" ";
            output<<std::endl;}
    output<<"------------------------------------------"<<std::endl;}
    return output;
}
// Avoid instantiating common non-trivial functions in large numbers of files.
#define COMMA ,
#define COMMON(T,IV)                                                    \
    extern template ARRAY<T,IV>::ARRAY(const RANGE<IV>&);               \
    extern template ARRAY<T,IV>::ARRAY(const RANGE<IV>&,NO_INIT);       \
    extern template void ARRAY<T,IV>::Initialize(const RANGE<IV>&,bool,const T&); \
    extern template void ARRAY<T,IV>::Resize(const RANGE<IV>&);         \
    extern template void ARRAY<T,IV>::Resize(const RANGE<IV>&,NO_INIT); \
    extern template ARRAY<T,IV>::~ARRAY()
#define COMMON1(T,d)                                    \
    COMMON(T,VECTOR<int COMMA d>);                      \
    COMMON(VECTOR<T COMMA d>,VECTOR<int COMMA d>)
#define COMMON2(T) COMMON1(T,1);COMMON1(T,2);COMMON1(T,3)
COMMON2(bool);
COMMON2(int);
COMMON2(float);
COMMON2(double);
#undef COMMON2
#undef COMMON1
#undef COMMON
#undef COMMA
}
#endif
