#include <windows.h>
#include <stdio.h>
#include <d3d8.h>
#include <d3dx8.h>
#include "stdverts.h"
#include "fmod.h"
#include "cfl3.h"

//////////
//
// DX8 dx8 + fmod + cfl tutorial
// Jari Komppa 2001

char                    progname[]="DX8+FMOD+CFL tutorial - Sol";

LPDIRECT3D8             d3d;        // Direct3d8 object
LPDIRECT3DDEVICE8       d3dd;       // Rendering device
LPD3DXFONT              debugfont;  // Debug font (only used in winmain)
LPDIRECT3DTEXTURE8      mytexture;  // The only texture in this example
LPDIRECT3DVERTEXBUFFER8 vbb;        // The only vertex buffer in this example
FMUSIC_MODULE           *mod;       // FMOD module pointer
CFL                     *cfl;       // CFL library object

int                     trigger1=0; // Global trigger for effect 1
int                     trigger2=0; // Global trigger for effect 2
int                     trigger3=0; // Global trigger for effect 3

#define PI 3.1415926535897932384626433832795f


// This is the FMOD instrument callback function. We trap several
// instrument triggers here, and set the trigger1-3 variables to
// current tick if found.
// Since the instument callback should be as fast as possible, all
// we do is set those variables. (Okay, so GetTickCount() may be
// heavy, but it works for me(tm) - alternative would be to set a
// global variable that stores the tick on every frame..)
void instrument_callback(FMUSIC_MODULE *mod,unsigned char param)
{
    if (param==2 || param==5 || param==7) // beat
    {
        trigger1=GetTickCount();
    }
    if (param==11) // main bass
    {
        trigger2=GetTickCount();
    }
    if (param==10) // reso
    {
        trigger3=GetTickCount();
    }
}


// This function sets up fmod and some rendering variables. (eg. creates
// vertex buffer, loads texture). Please note that winmain() also does some
// setting up.
void setup()
{
    // Fmod setup (we just die if something goes wrong)
    if (FSOUND_GetVersion() < FMOD_VERSION) exit(1);
    if (!FSOUND_Init(44100, 32, FSOUND_INIT_GLOBALFOCUS)) exit(1);

    // Load data from CFL.
    // Please note that CFL is, by design, case sensitive:
    char * musicfile=cfl->getFile("CFD.S3M");
    int musicfilesize=cfl->getFileSize("CFD.S3M");

    mod = FMUSIC_LoadSongMemory(musicfile,musicfilesize);
    if (!mod)
    {
        exit(1);
    }
    // Data is no longer needed:
    delete[] musicfile;

    // Just start playing..
    FMUSIC_PlaySong(mod);

    // Set up instrument callbacks.
    FMUSIC_SetInstCallback(mod,instrument_callback,2);
    FMUSIC_SetInstCallback(mod,instrument_callback,7);
    FMUSIC_SetInstCallback(mod,instrument_callback,10);
    FMUSIC_SetInstCallback(mod,instrument_callback,11);
    FMUSIC_SetInstCallback(mod,instrument_callback,5);

    // Set some render states..
    d3dd->SetTextureStageState(0,D3DTSS_ADDRESSU,D3DTADDRESS_CLAMP); // clamp texture of U or V are not 0..1
    d3dd->SetTextureStageState(0,D3DTSS_ADDRESSV,D3DTADDRESS_CLAMP);
    d3dd->SetTextureStageState(0,D3DTSS_MAGFILTER,D3DTEXF_LINEAR); // set up trilinear 
    d3dd->SetTextureStageState(0,D3DTSS_MINFILTER,D3DTEXF_LINEAR); // interpolation
    d3dd->SetTextureStageState(0,D3DTSS_MIPFILTER,D3DTEXF_LINEAR);
    d3dd->SetRenderState(D3DRS_ZENABLE,FALSE); // Disable zbuffer (we don't need it here)
    d3dd->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW); // Counter-clockwise cull.

    // Create a vertex buffer (die if failed)
    if (FAILED(d3dd->CreateVertexBuffer(
        sizeof(D3DTLVERTEX)*129,
        D3DUSAGE_WRITEONLY,
        D3DFVF_TLVERTEX,
        D3DPOOL_MANAGED,&vbb)))
        exit(1);

    //  Set up vbb as the vertex stream source.
    d3dd->SetStreamSource(0,vbb,sizeof(D3DTLVERTEX));
    d3dd->SetVertexShader(D3DFVF_TLVERTEX);

    // Load the texture. (If this fails, you'll see a white rectangle instead,
    // which is not so devastating in this example)

    char * jpegfile=cfl->getFile("flare64.jpg");
    int jpegfilesize=cfl->getFileSize("flare64.jpg");
    D3DXCreateTextureFromFileInMemory(d3dd,jpegfile,jpegfilesize,&mytexture);
    delete[] jpegfile;

    // set the texture to be active.
    d3dd->SetTexture(0,mytexture);
}



