///////////////////////////////////////////////////////////
//                                                       //
//  PC MAGAZIN - Demo Projekt                            //
//                                                       //
//  Ein kleines Beispieldemo, um die Musik-              //
//  synchronisation zu zeigen                            //
//                                                       //
///////////////////////////////////////////////////////////

#include "demo.h"
#include <mmsystem.h>
#include <stdio.h>
#include "effekte.h"
#include "grid.h"
#include "sinplas.h"

unsigned short *screen;

// Dies ist die Zeiteinheit für das Musiktiming
// Das ist die Zeit in ms für eine Songposition des Rave eJay
#define SPEED 1333

// Variablen für Wave-Output
unsigned char   *wavedata;
signed short	*wave16bit;
FILE			*wavefile;
WAVEFORMATEX	format;
HWAVEOUT		wavehandle;
WAVEHDR			wheader;
MMTIME			mmtime;
int				mmcode;
char			mmerror[1024];

// Variablen für Bitmapeffekte
static unsigned short	*shadepalette = NULL;
static bitmaptype		texture;
short					plasmapalette[ 256 ];

// Variablen für Bitmaps
short			palette1[ 32 ][ 256 ];
bitmaptype		bmpheader1;
unsigned char	*bmp1;
short			palette2[ 32 ][ 256 ];
bitmaptype		bmpheader2;
unsigned char	*bmp2;
short			palette3[ 32 ][ 256 ];
bitmaptype		bmpheader3;
unsigned char	*bmp3;
short			palette4[ 32 ][ 256 ];
bitmaptype		bmpheader4;
unsigned char	*bmp4;

// Prototypes für die verwendeten Effektprozeduren
void LoadBitmap (char *name);
void makeShadingTable( bitmaptype bmpheader, short palette[32][256] );
void UVG_Effekt (int shade, float time, void (* Function)(float, GridPointUVG *));
void Feedback_Effekt (float Laufzeit);

extern HWND DemoHWND;

BOOL demoinit (void)
{
    Fenster_Modus = FENSTER;

	// Speicher für das Bild reservieren
    screen = (unsigned short *)malloc( SCREEN_X * SCREEN_Y * 2 );
    if ( screen == NULL ) return 0;
    memset (screen, 0, SCREEN_X * SCREEN_Y * 2);

    // BMP-Files einlesen
    if ( bmp_load( "x_logo1.BMP", bmpheader1 ) !=
         BMP_NOERROR ) return 0;
    if ( bmp_load( "x_logo2.BMP", bmpheader2 ) !=
         BMP_NOERROR ) return 0;
    if ( bmp_load( "logo3.BMP", bmpheader3 ) !=
         BMP_NOERROR ) return 0;
    if ( bmp_load( "logo3a.BMP", bmpheader4 ) !=
         BMP_NOERROR ) return 0;
    bmp1 = (unsigned char *)bmpheader1.cBitmap;
    bmp2 = (unsigned char *)bmpheader2.cBitmap;
    bmp3 = (unsigned char *)bmpheader3.cBitmap;
    bmp4 = (unsigned char *)bmpheader4.cBitmap;
	makeShadingTable( bmpheader1, palette1 );
	makeShadingTable( bmpheader2, palette2 );
	makeShadingTable( bmpheader3, palette3 );
	makeShadingTable( bmpheader4, palette4 );

    // Bild für den 8x8 Effekt laden
	LoadBitmap ("chess.bmp");

	// Sinusplasma initialisieren
	init_sineplasma ();

	for ( int i = 0; i < 256; i++ )
	{
		plasmapalette[ i ] = ColorCode( i*i/256, i, i );
	}
	
	// Sampledaten einlesen
	wavefile = fopen( "EJAYYLAW.RAW", "rb" );
	if ( wavefile == NULL ) return 0;

	fseek( wavefile, 0, SEEK_END );
	int size = ftell( wavefile );
	fseek( wavefile, 0, SEEK_SET );

	wavedata = (unsigned char*)malloc( size );
	wave16bit = (signed short*)malloc( sizeof(short)*size );
	if ( wavedata == NULL || wave16bit == NULL ) return 0;

	fread( wavedata, 1, size, wavefile );

	fclose( wavefile );

	// My-Law Kompression rückgängig machen
	int table[ 256 ];
	for ( i = 0; i < 256; i++ )
		table[ i ] = (int)( 65535.0 * log10( 1.0 + (double)i / 256.0 ) / log10( 2 ) - 32767.0 );

	// Zurück zu linearen Samplewerten + Low Pass Filter
	wave16bit[ 0 ] = table[ wavedata[ i ] ];
	for ( i = 1; i < size; i++ )
		wave16bit[ i ] = ( table[ wavedata[ i ] ] + wave16bit[ i - 1 ] ) / 2;

	// Wave-Output Format festlegen
	format.wFormatTag      = WAVE_FORMAT_PCM;
    format.nChannels       = 1;
    format.nSamplesPerSec  = 22050;
    format.nAvgBytesPerSec = 22050*2;
    format.nBlockAlign     = 2;
    format.wBitsPerSample  = 16;
    format.cbSize          = 0;

	// WaveOut Gerät suchen
	mmcode = !MMSYSERR_NOERROR;
	int versuche = 0;
	while ( ( mmcode != MMSYSERR_NOERROR ) && ( versuche++ < 5 ) )
	{
		mmcode = waveOutOpen( &wavehandle, WAVE_MAPPER, &format, 0, 0, 0);
	}

	// Und Datenblock vorbereiten
	wheader.lpData = (char*)wave16bit;
	wheader.dwBufferLength = size * 2;
	wheader.dwBytesRecorded = 0;
	wheader.dwUser = 0;
	wheader.dwFlags = 0;
	wheader.dwLoops = 0;

	mmcode = waveOutPrepareHeader( wavehandle, &wheader, sizeof ( wheader ) );
	if ( mmcode != MMSYSERR_NOERROR )
	{
		waveOutGetErrorText( mmcode, mmerror, 1024);
		MessageBox( 0, mmerror, "Windows MMSystem Error - waveOutPrepareHeader", 0);
	}

    return TRUE;
}

