////////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2026 OVITO GmbH, Germany
//
//  This file is part of OVITO (Open Visualization Tool).
//
//  OVITO is free software; you can redistribute it and/or modify it either under the
//  terms of the GNU General Public License version 3 as published by the Free Software
//  Foundation (the "GPL") or, at your option, under the terms of the MIT License.
//  If you do not alter this notice, a recipient may use your version of this
//  file under either the GPL or the MIT License.
//
//  You should have received a copy of the GPL along with this program in a
//  file LICENSE.GPL.txt.  You should have received a copy of the MIT License along
//  with this program in a file LICENSE.MIT.txt
//
//  This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
//  either express or implied. See the GPL or the MIT License for the specific language
//  governing rights and limitations.
//
////////////////////////////////////////////////////////////////////////////////////////

/**
 * \file
 * \brief Contains the definition of the Ovito::ScalingT class template.
 */

#pragma once


#include <ovito/core/Core.h>
#include <ovito/core/utilities/io/SaveStream.h>
#include <ovito/core/utilities/io/LoadStream.h>
#include "Vector3.h"
#include "Quaternion.h"

namespace Ovito {

/**
 * \brief A transformation that describes a non-uniform scaling in an arbitrary axis system.
 *
 * The Vector3 #S specifies the scaling factors along the x, y, and z axes, and the quaternion #Q
 * defines the axis system in which the scaling is applied.
 */
template<typename T>
class ScalingT
{
public:

    struct Identity {};

public:

    /// \brief The scaling factors in x, y and z directions of the axis system specified by Q.
    Vector_3<T> S;

    /// \brief The orientation of the axis system the scaling is applied in.
    QuaternionT<T> Q;

    /////////////////////////////// Constructors /////////////////////////////////

    /// \brief Constructs a Scaling object without initializing its components.
    /// \note The components are left uninitialized by this constructor and will therefore have a random value!
    ScalingT() = default;

    /// \brief Initializes a Scaling with the scaling factors and the coordinate system.
    /// \param scaling The scaling factors in x, y and z directions of the axis system specified by \a orientation.
    /// \param orientation The orientation of the axis system the scaling is applied in.
    constexpr ScalingT(const Vector_3<T>& scaling, const QuaternionT<T>& orientation) : S(scaling), Q(orientation) {}

    /// \brief Initializes the object to the identity.
    /// The Scaling structure is initialized with the scaling factors (1,1,1), i.e. no scaling at all.
    constexpr ScalingT(Identity) : S(T(1)), Q(typename QuaternionT<T>::Identity()) {}

    /////////////////////////////// Unary operators //////////////////////////////

    /// \brief Returns the inverse of this scaling.
    /// \return The inverse scaling that exactly compensates this scaling.
    ScalingT inverse() const {
        OVITO_ASSERT_MSG(S != typename Vector_3<T>::Zero(), "Scaling::inverse()", "Cannot invert a singular scaling value.");
        return { Vector_3<T>(T(1) / S.x(), T(1) / S.y(), T(1) / S.z()), Q.inverse().normalized() };
    }

    /////////////////////////////// Binary operators /////////////////////////////

    /// \brief Performs the multiplication of two scaling structures.
    /// \param s2 The second scaling.
    /// \return A scaling structure that is equal to first applying scaling \a s2 and then \c this scaling.
    ScalingT operator*(const ScalingT& s2) const {
        if(Q == s2.Q) {
            return ScalingT(Vector_3<T>(S.x() * s2.S.x(), S.y() * s2.S.y(), S.z() * s2.S.z()), Q);
        }
        else {
            //AffineDecomposition decomp(AffineTransformation::scaling(*this) * AffineTransformation::scaling(s2));
            //return decomp.scaling;
            OVITO_ASSERT_MSG(false, "Scaling product", "Product of two Scaling values is not implemented yet.");
            return ScalingT(Identity());
        }
    }

    /// \brief Adds the given scaling to this scaling.
    /// \param s2 The scaling to add to this scaling.
    /// \return This resulting scaling which is equal to \c s2*(*this).
    ScalingT& operator+=(const ScalingT& s2) { *this = s2 * (*this); return *this; }

    /// \brief Adds the inverse of another scaling to this scaling.
    /// \param s2 The scaling to subtract from this scaling.
    /// \return This resulting scaling which is equal to \c (*this)*s2.inverse().
    ScalingT& operator-=(const ScalingT& s2) { *this = *this * s2.inverse(); return *this; }

    /// \brief Sets the scaling to the identity scaling.
    ScalingT& setIdentity() {
        S = Vector_3<T>(T(1));
        Q.setIdentity();
        return *this;
    }

    /// \brief Sets the scaling to the identity scaling.
    ScalingT& operator=(Identity) { return setIdentity(); }

    ////////////////////////////////// Comparison ////////////////////////////////

