/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
 * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
 * Copyright (C) 2013 CERN
 * @author Maciej Suminski <maciej.suminski@cern.ch>
 *
 * Graphics Abstraction Layer (GAL) for OpenGL
 *
 * This program is free software; 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.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#ifndef OPENGLGAL_H_
#define OPENGLGAL_H_

// GAL imports
#include <gal/graphics_abstraction_layer.h>
#include <GL/glew.h>

// OpenGL mathematics library
#define GLM_FORCE_RADIANS
#include <gal/opengl/glm/gtc/matrix_transform.hpp>

#include <gal/opengl/vbo_container.h>
#include <gal/opengl/shader.h>

// wxWidgets imports
#include <wx/wx.h>
#include <wx/glcanvas.h>

// STL imports
#include <cmath>
#include <iterator>
#include <vector>
#include <algorithm>
#include <boost/unordered_map.hpp>

#include <stdlib.h>
#include <iostream>
#include <fstream>


namespace KiGfx
{
class SHADER;

/**
 * @brief Class OpenGL_GAL is the OpenGL implementation of the Graphics Abstraction Layer.
 *
 * This is a direct OpenGL-implementation and uses low-level graphics primitives like triangles
 * and quads. The purpuse is to provide a fast graphics interface, that takes advantage of modern
 * graphics card GPUs. All methods here benefit thus from the hardware acceleration.
 */
class OPENGL_GAL : public GAL, public wxGLCanvas
{
public:

    /**
     * @brief Constructor OPENGL_GAL
     *
     * @param aParent is the wxWidgets immediate wxWindow parent of this object.
     *
     * @param aMouseListener is the wxEvtHandler that should receive the mouse events,
     *  this can be can be any wxWindow, but is often a wxFrame container.
     *
     * @param aPaintListener is the wxEvtHandler that should receive the paint
     *  event.  This can be any wxWindow, but is often a derived instance
     *  of this class or a containing wxFrame.  The "paint event" here is
     *  a wxCommandEvent holding EVT_GAL_REDRAW, as sent by PostPaint().
     *
     * @param aName is the name of this window for use by wxWindow::FindWindowByName()
     *
     * @param isUseShaders is a flag, that  indicates, if shaders should be used
     *        for higher quality rendering.
     *
     */
    OPENGL_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener = NULL,
                wxEvtHandler* aPaintListener = NULL, bool isUseShaders = false,
                const wxString& aName = wxT( "GLCanvas" ) );

    virtual ~OPENGL_GAL();

    // ---------------
    // Drawing methods
    // ---------------

    /// @copydoc GAL::BeginDrawing()
    virtual void BeginDrawing();

    /// @copydoc GAL::EndDrawing()
    virtual void EndDrawing();

