/*
 * (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.
 */

#pragma once

#include "atlas/array/ArrayView.h"
#include "atlas/field/Field.h"
#include "atlas/functionspace/FunctionSpace.h"
#include "atlas/functionspace/detail/FunctionSpaceImpl.h"
#include "atlas/runtime/Exception.h"
#include "atlas/util/Config.h"
#include "atlas/util/Point.h"

namespace atlas {
class Grid;

namespace functionspace {

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

namespace detail {

class PointCloud : public functionspace::FunctionSpaceImpl {
public:
    template <typename Point>
    PointCloud(const std::vector<Point>&);
    PointCloud(const Field& lonlat);
    PointCloud(const Field& lonlat, const Field& ghost);
    PointCloud(const Grid&);
    virtual ~PointCloud() override {}
    virtual std::string type() const override { return "PointCloud"; }
    virtual operator bool() const override { return true; }
    virtual size_t footprint() const override { return sizeof(*this); }
    virtual std::string distribution() const override;
    Field lonlat() const override { return lonlat_; }
    const Field& vertical() const { return vertical_; }
    Field ghost() const override;
    virtual idx_t size() const override { return lonlat_.shape(0); }

    using FunctionSpaceImpl::createField;
    virtual Field createField(const eckit::Configuration&) const override;
    virtual Field createField(const Field&, const eckit::Configuration&) const override;

    void haloExchange(const FieldSet&, bool /*on_device*/ = false) const override {}
    void haloExchange(const Field&, bool /*on_device*/ = false) const override {}

    template <typename Point>
    class IteratorT {
    public:
        using difference_type   = std::ptrdiff_t;
        using value_type        = Point;
        using pointer           = Point*;
        using reference         = Point&;
        using iterator_category = std::output_iterator_tag;

        IteratorT(const PointCloud& fs, bool begin = true);

        bool next(Point&);

        const Point operator*() const;

        const IteratorT& operator++() {
            ++n_;
            return *this;
        }

        bool operator==(const IteratorT& other) const { return n_ == other.n_; }
        bool operator!=(const IteratorT& other) const { return n_ != other.n_; }

    private:
        const PointCloud& fs_;
        const array::ArrayView<const double, 2> xy_;
        const array::ArrayView<const double, 1> z_;
        idx_t n_;
        idx_t size_;
    };

    template <typename Point>
    class IterateT {
    public:
        using value_type     = Point;
        using iterator       = IteratorT<Point>;
        using const_iterator = iterator;

    public:
        IterateT(const PointCloud& fs): fs_(fs) {}
        iterator begin() const { return IteratorT<Point>(fs_); }
        iterator end() const { return IteratorT<Point>(fs_, false); }
        idx_t size() const { return fs_.size(); }

    private:
        const PointCloud& fs_;
    };


    class Iterate {
    public:
        Iterate(const PointCloud& fs): fs_(fs) {}
        IterateT<PointXYZ> xyz() const {
            ATLAS_ASSERT(fs_.vertical());
            return IterateT<PointXYZ>{fs_};
        }
        IterateT<PointXY> xy() const { return IterateT<PointXY>{fs_}; }
        IterateT<PointLonLat> lonlat() const { return IterateT<PointLonLat>{fs_}; }

    private:
        const PointCloud& fs_;
    };

    Iterate iterate() const { return Iterate(*this); }

private:
    Field lonlat_;
    Field vertical_;
    mutable Field ghost_;
    idx_t levels_{0};
};

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

}  // namespace detail

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

class PointCloud : public FunctionSpace {
public:
    PointCloud(const FunctionSpace&);
    PointCloud(const Field& points);
    PointCloud(const std::vector<PointXY>&);
    PointCloud(const std::vector<PointXYZ>&);
    PointCloud(const std::initializer_list<std::initializer_list<double>>&);
    PointCloud(const Grid& grid);

    operator bool() const { return valid(); }
    bool valid() const { return functionspace_; }

    const Field& vertical() const { return functionspace_->vertical(); }

    detail::PointCloud::Iterate iterate() const { return functionspace_->iterate(); }


private:
    const detail::PointCloud* functionspace_;
};

}  // namespace functionspace
}  // namespace atlas