// Rendering function. This is called from winmain() on each rendering pass.
void Render()
{
    D3DTLVERTEX * vt;
    // d3dd may be null if we're quitting.
    if (d3dd==NULL) return;

    int itick=GetTickCount();  // Current tick
    float tick=itick/100.0f;   // and 100th of it as float

    float sizeinc1=0; // sizeincs are calculated from triggers, below..
    float sizeinc2=0;
    float sizeinc3=0;

    // Triggers are set in instrument callback. We calculate how many ticks
    // ago they were set, and scale effects accordingly. If the trigger happened
    // too long ago, the sizeinc will remain zero.
    if (trigger1!=0)
    {
        sizeinc1=(200-(itick-trigger1))/2.0f;
        if (sizeinc1<0)
            sizeinc1=0;
    }
    if (trigger2!=0)
    {
        sizeinc2=(400-(itick-trigger2))/6.0f;
        if (sizeinc2<0)
            sizeinc2=0;
    }
    if (trigger3!=0)
    {
        sizeinc3=(200-(itick-trigger3))/240.0f;
        if (sizeinc3<0)
            sizeinc3=0;
    }

    // Lock vertex buffer, discarding old data if any
    vbb->Lock(0,0,(unsigned char**)&vt,D3DLOCK_DISCARD);
    // Set one vertex in the center..
    vt[0].sx=320;
    vt[0].sy=240;
    vt[0].tu=0.5f;
    vt[0].tv=0.5f;
    vt[0].color=0xffffffff;
    vt[0].rhw=1;

    // ..and the rest as a circle around it, with distance from center
    // varying with different amounts.
    for (int i=0;i<128;i++)
    {
        float y=(float)sin((i+1)*(PI/64)+tick*0.1);
        float x=(float)cos((i+1)*(PI/64)+tick*0.1);
        float dista=(float)(sizeinc2+100+(40*sin((x+i*0.6-tick))*cos(((y+i*(0.4+sizeinc3)+tick)*0.1))));
        vt[i+1].sz=1;
        vt[i+1].sx=320+x*dista;
        vt[i+1].sy=240+y*dista;
        vt[i+1].tu=(x+1)/2;
        vt[i+1].tv=(y+1)/2;
        vt[i+1].color=0xeeeeeeee;
        vt[i+1].rhw=1;
    }
    // make sure the last vertex is the same as the second one so we won't
    // have a HORRIBLE gap there (it's still ugly but who cares)
    vt[128]=vt[1];
    vbb->Unlock();
    // and render the first fan.
    d3dd->DrawPrimitive(D3DPT_TRIANGLEFAN,0,127);

    // While the hardware is drawing, we lock the buffer again and discard the
    // contents.. 
    vbb->Lock(0,0,(unsigned char**)&vt,D3DLOCK_DISCARD);
    vt[0].sx=320;
    vt[0].sy=240;
    vt[0].tu=0.5f;
    vt[0].tv=0.5f;
    vt[0].color=0xffffffff;
    vt[0].rhw=1;
    for (i=0;i<128;i++)
    {
        float y=(float)sin((i+1)*(PI/64)+tick*-0.1);
        float x=(float)cos((i+1)*(PI/64)+tick*-0.1);
        float dista=(float)(60+sizeinc1+((30-sizeinc2)*sin((i*0.5)*(3+sin(tick*0.0242))-tick)));
        vt[i+1].sz=1;
        vt[i+1].sx=320+x*dista;
        vt[i+1].sy=240+y*dista;
        vt[i+1].tu=(x+1)/2;
        vt[i+1].tv=(y+1)/2;
        vt[i+1].color=0x88888888;
        vt[i+1].rhw=1;
    }
    vt[128]=vt[1];
    vbb->Unlock();

    // this time we set up additive alpha blend before drawing..
    d3dd->SetRenderState(D3DRS_SRCBLEND ,D3DBLEND_SRCALPHA);
    d3dd->SetRenderState(D3DRS_DESTBLEND ,D3DBLEND_ONE);
    d3dd->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
    d3dd->DrawPrimitive(D3DPT_TRIANGLEFAN,0,127);
    // ..and remember to set the alpha blend off when we're done.
    d3dd->SetRenderState(D3DRS_ALPHABLENDENABLE,FALSE);

}


