////////////////////////////////////////////////////////////////////////////////////////
//
//  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::Plane_3 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 "Point3.h"
#include "Ray.h"
#include "AffineTransformation.h"

namespace Ovito {

/**
 * \brief An infinite plane in 3d space.
 *
 * The plane is defined by a unit #normal vector and a value #dist, measuring
 * the plane's signed distance from the origin.
 *
 * The template parameter \a T specifies the data type used for spatial coordinates.
 * The standard template instantiation for floating-point coordinates is predefined:
 *
 * \code
 *      typedef Plane_3<FloatType>  Plane3;
 * \endcode
 */
template<typename T>
class Plane_3
{
public:

    /// \brief The unit normal vector.
    Vector_3<T> normal;

    /// \brief The signed distance of the plane from the origin.
    T dist;

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

    /// Empty default constructor that does not initialize the fields of the object for performance reasons!
    /// Both the normal vector and the distance parameter are undefined.
    Plane_3() = default;

    /// \brief Initializes the plane from a normal vector and a distance parameter.
    /// \param n The normal vector. This should be a unit vector.
    /// \param d The distance of the plane from the origin in the direction of the normal vector \a n.
    constexpr Plane_3(const Vector_3<T>& n, T d) : normal(n), dist(d) {}

    /// \brief Initializes the plane from a base point and a normal vector.
    /// \param basePoint A point in the plane.
    /// \param n The normal vector. This must be a unit vector.
    constexpr Plane_3(const Point_3<T>& basePoint, const Vector_3<T>& n) : normal(n), dist(normal.dot((basePoint - typename Point_3<T>::Origin()))) {}

    /// \brief Initializes the plane from three points (without normalizing the normal vector).
    /// \param p1 The first point in the plane.
    /// \param p2 The second point in the plane.
    /// \param p3 The third point in the plane.
    /// \note The three points must be linearly independent of each other.
    /// \note The normal vector computed from the three points is NOT normalized by this constructor. It can be normalized later by calling normalizePlane().
    constexpr Plane_3(const Point_3<T>& p1, const Point_3<T>& p2, const Point_3<T>& p3) {
        normal = (p2-p1).cross(p3-p1);
        T lsq = normal.squaredLength();
        if(lsq) dist = normal.dot(p1 - typename Point_3<T>::Origin()) / lsq;
        else dist = 0;
    }

    /// \brief Initializes the plane from three points with optional normalization of the normal vector.
    /// \param p1 The first point in the plane.
    /// \param p2 The second point in the plane.
    /// \param p3 The third point in the plane.
    /// \param normalize Controls the normalization of the computed normal vector.
    ///        If \c false, then the normal vector can be normalized later by calling normalizePlane().
    /// \note The three points must linearly independent of each other.
    constexpr Plane_3(const Point_3<T>& p1, const Point_3<T>& p2, const Point_3<T>& p3, bool normalize) {
        if(normalize) {
            normal = (p2-p1).cross(p3-p1).safelyNormalized();
            dist = normal.dot(p1 - typename Point_3<T>::Origin());
        }
        else {
            normal = (p2-p1).cross(p3-p1);
            T lsq = normal.squaredLength();
            if(lsq) dist = normal.dot(p1 - typename Point_3<T>::Origin()) / lsq;
            else dist = 0;
        }
    }

    /// \brief Initializes the plane from one base point and two in-plane vectors.
    /// \param p The base point in the plane.
    /// \param v1 The first vector in the plane.
    /// \param v2 The second vector in the plane.
    /// \param normalize Controls the normalization of the computed normal vector.
    ///        If \c false, then the normal vector can be normalized later by calling normalizePlane().
    /// \note The two vectors must be linearly independent of each other.
    constexpr Plane_3(const Point_3<T>& p, const Vector_3<T>& v1, const Vector_3<T>& v2, bool normalize = true) {
        if(normalize)
            normal = v1.cross(v2).normalized();
        else
            normal = v1.cross(v2);
        dist = normal.dot(p - typename Point_3<T>::Origin());
    }

