/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-9 by Raw Material Software Ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   JUCE 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.

  ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

  ==============================================================================
*/

#include "../jucedemo_headers.h"

#if JUCE_OPENGL

//==============================================================================
class DemoOpenGLCanvas  : public Component,
                          public OpenGLRenderer,
                          public Timer
{
public:
    DemoOpenGLCanvas()
        : rotation (0.0f),
          textScrollPos (200)
    {
        infoLabel.setText ("These sliders demonstrate how components and 2D graphics can be rendered "
                           "using OpenGL by using the OpenGLContext class.", false);
        infoLabel.setInterceptsMouseClicks (false, false);
        addAndMakeVisible (&infoLabel);
        infoLabel.setBounds ("parent.width * 0.05, bottom - 150, parent.width * 0.4, parent.height - 60");

        speedSlider.setRange (-10.0, 10.0, 0.1);
        speedSlider.setPopupMenuEnabled (true);
        speedSlider.setValue (Random::getSystemRandom().nextDouble() * 3.0, false, false);
        speedSlider.setSliderStyle (Slider::LinearHorizontal);
        speedSlider.setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20);
        addAndMakeVisible (&speedSlider);
        speedSlider.setBounds ("parent.width * 0.05, parent.height - 65, parent.width * 0.6, top + 24");

        sizeSlider.setRange (0.2, 2.0, 0.01);
        sizeSlider.setPopupMenuEnabled (true);
        sizeSlider.setValue (Random::getSystemRandom().nextDouble() + 0.5, false, false);
        sizeSlider.setSliderStyle (Slider::LinearHorizontal);
        sizeSlider.setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20);
        addAndMakeVisible (&sizeSlider);
        sizeSlider.setBounds ("parent.width * 0.05, parent.height - 35, parent.width * 0.6, top + 24");

        openGLContext.setRenderer (this);
        openGLContext.setComponentPaintingEnabled (true);
        openGLContext.attachTo (*this);

        startTimer (1000 / 30);
    }

    ~DemoOpenGLCanvas()
    {
        openGLContext.detach();
    }

    // when the component creates a new internal context, this is called, and
    // we'll use the opportunity to create some images to use as textures.
    void newOpenGLContextCreated()
    {
        logoImage = createLogoImage();
        dynamicTextureImage = Image (Image::ARGB, 128, 128, true, OpenGLImageType());
    }

    void openGLContextClosing()
    {
        // We have to make sure we release any openGL images before the
        // GL context gets closed..
        logoImage = Image::null;
        dynamicTextureImage = Image::null;
    }

    void mouseDown (const MouseEvent& e)
    {
        draggableOrientation.mouseDown (e.getPosition());
    }

    void mouseDrag (const MouseEvent& e)
    {
        draggableOrientation.mouseDrag (e.getPosition());
        openGLContext.triggerRepaint();
    }

    void resized()
    {
        draggableOrientation.setViewport (getLocalBounds());
    }

    void paint (Graphics&) {}

    void renderOpenGL()
    {
        OpenGLHelpers::clear (Colours::darkgrey.withAlpha (1.0f));

        {
            MessageManagerLock mm (Thread::getCurrentThread());
            if (! mm.lockWasGained())
                return;

            updateTextureImage();  // this will update our dynamically-changing texture image.
            drawBackground2DStuff(); // draws some 2D content to demonstrate the OpenGLGraphicsContext class
        }

        // Having used the juce 2D renderer, it will have messed-up a whole load of GL state, so
        // we'll put back any important settings before doing our normal GL 3D drawing..
        glEnable (GL_DEPTH_TEST);
        glDepthFunc (GL_LESS);
        glEnable (GL_BLEND);
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable (GL_TEXTURE_2D);

       #if JUCE_USE_OPENGL_FIXED_FUNCTION
        OpenGLHelpers::prepareFor2D (getWidth(), getHeight());
        OpenGLHelpers::setPerspective (45.0, getWidth() / (double) getHeight(), 0.1, 100.0);

        glTranslatef (0.0f, 0.0f, -5.0f);
        draggableOrientation.applyToOpenGLMatrix();

        // logoImage and dynamicTextureImage are actually OpenGL images, so we can use this utility function to
        // extract the frame buffer which is their backing store, and use it directly.
        OpenGLFrameBuffer* tex1 = OpenGLImageType::getFrameBufferFrom (logoImage);
        OpenGLFrameBuffer* tex2 = OpenGLImageType::getFrameBufferFrom (dynamicTextureImage);

        if (tex1 != nullptr && tex2 != nullptr)
        {
            // This draws the sides of our spinning cube.
            // I've used some of the juce helper functions, but you can also just use normal GL calls here too.
            tex1->draw3D (-1.0f,  1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f, Colours::white);
            tex1->draw3D (-1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, Colours::white);
            tex1->draw3D (-1.0f, -1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f, -1.0f, -1.0f,  1.0f, Colours::white);
            tex2->draw3D (-1.0f,  1.0f, -1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f,  1.0f, -1.0f, Colours::white);
            tex2->draw3D ( 1.0f,  1.0f, -1.0f,  1.0f,  1.0f,  1.0f,  1.0f, -1.0f,  1.0f,  1.0f, -1.0f, -1.0f, Colours::white);
            tex2->draw3D (-1.0f,  1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f,  1.0f, -1.0f,  1.0f,  1.0f, Colours::white);
        }
       #endif
    }

    void updateTextureImage()
    {
        // This image is a special framebuffer-backed image, so when we draw to it, the context
        // will render directly into its framebuffer

        if (dynamicTextureImage.isValid())
        {
            dynamicTextureImage.clear (dynamicTextureImage.getBounds(),
                                       Colours::red.withRotatedHue (fabsf (::sinf (rotation / 300.0f))).withAlpha (0.7f));

            Graphics g (dynamicTextureImage);

            g.setFont (dynamicTextureImage.getHeight() / 3.0f);
            g.setColour (Colours::black);
            drawScrollingMessage (g, dynamicTextureImage.getHeight() / 2);
        }
    }

    void drawBackground2DStuff()
    {
        // Create an OpenGLGraphicsContext that will draw into this GL window..
        ScopedPointer<LowLevelGraphicsContext> glRenderer (createOpenGLGraphicsContext (openGLContext));

        if (glRenderer != nullptr)
        {
            Graphics g (glRenderer);

            // This stuff just creates a spinning star shape and fills it..
            Path p;
            const float scale = getHeight() * 0.4f;
            p.addStar (Point<float> (getWidth() * 0.7f, getHeight() * 0.4f), 7,
                       scale * (float) sizeSlider.getValue(), scale,
                       rotation / 50.0f);

            g.setGradientFill (ColourGradient (Colours::green.withRotatedHue (fabsf (::sinf (rotation / 300.0f))),
                                               0, 0,
                                               Colours::green.withRotatedHue (fabsf (::cosf (rotation / -431.0f))),
                                               0, (float) getHeight(), false));
            g.fillPath (p);
        }
    }

    void timerCallback()
    {
        rotation += (float) speedSlider.getValue();
        textScrollPos += 1.4f;
        openGLContext.triggerRepaint();
    }

