#include <cl.h>
#include <clu.h>
#include <clio.h>

#include <GL/glut.h>

#include <fmod.h>

#include <zf.h>

#if 1
#include <fmod_errors.h>
#endif

#define TIME_STEP 5000

#define SPIN_SPEED (CL_PI) /* rotation angle per second */

const unsigned int NUM_SPECTRUM_ENTRIES = 256;
const GLfloat      ANALYSER_HEIGHT = 1.0;
const GLfloat      ANALYSER_WIDTH = 2.0;
const GLfloat      ANALYSER_DEPTH = 0.01;

CLlight*         light0;
CLlight*         light1;

char*            filename;

GLfloat          zoom = 2.0f;

GLfloat          last_x = 0.0f;
GLfloat          last_y = 0.0f;

GLfloat          angle_x = 0.0f;
GLfloat          angle_y = 0.0f;
GLfloat          angle_z = 0.0f;

bool             fullscreen_flag = false;
bool             helix_mode_flag = false;

GLuint           frame_count = 0;
GLfloat          time_step_seconds = TIME_STEP/1000.0f;

GLint            width;
GLint            height;

GLfloat          column_width;
GLfloat          column_height;
GLfloat          column_depth;

ZfAudioStream*   music;
char*            input_filename;
float*           spectrum;
float*           spectrum_max;
unsigned int     i;   

ZfAudioSample*   sample0;
ZfAudioSample*   sample1;
ZfAudioSample*   sample2;

unsigned int     offset = 0;
unsigned int     framecount = 0;

#if 0
static signed char F_CALLBACKAPI streamcallback(FSOUND_STREAM* stream, void* buff, int len, void* userdata)
{
    /*  end of stream callback doesnt have a 'buff' value, if it doesnt it could be a synch point. */
    if (buff)
    {
        printf("\nSYNCHPOINT : \"%s\"\n", (char*) buff);
    }
    else
    {
        printf("\nSTREAM ENDED!! buff=%s, len=%d, userdata=%s\n", (char*) buff, len, (char*) userdata);
	/* play a sample */
	zf_audio_sample_play(sample);
    }
    
    return '1';
}
#endif

static void F_CALLBACKAPI ordercallback(FMUSIC_MODULE* mod, unsigned char param)
{
    printf("started order %u\n", (unsigned int) param);
}

static void F_CALLBACKAPI rowcallback(FMUSIC_MODULE* mod, unsigned char param)
{
    printf("started row %u\n", (unsigned int) param);
}

static void F_CALLBACKAPI instcallback(FMUSIC_MODULE* mod, unsigned char param)
{
    printf("started inst %u\n", (unsigned int) param);
}

static void F_CALLBACKAPI zxxcallback(FMUSIC_MODULE* mod, unsigned char param)
{
    printf("zxx %u\n", (unsigned int) param);
}

static void init(void)
{
    light0 = clDefaultLight(clNewLight());
    light1 = clDefaultLight(clNewLight());
    light1->position[2] = -light1->position[2];
    light1->id = GL_LIGHT1;
    
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    
    glEnable(GL_LIGHTING);
    glEnable(light0->id);
    glEnable(light1->id);
    
    glEnable(GL_TEXTURE_2D);
    
    glEnable(GL_COLOR_MATERIAL);
    
    column_width = ANALYSER_WIDTH / NUM_SPECTRUM_ENTRIES;
    column_height = ANALYSER_HEIGHT;
    column_depth = ANALYSER_DEPTH;

    /* malloc and init spectrum max */
    spectrum_max = (float*) malloc (NUM_SPECTRUM_ENTRIES * sizeof(float));
    for (i = 0 ; i < NUM_SPECTRUM_ENTRIES ; i++)
    {
	spectrum_max[i] = 0.0;
    }

    /* init */
    zf_audio_init();
    
    /* print audio subsystem details */
    zf_audio_print();
    
    /* load song */
    music = zf_audio_stream_load(input_filename);

    /* load smaple */
    sample0 = zf_audio_sample_load("../data/sound_effects/knokart.wav");
    sample1 = zf_audio_sample_load("../data/sound_effects/laser01.wav");
    sample2 = zf_audio_sample_load("../data/sound_effects/waterxplo.wav");

#if 0    
    /* setup stream callbacks */
    FSOUND_Stream_SetEndCallback(sample, streamcallback, 0);
    FSOUND_Stream_SetSyncCallback(sample, streamcallback, 0);
    
    /* setup mod callbacks */
    FMUSIC_SetOrderCallback(mod, ordercallback, 1);
    FMUSIC_SetRowCallback(mod, rowcallback, 4);
    for (i = 1 ; i > 50 ; i++)
    {
	FMUSIC_SetInstCallback(mod, instcallback, i);
    }
    FMUSIC_SetZxxCallback(mod, zxxcallback);
#endif    

    /* disable looping */
    /* zf_audio_mod_set_looping(mod, '0'); */
    /* zf_audio_sample_set_loop_count(sample, 1); */
    //zf_audio_sample_set_loop_count(sample, 0);
    
    /* set mod speed */
    /* zf_audio_mod_set_speed(mod, input_speed); */
    
    /* play */
    zf_audio_stream_play(music);
    
    /* print mod details */
    /* zf_audio_mod_print(mod); */
    
    /* print sample details */
    /* zf_audio_sample_print(sample); */
}

