/*
 * Copyright (c) 2015 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Andreas Sandberg
 */

#ifndef __DEV_PIXELPUMP_HH__
#define __DEV_PIXELPUMP_HH__

#include "base/framebuffer.hh"
#include "sim/clocked_object.hh"

struct BasePixelPumpParams;

struct DisplayTimings : public Serializable
{
    /**
     * Create a display timing configuration struct
     *
     * @param width Width of the visible area of the screen.
     * @param height Height of the visible area of the screen.
     * @param hfp Horizontal front porch in pixel clocks.
     * @param h_sync Horizontal sync in pixel clocks.
     * @param hbp Horizontal back porch in pixel clocks.
     * @param vfp Vertical front porch in scan lines.
     * @param v_sync Vertical sync in scan lines.
     * @param vbp Vertical back porch in scan lines.
     */
    DisplayTimings(unsigned width, unsigned height,
                   unsigned hbp, unsigned h_sync, unsigned hfp,
                   unsigned vbp, unsigned v_sync, unsigned vfp);

    void serialize(CheckpointOut &cp) const override;
    void unserialize(CheckpointIn &cp) override;

    /** How many pixel clocks are required for one line? */
    Cycles cyclesPerLine() const {
        return Cycles(hSync + hBackPorch +  width + hBackPorch);
    }

    /** How many pixel clocks are required for one frame? */
    Cycles cyclesPerFrame() const {
        return Cycles(cyclesPerLine() * linesPerFrame());
    }

    /** Calculate the first line of the vsync signal */
    unsigned lineVSyncStart() const {
        return 0;
    }

    /** Calculate the first line of the vertical back porch */
    unsigned lineVBackPorchStart() const {
        return lineVSyncStart() + vSync;
    }

    /** Calculate the first line of the visible region */
    unsigned lineFirstVisible() const {
        return lineVBackPorchStart() + vBackPorch;
    }

    /** Calculate the first line of the back porch */
    unsigned lineFrontPorchStart() const {
        return lineFirstVisible() + height;
    }

    /** Calculate the total number of lines in a frame */
    unsigned linesPerFrame() const {
        return lineFrontPorchStart() + vFrontPorch;
    }

    /** Display width in pixels */
    unsigned width;
    /** Display height in pixels */
    unsigned height;

    /** Horizontal back porch in pixels */
    unsigned hBackPorch;
    /** Horizontal front porch in pixels */
    unsigned hFrontPorch;
    /** Horizontal sync signal length in pixels */
    unsigned hSync;

    /** Vertical back porch in lines */
    unsigned vBackPorch;
    /** Vertical front porch in lines */
    unsigned vFrontPorch;
    /** Vertical sync signal in lines */
    unsigned vSync;

    static const DisplayTimings vga;
};

/**
 * Timing generator for a pixel-based display.
 *
 * Pixels are ordered relative to the top left corner of the
 * display. Scan lines appear in the following order:
 * <ol>
 *   <li>Vertical Sync (starting at line 0)
 *   <li>Vertical back porch
 *   <li>Visible lines
 *   <li>Vertical front porch
 * </ol>
 *
 * Pixel order within a scan line:
 * <ol>
 *   <li>Horizontal Sync
 *   <li>Horizontal Back Porch
 *   <li>Visible pixels
 *   <li>Horizontal Front Porch
 * </ol>
 */
