// cRenderClass.cpp
// 
//////////////////////////////////////////////////////////////////////////////////////////
// includes 
//////////////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"

//////////////////////////////////////////////////////////////////////////////////////////
// Declarations 
//////////////////////////////////////////////////////////////////////////////////////////
cRenderClass graphics( SCREEN_WIDTH, SCREEN_HEIGHT );

//////////////////////////////////////////////////////////////////////////////////////////
// cRenderClass() - constructor 
//////////////////////////////////////////////////////////////////////////////////////////
cRenderClass::cRenderClass(const int w, const int h)
{
	m_sw = w;
	m_sh = h;

	int total_count = w*h*3;

	m_buffer = new float[total_count];

	for( int i=0; i<total_count; i++ )
	{
		m_buffer[i] = 0;
	}

	m_point_size = 1;
}

//////////////////////////////////////////////////////////////////////////////////////////
// cRenderClass() - destructor 
//////////////////////////////////////////////////////////////////////////////////////////
cRenderClass::~cRenderClass()
{
}

//////////////////////////////////////////////////////////////////////////////////////////
// loop() - enters game loop
//////////////////////////////////////////////////////////////////////////////////////////
void cRenderClass::loop()
{
	glutMainLoop();
}

//////////////////////////////////////////////////////////////////////////////////////////
// initialize glut stuff
//////////////////////////////////////////////////////////////////////////////////////////
void cRenderClass::create(int argc, _TCHAR* argv[])
{
	// initialise the glut library
	glutInit(&argc, (char**)argv);

	// set up the initial display mode
	// need both double buffering and z-buffering
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

	// set the initial window position
	glutInitWindowPosition(100, 100);

	// set the initial window size
	glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT);

	// create and name the window
	glutCreateWindow("Wow!");

	// reshape callback for current window
	glutReshapeFunc(winReshapeFunc);

	// set display callback for current window
	glutDisplayFunc(renderScene);	

	// set up the global idle callback
	glutIdleFunc(update);
}

//////////////////////////////////////////////////////////////////////////////////////////
// clear() - clears out the render buffer 
//////////////////////////////////////////////////////////////////////////////////////////
void cRenderClass::clear()
{
	memset( m_buffer, 0, sizeof(float)*m_sw*m_sh*3 );
}

//////////////////////////////////////////////////////////////////////////////////////////
// setPointSize() - set the pixel size 
//////////////////////////////////////////////////////////////////////////////////////////
void cRenderClass::setPointSize(int size)
{
	m_point_size = size;
}

//////////////////////////////////////////////////////////////////////////////////////////
// drawPixel() -  
//////////////////////////////////////////////////////////////////////////////////////////
void cRenderClass::drawPixel(int x, int y)
{
	int start, end;

	start = -(m_point_size/2);
	end	= (int)((m_point_size/2.0f) + 0.5);

	for( int i=start; i<end; i++ )
	{
		for( int j=start; j<end; j++ )
		{
			if( (x+j < 0) || (x+j >= m_sw) )
				continue;

			if( (y+i < 0) || (y+i >= m_sh) )
				continue;

			m_buffer[(((y+i)*m_sw+(x+j))*3) + 0] = m_colour.r;
			m_buffer[(((y+i)*m_sw+(x+j))*3) + 1] = m_colour.g;
			m_buffer[(((y+i)*m_sw+(x+j))*3) + 2] = m_colour.b;
		}
	}
}


void cRenderClass::drawPixel(int x, int y, sRGB& colour_, int size_)
{
    int start, end;

    start = -(size_ / 2);
    end = (int)((size_ / 2.0f) + 0.5);

    for (int i = start; i < end; i++)
    {
        for (int j = start; j < end; j++)
        {
            if ((x + j < 0) || (x + j >= m_sw))
                continue;

            if ((y + i < 0) || (y + i >= m_sh))
                continue;

            m_buffer[(((y + i) * m_sw + (x + j)) * 3) + 0] = colour_.r;
            m_buffer[(((y + i) * m_sw + (x + j)) * 3) + 1] = colour_.g;
            m_buffer[(((y + i) * m_sw + (x + j)) * 3) + 2] = colour_.b;
        }
    }
}

//////////////////////////////////////////////////////////////////////////////////////////
// render() - renders this buffer to screen 
//////////////////////////////////////////////////////////////////////////////////////////
void cRenderClass::render()
{
	// clear the back buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glDrawPixels( m_sw, m_sh, GL_RGB, GL_FLOAT, m_buffer );

	// swap the buffers of the current window
	glutSwapBuffers();

	// clear out the buffer
	clear();
}

