//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2007      Andrew Manson  <g.real.ate@gmail.com>
// Copyright 2008-2009 Torsten Rahn   <rahn@kde.org>
//


#ifndef MARBLE_GEODATALATLONBOX_H
#define MARBLE_GEODATALATLONBOX_H


#include "MarbleGlobal.h"

#include "GeoDataObject.h"
#include "GeoDataCoordinates.h"

#include "geodata_export.h"

namespace Marble
{

class GeoDataLatLonBoxPrivate;

class GeoDataLineString;

/**
 * @short A class that defines a 2D bounding box for geographic data.
 *
 * GeoDataLatLonBox is a 2D bounding box that describes a geographic area
 * in terms of latitude and longitude.
 *
 * The bounding box gets described by assigning the northern, southern, 
 * eastern and western boundary.
 * So usually the value of the eastern boundary is bigger than the
 * value of the western boundary.
 *
 * This is also true if the GeoDataLatLonBox covers the whole longitude
 * range from 180 deg West to 180 deg East. Notably in this case
 * the bounding box crosses the date line.
 *
 * If the GeoDataLatLonBox does not cover the whole longitude range but still
 * crosses the date line then the eastern boundary has got a smaller value than
 * the western one.
 */

class GEODATA_EXPORT GeoDataLatLonBox : public GeoDataObject
{
    friend bool GEODATA_EXPORT operator==( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs );
    friend bool GEODATA_EXPORT operator!=( GeoDataLatLonBox const& lhs, GeoDataLatLonBox const& rhs );

 public:
    GeoDataLatLonBox();
    GeoDataLatLonBox( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );
    GeoDataLatLonBox( const GeoDataLatLonBox & );
    ~GeoDataLatLonBox() override;

    GeoDataLatLonBox& operator=( const GeoDataLatLonBox& other );

    /// Provides type information for downcasting a GeoData
    const char* nodeType() const override;

    /**
     * @brief Get the northern boundary of the bounding box.
     * @return the latitude of the northern boundary.
     */
    qreal north( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;
    void setNorth( const qreal north, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    /**
     * @brief Get the southern boundary of the bounding box.
     * @return the latitude of the southern boundary.
     */
    qreal south( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;
    void setSouth( const qreal south, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    /**
     * @brief Get the eastern boundary of the bounding box.
     * @return the longitude of the eastern boundary.
     */
    qreal east( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;
    void setEast( const qreal east, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    /**
     * @brief Get the western boundary of the bounding box.
     * @return the longitude of the western boundary.
     */
    qreal west( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;
    void setWest( const qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    /**
     * @brief Get the rotation of the bounding box.
     * @return the rotation of the bounding box.
     */
    qreal rotation( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;
    void setRotation( const qreal rotation, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    void boundaries( qreal &north, qreal &south, qreal &east, qreal &west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;
    void setBoundaries( qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    /**
     * @brief Changes the differences between the boundaries and the center by the given factor, keeping the center unchanged
     * @param verticalFactor Vertical scale factor (affects north and south boundaries)
     * @param horizontalFactor Horizontal scale factor (affects west and east boundaries)
     */
    void scale(qreal verticalFactor, qreal horizontalFactor) const;
    GeoDataLatLonBox scaled(qreal verticalFactor, qreal horizontalFactor) const;

    /**
     * @brief Get the width of the longitude interval
     * @return the angle covered by the longitude range.
     */
    qreal width( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;

    /**
     * @brief Get the width of the longitude interval. East and west parameters are in radians.
     * @return the angle covered by the longitude range in given unit.
     */
    static qreal width( qreal east, qreal west, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    /**
     * @brief Get the height of the latitude interval
     * @return the angle covered by the latitude range.
     */
    qreal height( GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ) const;

    /**
     * @brief Get the height of the latitude interval. North and south parameters are in radians.
     * @return the angle covered by the latitude range in given unit.
     */
    static qreal height( qreal north, qreal south, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian );

    /**
     * @brief Detect whether the bounding box crosses the IDL.
     * @return @c true  the bounding box crosses the +/-180 deg longitude.
     *         @c false the bounding box doesn't cross the +/-180 deg longitude.
     */
    bool     crossesDateLine() const;

    /**
     * @brief Detect whether the bounding box crosses the IDL.
     * @param east radians east.
     * @param west radians west.
     * @return @c true  the bounding box crosses the +/-180 deg longitude.
     *         @c false the bounding box doesn't cross the +/-180 deg longitude.
     */
    static bool crossesDateLine(qreal east, qreal west);

    /**
     * @brief returns the center of this box
     * @return a coordinate, face-center of the box
     */
    virtual GeoDataCoordinates center() const;

    /**
     * @brief Detect whether the bounding box contains one of the poles.
     * @return @c true  the bounding box contains one of the poles.
     *         @c false the bounding box doesn't contain one of the poles.
     */
    bool containsPole( Pole pole = AnyPole ) const;

    virtual bool contains( const GeoDataCoordinates & ) const;
    bool     contains( const GeoDataLatLonBox & ) const;

    /**
     * @brief Detect whether the bounding box contains a point of given lon and lat.
     * @param lon longitude in radians.
     * @param lat latitude in radians.
     * @return true if the box contains given point, false otherwise
     */
    bool contains(qreal lon, qreal lat) const; //Optimized version for overlay painting

    virtual bool intersects( const GeoDataLatLonBox & ) const;

    /**
     * @brief Returns the bounding LatLonBox of this box with the given one.
     */
    GeoDataLatLonBox united( const GeoDataLatLonBox& other) const;

    /**
     * @return Returns the smallest bounding box that contains this LatLonBox rotated with its given angle.
     */
    GeoDataLatLonBox toCircumscribedRectangle() const;

    /**
     * @brief Create the smallest bounding box from a line string.
     * @return the smallest bounding box that contains the linestring.
     */
    static GeoDataLatLonBox fromLineString( const GeoDataLineString& lineString );

    /**
     * @brief Indicates whether the bounding box only contains a single 2D point ("singularity").
     * @return Return value is true if the height and the width of the bounding box equal zero.
     */
    virtual bool isNull() const;

    /**
     * @brief Indicates whether the bounding box is not initialised (and contains nothing).
     * @return Return value is true if bounding box is not initialised.
     */
    virtual bool isEmpty() const;

    /**
     * @brief Indicates whether two bounding boxes are roughly equal.
     *        The factor specifies the margin threshold relative to the left handside
     *        bounding box within which both bounding boxes are considered equal.
     * @return Return value is true if both bounding box are approximately equal.
     */
    static bool fuzzyCompare(const GeoDataLatLonBox& lhs,
                             const GeoDataLatLonBox& rhs,
                             const qreal factor = 0.01);

    /**
     * @brief Resets the bounding box to its uninitialised state (and thus contains nothing).
     */
    virtual void clear();

    GeoDataLatLonBox operator|( const GeoDataLatLonBox& other ) const;

    /**
     * @brief Unites this bounding box with the given one.
     * @return Returns a reference to self.
     */
    GeoDataLatLonBox& operator |=( const GeoDataLatLonBox& other) ;

    /// Serialize the contents of the feature to @p stream.
    void pack( QDataStream& stream ) const override;
    /// Unserialize the contents of the feature from @p stream.
    void unpack( QDataStream& stream ) override;

 private:
    GeoDataLatLonBoxPrivate  * const d;
    static const GeoDataLatLonBox empty;
};

}

Q_DECLARE_METATYPE( Marble::GeoDataLatLonBox )

#endif
