#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <termios.h>

#include "data/drums.h"
#include "main.h"
#include "keysong.h"
#include "keysong2.h"
#include "volumesong.h"
#include "typesong.h"

#include <fcntl.h>
#include <sys/soundcard.h>

#include "decrunch.h"

#ifdef SDL
#include <SDL.h>

SDL_AudioSpec audio_spec;
#endif

#define CONSTANT	(1.004f)
#define SONG_SIZE	7642112
FILE *audp;

float volume = 0;

tcflag_t old_lflag;

float drum_loop[3][0x1ffff];

int mods[0x20];


int keysong_pos = 0;
int keysong2_pos = 0;
int volumesong_pos = 0;
int typesong_pos = 0;
int clock, pat;

char bit_codes[] = {
 "qwertyuiasdfghjkl"
};

float speed=1.0f;
int bend_up, bend_down;

float pass2_buf[SONG_SIZE];

const char spinner[4] = { "|/-\\" };

short rand_short(int *seed) {
 return ((*seed = *seed * 214013L + 2531011L) >>16) &0x7fff;
}

#define RAND_NORMAL(X) \
 ((float)rand_short(X) / (float)0x7fff) 

void q_input(void) {
#ifdef SDL
 int i;
 SDL_Event event;
 while(SDL_PollEvent(&event) ) {
  switch(event.type) {
   case SDL_QUIT:
     exit(-1);
    break;
   case SDL_KEYDOWN:
    if(event.key.keysym.sym == SDLK_ESCAPE)
     exit(-1);
    for(i=0;i<16;i++) 
     if(event.key.keysym.sym == bit_codes[i]) {
      mods[i+1] = 1;
      printf("{%d, %d, 1},\n", clock, i+1);
     }
    if(event.key.keysym.sym == SDLK_SPACE )
     mods[18] = 1;
    break;
   case SDL_KEYUP:
    for(i=0;i<16;i++) 
     if(event.key.keysym.sym == bit_codes[i]) {
      mods[i+1] = 0;
      printf("{%d, %d, 0},\n", clock, i+1);
     }
    if(event.key.keysym.sym == SDLK_SPACE) 
     mods[18] = 0;
    break;
  }
 }

#endif
}

#define SET_BIT(x,y) (x|(1<<y))
#define TEST_BIT(x,y) ((x>>y)&1)
int mod_pos(int in) {
 int out, pool,i;
 pool = 0;
 out = in;
 for(i=0;i<17;i++)
  if(mods[i+1] == 1)
   if(TEST_BIT(out,i) == 1) 
    pool = 1;
 if((mods[18] == 1) && (pool == 1)) 
  return 0; 
 for(i=0;i<17;i++)
  if((mods[i+1] == 1) && (pool == 1))
   out = SET_BIT(out,i); 
 return out%0x1ffff;
}


void build_loop(void) {
 int clock, pos = 0, i;
 float acum;
 for(clock = 0;clock<0x1ffff;clock++){
  if((clock%4096) == 0) {
   if(pattern[pos%32].l!=0)
    trigger(1, &samples[pattern[pos%32].l - 6]);

   pos++; 
  }
  acum = 0;
  for(i=0;i<18;i++)
   acum += pull(&samples[i]);
  drum_loop[0][clock] = acum;
 }

 for(clock = 0;clock<0x1ffff;clock++){
  if((clock%4096) == 0) {
   if(pattern[pos%32].r!=0)
    trigger(1, &samples[pattern[pos%32].r - 6]);

   pos++; 
  }
  acum = 0;
  for(i=0;i<18;i++)
   acum += pull(&samples[i]);
  drum_loop[1][clock] = acum;
 }

 for(clock = 0;clock<0x1ffff;clock++){
  if((clock%4096) == 0) {
   if(pattern[pos%32].l!=0)
    trigger(2, &samples[pattern[pos%32].l - 6]);
   pos++; 
  }
  acum = 0;
  for(i=0;i<18;i++)
   acum += pull(&samples[i]);
  drum_loop[2][clock] = acum;
 }

}


void end_game(int in) {
 struct termios ios;
 tcgetattr(fileno(stdin), &ios);
 ios.c_lflag = old_lflag;
 tcsetattr(fileno(stdin), TCSANOW, &ios);
 exit(0);
}

void check_escape(void) {
 unsigned char dat;
 if((read(fileno(stdin), &dat, 1))==1) {
  if(dat == '\33') end_game(0);
  putchar(dat);
 }
}