int synctime;
// Diese Funktion liefert die verstrichene Zeit seit "synctime" 
int GetSyncTime()
{
	return GetDemoTime() - synctime;
}


void demomain( void )
{
	// Waveausgabe starten
	mmcode = waveOutWrite( wavehandle, &wheader, sizeof ( wheader ));
	synctime = GetDemoTime();
	if ( mmcode != MMSYSERR_NOERROR )
	{
		waveOutGetErrorText( mmcode, mmerror, 1024);
		MessageBox( 0, mmerror, "Windows MMSystem Error - waveOutWrite", 0);
	}

	int i, j, offset, c;

    while ( DemoRunning )
    {
		BlitGraphic( screen );

		// Am Anfang schwarzer Bildschirm
		while ( GetSyncTime() < SPEED*4 );


		// 1. Bild ein/ausblenden
		while ( GetSyncTime() < SPEED*(4+4) )
		{
			if ( GetSyncTime() < SPEED * 7 )
				c = min( 31, (GetSyncTime()-SPEED*4) / 16 ); else
				c = max( 0, 31-(GetSyncTime()-SPEED*7) / 16 );

			for ( offset = 0; offset < SCREEN_X * SCREEN_Y; offset++ )
				screen[ offset ] = palette1[ c ][ bmp1[ offset ] ];

			BlitGraphic( screen );
		}

		// 2. Bild ein/ausblenden
		while ( GetSyncTime() < SPEED*(8+4) )
		{
			if ( GetSyncTime() < SPEED * 11 )
				c = min( 31, (GetSyncTime()-SPEED*8) / 16 ); else
				c = max( 0, 31-(GetSyncTime()-SPEED*11) / 16 );

			for ( offset = 0; offset < SCREEN_X * SCREEN_Y; offset++ )
				screen[ offset ] = palette2[ c ][ bmp2[ offset ] ];

			BlitGraphic( screen );
		}

		// 3. Bild einblenden
		while ( GetSyncTime() < SPEED*(12+4) )
		{
			c = min( 31, (GetSyncTime()-SPEED*12) / 16 );
			
			for ( offset = 0; offset < SCREEN_X * SCREEN_Y; offset++ )
				screen[ offset ] = palette4[ c ][ bmp4[ offset ] ];

			BlitGraphic( screen );
		}

		// Hintergrundeffekt und Bild darstellen
		while ( GetSyncTime() < SPEED*(16+12) )
		{
			int c2 = 31;
			if ( GetSyncTime() < SPEED*27 )
				c = min( 31, (GetSyncTime()-SPEED*16) / 128 ); else
				c2 = c = max( 3, 31-(GetSyncTime()-SPEED*27) / 32 );

			// Hintergrundeffekt zeichnen
			UVG_Effekt  (c, (float)GetSyncTime(), make_projecteffect_shaded);
			
			// Nur die nicht-schwarzen Pixel setzen
			offset = 108 * 320 + 45;
			for ( j = 108; j < 136; j++ )
			{
				for ( i = 45; i < 284; i++ )
				{
					unsigned char p = bmp4[ offset ];
					if ( p != 255 )
					screen[ offset ] = palette4[ c2 ][ p ];
					offset ++;
				}
				offset += 320 - ( 284 - 45 );
			}
			BlitGraphic( screen );
		}
		
		// Sinusplasma und springendes Logo
		float time = 0;
		int old = GetSyncTime();
		int count = 0;
		while ( GetSyncTime() < SPEED*(28+8) )
		{
			make_sineplasma ( (float)(GetDemoTime()/1000.0), screen, 
				(unsigned short*)plasmapalette, SCREEN_X, SCREEN_Y );

			// Position des Logos
			time = (float)( GetSyncTime() % (int)(SPEED/4) );
			int movex = (int)fabs( sin( time/1000.0 ) * 30.0 );

			count += GetSyncTime() - old;
			old = GetSyncTime();
			// Blitz ?
			if ( ( GetSyncTime() > SPEED * 35 ) && ( count > SPEED / 16 ) )
			{
				count = 0;
				c = ColorCode( 255, 255, 255 );
				for ( i = 0; i < SCREEN_X * SCREEN_Y; i++ )
					screen[ i ] = c;
			} else
			// Nein, dann Logo über das Sinusplasma zeichnen
			for ( offset = j = 0; j < SCREEN_Y; j++ )
			{
				for ( i = 0; i < 40; i++ )
				{
					unsigned char p = bmp3[ offset ];
					if ( p != 255 )
					screen[ movex + offset ] = palette3[ 31 ][ p ];
					offset ++;
				}
				offset += 320 - 40;
			}
			BlitGraphic( screen );
		}

		// Feedbackeffekt
		Feedback_Effekt( SPEED * (36+16) );

		// Weiss nach Schwarz Farbverlauf
		while ( GetSyncTime() < SPEED * 56 )
		{
			c = max( 0, 255-(GetSyncTime()-SPEED*52) / 4 );
			c = ColorCode( c, c, c );
			for ( i = 0; i < SCREEN_X * SCREEN_Y; i++ )
				screen[ i ] = c;
			BlitGraphic( screen );
		}

		goto demo_ende;
    }
demo_ende:;
	demoquit();
}

