//#####################################################################
// Copyright 2007-2008, Geoffrey Irving, Eftychios Sifakis.
// This file is part of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
//#####################################################################
// Class ARRAY_VIEW
//#####################################################################
#ifndef __ARRAY_VIEW__
#define __ARRAY_VIEW__

#include <Core/Arrays/ARRAY_BASE.h>
#include <Core/Log/DEBUG_UTILITIES.h>
#include <Core/Utilities/EXCEPTIONS.h>
#include <Core/Utilities/TYPE_UTILITIES.h>
namespace PhysBAM{

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

template<class T,class ID>
class ARRAY_VIEW:public ARRAY_BASE<typename remove_const<T>::type,ARRAY_VIEW<T,ID>,ID>
{
    struct UNUSABLE{};
public:
    static constexpr int fixed_m=-1;
    typedef typename remove_const<T>::type ELEMENT;typedef ID INDEX;
    typedef ARRAY_BASE<ELEMENT,ARRAY_VIEW<T,ID>,ID> BASE;
    typedef typename conditional<is_const<T>::value,ELEMENT,UNUSABLE>::type U;
    typedef T& RESULT_TYPE;
    typedef const T& CONST_RESULT_TYPE;
    typedef T* iterator; // for stl
    typedef const T* const_iterator; // for stl

    // m and base_pointer inherit constness of T
    T* base_pointer=nullptr;
    ID m{};

    using BASE::Same_Array;

    // No data source
    ARRAY_VIEW() = default;

    // Non-array data source
    ARRAY_VIEW(T* raw_data,ID m)
        :base_pointer(raw_data),m(m)
    {}

    // Array source
    ARRAY_VIEW(const ARRAY_VIEW<T,ID>&) = default;

    ARRAY_VIEW(const ARRAY_VIEW<U,ID>& array)
        :base_pointer(array.base_pointer),m(array.m)
    {}

    template<class T_ARRAY,class UNUSED=std::enable_if_t<HAS_POINTER<T_ARRAY>::value>>
    ARRAY_VIEW(ARRAY_BASE<ELEMENT,T_ARRAY,ID>& array)
        :base_pointer(array.Derived().Get_Array_Pointer()),m(array.Size())
    {}

    template<class T_ARRAY,class UNUSED=std::enable_if_t<HAS_POINTER<T_ARRAY>::value>>
    ARRAY_VIEW(const ARRAY_BASE<ELEMENT,T_ARRAY,ID>& array)
        :base_pointer(array.Derived().Get_Array_Pointer()),m(array.Size())
    {}

    // Array source with forcing
    template<class T_ARRAY,class I,class UNUSED=std::enable_if_t<HAS_POINTER<T_ARRAY>::value>>
    ARRAY_VIEW(ARRAY_BASE<ELEMENT,T_ARRAY,I>& array,FORCE_IDX)
        :base_pointer(array.Derived().Get_Array_Pointer()),m(ID(Value(array.Size())))
    {}

    template<class T_ARRAY,class I,class UNUSED=std::enable_if_t<HAS_POINTER<T_ARRAY>::value>>
    ARRAY_VIEW(const ARRAY_BASE<ELEMENT,T_ARRAY,I>& array,FORCE_IDX)
        :base_pointer(array.Derived().Get_Array_Pointer()),m(ID(Value(array.Size())))
    {}
    
    ~ARRAY_VIEW() = default;

    ID Size() const
    {return m;}

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

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

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

    ARRAY_VIEW& operator=(const ARRAY_VIEW& source)
    {return BASE::operator=(source);}

    ARRAY_VIEW& operator=(ARRAY_VIEW&& source) = delete;

    template<class T_ARRAY1>
    ARRAY_VIEW& operator=(const ARRAY_BASE<ELEMENT,T_ARRAY1,ID>& source)
    {return BASE::operator=(source);}

    T* Get_Array_Pointer()
    {return base_pointer;}

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

    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);}

    static bool Same_Array(const ARRAY_VIEW& array0,const ARRAY_VIEW& array1)
    {return array0.Get_Array_Pointer()==array1.Get_Array_Pointer();}

    void Exchange(ARRAY_VIEW& other)
    {std::swap(m,other.m);std::swap(base_pointer,other.base_pointer);}

    void Set(T* raw_data,ID m_input)
    {m=m_input;base_pointer=raw_data;}

    void Set(const ARRAY_VIEW<T,ID>& array)
    {Set(array.base_pointer,array.m);}

    void Set(const ARRAY_VIEW<U,ID>& array)
    {Set(array.base_pointer,array.m);}

    template<class T_ARRAY>
    void Set(const ARRAY_BASE<ELEMENT,T_ARRAY,ID>& array)
    {Set(array.Derived().Get_Array_Pointer(),array.Size());}

    template<class T_ARRAY>
    void Set(ARRAY_BASE<ELEMENT,T_ARRAY,ID>& array)
    {Set(array.Derived().Get_Array_Pointer(),array.Size());}

    template<class RW>
    void Read(UNTYPED_ISTREAM<RW>& input)
    {
        Get(input,'(');
        if constexpr(!is_same<RW,char>::value)
        {
            ID read_size;
            Read_Binary(input,read_size);
            PHYSBAM_ASSERT(read_size==m);
        }
        Read_Binary_Array(input,Get_Array_Pointer(),Value(m));
        Get(input,')');
    }

    template<class RW>
    void Write(UNTYPED_OSTREAM<RW>& output) const
    {
        Beg(output,'(');
        ID m=Size();
        if constexpr(!is_same<RW,char>::value)
            Write_Binary(output,m);
        Write_Binary_Array(output,Get_Array_Pointer(),Value(m));
        End(output,')');
    }

//#####################################################################
};
template<> struct HASH_REDUCE<const char*>
{
    static int H(const char* key)
    {
        return HASH_REDUCE<ARRAY_VIEW<const char> >::H(ARRAY_VIEW<const char>(key,(int)strlen(key)));
    }
};
template<> struct HASH_REDUCE<char*>
{
    static int H(const char* key)
    {
        return HASH_REDUCE<const char*>::H(key);
    }
};
template<> struct HASH_REDUCE<std::string>
{
    static int H(const std::string& key)
    {
        return HASH_REDUCE<ARRAY_VIEW<const char> >::H(ARRAY_VIEW<const char>(key.c_str(),(int)key.length()));
    }
};
}
#endif
