//-------------------------------------------------------
//	Defines
//-------------------------------------------------------

// Changes title screen and adds fadeout
#define NOT_FINAL

// This extracts MFC stuff off
#define WIN32_LEAN_AND_MEAN
#define VC_LEANMEAN           

//-------------------------------------------------------
//	Headers
//-------------------------------------------------------

#include <windows.h>
#include <mmsystem.h>
#include <shellapi.h>
#include <stdlib.h>
#include <stdio.h>
#include "resource.h"

#include "effects/kukinto.hpp" 
#include "effects/matka.hpp" 
#include "effects/pensas.hpp" 
#include "effects/displace.hpp"
#include "effects/tuli.hpp"
#include "effects/mustikka.hpp"
#include "effects/pylvaikko.hpp"
#include "effects/revolution.hpp"
#include "effects/synkka.hpp"
#include "effects/synkkakappale.hpp"
#include "effects/keiju.hpp"
#include "effects/jakautuminen.hpp"
#include "effects/myrsky.hpp"
#include "effects/bezierkukka.hpp"
#include "effects/boyc.hpp"
#include "effects/alkulattia.hpp"
#include "effects/loppukuvat.hpp"
#include "effects/sivuttain.hpp"
#include "effects/kliimaksi.hpp"
#include "effects/loppu.hpp"
#include "effects/alkutekstit.hpp"
#include "effects/henget.hpp"
#include "effects/salamat.hpp"
#include "effects/countdown.hpp"
#include "effects/jytky2.hpp"
#include "effects/lopputekstit.hpp"
#include "effects/elokuva.hpp"

#include "effects/jytky.hpp"
//#include "effects/vesi.hpp"

#include "externs.hpp"
#include "config.hpp"
#include "extension_functions.hpp"

#include "primitives.hpp"

//#define _DEBUG
#ifdef _DEBUG
	#include <stdlib.h>
	#include "mmgr.h"
#endif


using namespace TRACTION_DEMOTRACTOR;

Ext glExt;
PostProc filter;
ShaderHolder shaders;
BPM *beat;
BPM *beat_here;
Vector clearcol;
const float FadeOutMaxTime = 1.15f;
bool fadeDemoOut(float &f);

