/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "GridTopology.h"
#include <MeshComponent.h>
#include <Property.h>
#include <Application.h>
#include <Log.h>

using namespace camitk;

#include <vtkCell.h>
#include <vtkPoints.h>
#include <vtkSmartPointer.h>
#include <vtkHexahedron.h>
#include <vtkQuad.h>
#include <vtkLine.h>
#include <vtkCellArray.h>
#include <vtkTriangleFilter.h>
#include <vtkDataSetTriangleFilter.h>

GridTopology::GridTopology(ActionExtension* extension) : Action(extension) {
    setName("Grid Topology");
    setDescription("Build a regular grid");
    setComponentClassName("");    // empty component: it does not need any data to be triggered
    setFamily("Mesh Processing");
    addTag("Build Grid");

    addParameter(new Property("Simplex", false, tr("Create a simplex mesh from the generated grid."), ""));
    addParameter(new Property("Network", false, tr("Link all generated nodes to form a regular network."), ""));
    addParameter(new Property("Dimension X", 1, tr("Number of nodes in the X direction (enter 0 for YZ 2D grid)."), ""));
    addParameter(new Property("Dimension Y", 1, tr("Number of nodes in the Y direction (enter 0 for XZ 2D grid)."), ""));
    addParameter(new Property("Dimension Z", 1, tr("Number of nodes in the Z direction (enter 0 for XY 2D grid)."), ""));
    addParameter(new Property("Position X", 0., tr("X coordinate of the grid origin."), ""));
    addParameter(new Property("Position Y", 0., tr("Y coordinate of the grid origin."), ""));
    addParameter(new Property("Position Z", 0., tr("Z coordinate of the grid origin."), ""));
    addParameter(new Property("Width", 1., tr("Grid size in the X direction (node spacing in the X direction will be <tt>Width / Dimension X</tt>)."), ""));
    addParameter(new Property("Height", 1., tr("Grid size in the Y direction (node spacing in the Y direction will be <tt>Height / Dimension Y</tt>)."), ""));
    addParameter(new Property("Depth", 1., tr("Grid size in the Z direction (node spacing in the Z direction will be <tt>Depth / Dimension Z</tt>)."), ""));
}

Action::ApplyStatus GridTopology::apply() {
    int dimX = property("Dimension X").toInt();
    int dimY = property("Dimension Y").toInt();
    int dimZ = property("Dimension Z").toInt();
    double posX = property("Position X").toDouble();
    double posY = property("Position Y").toDouble();
    double posZ = property("Position Z").toDouble();
    double width = property("Width").toDouble();
    double height = property("Height").toDouble();
    double depth = property("Depth").toDouble();
    double stepX = (dimX > 0) ? (width / dimX) : 0;
    double stepY = (dimY > 0) ? (height / dimY) : 0;
    double stepZ = (dimZ > 0) ? (depth / dimZ) : 0;

    // all the dimension are 0
    if (dimX == 0 && dimY == 0 && dimZ == 0) {
        CAMITK_ERROR(tr("All dimensions set to 0"))
        return ERROR;
    }

    // create points
    double pt[3];
    vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();

    for (int i = 0; i <= dimX; i++)
        for (int j = 0; j <= dimY; j++)
            for (int k = 0; k <= dimZ; k++) {
                pt[0] = posX + i * stepX;
                pt[1] = posY + j * stepY;
                pt[2] = posZ + k * stepZ;
                points->InsertNextPoint(pt);
            }

    vtkSmartPointer<vtkUnstructuredGrid> grid = vtkSmartPointer<vtkUnstructuredGrid>::New();
    grid->Allocate();
    grid->SetPoints(points);

    // build grid
    if (dimX && dimY && dimZ) {
        if (property("Network").toBool()) {
            buildNetwork(grid);
        }
        else {
            build3DGrid(grid);
        }
    }
    else if ((dimX && dimY) || (dimY && dimZ) || (dimX && dimZ)) {
        build2DGrid(grid);
    }
    else {
        build1DGrid(grid);
    }

    if (property("Simplex").toBool()) {
        vtkSmartPointer<vtkDataSetTriangleFilter> simplexFilter = vtkSmartPointer<vtkDataSetTriangleFilter>::New();
        simplexFilter->SetInputData(grid);
        simplexFilter->Update();
        grid = simplexFilter->GetOutput();
    }

    new MeshComponent(grid, "Grid_" + QString::number(dimX) + "x" + QString::number(dimY) + "x" + QString::number(dimZ));

    refreshApplication();
    Application::resetProgressBar();
    Application::showStatusBarMessage("");

    return SUCCESS;
}

