//#####################################################################
// Copyright 2004-2009, Kevin Der, Ronald Fedkiw, Geoffrey Irving, Frank Losasso, Craig Schroeder, Andrew Selle, Tamar Shinar, Eftychios Sifakis, Jerry Talton, Wen Zheng.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class ARRAY_BASE
//#####################################################################
#ifndef __ARRAY_BASE__
#define __ARRAY_BASE__

#include <Core/Arrays/ARRAYS_FORWARD.h>
#include <Core/Utilities/TYPE_UTILITIES.h>
#include <Core/Vectors/ARITHMETIC_POLICY.h>
#include <Core/Vectors/SCALAR_POLICY.h>
#include <iostream>
namespace PhysBAM{

struct NO_INIT{};
struct INIT_NEW{};
struct INIT_ALL{};
struct FORCE_IDX{};
struct CAST_DATA{};
static const NO_INIT no_init={};
static const INIT_NEW init_new={};
static const INIT_ALL init_all={};
static const FORCE_IDX force_idx={};
static const CAST_DATA cast_data={};

template<class T,class T_ARRAY,class ID> class ARRAY_BASE;
template<class T> class RANDOM_NUMBERS;

template<class T_ARRAY,class ENABLER=void> struct CANONICALIZE_CONST_ARRAY:public FIRST<T_ARRAY>{};

template<class A,class B> struct SAME_ARRAY_CANONICAL{static bool Same_Array(const A& array0,const B& array1)
{static_assert(!is_same<A,B>::value);return false;}};

template<class T_ARRAY> struct SAME_ARRAY_CANONICAL<T_ARRAY,T_ARRAY>{static bool Same_Array(const T_ARRAY& array0,const T_ARRAY& array1)
{return T_ARRAY::Same_Array(array0,array1);}};

template<class TA0,class TA1> struct SAME_ARRAY:public SAME_ARRAY_CANONICAL<typename CANONICALIZE_CONST_ARRAY<TA0>::TYPE,typename CANONICALIZE_CONST_ARRAY<TA1>::TYPE>{};

template<class TV> struct ELEMENT_OF_VECTOR {private:struct UNUSABLE;public:typedef UNUSABLE TYPE;};
template<class T,int d> struct ELEMENT_OF_VECTOR<VECTOR<T,d> > {typedef T TYPE;};

template<class A,class enabler=void> struct EQUIVALENT_ARRAY;
template<class T> struct EQUIVALENT_ARRAY<T,enable_if_t<!IS_ARRAY<T>::value> > {typedef T TYPE;};
template<class T> struct EQUIVALENT_ARRAY<T,enable_if_t<IS_ARRAY<T>::value> > {typedef ARRAY<typename EQUIVALENT_ARRAY<typename T::ELEMENT>::TYPE> TYPE;};

template<class T,class T_ARRAY,class ID>
class ARRAY_BASE
{
    struct UNUSABLE{};
public:
    typedef T ELEMENT;
    typedef ID INDEX;
    typedef T value_type; // for stl
    typedef int difference_type; // for stl

    typedef SCALAR_TYPE<T> SCALAR;
    typedef conditional_t<is_same<SCALAR,float>::value,double,SCALAR> D;
    typedef conditional_t<is_same<SCALAR,T>::value,UNUSABLE,SCALAR> S;
    static constexpr bool is_zero=false;
    static constexpr int type=vec_gen;

protected:
    ARRAY_BASE() = default;
    ARRAY_BASE(const ARRAY_BASE&) = default;
    ARRAY_BASE(ARRAY_BASE&&) = default;
    ~ARRAY_BASE() = default;
public:

    T_ARRAY& Derived()
    {return static_cast<T_ARRAY&>(*this);}

    const T_ARRAY& Derived() const
    {return static_cast<const T_ARRAY&>(*this);}

protected:
    template<class A> struct IS_ARRAY_BASE {static const bool value=false;};
    template<class T2,class A> struct IS_ARRAY_BASE<ARRAY_BASE<T2,A,ID> > {static const bool value=true;};
public:

    template<class A> static bool Same_Array(const A& array0,const A& array1)
    {return &array0==&array1;}

    template<class A,class B> static bool
    Same_Array(const A& array0,const B& array1)
    {return SAME_ARRAY<A,B>::Same_Array(array0,array1);}

    
    
protected:
    ARRAY_BASE& operator=(const ARRAY_BASE& source) = default;
    ARRAY_BASE& operator=(ARRAY_BASE&& source) = default;

    template<class A>
    T_ARRAY& operator=(const A& source);
public:
    template<class A>
    void Assert_Same_Size(const A&v) const;

    template<class T_INDICES>
    INDIRECT_ARRAY<T_ARRAY,T_INDICES&> Subset(const T_INDICES& indices);
    template<class T_INDICES>
    INDIRECT_ARRAY<const T_ARRAY,T_INDICES&> Subset(const T_INDICES& indices) const;
    ARRAY_VIEW<T> Prefix(const ID prefix_size);
    ARRAY_VIEW<const T> Prefix(const ID prefix_size) const;

