////////////////////////////////////////////////////////////////////////////////////////
//
//  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::Box_2 class template.
 */

#pragma once


#include <ovito/core/Core.h>
#include <ovito/core/utilities/io/SaveStream.h>
#include <ovito/core/utilities/io/LoadStream.h>
#include "Vector2.h"
#include "Point2.h"

namespace Ovito {

/**
 * \brief An axis-aligned box in 2d space.
 *
 * The box is defined by the lower and upper X and Y coordinates (#minc and #maxc fields).
 *
 * The template parameter \a T specifies the data type used for the box coordinates.
 * Two standard instantiations of Box_2 for floating-point and integer coordinates are predefined:
 *
 * \code
 *      typedef Box_2<FloatType>  Box2;
 *      typedef Box_2<int>        Box2I;
 * \endcode
 *
 * A box is considered empty if any of its lower coordinates is greater than the corresponding
 * upper coordinate.
 *
 * \sa Box_3
 * \sa Vector_2, Point_2
 */
template<typename T>
class Box_2
{
public:

    /// The lower XY coordinates of the box.
    Point_2<T> minc;
    /// The upper XY coordinates of the box.
    Point_2<T> maxc;

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

    /// \brief Constructs an empty box.
    Box_2() noexcept : minc(std::numeric_limits<T>::max()), maxc(std::numeric_limits<T>::lowest()) {}

    /// \brief Initializes the box with lower and upper coordinates.
    /// \param lower The corner of the box that specifies the lower boundary coordinates.
    /// \param upper The corner of the box that specifies the upper boundary coordinates.
    Box_2(const Point_2<T>& lower, const Point_2<T>& upper) noexcept : minc(lower), maxc(upper) {
        OVITO_ASSERT_MSG(minc.x() <= maxc.x(), "Box_2 constructor", "Lower X coordinate must not be larger than upper X coordinate.");
        OVITO_ASSERT_MSG(minc.y() <= maxc.y(), "Box_2 constructor", "Lower Y coordinate must not be larger than upper Y coordinate.");
    }

    /// \brief Initializes the box with the given coordinates.
    Box_2(T xmin, T ymin, T xmax, T ymax) noexcept : minc(Point_2<T>(xmin, ymin)), maxc(Point_2<T>(xmax, ymax)) {
        OVITO_ASSERT(minc.x() <= maxc.x());
        OVITO_ASSERT(minc.y() <= maxc.y());
    }

    /// \brief Crates a square box.
    /// \param center The center of the cubic box.
    /// \param halfEdgeLength One half of the edge length of the square.
    Box_2(const Point_2<T>& center, T halfEdgeLength) noexcept {
        OVITO_ASSERT(halfEdgeLength >= 0);
        minc.x() = center.x() - halfEdgeLength;
        minc.y() = center.y() - halfEdgeLength;
        maxc.x() = center.x() + halfEdgeLength;
        maxc.y() = center.y() + halfEdgeLength;
    }

    /// \brief Casts the box to a box with another data type.
    template<typename U>
    constexpr auto toDataType() const noexcept -> std::conditional_t<!std::is_same_v<T,U>, Box_2<U>, const Box_2<T>&> {
        if constexpr(!std::is_same_v<T,U>)
            if(!isEmpty())
                return Box_2<U>(minc.template toDataType<U>(), maxc.template toDataType<U>());
            else
                return Box_2<U>();
        else
            return *this;  // When casting to the same type \a T, this method becomes a no-op.
    }

    ///////////////////////////////// Attributes /////////////////////////////////

    /// \brief Checks whether this is box is empty.
    ///
    /// The box is considered empty if one of the upper boundary coordinates is smaller than
    /// the corresponding lower boundary coordinate.
    constexpr bool isEmpty() const {
        return (minc.x() > maxc.x()) || (minc.y() > maxc.y());
    }

    /// \brief Resets the box to the empty state.
    void setEmpty() {
        minc = Point_2<T>(std::numeric_limits<T>::max());
        maxc = Point_2<T>(std::numeric_limits<T>::lowest());
    }