class BasePixelPump
    : public EventManager, public Clocked,
      public Serializable
{
  public:
    BasePixelPump(EventManager &em, ClockDomain &pxl_clk, unsigned pixel_chunk);
    virtual ~BasePixelPump();

    void serialize(CheckpointOut &cp) const override;
    void unserialize(CheckpointIn &cp) override;

  public: // Public API
    /** Starting pushing pixels using the supplied display timings. */
    void start(const DisplayTimings &timings);

    /** Immediately stop pushing pixels */
    void stop();

    /** Get a constant reference of the current display timings */
    const DisplayTimings &timings() const { return _timings; }

    /** Is the pixel pump active and refreshing the display? */
    bool active() const { return evBeginLine.active(); }

    /** Did a buffer underrun occur within this refresh interval? */
    bool underrun() const { return _underrun; }

    /** Is the current line within the visible range? */
    bool visibleLine() const {
        return line >= _timings.lineFirstVisible() &&
            line < _timings.lineFrontPorchStart();
    }

    /** Current pixel position within the visible area */
    unsigned posX() const { return _posX; }

    /** Current pixel position within the visible area */
    unsigned posY() const {
        return visibleLine() ? line - _timings.lineFirstVisible() : 0;
    }

    /** Output frame buffer */
    FrameBuffer fb;

  protected: // Callbacks
    /**
     * Get the next pixel from the scan line buffer.
     *
     * @param p Output pixel value, undefined on underrun
     * @return true on success, false on buffer underrun
     */
    virtual bool nextPixel(Pixel &p) = 0;

    /** First pixel clock of the first VSync line. */
    virtual void onVSyncBegin() {};

    /**
     * Callback on the first pixel of the line after the end VSync
     * region (typically the first pixel of the vertical back porch).
     */
    virtual void onVSyncEnd() {};

    /**
     * Start of the HSync region.
     *
     * @note This is called even for scan lines outside of the visible
     * region.
     */
    virtual void onHSyncBegin() {};

    /**
     * Start of the first pixel after the HSync region.
     *
     * @note This is called even for scan lines outside of the visible
     * region.
     */
    virtual void onHSyncEnd() {};

    /**
     * Buffer underrun occurred on a frame.
     *
     * This method is called once if there is buffer underrun while
     * refreshing the display. The underrun state is reset on the next
     * refresh.
     *
     * @param x Coordinate within the visible region.
     * @param y Coordinate within the visible region.
     */
    virtual void onUnderrun(unsigned x, unsigned y) {};

    /** Finished displaying the visible region of a frame */
    virtual void onFrameDone() {};

  private: // Params
    /** Maximum number of pixels to handle per render callback */
    const unsigned pixelChunk;

  private:
    /**
     * Callback helper class with suspend support.
     *
     * Unlike a normal EventWrapper, this class suspends an event on
     * drain() and restarts it at drainResume(). The suspend operation
     * stores the tick relative to curTick() and then deschedules the
     * event. The resume operation schedules the event at curTick()
     * plus the relative tick stored when the event was suspended.
     */
    class PixelEvent : public Event, public Drainable
    {
        typedef void (BasePixelPump::* CallbackType)();

      public:
        PixelEvent(const char *name, BasePixelPump *parent, CallbackType func);

        DrainState drain() override;
        void drainResume() override;

        void serialize(CheckpointOut &cp) const override;
        void unserialize(CheckpointIn &cp) override;

        const std::string name() const override { return _name; }
        void process() override {
            (parent.*func)();
        }

        bool active() const { return scheduled() || suspended; }

      private:
        void suspend();
        void resume();

        const std::string _name;
        BasePixelPump &parent;
        const CallbackType func;

        bool suspended;
        Tick relativeTick;
    };

    void beginLine();
    void renderPixels();

    /** Convenience vector when doing operations on all events */
    std::vector<PixelEvent *> pixelEvents;

    PixelEvent evVSyncBegin;
    PixelEvent evVSyncEnd;
    PixelEvent evHSyncBegin;
    PixelEvent evHSyncEnd;
    PixelEvent evBeginLine;
    PixelEvent evRenderPixels;

    DisplayTimings _timings;

    /**
     * Current line (including back porch, front porch, and vsync)
     * within a frame.
     */
    unsigned line;
    /** X-coordinate within the visible region of a frame */
    unsigned _posX;

    /** Did a buffer underrun occur within this refresh interval? */
    bool _underrun;
};

#endif // __DEV_PIXELPUMP_HH__
