/*****************************************************************************
 * $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$
 ****************************************************************************/
// CamiTK includes
#include "ResampleAction.h"
#include <Application.h>
#include <Property.h>

#include <Log.h>

// Vtk includes
// disable warning generated by clang about the surrounded headers
#include <CamiTKDisableWarnings>
#include <vtkImageShiftScale.h>
#include <CamiTKReEnableWarnings>

#include <vtkCallbackCommand.h>
#include <vtkImageResample.h>

using namespace camitk;

// --------------- Constructor -------------------
ResampleAction::ResampleAction(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Resample");
    setDescription("Resamples the input image in two different ways: \
                   <ul> \
                   <li>1. Resamples the scalar type of the voxels unless new scalard type is SAME_AS_INPUT</li>\
                   <li>2. Resamples the size of the voxels</li> \
                   </ul>");
    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    setFamily("Image Processing");
    // Tags allow the user to search the actions through themes
    // You can add tags here with the method addTag("tagName");

    // Setting the actions parameters
    Property* newImageDimensionPropertyX = new Property(tr("New Image X Dimension"), 256, tr("The new image width (in voxels)."), "");
    newImageDimensionPropertyX->setAttribute("minimum", 1);
    newImageDimensionPropertyX->setAttribute("maximum", std::numeric_limits<int>::max());
    newImageDimensionPropertyX->setAttribute("singleStep", 1);
    addParameter(newImageDimensionPropertyX);

    Property* newImageDimensionPropertyY = new Property(tr("New Image Y Dimension"), 256, tr("The new image height (in voxels)."), "");
    newImageDimensionPropertyY->setAttribute("minimum", 1);
    newImageDimensionPropertyY->setAttribute("maximum", std::numeric_limits<int>::max());
    newImageDimensionPropertyY->setAttribute("singleStep", 1);
    addParameter(newImageDimensionPropertyY);

    Property* newImageDimensionPropertyZ = new Property(tr("New Image Z Dimension"), 256, tr("The new image depth (in voxels)."), "");
    newImageDimensionPropertyZ->setAttribute("minimum", 1);
    newImageDimensionPropertyZ->setAttribute("maximum", std::numeric_limits<int>::max());
    newImageDimensionPropertyZ->setAttribute("singleStep", 1);
    addParameter(newImageDimensionPropertyZ);

    Property* scalarTypeProperty = new Property(tr("New Image Scalar Type"), SAME_AS_INPUT, tr("The new image voxels scalar type"), "");
    scalarTypeProperty->setEnumTypeName("ScalarType", this);
    addParameter(scalarTypeProperty);
}

// --------------- destructor -------------------
ResampleAction::~ResampleAction() {

}

// --------------- getWidget -------------------
QWidget* ResampleAction::getWidget() {

    // set min/max for the dimensions using the last selected target dimensions
    int* dims = getTargets().last()->getImageData()->GetDimensions();

    camitk::Property* dimProperty = getProperty("New Image X Dimension");
    dimProperty->setAttribute("minimum", 1);
    dimProperty->setAttribute("maximum", dims[0]);
    setParameterValue("New Image X Dimension", dims[0]);

    dimProperty = getProperty("New Image Y Dimension");
    dimProperty->setAttribute("minimum", 1);
    dimProperty->setAttribute("maximum", dims[1]);
    setParameterValue("New Image Y Dimension", dims[1]);

    dimProperty = getProperty("New Image Z Dimension");
    dimProperty->setAttribute("minimum", 1);
    dimProperty->setAttribute("maximum", dims[2]);
    setParameterValue("New Image Z Dimension", dims[2]);
    dimProperty->setReadOnly(dims[2] == 1);

    // return the default widget
    return Action::getWidget();
}

// --------------- apply -------------------
Action::ApplyStatus ResampleAction::apply() {

    for (Component* comp : getTargets()) {
        ImageComponent* input = dynamic_cast<ImageComponent*>(comp);
        process(input);
    }

    return SUCCESS;
}

