// Copyright (C) 2017 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef GRIDKERNELINDEXHELPER_H
#define GRIDKERNELINDEXHELPER_H
#include <array>
#include <vector>
#include <boost/multi_array.hpp>


/// \file gridKernelIndexHelper.h
/// \brief Index creation to trim the storage required by a more naive implementation
///        of the fast kernel summation algorithm

namespace StOpt
{

/// \brief Index for second member Linear regressions
/// \param p_indexB        permits to store posiiton of a x_i^{id} x_k^{idd}
/// \param p_tabToIndexB   helper for index for second member regression
template< int NND>
void createLinearIndexB(std::vector<std::array<size_t, 4> > &p_indexB, boost::multi_array<int, 2> &p_tabToIndexB)
{
    std::array<size_t, 4> ind1{{0, 0, 0, 0}};
    p_indexB.push_back(ind1);
    for (size_t id = 0; id < NND; ++id)
    {
        for (size_t j = 1; j <= 3; ++j)
        {
            // x^j term
            std::array<size_t, 4> indtAdd{{0, 0, id + 1, j}};
            p_indexB.push_back(indtAdd);
        }
    }
    for (size_t id = 1; id <= NND ; ++id)
    {
        for (size_t idd = id + 1; idd <= NND; ++idd)
        {
            std::array<size_t, 4 >  intAdd{{id, 1, idd, 1}};
            p_indexB.push_back(intAdd);
            std::array<size_t, 4 >  intAdd1{{id, 2, idd, 1}};
            p_indexB.push_back(intAdd1);
            std::array<size_t, 4 >  intAdd2{{id, 1, idd, 2}};
            p_indexB.push_back(intAdd2);
        }
    }
    for (size_t ii = 0; ii < 1 + NND; ++ii)
    {
        int ipow = (NND - 1 + ii) / NND;
        int idim = (NND - 1 + ii) % NND + 1;
        if (ipow == 0)
            idim = 0;

        for (size_t kk = 0; kk < 1 + 2 * NND; ++kk)
        {
            int kpow = (NND - 1 + kk) / NND;
            int kdim = (NND - 1 + kk) % NND + 1;
            if (kpow == 0)
                kdim = 0;
            std::array<size_t, 4> indexT ;
            if (idim == kdim)
            {
                indexT[0] = 0;
                indexT[1] = 0;
                indexT[2] = idim;
                indexT[3] = ipow + kpow;
            }
            else if (idim < kdim)
            {
                indexT[0] = idim;
                indexT[1] = ipow;
                indexT[2] = kdim;
                indexT[3] = kpow;
            }
            else
            {
                indexT[0] = kdim;
                indexT[1] = kpow;
                indexT[2] = idim;
                indexT[3] = ipow;
            }
            // search
            int ipos = 0;
            bool bCont = true;
            while (bCont)
            {
                bCont = false;
                for (int id = 0; id < 4; ++id)
                {
                    if (p_indexB[ipos][id] != indexT[id])
                    {
                        bCont = true;
                        ipos += 1;
                        break;
                    }
                }
            }
            p_tabToIndexB[ii][kk] = ipos;
        }
    }
}

/// \brief algorithm for index in Linear regression matrix
/// \param p_indexA        permits to store posiiton of a x_i^{id} x_k^{idd} x_l^{iddd}
/// \param p_tabToIndexB   helper for index in elements for regression matrix construction
template< int NND>
void createLinearIndexA(std::vector<std::array<size_t, 6> > &p_indexA, boost::multi_array<int, 3> &p_tabToIndexA)
{
    std::array<size_t, 6> ind1{{0, 0, 0, 0, 0, 0}};
    p_indexA.push_back(ind1);
    for (size_t id = 0; id < NND; ++id)
    {
        for (size_t j = 1; j <= 4; ++j)
        {
            // x^j term
            std::array<size_t, 6> indtAdd{{0, 0, 0, 0, id + 1, j}};
            p_indexA.push_back(indtAdd);
        }
    }
    for (size_t id = 1; id <= NND ; ++id)
    {
        for (size_t idd = id + 1; idd <= NND; ++idd)
        {
            for (size_t i = 1; i <= 3; ++i)
                for (size_t j = 1; j <= 4 - i; ++j)
                {
                    std::array<size_t, 6 >  intAdd{{0, 0, id, i, idd, j}};
                    p_indexA.push_back(intAdd);
                }
        }
    }
    for (size_t id = 1; id <= NND ; ++id)
    {
        for (size_t idd = id + 1; idd <= NND; ++idd)
        {
            for (size_t iddd = idd + 1; iddd <= NND; ++iddd)
            {
                for (size_t i = 1; i <= 2; ++i)
                    for (size_t j = 1; j <= 3 - i; ++j)
                        for (size_t k = 1; k <= 4 - i - j; ++k)
                        {
                            std::array<size_t, 6 >  intAdd{{id, i, idd, j, iddd, k}};
                            p_indexA.push_back(intAdd);
                        }
            }
        }
    }
    // fill in  matrix index
    for (size_t ii = 0; ii < 1 + NND; ++ii)
    {
        int ipow = (NND - 1 + ii) / NND;
        int idim = (NND - 1 + ii) % NND + 1;
        if (ipow == 0)
            idim = 0;

        for (int jj = 0; jj < 1 + NND; ++jj)
        {
            int jpow = (NND - 1 + jj) / NND;
            int jdim = (NND - 1 + jj) % NND + 1;
            if (jpow == 0)
                jdim = 0;
            for (size_t kk = 0; kk < 1 + 2 * NND; ++kk)
            {
                int kpow = (NND - 1 + kk) / NND;
                int kdim = (NND - 1 + kk) % NND + 1;
                if (kpow == 0)
                    kdim = 0;
                std::array<size_t, 6> indexT ;
                if ((idim == kdim) && (idim == jdim))
                {
                    indexT[0] = 0;
                    indexT[1] = 0;
                    indexT[2] = 0;
                    indexT[3] = 0;
                    indexT[4] = idim;
                    indexT[5] = ipow + kpow + jpow;
                }
                else if (idim == kdim)
                {
                    indexT[0] = 0;
                    indexT[1] = 0;
                    if (jdim < idim)
                    {
                        indexT[2] = jdim;
                        indexT[3] = jpow;
                        indexT[4] = idim;
                        indexT[5] = ipow + kpow;
                    }
                    else
                    {
                        indexT[2] = idim;
                        indexT[3] = ipow + kpow;
                        indexT[4] = jdim;
                        indexT[5] = jpow;
                    }
                }
                else if (idim == jdim)
                {
                    indexT[0] = 0;
                    indexT[1] = 0;
                    if (kdim < idim)
                    {
                        indexT[2] = kdim;
                        indexT[3] = kpow;
                        indexT[4] = idim;
                        indexT[5] = ipow + jpow;
                    }
                    else
                    {
                        indexT[2] = idim;
                        indexT[3] = ipow + jpow;
                        indexT[4] = kdim;
                        indexT[5] = kpow;
                    }
                }
                else if (jdim == kdim)
                {
                    indexT[0] = 0;
                    indexT[1] = 0;
                    if (idim < jdim)
                    {
                        indexT[2] = idim;
                        indexT[3] = ipow;
                        indexT[4] = jdim;
                        indexT[5] = jpow + kpow;
                    }
                    else
                    {
                        indexT[2] = jdim;
                        indexT[3] = jpow + kpow;
                        indexT[4] = idim;
                        indexT[5] = ipow;
                    }
                }
                else
                {
                    // general case
                    if (idim < jdim)
                    {
                        if (jdim < kdim)
                        {
                            indexT[0] = idim;
                            indexT[1] = ipow;
                            indexT[2] = jdim;
                            indexT[3] = jpow;
                            indexT[4] = kdim;
                            indexT[5] = kpow;
                        }
                        else if (kdim < idim)
                        {
                            indexT[0] = kdim;
                            indexT[1] = kpow;
                            indexT[2] = idim;
                            indexT[3] = ipow;
                            indexT[4] = jdim;
                            indexT[5] = jpow;
                        }
                        else
                        {
                            indexT[0] = idim;
                            indexT[1] = ipow;
                            indexT[2] = kdim;
                            indexT[3] = kpow;
                            indexT[4] = jdim;
                            indexT[5] = jpow;
                        }
                    }
                    else // jdim < idim
                    {
                        if (idim < kdim)
                        {
                            indexT[0] = jdim;
                            indexT[1] = jpow;
                            indexT[2] = idim;
                            indexT[3] = ipow;
                            indexT[4] = kdim;
                            indexT[5] = kpow;
                        }
                        else if (kdim < jdim)
                        {
                            indexT[0] = kdim;
                            indexT[1] = kpow;
                            indexT[2] = jdim;
                            indexT[3] = jpow;
                            indexT[4] = idim;
                            indexT[5] = ipow;
                        }
                        else
                        {
                            indexT[0] = jdim;
                            indexT[1] = jpow;
                            indexT[2] = kdim;
                            indexT[3] = kpow;
                            indexT[4] = idim;
                            indexT[5] = ipow;
                        }
                    }
                }
                // search
                int ipos = 0;
                bool bCont = true;
                while (bCont)
                {
                    bCont = false;
                    for (int id = 0; id < 6; ++id)
                    {
                        if (p_indexA[ipos][id] != indexT[id])
                        {
                            bCont = true;
                            ipos += 1;
                            break;
                        }
                    }
                }
                p_tabToIndexA[ii][jj][kk] = ipos;
            }
        }
    }
}
}
#endif /* GRIDKERNELINDEXHELPER_H */