    /// \brief Rescales the normal vector of the plane to make it a unit vector.
    constexpr void normalizePlane() {
        T len = normal.length();
        OVITO_ASSERT_MSG(len != T(0), "Plane_3::normalizePlane()", "The normal vector of the plane must not be the null vector.");
        dist /= len;
        normal /= len;
        OVITO_ASSERT(std::abs(normal.squaredLength() - T(1)) <= Ovito::epsilon_v<T>);
    }

    ////////////////////////////////// operators /////////////////////////////////

    /// \brief Flips the plane's orientation.
    /// \return A new plane with reversed orientation.
    constexpr Plane_3<T> operator-() const { return Plane_3<T>(-normal, -dist); }

    /// \brief Compares two planes for equality.
    /// \return \c true if the normal vectors and the distance parameter of both planes a exactly equal; \c false otherwise.
    constexpr bool operator==(const Plane_3<T>& other) const { return normal == other.normal && dist == other.dist; }

    /////////////////////////////// Classification ///////////////////////////////

    /// \brief Classifies a point with respect to the plane.
    /// \param p The point to classify.
    /// \param tolerance A non-negative threshold value that is used to test whether the point is located on the plane.
    /// \return 1 if \a p is on the POSITIVE side of the plane,
    ///         -1 if \a p is on the NEGATIVE side, or 0 if \a p is ON the plane within the given tolerance.
    /// \sa pointDistance()
    constexpr int classifyPoint(const Point_3<T>& p, const T tolerance = Ovito::epsilon_v<T>) const
    {
        OVITO_ASSERT_MSG(tolerance >= 0, "Plane_3::classifyPoint()", "Tolerance value must be non-negative.");
        T d = pointDistance(p);
        if(d < -tolerance) return -1;
        else if(d > tolerance) return 1;
        else return 0;
    }

    /// \brief Computes the signed distance of a point to the plane.
    /// \param p The input point.
    /// \return The distance of the point to the plane. A positive value indicates that the point
    ///         is on the positive side of the plane. A negative value means that the points is located in the
    ///         back side of the plane.
    /// \note This method requires the plane's normal to be a unit vector.
    /// \sa classifyPoint()
    constexpr T pointDistance(const Point_3<T>& p) const {
        return (normal.x() * p.x() + normal.y() * p.y() + normal.z() * p.z()) - dist;
    }

    ///////////////////////////////// Intersection ///////////////////////////////

    /// \brief Computes the intersection point of a ray with the plane.
    /// \param ray The input ray.
    /// \param epsilon A threshold value that is used to test if the ray is parallel to the plane.
    /// \return The intersection point.
    /// \throw Exception if there is not intersection, because the ray is parallel to the plane.
    /// \note This method requires the plane's normal to be a unit vector.
    /// \sa intersectionT()
    Point_3<T> intersection(const Ray3& ray, T epsilon = T(0)) const {
        T t = intersectionT(ray, epsilon);
        if(t == std::numeric_limits<T>::max())
            throw Exception("Error in Plane_3::intersection(): There is no intersection point. Ray is parallel to plane.");
        return ray.point(t);
    }

    /// \brief Computes the \a t value for a ray-plane intersection.
    /// \param ray The input ray.
    /// \param epsilon A threshold value that is used to test if the ray is parallel to the plane.
    /// \return A value \a t such that (ray.base + t*ray.dir) == point of intersection.
    ///         If there is no intersection, then FLOATTYPE_MAX is returned.
    /// \note This method requires the plane's normal to be a unit vector.
    /// \sa intersection()
    constexpr T intersectionT(const Ray3& ray, T epsilon = T(0)) const {
        // The plane's normal vector should be normalized.
        OVITO_ASSERT(std::abs(normal.squaredLength() - T(1)) <= Ovito::epsilon_v<T>);
        T dot = normal.dot(ray.dir);
        if(std::abs(dot) <= epsilon) return std::numeric_limits<T>::max();
        return -pointDistance(ray.base) / dot;
    }

    ////////////////////////////////// Projection /////////////////////////////////