float pull_pass1(void) {
 int pos2;
 float ret;

 if(clock>=keysong[keysong_pos].clock_pos) {
  mods[keysong[keysong_pos].mod] = keysong[keysong_pos].value;
  keysong_pos++;
 }

 if(clock>=volumesong[volumesong_pos].clock_pos) {
  volume = volumesong[volumesong_pos].value;
  volumesong_pos++;
 }

 pos2 = mod_pos(clock);
  
 ret = 0;
 ret = drum_loop[0][pos2 % 0x1ffff];
 if(pat > 8)
  ret+= drum_loop[1][pos2 % 0x1ffff];
 if(((pat-8)%8)>5)
  ret+= drum_loop[2][pos2 % 0x1ffff];

 return ret * volume;
}

enum {
 A,S,REST
};

typedef struct {
 float a,s;
} env_param_t;

typedef struct {
 float v;
 int phase;
} env_t;

float env(env_param_t *in, env_t *cur) {
 switch(cur->phase) {
  case A:
   cur->v+=in->a;
   if(cur->v >= 1.0f) {
    cur->v = 1.0f;
    cur->phase = S;
   }
   break;
  case S:
   cur->v-=in->s;
   if(cur->v<=0.0f)
    cur->v = 0.0f;
   break;
 }
 return cur->v;
}

typedef struct {
 float v;
 double pos;
} osc_t;

float osc(float freq, osc_t *in) {
 in->v = sin(in->pos); 
 in->pos+=freq;
 return in->v;
}

typedef struct {
 float v;
 float freq;
 float tune[4];
 env_param_t op_env_param[4];
 env_t       op_env[4];
 osc_t	     op_osc[4];
} fm_t;

void comput_fm(int seed, fm_t *in) {
 int i;
 for(i=0;i<4;i++) {
  in->tune[i] = (RAND_NORMAL(&seed) - 0.5f) * 0.23f;
  in->op_env_param[i].a = RAND_NORMAL(&seed)*0.023f;
  in->op_env_param[i].s = RAND_NORMAL(&seed)*0.000023f;
 }
}

void trigger_fm(float freq, fm_t *in) {
 int i; 
 in->freq = freq;
 for(i=0;i<4;i++) 
  in->op_env[i].phase = A;
}

float pull_fm(fm_t *in) {
 float tmp_float[4];

 tmp_float[0] = env(&in->op_env_param[0], &in->op_env[0]) * 
                osc(in->freq + in->tune[0], &in->op_osc[0]);
 tmp_float[1] = env(&in->op_env_param[1], &in->op_env[1]) * 
                osc(in->freq + in->tune[1], &in->op_osc[1]);

 tmp_float[2] = env(&in->op_env_param[2], &in->op_env[2]) * 
                osc(in->freq + in->tune[2] + tmp_float[0], &in->op_osc[2]);
 tmp_float[3] = env(&in->op_env_param[3], &in->op_env[3]) * 
                osc(in->freq + in->tune[3] + tmp_float[1], &in->op_osc[3]);

 in->v = tmp_float[2] + tmp_float[3];


 return in->v;
}

fm_t pass2_fm;

void init_pass2(void) {
// comput_fm(589806, &pass2_fm);
  comput_fm(70, &pass2_fm);
// trigger_fm(0.0034f, &pass2_fm);
}

float pull_pass2(void) {
 osc_t o;
 float acum;
 acum = pull_pass1();

 acum+= pull_fm(&pass2_fm) * 0.33f; 

 if(clock%(0x1ffff/4)==0) {
  trigger_fm(0.0000034f, &pass2_fm);
 }
 return acum;
}

#ifdef SDL
void audio(void *userdata, Uint8 *stream, int len) {
 float acum;
 unsigned char dat1, dat2;
 signed short data;
 int i;
 int pos2;
 for(i=0;i<len;) {
  if((clock%0x1ffff)==0) pat++;
 
  pos2 = mod_pos(clock%0x1ffff);

  acum = pass2_buf[pos2 + (0x1ffff * pat)];

  if(clock>=keysong2[keysong2_pos].clock_pos) {
   mods[keysong2[keysong2_pos].mod] = keysong2[keysong2_pos].value;
   keysong2_pos++;
  }

  if(acum > 1.2f)
   acum = 1.2f;
  if(acum < -1.2f)
   acum = -1.2f;
  data = acum * 20000;
  dat1 = data&0xff;
  dat2 = (data&0xff00)>>8;
  stream[i++] = dat1;
  stream[i++] = dat2;
  stream[i++] = dat1;
  stream[i++] = dat2;


  clock++;
 }
}
#endif