private:
    OpenGLContext openGLContext;
    Image logoImage, dynamicTextureImage;
    float rotation, textScrollPos;
    Draggable3DOrientation draggableOrientation;

    Slider speedSlider, sizeSlider;
    Label infoLabel;

    // Functions to create a couple of images to use as textures..
    static Image createLogoImage()
    {
        Image image (Image::ARGB, 256, 256, true, OpenGLImageType());

        if (image.isValid())
        {
            Graphics g (image);

            g.fillAll (Colours::lightgrey.withAlpha (0.8f));
            g.drawImageWithin (ImageFileFormat::loadFrom (BinaryData::juce_png, BinaryData::juce_pngSize),
                               0, 0, image.getWidth(), image.getHeight(), RectanglePlacement::stretchToFit);

            drawRandomStars (g, image.getWidth(), image.getHeight());
        }

        return image;
    }

    static void drawRandomStars (Graphics& g, int w, int h)
    {
        Random r;
        for (int i = 10; --i >= 0;)
        {
            Path pp;
            pp.addStar (Point<float> (r.nextFloat() * w, r.nextFloat() * h), r.nextInt (8) + 3, 10.0f, 20.0f, 0.0f);
            g.setColour (Colours::pink.withAlpha (0.4f));
            g.fillPath (pp);
        }
    }

    void drawScrollingMessage (Graphics& g, int y) const
    {
        g.drawSingleLineText ("The background, foreground and texture are all being drawn using the OpenGLGraphicsContext class, which "
                              "lets you use a standard JUCE 2D graphics context to render directly onto an OpenGL window or framebuffer...  ",
                              (int) -std::fmod (textScrollPos, 2500.0f), y);
    }
};


//==============================================================================
class OpenGLDemo  : public Component
{
public:
    OpenGLDemo()
        : Component ("OpenGL")
    {
        addAndMakeVisible (&canvas);
    }

    void resized()
    {
        canvas.setBounds (10, 10, getWidth() - 20, getHeight() - 50);
    }

private:
    DemoOpenGLCanvas canvas;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLDemo);
};

//==============================================================================
Component* createOpenGLDemo()
{
    return new OpenGLDemo();
}

#endif
