#ifndef __AMSCPPNARRAY_MATHIMPL_HPP__
#define __AMSCPPNARRAY_MATHIMPL_HPP__

namespace ams
{
namespace narray
{

    template<typename T> T narray_max(const narray<T> &q)
    {
        return q.max();
    }

    template<typename T> T narray_min(const narray<T> &q)
    {
        return q.min();
    }

    template<typename T> void narray_sum_tf(
        const narray<T> *in,
        narray<T> *sums,
        int threadnum,
        int nthreads
    )
    {
        int I0,I1,Is,I;
    
        Is = in->length/nthreads;
        I0 = Is*threadnum;
        I1 = (threadnum>=(nthreads-1))? in->length: Is*(threadnum+1);
        for(I=I0;I<I1;I++)
        {
            sums->data[threadnum] = sums->data[threadnum] + in->data[I];
        }
    }

    template<typename T> T narray_sum(const narray<T> &q)
    {
        T ret = T();
        narray<T> sums;
        narray_size_t I;
        int J;
        int nthreads;
        std::vector<std::thread*> threads;

        if(q.length<=0)
        {
            return ret;
        }

        if(q.length<narray_thread_sz)
        {
            ret = T();
            for(I=0;I<q.length;I++)
            {
                ret = ret + q.data[I];
            }
        }
        else
        {
            //threaded operation
            
            nthreads = std::thread::hardware_concurrency();
            nthreads = (nthreads<1) ? 1 : nthreads;
            nthreads = (nthreads>narray_max_threads) ? narray_max_threads : nthreads;
            threads.resize(nthreads);
            sums.resize(nthreads);
            sums.setall(T());
            for(J=0;J<nthreads;J++)
            {
                threads[J] = new(std::nothrow) std::thread(
                    &narray_sum_tf<T>,
                    &q,
                    &sums,
                    J,nthreads
                );
                if(threads[J]==NULL)
                {
                    printf("narray_sum: warning: thread %d failed to initialize.\n",J);
                    //handle errors
                }
            }
            for(J=0;J<nthreads;J++)
            {
                if(threads[J]!=NULL)
                { 
                    threads[J]->join(); 
                    delete threads[J]; 
                    threads[J]= NULL;
                }
            }
            ret = T();
            for(J=0;J<nthreads;J++)
            {
                ret = ret + sums.data[J];
            }
        }
        return ret;
    }

    template<typename T> double narray_mean(const narray<T> &q)
    {
        double ret = 0.0;
        T sum = narray_sum(q);
        ret = (double)sum/((double)q.length);
        return ret;
    }

    template<typename T> void narray_variance_tf(
        const narray<T> *in,
        narray<double> *sums,
        double mean,
        int threadnum,
        int nthreads
    )
    {
        int I0,I1,Is,I;
        double q2;
        Is = in->length/nthreads;
        I0 = Is*threadnum;
        I1 = (threadnum>=(nthreads-1))? in->length: Is*(threadnum+1);
        for(I=I0;I<I1;I++)
        {
            q2 = in->data[I]-mean;
            sums->data[threadnum] = sums->data[threadnum] + q2*q2/((double)in->length);
        }
    }

    template<typename T> double narray_var(const narray<T> &q)
    {
        double ret = 0.0;
        narray<double> sums;
        narray_size_t I;
        int J;
        int nthreads;
        std::vector<std::thread*> threads;

        double mn = ams::narray::narray_mean(q);
        double q2;

        if(q.length<=0)
        {
            return ret;
        }

        if(q.length<narray_thread_sz)
        {
            ret = 0.0;
            for(I=0;I<q.length;I++)
            {
                q2 = (double)q.data[I]-mn;
                ret = ret + q2*q2/((double)q.length);
            }
        }
        else
        {
            //threaded operation
            
            nthreads = std::thread::hardware_concurrency();
            nthreads = (nthreads<1) ? 1 : nthreads;
            nthreads = (nthreads>narray_max_threads) ? narray_max_threads : nthreads;
            threads.resize(nthreads);
            sums.resize(nthreads);
            sums.setall(T());
            for(J=0;J<nthreads;J++)
            {
                threads[J] = new(std::nothrow) std::thread(
                    &narray_variance_tf<T>,
                    &q,
                    &sums,
                    mn,
                    J,nthreads
                );
                if(threads[J]==NULL)
                {
                    printf("narray_var: warning: thread %d failed to initialize.\n",J);
                    //handle errors
                }
            }
            for(J=0;J<nthreads;J++)
            {
                if(threads[J]!=NULL)
                { 
                    threads[J]->join(); 
                    delete threads[J]; 
                    threads[J]= NULL;
                }
            }
            ret = T();
            for(J=0;J<nthreads;J++)
            {
                ret = ret + sums.data[J];
            }
        }
        return ret;
    }

