//
// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

//            Based on ParticleSystem.c from
// Book:      OpenGL(R) ES 2.0 Programming Guide
// Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner
// ISBN-10:   0321502795
// ISBN-13:   9780321502797
// Publisher: Addison-Wesley Professional
// URLs:      http://safari.informit.com/9780321563835
//            http://www.opengles-book.com

#include "SampleApplication.h"

#include "common/vector_utils.h"
#include "shader_utils.h"
#include "random_utils.h"
#include "system_utils.h"
#include "tga_utils.h"

#define _USE_MATH_DEFINES
#include <math.h>

using namespace angle;

class ParticleSystemSample : public SampleApplication
{
  public:
    ParticleSystemSample()
        : SampleApplication("ParticleSystem", 1280, 720)
    {
    }

    bool initialize() override
    {
        const std::string vs = SHADER_SOURCE
        (
            uniform float u_time;
            uniform vec3 u_centerPosition;
            attribute float a_lifetime;
            attribute vec3 a_startPosition;
            attribute vec3 a_endPosition;
            varying float v_lifetime;
            void main()
            {
                if (u_time <= a_lifetime)
                {
                    gl_Position.xyz = a_startPosition + (u_time * a_endPosition);
                    gl_Position.xyz += u_centerPosition;
                    gl_Position.w = 1.0;
                }
                else
                {
                    gl_Position = vec4(-1000, -1000, 0, 0);
                }
                v_lifetime = 1.0 - (u_time / a_lifetime);
                v_lifetime = clamp(v_lifetime, 0.0, 1.0);
                gl_PointSize = (v_lifetime * v_lifetime) * 40.0;
            }
        );

        const std::string fs = SHADER_SOURCE
        (
            precision mediump float;
            uniform vec4 u_color;
            varying float v_lifetime;
            uniform sampler2D s_texture;
            void main()
            {
                vec4 texColor;
                texColor = texture2D(s_texture, gl_PointCoord);
                gl_FragColor = vec4(u_color) * texColor;
                gl_FragColor.a *= v_lifetime;
            }
        );

        mProgram = CompileProgram(vs, fs);
        if (!mProgram)
        {
            return false;
        }

        // Get the attribute locations
        mLifetimeLoc = glGetAttribLocation(mProgram, "a_lifetime");
        mStartPositionLoc = glGetAttribLocation(mProgram, "a_startPosition");
        mEndPositionLoc = glGetAttribLocation(mProgram, "a_endPosition");

        // Get the uniform locations
        mTimeLoc = glGetUniformLocation(mProgram, "u_time");
        mCenterPositionLoc = glGetUniformLocation(mProgram, "u_centerPosition");
        mColorLoc = glGetUniformLocation(mProgram, "u_color");
        mSamplerLoc = glGetUniformLocation(mProgram, "s_texture");

        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

        // Fill in particle data array
        for (size_t i = 0; i < mParticleCount; i++)
        {
            mParticles[i].lifetime = mRNG.randomFloatBetween(0.0f, 1.0f);

            float endAngle                = mRNG.randomFloatBetween(0, 2.0f * float(M_PI));
            float endRadius               = mRNG.randomFloatBetween(0.0f, 2.0f);
            mParticles[i].endPosition.x() = sinf(endAngle) * endRadius;
            mParticles[i].endPosition.y() = cosf(endAngle) * endRadius;
            mParticles[i].endPosition.z() = 0.0f;

            float startAngle                = mRNG.randomFloatBetween(0, 2.0f * float(M_PI));
            float startRadius               = mRNG.randomFloatBetween(0.0f, 0.25f);
            mParticles[i].startPosition.x() = sinf(startAngle) * startRadius;
            mParticles[i].startPosition.y() = cosf(startAngle) * startRadius;
            mParticles[i].startPosition.z() = 0.0f;
        }

        mParticleTime = 1.0f;

        std::stringstream smokeStr;
        smokeStr << angle::GetExecutableDirectory() << "/smoke.tga";

        TGAImage img;
        if (!LoadTGAImageFromFile(smokeStr.str(), &img))
        {
            return false;
        }
        mTextureID = LoadTextureFromTGAImage(img);
        if (!mTextureID)
        {
            return false;
        }

        return true;
    }

    void destroy() override { glDeleteProgram(mProgram); }

    void step(float dt, double totalTime) override
    {
        // Use the program object
        glUseProgram(mProgram);

        mParticleTime += dt;
        if (mParticleTime >= 1.0f)
        {
            mParticleTime = 0.0f;

            // Pick a new start location and color
            Vector3 centerPos(mRNG.randomFloatBetween(-0.5f, 0.5f),
                              mRNG.randomFloatBetween(-0.5f, 0.5f),
                              mRNG.randomFloatBetween(-0.5f, 0.5f));
            glUniform3fv(mCenterPositionLoc, 1, centerPos.data());

            // Random color
            Vector4 color(mRNG.randomFloatBetween(0.0f, 1.0f), mRNG.randomFloatBetween(0.0f, 1.0f),
                          mRNG.randomFloatBetween(0.0f, 1.0f), 0.5f);
            glUniform4fv(mColorLoc, 1, color.data());
        }

        // Load uniform time variable
        glUniform1f(mTimeLoc, mParticleTime);
    }

    void draw() override
    {
        // Set the viewport
        glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());

        // Clear the color buffer
        glClear(GL_COLOR_BUFFER_BIT);

        // Use the program object
        glUseProgram(mProgram);

        // Load the vertex attributes
        glVertexAttribPointer(mLifetimeLoc, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), &mParticles[0].lifetime);
        glVertexAttribPointer(mEndPositionLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), &mParticles[0].endPosition);
        glVertexAttribPointer(mStartPositionLoc, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), &mParticles[0].startPosition);

        glEnableVertexAttribArray(mLifetimeLoc);
        glEnableVertexAttribArray(mEndPositionLoc);
        glEnableVertexAttribArray(mStartPositionLoc);

        // Blend particles
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE);

        // Bind the texture
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTextureID);

        // Set the sampler texture unit to 0
        glUniform1i(mSamplerLoc, 0);

        glDrawArrays(GL_POINTS, 0, mParticleCount);
    }

  private:
    // Handle to a program object
    GLuint mProgram;

    // Attribute locations
    GLint mLifetimeLoc;
    GLint mStartPositionLoc;
    GLint mEndPositionLoc;

    // Uniform location
    GLint mTimeLoc;
    GLint mColorLoc;
    GLint mCenterPositionLoc;
    GLint mSamplerLoc;

    // Texture handle
    GLuint mTextureID;

    // Particle vertex data
    struct Particle
    {
        float lifetime;
        Vector3 startPosition;
        Vector3 endPosition;
    };
    static const size_t mParticleCount = 1024;
    std::array<Particle, mParticleCount> mParticles;
    float mParticleTime;
    RNG mRNG;
};

int main(int argc, char **argv)
{
    ParticleSystemSample app;
    return app.run();
}
