#include "multi_array.h"

namespace MultiArrayTools
{
    template <typename T>
    Scalar<T> scalar(const T& in)
    {
	NullRF nrf;
	return Scalar<T>( std::dynamic_pointer_cast<NullRange>( nrf.create() ), std::vector<T>( { in } ) );
    }
    
    /*******************
     *  MultiArray     *	     
     *******************/

    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::MultiArray(const typename CRange::Space& space) :
	MutableMultiArrayBase<T,SRanges...>(space),
	mCont(MAB::mRange->size())
    {
	MAB::mInit = true;
    }

    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::MultiArray(const typename CRange::Space& space,
					 std::vector<T>&& vec) :
	MutableMultiArrayBase<T,SRanges...>(space),
	mCont(vec)
    {
	MAB::mInit = true;
	if(mCont.size() > MAB::mRange->size()){
	    mCont.erase(mCont.begin() + MAB::mRange->size(), mCont.end());
	}
    }

    
    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::MultiArray(const std::shared_ptr<SRanges>&... ranges) :
	MutableMultiArrayBase<T,SRanges...>(ranges...),
	mCont(MAB::mRange->size())
    {
	MAB::mInit = true;
    }
    
    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::MultiArray(const std::shared_ptr<SRanges>&... ranges, const T& val) : 
	MutableMultiArrayBase<T,SRanges...>(ranges...),
	mCont(MAB::mRange->size(), val)
    {
	MAB::mInit = true;
    }
    
    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::MultiArray(const std::shared_ptr<SRanges>&... ranges, const std::vector<T>& vec) :
	MutableMultiArrayBase<T,SRanges...>(ranges...),
	mCont(vec)
    {
	MAB::mInit = true;
	if(mCont.size() > MAB::mRange->size()){
	    mCont.erase(mCont.begin() + MAB::mRange->size(), mCont.end());
	}
    }
    
    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::MultiArray(const std::shared_ptr<SRanges>&... ranges, std::vector<T>&& vec) :
	MutableMultiArrayBase<T,SRanges...>(ranges...),
	mCont(vec)
    {
	MAB::mInit = true;
	if(mCont.size() > MAB::mRange->size()){
	    mCont.erase(mCont.begin() + MAB::mRange->size(), mCont.end());
	}
    }

    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::MultiArray(MultiArray<T,AnonymousRange>&& ama, SIZET<SRanges>... sizes) :
	MutableMultiArrayBase<T,SRanges...>
	( ama.range()->template get<0>().template scast<SRanges...>(sizes...)->space() ),
	mCont( std::move( ama.mCont ) )
    {
	MAB::mInit = true;
    }
    
    /*
    template <typename T, class... SRanges>
    template <class Range2, class Range3>
    MultiArray<T,SRanges...>::MultiArray(const MultiArray<MultiArray<T,Range2>,Range3> in) :
	MutableMultiArrayBase<T,SRanges...>(merge(in.range(), in[ in.beginIndex() ].range()))
	// assert that Range2 has always same extension
    {
	MAB::mInit = true;
	mCont.clear();
	for(auto i = in.beginIndex(); i != in.endIndex(); ++i){
	    mCont.insert(mCont.end(), in[i].mCont.begin(), in[i].mCont.end());
	}
	assert(mCont.size() == MAB::mRange->size());
    }
    */
    /*
    template <typename T, class... SRanges>
    template <class Range2, class Range3>
    MultiArray<T,SRanges...>& MultiArray<T,SRanges...>::operator=(const MultiArray<MultiArray<T,Range2>,Range3> in)
    {
	MAB::mRange.reset(new Range(merge(in.range(), in[ in.beginIndex() ].range())));
	// assert that Range2 has always same extension
	mCont.clear();
	for(auto i = in.beginIndex(); i != in.endIndex(); ++i){
	    mCont.insert(mCont.end(), in[i].mCont.begin(), in[i].mCont.end());
	}
	assert(mCont.size() == MAB::mRange->size());
	return *this;
	} */   

    template <typename T, class... SRanges>
    T& MultiArray<T,SRanges...>::operator[](const IndexType& i)
    {
	return mCont[ i.pos() ];
    }

    template <typename T, class... SRanges>
    const T& MultiArray<T,SRanges...>::operator[](const IndexType& i) const
    {
	return mCont[ i.pos() ];
    }

    template <typename T, class... SRanges>
    T& MultiArray<T,SRanges...>::at(const typename IndexType::MetaType& meta)
    {
	return mCont[ MAB::beginIndex().at(meta).pos() ];
    }