// This is the cleanup function called from winmain().. make sure
// fmod is dead and clean up dx objects.
void Cleanup()
{
    FMUSIC_FreeSong(mod);
    FSOUND_Close();

    if( d3dd != NULL)
        d3dd->Release();
    if( d3d != NULL)
        d3d->Release();
    d3dd=NULL;
    d3d=NULL;
}


// Window procedure.. this function gets all the window messages.
// (note that clicking on the top left corner 'x' may go directly here
// and skip winmain() loop completely!)
LRESULT CALLBACK WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch (uMsg) {
    case WM_KEYDOWN:
        switch( wParam ) {
        case VK_ESCAPE:
        case VK_F12:
            // if escape or f12 is pressed, post kill message to us.
            PostMessage(hWnd, WM_CLOSE, 0, 0);
            break;
        }
        case WM_DESTROY:
            // kill message received, post quit one, clean up and quit.
            PostQuitMessage(0);
            Cleanup();
            exit(wParam);
            break;
        case WM_PAINT:
            // If wm_paint should be received, just tell windows everything
            // is a-ok..
            ValidateRect( hWnd, NULL );
            break;
        default:
            // rest of the messages may do whatever they do by default.
            return DefWindowProc (hWnd, uMsg, wParam, lParam) ;
            break;
    }
    return 0;
}


