/**
  ******************************************************************************
  * This file is part of the TouchGFX 4.16.1 distribution.
  *
  * <h2><center>&copy; Copyright (c) 2021 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
  *
  ******************************************************************************
  */

/**
 * @file touchgfx/widgets/TextureMapper.hpp
 *
 * Declares the touchgfx::TextureMapper class.
 */
#ifndef TEXTUREMAPPER_HPP
#define TEXTUREMAPPER_HPP

#include <math.h> //lint !e829
#include <touchgfx/Bitmap.hpp>
#include <touchgfx/widgets/Image.hpp>

namespace touchgfx
{
/**
 * The TextureMapper widget displays a transformed image. It can be used to generate effects
 * where an image should be rotated in two or three dimensions.
 *
 * The image can be freely scaled and rotated in three dimensions. The scaling and
 * rotation is done around the adjustable origin. A virtual camera is applied to the
 * rendered image yielding a perspective impression. The amount of perspective
 * impression can be adjusted. The transformed image is clipped according to the
 * dimensions of the TextureMapper widget. In order to make the image fully visible the
 * TextureMapper should be large enough to accommodate the transformed image, which may
 * be larger than the raw image.
 *
 * @see Widget
 *
 * @note The drawing of this widget is not trivial and typically has a significant performance
 *       penalty. The number of pixels drawn, the presence of global alpha or per pixel alpha
 *       inflicts the computation and should be considered.
 * @note This widget does not support 1 bit per pixel color depth.
 */
class TextureMapper : public Image
{
public:
    /**
     * Rendering algorithm to use when scaling the bitmap. Nearest neighbor simply finds the
     * closest pixel in the source bitmap. Bilinear interpolation averages 4 pixels to find
     * a much better pixel representation, and is therefore slower than the Nearest neighbor
     * algorithm.
     */
    enum RenderingAlgorithm
    {
        NEAREST_NEIGHBOR,      ///< Fast but not a very good image quality. Good for fast animations.
        BILINEAR_INTERPOLATION ///< Slower but better image quality. Good for static representation of a scaled image.
    };

    /**
     * Constructs a new TextureMapper with a default alpha value of 255 (solid) and a
     * default Bitmap (undefined) if none is specified. If a Bitmap is passed to the
     * constructor, the width and height of this widget is set to those of the bitmap.
     *
     * @param  bitmap (Optional) The bitmap to display.
     *
     * @see setBitmap
     */
    TextureMapper(const Bitmap& bitmap = Bitmap());

    /**
     * Sets the bitmap for this TextureMapper and updates the width and height of this
     * widget to match those of the Bitmap.
     *
     * @param  bitmap The bitmap instance.
     *
     * @note The user code must call invalidate() in order to update the image on the display.
     */
    virtual void setBitmap(const Bitmap& bitmap);

    virtual void draw(const Rect& invalidatedArea) const;

    virtual Rect getSolidRect() const;

    /**
     * Sets the render algorithm to be used. Default setting is NEAREST_NEIGHBOR.
     *
     * @param  algorithm The algorithm to use when rendering.
     */
    virtual void setRenderingAlgorithm(RenderingAlgorithm algorithm)
    {
        currentRenderingAlgorithm = algorithm;
    }

    /**
     * Gets the algorithm used when rendering.
     *
     * @return The algorithm used when rendering.
     */
    virtual RenderingAlgorithm getRenderingAlgorithm() const
    {
        return currentRenderingAlgorithm;
    }

    /**
     * Updates the angles of the image. The area covered by the image before and after
     * changing the angles is invalidated, which is the smallest required rectangle.
     *
     * @param  newXAngle The new x Angle.
     * @param  newYAngle The new y Angle.
     * @param  newZAngle The new x Angle.
     *
     * @see updateXAngle, updateYAngle, updateZAngle, getXAngle, getYAngle, getZAngle
     */
    virtual void updateAngles(float newXAngle, float newYAngle, float newZAngle);

    /**
     * Updates the x angle.
     *
     * @param  newXAngle The new x angle.
     *
     * @see updateAngles, getXAngle
     */
    virtual void updateXAngle(float newXAngle)
    {
        updateAngles(newXAngle, yAngle, zAngle);
    }

    /**
     * Updates the y angle.
     *
     * @param  newYAngle The new y angle.
     *
     * @see updateAngles, getYAngle
     */
    virtual void updateYAngle(float newYAngle)
    {
        updateAngles(xAngle, newYAngle, zAngle);
    }

    /**
     * Updates the z angle.
     *
     * @param  newZAngle The new z angle.
     *
     * @see updateAngles, getZAngle
     */
    virtual void updateZAngle(float newZAngle)
    {
        updateAngles(xAngle, yAngle, newZAngle);
    }

    /**
     * Get the x angle.
     *
     * @return The x angle.
     *
     * @see updateXAngle
     */
    virtual float getXAngle() const
    {
        return xAngle;
    }

    /**
     * Get the y angle.
     *
     * @return The y angle.
     *
     * @see updateYAngle
     */
    virtual float getYAngle() const
    {
        return yAngle;
    }