    /// @copydoc GAL::DrawLine()
    virtual void DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint );

    /// @copydoc GAL::DrawSegment()
    virtual void DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint, double aWidth );

    /// @copydoc GAL::DrawPolyline()
    virtual void DrawPolyline( std::deque<VECTOR2D>& aPointList );

    /// @copydoc GAL::DrawCircle()
    virtual void DrawCircle( const VECTOR2D& aCenterPoint, double aRadius );

    /// @copydoc GAL::DrawArc()
    virtual void
    DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double aStartAngle, double aEndAngle );

    /// @copydoc GAL::DrawRectangle()
    virtual void DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint );

    /// @copydoc GAL::DrawPolygon()
    virtual void DrawPolygon( const std::deque<VECTOR2D>& aPointList );

    /// @copydoc GAL::DrawCurve()
    virtual void DrawCurve( const VECTOR2D& startPoint, const VECTOR2D& controlPointA,
                            const VECTOR2D& controlPointB, const VECTOR2D& endPoint );

    // --------------
    // Screen methods
    // --------------

    /// @brief Resizes the canvas.
    virtual void ResizeScreen ( int aWidth, int aHeight );

    /// @brief Shows/hides the GAL canvas
    virtual bool Show( bool aShow );

    /// @copydoc GAL::Flush()
    virtual void Flush();

    /// @copydoc GAL::ClearScreen()
    virtual void ClearScreen();

    // -----------------
    // Attribute setting
    // -----------------

    /// @copydoc GAL::SetIsFill()
    virtual void SetIsFill( bool aIsFillEnabled )
    {
        isFillEnabled = aIsFillEnabled;
    }

    /// @copydoc GAL::SetIsStroke()
    virtual void SetIsStroke( bool aIsStrokeEnabled )
    {
        isStrokeEnabled = aIsStrokeEnabled;
    }

    /// @copydoc GAL::SetFillColor()
    virtual void SetFillColor( const COLOR4D& aColor );

    /// @copydoc GAL::SetStrokeColor()
    virtual void SetStrokeColor( const COLOR4D& aColor );

    /// @copydoc GAL::GetStrokeColor()
    COLOR4D      GetStrokeColor();

    /// @copydoc GAL::SetBackgroundColor()
    virtual void SetBackgroundColor( const COLOR4D& aColor );

    /// @copydoc GAL::SetLineWidth()
    virtual void SetLineWidth( double aLineWidth );

    /// @copydoc GAL::GetLineWidth()
    double       GetLineWidth();

    /// @copydoc GAL::SetLayerDepth()
    virtual void SetLayerDepth( double aLayerDepth )
    {
        super::SetLayerDepth( aLayerDepth );
    }

    // --------------
    // Transformation
    // --------------

    /// @copydoc GAL::Transform()
    virtual void Transform( MATRIX3x3D aTransformation );

    /// @copydoc GAL::Rotate()
    virtual void Rotate( double aAngle );

    /// @copydoc GAL::Translate()
    virtual void Translate( const VECTOR2D& aTranslation );

    /// @copydoc GAL::Scale()
    virtual void Scale( const VECTOR2D& aScale );

    /// @copydoc GAL::Save()
    virtual void Save();

    /// @copydoc GAL::Restore()
    virtual void Restore();

    // --------------------------------------------
    // Group methods
    // ---------------------------------------------

    /// @copydoc GAL::BeginGroup()
    virtual int BeginGroup();

    /// @copydoc GAL::EndGroup()
    virtual void EndGroup();

    /// @copydoc GAL::DrawGroup()
    virtual void DrawGroup( int aGroupNumber );

    /// @copydoc GAL::ChangeGroupColor()
    virtual void ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor );

    /// @copydoc GAL::ChangeGroupDepth()
    virtual void ChangeGroupDepth( int aGroupNumber, int aDepth );

    /// @copydoc GAL::DeleteGroup()
    virtual void DeleteGroup( int aGroupNumber );

    /// @copydoc GAL::ClearCache()
    virtual void ClearCache();

    // --------------------------------------------------------
    // Handling the world <-> screen transformation
    // --------------------------------------------------------

    /// @copydoc GAL::ComputeWorldScreenMatrix()
    virtual void ComputeWorldScreenMatrix();

    /// @copydoc GAL::GetWorldScreenMatrix()
    MATRIX3x3D GetWorldScreenMatrix();

    /// @copydoc GAL::SetWorldScreenMatrix()
    void SetWorldScreenMatrix( MATRIX3x3D aMatrix );

    /// @copydoc GAL::SetWorldUnitLength()
    void SetWorldUnitLength( double aWorldUnitLength );

    /// @copydoc GAL::SetScreenDPI()
    void SetScreenDPI( double aScreenDPI );

    /// @copydoc GAL::SetLookAtPoint()
    void SetLookAtPoint( const VECTOR2D& aPoint );

    /// @copydoc GAL::GetLookAtPoint()
    VECTOR2D GetLookAtPoint();

    /// @copydoc GAL::SetZoomFactor()
    void SetZoomFactor( double aZoomFactor );

    /// @copydoc GAL::GetZoomFactor()
    double GetZoomFactor();

    /// @copydoc GAL::SaveScreen()
    virtual void SaveScreen();

    /// @copydoc GAL::RestoreScreen()
    virtual void RestoreScreen();

    // -------
    // Cursor
    // -------

    /// @copydoc GAL::ComputeCursorToWorld()
    virtual VECTOR2D ComputeCursorToWorld( const VECTOR2D& aCursorPosition );

    /// @copydoc GAL::SetIsCursorEnabled()
    void SetIsCursorEnabled( bool aIsCursorEnabled );

    /// @copydoc GAL::DrawCursor()
    virtual void DrawCursor( VECTOR2D aCursorPosition );

    /**
     * @brief Function PostPaint
     * posts an event to m_paint_listener.  A post is used so that the actual drawing
     * function can use a device context type that is not specific to the wxEVT_PAINT event.
     */
    void PostPaint()
    {
        if( paintListener )
        {
            wxPaintEvent redrawEvent;
            wxPostEvent( paintListener, redrawEvent );
        }
    }

    void SetMouseListener( wxEvtHandler* aMouseListener )
    {
        mouseListener = aMouseListener;
    }

    void SetPaintListener( wxEvtHandler* aPaintListener )
    {
        paintListener = aPaintListener;
    }

    ///< Parameters passed to the GLU tesselator
    typedef struct
    {
        VBO_ITEM* vboItem;                        ///< VBO_ITEM for storing new vertices
        std::vector<GLdouble*>& intersectPoints;  ///< Intersect points, that have to be freed
    } TessParams;