// Windows main function. Set up window, set up dx, call setup to set up the rest
// and finally do the rendering loop.
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
    WNDCLASSEX winclass;
    HWND hWnd;
    MSG msg;

    // Create window class
    winclass.cbSize=sizeof(WNDCLASSEX);
    winclass.style=CS_DBLCLKS;
    winclass.lpfnWndProc=&WindowProc;
    winclass.cbClsExtra=0;
    winclass.cbWndExtra=0;
    winclass.hInstance=hInst;
    winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    winclass.hCursor=LoadCursor(NULL,IDC_ARROW);
    winclass.hbrBackground=GetSysColorBrush(COLOR_APPWORKSPACE);
    winclass.lpszMenuName=NULL;
    winclass.lpszClassName=progname;
    winclass.hIconSm=NULL;

    // I don't know if this can ever fail, really:
    if (!RegisterClassEx(&winclass)) return 0;

    // Create a 640 by 480 window (note that the client area will be somewhat smaller!)
    hWnd=CreateWindow(progname,progname,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,640,480,NULL,NULL,hInst,NULL);
    // Show the window
    ShowWindow(hWnd,nCmdShow);

    char * buf;
    buf=new char[1024];
    GetModuleFileName(hInst,buf,1024);

    // First try to open the CFL from the executable (if copy/b:d to the end)
    if (!(cfl=CFL::create(buf,0)))
    {
        // if not found from the exe, try to open the separate file
        if (!(cfl=CFL::create("cfltut.cfl",0)))
        {
            MessageBox(NULL,"Filesystem mount failed.","Oh dear",MB_OK);
            exit(1);
        }
    }
    // Free the buffer that contained the exe name.
    delete[] buf;


    // Create d3d object, or if fail, quit.
    if (NULL == (d3d = Direct3DCreate8(D3D_SDK_VERSION))) return E_FAIL;

    // Ask user whether we want to go fullscreen or not.
    int fullscreen=0;
    if (MessageBox(hWnd,"Go fullscreen?",progname,MB_YESNO)==IDYES) fullscreen=1;

    // Set up presentation. This is the biggest mess in dx8 and could have
    // been designed better in my opinnion.

    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    if (fullscreen) {
        d3dpp.Windowed=FALSE;
        d3dpp.BackBufferWidth=640;
        d3dpp.BackBufferHeight=480;
        d3dpp.BackBufferFormat=D3DFMT_X8R8G8B8;
        d3dpp.BackBufferCount=3;
        d3dpp.SwapEffect=D3DSWAPEFFECT_DISCARD;
        d3dpp.EnableAutoDepthStencil=TRUE;
        d3dpp.AutoDepthStencilFormat=D3DFMT_D24S8;
    } else {
        D3DDISPLAYMODE d3ddm;
        if( FAILED( d3d->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) )
            return E_FAIL;
        d3dpp.Windowed   = TRUE;
        d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        d3dpp.BackBufferFormat = d3ddm.Format;
        d3dpp.EnableAutoDepthStencil=TRUE;
        d3dpp.AutoDepthStencilFormat=D3DFMT_D24S8;
    }


    // Basically, try to create the device first with whatever we wanted, 
    // then with 16-bit zbuffer and then with 16-bit color buffer. If all
    // of this fails, die.
    if( FAILED( d3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dd ) ) )
    {
        if( FAILED( d3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dd ) ) )
        {
            d3dpp.AutoDepthStencilFormat=D3DFMT_D16;
            if( FAILED( d3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dd ) ) )
            {
                d3dpp.BackBufferFormat=D3DFMT_R5G6B5;
                if( FAILED( d3d->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3dd ) ) )
                {
                    return 0;
                }
            }
        }
    }

    // Now that dx is up, create a debug font so we can see the framerate:
    D3DXCreateFont(d3dd,(HFONT)GetStockObject(SYSTEM_FONT),&debugfont);

    // call setup to set up the rest of the stuff.
    setup();

    int frame=0;
    int starttime=GetTickCount();
    char str[200];

    // Main rendering loop.
    while (1) {
        // Hide the cursor if fullscreen.
        if (fullscreen) SetCursor(NULL);
        // Clear framebuffer and zbuffer.
        d3dd->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER , D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
        // Begin rendering.
        d3dd->BeginScene();
        // Do the rendering.
        Render();
        // Count frames
        frame++;
        int sec=GetTickCount()-starttime;
        // and if we've done counting for more than a second, print the framerate
        if (sec>0)
            sprintf(str,"fps:%3.3f",(frame*1000.0)/sec);
        // Only consider 1-100 frames for frame counting.
        if (frame>100) {
            frame=0;
            starttime+=sec;
        }
        // Print the framerate on screen. (The text printing is much
        // better in dx8 than in dx7, but you get all this rectangle
        // mess as well)
        RECT r; r.top=11;r.left=11;r.bottom=470;r.right=630;
        debugfont->DrawTextA(str,-1,&r,DT_LEFT|DT_TOP,0xff000000);
        r.top=10;r.left=10;r.bottom=470;r.right=630;
        debugfont->DrawTextA(str,-1,&r,DT_LEFT|DT_TOP,0xffffffff);
        // We're done with the scene.
        d3dd->EndScene();
        // Dump the frame on screen.
        d3dd->Present( NULL, NULL, NULL, NULL );
        // Check the windows messages and process them.
        if(PeekMessage(&msg,hWnd,0,0,PM_REMOVE)) {
            WindowProc(hWnd,msg.message,msg.wParam,msg.lParam);
        }
    }
    // This line is never reached:
    return 0;
}