    /**
     * Get the z angle.
     *
     * @return The z angle.
     *
     * @see updateZAngle
     */
    virtual float getZAngle() const
    {
        return zAngle;
    }

    /**
     * Sets the scale of the image.
     *
     * @param  scale The new scale value.
     *
     * @see getScale
     */
    virtual void setScale(float scale);

    /**
     * Gets the scale of the image.
     *
     * @return The scale.
     *
     * @see setScale
     */
    virtual float getScale() const
    {
        return scale;
    }

    /**
     * Sets the transformation origo (center).
     *
     * @param  x The x coordinate.
     * @param  y The y coordinate.
     * @param  z The z coordinate.
     *
     * @see getOrigoX, getOrigoY, getOrigoZ
     */
    virtual void setOrigo(float x, float y, float z)
    {
        xOrigo = x;
        yOrigo = y;
        zOrigo = z;
        applyTransformation();
    }

    /**
     * Sets the transformation origo (center) in two dimensions. Leaves the z coordinate
     * untouched.
     *
     * @param  x The x coordinate.
     * @param  y The y coordinate.
     *
     * @see getOrigoX, getOrigoY
     */
    virtual void setOrigo(float x, float y)
    {
        xOrigo = x;
        yOrigo = y;
        applyTransformation();
    }

    /**
     * Gets transformation origo x coordinate.
     *
     * @return The transformation origo x coordinate.
     *
     * @see setOrigo
     */
    virtual float getOrigoX() const
    {
        return xOrigo;
    }

    /**
     * Gets transformation origo y coordinate.
     *
     * @return The transformation origo y coordinate.
     *
     * @see setOrigo
     */
    virtual float getOrigoY() const
    {
        return yOrigo;
    }

    /**
     * Gets transformation origo z coordinate.
     *
     * @return The transformation origo z coordinate.
     *
     * @see setOrigo
     */
    virtual float getOrigoZ() const
    {
        return zOrigo;
    }

    /**
     * Sets the camera coordinate.
     *
     * @param  x The x coordinate for the camera.
     * @param  y The y coordinate for the camera.
     *
     * @see getCameraX, getCameraY
     */
    virtual void setCamera(float x, float y)
    {
        xCamera = x;
        yCamera = y;
        applyTransformation();
    }

    /**
     * Gets camera x coordinate.
     *
     * @return The camera x coordinate.
     *
     * @see setCamera
     */
    virtual float getCameraX() const
    {
        return xCamera;
    }

    /**
     * Gets camera y coordinate.
     *
     * @return The camera y coordinate.
     *
     * @see setCamera
     */
    virtual float getCameraY() const
    {
        return yCamera;
    }

    /**
     * Sets camera distance. If the given value is below
     * TextureMapper::MINIMAL_CAMERA_DISTANCE, it will be set to
     * TextureMapper::MINIMAL_CAMERA_DISTANCE.
     *
     * @param  d The new camera distance.
     *
     * @see getCameraDistance
     */
    virtual void setCameraDistance(float d)
    {
        cameraDistance = MAX(d, MINIMAL_CAMERA_DISTANCE);
        applyTransformation();
    }

    /**
     * Gets camera distance.
     *
     * @return The camera distance.
     *
     * @see setCameraDistance
     */
    virtual float getCameraDistance() const
    {
        return cameraDistance;
    }

    /**
     * Sets the position of the bitmap within the TextureMapper. The bitmap is clipped with
     * respect to the dimensions of the TextureMapper widget.
     *
     * @param  x The x coordinate.
     * @param  y The y coordinate.
     *
     * @see getBitmapPositionX, getBitmapPositionY
     */
    virtual void setBitmapPosition(float x, float y)
    {
        xBitmapPosition = x;
        yBitmapPosition = y;
        applyTransformation();
    }

    /**
     * Sets the position of the bitmap within the TextureMapper. The bitmap is clipped with
     * respect to the dimensions of the TextureMapper widget.
     *
     * @param  x The x coordinate.
     * @param  y The y coordinate.
     *
     * @see getBitmapPositionX, getBitmapPositionY
     */
    virtual void setBitmapPosition(int x, int y)
    {
        setBitmapPosition((float)x, (float)y);
    }

    /**
     * Gets bitmap position x coordinate.
     *
     * @return The bitmap position x coordinate.
     *
     * @see setBitmapPosition
     */
    virtual float getBitmapPositionX() const
    {
        return xBitmapPosition;
    }

    /**
     * Gets bitmap position y coordinate.
     *
     * @return The bitmap position y coordinate.
     *
     * @see setBitmapPosition
     */
    virtual float getBitmapPositionY() const
    {
        return yBitmapPosition;
    }

    /**
     * Get the x coordinate of the top left corner of the transformed bitmap.
     *
     * @return The X0 coordinate.
     */
    virtual float getX0() const
    {
        return imageX0;
    }

    /**
     * Get the x coordinate of the top right corner of the transformed bitmap.
     *
     * @return The X1 coordinate.
     */
    virtual float getX1() const
    {
        return imageX1;
    }