protected:
    virtual void DrawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint );

private:
    /// Super class definition
    typedef GAL super;

    static const int    CIRCLE_POINTS   = 64;   ///< The number of points for circle approximation
    static const int    CURVE_POINTS    = 32;   ///< The number of points for curve approximation
    static const double MITER_LIMIT     = 1.5;  ///< Limit for mitered edges ( * lineWidth )

    /// This factor is used to for correct merging of antialiased edges,
    /// a very small value is required
    static const double DEPTH_ADJUST_FACTOR = ( 1.0 / (1 << 23) );

    wxClientDC*     clientDC;               ///< Drawing context
    wxGLContext*    glContext;              ///< OpenGL context of wxWidgets
    wxWindow*       parentWindow;           ///< Parent window
    wxEvtHandler*   mouseListener;
    wxEvtHandler*   paintListener;

    // VBO buffered vertices for faster circle & semicircle drawing
    VBO_CONTAINER         precomputedContainer;   ///< Container for storing display lists
    VBO_ITEM              verticesCircle;         ///< Buffer for circle & semicircle vertices

    // Vertex buffer objects related fields
    typedef boost::unordered_map<unsigned int, VBO_ITEM*> GroupsMap;
    GroupsMap             groups;                 ///< Stores informations about VBO objects (groups)
    unsigned int          groupCounter;           ///< Counter used for generating keys for groups
    VBO_ITEM*             currentItem;            ///< Currently used VBO_ITEM (for grouping)
    VBO_CONTAINER*        currentContainer;       ///< Currently used VBO_CONTAINER (for storing VBO_ITEMs)
    VBO_CONTAINER         cachedVbo;              ///< Container for storing VBO_ITEMs
    GLuint                cachedVerts;            ///< Currently used vertices VBO handle
    GLuint                cachedInds;             ///< Currently used indices VBO handle
    bool                  vboNeedsUpdate;         ///< Flag indicating if VBO should be rebuilt
    VBO_CONTAINER         nonCachedVbo;           ///< Container for storing non-cached VBO_ITEMs
    VBO_ITEM*             nonCachedItem;          ///< Item that is gathering non-cached vertices

    glm::mat4             transform;              ///< Current transformation matrix
    std::stack<glm::mat4> transformStack;         ///< Stack of transformation matrices
    int                   indicesSize;            ///< Number of indices to be drawn
    GLuint*               indicesPtr;             ///< Pointer to mapped GPU memory

    // Polygon tesselation
    GLUtesselator*        tesselator;             ///< Pointer to the tesselator
    std::vector<GLdouble*> tessIntersects;        ///< Storage of intersecting points

    // Shader
    // Possible types of shaders
    typedef enum
    {
        SHADER_NONE             = 0,
        SHADER_LINE,
        SHADER_FILLED_CIRCLE,
        SHADER_STROKED_CIRCLE,
    } SHADER_TYPE;

    SHADER          shader;                 ///< There is only one shader used for different objects
    int             shaderAttrib;           ///< Location of shader attributes (for glVertexAttribPointer)

    // Cursor
    int             cursorSize;             ///< Size of the cursor in pixels
    GLubyte*        cursorShape;            ///< Cursor pixel storage
    GLubyte*        cursorSave;             ///< Saved cursor pixels
    bool            isDeleteSavedPixels;    ///< Flag for deleting saved pixels
    VECTOR2D        savedCursorPosition;    ///< Last saved cursor position

    // Frame buffer
    GLuint          frameBuffer;            ///< Main FBO handle
    GLuint          depthBuffer;            ///< Depth buffer handle
    GLuint          texture;                ///< Main texture handle
    GLuint          frameBufferBackup;      ///< Backup FBO handle
    GLuint          depthBufferBackup;      ///< Backup depth buffer handle
    GLuint          textureBackup;          ///< Backup texture handle

    // Internal flags
    bool            isGlewInitialized;          ///< Is GLEW initialized?
    bool            isFrameBufferInitialized;   ///< Are the frame buffers initialized?
    bool            isVboInitialized;
    bool            isShaderInitialized;        ///< Was the shader initialized?
    bool            isUseShader;                ///< Should the shaders be used?
    bool            isGrouping;                 ///< Was a group started?

    /**
     * @brief Draw a semi circle. Depending on settings (isStrokeEnabled & isFilledEnabled) it runs
     * the proper function (drawStrokedSemiCircle or drawFilledSemiCircle).
     *
     * @param aCenterPoint is the center point.
     * @param aRadius is the radius of the semi-circle.
     * @param aAngle is the angle of the semi-circle.
     *
     */
    void drawSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle );

    /**
     * @brief Draw a filled semi circle.
     *
     * @param aCenterPoint is the center point.
     * @param aRadius is the radius of the semi-circle.
     * @param aAngle is the angle of the semi-circle.
     *
     */
    void drawFilledSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle );

    /**
     * @brief Draw a stroked semi circle.
     *
     * @param aCenterPoint is the center point.
     * @param aRadius is the radius of the semi-circle.
     * @param aAngle is the angle of the semi-circle.
     *
     */
    void drawStrokedSemiCircle( const VECTOR2D& aCenterPoint, double aRadius, double aAngle );

    /// Compute the points of an unit circle & semicircle and store them in VBO.
    void computeCircleVbo();

    // Event handling
    /**
     * @brief This is the window creation event handler.
     *
     * @param aEvent is the window creation event.
     */
    void onCreate( wxWindowCreateEvent& aEvent );

    /**
     * @brief This is the OnPaint event handler.
     *
     * @param aEvent is the OnPaint event.
     */
    void onPaint( wxPaintEvent& aEvent );

    /**
     * @brief Skip the mouse event to the parent.
     *
     * @param aEvent is the mouse event.
     */
    void skipMouseEvent( wxMouseEvent& aEvent );

    /// Initialize GLEW.
    void initGlew();

    /**
     * @brief Initialize the cursor.
     *
     * @param aCursorSize is the cursor size in pixels (screen coordinates).
     */
    void initCursor( int aCursorSize );

    /**
     * @brief Blit the main texture to the screen.
     *
     * @param aIsClearFrameBuffer if true, the frame buffer is cleared as well.
     */
    void blitMainTexture( bool aIsClearFrameBuffer );

    /// @brief Initialize the frame buffers for main contents and backup storage.
    void initFrameBuffers();

    /**
     * @brief Generate a frame buffer for the screen contents.
     *
     * @param aFrameBuffer is the pointer to the frame buffer handle.
     * @param aDepthBuffer is the pointer to the depth buffer handle.
     * @param aTexture is the pointer to the texture handle.
     */
    void generateFrameBuffer( GLuint* aFrameBuffer, GLuint* aDepthBuffer, GLuint* aTexture );

    /**
     * @brief Delete the frame buffer for the screen contents.
     *
     * @param aFrameBuffer is the pointer to the frame buffer handle.
     * @param aDepthBuffer is the pointer to the depth buffer handle.
     * @param aTexture is the pointer to the texture handle.
     */
    void deleteFrameBuffer( GLuint* aFrameBuffer, GLuint* aDepthBuffer, GLuint* aTexture );

    /**
     * @brief Initializes everything needed to use vertex buffer objects.
     */
    void initVertexBufferObjects();

    /**
     * @brief Deinitializes everything when vertex buffer objects are not used anymore.
     */
    void deleteVertexBufferObjects();

    /**
     * @brief Rebuilds vertex buffer object using stored VBO_ITEMS and sends it to
     * the graphics card memory.
     */
    void rebuildVbo();

    /**
     * @brief Draw a quad for the line.
     *
     * @param aStartPoint is the start point of the line.
     * @param aEndPoint is the end point of the line.
     */
    inline void drawLineQuad( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint );

    /**
     * @brief Returns a valid key that can be used as a group number.
     *
     * @return An unique group number that is not used by any other group.
     */
    unsigned int getGroupNumber();

    /**
     * @brief Adds vertex to the current item or draws it in immediate mode.
     * @param aX is X coordinate.
     * @param aY is Y coordinate.
     * @param aZ is Z coordinate.
     */
    inline void vertex3( double aX, double aY, double aZ )
    {
        // New vertex coordinates for VBO
        const VBO_VERTEX vertex = { aX, aY, aZ };
        currentItem->PushVertex( &vertex );
    }

    /**
     * @brief Function that replaces glTranslate. It modifies transformation matrix.
     *
     * @param aX is translation in X axis direction.
     * @param aY is translation in Y axis direction.
     * @param aZ is translation in Z axis direction.
     */
    inline void translate3( double aX, double aY, double aZ )
    {
        transform = glm::translate( transform, glm::vec3( aX, aY, aZ ) );
    }

    /**
     * @brief Function that replaces glColor. It modifies color used by current VBO_ITEM.
     *
     * @param aR is red component.
     * @param aG is green component.
     * @param aB is blue component.
     * @param aA is alpha component.
     */
    inline void color4( double aRed, double aGreen, double aBlue, double aAlpha )
    {
        currentContainer->UseColor( aRed, aGreen, aBlue, aAlpha );
    }

    /**
     * @brief Function that replaces glColor. It modifies color used by current VBO_ITEM.
     *
     * @param aColor is the new color.
     */
    inline void color4( const COLOR4D& aColor )
    {
        currentContainer->UseColor( aColor );
    }

    /**
     * @brief Function that sets shader and its parameters for the currently used VBO_ITEM.
     * It should be used before adding any vertices that have to be shaded.
     * @param aShader is the type of shader used for vertices.
     * @param aParam[1..3] are shader's parameters. Their meaning depends on the type of used shader.
     * For more information you may check shaders' source code.
     */
    inline void setShader( SHADER_TYPE aShader, GLfloat aParam1 = 0.0f,
                           GLfloat aParam2 = 0.0f, GLfloat aParam3 = 0.0f )
    {
        if( isUseShader )
        {
            const GLfloat shader[] = { aShader, aParam1, aParam2, aParam3 };
            currentContainer->UseShader( shader );
        }
    }
};
} // namespace KiGfx

#endif // OPENGLGAL_H_