    /// \brief Returns the position of one of the eight corners of the box.
    /// \param i The index of the corner in the range 0 to 3.
    /// \return The coordinates of the i-th corner of the box.
    Point_2<T> operator[](typename Point_2<T>::size_type i) const {
        OVITO_ASSERT_MSG(!isEmpty(), "Box_2::operator[]", "Cannot compute the corners of an empty box.");
        OVITO_ASSERT_MSG(i >= 0 && i < 4, "Box_2::operator[]", "Corner index out of range.");
        const Point_2<T>* const c = &minc;
        OVITO_ASSERT(&c[1] == &maxc);
        return Point_2<T>(c[i&1].x(), c[(i>>1)&1].y());
    }

    /// \brief Computes the width of the box.
    constexpr T width() const { return maxc.x() - minc.x(); }

    /// \brief Computes the height of the box.
    constexpr T height() const { return maxc.y() - minc.y(); }

    /// \brief Computes the center of the box.
    constexpr Point_2<T> center() const {
        return Point_2<T>((minc.x() + maxc.x()) / 2, (minc.y() + maxc.y()) / 2);
    }

    /// \brief Computes the size of the box.
    /// \return The difference between the upper and lower boundary coordinates.
    /// \sa width(), height()
    constexpr Vector_2<T> size() const {
        return maxc - minc;
    }

    /// \brief Returns the size of the box in the given dimension.
    /// \param dimension The dimension (0 - 2).
    /// \return The difference between the upper and lower boundary of the box in the given dimension.
    /// \sa size(), width(), height()
    constexpr T size(typename Point_2<T>::size_type dimension) const {
        return maxc[dimension] - minc[dimension];
    }

    /// \brief Compares two bxoes for equality.
    constexpr bool operator==(const Box_2<T>& other) const { return minc == other.minc && maxc == other.maxc; }

    /// Conversion operator to a Qt rectangle.
    operator QRectF() const {
        return QRectF(minc.x(), minc.y(), width(), height());
    }

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

    /// \brief Checks whether a point is located inside the box.
    /// \param p The input point.
    /// \return \c true if the point \a p is inside or on the boundaries of the box; \c false if it is outside the box.
    constexpr bool contains(const Point_2<T>& p) const {
        return (p.x() >= minc.x() && p.x() <= maxc.x() && p.y() >= minc.y() && p.y() <= maxc.y());
    }

    /// \brief Classifies a point with respect to the box.
    /// \param p The input point.
    /// \param epsilon This threshold is used to test whether the point is on the boundary of the box.
    /// \return -1 if \a p is outside the box; 0 if \a p is on the boundary of the box within the specified tolerance; +1 if inside the box.
    constexpr int classifyPoint(const Point_2<T>& p, T epsilon = Ovito::epsilon_v<T>) const
    {
        return
                (p.x() > maxc.x() + epsilon || p.y() > maxc.y() + epsilon) ||
                (p.x() < minc.x() - epsilon || p.y() < minc.y() - epsilon)
                        ? -1 :
                ((p.x() < maxc.x() - epsilon && p.x() > minc.x() + epsilon && p.y() < maxc.y() - epsilon && p.y() > minc.y() + epsilon)
                        ? 1 : 0);
    }

    /// \brief Tests if another box is contained in this box.
    /// \param b The other box.
    /// \return \c true if the box \a b is completely inside this box.
    constexpr bool containsBox(const Box_2<T>& b) const {
        return (b.minc.x() >= minc.x() && b.maxc.x() <= maxc.x()) &&
            (b.minc.y() >= minc.y() && b.maxc.y() <= maxc.y());
    }

    /// \brief Tests whether the intersection of two boxes is not empty.
    /// \param b The other box.
    /// \return \c true if the box \a b is not completely outside of this box;
    ///         \c false if the two boxes do not overlap or are empty.
    constexpr bool intersects(const Box_2<T>& b) const {
        return (maxc.x() > b.minc.x() && minc.x() < b.maxc.x() &&
                maxc.y() > b.minc.y() && minc.y() < b.maxc.y() &&
                !isEmpty() && !b.isEmpty());
    }

    //////////////////////////////// Modification ////////////////////////////////

    /// \brief Extends this box to include the given point.
    /// \param p The point which should be included in this box after the method returns.
    /// \sa addPoints(), addBox()
    void addPoint(const Point_2<T>& p) {
        minc.x() = std::min(minc.x(), p.x()); maxc.x() = std::max(maxc.x(), p.x());
        minc.y() = std::min(minc.y(), p.y()); maxc.y() = std::max(maxc.y(), p.y());
    }

