#include <xtl.h>
#include <xgraphics.h>
#include <dsound.h>

#include <tchar.h>
#include <stdio.h>

#include "externals.h"
#include "utility.h"
#include "animation.h"
#include "hvl_replay.h"
#include "nyansong.h"
#include "dsstdfx.h"
#include "dsstdfx_bin.h"

//-----------------------------------------------------------------------------
// Global variables
//-----------------------------------------------------------------------------

float bob, weave;

unsigned short colors[256] = { 0 };

ULONG g_Frame = 0;
#define FINALFRAME 1150
BOOL g_ExitNow = FALSE;
BOOL g_SoundEnabled = FALSE;
BOOL g_PlayOnce = TRUE;

struct hvl_tune *ht = NULL;

LPDIRECTSOUND8 g_pDSound;
LPDIRECTSOUNDBUFFER8 g_pDSBuffer;
WAVEFORMATEX g_wfx;
DSBUFFERDESC g_dsbd;
HANDLE hFillAudioBufferEvent;

#define AUDIOFRAMESIZE ((44100*2*2)/50)
#define AUDIOFRAMES 10
#define AUDIOBUFFERSIZE (AUDIOFRAMESIZE*AUDIOFRAMES)
DSBPOSITIONNOTIFY g_dspn[AUDIOFRAMES];

int nextaudiobuf = 0;

ULONG g_ButtonsLast = 0;
ULONG g_ButtonsHeld = 0;
ULONG g_ButtonsPressed = 0;
ULONG g_ButtonsReleased = 0;
HANDLE g_Controller[4];

LPDIRECT3D8             g_pD3D                = NULL; // Used to create the D3DDevice
LPDIRECT3DDEVICE8       g_pd3dDevice          = NULL; // Our rendering device

D3DMATERIAL8 g_mtrl;

D3DXMATRIX g_matView;
D3DXMATRIX g_matProj;
D3DXMATRIX g_matWorld;

//-----------------------------------------------------------------------------
// Name: XBUtil_DebugPrint()
// Desc: For printing to the debugger with formatting.
//-----------------------------------------------------------------------------
VOID XBUtil_DebugPrint( const CHAR* buf, ... )
{
    CHAR strBuffer[1024];

    va_list arglist;
    va_start( arglist, buf );
    _vsnprintf( strBuffer, sizeof(strBuffer), buf, arglist );
    va_end( arglist );

    strBuffer[sizeof(strBuffer)-1] = '\0';
    OutputDebugStringA( strBuffer );
}

VOID XBUtil_Reboot(VOID)
{
	HalReturnToFirmware(2);
}

typedef struct {
    DWORD dwWidth;
    DWORD dwHeight;
    BOOL  fProgressive;
    BOOL  fWideScreen;
	DWORD dwFreq;
} DISPLAY_MODE;

// Display modes in order of our preference
DISPLAY_MODE g_aDisplayModes[] =
{
//    Width  Height Progressive Widescreen

// HDTV Progressive Modes
    {  1280,    720,    TRUE,   TRUE,  60 },         // 1280x720 progressive 16x9

// EDTV Progressive Modes
    {   720,    480,    TRUE,   TRUE,  60 },         // 720x480 progressive 16x9
    {   640,    480,    TRUE,   TRUE,  60 },         // 640x480 progressive 16x9
    {   720,    480,    TRUE,   FALSE, 60 },         // 720x480 progressive 4x3
    {   640,    480,    TRUE,   FALSE, 60 },         // 640x480 progressive 4x3

// HDTV Interlaced Modes
//    {  1920,   1080,    FALSE,  TRUE,  60 },         // 1920x1080 interlaced 16x9

// SDTV PAL-50 Interlaced Modes
    {   720,    480,    FALSE,  TRUE,  50 },         // 720x480 interlaced 16x9 50Hz
    {   640,    480,    FALSE,  TRUE,  50 },         // 640x480 interlaced 16x9 50Hz
    {   720,    480,    FALSE,  FALSE, 50 },         // 720x480 interlaced 4x3  50Hz
    {   640,    480,    FALSE,  FALSE, 50 },         // 640x480 interlaced 4x3  50Hz

// SDTV NTSC / PAL-60 Interlaced Modes
    {   720,    480,    FALSE,  TRUE,  60 },         // 720x480 interlaced 16x9
    {   640,    480,    FALSE,  TRUE,  60 },         // 640x480 interlaced 16x9
    {   720,    480,    FALSE,  FALSE, 60 },         // 720x480 interlaced 4x3
    {   640,    480,    FALSE,  FALSE, 60 },         // 640x480 interlaced 4x3
};
#define NUM_MODES ( sizeof( g_aDisplayModes ) / sizeof( g_aDisplayModes[0] ) )

