#pragma once
#include <list>
#include <memory>
#include "viewport.hpp"


template <class T>
class BufferView;
class XWin; // keep out X includes
class XFrame;


/**
 * @brief Final anti-aliasing and scaling/sub-sampling pass.
 *
 * I.e., either 1:1, nearest neighbour, or bilinear interpolation.
 */
class Sampler {
  private:
    const res_t res;
    std::unique_ptr<BufferView<vertex_t>> view; ///< concrete buffer accessor instance
    static std::unique_ptr<BufferView<vertex_t>> make(res_t, bool); ///< actual view factory

  public:
    Sampler(res_t, bool subsample);
    ~Sampler();

    BufferView<vertex_t>* get();
    res_t resolution_in() const; ///< tracing dimension
    res_t resolution_off() const; ///< offset if not square
    res_t resolution_out() const; ///< output dimension
};


/** @brief Frame pipeline duration feedback for the main loop. */
template <class T>
class SlidingAverage {
  private:
    const size_t len;
    size_t total_num{};
    std::list<T> buf{};
    T sum{}, total_sum{};
    float avg{}, total_avg{}; // precomputed averages, as technically not really threadsafe

  public:
    SlidingAverage(size_t max_len) : len(max_len) {
        assert(len > 0);
    }

    void push(T value);

    size_t get_num() const {
        return total_num;
    }

    size_t get_size() const {
        return buf.size();
    }

    float get_avg() const {
        return total_avg;
    }

    float get_sliding_avg() const {
        return avg;
    }
};


/**
 * @brief Final stage, combining hit texture and light information to X raster image.
 *
 * Only interface to interact with X, thus also provides key input from window to main loop.
 */
class Renderer {
  public:
    struct input_t {
        vertex_t offset{}; ///< movement as determined by keypress duration
        union {
            unsigned actions{};
            struct {
                unsigned move : 1; ///< move by offset
                unsigned beam : 1; ///< move to offset
                unsigned action : 1; ///< interact (drop/pick light)
                unsigned restart : 1; ///< restart level
                unsigned regen : 1; ///< start next level with new seed
                unsigned quit : 1; ///< program exit
            } action;
        };
    };

    struct render_task_t { ///< aggregate as @ref ThreadPool callback argument
        const context_t& context;
        const BufferView<vertex_t>& view;
        XFrame& frame;
    };

  private:
    SlidingAverage<msec_t> frame_duration;
    Sampler& sampler;
    std::unique_ptr<XWin> xwin;
    ThreadQueue<context_t> thread;
    ThreadPool<render_task_t, 4> threads;

    void draw(std::unique_ptr<context_t>&&);

  public:
    Renderer(ContextFactory&, Sampler&);
    ~Renderer();

    ThreadQueue<context_t>& queue();
    input_t get_input(fcoord_t sensitivity); ///< pop key presses from @ref XKeyEvents thread
    const SlidingAverage<msec_t>& duration_hint() const; ///< collect average duration until render
};