// Diese Funktion der Basisbibliothek ist neu. Hier stehen die Befehle, die unbedingt beim
// Abbruch/Ende des Demos ausgeführt werden müssen
void demoquit( void )
{
	// Waveausgabe beenden
	int code = MMSYSERR_NOERROR + 1;

	code = waveOutReset( wavehandle );

	code = waveOutUnprepareHeader( wavehandle, &wheader, sizeof( wheader ) );
	
	waveOutClose(wavehandle);
	DemoRunning = 0;
}

void LoadBitmap (char *name)
// -----------------------------------------------------------
// Lädt eine BMP-Datei (256 Farben) und berechnen alle Tabellen
// -----------------------------------------------------------
{
        if ( shadepalette)
        {
                delete shadepalette;
                shadepalette = NULL;
        }

        if ( texture.cBitmap) bmp_free( texture );

        bmp_load(name, texture);
        shadepalette = MakeShadedPalette (&texture);
        bmp_make16bitpalette( texture);
}

void makeShadingTable( bitmaptype bmpheader, short palette[32][256] )
{
    // Shading Tabelle (für das Bild) erstellen
    for ( int level = 0; level < 32; level ++ )
        for ( int i = 0; i < 256; i++ )
        {
            int r = bmpheader.cColors[ i * 4 ];
            int g = bmpheader.cColors[ i * 4 + 1 ];
            int b = bmpheader.cColors[ i * 4 + 2 ];
            r = ( r * level ) / 32;
            g = ( g * level ) / 32;
            b = ( b * level ) / 32;
            palette[ level ][ i ] = ColorCode( r, g, b );
        }

}