CameraHolder *cam;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{	

    int i = 0;
	int rewind_dir;
	rewind_dir = 0;	

	// Stores messages that windows sends to the application
	MSG msg = {0};			
	HDC hdc = NULL;
	bool done = false;

	clearcol = Vector(0,0,0);
    
	try
	{
 	    // Tee PAK-tiedosto käsin
 
	    dmsMakePAK("resources", "data.pak");
//		dmsSetLogging (true);

//#define RALE_SETTINGS
#ifndef RALE_SETTINGS
        Config cfg;
		cfg.run(); 
		if(cfg.getRunFlag())
#endif
        {


			// Lukee setup.ini:n
			//dmsInit();

			// Tää on tällä hetkellä testatuin.. hoitaa suunilleen kaiken tarvittavan
			// Lukee datat .PAK-tiedostosta ja lisää ne managereille
#ifndef RALE_SETTINGS
            dmsInitEx(cfg.getScreenX(), cfg.getScreenY(), cfg.getBpp(), cfg.getFullscreen(), 
					  cfg.getSound(), cfg.getVsync(), cfg.getOnTop(), cfg.getFsaa(), cfg.getAspectRatio(), "data.pak");
#else
			dmsInitEx(800,600,32 ,false,
					  true, true,false, 1, 0,"data.pak");
#endif


			// Asetetaan ikoni ikkunalle
			SendMessage(dmsGetHWND(),WM_SETICON,ICON_BIG,(LPARAM)LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1)));

			
			dmsAddFont(32, 32, "fontti.pcx");
			dmsAddRenderToTexture(512, 512, "render512_0");
			dmsAddRenderToTexture(512, 512, "render512_1");
			dmsAddRenderToTexture(512, 512, "render512_2");
			
			dmsUploadTextures();
            Primitives::init();
						
			glExt.init();
			shaders.init();
        	cam = new CameraHolder(6);//kuinka monta kameraa
            srand(timeGetTime());

            const int campoints0 = 10;
            const int campoints3 = 9;
            cam->initCamera(0, campoints0, 8500, 104000 ); 
            cam->initCamera(3, campoints3, 258000, 295000); 
            
            //KAMERAT:
            //KAMERA 0 -> ennen tunnelia
            //KAMERA 1 -> kukat
            //KAMERA 2 -> tunnelin jälkeen ennen toista kitarointia
            //KAMERA 3 -> loppuskene
            //KAMERA 4 -> jytky
            //KAMERA 5 -> jytky

            //koordinaatit

            srand(1170);

            for (i = 0; i < campoints0; i++)
            {
                Vector campoint = Mathematics::randVectSphere()*(4+Mathematics::randFloat()*6.5f);
                Vector camtarget = Mathematics::randVectSphere()*2.0f;

                campoint.y /= 4;
                campoint.y += 4;

                if (campoint.y < 2.0f)
                {
                    campoint.y = fabsf(campoint.y);
                    campoint.y *= 2.6f;
                }
                cam->addCameraPoint(0, campoint);
                cam->addCameraTargetPoint(0, camtarget);

                dmsMsg("Vector(%f,%f,%f),\n", campoint.x, campoint.y, campoint.z);

            }

            srand(1611);
            //const int campoints1 = 27;
			const int campoints1 = 35;
            cam->initCamera(1, campoints1, 104000, 138500); 

            float a = Mathematics::randFloat();
            for (i = 0; i < campoints1; i++)
            {
                a += Mathematics::randBetween(1.0f, 1.8f);//i * (1.4f + 0.14f*cosf(i*0.05)) ;
                float r = 4.9f + 0.7f*cosf(i*0.31f);//Mathematics::randBetween(-0.7f, 1.1f);

                Vector campoint = Vector(cosf(a), 0, sinf(a))*r;
                campoint.y = 3.5f + 1.0f*cosf(i*0.8f);//Mathematics::randBetween(-0.4f, 1.6f);

                Vector targetpoint = campoint*0.4f + Mathematics::randVectSphere()*0.5f;
                    
                cam->addCameraPoint(1, campoint + Mathematics::randVectSphere()*1.3f);
                cam->addCameraTargetPoint(1, targetpoint);
            }

            //tässä kamerassa etäisyys hieman kauempana

            srand(887676);
            const int campoints2 = 15;//7;
            cam->initCamera (2, campoints2, 159000, 214700); 
            for (i = 0; i < campoints2; i++)
            {
                if (i == 7)
                {
                    srand(19179);
                }

                Vector campoint = Mathematics::randVectSphere()*(17+Mathematics::randFloat()*6.5f);
                if (campoint.y < 10)
                {
                    campoint.y = fabsf(campoint.y) * 1.0f;

                }
                Vector targetpoint = Mathematics::randVectSphere()*1.0f;
                if (i > 8)
                {
                    targetpoint.y += 3;


                }
                cam->addCameraPoint(2, campoint);
                cam->addCameraTargetPoint(2, targetpoint);
            }


            srand(154);
            const int campoints4 = 6;
            cam->initCamera(4, campoints4, 131500, 159000); 
            for (i = 0; i < campoints4; i++)
            {
                Vector campoint = Mathematics::randVectSphere()*(25+Mathematics::randFloat()*6.5f);
/*
                if (campoint.y < 10)
                {
                    campoint.y = fabsf(campoint.y) * 1.0f;

                }
*/
                cam->addCameraPoint(4, campoint);
                cam->addCameraTargetPoint(4, Mathematics::randVectSphere()*1.0f);
            }

            srand(1019171);
            for (i = 0; i < campoints3; i++)
            {
                Vector campoint = Mathematics::randVectSphere()*(3+Mathematics::randFloat()*6.5f);
                if (campoint.y < 0.8f)
                {
                    campoint.y = fabsf(campoint.y);
                    campoint.y *= 1.6f;
                }
                cam->addCameraPoint(3, campoint);
                cam->addCameraTargetPoint(3, Mathematics::randVectSphere()*1.0f);
            }

            srand(87811);
            const int campoints5 = 6;
            cam->initCamera(5, campoints4, 241000, 258000); 
            for (i = 0; i < campoints5; i++)
            {
                Vector campoint = Mathematics::randVectSphere()*(2+Mathematics::randFloat()*6.5f) + Vector(0, 6-i*0.4f, 0);
                if (campoint.y < 1.8f)
                {
                    campoint.y = fabsf(campoint.y);
                    campoint.y *= 1.2f;
                }
                cam->addCameraPoint(5, campoint);
                cam->addCameraTargetPoint(5, Mathematics::randVectSphere()*1.0f);
            }
 

#define COMPLETE_DEMO
#ifndef COMPLETE_DEMO
            //dmsAddEffect(185000, 214700, 11, "synkka", new Kliimaksi());
/*			dmsAddEffect(76800, 104000, 19, "synkka", new Synkka());
			dmsAddEffect(76800, 107000, 1, "synkkakpl", new Synkkakappale());
			dmsAddEffect(104000, 131000, 10, "jytky2", new Jytky2());
			//dmsAddEffect(258000, 295000, 1, "loppu3d", new Stream());
			//dmsAddEffect(258000, 295000, 1, "loppu3d", new Keiju());
*/			//dmsAddEffect(258000, 295000, 1, "loppu2d", new Lopputekstit());

			//dmsAddEffect(159000, 190000, 17110, "synkka", new Myrsky());
//            dmsAddEffect(185000, 214700, 11, "synkka", new Kliimaksi());
//			dmsAddEffect(213700, 241000, 11000, "laukeaminen", new Sivuttain());
			dmsAddEffect(76500, 104000, 19, "synkka", new Synkka());
            
			dmsAddEffect(104000, 131000, 160000, "salamat", new Salamat());
//			dmsAddEffect(104000, 131000, 10, "jytky2", new Jytky2());
			dmsAddEffect(104000, 131500, 10, "elokuva", new Elokuva());
			dmsAddEffect(258000, 295000, 1, "loppu3d", new Stream());
			//dmsAddEffect(258000, 295000, 1, "loppu2d", new Lopputekstit());


#else

			dmsAddEffect(2000, 19000, 0, "tekstit", new Alkutekstit());
			// Rauli TODO:
			// - Jytky uus tausta
			// - Synkkitaulut!
			// - Poista turhat shaderit
			// - Loppuscrolleri + partkkelien fillausalue leveys paremmaksi

            //Martti TODO:
            // - kamerat
			// - Myrsky partikkelifiksi, img + liike

            /*

             [fixed]   12:22 <rale> 1. mustikka effus osa mustikoist on maaplanen alla
             [fixed]   12:22 <rale> 2. bezierkukat effus maaplane vois olla teksturoitu jollain ruohosetillä
             [fixed]   12:22 <rale> 3. bezierit toka vikaks kuvien tilalle ja uus effu bezierin tilalle?
             [fixed]   12:22 <rale> 4. myrskyyn lisää frameja
                12:22 <rale> 5. jytkyn modeli näyttää skeidalta
                12:23 <rale> 6. alkuun jotain taivaalle?
             [fixed, tavallaan]   12:23 <rale> 7. tuli effuun spiraali maahan
                12:23 <rale> 8. lisää spiraaleja
                12:23 <rale> 9. transitio joka tuo kameran sivuttain effuun on melko kauhea

  Martille 1, 3, 4, 7, 8




  */

            dmsAddEffect(8500, 33500, 10, "revolution", new Revolution());
			dmsAddEffect(9000, 50000, 1600, "lattia", new Alkulattia());
			dmsAddEffect(28000, 51500, 16, "mustikat", new Mustikka());
            dmsAddEffect(48500, 76500, 11, "tuli", new Tuli());
            dmsAddEffect(62000, 75000, 666, "henget", new Henget());
			//dmsAddEffect(76800, 107000, 6, "countdown", new Countdown());
			dmsAddEffect(76500, 107000, 1, "synkkakpl", new Synkkakappale());
			dmsAddEffect(76500, 104000, 19, "synkka", new Synkka());
            
			dmsAddEffect(104000, 131000, 160000, "salamat", new Salamat());
//			dmsAddEffect(104000, 131000, 10, "jytky2", new Jytky2());
			dmsAddEffect(104000, 131500, 10, "elokuva", new Elokuva());

            dmsAddEffect(131500, 159000, 0, "jytky", new Jytky());
            dmsAddEffect(159000, 190000, 17110, "synkka", new Myrsky());
            dmsAddEffect(185000, 214700, 11, "synkka", new Kliimaksi());
            dmsAddEffect(213700, 241000, 11000, "laukeaminen", new Sivuttain());
            dmsAddEffect(241000, 258000, 11000, "kukat", new BezierKukka());
			dmsAddEffect(258000, 19000, 0, "tekstit", new Alkutekstit());
			dmsAddEffect(258000, 295000, 1, "loppu3d", new Stream());
//			dmsAddEffect(258000, 295000, 1, "loppu2d", new Lopputekstit());

#endif
            
			beat = new BPM(130, 0);
			dmsInitEffects();
			 
			// Tää bugaa
			//dmsSetGamma(cfg.getGamma());
			dmsInitTimer();
			dmsPlaySong(true);
		}