    /// \brief Projects a point onto the plane.
    /// \param p The point to be projected.
    /// \return The projected point. This is the point on the plane closest to \a p.
    constexpr Point_3<T> projectPoint(const Point_3<T>& p) const {
        return p - pointDistance(p) * normal;
    }

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

    /// \brief Generates a string representation of the plane.
    QString toString() const {
        return "[Normal: " + normal.toString() + " D: " + QString::number(dist) + "]";
    }
};

/// \brief Transforms a plane.
/// \param tm The transformation matrix.
/// \param ray The plane to be transformed.
/// \return A new plane with transformed normal vector and distance parameter.
///         The normal vector is automatically normalized after the transformation.
/// \relates Plane_3
template<typename T>
constexpr inline Plane_3<T> operator*(const AffineTransformationT<T>& tm, const Plane_3<T>& plane) {
    Plane_3<T> p2;
    Matrix_3<T> inv_tm;
    if(tm.linear().inverse(inv_tm)) {
        Matrix_3<T> normalTM = inv_tm.transposed();
        p2.normal = (normalTM * plane.normal).safelyNormalized();
        Point_3<T> base = tm * (typename Point_3<T>::Origin() + plane.normal * plane.dist);
        p2.dist = p2.normal.dot(base - typename Point_3<T>::Origin());
    }
    else {
        p2.normal.setZero();
        p2.dist = 0;
    }
    return p2;
}

/// \brief Writes a plane to an output stream.
/// \param os The output stream.
/// \param p The plane to write to the output stream \a os.
/// \return The output stream \a os.
/// \relates Plane_3
template<typename T>
inline std::ostream& operator<<(std::ostream &os, const Plane_3<T>& p) {
    return os << '[' << p.normal.x() << ' ' << p.normal.y()  << ' ' << p.normal.z() << "], " << p.dist;
}

/// \brief Prints a plane to a Qt debug stream.
/// \relates Plane_3
template<typename T>
inline QDebug operator<<(QDebug dbg, const Plane_3<T>& p) {
    dbg.nospace() << '[' << p.normal.x() << ' ' << p.normal.y()  << ' ' << p.normal.z() << "], " << p.dist;
    return dbg.space();
}

/// \brief Writes a plane to a binary output stream.
/// \param stream The output stream.
/// \param p The plane to write to the output stream \a stream.
/// \return The output stream \a stream.
/// \relates Plane_3
template<typename T>
inline SaveStream& operator<<(SaveStream& stream, const Plane_3<T>& p)
{
    return stream << p.normal << p.dist;
}

/// \brief Loads a plane from a binary output stream.
/// \param stream The input stream.
/// \param p The plane variable.
/// \return The input stream \a stream.
/// \relates Plane_3
template<typename T>
inline LoadStream& operator>>(LoadStream& stream, Plane_3<T>& p)
{
    return stream >> p.normal >> p.dist;
}

/// \brief Writes a plane to a Qt data stream.
/// \relates Plane_3
template<typename T>
inline QDataStream& operator<<(QDataStream& stream, const Plane_3<T>& p) {
    return stream << p.normal << p.dist;
}

/// \brief Reads a plane from a Qt data stream.
/// \relates Plane_3
template<typename T>
inline QDataStream& operator>>(QDataStream& stream, Plane_3<T>& p) {
    return stream >> p.normal >> p.dist;
}

/**
 * \brief Instantiation of the Plane_3 class template with the default floating-point type (double precision).
 * \relates Plane_3
 */
using Plane3 = Plane_3<FloatType>;

/**
 * \brief Instantiation of the Plane_3 class template with the single-precision floating-point type.
 * \relates Plane_3
 */
using Plane3F = Plane_3<float>;

}   // End of namespace

Q_DECLARE_METATYPE(Ovito::Plane3);
Q_DECLARE_METATYPE(Ovito::Plane3F);
Q_DECLARE_TYPEINFO(Ovito::Plane3, Q_PRIMITIVE_TYPE);
Q_DECLARE_TYPEINFO(Ovito::Plane3F, Q_PRIMITIVE_TYPE);