void GridTopology::build3DGrid(vtkSmartPointer<vtkUnstructuredGrid> grid) {
    vtkSmartPointer<vtkCellArray> elements = vtkSmartPointer<vtkCellArray>::New();
    vtkSmartPointer<vtkHexahedron> element = vtkSmartPointer<vtkHexahedron>::New();

    int dimX = property("Dimension X").toInt();
    int dimY = property("Dimension Y").toInt();
    int dimZ = property("Dimension Z").toInt();

    for (int i = 1; i <= dimX; i++)
        for (int j = 1; j <= dimY; j++)
            for (int k = 1; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, (((i - 1) * (dimY + 1)) + j - 1) * (dimZ + 1) + k - 1);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + (j - 1)) * (dimZ + 1) + k - 1);
                element->GetPointIds()->SetId(2, ((i * (dimY + 1)) + j) * (dimZ + 1) + k - 1);
                element->GetPointIds()->SetId(3, (((i - 1) * (dimY + 1)) + j) * (dimZ + 1) + k - 1);
                element->GetPointIds()->SetId(4, (((i - 1) * (dimY + 1)) + j - 1) * (dimZ + 1) + k);
                element->GetPointIds()->SetId(5, ((i * (dimY + 1)) + (j - 1)) * (dimZ + 1) + k);
                element->GetPointIds()->SetId(6, ((i * (dimY + 1)) + j) * (dimZ + 1) + k);
                element->GetPointIds()->SetId(7, (((i - 1) * (dimY + 1)) + j) * (dimZ + 1) + k);

                elements->InsertNextCell(element);
            }

    grid->SetCells(VTK_HEXAHEDRON, elements);
}

void GridTopology::build1DGrid(vtkSmartPointer< vtkUnstructuredGrid > grid) {
    // TODO
}


void GridTopology::build2DGrid(vtkSmartPointer< vtkUnstructuredGrid > grid) {
    vtkSmartPointer<vtkCellArray> elements = vtkSmartPointer<vtkCellArray>::New();
    vtkSmartPointer<vtkQuad> element = vtkSmartPointer<vtkQuad>::New();

    int dimX = property("Dimension X").toInt();
    int dimY = property("Dimension Y").toInt();
    int dimZ = property("Dimension Z").toInt();
    int dim1, dim2;

    if (dimX > 0) {
        dim1 = dimX;
        if (dimY > 0) {
            dim2 = dimY;
        }
        else {
            dim2 = dimZ;
        }
    }
    else {
        dim1 = dimY;
        dim2 = dimZ;
    }

    for (int i = 1; i <= dim1; i++)
        for (int j = 1; j <= dim2; j++) {
            element->GetPointIds()->SetId(0, ((i - 1) * (dim2 + 1)) + j - 1);
            element->GetPointIds()->SetId(1, (i * (dim2 + 1)) + (j - 1));
            element->GetPointIds()->SetId(2, (i * (dim2 + 1)) + j);
            element->GetPointIds()->SetId(3, ((i - 1) * (dim2 + 1)) + j);

            elements->InsertNextCell(element);
        }

    grid->SetCells(VTK_QUAD, elements);
}

void GridTopology::buildNetwork(vtkSmartPointer<vtkUnstructuredGrid> grid) {
    vtkSmartPointer<vtkCellArray> elements = vtkSmartPointer<vtkCellArray>::New();
    vtkSmartPointer<vtkLine> element = vtkSmartPointer<vtkLine>::New();

    int dimX = property("Dimension X").toInt();
    int dimY = property("Dimension Y").toInt();
    int dimZ = property("Dimension Z").toInt();

    for (int i = 1; i <= dimX; i++)
        for (int j = 0; j <= dimY; j++)
            for (int k = 0; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, (((i - 1) * (dimY + 1)) + j) * (dimZ + 1) + k);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + j) * (dimZ + 1) + k);
                elements->InsertNextCell(element);
            }

    for (int i = 0; i <= dimX; i++)
        for (int j = 1; j <= dimY; j++)
            for (int k = 0; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, ((i * (dimY + 1)) + j - 1) * (dimZ + 1) + k);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + j) * (dimZ + 1) + k);
                elements->InsertNextCell(element);
            }

    for (int i = 0; i <= dimX; i++)
        for (int j = 0; j <= dimY; j++)
            for (int k = 1; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, ((i * (dimY + 1)) + j) * (dimZ + 1) + k - 1);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + j) * (dimZ + 1) + k);
                elements->InsertNextCell(element);
            }

    for (int i = 1; i <= dimX; i++)
        for (int j = 1; j <= dimY; j++)
            for (int k = 1; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, (((i - 1) * (dimY + 1)) + j - 1) * (dimZ + 1) + k - 1);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + j) * (dimZ + 1) + k);
                elements->InsertNextCell(element);
            }

    for (int i = 1; i <= dimX; i++)
        for (int j = 1; j <= dimY; j++)
            for (int k = 1; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, (((i - 1) * (dimY + 1)) + j) * (dimZ + 1) + k);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + j - 1) * (dimZ + 1) + k - 1);
                elements->InsertNextCell(element);
            }

    for (int i = 1; i <= dimX; i++)
        for (int j = 1; j <= dimY; j++)
            for (int k = 1; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, (((i - 1) * (dimY + 1)) + j - 1) * (dimZ + 1) + k);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + j) * (dimZ + 1) + k - 1);
                elements->InsertNextCell(element);
            }

    for (int i = 1; i <= dimX; i++)
        for (int j = 1; j <= dimY; j++)
            for (int k = 1; k <= dimZ; k++) {
                element->GetPointIds()->SetId(0, (((i - 1) * (dimY + 1)) + j) * (dimZ + 1) + k - 1);
                element->GetPointIds()->SetId(1, ((i * (dimY + 1)) + j - 1) * (dimZ + 1) + k);
                elements->InsertNextCell(element);
            }

    grid->SetCells(VTK_LINE, elements);
}