// --------------- process -------------------
void ResampleAction::process(ImageComponent* comp) {
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    Application::showStatusBarMessage(tr("Compute resample..."));
    Application::resetProgressBar();
    vtkSmartPointer<vtkCallbackCommand> progressCallback = vtkSmartPointer<vtkCallbackCommand>::New();
    progressCallback->SetCallback(&Application::vtkProgressFunction);

    // Get the parameters
    double xSize = getParameterValue("New Image X Dimension").toDouble();
    double ySize = getParameterValue("New Image Y Dimension").toDouble();
    double zSize = getParameterValue("New Image Z Dimension").toDouble();
    ScalarType userType = (ScalarType) getParameterValue("New Image Scalar Type").toInt();
    QString nameScalarType;

    // Getting the input image
    vtkSmartPointer<vtkImageData> inputImage  = comp->getImageData();

    /* 1. Resamples the Scalar Type thanks to the shiftScaleFilter
       the shiftScaleFilter applies the following formula to each
       Voxel :
                           f(x)=a*(x+b)
       where b is the shift parameter, and a is the scale one.

       All the work is to compute the parameters according to
       the Scalar Type that the user wants.
       */
    vtkSmartPointer<vtkImageShiftScale> shiftScaleFilter;
    if (userType != SAME_AS_INPUT) {
        double* imgRange = inputImage->GetScalarRange();
        double maxType, minType; // extrema values of the output Scalar Type
        double shift, scale;

        shiftScaleFilter = vtkSmartPointer<vtkImageShiftScale>::New();
        shiftScaleFilter->AddObserver(vtkCommand::ProgressEvent, progressCallback);
        shiftScaleFilter->SetInputData(inputImage);

        // Case on the input scalar type : compute the max and min type of the output
        // and also the name of the new image.
        switch (userType) {
            case ResampleAction::UNSIGNED_CHAR:
                shiftScaleFilter->SetOutputScalarTypeToUnsignedChar();
                maxType = 255; // VTK_CHAR_MAX depends on the machine
                minType = 0.0; // same reason
                nameScalarType = "_UChar";
                break;

            case ResampleAction::CHAR:
                shiftScaleFilter->SetOutputScalarTypeToChar();
                maxType = VTK_SIGNED_CHAR_MAX;
                minType = VTK_SIGNED_CHAR_MIN;
                nameScalarType = "_Char";
                break;

            case ResampleAction::UNSIGNED_SHORT:
                shiftScaleFilter->SetOutputScalarTypeToUnsignedShort();
                maxType = VTK_UNSIGNED_SHORT_MAX;
                minType = VTK_UNSIGNED_SHORT_MIN;
                nameScalarType = "_UShort";
                break;

            case ResampleAction::SHORT:
                shiftScaleFilter->SetOutputScalarTypeToShort();
                maxType = VTK_SHORT_MAX;
                minType = VTK_SHORT_MIN;
                nameScalarType = "_Short";
                break;

            case ResampleAction::UNSIGNED_INT:
                shiftScaleFilter->SetOutputScalarTypeToUnsignedInt();
                maxType = VTK_UNSIGNED_INT_MAX;
                minType = VTK_UNSIGNED_INT_MIN;
                nameScalarType = "_UInt";
                break;

            case ResampleAction::INT:
                shiftScaleFilter->SetOutputScalarTypeToInt();
                maxType = VTK_INT_MAX;
                minType = VTK_INT_MIN;
                nameScalarType = "_Int";
                break;

            case ResampleAction::FLOAT:
                shiftScaleFilter->SetOutputScalarTypeToFloat();
                maxType = VTK_FLOAT_MAX;
                minType = VTK_FLOAT_MIN;
                nameScalarType = "_Float";
                break;

            case ResampleAction::DOUBLE:
            default:
                shiftScaleFilter->SetOutputScalarTypeToDouble();
                maxType = VTK_DOUBLE_MAX;
                minType = VTK_DOUBLE_MIN;
                nameScalarType = "_Double";
                break;
        }

        // Apply the shift scale filter
        shift = (minType / (maxType - minType)) * (imgRange[1] - imgRange[0]) - imgRange[0];
        scale = (maxType - minType) / (imgRange[1] - imgRange[0]);
        shiftScaleFilter->SetShift(shift);
        shiftScaleFilter->SetScale(scale);
        shiftScaleFilter->Update();
    }

    /* Resamples the size of the voxels.

        It just has to compute the resize factor that is equal to the
        new size divided by the old size for each axe. */

    vtkSmartPointer <vtkImageResample> resampleFilter = vtkSmartPointer<vtkImageResample>::New();

    resampleFilter->AddObserver(vtkCommand::ProgressEvent, progressCallback);

    int dims[3];
    inputImage->GetDimensions(dims);
    double xFactor = xSize / (double)dims[0];
    double yFactor = ySize / (double)dims[1];
    double zFactor = zSize / (double)dims[2];

    if (userType != SAME_AS_INPUT) {
        resampleFilter->SetInputData(shiftScaleFilter->GetOutput());
    }
    else {
        resampleFilter->SetInputData(inputImage);
    }
    resampleFilter->SetAxisMagnificationFactor(0, xFactor);
    resampleFilter->SetAxisMagnificationFactor(1, yFactor);
    resampleFilter->SetAxisMagnificationFactor(2, zFactor);

    resampleFilter->Update();

    // Create the output component
    ImageComponent* outputComp = new ImageComponent(resampleFilter->GetOutput(), comp->getName() + nameScalarType + "_" + getParameterValue("New Image X Dimension").toString() + "x" + getParameterValue("New Image Y Dimension").toString() + "x" + getParameterValue("New Image Z Dimension").toString());

    // same frame as input
    outputComp->setFrameFrom(comp);

    // refresh restore the normal cursor and progress bar
    refreshApplication();
    Application::resetProgressBar();
    Application::showStatusBarMessage("");
    QApplication::restoreOverrideCursor();

}

