/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * 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>

// Vtk includes
#include <vtkCallbackCommand.h>
#include "vtkImageShiftScale.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</li>\
                   <li>2. Resamples the size of the voxels</li> \
                   </ul>");
    setComponent("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("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("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("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() {

}

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

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

    return SUCCESS;
}

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 = property("New image X dimension").toDouble();
    double ySize = property("New image Y dimension").toDouble();
    double zSize = property("New image Z dimension").toDouble();

    // Getting the input image
    vtkSmartPointer<vtkImageData> inputImage  = comp->getImageData();
    int dims[3];
    inputImage->GetDimensions(dims);


    /* 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.
       */

    double* imgRange = inputImage->GetScalarRange();
    double maxType, minType; // extrema values of the output Scalar Type
    double shift, scale;

    QString nameScalarType;

    vtkSmartPointer<vtkImageShiftScale> 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 (ResampleAction::getScalarType()) {
        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";
        case ResampleAction::DOUBLE:
            shiftScaleFilter->SetOutputScalarTypeToDouble();
            maxType = VTK_DOUBLE_MAX;
            minType = VTK_DOUBLE_MIN;
            nameScalarType = "_Double";
        default:
            break;
    }

    // Apply the shift scale filter
    if (ResampleAction::getScalarType() != SAME_AS_INPUT) {
        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);

    double xFactor = xSize / (double)dims[0];
    double yFactor = ySize / (double)dims[1];
    double zFactor = zSize / (double)dims[2];

    resampleFilter->SetInputData(shiftScaleFilter->GetOutput());
    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 + "_" + property("Size X (nb of Voxels)").toInt() + "*" + property("Size Y (nb of Voxels)").toInt() + "*" + property("Size Z (nb of Voxels)").toInt());

    // consider frame policy on new image created
    Action::applyTargetPosition(comp, outputComp);

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

}

ResampleAction::ScalarType ResampleAction::getScalarType() {
    return (ScalarType) property("New image scalar type").toInt();
}