    /// \brief Extends this box to include the given point.
    /// \sa addPoints(), addBox()
    void addPoint(T x, T y) {
        minc.x() = std::min(minc.x(), x); maxc.x() = std::max(maxc.x(), x);
        minc.y() = std::min(minc.y(), y); maxc.y() = std::max(maxc.y(), y);
    }

    /// \brief Extends the box to include the given set of points.
    /// \param points Pointer to the first element of an array of points.
    /// \param count The number of points in the array.
    /// \sa addPoint()
    void addPoints(const Point_2<T>* points, std::size_t count) {
        for(; count != 0; count--, points++) {
            minc.x() = std::min(minc.x(), points->X); maxc.x() = std::max(maxc.x(), points->X);
            minc.y() = std::min(minc.y(), points->Y); maxc.y() = std::max(maxc.y(), points->Y);
        }
    }

    /// \brief Extends this box to include the given box.
    /// \param b The other box.
    /// \sa addPoint()
    void addBox(const Box_2& b) {
        minc.x() = std::min(minc.x(), b.minc.x()); maxc.x() = std::max(maxc.x(), b.maxc.x());
        minc.y() = std::min(minc.y(), b.minc.y()); maxc.y() = std::max(maxc.y(), b.maxc.y());
    }

    /// \brief Extends the box to include the given x coordinate.
    void includeX(T x) {
        minc.x() = std::min(minc.x(), x); maxc.x() = std::max(maxc.x(), x);
    }

    /// \brief Extends the box to include the given y coordinate.
    void includeY(T y) {
        minc.y() = std::min(minc.y(), y); maxc.y() = std::max(maxc.y(), y);
    }

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

    /// \brief Generates a string representation of this box.
    QString toString() const {
        return "[Min: " + minc.toString() + " Max: " + maxc.toString() + "]";
    }
};

/// \brief Writes a 2d box to a binary output stream.
/// \relates Box_2
template<typename T>
inline SaveStream& operator<<(SaveStream& stream, const Box_2<T>& b)
{
    return stream << b.minc << b.maxc;
}

/// \brief Reads a 2d box from a binary input stream.
/// \relates Box_2
template<typename T>
inline LoadStream& operator>>(LoadStream& stream, Box_2<T>& b)
{
    return stream >> b.minc >> b.maxc;
}

/// \brief Prints a box to a Qt debug stream.
/// \relates Box_2
template<typename T>
inline QDebug operator<<(QDebug dbg, const Box_2<T>& b) {
    dbg.nospace() << "[" << b.minc << "] - [" << b.maxc << "]";
    return dbg.space();
}

/// \brief Prints a box to a text output stream.
/// \relates Box_2
template<typename T>
inline std::ostream& operator<<(std::ostream &os, const Box_2<T> &b) {
    return os << '[' << b.minc << "] - [" << b.maxc << ']';
}

/// \brief Writes a box to a Qt data stream.
/// \relates Box_2
template<typename T>
inline QDataStream& operator<<(QDataStream& stream, const Box_2<T>& b) {
    return stream << b.minc << b.maxc;
}

/// \brief Reads a box from a Qt data stream.
/// \relates Box_2
template<typename T>
inline QDataStream& operator>>(QDataStream& stream, Box_2<T>& b) {
    return stream >> b.minc >> b.maxc;
}

/**
 * \brief Instance of the Box_2 class template used for floating-point calculations based on Point2.
 * \relates Box_2
 */
using Box2 = Box_2<FloatType>;

/**
 * \brief Instance of the Box_2 class template used for floating-point calculations based on Point2F.
 * \relates Box_2
 */
using Box2F = Box_2<float>;

/**
 * \brief Instance of the Box_2 class template used for floating-point calculations based on Point2G.
 * \relates Box_2
 */
using Box2G = Box_2<GraphicsFloatType>;

/**
 * \brief Instance of the Box_2 class template used for integer calculations based on Point2I.
 * \relates Box_2
 */
using Box2I = Box_2<int>;

}   // End of namespace

Q_DECLARE_METATYPE(Ovito::Box2);
Q_DECLARE_METATYPE(Ovito::Box2F);
Q_DECLARE_METATYPE(Ovito::Box2I);
Q_DECLARE_TYPEINFO(Ovito::Box2, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(Ovito::Box2F, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(Ovito::Box2I, Q_MOVABLE_TYPE);
