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

namespace camitk {

// -------------------- Transformation constructors --------------------
Transformation::Transformation(const std::shared_ptr<FrameOfReference>& from, const std::shared_ptr<FrameOfReference>& to, QUuid id): from(from), to(to), uuid(id), transform(vtkSmartPointer<vtkTransform>::New()) {
    transform->Identity();
    setName(from->getName() + " → " + to->getName());
};

Transformation::Transformation(const std::shared_ptr<FrameOfReference>& from, const std::shared_ptr<FrameOfReference>& to, vtkSmartPointer<vtkTransform> tr, QUuid id): from(from), to(to), uuid(id), transform(tr) {
    setName(from->getName() + " → " + to->getName());
}

// -------------------- getInverse --------------------
Transformation* Transformation::getInverse() {
    if (transform != nullptr) {
        return new Transformation(to, from, vtkTransform::SafeDownCast(transform->GetInverse()));
    }
    else {
        return nullptr;
    }
}

// -------------------- getName --------------------
QString Transformation::getName() const {
    return name;
}


// -------------------- setTransform --------------------
void Transformation::setTransform(vtkSmartPointer<vtkTransform> tr) {
    if (transform == nullptr) {
        transform = vtkSmartPointer<vtkTransform>::New();
    }
    // update the matrix
    transform->SetMatrix(tr->GetMatrix());
}

// -------------------- setMatrix --------------------
void Transformation::setMatrix(vtkSmartPointer<vtkMatrix4x4> m) {
    if (transform == nullptr) {
        transform = vtkSmartPointer<vtkTransform>::New();
    }
    transform->SetMatrix(m);
}



// -------------------- getMatrix --------------------
vtkMatrix4x4* Transformation::getMatrix() const {
    if (transform != nullptr) {
        return transform->GetMatrix();
    }
    else {
        return nullptr;
    }
}

// -------------------- setUuid --------------------
bool Transformation::setUuid(QUuid id) {
    if (uuid.isNull()) {
        uuid  = id;
        return true;
    }
    else {
        return false;
    }
}

// -------------------- toVariant --------------------
QVariant Transformation::toVariant() const {
    QVariantMap data = {
        {"uuid", uuid},
        {"name", name},
        {"description", description},
        {"from", from == nullptr ? QUuid() : from->getUuid()},
        {"to", to == nullptr ? QUuid() : to->getUuid()}
    };
    data.insert("type", "linear");
    auto matrix = transform->GetMatrix();
    data.insert("matrix", QList<QVariant> {
        QList<QVariant>{matrix->GetElement(0, 0), matrix->GetElement(0, 1), matrix->GetElement(0, 2), matrix->GetElement(0, 3)},
        QList<QVariant>{matrix->GetElement(1, 0), matrix->GetElement(1, 1), matrix->GetElement(1, 2), matrix->GetElement(1, 3)},
        QList<QVariant>{matrix->GetElement(2, 0), matrix->GetElement(2, 1), matrix->GetElement(2, 2), matrix->GetElement(2, 3)},
        QList<QVariant>{matrix->GetElement(3, 0), matrix->GetElement(3, 1), matrix->GetElement(3, 2), matrix->GetElement(3, 3)}
    });
    return data;
}

// -------------------- fromVariant --------------------
void Transformation::fromVariant(const QVariant& v) {
    QVariantMap data = v.toMap();
    uuid = data.value("uuid").toUuid();
    name = data.value("name").toString();
    description = data.value("description").toString();
    // WARNING: from and to are saved as uuid, it is up to the caller
    // to set the from and to pointers to the correct Frames
    // (e.g. in TransformationManager which has the UUID->FrameOfReference* table)

    if (data.value("type").toString() == "linear") {
        auto trMatrix = vtkMatrix4x4::New();
        trMatrix->Identity();
        QVariantList matrixRows = data.value("matrix").toList();
        if (matrixRows.size() == 4) {
            for (int i = 0; i < 4; ++i) {
                QVariantList matrixRow = matrixRows[i].toList();
                if (matrixRow.size() == 4) {
                    for (int j = 0; j < 4; ++j) {
                        trMatrix->SetElement(i, j, matrixRow[j].toDouble());
                    }
                }
                else {
                    CAMITK_WARNING_ALT("Invalid matrix does not have 4 columns: " + name)
                }
            }

        }
        else {
            CAMITK_WARNING_ALT("Invalid matrix does not have 4 rows: " + name)
        }

        setMatrix(trMatrix);

    }
    else {
        CAMITK_INFO_ALT("Nonlinear transformations are not supported")
    }
}

} // namespace