    bool Valid_Index(const ID i) const;
    decltype(auto) operator()(const ID i)
    {return (Derived()(i));}
    decltype(auto) operator()(const ID i) const
    {return (Derived()(i));}

    template<class A,class I>
    void Copy(const ARRAY_BASE<T,A,I>& a,FORCE_IDX);
    template<class A,class I>
    void Copy(const ARRAY_BASE<T,A,I>& a,CAST_DATA);
    template<class A,class I>
    void Copy(const ARRAY_BASE<T,A,I>& a,CAST_DATA,FORCE_IDX);
    template<class A,class I>
    void Copy(const ARRAY_BASE<T,A,I>& a,FORCE_IDX,CAST_DATA);

    // valid only for contiguous arrays of VECTOR<T,d>
    ARRAY_VIEW<typename ELEMENT_OF_VECTOR<T>::TYPE> Flattened();
    // valid only for contiguous arrays of VECTOR<T,d>
    ARRAY_VIEW<const typename ELEMENT_OF_VECTOR<T>::TYPE> Flattened() const;

    template<class ID1>
    ARRAY_VIEW<const T,ID1> Array_View(const ID first,const ID1 length) const;
    template<class ID1>
    ARRAY_VIEW<T,ID1> Array_View(const ID first,const ID1 length);
    ARRAY_VIEW<const T,ID> Array_View(INTERVAL<ID> I) const;
    ARRAY_VIEW<T,ID> Array_View(INTERVAL<ID> I);

    template<class A,class O=T_ARRAY>
    std::enable_if_t<((O::fixed_m>=0||A::fixed_m>=0)&&compat(O::fixed_m,A::fixed_m)),bool>
    operator==(const ARRAY_BASE<T,A,ID>& v) const;
    template<class A,class O=T_ARRAY>
    std::enable_if_t<(O::fixed_m<0&&A::fixed_m<0),bool>
    operator==(const A& v) const;
    template<class A>
    bool operator!=(const A& v) const;

    const T_ARRAY& operator+() const;
    template<class A> T_ARRAY& operator+=(const ARRAY_BASE<T,A,ID>& v);
    T_ARRAY& operator+=(const S& a);
    T_ARRAY& operator+=(const T& a);
    template<class A> T_ARRAY& operator-=(const ARRAY_BASE<T,A,ID>& v);
    T_ARRAY& operator-=(const S& a);
    T_ARRAY& operator-=(const T& a);
    template<class A> T_ARRAY& operator*=(const ARRAY_BASE<T,A,ID>& v);
    T_ARRAY& operator*=(const S& a);
    T_ARRAY& operator*=(const T& a);
    template<class A> T_ARRAY& operator/=(const ARRAY_BASE<T,A,ID>& v);
    T_ARRAY& operator/=(const S& a);
    T_ARRAY& operator/=(const T& a);

    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator-() const;
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator+(const ARRAY_BASE<T,A,ID>& v) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator+(const S& a) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator+(const T& a) const;
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator-(const ARRAY_BASE<T,A,ID>& v) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator-(const S& a) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator-(const T& a) const;
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator*(const ARRAY_BASE<T,A,ID>& v) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator*(const S& a) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator*(const T& a) const;
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator/(const ARRAY_BASE<T,A,ID>& v) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator/(const S& a) const;
    template<class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    operator/(const T& a) const;
    
    T& Last();
    const T& Last() const;
    ID Size() const;

    T Max() const;
    T Min() const;
    T Max_Abs() const;
    T Max_Mag() const;
    T Min_Mag() const;
    ID Arg_Max() const;
    ID Arg_Min() const;
    T Componentwise_Max_Abs() const;
    T Componentwise_Max() const;
    T Componentwise_Min() const;
    T Sum() const;
    T Product() const;
    T Sum_Abs() const;
    T Average() const;

    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    Componentwise_Min(const ARRAY_BASE<T,A,ID>& v) const;
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    Componentwise_Max(const ARRAY_BASE<T,A,ID>& v) const;
    
    template<class A> SCALAR_TYPE<T>
    Dot(const ARRAY_BASE<T,A,ID>& a) const;
    template<class A> D
    Dot_Double_Precision(const ARRAY_BASE<T,A,ID>& a) const;
    SCALAR_TYPE<T> Magnitude_Squared() const;
    SCALAR_TYPE<T> Magnitude() const;
    T Normalize();
    SCALAR_TYPE<T> Maximum_Magnitude() const;
    ID Arg_Maximum_Magnitude() const;

    template<class A>
    ELEMENT Weighted_Sum(const A& weights) const;

    T_ARRAY Householder_Vector(const int k) const;
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<T,O::fixed_m,ID>> R
    Householder_Transform(const ARRAY_BASE<T,A>& v) const;
    void Givens_Rotate(const int i,const int j,const T c,const T s);

