////////////////////////////////////////////////////////////////////////////////
//
//  File: Octree.cpp
//
//  For more information, please see: http://www.nektar.info/
//
//  The MIT License
//
//  Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
//  Department of Aeronautics, Imperial College London (UK), and Scientific
//  Computing and Imaging Institute, University of Utah (USA).
//
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included
//  in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
//  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
//  Description:
//
////////////////////////////////////////////////////////////////////////////////

#include "Octree.h"
#include <NekMesh/CADSystem/CADCurve.h>
#include <NekMesh/CADSystem/CADSurf.h>
#include <NekMesh/Module/Module.h>

#include <LibUtilities/BasicUtils/ParseUtils.h>

#include <boost/algorithm/string.hpp>

using namespace std;
namespace Nektar::NekMesh
{

// Number of samples to use.
constexpr int nSamp = 40;

void Octree::Process()
{
    auto boundingBox = m_mesh->m_cad->GetBoundingBox();

    // build curvature samples
    CompileSourcePointList();

    m_log(VERBOSE) << "  - Curvature samples: " << m_SPList.size() << endl;

    // make master octant based on the bounding box of the domain
    m_dim = max((boundingBox[1] - boundingBox[0]) / 2.0,
                (boundingBox[3] - boundingBox[2]) / 2.0);

    m_dim = max(m_dim, (boundingBox[5] - boundingBox[4]) / 2.0);

    m_centroid = {(boundingBox[1] + boundingBox[0]) / 2.0,
                  (boundingBox[3] + boundingBox[2]) / 2.0,
                  (boundingBox[5] + boundingBox[4]) / 2.0};

    m_masteroct = MemoryManager<Octant>::AllocateSharedPtr(
        0, m_centroid[0], m_centroid[1], m_centroid[2], m_dim, m_SPList);

    // begin recersive subdivision
    SubDivide();

    m_octants.clear();
    m_masteroct->CompileLeaves(m_octants);

    m_log(VERBOSE) << "Octree construction complete:" << endl;
    m_log(VERBOSE) << "  - Octants: " << m_octants.size() << endl;

    SmoothSurfaceOctants();

    PropagateDomain();

    SmoothAllOctants();

    m_log(VERBOSE) << "  - Predicted mesh size: " << CountElemt()
                   << " elements." << endl;
}

NekDouble Octree::Query(std::array<NekDouble, 3> loc)
{
    // starting at master octant 0 move through succsesive m_octants which
    // contain the point loc until a leaf is found
    // first search through sourcepoints

    NekDouble tmp = numeric_limits<double>::max();

    for (int i = 0; i < m_lsources.size(); i++)
    {
        if (m_lsources[i].WithinRange(loc))
        {
            tmp = min(m_lsources[i].delta, tmp);
        }
    }

    for (int i = 0; i < m_csources.size(); i++)
    {
        if (m_csources[i].WithinRange(loc))
        {
            tmp = min(m_csources[i].delta, tmp);
        }
    }

    OctantSharedPtr n = m_masteroct;
    int quad          = 0;

    bool found = false;

    while (!found)
    {
        auto octloc = n->GetLoc();

        if (!(loc[0] < octloc[0]) && // forward
            !(loc[1] < octloc[1]) && // forward
            !(loc[2] < octloc[2]))   // forward
        {
            quad = 0;
        }
        else if (!(loc[0] < octloc[0]) && // forward
                 !(loc[1] < octloc[1]) && // forward
                 !(loc[2] > octloc[2]))   // back
        {
            quad = 1;
        }
        else if (!(loc[0] < octloc[0]) && // forward
                 !(loc[1] > octloc[1]) && // back
                 !(loc[2] < octloc[2]))   // forward
        {
            quad = 2;
        }
        else if (!(loc[0] < octloc[0]) && // forward
                 !(loc[1] > octloc[1]) && // back
                 !(loc[2] > octloc[2]))   // back
        {
            quad = 3;
        }
        else if (!(loc[0] > octloc[0]) && // back
                 !(loc[1] < octloc[1]) && // forward
                 !(loc[2] < octloc[2]))   // forward
        {
            quad = 4;
        }
        else if (!(loc[0] > octloc[0]) && // back
                 !(loc[1] < octloc[1]) && // forward
                 !(loc[2] > octloc[2]))   // back
        {
            quad = 5;
        }
        else if (!(loc[0] > octloc[0]) && // back
                 !(loc[1] > octloc[1]) && // back
                 !(loc[2] < octloc[2]))   // forward
        {
            quad = 6;
        }
        else if (!(loc[0] > octloc[0]) && // back
                 !(loc[1] > octloc[1]) && // back
                 !(loc[2] > octloc[2]))   // back
        {
            quad = 7;
        }
        else
        {
            ASSERTL0(false, "Cannot locate quadrant");
        }

        n = n->GetChild(quad);

        if (n->IsLeaf())
        {
            found = true;
        }
    }

    return min(n->GetDelta(), tmp);
}

NekDouble Octree::GetMinDelta()
{
    NekDouble tmp = numeric_limits<double>::max();

    for (int i = 0; i < m_lsources.size(); i++)
    {
        tmp = min(m_lsources[i].delta, tmp);
    }

    for (int i = 0; i < m_csources.size(); i++)
    {
        tmp = min(m_csources[i].delta, tmp);
    }
    return min(m_minDelta, tmp);
}

void Octree::WriteOctree(string nm)
{
    MeshSharedPtr oct = std::shared_ptr<Mesh>(new Mesh());
    oct->m_expDim     = 3;
    oct->m_spaceDim   = 3;
    oct->m_nummode    = 2;

    for (int i = 0; i < m_octants.size(); i++)
    {
        vector<NodeSharedPtr> ns(8);

        ns[0] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eBack),
                                               m_octants[i]->FX(eDown),
                                               m_octants[i]->FX(eRight)));

        ns[1] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eForward),
                                               m_octants[i]->FX(eDown),
                                               m_octants[i]->FX(eRight)));

        ns[2] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eForward),
                                               m_octants[i]->FX(eUp),
                                               m_octants[i]->FX(eRight)));

        ns[3] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eBack),
                                               m_octants[i]->FX(eUp),
                                               m_octants[i]->FX(eRight)));

        ns[4] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eBack),
                                               m_octants[i]->FX(eDown),
                                               m_octants[i]->FX(eLeft)));

        ns[5] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eForward),
                                               m_octants[i]->FX(eDown),
                                               m_octants[i]->FX(eLeft)));

        ns[6] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eForward),
                                               m_octants[i]->FX(eUp),
                                               m_octants[i]->FX(eLeft)));

        ns[7] = std::shared_ptr<Node>(new Node(0, m_octants[i]->FX(eBack),
                                               m_octants[i]->FX(eUp),
                                               m_octants[i]->FX(eLeft)));

        vector<int> tags;
        tags.push_back(0);
        ElmtConfig conf(LibUtilities::eHexahedron, 1, false, false);
        ElementSharedPtr E = GetElementFactory().CreateInstance(
            LibUtilities::eHexahedron, conf, ns, tags);
        oct->m_element[3].push_back(E);
    }

    ModuleSharedPtr mod =
        GetModuleFactory().CreateInstance(ModuleKey(eOutputModule, "xml"), oct);
    mod->RegisterConfig("outfile", "octree.xml");
    mod->RegisterConfig("order", nm);
    mod->ProcessVertices();
    mod->ProcessEdges();
    mod->ProcessFaces();
    mod->ProcessElements();
    mod->ProcessComposites();
    mod->Process();
}