    template<typename T> double narray_stdev(const narray<T> &q)
    {
        double ret = narray_var(q);
        ret = ::sqrt(ret);
        return ret;
    }

    template<typename T> void narray_L2norm_tf(
        const narray<T> *in,
        narray<double> *sums,
        int threadnum,
        int nthreads
    )
    {
        int I0,I1,Is,I;
        double q2;
        Is = in->length/nthreads;
        I0 = Is*threadnum;
        I1 = (threadnum>=(nthreads-1))? in->length: Is*(threadnum+1);
        for(I=I0;I<I1;I++)
        {
            q2 = in->data[I];
            sums->data[threadnum] = sums->data[threadnum] + q2*q2;
        }
    }

    template<typename T> double narray_L2norm(const narray<T> &q)
    {
        double ret = 0.0;
        narray<double> sums;
        narray_size_t I;
        int J;
        int nthreads;
        std::vector<std::thread*> threads;
        double q2;

        if(q.length<=0)
        {
            return ret;
        }

        if(q.length<narray_thread_sz)
        {
            ret = 0.0;
            for(I=0;I<q.length;I++)
            {
                q2 = (double)q.data[I];
                ret = ret + q2*q2;
            }
        }
        else
        {
            //threaded operation
            
            nthreads = std::thread::hardware_concurrency();
            nthreads = (nthreads<1) ? 1 : nthreads;
            nthreads = (nthreads>narray_max_threads) ? narray_max_threads : nthreads;
            threads.resize(nthreads);
            sums.resize(nthreads);
            sums.setall(T());
            for(J=0;J<nthreads;J++)
            {
                threads[J] = new(std::nothrow) std::thread(
                    &narray_L2norm_tf<T>,
                    &q,
                    &sums,
                    J,nthreads
                );
                if(threads[J]==NULL)
                {
                    printf("narray_L2norm: warning: thread %d failed to initialize.\n",J);
                    //handle errors
                }
            }
            for(J=0;J<nthreads;J++)
            {
                if(threads[J]!=NULL)
                { 
                    threads[J]->join(); 
                    delete threads[J]; 
                    threads[J]= NULL;
                }
            }
            ret = T();
            for(J=0;J<nthreads;J++)
            {
                ret = ret + sums.data[J];
            }
        }

        ret = ::sqrt(ret);
        return ret;
    }


    template<typename T> void narray_abs_tf(
        const narray<T> *in,
        narray<T> *out,
        int threadnum,
        int nthreads
    )
    {
        int I0,I1,Is,I;
        double q2;
        Is = in->length/nthreads;
        I0 = Is*threadnum;
        I1 = (threadnum>=(nthreads-1))? in->length: Is*(threadnum+1);
        for(I=I0;I<I1;I++)
        {
            out->data[I] = (in->data[I]<T(0)) ? -in->data[I] : in->data[I];
        }
    }


    template<typename T> narray<T> narray_abs(narray<T> &q)
    {
        narray<T> ret;
        narray_size_t I;
        int J;
        int nthreads;
        std::vector<std::thread*> threads;

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

        ret.resize(q.length);
        if(ret.length!=q.length)
        {
            printf("narray_abs: error - could not allocate return array.\n");
            ret.resize(0);
            return ret;
        }

        if(q.length<narray_thread_sz)
        {
            for(I=0;I<q.length;I++)
            {
                ret.data[I] = (q.data[I]<T(0)) ? -q.data[I] : q.data[I];
            }
        }
        else
        {
            //threaded operation
            
            nthreads = std::thread::hardware_concurrency();
            nthreads = (nthreads<1) ? 1 : nthreads;
            nthreads = (nthreads>narray_max_threads) ? narray_max_threads : nthreads;
            threads.resize(nthreads);
            for(J=0;J<nthreads;J++)
            {
                threads[J] = new(std::nothrow) std::thread(
                    &narray_abs_tf<T>,
                    &q,
                    &ret,
                    J,nthreads
                );
                if(threads[J]==NULL)
                {
                    printf("narray_abs: warning: thread %d failed to initialize.\n",J);
                    //handle errors
                }
            }
            for(J=0;J<nthreads;J++)
            {
                if(threads[J]!=NULL)
                { 
                    threads[J]->join(); 
                    delete threads[J]; 
                    threads[J]= NULL;
                }
            }
        }

        return ret;
    }

    
}; //end namespaces
};

#endif