/*****************************************************************************
 * $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 "PixelColorChanger.h"

#include <Application.h>
#include <InteractiveSliceViewer.h>
#include <Property.h>
#include <Log.h>

using namespace camitk;


// --------------- constructor -------------------
PixelColorChanger::PixelColorChanger(ActionExtension* extension) : Action(extension) {
    // Setting name, description and input component
    setName("Pixel Color Changer");

    setDescription(tr("<br>Sets clicked XxYxZ cube of voxels (by CTRL + Mouse Left) to a new defined color value.  <ul> \
                   <li>A size of 1 (X, Y or Z) corresponds to one pixel in the considered slice.</li>\
                   <li>A size of 2 corresponds to 3 pixels in the slice, in the aim to always keep the picked pixel as the center.  <br />(Size 3-> 5pix; 4->7pix...)</li></ul>\
<br /> Compatible with this pixel types formats :[unsigned] short / float / double / char"));

    setComponentClassName("ImageComponent");

    // Setting classification family and tags
    this->setFamily("Image Processing");
    this->addTag(tr("Color Changer"));
    this->addTag(tr("Picked Pixel"));

    // Setting properties
    Property* newColorProperty = new Property(tr("New color"), 255, tr("The new color of the voxel(s) picked."), "");
    newColorProperty->setAttribute("minimum", 0);
    newColorProperty->setAttribute("maximum", 255);
    newColorProperty->setAttribute("singleStep", 1);
    addParameter(newColorProperty);

    Property* cubeSizeX = new Property(tr("Cube picking size in X"), 1, tr("The cube picking width = 2xn + 1 where n is this property."), "");
    cubeSizeX->setAttribute("minimum", 1);
    cubeSizeX->setAttribute("singleStep", 1);
    addParameter(cubeSizeX);

    Property* cubeSizeY = new Property(tr("Cube picking size in Y"), 1, tr("The cube picking height = 2xn + 1 where n is this property."), "");
    cubeSizeY->setAttribute("minimum", 1);
    cubeSizeY->setAttribute("singleStep", 1);
    addParameter(cubeSizeY);

    Property* cubeSizeZ = new Property(tr("Cube picking size in Z"), 1, tr("The cube picking depth = 2xn + 1 where n is this property."), "");
    cubeSizeZ->setAttribute("minimum", 1);
    cubeSizeZ->setAttribute("singleStep", 1);
    addParameter(cubeSizeZ);

    setAutoUpdateProperties(true);
    isConnected = false;
}

// --------------- destructor -------------------
PixelColorChanger::~PixelColorChanger() {
    // do not delete the widget has it might have been used in the ActionViewer (i.e. the ownership might have been taken by the stacked widget)
}

// --------------- getWidget -------------------
QWidget* PixelColorChanger::getWidget() {
    connectViewerInteraction();
    return Action::getWidget();
}

// --------------- apply -------------------
Action::ApplyStatus PixelColorChanger::apply() {
    // check if widget is still visible, otherwise disconnect action
    if (this->getWidget()->isVisible()) {
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
        // Get the image
        ImageComponent* input = dynamic_cast<ImageComponent*>(getTargets().last());

        if (input->getImageData()->GetNumberOfScalarComponents() != 1) {
            disconnectViewerInteraction();
            // restore the normal cursor
            QApplication::restoreOverrideCursor();

            CAMITK_WARNING("This action does not support color images. Aborting")
            return ABORTED;
        }

        // get the last picked pixel index
        int i, j, k = -1;
        input->getLastPixelPicked(&i, &j, &k);

        // Get the parameters, which represents a cube of dimensions (2 * size_i + 1) * (2 * size_j + 1) * (2 * size_k + 1)
        sizei = getParameterValue("Cube picking size in X").toInt();
        sizej = getParameterValue("Cube picking size in Y").toInt();
        sizek = getParameterValue("Cube picking size in Z").toInt();

        // check validity
        if (i != -1 && j != -1 && k != -1 && sizei > 0 && sizej > 0 && sizek > 0) {
            // retrieve information
            newColorValue = getParameterValue("New color").toInt();
            //get dims of image
            int* ext = input->getImageData()->GetExtent();

            for (int i1 =  i - (sizei - 1); i1 < i + sizei; i1++) {
                for (int j1 = j - (sizej - 1); j1 < j + sizej; j1++) {
                    for (int k1 = k - (sizek - 1); k1 < k + sizek; k1++) {

                        if (i1 > ext[0] && i1 < ext[1] && j1 > ext[2] && j1 < ext[3] && k1 > ext[4] && k1 < ext[5]) {

                            switch (input->getImageData()->GetScalarType()) {
                                case VTK_CHAR: {
                                    // get pixel value and modify it
                                    char* pixel = (char*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (char) newColorValue;
                                }
                                break;

                                case VTK_SIGNED_CHAR: {
                                    // get pixel value and modify it
                                    signed char* pixel = (signed char*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (signed char) newColorValue;
                                }
                                break;

                                case VTK_UNSIGNED_CHAR: {
                                    // get pixel value and modify it
                                    unsigned char* pixel = (unsigned char*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (unsigned char) newColorValue;
                                }
                                break;

                                case VTK_SHORT: {
                                    // get pixel value and modify it
                                    short* pixel = (short*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (short) newColorValue;
                                }
                                break;

                                case VTK_UNSIGNED_SHORT: {
                                    // get pixel value and modify it
                                    unsigned short* pixel = (unsigned short*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (unsigned short) newColorValue;
                                }
                                break;

                                case VTK_INT: {
                                    // get pixel value and modify it
                                    int* pixel = (int*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (int) newColorValue;
                                }
                                break;

                                case VTK_UNSIGNED_INT: {
                                    // get pixel value and modify it
                                    unsigned int* pixel = (unsigned int*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (unsigned int) newColorValue;
                                }
                                break;

                                case VTK_LONG: {
                                    // get pixel value and modify it
                                    long* pixel = (long*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (long) newColorValue;
                                }
                                break;

                                case VTK_UNSIGNED_LONG: {
                                    // get pixel value and modify it
                                    unsigned long* pixel = (unsigned long*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (unsigned long) newColorValue;
                                }
                                break;

                                case VTK_FLOAT: {
                                    // get pixel value and modify it
                                    float* pixel = (float*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (float) newColorValue;
                                }
                                break;

                                case VTK_DOUBLE: {
                                    // get pixel value and modify it
                                    double* pixel = (double*) input->getImageData()->GetScalarPointer(i1, j1, k1);
                                    *pixel = (double) newColorValue;
                                }
                                break;

                                default:
                                    break;
                            }

                        }
                    }
                }
            }
        }

        // Refresh LUT if existing, and then force image to be refreshed
        if (input->getLut()) {
            input->getLut()->Modified();
            input->getLut()->Build();
        }
        refreshApplication();

        // restore the normal cursor
        QApplication::restoreOverrideCursor();
    }
    else {
        disconnectViewerInteraction();
    }

    return SUCCESS;
}

// --------------- connectViewerInteraction -------------------
void PixelColorChanger::connectViewerInteraction() {
    if (!isConnected) {
        if (Application::getViewer("Axial Viewer") != nullptr) {
            //-- run the action every time a picking is done in the axial/sagittal or coronal planes
            QObject::connect(Application::getViewer("Axial Viewer"), SIGNAL(selectionChanged()), this, SLOT(apply()));
            QObject::connect(Application::getViewer("Coronal Viewer"), SIGNAL(selectionChanged()), this, SLOT(apply()));
            QObject::connect(Application::getViewer("Sagittal Viewer"), SIGNAL(selectionChanged()), this, SLOT(apply()));
            isConnected = true;
        }
    }
}

// --------------- disconnectViewerInteraction -------------------
void PixelColorChanger::disconnectViewerInteraction() {
    if (isConnected) {
        if (Application::getViewer("Axial Viewer") != nullptr) {
            // disconnect
            QObject::disconnect(Application::getViewer("Axial Viewer"), SIGNAL(selectionChanged()), this, SLOT(apply()));
            QObject::disconnect(Application::getViewer("Coronal Viewer"), SIGNAL(selectionChanged()), this, SLOT(apply()));
            QObject::disconnect(Application::getViewer("Sagittal Viewer"), SIGNAL(selectionChanged()), this, SLOT(apply()));
            isConnected = false;
        }
    }
}