    template <typename T, class... SRanges>
    const T& MultiArray<T,SRanges...>::at(const typename IndexType::MetaType& meta) const
    {
	return mCont[ MAB::beginIndex().at(meta).pos() ];
    }
	
    template <typename T, class... SRanges>
    bool MultiArray<T,SRanges...>::isConst() const
    {
	return false;
    }
    
    template <typename T, class... SRanges>
    bool MultiArray<T,SRanges...>::isSlice() const
    {
	return false;
    }

    template <typename T, class... SRanges>
    template <class... SRanges2>
    MultiArray<T,SRanges2...> MultiArray<T,SRanges...>::format(const std::shared_ptr<SRanges2>&... nrs)
    {
	MAB::mInit = false;
	return MultiArray<T,SRanges2...>( nrs... , std::move(mCont) );
    }

    template <typename T, class... SRanges>
    template <class... SRanges2>
    MultiArray<T,SRanges2...> MultiArray<T,SRanges...>::format(const std::tuple<std::shared_ptr<SRanges2>...>& nrs)
    {
	MAB::mInit = false;
	return MultiArray<T,SRanges2...>( nrs , std::move(mCont) );
    }

    template <typename T, class... SRanges>
    const T* MultiArray<T,SRanges...>::data() const
    {
	return mCont.data();
    }

    template <typename T, class... SRanges>
    T* MultiArray<T,SRanges...>::data()
    {
	return mCont.data();
    }

    template <typename T, class... SRanges>
    std::shared_ptr<MultiArrayBase<T,AnonymousRange> > MultiArray<T,SRanges...>::anonymous(bool slice) const
    {
	AnonymousRangeFactory arf(MAB::mRange->space());
	if(slice){
	    return std::make_shared<ConstSlice<T,AnonymousRange> >
		( std::dynamic_pointer_cast<AnonymousRange>( arf.create() ),
		  data() );
	}
	else {
	    return std::make_shared<MultiArray<T,AnonymousRange> >
		( std::dynamic_pointer_cast<AnonymousRange>( arf.create() ),
		  mCont );
	}
    }	

    template <typename T, class... SRanges>
    std::shared_ptr<MultiArrayBase<T,AnonymousRange> > MultiArray<T,SRanges...>::anonymousMove()
    {
	AnonymousRangeFactory arf(MAB::mRange->space());
	MAB::mInit = false;
	return std::make_shared<MultiArray<T,AnonymousRange> >
	    ( std::dynamic_pointer_cast<AnonymousRange>( arf.create() ),
	      std::move(mCont) );
    }	

    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>& MultiArray<T,SRanges...>::operator=(const T& in)
    {
	for(auto& x: mCont){
	    x = in;
	}
	return *this;
    }
    
    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>& MultiArray<T,SRanges...>::operator+=(const MultiArray& in)
    {
	if(not MAB::mInit){ // not initialized by default constructor !!
	    (*this) = in;
	}
	else {
	    assert( PackNum<sizeof...(SRanges)-1>::checkIfSameInstance( MAB::mRange->space(), in.mRange->space() ) );
	    std::transform(mCont.begin(), mCont.end(), in.mCont.begin(), mCont.begin(), std::plus<T>());
	}
	return *this;
    }

    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>& MultiArray<T,SRanges...>::operator-=(const MultiArray& in)
    {
	if(not MAB::mInit){ // not initialized by default constructor !!
	    (*this) = in;
	}
	else {
	    assert( PackNum<sizeof...(SRanges)-1>::checkIfSameInstance( MAB::mRange->space(), in.mRange->space() ) );
	    std::transform(mCont.begin(), mCont.end(), in.mCont.begin(), mCont.begin(), std::minus<T>());
	}
	return *this;
    }
    
    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>& MultiArray<T,SRanges...>::operator*=(const T& in)
    {
	for(auto& x: mCont){
	    x *= in;
	}
	return *this;
    }

    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>& MultiArray<T,SRanges...>::operator/=(const T& in)
    {
	for(auto& x: mCont){
	    x /= in;
	}
	return *this;
    }
    
    template <typename T, class... SRanges>
    MultiArray<T,SRanges...>::operator T() const
    {
	static_assert( sizeof...(SRanges) == 1, "try to cast non-scalar type into scalar" );
	// TODO: check that SIZE is statically = 1 !!!
	return mCont[0];
    }

    template <typename T, class... SRanges>
    auto MultiArray<T,SRanges...>::cat() const
	-> decltype(ArrayCatter<T>::cat(*this))
    {
	return ArrayCatter<T>::cat(*this);
    }
}