///////////////////////////////////////////////////////////////////////////////
BOOL SupportsMode( DISPLAY_MODE mode, DWORD dwVideoStandard, DWORD dwVideoFlags )
{
    if( mode.dwFreq == 60 && !(dwVideoFlags & XC_VIDEO_FLAGS_PAL_60Hz) && (dwVideoStandard == XC_VIDEO_STANDARD_PAL_I))
        return FALSE;
    
    if( mode.dwFreq == 50 && (dwVideoStandard != XC_VIDEO_STANDARD_PAL_I))
        return FALSE;

    // Need to check for widescreen on 480 modes only - 
    // 720p and 1080i are by definition widescreen.
    if( mode.dwHeight == 480 && mode.fWideScreen && !(dwVideoFlags & XC_VIDEO_FLAGS_WIDESCREEN ) )
        return FALSE;

    // Explicit check for 480p
    if( mode.dwHeight == 480 && mode.fProgressive && !(dwVideoFlags & XC_VIDEO_FLAGS_HDTV_480p) )
        return FALSE;

    // Explicit check for 720p (only 720 mode)
    if( mode.dwHeight == 720 && !(dwVideoFlags & XC_VIDEO_FLAGS_HDTV_720p) )
        return FALSE;

    // Explicit check for 1080i (only 1080 mode)
    if( mode.dwHeight == 1080 && !(dwVideoFlags & XC_VIDEO_FLAGS_HDTV_1080i) )
        return FALSE;

    return TRUE;
}


//-----------------------------------------------------------------------------
// Name: SetupMatrices()
// Desc: Creates the world, view, and projection transform matrices.
//-----------------------------------------------------------------------------
VOID SetupMatrices(BOOL bWidescreen)
{
	float scale = 0.6f;

    // Set up our view matrix.
    const D3DXVECTOR3 vEyePos( 0.0f, 0.0f, 10.0f );
    const D3DXVECTOR3 vLookAt( 0.0f, 0.0f, 0.0f );
    const D3DXVECTOR3 vUp    ( 0.0f, 1.0f, 0.0f );
    D3DXMatrixLookAtLH( &g_matView, &vEyePos, &vLookAt, &vUp );

    // For the projection matrix.
	D3DXMatrixOrthoLH( &g_matProj, bWidescreen ? (106.0f*scale) : (80.0f*scale), (48.0f*scale), 1.0f, 100.0f );

	D3DXMatrixIdentity( &g_matWorld );
}