/*
  drawstring... just like pants! - har har
*/
static void drawstring(void* font, char* s)
{
    if (s && strlen(s)) 
    {
	while (*s) 
	{
	    glutBitmapCharacter(font, *s);
	    s++;
	}
    }
}

void draw_spectrum(void)
{
    unsigned int i;
    unsigned int j;
    
    for (i = 0 ; i < NUM_SPECTRUM_ENTRIES ; i++)
    {
	j = (i + offset) % NUM_SPECTRUM_ENTRIES;
	
	glPushMatrix();
	
	if (helix_mode_flag)
	{
	    glRotatef(20.0f * i, 1.0f, 0.0f, 0.0f);
	    glTranslatef(0.0f, 0.04f, 0.0f);
	}
	
	glBegin(GL_QUADS);
	
	/* max */
	glColor3f(0.0f, 0.0f, 1.0f);
	glVertex3f(column_width, 0.0f,                      0.0f);
	glVertex3f(0.0f,         0.0f,                      0.0f);
	glVertex3f(0.0f,         spectrum_max[j]*column_height, 0.0f);
	glVertex3f(column_width, spectrum_max[j]*column_height, 0.0f);
	
	/* front */
	glColor3f(0.0f, 1.0f, 0.0f);
	glVertex3f(column_width, 0.0f,                      0.0f);
	glVertex3f(0.0f,         0.0f,                      0.0f);
	glColor3f(spectrum[j], 1.0f - spectrum[j], 0.0f);
	glVertex3f(0.0f,         spectrum[j]*column_height, 0.0f);
	glVertex3f(column_width, spectrum[j]*column_height, 0.0f);
	
	if (helix_mode_flag)
	{
	    /* left side */
	    glColor3f(0.0f, 1.0f, 0.0f);
	    glVertex3f(0.0f,         0.0f,                      0.0f);
	    glVertex3f(0.0f,         0.0f,                      column_depth);
	    glColor3f(spectrum[j], 1.0f - spectrum[j], 0.0f);
	    glVertex3f(0.0f,         spectrum[j]*column_height, column_depth);
	    glVertex3f(0.0f,         spectrum[j]*column_height, 0.0f);
	    
	    /* right side */
	    glColor3f(0.0f, 1.0f, 0.0f);
	    glVertex3f(column_width, 0.0f,                      0.0f);
	    glVertex3f(column_width, 0.0f,                      column_depth);
	    glColor3f(spectrum[j], 1.0f - spectrum[j], 0.0f);
	    glVertex3f(column_width, spectrum[j]*column_height, column_depth);
	    glVertex3f(column_width, spectrum[j]*column_height, 0.0f);
	    
	    /* back */
	    glColor3f(0.0f, 1.0f, 0.0f);
	    glVertex3f(column_width, 0.0f,                      column_depth);
	    glVertex3f(0.0f,         0.0f,                      column_depth);
	    glColor3f(spectrum[j], 1.0f - spectrum[j], 0.0f);
	    glVertex3f(0.0f,         spectrum[j]*column_height, column_depth);
	    glVertex3f(column_width, spectrum[j]*column_height, column_depth);
	    
	    /* top */
	    glColor3f(spectrum[j], 1.0f - spectrum[j], 0.0f);
	    glVertex3f(0.0f,         spectrum[j]*column_height, 0.0f);
	    glVertex3f(column_width, spectrum[j]*column_height, 0.0f);
	    glVertex3f(column_width, spectrum[j]*column_height, column_depth);
	    glVertex3f(0.0f,         spectrum[j]*column_height, column_depth);
	}
	glEnd();
	
	glPopMatrix();
	
	glTranslatef(column_width, 0.0f, 0.0f);
    }
}