//////////////////////////////////////////////////////////////////////////////////////////
// setColour() - sets the current colour 
//////////////////////////////////////////////////////////////////////////////////////////
void cRenderClass::setColour(float r, float g, float b)
{
	m_colour.r = r;
	m_colour.g = g;
	m_colour.b = b;
}


// winReshapeFunc() - gets called initially and whenever the window get resized
// resizing has been locked
//////////////////////////////////////////////////////////////////////////////////////////
void winReshapeFunc(GLint w, GLint h)
{
	// specify current matrix
	glMatrixMode(GL_PROJECTION);

	// load an identity matrix
	glLoadIdentity();	

	// create a projection matrix... i.e. 2D projection onto xy plane
	glOrtho( -SCREEN_WIDTH, SCREEN_WIDTH, -SCREEN_HEIGHT, SCREEN_HEIGHT, -100, 100);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// set up the viewport
	glViewport
	(
		0,				// lower left x position
		0,				// lower left y position
		(GLsizei) SCREEN_WIDTH,	// viewport width
		(GLsizei) SCREEN_HEIGHT	// viewport height
	);
}





extern Popcorn* new_a_Popcorn();
extern void popcorn_join(Popcorn* p);

int Popcorn::count = 0;

// A quick operation--helping to darken the eyes
void sRGB::darken(float c_) {
    r *= c_;
    g *= c_;
    b *= c_;
}

// The initialization process is separated from the constructor
// This process includes the random generation of a lot of data
void Popcorn::init(int x_, int y_, int size_, int boom_count_) {
    x = init_x = x_; y = init_y = y_;
    xv = (rand() % xv_range) - xv_range / 2;
    yv = (rand() % yv_range) - yv_range / 2;
    theta = std::atan2(xv, yv);
    t = 0;
    colour = { (float)rand(255,200) / 256, (float)rand(255,125) / 256, (float)rand(255,125) / 256 };
    size = size_;
    boom_count = boom_count_;
    boom_time = rand(max_boom_time, min_boom_time);
    ZeroMemory(&tail[0], sizeof(tail));
}
// Constructor includes increasing the count of the number of global popcorn firework
Popcorn::Popcorn() { init(rand() % 500, 0, init_size, 0); ++count; }
// The destructor includes a count to reduce the number of popcorn fireworks
Popcorn::~Popcorn() { count--; }

// Is it visible
bool Popcorn::invisible() {
    // If all the tail points disappear, it is judged as invisible
    for (int i = 0; i < tail_count; ++i) {
        auto&& x = tail[i].x;
        auto&& y = tail[i].y;
        if (!(x < 0 || x>500 || y < 0 || y>500))
            return false;
    }
    return true;
}

// Update value
void Popcorn::update() {
    auto xt = x;
    auto yt = y;
    // Oblique parabola algorithm
    x = init_x + xv * t;
    y = init_y + yv * t - 0.5 * g * t * t;
    t += dt;

    // Move the trail of the tail back to add a new position
    for (int i = tail_count - 1; i > 0; --i) {
        tail[i] = tail[i - 1];
    }
    tail[0].x = xt;
    tail[0].y = yt;

    if (t > boom_time) {
        boom();
    }
}



void Popcorn::boom() {
    if (boom_count == max_boom_count) {
        return;
    }
    if (x < 0 || y < 0 || x>500 || y>500) {
        return;
    }
    // Generate some new explosives
    auto count = rand(max_boom_popcorn, min_boom_popcorn);
    for (int i = 0; i < count; ++i) {
        auto p = new_a_Popcorn();
        if (p != nullptr) {
            p->init(x, y, 1, boom_count + 1);
            popcorn_join(p);
        }
    }
    // Put this popcorn fireworks outside the screen and wait for it to be recycled during the rendering process
    x = -1;
    y = -1;
}

void Popcorn::draw() {
    // Update rendering values
    update();
    // Let each popcorn firework render itself
    for (int i = tail_count - 1; i >= 0; --i) {
        sRGB temp_colour = colour;
        temp_colour.darken((tail_count - i) / (float)tail_count);
        graphics.drawPixel(tail[i].x, tail[i].y, temp_colour, size);
    }
    //graphics.drawPixel(x, y, colour, size);
}