int main(void) {
 struct termios ios;
 float acum;
 unsigned char dat1, dat2;
 signed short data;
 audio_buf_info buf_info;
 char *name;
 int format, channels, rate, fragspec;
 int qq, i,j;
 int lpos, pos2;
 float beat_pos;
#ifdef SDL
 SDL_AudioSpec spec;
#endif
 name = "/dev/dsp";

#ifdef SDL
 SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
#endif

#ifndef SDL
 if((audp=fopen(name, "w"))<=0) {
  perror(name);
  exit(-1);

 } 
 format = AFMT_S16_LE;
 rate = 44100;
 channels = 2;
 if( ioctl(fileno(audp), SNDCTL_DSP_SETFMT, &format) == -1 ||
     ioctl(fileno(audp), SNDCTL_DSP_SPEED, &rate)    == -1 ||
     ioctl(fileno(audp), SNDCTL_DSP_STEREO, &channels)==-1 ){
  printf("couldn't set proper mode for %s\n", name);
  exit(-1);
 } else
  if(format != AFMT_S16_LE ||
     channels!=1) {
   printf("couldn't get proper playback for %s\n", name);
   exit(-1);
  }
 ioctl(fileno(audp), SNDCTL_DSP_GETOSPACE, &buf_info);
 if(buf_info.fragsize >= 32768) 
  printf("you have a big fragment size, audio might be delayed\n");
 fragspec = 0x00080000 | (buf_info.fragsize&0xffff);
 if(ioctl(fileno(audp), SNDCTL_DSP_SETFRAGMENT, &fragspec) == -1) { 
  printf("can't set fragments\n");
  exit(-1);
 } 
#endif

#ifdef SDL
 spec.freq = 44100;
 spec.format = AUDIO_S16;
 spec.channels = 2;
 spec.samples=512;
 spec.callback = audio;
 if(SDL_OpenAudio(&spec, &audio_spec)<0) {
  printf("%s\n", SDL_GetError());
  exit(-1);
 }
#endif
 signal(SIGQUIT, end_game);
 signal(SIGABRT, end_game);
 signal(SIGTERM, end_game);
 signal(SIGKILL, end_game);

 
 fcntl(fileno(stdin),F_SETFL, fcntl(fileno(stdin),F_GETFL,0)|O_NONBLOCK);
 tcgetattr(fileno(stdin), &ios);
 old_lflag = ios.c_lflag;
 ios.c_lflag &= ~ICANON;
 ios.c_lflag &= ~ECHO;
 tcsetattr(fileno(stdin), TCSANOW, &ios);


 decrunch();

 build_loop();

#ifdef SDL
 SDL_SetVideoMode(100,100,0,0);
#endif

 bzero(mods, 0x20*sizeof(int));
 pat = 0;

 clock = 0;
 pat = 0;

 init_pass2();
 for(i=0;i<SONG_SIZE;i++) {
  pass2_buf[i] = pull_pass2();
  if((clock%0x1ffff)==0) pat++;
   clock++;

 }



#ifndef SDL

 clock = 0;
 pat = 0;
 bzero(mods, sizeof(int) * 18);

 for(clock = 0;clock<SONG_SIZE;clock++) {
  if((clock%0x1ffff)==0) pat++;

  pos2 = mod_pos(clock%0x1ffff);

  acum = pass2_buf[pos2 + (0x1ffff * pat)];

  if(clock>=keysong2[keysong2_pos].clock_pos) {
   mods[keysong2[keysong2_pos].mod] = keysong2[keysong2_pos].value;
   keysong2_pos++;
  }


  if(clock>=typesong[typesong_pos].clock_pos) {
   putchar(typesong[typesong_pos].value);
   fflush(stdout);
   typesong_pos++;
  }

  if(acum > 1.2f)
   acum = 1.2f;
  if(acum < -1.2f)
   acum = -1.2f;
  data = acum * 20000;
  dat1 = data&0xff;
  dat2 = (data&0xff00)>>8;
  write(fileno(audp), &dat1, 1);
  write(fileno(audp), &dat2, 1);
  write(fileno(audp), &dat1, 1);
  write(fileno(audp), &dat2, 1);

  check_escape();
  q_input();
  fflush(stdout);
  fflush(audp);
 }
#endif

#ifdef SDL
 clock = 0;
 pat = 0;
 bzero(mods, sizeof(int) * 18);
 SDL_PauseAudio(0);
 for(;;) 
  q_input();
#endif
}