void Octree::SubDivide()
{
    bool repeat;
    int ct = 1;

    m_numoct = 1;
    m_masteroct->Subdivide(m_masteroct, m_numoct);

    do
    {
        m_log(VERBOSE).Overwrite();
        m_log(VERBOSE) << "  - Subdivide iteration: " << ct << flush;

        ct++;
        repeat = false;
        m_octants.clear();
        // grab a list of the leaves curently in the octree
        m_masteroct->CompileLeaves(m_octants);

        VerifyNeigbours();

        // neeed to create a divide list, in the first list will be m_octants
        // which need to subdivide based on curvature,
        // in the next list will be ocants which need to subdivide to make sure
        // level criteria is statisified for the previous list and so on
        // the list will keep building till no more lists are required.
        // the list will then be iterated through backwards to subdivide all the
        // sublists in turn.
        vector<vector<OctantSharedPtr>> dividelist;
        set<int> inlist;
        // build initial list
        {
            vector<OctantSharedPtr> sublist;
            for (int i = 0; i < m_octants.size(); i++)
            {
                if (m_octants[i]->NeedDivide() &&
                    m_octants[i]->DX() / 4.0 >
                        (m_octants[i]->HasDelta()
                             ? min(m_minDelta, m_octants[i]->GetDelta())
                             : m_minDelta))
                {
                    sublist.push_back(m_octants[i]);
                    inlist.insert(m_octants[i]->GetId());
                    repeat = true; // if there is a subdivision this whole
                                   // process needs to be repeated
                }
            }
            dividelist.push_back(sublist);
        }
        // then loop over building sublists until no more are required
        while (true)
        {
            vector<OctantSharedPtr> newsublist,
                previouslist = dividelist.back();
            for (int i = 0; i < previouslist.size(); i++)
            {
                map<OctantFace, vector<OctantSharedPtr>> nlist =
                    previouslist[i]->GetNeigbours();
                map<OctantFace, vector<OctantSharedPtr>>::iterator it;
                for (it = nlist.begin(); it != nlist.end(); it++)
                {
                    for (int j = 0; j < it->second.size(); j++)
                    {
                        if (previouslist[i]->DX() < it->second[j]->DX())
                        {
                            set<int>::iterator s =
                                inlist.find(it->second[j]->GetId());
                            if (s == inlist.end())
                            {
                                inlist.insert(it->second[j]->GetId());
                                newsublist.push_back(it->second[j]);
                            }
                        }
                    }
                }
            }
            if (newsublist.size() == 0)
            {
                break;
            }
            else
            {
                dividelist.push_back(newsublist);
            }
        }

        vector<vector<OctantSharedPtr>>::reverse_iterator rit;
        for (rit = dividelist.rbegin(); rit != dividelist.rend(); rit++)
        {
            vector<OctantSharedPtr> currentlist = *rit;
            for (int i = 0; i < currentlist.size(); i++)
            {
                currentlist[i]->Subdivide(currentlist[i], m_numoct);
            }
        }
    } while (repeat);

    m_log(VERBOSE).Newline();
}

