/**
  ******************************************************************************
  * This file is part of the TouchGFX 4.10.0 distribution.
  *
  * <h2><center>&copy; Copyright (c) 2018 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

#ifndef HALSDL2_HPP
#define HALSDL2_HPP

#include <touchgfx/hal/HAL.hpp>
#include <platform/driver/touch/TouchController.hpp>
#include <touchgfx/lcd/LCD.hpp>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_render.h>

namespace touchgfx
{
/**
 * @class HALSDL2 HALSDL2.hpp platform/hal/simulator/sdl/HALSDL2.hpp
 *
 * @brief HAL implementation for the TouchGFX simulator.
 *
 *        HAL implementation for the TouchGFX simulator.
 *
 * @see HAL
 */
class HALSDL2 : public HAL
{
public:

    /**
     * @fn HALSDL2::HALSDL2(DMA_Interface& dma, LCD& lcd, TouchController& touchCtrl, uint16_t width, uint16_t height)
     *
     * @brief Constructor. Initializes members.
     *
     *        Constructor. Initializes members.
     *
     * @param [in] dma       Reference to DMA interface.
     * @param [in] lcd       Reference to the LCD.
     * @param [in] touchCtrl Reference to Touch Controller driver.
     * @param width          Width of the display.
     * @param height         Height of the display.
     */
    HALSDL2(DMA_Interface& dma, LCD& lcd, TouchController& touchCtrl, uint16_t width, uint16_t height) :
        HAL(dma, lcd, touchCtrl, width, height),
        debugInfoEnabled(false),
        customTitle(0),
        portraitSkin(),
        landscapeSkin(),
        currentSkin(0),
        isSkinActive(true),
        borderless(false),
        flashInvalidatedRect(false),
        windowDrag(false)
    {
        setVsyncInterval(30.f); // Simulate 20Hz per default for backward compatibility
        updateCurrentSkin();
    }

    /**
     * @fn virtual void HALSDL2::taskEntry();
     *
     * @brief Main event loop.
     *
     *        Main event loop. Will wait for VSYNC signal, and then process next frame. Call
     *        this function from your GUI task.
     *
     * @note This function never returns!
     */
    virtual void taskEntry();

    /**
     * @fn virtual bool HALSDL2::sampleKey(uint8_t& key);
     *
     * @brief Sample key event from keyboard.
     *
     *        Sample key event from keyboard.
     *
     * @param [out] key Output parameter that will be set to the key value if a key press was
     *                  detected.
     *
     * @return True if a key press was detected and the "key" parameter is set to a value.
     */
    virtual bool sampleKey(uint8_t& key);

    /**
     * @fn virtual void HALSDL2::flushFrameBuffer();
     *
     * @brief This function is called whenever the framework has performed a complete draw.
     *
     *        On some platforms, a local frame buffer needs to be pushed to the display through
     *        a SPI channel or similar. Implement that functionality here. This function is
     *        called whenever the framework has performed a complete draw.
     */
    virtual void flushFrameBuffer();

    /**
     * @fn virtual void HALSDL2::flushFrameBuffer(const Rect& rect);
     *
     * @brief This function is called whenever the framework has performed a partial draw.
     *
     *        This function is called whenever the framework has performed a partial draw.
     *
     * @param rect The area of the screen that has been drawn, expressed in absolute coordinates.
     *
     * @see flushFrameBuffer(). This function is called whenever the framework has performed a
     *      partial draw.
     */
    virtual void flushFrameBuffer(const Rect& rect);

    /**
     * @fn virtual bool HALSDL2::blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes);
     *
     * @brief This function performs a platform-specific memcpy.
     *
     *        This function performs a platform-specific memcpy, if supported by the hardware.
     *
     * @param [out] dest Pointer to destination memory.
     * @param src        Pointer to source memory.
     * @param numBytes   Number of bytes to copy.
     *
     * @return true if the copy succeeded, false if copy was not performed.
     */
    virtual bool blockCopy(void* RESTRICT dest, const void* RESTRICT src, uint32_t numBytes);

    /**
     * @fn virtual void HALSDL2::blitSetTransparencyKey(uint16_t key);
     *
     * @brief If Blit-operations are supported, transparency-keying support is implicitly assumed.
     *
     *        If Blit-operations are supported, transparency-keying support is implicitly
     *        assumed.
     *
     * @param key The "transparent" color value.
     */
    virtual void blitSetTransparencyKey(uint16_t key);

