// curveplot.h
//
// Copyright (C) 2009-2010 Chris Laurel <claurel@gmail.com>.
//
// curveplot is a module for rendering curves in OpenGL at high precision. A
// plot is a series of cubic curves. The curves are transformed
// to camera space in software because double precision is absolutely
// required. The cubics are adaptively subdivided based on distance from
// the camera position.
//
// curveplot is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// Alternatively, you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// curveplot is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License and a copy of the GNU General Public License along with
// orbitpath. If not, see <http://www.gnu.org/licenses/>.

#pragma once

#include <deque>
#include <memory>
#include <vector>

#include <Eigen/Core>
#include <Eigen/Geometry>

#include <celutil/color.h>

class Color;
class Renderer;

class CurvePlotVertexBuffer
{
public:
    explicit CurvePlotVertexBuffer(const Renderer& _renderer);

    void setup(const Color& _color);
    void finish();
    void vertex(const Eigen::Vector4d& v, float opacity = 1.0f);
    void end();
    void flush();

private:
    unsigned int currentStripLength { 0 };
    std::vector<unsigned int> stripLengths;
    std::unique_ptr<celestia::render::LineRenderer> lr;
    const Renderer *renderer { nullptr };
    Color color;
};

class CurvePlotSample
{
public:
    Eigen::Vector3d position;
    double t;
    Eigen::Vector3d velocity;
    double boundingRadius { 0.0 };
};

class CurvePlot
{
public:
    explicit CurvePlot(CurvePlotVertexBuffer& vbuf);

    double duration() const { return m_duration; }
    void setDuration(double duration);

    double startTime() const
    {
        return m_samples.empty() ? 0.0 : m_samples.front().t;
    }

    double endTime() const
    {
        return m_samples.empty() ? 0.0 : m_samples.back().t;
    }

    void render(const Eigen::Affine3d& modelview,
                double nearZ,
                double farZ,
                const Eigen::Vector3d viewFrustumPlaneNormals[],
                double subdivisionThreshold,
                const Eigen::Vector4f& color) const;
    void render(const Eigen::Affine3d& modelview,
                double nearZ,
                double farZ,
                const Eigen::Vector3d viewFrustumPlaneNormals[],
                double subdivisionThreshold,
                double startTime,
                double endTime,
                const Eigen::Vector4f& color) const;
    void renderFaded(const Eigen::Affine3d& modelview,
                     double nearZ,
                     double farZ,
                     const Eigen::Vector3d viewFrustumPlaneNormals[],
                     double subdivisionThreshold,
                     double startTime,
                     double endTime,
                     const Eigen::Vector4f& color,
                     double fadeStartTime,
                     double fadeEndTime) const;

    unsigned int lastUsed() const { return m_lastUsed; }
    void setLastUsed(unsigned int lastUsed) { m_lastUsed = lastUsed; }

    void addSample(const CurvePlotSample& sample);
    void removeSamplesBefore(double t);
    void removeSamplesAfter(double t);

    bool empty() const { return m_samples.empty(); }

    unsigned int sampleCount() const { return static_cast<unsigned int>(m_samples.size()); }

private:
    std::deque<CurvePlotSample>     m_samples;
    CurvePlotVertexBuffer          &m_vbuf;
    double                          m_duration      { 0.0 };
    unsigned int                    m_lastUsed      { 0   };
};