bool Octree::VerifyNeigbours()
{
    // check all octant links to their neighbours
    // at all times in the subdivision the set of neigbours must
    // conform to a set of criteria such as smoothness in size
    // this checks that
    bool error = false;
    for (int i = 0; i < m_octants.size(); i++)
    {
        bool valid = true;
        map<OctantFace, vector<OctantSharedPtr>> nlist =
            m_octants[i]->GetNeigbours();
        map<OctantFace, vector<OctantSharedPtr>>::iterator it;
        for (it = nlist.begin(); it != nlist.end(); it++)
        {
            if (it->second.size() == 0)
            {
                NekDouble expectedfx = 0.0;
                switch (it->first)
                {
                    case eUp:
                        expectedfx = m_centroid[1] + m_dim;
                        break;
                    case eDown:
                        expectedfx = m_centroid[1] - m_dim;
                        break;
                    case eLeft:
                        expectedfx = m_centroid[2] + m_dim;
                        break;
                    case eRight:
                        expectedfx = m_centroid[2] - m_dim;
                        break;
                    case eForward:
                        expectedfx = m_centroid[0] + m_dim;
                        break;
                    case eBack:
                        expectedfx = m_centroid[0] - m_dim;
                        break;
                }
                if (fabs(m_octants[i]->FX(it->first) - expectedfx) > 1E-6)
                {
                    valid = false;
                    m_log(WARNING) << "wall neigbour error" << endl;
                    m_log(WARNING)
                        << expectedfx << " " << m_octants[i]->FX(it->first)
                        << " " << it->first << endl;
                }
            }
            else if (it->second.size() == 1)
            {
                if (!(m_octants[i]->DX() == it->second[0]->DX() ||
                      it->second[0]->DX() == 2.0 * m_octants[i]->DX()))
                {
                    valid = false;
                    m_log(WARNING) << " 1 neigbour error" << endl;
                    m_log(WARNING) << m_octants[i]->DX() << " "
                                   << it->second[0]->DX() << endl;
                }
            }
            else if (it->second.size() == 4)
            {
                if (!(m_octants[i]->DX() / 2.0 == it->second[0]->DX()))
                {
                    valid = false;
                    m_log(WARNING) << "4 neighbour error" << endl;
                    m_log(WARNING) << m_octants[i]->DX() << " "
                                   << it->second[0]->DX() << endl;
                }
            }
        }
        if (!valid)
        {
            error = true;
            m_log(WARNING) << "invalid neigbour config" << endl;
        }
    }
    return !error;
}

