#ifndef __AMSMATHUTIL25_AMSARRAY_IMPL_HPP__
#define __AMSMATHUTIL25_AMSARRAY_IMPL_HPP__

namespace ams
{

    template<typename T> amsarray<T>::amsarray()
    {
        length = 0;
        reserved = 0;
        growfactor = 1.5;
        data = NULL;
        return;
    }

    template<typename T> amsarray<T>::~amsarray()
    {
        length = 0;
        reserved = 0;
        growfactor = 1.5;
        if(data!=NULL) {delete[] data; data = NULL;}
        return;
    }

    template<typename T> const amsarray_size_t amsarray<T>::size() const
    {
        return this->length;
    }

    template<typename T> const amsarray_size_t amsarray<T>::capacity() const
    {
        return this->reserved;
    }

    template<typename T> int amsarray<T>::reserve(amsarray_size_t _newcap)
    {
        int ret = amsarray_success;
        T *newdata = NULL;
        amsarray_size_t lmin;
        T defval = T();

        if(_newcap<=0)
        {
            length = 0;
            reserved = 0;
            if(data!=NULL) {delete[] data; data = NULL;}
            ret = amsarray_success;
            return ret;
        }

        newdata = new(std::nothrow) T[_newcap];
        if(newdata==NULL)
        {
            ret = amsarray_failure;
            return ret;
        }

        if(data!=NULL)
        {
            lmin = (_newcap>=reserved) ? length : _newcap;
            ams::buffer_cast_copy<T,T>(newdata,data,lmin);
        }
        ams::buffer_set<T>(newdata,reserved,_newcap,defval);

        if(data!=NULL) {delete[] data; data = NULL;}
        data = newdata;
        reserved = _newcap;
        length = (length<reserved)? reserved : length;

        return ret;
    }

    template<typename T> int amsarray<T>::resize(amsarray_size_t _newlen)
    {
        int ret = amsarray_success;
        int res;

        _newlen = (_newlen<0)? 0:_newlen;

        res = this->reserve(_newlen);
        if(res!=amsarray_success)
        {
            ret = amsarray_failure;
            return ret;
        }

        length = _newlen;
        ret = amsarray_success;
        return ret;
    }

    //resizing routine for insertions/erasures, 
    //adjusts reserved capacity according to the growth factor
    template<typename T> int amsarray<T>::resize_insert(amsarray_size_t _newlen)
    {
        int ret = amsarray_success;
        int res;
        amsarray_size_t q;

        if(_newlen<=0) 
        {
            res = this->reserve(0);
            if(res!=amsarray_success)
            {
                ret = amsarray_failure;
                return ret;
            }
            length = 0;
            return ret;
        }

        if(growfactor>1.0)
        {
            
            if(_newlen>reserved)
            {
                q = (amsarray_size_t)(growfactor*(double)_newlen);
                q = (q<_newlen) ? _newlen : q;
                res = this->reserve(q);
                if(res!=amsarray_success)
                {
                    ret = amsarray_failure;
                    return ret;
                }
                length = _newlen;
                return ret;
            }

            q = (amsarray_size_t)((1.0/growfactor)*(double)_newlen);
            if(_newlen<q)
            {
                res = this->reserve(_newlen);
                if(res!=amsarray_success)
                {
                    ret = amsarray_failure;
                    return ret;
                }
                length = _newlen;
                return ret;
            }

            //else do nothing
            length = _newlen;
            return ret;
        }
        else
        {
            //ignore growfactor
            res = this->reserve(_newlen);
            if(res!=amsarray_success)
            {
                ret = amsarray_failure;
                return ret;
            }
            length = _newlen;
            return ret;
        }

        //technically you shouldn't ever reach here
        length = _newlen;
        return ret;
    }

    template<typename T> int amsarray<T>::shrink_to_fit()
    {
        int ret;
        ret = this->reserve(this->length);
        return ret;
    }