    /// \brief Compares two scaling structures for equality.
    /// \param s The scaling to compare with.
    /// \return \c true if each of the components are equal; \c false otherwise.
    constexpr bool operator==(const ScalingT& s) const { return (s.S==S) && (s.Q==Q); }

    /// \brief Compares two scaling structures for inequality.
    /// \param s The scaling to compare with.
    /// \return \c true if any of the components are not equal; \c false if all are equal.
    constexpr bool operator!=(const ScalingT& s) const { return (s.S!=S) || (s.Q!=Q); }

    /// \brief Returns whether this is the identity.
    /// \return \c true if the scaling in each of the three spatial directions is 1;
    ///         \c false otherwise.
    constexpr bool operator==(Identity) const { return (S == Vector_3<T>(1)); }

    /// \brief Returns whether this is not the identity.
    /// \return \c true if the scaling in any of the three spatial directions is not 1;
    ///         \c false otherwise.
    constexpr bool operator!=(Identity) const { return (S != Vector_3<T>(1)); }

    ///////////////////////////////// Interpolation //////////////////////////////

    /// \brief Computes a linear interpolation between two scaling structures.
    /// \param s1 The first scaling.
    /// \param s2 The second scaling.
    /// \param t The parameter for the linear interpolation in the range [0,1].
    /// \return A linear interpolation between \a s1 and \a s2.
    static ScalingT interpolate(const ScalingT& s1, const ScalingT& s2, T t) {
        return ScalingT(t * s2.S + (T(1) - t) * s1.S, QuaternionT<T>::interpolateSafely(s1.Q, s2.Q, t));
    }

    /// \brief Computes a quadratic interpolation between two scaling structures.
    /// \param s1 The first scaling (at t==0.0).
    /// \param s2 The second scaling (at t==1.0).
    /// \param out Controls the tangential direction at \a s1.
    /// \param in Controls the tangential direction at \a s2.
    /// \param t The parameter for the linear interpolation in the range [0,1].
    /// \return A linear interpolation between \a s1 and \a s2.
    static ScalingT interpolateQuad(const ScalingT& s1, const ScalingT& s2, const ScalingT& out, const ScalingT& in, T t) {
        T Ti = 1 - t;
        T U2 = t*t, T2 = Ti*Ti;
        T U3 = U2 * t, T3 = T2 * Ti;
        Vector_3<T> s = s1.S * T3 + out.S * (3 * t * T2) + in.S * (3 * U2 * Ti) + s2.S * U3;
        return ScalingT(s, QuaternionT<T>::interpolateQuad(s1.Q, s2.Q, in.Q, out.Q, t));
    }

    ////////////////////////////////// Utilities /////////////////////////////////

    /// Returns a string representation of this scaling.
    QString toString() const { return QString("[Scaling: %1 Orientation: %2]").arg(S.toString(), Q.toString()); }
};

/// \brief Writes the Scaling to a text output stream.
/// \param os The output stream.
/// \param s The scaling to write to the output stream \a os.
/// \return The output stream \a os.
/// \relates ScalingT
template<typename T>
inline std::ostream& operator<<(std::ostream &os, const ScalingT<T>& s) {
    return os << '[' << s.S << "], " << s.Q;
}

/// \brief Writes the scaling to the Qt debug stream.
/// \relates ScalingT
template<typename T>
inline QDebug operator<<(QDebug dbg, const ScalingT<T>& s) {
    dbg.nospace() << "[" << s.S << "]";
    return dbg.space();
}

/// \brief Writes a Scaling to a binary output stream.
/// \param stream The output stream.
/// \param s The scaling to write to the output stream \a stream.
/// \return The output stream \a stream.
/// \relates ScalingT
template<typename T>
inline SaveStream& operator<<(SaveStream& stream, const ScalingT<T>& s)
{
    return stream << s.S << s.Q;
}

/// \brief Reads a Scaling from a binary input stream.
/// \param stream The input stream.
/// \param s Reference to a scaling variable where the parsed data will be stored.
/// \return The input stream \a stream.
/// \relates ScalingT
template<typename T>
inline LoadStream& operator>>(LoadStream& stream, ScalingT<T>& s)
{
    return stream >> s.S >> s.Q;
}

/// \brief Writes a scaling to a Qt data stream.
/// \relates ScalingT
template<typename T>
inline QDataStream& operator<<(QDataStream& stream, const ScalingT<T>& s) {
    return stream << s.S << s.Q;
}

/// \brief Reads a scaling from a Qt data stream.
/// \relates ScalingT
template<typename T>
inline QDataStream& operator>>(QDataStream& stream, ScalingT<T>& s) {
    return stream >> s.S >> s.Q;
}

/**
 * \brief Template class instance of the ScalingT template.
 * \relates ScalingT
 */
using Scaling = ScalingT<FloatType>;

}   // End of namespace

Q_DECLARE_METATYPE(Ovito::Scaling);
Q_DECLARE_TYPEINFO(Ovito::Scaling, Q_PRIMITIVE_TYPE);