    template<class A>
    T Angle_Between(const ARRAY_BASE<T,A>& v) const; // 0 .. pi
    
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<bool,O::fixed_m,ID>> R
    Componentwise_Greater_Equal(const ARRAY_BASE<T,A>& v) const;
    template<class A,class O=T_ARRAY,class R=VEC_TYPE_NZ<bool,O::fixed_m,ID>> R
    Componentwise_And(const ARRAY_BASE<bool,A>& v) const;

    void Reverse();

    template<class A>
    bool All_Greater_Equal(const ARRAY_BASE<T,A,ID>& v) const;
    template<class A>
    bool All_Less(const ARRAY_BASE<T,A,ID>& v) const;
    template<class A>
    bool All_Less_Equal(const ARRAY_BASE<T,A,ID>& v) const;
    template<class A>
    bool All_Greater(const ARRAY_BASE<T,A,ID>& v) const;

    ID Find(const T& element) const;
    // returns the first occurence of an element in an array
    bool Find(const T& element,ID& index) const;
    // returns the first occurence after start_index of an element in an array
    bool Find(const T& element,const ID start_index,ID& index) const;

    bool Contains(const T& element) const;
    bool Contains_Only(const T& element) const;
    int Count_Matches(const T& value) const;
    int Number_True() const;
    int Number_False() const;

    template<class T_COMPARE>
    void Sort(const T_COMPARE comparison);
    template<class T_COMPARE>
    void Stable_Sort(const T_COMPARE& comparison);
    void Sort();
    void Stable_Sort();

    void Fill(T value);

    template<class A>
    void Copy(const A& v1);
    template<class A>
    void Copy(const SCALAR c1,const A& v1);
    template<class A,class B>
    void Copy(const SCALAR c1,const A& v1,const B& v2);
    template<class A,class B>
    void Copy(const SCALAR c1,const A& v1,const SCALAR c2,const B& v2);

    template<class A>
    void Remove_Sorted_Indices_Lazy(const A& index);

    template<class A,class B>
    void Extract(ARRAY_BASE<T,A,ID>& a,ARRAY_BASE<T,B,ID>& b) const;
    template<class A,class B,class C>
    void Extract(ARRAY_BASE<T,A,ID>& a,ARRAY_BASE<T,B,ID>& b,ARRAY_BASE<T,C,ID>& c) const;
    template<class A,class B>
    void Combine(const ARRAY_BASE<T,A,ID>& a,const ARRAY_BASE<T,B,ID>& b);
    template<class A,class B,class C>
    void Combine(const ARRAY_BASE<T,A,ID>& a,const ARRAY_BASE<T,B,ID>& b,const ARRAY_BASE<T,C,ID>& c);

    auto begin() // for stl
    {return Derived().begin();}
    auto begin() const // for stl
    {return Derived().begin();}
    auto end() // for stl
    {return Derived().end();}
    auto end() const // for stl
    {return Derived().end();}

    ID Binary_Search(const T& value) const; // lower_bound binary search
    template<class T_COMPARE>
    ID Binary_Search(const T& value,const T_COMPARE comparison) const; // lower_bound binary search

    void Write_Raw(std::ostream& output) const;

    template<class S,class... Args> auto
    Fill_Uniform(RANDOM_NUMBERS<S>& rand,Args&&... r)
        -> enable_if_t<sizeof((rand.Fill_Uniform(std::declval<T&>(),std::forward<Args>(r)...),1))!=0>
    {
        for(auto&x:*this) rand.Fill_Uniform(x,std::forward<Args>(r)...);
    }
//#####################################################################
};

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator+(const typename A::S& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator+(const ND<T>& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator-(const typename A::S& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator-(const ND<T>& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator*(const typename A::S& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator*(const ND<T>& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator/(const typename A::S& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID> VEC_TYPE_NZ<T,A::fixed_m,ID>
operator/(const ND<T>& a,const ARRAY_BASE<T,A,ID>& v);

template<class T,class A,class ID>
inline std::ostream& operator<<(std::ostream& output,const ARRAY_BASE<T,A,ID>& a);

template<class A> void
Set_Counting(A&& a)
{
    for(decltype(a.Size()) i(0);i<a.Size();i++) a(i)=i;
}

template<class ID> void
Set_Counting(ARRAY<ID,ID>& a,ID n)
{
    a.Resize(n);
    for(ID i(0);i<a.Size();i++) a(i)=i;
}

//#####################################################################
template<class A,class B> struct CAN_ASSIGN<A,B,enable_if_t<IS_ARRAY<A>::value && IS_ARRAY<B>::value && is_same<typename A::ELEMENT,typename B::ELEMENT>::value && !is_same<A,B>::value>>
{static const bool value=true;};
}
//#####################################################################
#include <Core/Arrays/ARRAY_BASE_DEFINITIONS.h>
//#####################################################################
#include <Core/Arrays/ARRAY_VIEW.h>
#include <Core/Arrays/INDIRECT_ARRAY.h>
#endif