    template<typename T> amsarray<T>::amsarray(const amsarray<T>& other)
    {
        int res = amsarray_success;
        length = 0;
        reserved = 0;
        growfactor = 1.5;
        data = NULL;
        if(this!=&other)
        {
            res = this->resize(other.length);
            if(res==amsarray_success)
            {
                buffer_cast_copy<T,T>(this->data,other.data,length);
            }
        }
        return;
    }

    template<typename T> amsarray<T>::amsarray(amsarray<T>&& other) noexcept
    {
        length = 0;
        reserved = 0;
        growfactor = 1.5;
        data = NULL;
        if(this!=&other)
        {
            if(data!=NULL) {delete[] data; data = NULL;}
            length = other.length;
            data = other.data;
            other.length = 0;
            other.data = NULL;
        }
        return;
    }

    template<typename T> amsarray<T>& amsarray<T>::operator=(const amsarray<T> &other)
    {
        int res = amsarray_success;
        if(this!=&other)
        {
            res = this->resize(other.length);
            if(res==amsarray_success)
            {
                buffer_cast_copy<T,T>(this->data,other.data,length);
            }
        }
        return *this;
    }

    template<typename T> amsarray<T>& amsarray<T>::operator=(amsarray<T> &&other) noexcept
    {
        if(this!=&other)
        {
            if(data!=NULL) {delete[] data; data = NULL;}
            length = other.length;
            reserved = other.reserved;
            growfactor = other.growfactor;
            data = other.data;
            other.length = 0;
            other.data = NULL;
        }
        return *this;
    }

    template<typename T> amsarray<T>& amsarray<T>::operator=(const std::vector<T>& other)
    {
        amsarray_size_t I;
        int res;
    
        res = this->resize(other.size());
        if(res!=amsarray_success)
        {
            return *this;
        }
        else
        {
            for(I=0;I<length && I<other.size();I++)
            {
                data[I] = other[I];
            }
        }

        return *this;
    }
    
    template<typename T> amsarray<T>::amsarray(const std::initializer_list<T> initlist)
    {
        amsarray_size_t I;
        int res;
        length = 0;
        reserved = 0;
        growfactor = 1.5;
        data = NULL;
    
        res = this->resize(initlist.size());
        if(res!=amsarray_success)
        {
            return;
        }
        else
        {
            I = 0;
            for(T elem : initlist)
            {
                data[I] = elem;
                if(I<length) I++;
            }
        }
    }
    
    template<typename T> amsarray<T>& amsarray<T>::operator=(const std::initializer_list<T> initlist)
    {
        amsarray_size_t I;
        int res;
    
        res = this->resize(initlist.size());
        if(res!=amsarray_success)
        {
            this->length = 0;
            if(this->data != NULL) {delete[] this->data; this->data=NULL;}
            return *this;
        }
        else
        {
            I = 0;
            for(T elem : initlist)
            {
                data[I] = elem;
                if(I<length) I++;
            }
        }
    
        return *this;
    }
    
    template<class T> template<class T2> amsarray<T>::operator amsarray<T2>() const
    {
        //casting datatypes
        amsarray<T2> ret;
        int res;
    
        res = ret.resize(this->length);
        if(res!=amsarray_success)
        {
            ret.resize(0);
            return ret;
        }
        else
        {
            buffer_cast_copy<T2,T>(ret.data,data,length);
        }
    
        return ret;
    }



    template<typename T> T& amsarray<T>::operator[](amsarray_size_t ind)
    {
        return data[ind];
    }
    template<typename T> const T& amsarray<T>::operator[](amsarray_size_t ind) const
    {
        return data[ind];
    }
    template<typename T> T& amsarray<T>::at(amsarray_size_t ind)
    {
        return data[ind];
    }
    template<typename T> const T& amsarray<T>::at(amsarray_size_t ind) const
    {
        return data[ind];
    }

    template<typename T> int amsarray<T>::setall(const T &val)
    {
        int ret = amsarray_success;
        int res;
        res = buffer_set<T>(data,length,val);
        if(res!=amsmathutil25_success)
        {
            ret = amsarray_failure;
        }
        return ret;
    }