#ifndef RALE_SETTINGS
		else
		{
			done = true;
		}
#endif		

		float fadeOutTime=0;
		// Demo loop
		while(!done)
		{

			// fadein!	
#ifndef RALE_SETTINGS
/*
            if(dmsGetTime()<10.0f && cfg.getSound())
			{
				const float volume = powf(Mathematics::calcPosFloat(dmsGetTime(), 0, 9.0f),2);
				dmsSetVolume(volume);
			}
*/
#endif

			// Check for Windows messages in queue
			if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{

				//If app was closed send a note to the demo to stop the Demo loop
				if(msg.message == WM_QUIT)
				{
					//done = true;
					fadeOutTime = FadeOutMaxTime;
				} else if(msg.message == WM_KEYDOWN) {
					const float jump = 1000.0f;
					if(msg.wParam == VK_ESCAPE)		fadeOutTime = FadeOutMaxTime;
					else if(msg.wParam == VK_LEFT)	rewind_dir = -1;
					else if(msg.wParam == VK_RIGHT) rewind_dir = 1;
				} else if(msg.message == WM_KEYUP) {
					if(msg.wParam == VK_LEFT || msg.wParam == VK_RIGHT) 
						rewind_dir = 0;

				} else {
					// Other messages are handled by Windows own handlers
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
			// No messages left, we can do some own code now which means
			// actually running the demo :)
			else
			{	
				dmsUpdateTimer();
                cam->update();
				 
#ifdef NOT_FINAL
				if(rewind_dir!=0) {
					dmsAdjustTime(rewind_dir*1000.0f);
				}
#endif

				glClearColor(clearcol.x,clearcol.y,clearcol.z,0);
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);



//                filter.init(true);
				dmsRun();
                /*

                glEnable(GL_TEXTURE_2D);
                glEnable(GL_BLEND);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

                glBindTexture(GL_TEXTURE_2D, dmsGetTexture("foreground.png")->getID());
                glDisable(GL_DEPTH_TEST);
                glDepthMask(0);
                dmsPerspective2D(1,1);
                glColor4f(1,1,1,1);

                glLoadIdentity();
                glBegin(GL_QUADS);
                glTexCoord2f(0, 0);
                glVertex2f(0, 0);
                glTexCoord2f(1, 0);
                glVertex2f(1, 0);
                glTexCoord2f(1, 1);
                glVertex2f(1, 1);
                glTexCoord2f(0, 1);
                glVertex2f(0, 1);
                glEnd();

                glDepthMask(1);
                dmsPerspective3D();
                glLoadIdentity();
                */              
                //                filter.glow(6, 0.005f, 0.005f, 0.85f, -1.0f, 1.0f);
//                dmsMsg("frame : %d\n", dmsGetModulePosition());

		#ifdef NOT_FINAL
					char buf[256] = {0};
					sprintf(buf, "Time: %f s | FPS: %0.2f", dmsGetModulePosition() / 1000.0f, dmsGetFPS() );
					dmsSetWindowTitle(buf);
					if(fadeOutTime>0) 
						done = true;
		#else
					// smooth fade out
					if(fadeOutTime>0) 
						done = fadeDemoOut(fadeOutTime);
		#endif	

				// TODO: Fix end time
				if (dmsGetModulePosition() > 295500) 
					done = true;

				hdc = dmsGetHDC();
				SwapBuffers(hdc);		
			}
		}	
	}
	catch(const char *msg)
	{
		dmsMsg("Exception: %s\n", msg);
	}

	glExt.deinit();
	shaders.deinit();
	
    delete beat;
    delete beat_here;
	dmsSetGamma(0);
	dmsShutDown();

	// This application is done and we let the Windows know that
	return msg.wParam;
}


bool fadeDemoOut(float &fadeOutTime)
{
		glLoadIdentity();
		dmsPerspective2D(1, 1);

		glDisable(GL_TEXTURE_2D);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		const float fadeout = powf(Mathematics::calcPosCos(fadeOutTime, 0, FadeOutMaxTime),2);
		glColor4f(clearcol.x, clearcol.y,clearcol.z, 1-fadeout);
		glBegin(GL_QUADS);
			glVertex2f(0, 0);
			glVertex2f(1, 0);
			glVertex2f(1, 1);
			glVertex2f(0, 1);
		glEnd();
		dmsPerspective3D();
		glLoadIdentity();

		dmsSetVolume(fadeout);
			
		fadeOutTime -= dmsGetDeltaTime();
		glEnable(GL_TEXTURE_2D);

		if(fadeOutTime<=0) return true;
		else return false;
}