void Octree::SmoothSurfaceOctants()
{
    // for all the m_octants which are surface containing and know their delta
    // specification, look over all neighbours and ensure the specification
    // between them is smooth
    int ct = 0;

    do
    {
        ct = 0;
        for (int i = 0; i < m_octants.size(); i++)
        {
            OctantSharedPtr oct = m_octants[i];

            if (oct->IsDeltaKnown())
            {
                vector<OctantSharedPtr> check;
                map<OctantFace, vector<OctantSharedPtr>> nList =
                    oct->GetNeigbours();
                map<OctantFace, vector<OctantSharedPtr>>::iterator it;

                for (it = nList.begin(); it != nList.end(); it++)
                {
                    for (int j = 0; j < it->second.size(); j++)
                    {
                        if (it->second[j]->IsDeltaKnown() &&
                            it->second[j]->GetDelta() < oct->GetDelta() &&
                            ddx(oct, it->second[j]) > 0.2)
                        {
                            check.push_back(it->second[j]);
                        }
                    }
                }

                // for each neighbour listed in check_id, figure out the
                // smoothed
                // delta, and asign the miminum of these to nodes[i].GetDelta()
                if (check.size() > 0)
                {
                    NekDouble deltaSM = numeric_limits<double>::max();
                    for (int j = 0; j < check.size(); j++)
                    {
                        NekDouble r = oct->Distance(check[j]);

                        if (0.199 * r + check[j]->GetDelta() < deltaSM)
                        {
                            deltaSM = 0.199 * r + check[j]->GetDelta();
                        }
                    }
                    oct->SetDelta(deltaSM);
                    ct += 1;
                }
            }
        }
    } while (ct > 0);
}

void Octree::PropagateDomain()
{
    // until all m_octants know their delta specifcation and orientaion
    // look over all m_octants and if their neighours know either their
    // orientation
    // or specifcation calculate one for this octant
    int ct = 0;

    do
    {
        ct = 0;
        for (int i = 0; i < m_octants.size(); i++)
        {
            OctantSharedPtr oct = m_octants[i];

            if (!oct->IsDeltaKnown())
            { // if delta has not been asigned
                vector<OctantSharedPtr> known;
                map<OctantFace, vector<OctantSharedPtr>> nList =
                    oct->GetNeigbours();
                map<OctantFace, vector<OctantSharedPtr>>::iterator it;

                for (it = nList.begin(); it != nList.end(); it++)
                {
                    for (int j = 0; j < it->second.size(); j++)
                    {
                        if (it->second[j]->IsDeltaKnown())
                        {
                            known.push_back(it->second[j]);
                        }
                    }
                }

                if (known.size() > 0)
                {
                    vector<NekDouble> deltaPrime;
                    for (int j = 0; j < known.size(); j++)
                    {
                        NekDouble r = oct->Distance(known[j]);

                        if (0.199 * r + known[j]->GetDelta() < m_maxDelta)
                        {
                            deltaPrime.push_back(0.199 * r +
                                                 known[j]->GetDelta());
                        }
                        else
                        {
                            deltaPrime.push_back(m_maxDelta);
                        }
                    }
                    NekDouble min = numeric_limits<double>::max();
                    for (int j = 0; j < deltaPrime.size(); j++)
                    {
                        if (deltaPrime[j] < min)
                        {
                            min = deltaPrime[j];
                        }
                    }
                    oct->SetDelta(min);
                    ct += 1;
                    deltaPrime.clear();
                }
                known.clear();
            }

            if (oct->GetLocation() == eUnknown)
            { // if the node does not know its location
                vector<OctantSharedPtr> known;
                map<OctantFace, vector<OctantSharedPtr>> nList =
                    oct->GetNeigbours();
                map<OctantFace, vector<OctantSharedPtr>>::iterator it;

                for (it = nList.begin(); it != nList.end(); it++)
                {
                    for (int j = 0; j < it->second.size(); j++)
                    {
                        if (it->second[j]->GetLocation() != eUnknown)
                        {
                            known.push_back(it->second[j]);
                        }
                    }
                }

                if (known.size() > 0)
                {
                    vector<OctantSharedPtr> isNotOnBound;
                    for (int j = 0; j < known.size(); j++)
                    {
                        if (known[j]->GetLocation() != eOnBoundary)
                        {
                            isNotOnBound.push_back(known[j]);
                        }
                    }

                    if (isNotOnBound.size() > 0)
                    {
                        oct->SetLocation(isNotOnBound[0]->GetLocation());
                    }
                    else
                    {
                        NekDouble dist = numeric_limits<double>::max();

                        OctantSharedPtr closest;

                        bool f = false;
                        for (int j = 0; j < known.size(); j++)
                        {
                            if (oct->Distance(known[j]) < dist)
                            {
                                closest = known[j];
                                dist    = oct->Distance(known[j]);
                                f       = true;
                            }
                        }
                        ASSERTL0(f, "closest never set");

                        SPBaseSharedPtr sp = closest->GetABoundPoint();

                        std::array<NekDouble, 3> octloc, sploc, vec, N;
                        std::array<NekDouble, 2> uv;
                        int surf;
                        sp->GetCAD(surf, uv);
                        N = m_mesh->m_cad->GetSurf(surf)->N(uv);

                        octloc = oct->GetLoc();
                        sploc  = sp->GetLoc();

                        vec[0] = octloc[0] - sploc[0];
                        vec[1] = octloc[1] - sploc[1];
                        vec[2] = octloc[2] - sploc[2];

                        NekDouble dot =
                            vec[0] * N[0] + vec[1] * N[1] + vec[2] * N[2];

                        if (dot <= 0.0)
                        {
                            oct->SetLocation(eOutside);
                            ct += 1;
                        }
                        else
                        {
                            oct->SetLocation(eInside);
                            ct += 1;
                        }
                    }
                }
                known.clear();
            }
        }
    } while (ct > 0);

    for (int i = 0; i < m_octants.size(); i++)
    {
        if (!m_octants[i]->IsDeltaKnown())
        {
            m_octants[i]->SetDelta(m_maxDelta);
        }
    }
}