    template<typename T> amsarray<T> amsarray<T>::subarray(amsarray_size_t ind1, amsarray_size_t ind2) const
    {
        int res;
        amsarray<T> ret;
        T defval = T();
        amsarray_size_t I,J;

        int nl = ind2-ind1;

        if(nl<=0)
        {
            ret.resize(0);
            return ret;
        }

        ret.resize(nl);
        for(I=0;I<nl;I++)
        {
            J = I + ind1;
            if(J<0 || J>=this->length)
            {
                ret.data[I] = defval;
            }
            else
            {
                ret.data[I] = this->data[J];
            }
        }
        
        return ret;
    }

//inserts a value at (before) index ind
//insert(0,val) would give {val, oldval0, oldval1, ...}
//insert(N,val)
template<typename T> int amsarray<T>::insert(amsarray_size_t ind, const T& val)
{
    int ret = amsarray_success;
    int res;
    amsarray<T> narr;
    amsarray_size_t I;

    if(ind<0)
    {
        ret = amsarray_failure;
        return ret;
    }
    else if(ind<=this->length)
    {
        res = narr.resize_insert(this->length+1);
        if(res!=amsarray_success)
        {
            ret = amsarray_failure;
            return ret;
        }

        if(this->data!=NULL)
        {
            for(I=0;I<ind && I<this->length;I++)
            {
                narr.data[I] = this->data[I];
            }
        }
        narr.data[ind] = val;
        if(this->data!=NULL)
        {
            for(I=ind+1;I<narr.length;I++)
            {
                narr.data[I] = this->data[I-1];
            }
        }

        //steal data from narr
        if(this->data!=NULL) {delete[] this->data; this->data=NULL;}
        this->length = narr.length;
        this->data = narr.data;
        narr.length = 0;
        narr.data = NULL;
    }
    else
    {
        //inserting past the end of the array
        res = narr.resize_insert(ind+1);
        if(res!=amsarray_success)
        {
            ret = amsarray_failure;
            return ret;
        }

        if(this->data!=NULL)
        {
            for(I=0;I<this->length;I++)
            {
                narr.data[I] = this->data[I];
            }
        }
        narr.data[ind] = val;

        //steal data from narr
        if(this->data!=NULL) {delete[] this->data; this->data=NULL;}
        this->length = narr.length;
        this->data = narr.data;
        narr.length = 0;
        narr.data = NULL;
    }

    return ret;
}

//removes the value at index ind, and reduces array size by 1
template<typename T> int amsarray<T>::erase(amsarray_size_t ind)
{
    int ret = amsarray_success;
    int res;
    amsarray<T> narr;
    amsarray_size_t I;

    if(ind<0 || ind>=this->length)
    {
        ret = amsarray_failure;
        return ret;
    }

    res = narr.resize_insert(this->length-1);
    if(res!=amsarray_success)
    {
        ret = amsarray_failure;
        return ret;
    }

    if(this->data!=NULL)
    {
        for(I=0;I<ind && I<this->length && I<narr.length;I++)
        {
            narr.data[I] = this->data[I];
        }
        for(I=ind+1;I<this->length && (I-1)<narr.length;I++)
        {
            narr.data[I-1] = this->data[I];
        }
    }

    //steal data from narr (should be similar to what std::move would do?)
    if(this->data!=NULL) {delete[] this->data; this->data=NULL;}
    this->length = narr.length;
    this->data = narr.data;
    narr.length = 0;
    narr.data = NULL;

    return ret;
}

//finds the first instance of val in the array
template<typename T> amsarray_size_t amsarray<T>::find(const T& val)
{
    amsarray_size_t ret = -1;
    amsarray_size_t I;

    for(I=0;I<this->length;I++)
    {
        if(this->data[I]==val)
        {
            ret = I;
            break;
        }
    }

    return ret;
}

//finds the next instance of val, starting consideration at indstart
template<typename T> amsarray_size_t amsarray<T>::findnext(amsarray_size_t indstart, const T& val)
{
    amsarray_size_t ret = -1;
    amsarray_size_t I;

    for(I=indstart;I<this->length;I++)
    {
        if(this->data[I]==val)
        {
            ret = I;
            break;
        }
    }

    return ret;
}

//finds where to insert a particular value such that the list remains ordered
template<typename T> amsarray_size_t amsarray<T>::find_insert_ordered(const T& val)
{
    amsarray_size_t ret = -1;
    amsarray_size_t I;

    ret = 0;
    for(I=0;I<this->length;I++)
    {
        ret = I+1;
        if(this->data[I]>val)
        {
            ret = I;
            break;
        }
    }
    ret = (ret<0) ? 0 : ret;
    ret = (ret>this->length) ? this->length : ret;
    

    return ret;
}


template<typename T> void amsarray_operator_comp_tf( 
    const amsarray<T> *a,
    const amsarray<T> *b, 
    amsarray<bool> *cmp,
    int threadnum, int nthreads
)
{
    int I0,I1,Is,I;

    
    Is = (nthreads<=1) ? a->length : a->length/nthreads;
    I0 = Is*threadnum;
    I1 = (threadnum>=(nthreads-1))? a->length: Is*(threadnum+1);
    
    cmp->data[threadnum] = 1;
    for(I=I0;I<I1;I++)
    {
        if(a->data[I]!=b->data[I])
        {
            cmp->data[threadnum] = 0;
            break;
        }
    }
    return;
}

template<typename T> bool amsarray<T>::operator==(const amsarray<T>& other) const
{
    bool ret = 1;
    amsarray<bool> cmp;
    amsarray_size_t I;
    int J;
    int nthreads;
    std::vector<std::thread*> threads;

    if(this->length != other.length)
    {
        ret = 0;
        return ret;
    }
    if(this->length == 0)
    {
        ret = 1;
        return ret;
    }
    if(this->length < amsmathutil25_threadpsz)
    {
        for(I=0;I<this->length;I++)
        {
            if(this->data[I]!=other.data[I])
            {
                ret = 0;
                break;
            }
        }
    }
    else
    {
        //threaded operation
        nthreads = std::thread::hardware_concurrency();
        nthreads = (nthreads<1) ? 1 : nthreads;
        nthreads = (nthreads>amsmathutil25_maxthreads) ? amsmathutil25_maxthreads : nthreads;
        //if(nthreads<1) nthreads=1;
        //if(nthreads>narray_max_threads) nthreads = narray_max_threads;
        threads.resize(nthreads);
        cmp.resize(nthreads);
        for(J=0;J<nthreads;J++)
        {
            //printf("debug: launching thread %d\n",J);
            threads[J] = new(std::nothrow) std::thread(
                &amsarray_operator_comp_tf<T>,
                this,
                &other,
                &cmp,
                J,nthreads
            );
        }
        for(J=0;J<nthreads;J++)
        {
            if(threads[J]!=NULL)
            { 
                threads[J]->join(); 
                delete threads[J]; 
                threads[J]= NULL;
            }
        }

        ret = 1;
        for(J=0;J<nthreads;J++)
        {
            if(cmp[J]==0)
            {
                ret = 0;
                break;
            }
        }

        cmp.resize(0);
    }

    return ret;
}

template<typename T> bool amsarray<T>::operator!=(const amsarray<T>& other) const
{
    return !(*this==other);
}

//aliases to insert/erase operations
template<typename T> int amsarray<T>::append(const T& val)
{
    int ret = amsarray_success;
    ret = insert(length,val);
    return ret;
}

template<typename T> int amsarray<T>::prepend(const T& val)
{
    int ret = amsarray_success;
    ret = insert(0,val);
    return ret;
}

template<typename T> int amsarray<T>::push_back(const T& val)
{
    return append(val);
}

template<typename T> int amsarray<T>::push_front(const T& val)
{
    return prepend(val);
}

template<typename T> T amsarray<T>::pop_back()
{
    T ret = data[length-1];
    erase(length-1);
    return ret;
}

template<typename T> T amsarray<T>::pop_front()
{
    T ret = data[0];
    erase(0);
    return ret;
}

template<typename T> void amsarray_select_tf(
    int threadnum,
    int nthreads,
    const amsarray<T> *array,
    const amsarray<amsarray_size_t> *inds,
    amsarray<T> *ret
)
{
    amsarray_size_t I,I0,I1,Is,N;
    amsarray_size_t ind, N2;
    T defval = T();
    N = inds->length;
    N2 = array->length;
    N = (N<=0) ? 0 : N;
    Is = (nthreads>=1) ? N/nthreads : N;
    I0 = Is*(threadnum);
    I1 = (threadnum<nthreads-1) ? Is*(threadnum+1) : N;

    for(I=I0;I<I1;I++)
    {
        ind = inds->at(I);
        if(ind<0 || ind >=N2)
        {
            ret->data[I] = defval;
        }
        else
        {
            ret->data[I] = array->data[ind];
        }
    }
    return;
}

//returns an array that is {thisarray[inds[0]],thisarray[inds[1]],...}
template<typename T> amsarray<T> amsarray<T>::select(const amsarray<amsarray_size_t> &inds)
{
    int res;
    amsarray<T> ret;
    amsarray_size_t psize = inds.length;
    amsarray_size_t I, ind;
    T defval = T();

    res = ret.resize(psize);
    if(res!=amsarray_success)
    {
        ret.resize(0);
        return ret;
    }

    if(psize<amsmathutil25_threadpsz)
    {
        for(I=0;I<psize;I++)
        {
            ind = inds[I];
            if(ind<0 || ind >=length)
            {
                ret[I] = defval;
            }
            else
            {
                ret[I] = data[ind];
            }
        }
    }
    else
    {
        threaded_execute(
            &amsarray_select_tf<T>,
            inds.length,
            this,
            &inds,
            &ret
        );
    }

    return ret;
}

template<typename T> void amsarray_reverse_tf(
    int threadnum,
    int nthreads,
    const amsarray<T> *array,
    amsarray<T> *ret
)
{
    amsarray_size_t I,I0,I1,Is,N;
    T defval = T();
    N = array->length;
    N = (N<=0) ? 0 : N;
    Is = (nthreads>=1) ? N/nthreads : N;
    I0 = Is*(threadnum);
    I1 = (threadnum<nthreads-1) ? Is*(threadnum+1) : N;

    for(I=I0;I<I1;I++)
    {
        ret->data[I] = array->data[N-I-1];
    }

    return;
}

//returns an array that is this array in reverse order
template<typename T> amsarray<T> amsarray<T>::reverse()
{
    int res;
    amsarray<T> ret;
    amsarray_size_t psize = length;
    amsarray_size_t I;
    T defval = T();

    res = ret.resize(psize);
    if(res!=amsarray_success)
    {
        ret.resize(0);
        return ret;
    }

    if(psize<amsmathutil25_threadpsz)
    {
        for(I=0;I<psize;I++)
        {
            ret[I] = data[length-I-1];
        }
    }
    else
    {
        threaded_execute(
            &amsarray_reverse_tf<T>,
            length,
            this,
            &ret
        );
    }

    return ret;
}

// If I implement this generic method, then things fail to find the 
// template specialized methods
template<typename T> void amsarray<T>::print(bool newline, int printstyle)
{
    //empty method - specialize for each type

    return;
}

//adding these declarations also seems to fix it.
template<> void amsarray<int>::print(bool newline, int printstyle);
template<> void amsarray<long>::print(bool newline, int printstyle);
template<> void amsarray<float>::print(bool newline, int printstyle);
template<> void amsarray<double>::print(bool newline, int printstyle);


}; //end namespace ams

#endif

