/*****************************************************************************
 * $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 <Component.h>
using namespace camitk;

#include "VtkMeshUtil.h"

// vtk include files
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
// vtk readers and writers
#include <vtkDataReader.h>
#include <vtkUnstructuredGridReader.h>
#include <vtkStructuredGridReader.h>
#include <vtkPolyDataReader.h>
#include <vtkUnstructuredGridWriter.h>
#include <vtkStructuredGridWriter.h>
#include <vtkPolyDataWriter.h>
#include <CamiTKReEnableWarnings>

#include <vtkUnstructuredGrid.h>
#include <vtkStructuredGrid.h>
#include <vtkPolyData.h>
#include <vtkDataSetSurfaceFilter.h>

// -------------------- vtkToGeometry --------------------
Geometry* VtkMeshUtil::vtkToGeometry(std::string vtkFileName) {
    // check the vtk type
    VtkMeshUtil::VtkPointSetType whatIsIt = VtkMeshUtil::typeOf(vtkFileName);

    // name comes from the header
    std::string name = getVtkPointSetHeaderString(vtkFileName, whatIsIt);

    // create the Geometry depending on the type
    vtkSmartPointer<vtkPointSet> ps = buildVtkPointSet(vtkFileName);

    // create the Geometry instance
    Geometry* o;

    if (whatIsIt == VtkMeshUtil::UNKNOWN) {
        o = nullptr;
    }
    else {
        o = new Geometry(name.c_str(), ps, InterfaceGeometry::Surface);
    }

    return o;
}

// -------------------- buildVtkPointSet --------------------
vtkSmartPointer<vtkPointSet> VtkMeshUtil::buildVtkPointSet(std::string vtkFileName, VtkPointSetType whatIsIt) {
    if (whatIsIt == VtkMeshUtil::UNKNOWN) {
        whatIsIt = VtkMeshUtil::typeOf(vtkFileName);
    }

    if (whatIsIt == VtkMeshUtil::UNKNOWN) {
        return nullptr;
    }

    // create the Geometry depending on the type
    vtkSmartPointer<vtkDataReader> vtkPointSetReader;
    switch (whatIsIt) {
        case VtkMeshUtil::UNSTRUCTURED_GRID:
            vtkPointSetReader = vtkSmartPointer<vtkUnstructuredGridReader>::New();
            break;

        case VtkMeshUtil::STRUCTURED_GRID:
            vtkPointSetReader = vtkSmartPointer<vtkStructuredGridReader>::New();
            break;

        case VtkMeshUtil::POLY_DATA:
            vtkPointSetReader = vtkSmartPointer<vtkPolyDataReader>::New();
            break;

        default:
            break;
    }

    vtkPointSetReader->SetFileName(vtkFileName.c_str());

    // read absolutely everything!
    vtkPointSetReader->ReadAllFieldsOn();
    vtkPointSetReader->ReadAllScalarsOn();
    vtkPointSetReader->ReadAllColorScalarsOn();
    vtkPointSetReader->ReadAllNormalsOn();
    vtkPointSetReader->ReadAllTCoordsOn();
    vtkPointSetReader->ReadAllTensorsOn();
    vtkPointSetReader->ReadAllVectorsOn();

    vtkPointSetReader->Update();

    switch (whatIsIt) {
        case VtkMeshUtil::UNSTRUCTURED_GRID:
            return vtkUnstructuredGridReader::SafeDownCast(vtkPointSetReader)->GetOutput();
            break;

        case VtkMeshUtil::STRUCTURED_GRID:
            return vtkStructuredGridReader::SafeDownCast(vtkPointSetReader)->GetOutput();
            break;

        case VtkMeshUtil::POLY_DATA:
            return vtkPolyDataReader::SafeDownCast(vtkPointSetReader)->GetOutput();
            break;

        default:
            return nullptr;
            break;
    }
}

// -------------------- saveGeometryToFile --------------------
bool VtkMeshUtil::saveGeometryToFile(Geometry* o, std::string vtkFileName, std::string oname) {
    return savePointSetToFile(o->getPointSet(), vtkFileName, oname);
}

// -------------------- savePointSetToFile --------------------
bool VtkMeshUtil::savePointSetToFile(vtkSmartPointer<vtkPointSet> ps, std::string fname, std::string oname) {
    VtkPointSetType whatIsIt = typeOf(ps);

    switch (whatIsIt) {
        case VtkMeshUtil::UNSTRUCTURED_GRID:
            saveUnstructuredGridToFile(vtkUnstructuredGrid::SafeDownCast(ps), fname, oname);
            return true;
        case VtkMeshUtil::POLY_DATA:
            savePolyDataToFile(vtkPolyData::SafeDownCast(ps), fname, oname);
            return true;
        case VtkMeshUtil::STRUCTURED_GRID:
            saveStructuredGridToFile(vtkStructuredGrid::SafeDownCast(ps), fname, oname);
            return true;
        default:
            return false; // unknown type
    }
}

// -------------------- saveUnstructuredGridToFile --------------------
void VtkMeshUtil::saveUnstructuredGridToFile(vtkSmartPointer<vtkUnstructuredGrid> uGrid, std::string fname, std::string oname) {
    vtkSmartPointer<vtkUnstructuredGridWriter> dataWriter = vtkSmartPointer<vtkUnstructuredGridWriter>::New();
    dataWriter->SetInputData(uGrid);

    dataWriter->SetFileName(fname.c_str());
    dataWriter->SetHeader(oname.c_str());
    dataWriter->SetFileType(VTK_ASCII);
    dataWriter->Update();
}

// -------------------- savePolyDataToFile --------------------
void VtkMeshUtil::savePolyDataToFile(vtkSmartPointer<vtkPolyData> pData, std::string fname, std::string oname)  {
    vtkSmartPointer<vtkPolyDataWriter> dataWriter = vtkSmartPointer<vtkPolyDataWriter>::New();
    dataWriter->SetInputData(pData);

    dataWriter->SetFileName(fname.c_str());
    dataWriter->SetHeader(oname.c_str());
    dataWriter->SetFileType(VTK_ASCII);
    dataWriter->Update();
}

// -------------------- saveStructuredGridToFile --------------------
void VtkMeshUtil::saveStructuredGridToFile(vtkSmartPointer<vtkStructuredGrid> sGrid, std::string fname, std::string oname) {
    vtkSmartPointer<vtkStructuredGridWriter> dataWriter = vtkSmartPointer<vtkStructuredGridWriter>::New();
    dataWriter->SetInputData(sGrid);

    dataWriter->SetFileName(fname.c_str());
    dataWriter->SetHeader(oname.c_str());
    dataWriter->SetFileType(VTK_ASCII);
    dataWriter->Update();
}

// -------------------- getVtkPointSetHeaderString --------------------
std::string VtkMeshUtil::getVtkPointSetHeaderString(std::string vtkFileName, VtkPointSetType whatIsIt) {
    std::string headerStr;

    if (whatIsIt == VtkMeshUtil::UNKNOWN) {
        // check the vtk type
        whatIsIt = VtkMeshUtil::typeOf(vtkFileName);
    }

    switch (whatIsIt) {
        case VtkMeshUtil::UNSTRUCTURED_GRID: {
            vtkSmartPointer<vtkUnstructuredGridReader> uGridReader = vtkSmartPointer<vtkUnstructuredGridReader>::New();
            uGridReader->SetFileName(vtkFileName.c_str());
            uGridReader->Update();

            headerStr = uGridReader->GetHeader();

            break;
        }
        case VtkMeshUtil::POLY_DATA: {
            vtkSmartPointer<vtkPolyDataReader> polyDataReader = vtkSmartPointer<vtkPolyDataReader>::New();
            polyDataReader->SetFileName(vtkFileName.c_str());
            polyDataReader->Update();

            headerStr = polyDataReader->GetHeader();
            break;
        }
        case VtkMeshUtil::STRUCTURED_GRID: {
            vtkSmartPointer<vtkStructuredGridReader> sGridReader = vtkSmartPointer<vtkStructuredGridReader>::New();
            sGridReader->SetFileName(vtkFileName.c_str());
            sGridReader->Update();

            headerStr = sGridReader->GetHeader();
            break;
        }
        case VtkMeshUtil::UNKNOWN: {
            size_t nbegin = vtkFileName.find_last_of('/');
            size_t nend = vtkFileName.find_last_of('.');
            headerStr = vtkFileName.substr(nbegin + 1, nend - nbegin - 1);
            break;
        }
    }

    return headerStr;
}

// -------------------- typeOf --------------------
VtkMeshUtil::VtkPointSetType VtkMeshUtil::typeOf(std::string vtkFileName) {
    // check the type
    VtkMeshUtil::VtkPointSetType whatIsIt = VtkMeshUtil::UNKNOWN;

    vtkSmartPointer<vtkDataReader> reader = vtkSmartPointer<vtkDataReader>::New();
    reader->SetFileName(vtkFileName.c_str());

    if (reader->IsFileUnstructuredGrid())     {
        whatIsIt = VtkMeshUtil::UNSTRUCTURED_GRID;
    }

    if (reader->IsFileStructuredGrid())     {
        whatIsIt = VtkMeshUtil::STRUCTURED_GRID;
    }

    if (reader->IsFilePolyData())     {
        whatIsIt = VtkMeshUtil::POLY_DATA;
    }

    return whatIsIt;
}

VtkMeshUtil::VtkPointSetType VtkMeshUtil::typeOf(vtkSmartPointer<vtkPointSet> aPointSet) {
    // check the type
    VtkMeshUtil::VtkPointSetType whatIsIt = VtkMeshUtil::UNKNOWN;

    if (aPointSet->IsA("vtkPolyData"))     {
        whatIsIt = VtkMeshUtil::POLY_DATA;
    }
    else if (aPointSet->IsA("vtkUnstructuredGrid"))     {
        whatIsIt = VtkMeshUtil::UNSTRUCTURED_GRID;
    }
    else if (aPointSet->IsA("vtkStructuredGrid"))     {
        whatIsIt = VtkMeshUtil::STRUCTURED_GRID;
    }

    return whatIsIt;
}

// -------------------- vtkPointSetToVtkPolyData --------------------
vtkSmartPointer< vtkPolyData > VtkMeshUtil::vtkPointSetToVtkPolyData(vtkSmartPointer< vtkPointSet > aPointSet) {
    vtkSmartPointer< vtkPolyData > result;

    VtkPointSetType whatIsIt = typeOf(aPointSet);

    if (whatIsIt == VtkMeshUtil::POLY_DATA) {
        result = vtkPolyData::SafeDownCast(aPointSet);
    }
    else {
        vtkSmartPointer<vtkDataSetSurfaceFilter> surfaceFilter = vtkSmartPointer<vtkDataSetSurfaceFilter>::New();
        surfaceFilter->SetInputData(aPointSet);
        surfaceFilter->Update();

        result = surfaceFilter->GetOutput();
    }
    return result;
}