//-----------------------------------------------------------------------------
// Name: InitD3D()
// Desc: Initializes Direct3D
//-----------------------------------------------------------------------------
HRESULT InitD3D()
{
	DWORD m_dwVideoFlags, m_dwVideoStandard;
	DWORD m_dwCurrentMode = 0;

    // Create the D3D object.
    if( NULL == ( g_pD3D = Direct3DCreate8( D3D_SDK_VERSION ) ) )
        return E_FAIL;

	// Find the best supported video mode.
    m_dwVideoStandard = XGetVideoStandard();
    m_dwVideoFlags = XGetVideoFlags();
    for(m_dwCurrentMode = 0; m_dwCurrentMode < NUM_MODES-1; m_dwCurrentMode++)
    {
		if(SupportsMode( g_aDisplayModes[ m_dwCurrentMode ], m_dwVideoStandard, m_dwVideoFlags )) break;
    } 

    // Set up the structure used to create the D3DDevice.
    D3DPRESENT_PARAMETERS d3dpp; 
    ZeroMemory( &d3dpp, sizeof(d3dpp) );
	d3dpp.BackBufferWidth        = g_aDisplayModes[ m_dwCurrentMode ].dwWidth;
    d3dpp.BackBufferHeight       = g_aDisplayModes[ m_dwCurrentMode ].dwHeight;
	d3dpp.Flags                  = g_aDisplayModes[ m_dwCurrentMode ].fProgressive ? D3DPRESENTFLAG_PROGRESSIVE : D3DPRESENTFLAG_INTERLACED;
    d3dpp.Flags                 |= g_aDisplayModes[ m_dwCurrentMode ].fWideScreen ? D3DPRESENTFLAG_WIDESCREEN : 0;
    d3dpp.FullScreen_RefreshRateInHz = g_aDisplayModes[ m_dwCurrentMode ].dwFreq;
    d3dpp.BackBufferFormat       = D3DFMT_A8R8G8B8;
    d3dpp.BackBufferCount        = 1;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
    d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
	d3dpp.MultiSampleType        = D3DMULTISAMPLE_4_SAMPLES_SUPERSAMPLE_GAUSSIAN;

	// Create the Direct3D device.
    if( FAILED( g_pD3D->CreateDevice( 0, D3DDEVTYPE_HAL, NULL,
                                      D3DCREATE_HARDWARE_VERTEXPROCESSING,
                                      &d3dpp, &g_pd3dDevice ) ) )
        return E_FAIL;

	SetupMatrices(g_aDisplayModes[ m_dwCurrentMode ].fWideScreen);

	return S_OK;
}

//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Draws the scene
//-----------------------------------------------------------------------------
VOID Render()
{
	int x, y;
	float vx, vy;
	unsigned int /*fg,*/ bg, r, g, b, fade;

	// Clear the backbuffer and the zbuffer
    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 
                         D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );

	DirectSoundDoWork();

	g_pd3dDevice->SetTransform( D3DTS_WORLD, &g_matWorld );
    g_pd3dDevice->SetTransform( D3DTS_VIEW, &g_matView );
    g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &g_matProj );

	g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
	g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );
	g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
    g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE );
    g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, FALSE );

    g_pd3dDevice->SetRenderState( D3DRS_ALPHAREF,        0x08 );
    g_pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL );

	g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xffffffff );
    g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );

	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_DISABLE );
	g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_DISABLE );
	g_pd3dDevice->SetVertexShader( D3DFVF_XYZ );

	if(g_Frame < 50) {
		fade = 0;
	} else if(g_Frame < 80) {
		fade = g_Frame - 50;
	} else if(g_Frame > (FINALFRAME - 30)) {
		fade = FINALFRAME - g_Frame;
	} else {
		fade = 30;
	}

	for(y = 0; y < FRAME_HEIGHT; y++ ) {
		vy = (FRAME_HEIGHT * 0.5f) - y + bob;
		for(x = 0; x < FRAME_WIDTH; x++ ) {
			vx = (FRAME_WIDTH * 0.5f) - x + weave;

			bg = colors[frames[((g_Frame/4)%12)][(y*FRAME_WIDTH)+x]];
			if(bg != 0x000) {
				r = (bg & 0xF00) >> 8;
				g = (bg & 0x0F0) >> 4;
				b = (bg & 0x00F) >> 0;

				g_mtrl.Ambient.r = (r * fade) / (15.0f * 30.0f);
				g_mtrl.Ambient.g = (g * fade) / (15.0f * 30.0f);
				g_mtrl.Ambient.b = (b * fade) / (15.0f * 30.0f);
				g_pd3dDevice->SetMaterial( &g_mtrl );

				g_pd3dDevice->Begin( D3DPT_QUADLIST );
				g_pd3dDevice->SetVertexData4f( D3DVSDE_VERTEX, vx-1.0f, vy-1.0f, 0.0f, 1.0f );
				g_pd3dDevice->SetVertexData4f( D3DVSDE_VERTEX, vx+0.0f, vy-1.0f, 0.0f, 1.0f );
				g_pd3dDevice->SetVertexData4f( D3DVSDE_VERTEX, vx+0.0f, vy+0.0f, 0.0f, 1.0f );
				g_pd3dDevice->SetVertexData4f( D3DVSDE_VERTEX, vx-1.0f, vy+0.0f, 0.0f, 1.0f );
				g_pd3dDevice->End();
			}
		}
	}

	// Present the backbuffer contents to the display
    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}