void Octree::SmoothAllOctants()
{
    // until no more changes occur smooth the mesh specification between all
    // m_octants not particualrly strictly
    int ct = 0;

    do
    {
        ct = 0;
        for (int i = 0; i < m_octants.size(); i++)
        {
            OctantSharedPtr oct = m_octants[i];

            vector<OctantSharedPtr> check;
            map<OctantFace, vector<OctantSharedPtr>> nList =
                oct->GetNeigbours();
            map<OctantFace, vector<OctantSharedPtr>>::iterator it;
            for (it = nList.begin(); it != nList.end(); it++)
            {
                for (int j = 0; j < it->second.size(); j++)
                {
                    if (it->second[j]->GetDelta() < oct->GetDelta() &&
                        ddx(oct, it->second[j]) > 0.2)
                    {
                        check.push_back(it->second[j]);
                    }
                }
            }

            if (check.size() > 0)
            {
                NekDouble deltaSM = numeric_limits<double>::max();
                for (int j = 0; j < check.size(); j++)
                {
                    NekDouble r = oct->Distance(check[j]);

                    if (0.199 * r + check[j]->GetDelta() < deltaSM)
                    {
                        deltaSM = 0.199 * r + check[j]->GetDelta();
                    }
                }
                oct->SetDelta(deltaSM);
                ct += 1;
            }
        }
    } while (ct > 0);
}

