/*****************************************************************************
 * $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 "VtkImageComponent.h"
#include <ImageComponent.h>
#include <Log.h>

//-- Qt
#include <QFileInfo>
#include <QRegularExpression>
#include <QTextStream>

//-- Vtk
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
// vtk image readers
#include <vtkImageReader2.h>
#include <vtkJPEGReader.h>
#include <vtkPNGReader.h>
#include <vtkTIFFReader.h>
#include <vtkBMPReader.h>
#include <vtkPNMReader.h>
#include <vtkMetaImageReader.h>
#include <CamiTKReEnableWarnings>

#include <vtkImageData.h>
#include <vtkImageFlip.h>
#include <vtkInformation.h>

using namespace camitk;

// -------------------- constructor --------------------
VtkImageComponent::VtkImageComponent(const QString& fileName)
    : ImageComponent(fileName, QFileInfo(fileName).baseName()) {
    readImageData(fileName);
}

// -------------------- readImageData --------------------
void VtkImageComponent::readImageData(const QString& filename) {
    if (!filename.isNull()) {
        try {
            // Reader and image variables
            vtkSmartPointer<vtkImageReader2> reader;
            vtkSmartPointer<vtkImageData> image = nullptr;

            // filename extension
            QString fileExt = QFileInfo(filename).suffix();

            // Reader initialization, depending on file extension
            if (QString::compare(fileExt, "jpg", Qt::CaseInsensitive) == 0) {
                reader = vtkSmartPointer<vtkJPEGReader>::New();
            }
            else if (QString::compare(fileExt, "png", Qt::CaseInsensitive) == 0) {
                reader = vtkSmartPointer<vtkPNGReader>::New();
            }
            else if ((QString::compare(fileExt, "tiff", Qt::CaseInsensitive) == 0) ||
                     (QString::compare(fileExt, "tif",  Qt::CaseInsensitive) == 0)) {
                reader = vtkSmartPointer<vtkTIFFReader>::New();
            }
            else if (QString::compare(fileExt, "bmp", Qt::CaseInsensitive) == 0) {
                reader = vtkSmartPointer<vtkBMPReader>::New();
            }
            else if ((QString::compare(fileExt, "pbm", Qt::CaseInsensitive) == 0) ||
                     (QString::compare(fileExt, "pgm", Qt::CaseInsensitive) == 0) ||
                     (QString::compare(fileExt, "ppm", Qt::CaseInsensitive) == 0)) {
                reader = vtkSmartPointer<vtkPNMReader>::New();
            }
            else if ((QString::compare(fileExt, "mhd", Qt::CaseInsensitive) == 0) ||
                     (QString::compare(fileExt, "mha", Qt::CaseInsensitive) == 0)) {
                reader = vtkSmartPointer<vtkMetaImageReader>::New();
            }
            else {
                throw AbortException("File format " + fileExt.toStdString() + " not supported yet.");
            }

            std::string stdFilename = QFileInfo(filename).absoluteFilePath().toStdString();
            if (reader) {
                reader->SetFileName(stdFilename.c_str());
                try {
                    reader->Update();
                }
                catch (...) {
                    throw AbortException("VTK was unable to read file " + stdFilename);
                }
                image = reader->GetOutput();
            }

            // Get the image orientation & rotation when possible
            ImageOrientationHelper::PossibleImageOrientations orientation = ImageOrientationHelper::RAI;
            vtkSmartPointer<vtkMatrix4x4> rotationMatrix;
            if (vtkSmartPointer<vtkMetaImageReader> metaImageReader = vtkMetaImageReader::SafeDownCast(reader)) {
                orientation = ImageOrientationHelper::getOrientationAsEnum(QString(metaImageReader->GetAnatomicalOrientation()));
                rotationMatrix = readMetaImageTransformMatrix(filename);
            }

            // set the image data, orientation and rotation matrix
            // note : the orientation is always displayed using RAI inside CamiTK
            // therefore the transformation from orientation to RAI is applied in setImageData
            // Inside the ImageComponent, initialImageDataTransform stores the transformation : [ translation ] * [ orientation -> RAI ]
            setImageData(image, false, orientation, rotationMatrix);

            image = nullptr;
            reader = nullptr;
        }

        catch (const AbortException& e) {
            throw (e);
        }
    }
}

// -------------------- readMetaImageTransformMatrix --------------------
vtkSmartPointer<vtkMatrix4x4> VtkImageComponent::readMetaImageTransformMatrix(const QString& fileName) {

    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        CAMITK_ERROR(tr("Cannot open file \"%1\" to read TransformMatrix tag from image").arg(fileName))
        return nullptr;
    }

    QRegularExpression regExp = QRegularExpression("TransformMatrix = ([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?[ |\\n])+");
    QTextStream in(&file);
    QString line = in.readLine();
    while (!line.isNull()) {
        // Find lind feature TransformMatrix tag information
        if (line.contains(regExp)) { // found it !
            // retrieve information
            QStringList values = line.split(" ");
            int nDim;

            if (values.size() >= 9 + 2) {
                // if the read matrix is 9 elements ( + "TransformMatrix" + "=") or more
                // manage it as a 3D matrix
                nDim = 3;
            }
            else {
                // else manage it as a 2D matrix
                nDim = 2;
            }

            // According to https://itk.org/Wiki/MetaIO/Documentation#Associated_transformations
            // "matrix that is serialized in a column-major format" -> fill column by column
            vtkSmartPointer<vtkMatrix4x4> rotationMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
            for (int j = 0; j < nDim; j++) { // lines
                for (int i = 0; i < nDim; i++) { // columns
                    rotationMatrix->SetElement(i, j, values.at(j * nDim + i + 2).toDouble());
                }
            }
            file.close();

            return rotationMatrix;
        }
        line = in.readLine();
    }

    file.close(); // close the file handle.
    return nullptr;
}