VOID XInputGetStateAll()
{
	XINPUT_STATE ControllerState[4];
	int i, j, c = -1;
	ULONG ButtonsCurrent = 0;

	for (i = 0; i < 4; i++)
	{
		if (g_Controller[i])
		{
			XInputGetState(g_Controller[i], &ControllerState[i]);
			ButtonsCurrent |= (ControllerState[i].Gamepad.wButtons) & 0xff;

#if 0
			if (ControllerState[i].Gamepad.sThumbLY < -28672)
				ButtonsCurrent |= XINPUT_GAMEPAD_DPAD_UP;
			if (ControllerState[i].Gamepad.sThumbLY > 28671)
				ButtonsCurrent |= XINPUT_GAMEPAD_DPAD_DOWN;
			if (ControllerState[i].Gamepad.sThumbLX < -28672)
				ButtonsCurrent |= XINPUT_GAMEPAD_DPAD_LEFT;
			if (ControllerState[i].Gamepad.sThumbLX > 28671)
				ButtonsCurrent |= XINPUT_GAMEPAD_DPAD_RIGHT;
#endif

			for (j = 0; j < 8; j++)
			{
				if (ControllerState[i].Gamepad.bAnalogButtons[j] > 0x7f)
					ButtonsCurrent |= (1 << (j + 8));
			}
		}
	}

	g_ButtonsHeld = ButtonsCurrent & g_ButtonsLast;
	g_ButtonsReleased = g_ButtonsLast & ~ButtonsCurrent;
	g_ButtonsPressed = ButtonsCurrent & ~g_ButtonsLast;
	g_ButtonsLast = ButtonsCurrent;
}

VOID ParseInput()
{
	XInputGetStateAll();

	g_Frame++;

	bob = (sin(g_Frame / 30.0f) * 0.5f);
	weave = (cos(g_Frame / 30.0f) * 0.5f);

	if((g_Frame < (FINALFRAME - 60)) && ((g_ButtonsLast & BUTTONS_BACK) || (!ht))) {
		g_Frame = (FINALFRAME - 60);
	}

	if(g_Frame >= FINALFRAME) {
		g_ExitNow = TRUE;
	}
}

VOID HivelyCallback()
{
	LPVOID audiobuffer;
	DWORD audiobytes;

	if( FAILED( g_pDSBuffer->Lock( (nextaudiobuf * AUDIOFRAMESIZE), AUDIOFRAMESIZE, &audiobuffer, &audiobytes, NULL, NULL, 0 ) ) ) {
		g_pDSBuffer->Stop();
		return;
	}

	if(ht && ht->ht_SongEndReached && g_PlayOnce) {
		ht = NULL;
	}

	if(!ht) {
		ZeroMemory( audiobuffer, AUDIOFRAMESIZE );
	} else {
		hvl_DecodeFrame( ht, ((char*)audiobuffer), ((char*)audiobuffer)+2, 4 );
	}
	nextaudiobuf = (nextaudiobuf+1)%AUDIOFRAMES;

	g_pDSBuffer->Unlock( audiobuffer, audiobytes, NULL, NULL );
}