/*!
  \todo Have a series of squares at the bottom of the screen that
  correspond to the samples in the mod, have them change from black to some
  colour as the sample plays

  \todo Display a hit when every 4th row plays.
*/
static void display(void)
{
    if (helix_mode_flag)
    {
	framecount++;
	
	if (!(framecount % 20))
	{
	    offset = (offset - 1) % NUM_SPECTRUM_ENTRIES;
	    /* printf("offset = %u\n", offset ); */
	}
    }
    
    /* get spectrum */
    spectrum = FSOUND_DSP_GetSpectrum();

    /* update spectrum_max */
    for (i = 0 ; i < NUM_SPECTRUM_ENTRIES ; i++)
    {
	if (spectrum[i] >= spectrum_max[i])
	{
	    spectrum_max[i] = spectrum[i];
	}
	else
	{
	    spectrum_max[i] -= 0.0005;
	}
    }
	
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    clLoadLight(light0);
    clLoadLight(light1);
    
    glPushMatrix();
    
    /* rotate and translate */
    glTranslatef(0.0f, 0.0f, -zoom);
    glRotatef(CL_RAD2DEG(angle_x), 1.0f, 0.0f, 0.0f);
    glRotatef(CL_RAD2DEG(angle_y), 0.0f, 1.0f, 0.0f);
    glRotatef(CL_RAD2DEG(angle_z), 0.0f, 0.0f, 1.0f);
    
    glPushMatrix();
    
    glTranslatef(-ANALYSER_WIDTH / 2.0f, -ANALYSER_HEIGHT / 2.0f, 0.0f);
    
    /* draw spectrum */
    draw_spectrum();
    
    glPopMatrix();
    
    /* draw max amplitude line */
    glColor3f(1.0f, 0.0f, 0.0f);
    glBegin(GL_LINES);
    glVertex3f(-ANALYSER_WIDTH / 2.0f, ANALYSER_HEIGHT / 2.0f, 0.0f);
    glVertex3f(ANALYSER_WIDTH / 2.0f, ANALYSER_HEIGHT / 2.0f, 0.0f);
    glEnd();
    
    glPopMatrix();
    
    glFlush();
    glutSwapBuffers();
    
    frame_count++;
    
    glutPostRedisplay();
}

static void reshape(int w, int h)
{
  width = w;
  height = h;

  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0, (double)width/(double)height, 0.1, 10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

/*! 
  \todo On escape press, stop song, free song, close fmod.
  \todo Add pause key, pause playback and check if spectrum analyser pauses, if not, stop getting new spectrums from the FFT unit.
*/
static void keyboard(unsigned char key, int x, int y)
{
    switch ((int) key)
    {
    case 27:
	exit(0);
	break;
	
    case 'r':
	/* reset coordinate frame */
	angle_x = 0.0f;
	angle_y = 0.0f;
	angle_z = 0.0f;
	break;

    case 'h':
	/* toggle draw information */
	helix_mode_flag = !helix_mode_flag;
	break;

    case 'p':
	/* play stream */
	zf_audio_stream_play(music);
	break;

    case 's':
	/* stop stream */
	zf_audio_stream_stop(music);
	break;

    case '1':
	zf_audio_sample_play(sample0);
	break;

    case '2':
	zf_audio_sample_play(sample1);
	break;

    case '3':
	zf_audio_sample_play(sample2);
	break;
    }
}

static void special(int key, int x, int y)
{
  switch (key)
  {
  case GLUT_KEY_UP:
    zoom -= 0.01f;
    break;
    
  case GLUT_KEY_DOWN:
    zoom += 0.01f;
    break;
  }
}

static void mouse(int button, int state, int x, int y)
{
  last_x = x;
  last_y = y;
}

static void motion(int x, int y)
{
  angle_x += ((GLfloat)(last_y - y) / height) * CL_PI;
  angle_y += ((GLfloat)(last_x - x) / width) * CL_PI;

  last_x = x;
  last_y = y;
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(1024, 768);
    glutInitWindowPosition(0,0);
    glutCreateWindow(argv[0]);
    
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(special);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);

    /* parse command line */
    argv++; argc--;
    
    /* get the input filename */
    if (argc > 0)
    {
	input_filename = argv[0];
	argv++; argc--; 
    }
    else
    {
	printf("Add a filename to the command line. Try some of the music in ../data/music/\n");
	exit(1);
    }
    
    init();
    
    if (fullscreen_flag)
    {
	glutFullScreen();
    }
    
    glutMainLoop();
    
    return 1;
}