void UVG_Effekt (int shade, float time, void (* Function)(float, GridPointUVG *))
// ------------------------------------------------------------------------
// UV-Mapping Effekte: Function ist ein Pointer zu einer Generator Funktion
//
// Die Generator-Funktion solltee wiefolgt aussehen:
// void meine_funktion (float time, GridPointUVG *gitter);
// ------------------------------------------------------------------------
{
        long  BlocksX = SCREEN_X/8;
        long  BlocksY = SCREEN_Y/8;

        GridPointUVG *gitter = new GridPointUVG[ (BlocksX+1)*(BlocksY+1)];

        Function (time*0.0005f, gitter);

		for ( int i = 0; i < (BlocksX+1)*(BlocksY+1); i++ )
			gitter[ i ].g = ( gitter[ i ].g * shade ) >> 5;

        // Zeichnen des Gitters
                RenderScreen8x8 (  gitter, screen,
                                   shadepalette,
                                   texture.cBitmap, BlocksX, BlocksY, SCREEN_X);

        delete gitter;
}

void Mix50Percent (unsigned short *Bild1, unsigned short *Bild2, int nPixels)
// ------------------------------------------------------------------------
// 50% Mischen zweier Bilder (Bild2 == (Bild1 + Bild2)
// ------------------------------------------------------------------------
{
        register unsigned long * data1 = (unsigned long *) Bild1;
        register unsigned long * data2 = (unsigned long *) Bild2;
        int nDwords = nPixels/2;

        unsigned long mask = (Rtab[255] & (Rtab[255] >> 1)) |
                             (Gtab[255] & (Gtab[255] >> 1)) |
                             (Btab[255] & (Btab[255] >> 1));
        mask = mask | (mask << 16);

        for ( register int i=0; i<nDwords; i++)
        {
                register unsigned long a = (data1[i] >> 1) & mask;
                register unsigned long b = (data2[i] >> 1) & mask;
                data2[i] = (a+b);
        }
}


void Feedback_Effekt (float Laufzeit)
// ------------------------------------------------------------------------
// Feedback Effekt
// ------------------------------------------------------------------------
{
        long  BlocksX = SCREEN_X/8;
        long  BlocksY = SCREEN_Y/8;

        GridPointUV *gitter = new GridPointUV[ (BlocksX+1)*(BlocksY+1)];

        unsigned short *buffer1 = new unsigned short[256*256];
        unsigned short *buffer2 = new unsigned short[256*256];
        unsigned short *Bild    = new unsigned short[256*256];

        // Umwandeln des Bildes von 8 Bit nach Highcolor:
        for ( int i=0; i<0x10000; i++)
          Bild[i] = texture.sColors[texture.cBitmap[i]];

        memset (buffer2, 0,    256*256*2);
        memcpy (buffer1, Bild, 256*256*2);

        unsigned long StartTime = GetDemoTime   ();
        do {
                float time = (float)(GetDemoTime()-StartTime)/1000.0f;

                // Berechnen des Gitters: (in effect.cpp)
                make_feedback(time, gitter, 32, 32);

                // Zeichnen des Gitters in den Buffer2:
                RenderScreen8x8 (gitter, buffer2, buffer1, 32, 32, 256);

                // Mischen des Originalbilds mit dem gedrehten Bild (Feedback);
                Mix50Percent (buffer2, buffer1, 256*256);

                // Kopieren eines Teils des original Bilds ind das gemischte Bild
                memcpy (buffer1+(256*(int)(127.0+64.0*sin(time*1.32))),
                        Bild   +(256*(int)(127.0+64.0*sin(time*3.00))), 256*2*32);

                // einfachen Zoom berechen und Zeigen des Bildes
                make_stretcher(gitter);
                RenderScreen8x8 (gitter, screen, buffer2, BlocksX, BlocksY, SCREEN_X);
                BlitGraphic( screen );

                // Abbruch?
                if ( GetSyncTime()>Laufzeit) break;

        } while (DemoRunning);

        delete gitter;
        delete buffer1;
        delete buffer2;
        delete Bild;
}