//-----------------------------------------------------------------------------
// Name: main()
// Desc: Entry point to the program.
//-----------------------------------------------------------------------------
VOID __cdecl main()
{
	// Initialize Direct3D
    if( FAILED( InitD3D() ) )
        return;

	Render();

	colors[',']  = 0x005; /* Blue background */
	colors['.']  = 0xfff; /* White stars */
	colors['\''] = 0x000; /* Black border */
	colors['@']  = 0xffd; /* Tan poptart */
	colors['$']  = 0xd8a; /* Pink poptart */
	colors['-']  = 0xd08; /* Red poptart */
	colors['>']  = 0xf00; /* Red rainbow */
	colors['&']  = 0xfa0; /* Orange rainbow */
	colors['+']  = 0xff0; /* Yellow Rainbow */
	colors['#']  = 0x8f0; /* Green rainbow */
	colors['=']  = 0x08f; /* Light blue rainbow */
	colors[';']  = 0x00d; /* Dark blue rainbow */
	colors['*']  = 0x555; /* Gray cat face */
	colors['%']  = 0xd8a; /* Pink cheeks */

	XDEVICE_PREALLOC_TYPE deviceTypes[] =
	{
		{XDEVICE_TYPE_GAMEPAD, 4},
	};

	XInitDevices( sizeof(deviceTypes) / sizeof(XDEVICE_PREALLOC_TYPE), deviceTypes );

	Sleep(2000);

	g_Controller[0] = XInputOpen( XDEVICE_TYPE_GAMEPAD, XDEVICE_PORT0, XDEVICE_NO_SLOT, 0);
	g_Controller[1] = XInputOpen( XDEVICE_TYPE_GAMEPAD, XDEVICE_PORT1, XDEVICE_NO_SLOT, 0);
	g_Controller[2] = XInputOpen( XDEVICE_TYPE_GAMEPAD, XDEVICE_PORT2, XDEVICE_NO_SLOT, 0);
	g_Controller[3] = XInputOpen( XDEVICE_TYPE_GAMEPAD, XDEVICE_PORT3, XDEVICE_NO_SLOT, 0);

	if( FAILED( DirectSoundCreate( NULL, &g_pDSound, NULL ) ) ) {
		g_SoundEnabled = FALSE;
	} else {
		g_SoundEnabled = TRUE;

		DSEFFECTIMAGELOC EffectLoc;
		EffectLoc.dwI3DL2ReverbIndex = GraphI3DL2_I3DL2Reverb;
		EffectLoc.dwCrosstalkIndex   = GraphXTalk_XTalk;
		g_pDSound->DownloadEffectsImage( dsstdfx_bin, sizeof(dsstdfx_bin), &EffectLoc, NULL );

		XAudioCreatePcmFormat( 2, 44100, 16, &g_wfx );

		ZeroMemory( &g_dsbd, sizeof(DSBUFFERDESC) );
		g_dsbd.dwSize = sizeof(DSBUFFERDESC);
		g_dsbd.dwBufferBytes = AUDIOBUFFERSIZE;
		g_dsbd.lpwfxFormat = &g_wfx;
		
		if( FAILED( g_pDSound->CreateSoundBuffer( &g_dsbd, &g_pDSBuffer, NULL ) ) ) {
			g_SoundEnabled = FALSE;
		} else {
			hFillAudioBufferEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

			hvl_InitReplayer();
			ht = hvl_LoadTune( nyansong, sizeof(nyansong), 44100, 0 );
			if(!ht)
				g_SoundEnabled = FALSE;
			else {
				int i;
				for( i = 0; i < AUDIOFRAMES; i++) {
					g_dspn[i].dwOffset = i * AUDIOFRAMESIZE;
					g_dspn[i].hEventNotify = hFillAudioBufferEvent;
				}
				for( i = 0; i < AUDIOFRAMES/4; i++) {
					HivelyCallback();
				}
				g_pDSBuffer->SetNotificationPositions( AUDIOFRAMES, g_dspn );
			}
		}
	}

	// Enter render loop
    while(!g_ExitNow)
    {
		while(g_SoundEnabled && (WaitForSingleObject( hFillAudioBufferEvent, 0 ) == WAIT_OBJECT_0)) {
			HivelyCallback();
		}
		if(g_SoundEnabled) {
			if(g_Frame == 51)
				g_pDSBuffer->Play(0, 0, DSBPLAY_LOOPING);
			if((g_Frame > 50) && (g_Frame < 75))
				g_pDSBuffer->SetVolume( DSBVOLUME_MIN + (((DSBVOLUME_MAX - DSBVOLUME_MIN) / 26) * (g_Frame-50)) );
			if(g_Frame > (FINALFRAME - 25))
				g_pDSBuffer->SetVolume( DSBVOLUME_MAX - (((DSBVOLUME_MAX - DSBVOLUME_MIN) / 26) * (FINALFRAME - g_Frame)) );
		}

		ParseInput();
		Render();
    }

	g_pd3dDevice->PersistDisplay();

	HalReturnToFirmware(2);
}