    /**
     * @fn void HALSDL2::setVsyncInterval(float ms);
     *
     * @brief Sets vsync interval.
     *
     *        Sets vsync interval for simulating same tick speed as the real hardware. Due to
     *        limitations in the granularity of SDL, the generated ticks in the simulator might
     *        not occur at the exact time, but accumulated over several ticks, the precision is
     *        very good.
     *
     * @param ms The milliseconds between ticks.
     *
     * @note That you can also use HAL::setFrameRateCompensation() in the simulator. The effect of
     *       this can easily be demonstrated by dragging the console output window of the
     *       simulator (when running from Visual Studio) as this will pause the SDL and generate a
     *       lot of ticks when the console window is released. Beware that since the missed vsyncs
     *       are accumulated in an 8 bit counter, only up to 255 ticks may be missed, so at
     *       VsyncInterval = 16.6667, dragging the windows for more than 255 * 16.6667ms = 4250ms
     *       = 4.25s will not generate all the ticks that were actually missed. This situation is,
     *       however, not very realistic, as normally just a couple of vsyncs are skipped.
     */
    void setVsyncInterval(float ms);

    /**
     * @fn bool HALSDL2::doSampleTouch(int32_t& x, int32_t& y) const;
     *
     * @brief Samples the position of the mouse cursor.
     *
     *        Samples the position of the mouse cursor.
     *
     * @param [out] x The x coordinate.
     * @param [out] y The y coordinate.
     *
     * @return True if touch detected, false otherwise.
     */
    bool doSampleTouch(int32_t& x, int32_t& y) const;

    /**
     * @fn virtual bool HALSDL2::sdl_init(int argcount, char** args);
     *
     * @brief Initializes SDL.
     *
     *        Initializes SDL.
     *
     * @param argcount  Number of arguments.
     * @param [in] args Arguments.
     *
     * @return True if init went well, false otherwise.
     */
    virtual bool sdl_init(int argcount, char** args);

    /**
     * @fn void HALSDL2::setWindowTitle(const char* title)
     *
     * @brief Sets window title.
     *
     *        Sets window title of the TouchGFX simulator.
     *
     * @param title The title, if null the original "TouchGFX simulator" will be used.
     *
     * @see getWindowTitle
     */
    void setWindowTitle(const char* title);

    /**
     * @fn const char* HALSDL2::getWindowTitle() const
     *
     * @brief Gets window title.
     *
     *        Gets window title.
     *
     * @return null "TouchGFX simulator" unless set to something else using setWindowTitle().
     *
     * @see setWindowTitle
     */
    const char* getWindowTitle() const;

    /**
     * @fn void HALSDL2::loadSkin(touchgfx::DisplayOrientation orientation, int x, int y);
     *
     * @brief Loads a skin for a given display orientation.
     *
     *        Loads a skin for a given display orientation that will be rendered in the
     *        simulator window with the the TouchGFX framebuffer placed inside the bitmap at
     *        the given coordinates. Different bitmaps can be loaded in landscape and portrait
     *        mode. If the provided bitmap cannot be loaded, the TouchGFX framebuffer will be
     *        displayed as normal. If the png files contain areas with alpha &lt; 255, this
     *        will be used to create a shaped window.
     *
     * @param orientation The orientation.
     * @param x           The x coordinate.
     * @param y           The y coordinate.
     *
     * @note The skins must be named "portrait.png" and "landscape.png" and placed inside the
     *       "simulator/" folder. The build process of the simulator will automatically copy the
     *       skins to the folder where the executable simulator is generated.
     * @note When as skin is set, the entire framebuffer is rendered through SDL whenever there is
     *       a change. Without a skin, only the areas with changes is rendered through SDL.
     */
    void loadSkin(touchgfx::DisplayOrientation orientation, int x, int y);

    /**
     * @fn void HALSDL2::saveScreenshot();
     *
     * @brief Saves a screenshot.
     *
     *        Saves a screenshot to the default folder and default filename.
     */
    void saveScreenshot();

    /**
     * @fn virtual void HALSDL2::saveNextScreenshots(int n);
     *
     * @brief Copy the next N screenshots to disk
     *
     *        Copy the next N screenshots to disk. On each screen update, the new screen is saved
     *        to disk.
     *
     * @param n Number of screenshots to save. These are added to any ongoing amount of screenshots
     *          in queue.
     */
    virtual void saveNextScreenshots(int n);

    /**
     * @fn virtual void HALSDL2::saveScreenshot(char* folder, char* filename);
     *
     * @brief Saves a screenshot.
     *
     *        Saves a screenshot.
     *
     * @param [in] folder   Folder name to place the screenshot in.
     * @param [in] filename Filename to save the screenshot to.
     */
    virtual void saveScreenshot(char* folder, char* filename);

    /**
     * @fn virtual void HALSDL2::copyScreenshotToClipboard();
     *
     * @brief Copies the screenshot to clipboard
     *
     *        Copies the screenshot to clipboard.
     */
    virtual void copyScreenshotToClipboard();

#ifndef __linux__
    /**
     * @fn static char** getArgv(int* argc);
     *
     * @brief Gets the argc and argv.
     *
     *        Gets the argc and argv for a Windows program.
     *
     * @param argc Pointer to where to store number of arguments
     * @return The argv list of arguments.
     */
    static char** getArgv(int* argc);
#endif

protected:

    /**
     * @fn virtual uint16_t* HALSDL2::getTFTFrameBuffer() const;
     *
     * @brief Gets TFT frame buffer.
     *
     *        Gets TFT frame buffer.
     *
     * @return null if it fails, else the TFT frame buffer.
     */
    virtual uint16_t* getTFTFrameBuffer() const;

    /**
     * @fn void HALSDL2::setTFTFrameBuffer(uint16_t* addr);
     *
     * @brief Sets TFT frame buffer.
     *
     *        Sets TFT frame buffer.
     *
     * @param [in] addr The address of the TFT frame buffer.
     */
    void setTFTFrameBuffer(uint16_t* addr);

    /**
     * @fn virtual void HALSDL2::renderLCD_FrameBufferToMemory(const Rect& _rectToUpdate, uint8_t* frameBuffer);
     *
     * @brief Update frame buffer using an SDL Surface.
     *
     *        Update frame buffer using an SDL Surface.
     *
     * @param _rectToUpdate    Area to update.
     * @param [in] frameBuffer Target frame buffer.
     */
    virtual void renderLCD_FrameBufferToMemory(const Rect& _rectToUpdate, uint8_t* frameBuffer);

    /**
     * @fn virtual void HALSDL2::disableInterrupts()
     *
     * @brief Disables the DMA and LCD interrupts.
     *
     *        Disables the DMA and LCD interrupts.
     */
    virtual void disableInterrupts()
    {
    }

    /**
     * @fn virtual void HALSDL2::enableInterrupts()
     *
     * @brief Enables the DMA and LCD interrupts.
     *
     *        Enables the DMA and LCD interrupts.
     */
    virtual void enableInterrupts()
    {
    }

    /**
     * @fn virtual void HALSDL2::configureLCDInterrupt()
     *
     * @brief Configures LCD interrupt.
     *
     *        Configures LCD interrupt.
     */
    virtual void configureLCDInterrupt()
    {
    }

    /**
     * @fn virtual void HALSDL2::enableLCDControllerInterrupt()
     *
     * @brief Enables the LCD interrupt.
     *
     *        Enables the LCD interrupt.
     */
    virtual void enableLCDControllerInterrupt()
    {
    }

    /**
     * @fn virtual void HALSDL2::configureInterrupts()
     *
     * @brief Configures the interrupts relevant for TouchGFX.
     *
     *        Configures the interrupts relevant for TouchGFX. This primarily entails setting
     *        the interrupt priorities for the DMA and LCD interrupts.
     */
    virtual void configureInterrupts()
    {
    }

    /**
     * @fn void HALSDL2::performDisplayOrientationChange();
     *
     * @brief Perform the actual display orientation change.
     *
     *        Perform the actual display orientation change.
     */
    void performDisplayOrientationChange();

private:
    uint8_t* scaleTo24bpp(uint16_t* src, uint16_t width, uint16_t height, uint16_t depth);
    uint8_t* doRotate(uint8_t* src, int16_t width, int16_t height);
    void recreateWindow(bool updateContent = true);
    void pushTouch(bool down) const;
    bool popTouch() const;
    void updateTitle(int32_t x, int32_t y) const;
    void alphaChannelCheck(SDL_Surface* surface, bool& isOpaque, bool& hasSemiTransparency);
    void updateCurrentSkin();
    int getCurrentSkinX() const;
    int getCurrentSkinY() const;

    bool debugInfoEnabled;

    float msBetweenTicks;
    float msPassed;

    static uint16_t icon[];
    const char* customTitle;

    class SkinInfo
    {
    public:
        SDL_Surface* surface;
        bool isOpaque;
        bool hasSemiTransparency;
        int offsetX;
        int offsetY;
        SkinInfo() : surface(0), isOpaque(true), hasSemiTransparency(false), offsetX(0), offsetY(0) {}
        virtual ~SkinInfo() {}
    };

    char programPath[300];
    SkinInfo portraitSkin;
    SkinInfo landscapeSkin;
    SkinInfo* currentSkin;
    bool isSkinActive;
    bool borderless;
    bool flashInvalidatedRect;

    bool windowDrag;
    int windowDragX;
    int windowDragY;

    static int32_t _xMouse;
    static int32_t _yMouse;
    static int32_t _x;
    static int32_t _y;
    static bool isWindowBeingDragged;
    static int initialWindowX;
    static int initialWindowY;
    static int initialMouseX;
    static int initialMouseY;
    static bool _lastTouch;
    static bool _touches[5];
    static int _numTouches;

    static uint8_t keyPressed;
};
} // namespace touchgfx

#endif // HALSDL2_HPP