    /**
     * Get the x coordinate of the bottom right of the transformed bitmap.
     *
     * @return The X2 coordinate.
     */
    virtual float getX2() const
    {
        return imageX2;
    }

    /**
     * Get the x coordinate of the bottom left corner of the transformed bitmap.
     *
     * @return The X3 coordinate.
     */
    virtual float getX3() const
    {
        return imageX3;
    }

    /**
     * Get the y coordinate of the top left corner of the transformed bitmap.
     *
     * @return The Y0 coordinate.
     */
    virtual float getY0() const
    {
        return imageY0;
    }

    /**
     * Get the y coordinate of the top right corner of the transformed bitmap.
     *
     * @return The Y1 coordinate.
     */
    virtual float getY1() const
    {
        return imageY1;
    }

    /**
     * Get the y coordinate of the bottom right corner of the transformed bitmap.
     *
     * @return The Y2 coordinate.
     */
    virtual float getY2() const
    {
        return imageY2;
    }

    /**
     * Get the y coordinate of the bottom left corner of the transformed bitmap.
     *
     * @return The Y3 coordinate.
     */
    virtual float getY3() const
    {
        return imageY3;
    }

    /**
     * Get the z coordinate of the top left corner of the transformed bitmap.
     *
     * @return The Z0 coordinate.
     */
    virtual float getZ0() const
    {
        return imageZ0;
    }

    /**
     * Get the z coordinate of the top right corner of the transformed bitmap.
     *
     * @return The Z1 coordinate.
     */
    virtual float getZ1() const
    {
        return imageZ1;
    }

    /**
     * Get the z coordinate of the bottom right corner of the transformed bitmap.
     *
     * @return The Z2 coordinate.
     */
    virtual float getZ2() const
    {
        return imageZ2;
    }

    /**
     * Get the z coordinate of the bottom left corner of the transformed bitmap.
     *
     * @return The Z3 coordinate.
     */
    virtual float getZ3() const
    {
        return imageZ3;
    }

    /**
     * Invalidate the bounding rectangle of the transformed bitmap.
     *
     * @see getBoundingRect
     */
    void invalidateBoundingRect() const
    {
        Rect r = getBoundingRect();
        invalidateRect(r);
    }

protected:
    /**
     * Transform the bitmap using the supplied origo, scale, rotation and camera. This
     * method is called by all the methods that manipulate origo, scale, rotation and camera.
     */
    void applyTransformation();

    /**
     * Gets bounding rectangle of the transformed bitmap. This is the smallest possible
     * rectangle which covers the image of the bitmap after applying scale and rotation.
     *
     * @return The bounding rectangle.
     */
    Rect getBoundingRect() const;

    /**
     * The TextureMapper will draw the transformed bitmap by drawing one transformed quad.
     * The quad is drawn from the points 0,1,2,3 using the x,y,z values from each point along
     * with the u,v coordinates in the bitmap associated with each point.
     *
     * @param      invalidatedArea The invalidated area.
     * @param [in] fb              The framebuffer.
     * @param      triangleXs      The triangle xs.
     * @param      triangleYs      The triangle ys.
     * @param      triangleZs      The triangle zs.
     * @param      triangleUs      The triangle us.
     * @param      triangleVs      The triangle vs.
     */
    void drawQuad(const Rect& invalidatedArea, uint16_t* fb, const float* triangleXs, const float* triangleYs, const float* triangleZs, const float* triangleUs, const float* triangleVs) const;

    /**
     * Returns the rendering variant based on the bitmap format, alpha value and rendering
     * algorithm.
     *
     * @return The RenderingVariant.
     */
    RenderingVariant lookupRenderVariant() const;

    RenderingAlgorithm currentRenderingAlgorithm; ///< The current rendering algorithm.

    static const int MINIMAL_CAMERA_DISTANCE = 1; ///< The minimal camera distance

    float xBitmapPosition; ///< The bitmap position x
    float yBitmapPosition; ///< The bitmap position y

    float xAngle; ///< The angle x
    float yAngle; ///< The angle y
    float zAngle; ///< The angle z
    float scale;  ///< The scale

    float xOrigo; ///< The origo x coordinate
    float yOrigo; ///< The origo y coordinate
    float zOrigo; ///< The origo z coordinate

    float xCamera;        ///< The camera x coordinate
    float yCamera;        ///< The camera y coordinate
    float cameraDistance; ///< The camera distance

    float imageX0; ///< The coordinate for the image points
    float imageY0; ///< The coordinate for the image points
    float imageZ0; ///< The coordinate for the image points
    float imageX1; ///< The coordinate for the image points
    float imageY1; ///< The coordinate for the image points
    float imageZ1; ///< The coordinate for the image points
    float imageX2; ///< The coordinate for the image points
    float imageY2; ///< The coordinate for the image points
    float imageZ2; ///< The coordinate for the image points
    float imageX3; ///< The coordinate for the image points
    float imageY3; ///< The coordinate for the image points
    float imageZ3; ///< The coordinate for the image points

    uint16_t subDivisionSize; ///< The size of the affine sub divisions
};

} // namespace touchgfx

#endif // TEXTUREMAPPER_HPP
