/*
 * (C) Copyright 2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

/// @file Polygon.h
/// @author Pedro Maciel
/// @author Willem Deconinck
/// @date September 2017

#pragma once

#include <iosfwd>
#include <memory>
#include <set>
#include <utility>
#include <vector>

#include "atlas/library/config.h"
#include "atlas/util/Config.h"
#include "atlas/util/Object.h"
#include "atlas/util/Point.h"
#include "atlas/util/VectorOfAbstract.h"

#include "atlas/projection/Projection.h"  // for ExplicitPartitionPolygon

namespace eckit {
class PathName;
}

namespace atlas {
class Field;
class RectangularDomain;
class Projection;
}  // namespace atlas

namespace atlas {
namespace util {

//------------------------------------------------------------------------------------------------------

/// Polygon
class Polygon : public std::vector<idx_t> {
public:
    // -- Types

    struct edge_t : std::pair<idx_t, idx_t> {
        edge_t(idx_t A, idx_t B): std::pair<idx_t, idx_t>(A, B) {}

        edge_t reverse() const { return edge_t(std::pair<idx_t, idx_t>::second, std::pair<idx_t, idx_t>::first); }

        struct LessThan {
            bool operator()(const edge_t& e1, const edge_t& e2) const {
                // order ascending by 'first'
                return (e1.first < e2.first ? true : e1.first > e2.first ? false : e1.second < e2.second);
            }
        };
    };

    using edge_set_t  = std::set<edge_t, typename edge_t::LessThan>;
    using container_t = std::vector<idx_t>;

    // -- Constructors

    Polygon();
    Polygon(const edge_set_t&);

    // -- Operators

    operator bool() const;

    Polygon& operator+=(const Polygon&);

    // -- Methods

    void print(std::ostream&) const;

    // -- Friends

    friend std::ostream& operator<<(std::ostream& s, const Polygon& p) {
        p.print(s);
        return s;
    }

protected:
    void setup(const edge_set_t&);
};

//------------------------------------------------------------------------------------------------------

class PartitionPolygons;

class PartitionPolygon : public Polygon, util::Object {
public:
    using Polygon::Polygon;
    using PointsXY     = std::vector<Point2>;
    using PointsLonLat = std::vector<Point2>;

    /// @brief Return inscribed rectangular domain (not rotated)
    virtual const RectangularDomain& inscribedDomain() const;

    /// @brief Return value of halo
    virtual idx_t halo() const { return 0; }

    /// @brief Return the memory footprint of the Polygon
    virtual size_t footprint() const { return 0; }

    /// @brief Output a python script that plots the partition
    virtual void outputPythonScript(const eckit::PathName&, const eckit::Configuration& = util::NoConfig()) const {}

    /// @brief All (x,y) coordinates defining a polygon. Last point should match first.
    virtual PointsXY xy() const = 0;

    /// @brief All (lon,lat) coordinates defining a polygon. Last point should match first.
    virtual PointsLonLat lonlat() const = 0;

    virtual void allGather(PartitionPolygons&) const = 0;
};

//------------------------------------------------------------------------------------------------------

class ExplicitPartitionPolygon : public util::PartitionPolygon {
public:
    explicit ExplicitPartitionPolygon(PointsXY&& points):
        ExplicitPartitionPolygon(std::move(points), RectangularDomain()) {}

    explicit ExplicitPartitionPolygon(PointsXY&& points, const RectangularDomain& inscribed):
        points_(std::move(points)), inscribed_(inscribed) {
        setup(compute_edges(points_.size()));
    }

    PointsXY xy() const override { return points_; }
    PointsLonLat lonlat() const override { return points_; }

    void allGather(util::PartitionPolygons&) const override;

    const RectangularDomain& inscribedDomain() const override { return inscribed_; }

private:
    static util::Polygon::edge_set_t compute_edges(idx_t points_size);


private:
    std::vector<Point2> points_;
    RectangularDomain inscribed_;
};  // namespace util

//------------------------------------------------------------------------------------------------------

class PartitionPolygons : public VectorOfAbstract<PartitionPolygon> {};

//------------------------------------------------------------------------------------------------------

class PolygonCoordinates {
public:
    using Vector = VectorOfAbstract<PolygonCoordinates>;
    // -- Constructors

    PolygonCoordinates(const Polygon&, const atlas::Field& coordinates, bool removeAlignedPoints);

    template <typename PointContainer>
    PolygonCoordinates(const PointContainer& points);

    template <typename PointContainer>
    PolygonCoordinates(const PointContainer& points, bool removeAlignedPoints);

    // -- Destructor

    virtual ~PolygonCoordinates();

    // -- Methods

    /// @brief Point-in-partition test
    /// @param[in] P given point
    /// @return if point is in polygon
    virtual bool contains(const Point2& P) const = 0;

    const Point2& coordinatesMax() const;
    const Point2& coordinatesMin() const;
    const Point2& centroid() const;

    template <typename Index>
    const Point2& operator[](Index i) const {
        return coordinates_[i];
    }

    idx_t size() const { return coordinates_.size(); }

    void print(std::ostream&) const;

    friend std::ostream& operator<<(std::ostream& out, const PolygonCoordinates& pc);

protected:
    // -- Members

    Point2 coordinatesMin_;
    Point2 coordinatesMax_;
    Point2 centroid_;
    std::vector<Point2> coordinates_;
};

//------------------------------------------------------------------------------------------------------

}  // namespace util
}  // namespace atlas