int Octree::CountElemt()
{
    // by considering the volume of a tet evaluate the number of elements in the
    // mesh

    NekDouble total = 0.0;

    auto boundingBox = m_mesh->m_cad->GetBoundingBox();

    for (int i = 0; i < m_octants.size(); i++)
    {
        OctantSharedPtr oct = m_octants[i];
        if (oct->GetLocation() == eInside)
        {
            total += 8.0 * oct->DX() * oct->DX() * oct->DX() /
                     (oct->GetDelta() * oct->GetDelta() * oct->GetDelta() /
                      6.0 / sqrt(2));
        }
        else if (oct->GetLocation() == eOnBoundary)
        {
            NekDouble vol = 1.0;
            if (oct->FX(eBack) < boundingBox[1] &&
                oct->FX(eForward) > boundingBox[0])
            {
                // then there is some over lap in x
                NekDouble min, max;
                if (oct->FX(eBack) > boundingBox[0])
                {
                    min = oct->FX(eBack);
                }
                else
                {
                    min = boundingBox[0];
                }
                if (boundingBox[1] < oct->FX(eForward))
                {
                    max = boundingBox[1];
                }
                else
                {
                    max = oct->FX(eForward);
                }
                vol *= (max - min);
            }
            else
            {
                vol *= 0.0;
            }

            if (oct->FX(eDown) < boundingBox[3] &&
                oct->FX(eUp) > boundingBox[2])
            {
                // then there is some over lap in x
                NekDouble min, max;
                if (oct->FX(eDown) > boundingBox[2])
                {
                    min = oct->FX(eDown);
                }
                else
                {
                    min = boundingBox[2];
                }
                if (boundingBox[3] < oct->FX(eUp))
                {
                    max = boundingBox[3];
                }
                else
                {
                    max = oct->FX(eUp);
                }
                vol *= (max - min);
            }
            else
            {
                vol *= 0.0;
            }

            if (oct->FX(eRight) < boundingBox[5] &&
                oct->FX(eLeft) > boundingBox[4])
            {
                // then there is some over lap in x
                NekDouble min, max;
                if (oct->FX(eRight) > boundingBox[4])
                {
                    min = oct->FX(eRight);
                }
                else
                {
                    min = boundingBox[4];
                }
                if (boundingBox[5] < oct->FX(eLeft))
                {
                    max = boundingBox[5];
                }
                else
                {
                    max = oct->FX(eLeft);
                }
                vol *= (max - min);
            }
            else
            {
                vol *= 0.0;
            }
            total += vol / 2.0 /
                     (oct->GetDelta() * oct->GetDelta() * oct->GetDelta() /
                      6.0 / sqrt(2));
        }
    }

    return int(total);
}

void Octree::CompileSourcePointList()
{
    // Get the information of any curves that contain refinement data
    map<int, pair<NekDouble, NekDouble>> curve_refinement;
    if (m_curverefinement.size() > 0)
    {
        m_log(VERBOSE) << "  - Modifying based on refinement curves" << endl;
        vector<string> curves;
        boost::split(curves, m_curverefinement, boost::is_any_of(":"));
        for (int i = 0; i < curves.size(); i++)
        {
            vector<NekDouble> data;
            ParseUtils::GenerateVector(curves[i], data);
            // data[0] = curve ID; data[1]= radius(R); data[2] = delta(D).
            curve_refinement[int(data[0])] = {data[1], data[2]};
        }
    }

    int totalEnt = 0;
    if (m_mesh->m_cad->Is2D())
    {
        totalEnt = m_mesh->m_cad->GetNumCurve();
        for (int i = 1; i <= m_mesh->m_cad->GetNumCurve(); i++)
        {
            m_log(VERBOSE).Progress(i, totalEnt, "  - Compiling source points");

            CADCurveSharedPtr curve = m_mesh->m_cad->GetCurve(i);

            // Check if curve i has refinement data
            map<int, pair<NekDouble, NekDouble>>::iterator it;
            it = curve_refinement.find(i);
            // Add curve as a refinement source. This works akin to line sources
            // and does not affect the octree.
            if (it != curve_refinement.end())
            {
                // it->second.first = radius(R); it->second.second = delta(D).
                m_csources.push_back(
                    CurveSource(it->second.first, it->second.second, curve));
            }

            auto bds = curve->GetBounds();
            // this works assuming the curves are not distorted
            int samples  = ceil(curve->Length(bds[0], bds[1]) / m_minDelta) * 2;
            samples      = max(nSamp, samples);
            NekDouble dt = (bds[1] - bds[0]) / (samples + 1);
            for (int j = 1; j < samples - 1;
                 j++) // dont want first and last point
            {
                NekDouble t = bds[0] + dt * j;
                NekDouble C = curve->Curvature(t);

                auto loc = curve->P(t);

                vector<pair<weak_ptr<CADSurf>, CADOrientation::Orientation>>
                    ss  = curve->GetAdjSurf();
                auto uv = ss[0].first.lock()->locuv(loc);

                if (C != 0.0)
                {
                    NekDouble del =
                        2.0 * (1.0 / C) * sqrt(m_eps * (2.0 - m_eps));

                    if (del > m_maxDelta)
                    {
                        del = m_maxDelta;
                    }
                    if (del < m_minDelta)
                    {
                        del = m_minDelta;
                    }
                    CPointSharedPtr newCPoint;

                    // Adds a curve source point with additional refinement
                    // delta. This promotes the octree to continue refining the
                    // octant containing the source point until the refinement
                    // delta is reached. it->second.second = delta(D);
                    if (it != curve_refinement.end())
                    {
                        newCPoint = MemoryManager<CPoint>::AllocateSharedPtr(
                            ss[0].first.lock()->GetId(), uv, loc, del,
                            it->second.second);
                    }
                    else
                    {
                        newCPoint = MemoryManager<CPoint>::AllocateSharedPtr(
                            ss[0].first.lock()->GetId(), uv, loc, del);
                    }
                    m_SPList.push_back(newCPoint);
                }
                else
                {
                    BPointSharedPtr newBPoint =
                        MemoryManager<BPoint>::AllocateSharedPtr(
                            ss[0].first.lock()->GetId(), uv, loc);

                    m_SPList.push_back(newBPoint);
                }
            }
        }
    }
    else
    {
        totalEnt = m_mesh->m_cad->GetNumSurf();
        for (int i = 1; i <= totalEnt; i++)
        {
            m_log(VERBOSE).Progress(i, totalEnt, "  - Compiling source points");

            CADSurfSharedPtr surf = m_mesh->m_cad->GetSurf(i);
            auto bounds           = surf->GetBounds();

            // to figure out the amount of curvature sampling to be conducted on
            // each parameter plane the surface is first sampled with a nSamp x
            // nSamp grid the real space lengths of this grid are analyzed to
            // find the largest stretching in the u and v directions this
            // stretching is then considered with the mindelta user input to
            // find a number of sampling points in each direction which ensures
            // that in the final octree each surface octant will have at least
            // one sample point within its volume.  the nSamp x nSamp grid is
            // used to ensure each surface has a minimum of nSamp x nSamp
            // samples.
            NekDouble du = (bounds[1] - bounds[0]) / (nSamp - 1);
            NekDouble dv = (bounds[3] - bounds[2]) / (nSamp - 1);

            NekDouble DeltaU = 0.0;
            NekDouble DeltaV = 0.0;

            Array<TwoD, std::array<NekDouble, 3>> samplepoints(nSamp, nSamp);

            for (int j = 0; j < nSamp; j++)
            {
                for (int k = 0; k < nSamp; k++)
                {
                    std::array<NekDouble, 2> uv = {k * du + bounds[0],
                                                   j * dv + bounds[2]};
                    if (j == nSamp - 1)
                    {
                        uv[1] = bounds[3];
                    }
                    if (k == nSamp - 1)
                    {
                        uv[0] = bounds[1];
                    }
                    samplepoints[k][j] = surf->P(uv);
                }
            }

            for (int j = 0; j < nSamp - 1; j++)
            {
                for (int k = 0; k < nSamp - 1; k++)
                {
                    NekDouble deltau = sqrt(
                        (samplepoints[k][j][0] - samplepoints[k + 1][j][0]) *
                            (samplepoints[k][j][0] -
                             samplepoints[k + 1][j][0]) +
                        (samplepoints[k][j][1] - samplepoints[k + 1][j][1]) *
                            (samplepoints[k][j][1] -
                             samplepoints[k + 1][j][1]) +
                        (samplepoints[k][j][2] - samplepoints[k + 1][j][2]) *
                            (samplepoints[k][j][2] -
                             samplepoints[k + 1][j][2]));
                    NekDouble deltav = sqrt(
                        (samplepoints[k][j][0] - samplepoints[k][j + 1][0]) *
                            (samplepoints[k][j][0] -
                             samplepoints[k][j + 1][0]) +
                        (samplepoints[k][j][1] - samplepoints[k][j + 1][1]) *
                            (samplepoints[k][j][1] -
                             samplepoints[k][j + 1][1]) +
                        (samplepoints[k][j][2] - samplepoints[k][j + 1][2]) *
                            (samplepoints[k][j][2] -
                             samplepoints[k][j + 1][2]));

                    if (deltau > DeltaU)
                    {
                        DeltaU = deltau;
                    }
                    if (deltav > DeltaV)
                    {
                        DeltaV = deltav;
                    }
                }
            }

            // these are the acutal number of sample points in each parametric
            // direction
            int nu = ceil(DeltaU * nSamp / m_minDelta) * 2;
            int nv = ceil(DeltaV * nSamp / m_minDelta) * 2;
            nu     = max(nSamp, nu);
            nv     = max(nSamp, nv);

            for (int j = 0; j < nu; j++)
            {
                for (int k = 0; k < nv; k++)
                {
                    std::array<NekDouble, 2> uv = {
                        (bounds[1] - bounds[0]) / (nu - 1) * j + bounds[0],
                        (bounds[3] - bounds[2]) / (nv - 1) * k + bounds[2]};

                    // this prevents round off error at the end of the surface
                    // may not be neseercary but works
                    if (j == nu - 1)
                    {
                        uv[0] = bounds[1];
                    }
                    if (k == nv - 1)
                    {
                        uv[1] = bounds[3];
                    }

                    NekDouble C = surf->Curvature(uv);

                    // create new point based on smallest R, flat surfaces have
                    // k=0 but still need a point for element estimation
                    if (C == -1.0)
                    {
                        // Curvature not defined
                        continue;
                    }
                    if (C != 0.0)
                    {
                        NekDouble del =
                            2.0 * (1.0 / C) * sqrt(m_eps * (2.0 - m_eps));

                        if (del > m_maxDelta)
                        {
                            del = m_maxDelta;
                        }
                        if (del < m_minDelta)
                        {
                            del = m_minDelta;
                        }

                        /// TODO Add refinement surface CPoints.

                        CPointSharedPtr newCPoint =
                            MemoryManager<CPoint>::AllocateSharedPtr(
                                surf->GetId(), uv, surf->P(uv), del);

                        m_SPList.push_back(newCPoint);
                    }
                    else
                    {
                        BPointSharedPtr newBPoint =
                            MemoryManager<BPoint>::AllocateSharedPtr(
                                surf->GetId(), uv, surf->P(uv));

                        m_SPList.push_back(newBPoint);
                    }
                }
            }
        }
    }

    m_log(VERBOSE).Newline();

    if (m_refinement.size() > 0)
    {
        m_log(VERBOSE) << "        Modifying based on refinement lines" << endl;

        // now deal with the user defined spacing
        vector<string> lines;

        boost::split(lines, m_refinement, boost::is_any_of(":"));

        for (int i = 0; i < lines.size(); i++)
        {
            vector<NekDouble> data;
            ParseUtils::GenerateVector(lines[i], data);

            std::array<NekDouble, 3> x1, x2;

            x1[0] = data[0];
            x1[1] = data[1];
            x1[2] = data[2];
            x2[0] = data[3];
            x2[1] = data[4];
            x2[2] = data[5];

            m_lsources.push_back(LineSource(x1, x2, data[6], data[7]));

            // add non-boundary source points on the line to inform the octree
            // of additional subdivisions
            NekDouble dx     = x2[0] - x1[0];
            NekDouble dy     = x2[1] - x1[1];
            NekDouble dz     = x2[2] - x1[2];
            NekDouble length = sqrt(dx * dx + dy * dy + dz * dz);
            int num_points   = ceil(length / data[7]) * 2;
            // update dx, dy & dz to now be the increment in each direction.
            dx = dx / (num_points + 1);
            dy = dy / (num_points + 1);
            dz = dz / (num_points + 1);

            for (size_t j = 0; j < num_points; j++)
            {
                std::array<NekDouble, 3> loc = {x1[0] + dx * j, x1[1] + dy * j,
                                                x1[2] + dz * j};
                SrcPointSharedPtr newSPoint =
                    MemoryManager<SrcPoint>::AllocateSharedPtr(loc, data[7]);
                m_SPList.push_back(newSPoint);
            }
        }
    }
}

NekDouble Octree::ddx(OctantSharedPtr i, OctantSharedPtr j)
{
    return fabs(i->GetDelta() - j->GetDelta()) / i->Distance(j);
}
} // namespace Nektar::NekMesh
