#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <SDL/SDL.h>

#include "global.h"
#include "psp_sdl.h"
#include "psp_dve.h"
#include "osint.h"
#include "vecx.h"
#include "bios.h"
#include "wnoise.h"            // White noise waveform

# if 0 //GOOD
#define SOUND_FREQ 11025
#define SOUND_SAMPLE 512
#define SOUND_FACTOR 660
# endif

# if 0 //TEST BOF
#define SOUND_FREQ   22050
#define SOUND_SAMPLE  1024
#define SOUND_FACTOR  1320
# endif

# if 1 //TEST
#define SOUND_FREQ   44100
#define SOUND_SAMPLE  1024
#define SOUND_FACTOR  2640
# endif
# if 0
#define SOUND_FREQ   22050
#define SOUND_SAMPLE 441
# endif

// AY38910 emulation stuff
extern unsigned snd_regs[16];
Uint8 AY_vol[3];
Uint16 AY_spufreq[3];
Uint16 AY_noisefreq;
Uint8 AY_tone_enable[3];
Uint8 AY_noise_enable[3];

#define video_buffer_pitch BLIT_WIDTH

static unsigned short color_set[VECTREX_COLORS];

# define scl_factor 100

static void 
osint_gencolors (void)
{
	int c;
	unsigned char rcomp, gcomp, bcomp;

	for (c = 0; c < VECTREX_COLORS; c++) {
		rcomp = c * 256 / VECTREX_COLORS;
		gcomp = c * 256 / VECTREX_COLORS;
		bcomp = c * 256 / VECTREX_COLORS;
    color_set[c] = SDL_MapRGB(back_surface->format,rcomp,gcomp,bcomp);
	}
}

static inline unsigned short *
osint_pixelptr (long x, long y)
{
	unsigned short *ptr;
	ptr = blit_surface->pixels;
	ptr += y * video_buffer_pitch;
	ptr += x;

	return ptr;
}

/* draw a line with a slope between 0 and 1.
 * x is the "driving" axis. x0 < x1 and y0 < y1.
 */

static void osint_linep01 (long x0, long y0, long x1, long y1, short color)
{
	long dx, dy;
	long i0, i1;
	long j, e;
	unsigned short *ptr;

	dx = x1 - x0;
	dy = y1 - y0;

	i0 = x0 / scl_factor;
	i1 = x1 / scl_factor;
	j  = y0 / scl_factor;

	e = dy * (scl_factor - (x0 % scl_factor)) -
		  dx * (scl_factor - (y0 % scl_factor));

	dx *= scl_factor;
	dy *= scl_factor;

	ptr = osint_pixelptr (i0, j);

	for (; i0 <= i1; i0++) {
    *ptr++ = color;
		if (e >= 0) {
			ptr += video_buffer_pitch;
			e -= dx;
		}
		e += dy;
	}
}

/* draw a line with a slope between 1 and +infinity.
 * y is the "driving" axis. y0 < y1 and x0 < x1.
 */

static void osint_linep1n (long x0, long y0, long x1, long y1, short color)
{
	long dx, dy;
	long i0, i1;
	long j, e;
	unsigned short *ptr;

	dx = x1 - x0;
	dy = y1 - y0;

	i0 = y0 / scl_factor;
	i1 = y1 / scl_factor;
	j  = x0 / scl_factor;

	e = dx * (scl_factor - (y0 % scl_factor)) -
		  dy * (scl_factor - (x0 % scl_factor));

	dx *= scl_factor;
	dy *= scl_factor;

	ptr = osint_pixelptr (j, i0);

	for (; i0 <= i1; i0++) {
    *ptr = color;

		if (e >= 0) {
			ptr++;
			e -= dy;
		}

		e += dx;
		ptr += video_buffer_pitch;
	}
}

/* draw a line with a slope between 0 and -1.
 * x is the "driving" axis. x0 < x1 and y1 < y0.
 */

static void osint_linen01 (long x0, long y0, long x1, long y1, short color)
{
	long dx, dy;
	long i0, i1;
	long j, e;
	unsigned short *ptr;

	dx = x1 - x0;
	dy = y0 - y1;

	i0 = x0 / scl_factor;
	i1 = x1 / scl_factor;
	j  = y0 / scl_factor;

	e = dy * (scl_factor - (x0 % scl_factor)) -
		  dx * (y0 % scl_factor);

	dx *= scl_factor;
	dy *= scl_factor;

	ptr = osint_pixelptr (i0, j);

	for (; i0 <= i1; i0++) {
    *ptr = color;

		if (e >= 0) {
			ptr -= video_buffer_pitch;
			e -= dx;
		}

		e += dy;
		ptr++;
	}
}

/* draw a line with a slope between -1 and -infinity.
 * y is the "driving" axis. y0 < y1 and x1 < x0.
 */

static void osint_linen1n (long x0, long y0, long x1, long y1, short color)
{
	long dx, dy;
	long i0, i1;
	long j, e;
	unsigned short *ptr;

	dx = x0 - x1;
	dy = y1 - y0;

	i0 = y0 / scl_factor;
	i1 = y1 / scl_factor;
	j  = x0 / scl_factor;

	e = dx * (scl_factor - (y0 % scl_factor)) -
		dy * (x0 % scl_factor);

	dx *= scl_factor;
	dy *= scl_factor;

	ptr = osint_pixelptr (j, i0);

	for (; i0 <= i1; i0++) {
    *ptr = color;
		if (e >= 0) {
			ptr--;
			e -= dy;
		}

		e += dx;
		ptr += video_buffer_pitch;
	}
}

static void osint_line (point_t* p0, point_t* p1, short color)
{
  long x0 = p0->x;
  long y0 = p0->y;
  long x1 = p1->x;
  long y1 = p1->y;

	if (x1 > x0) {
		if (y1 > y0) {
			if ((x1 - x0) > (y1 - y0)) {
				osint_linep01 (x0, y0, x1, y1, color);
			} else {
				osint_linep1n (x0, y0, x1, y1, color);
			}
		} else {
			if ((x1 - x0) > (y0 - y1)) {
				osint_linen01 (x0, y0, x1, y1, color);
			} else {
				osint_linen1n (x1, y1, x0, y0, color);
			}
		}
	} else {
		if (y1 > y0) {
			if ((x0 - x1) > (y1 - y0)) {
				osint_linen01 (x1, y1, x0, y0, color);
			} else {
				osint_linen1n (x0, y0, x1, y1, color);
			}
		} else {
			if ((x0 - x1) > (y0 - y1)) {
				osint_linep01 (x1, y1, x0, y0, color);
			} else {
				osint_linep1n (x1, y1, x0, y0, color);
			}
		}
	}
}

void
osint_video_reset(void)
{
  short *ptr = blit_surface->pixels;
  int len = blit_surface->w * blit_surface->h;
  while (len--) {
    *ptr++ = 0;
  }
}

static int loc_render_count = 0;

void 
osint_render(void) 
{
  if (++loc_render_count > 30) {
    osint_video_reset();
    loc_render_count = 0;
  }

  int v;
  for (v = 0; v < vector_erse_cnt; v++) {
    unsigned short color = vectors_erse[v].color;
    if (color & 0x80) continue;
    osint_line (&vectors_erse[v].p0, &vectors_erse[v].p1, 0);
  }

	for (v = 0; v < vector_draw_cnt; v++) {
    unsigned char color = vectors_draw[v].color;
    if (color & 0x80) continue;
    unsigned short color_pix = color_set[color];
		osint_line (&vectors_draw[v].p0, &vectors_draw[v].p1, color_pix);
	}

# if 0
  int x,y;
  unsigned short* scan_pixels = blit_surface->pixels;
  unsigned char * scan_buffer = video_buffer;
  size_t pixels_pitch = blit_surface->pitch >> 1;
  for (y = 0; y < SCR_HEIGHT; y++) {
    scan_buffer = video_buffer + (y + (y >> 1)) * SCR_WIDTH;
    for (x = 0; x < SCR_WIDTH; x++) {
      unsigned char vec_color = scan_buffer[x + (x >> 1)];
      *scan_pixels++ = color_set[ vec_color ];
    }
  }

  int x,y;
  unsigned short* scan_pixels = blit_surface->pixels;
  unsigned char * scan_buffer = video_buffer;
  size_t pixels_pitch = blit_surface->pitch >> 1;
  for (y = 0; y < SCR_HEIGHT; y++) {
    for (x = 0; x < SCR_WIDTH; x++) {
      unsigned char vec_color = *scan_buffer++;
      *scan_pixels++ = color_set[ vec_color ];
    }
    scan_pixels += pixels_pitch - SCR_WIDTH;
  }
# endif

# if 0
  int x,y;
  unsigned short* scan_pixels = back_surface->pixels;
  unsigned char * scan_buffer = video_buffer;
  size_t pixels_pitch = back_surface->pitch >> 1;
  for (y = 0; y < SCR_HEIGHT; y++) {
    for (x = 0; x < SCR_WIDTH; x++) {
      unsigned char vec_color = *scan_buffer++;
      *scan_pixels++ = color_set[ vec_color ];
    }
    scan_pixels += pixels_pitch - SCR_WIDTH;
  }
# endif
}



int
dve_loadcart(const char *filename) 
{
  FILE *f;
  unsigned int filesize;

  f = fopen(filename, "rb");
  if (!f) {
    fprintf(stderr, "Can't open cartridge image (%s).\n", filename);
    return (1);
  }

  fseek(f, 0, SEEK_END);
  filesize = ftell(f);
  fseek(f, 0, SEEK_SET);

  if (filesize > sizeof(cart)) {
    fprintf(
      stderr,
      "%s is not a valid Vectrex cartridge.\n"
      "It's larger that %d bytes.\n",
      filename, sizeof(cart)
    );
    fclose(f);
    return (1);
  }

  if (filesize != fread(cart, 1, filesize, f)) {
    fprintf(stderr, "Error while reading %s.\n", filename);
    fclose(f);
    return (1);
  }

  fclose(f);
  return 0;
}

// sound mixer callback
static void 
fillsoundbuffer(void *userdata, Uint8 *stream, int len) 
{
    // PS2 AY to SPU conversion:
    // SPU freq = (0x1000 * 213) / divisor

  // AY regs:
  // 0, 1 divisor channel A (12-bit)
  // 2, 3 divisor channel B
  // 4, 5 divisor channel C
  // 6    noise divisor (5-bit)
  // 7    mixer (|x|x|nC|nB|nA|tC|tB|tA)
  // 8  volume A
  // 9   volume B
  // 10   volume C
  static unsigned int noisepos = 0;
    int i;
    int divisor;

  float step[3];
  float noisestep;
  float flip[3];
  Uint8 val[3];
  Uint8 lastval;

    AY_tone_enable[0] = snd_regs[7] & 0x01;
    AY_tone_enable[1] = snd_regs[7] & 0x02;
    AY_tone_enable[2] = snd_regs[7] & 0x04;

    AY_noise_enable[0] = snd_regs[7] & 0x08;
    AY_noise_enable[1] = snd_regs[7] & 0x10;
    AY_noise_enable[2] = snd_regs[7] & 0x20;

    // calc noise freq
    divisor = (snd_regs[6] & 0x1F) << 4;
    if(divisor == 0) divisor = 256;
    
    AY_noisefreq = (SOUND_SAMPLE * 213) / divisor;

    // calc tone freq and vols
    for(i=0; i<3; i++) {
        //divisor = snd_regs[i*2] + snd_regs[i*2+1] * 256;
        divisor = snd_regs[i*2] | (snd_regs[i*2+1] << 8);
        if(divisor == 0) divisor = 4095;

        //AY_spufreq[i] = (SOUND_SAMPLE * 213) / divisor;
    AY_spufreq[i] = divisor;

        AY_vol[i] = (snd_regs[8+i] & 0x0F) << 2; //<< 9;

    }

# if 0
  step[0] = SOUND_SAMPLE*(float)AY_spufreq[0]/(float)SOUND_FREQ;
  step[1] = SOUND_SAMPLE*(float)AY_spufreq[1]/(float)SOUND_FREQ;
  step[2] = SOUND_SAMPLE*(float)AY_spufreq[2]/(float)SOUND_FREQ;
  noisestep = (SOUND_SAMPLE * AY_noisefreq) / (float)SOUND_FREQ;
# else
  step[0] = SOUND_FACTOR*(float)AY_spufreq[0]/(float)SOUND_FREQ;
  step[1] = SOUND_FACTOR*(float)AY_spufreq[1]/(float)SOUND_FREQ;
  step[2] = SOUND_FACTOR*(float)AY_spufreq[2]/(float)SOUND_FREQ;
  noisestep = (SOUND_FACTOR * AY_noisefreq) / (float)SOUND_FREQ;
# endif
  //step[0] = 660.0f*(float)AY_spufreq[0]/(float)SOUND_FREQ;
  //step[1] = 660.0f*(float)AY_spufreq[1]/(float)SOUND_FREQ;
  //step[2] = 660.0f*(float)AY_spufreq[2]/(float)SOUND_FREQ;
  //noisestep = (660.0f * AY_noisefreq) / (float)SOUND_FREQ;
  flip[0] = step[0];
  flip[1] = step[1];
  flip[2] = step[2];
  val[0] = AY_vol[0];
  val[1] = AY_vol[1];
  val[2] = AY_vol[2];

/* JH - REFERENCE CODE FROM VECXPS2
      // update tone voices
    for(i=0; i<3; i++) {
//        spu_remote(1,spuSetCore,1,0,0,0,0,0);
        voice_att.mask = SPU_VOICE_PITCH | SPU_VOICE_VOL_LEFT | SPU_VOICE_VOL_RIGHT;
        voice_att.voice = SPU_VOICE_X(i);
        voice_att.pitch = spufreq[i];
        voice_att.vol.left = voice_att.vol.right = 0;
        if( !tone_enable[i] ) 
            voice_att.vol.left = voice_att.vol.right = vol[i];
        spu_remote(1,spuSetVoiceAttr,(u32)&voice_att,sizeof(struct spu_voice_attr),0,0,0,0);
    }


    // update noise voices
    for(i=0; i<3; i++) {
        voice_att.mask = SPU_VOICE_PITCH | SPU_VOICE_VOL_LEFT | SPU_VOICE_VOL_RIGHT;
        voice_att.voice = SPU_VOICE_X(i+3);
        voice_att.pitch = noisefreq;
        voice_att.vol.left = voice_att.vol.right = 0;
        if( !noise_enable[i] ) 
            voice_att.vol.left = voice_att.vol.right = vol[i];
        spu_remote(1,spuSetVoiceAttr,(u32)&voice_att,sizeof(struct spu_voice_attr),0,0,0,0);
    }
*/
  // fill buffer
  lastval = 0;
  for(i=0; i<len; i++)
  {
    stream[i] = 0;

    // do tones
    if(!AY_tone_enable[0] && AY_spufreq[0] < 4095)
      stream[i] += val[0];
    if(!AY_tone_enable[1] && AY_spufreq[1] < 4095)
      stream[i] += val[1];
    if(!AY_tone_enable[2] && AY_spufreq[1] < 4095)
      stream[i] += val[2];

    if(i>(int)flip[0]) {
      if(val[0] == AY_vol[0]) val[0] = 0;
      else val[0] = AY_vol[0];
      flip[0] = flip[0] + step[0];
    }
    if(i>(int)flip[1]) {
      if(val[1] == AY_vol[1]) val[1] = 0;
      else val[1] = AY_vol[1];
      flip[1] = flip[1] + step[1];
    }
    if(i>(int)flip[2]) {
      if(val[2] == AY_vol[2]) val[2] = 0;
      else val[2] = AY_vol[2];
      flip[2] = flip[2] + step[2];
    }

    // do noise
    if(!AY_noise_enable[0])
      stream[i] += (AY_vol[0] * wnoise[noisepos] >> 9);
    if(!AY_noise_enable[1])
      stream[i] += (AY_vol[1] * wnoise[noisepos] >> 9);
    if(!AY_noise_enable[2])
      stream[i] += (AY_vol[2] * wnoise[noisepos] >> 9);

    noisepos += (int)noisestep;
    if(noisepos > wnoise_size)
      noisepos -= wnoise_size;

    // average last 2 samples
    stream[i] = (stream[i] + lastval) >> 1;        
    lastval = stream[i];
  }
}

static void
osint_initsound()
{
  // SDL audio stuff
  SDL_AudioSpec reqSpec;
  SDL_AudioSpec givenSpec;

  // set up audio buffering
  reqSpec.freq = SOUND_FREQ;            // Audio frequency in samples per second
  reqSpec.format = AUDIO_U8;          // Audio data format
  reqSpec.channels = 1;            // Number of channels: 1 mono, 2 stereo
  reqSpec.samples = SOUND_SAMPLE;            // Audio buffer size in samples
  reqSpec.callback = fillsoundbuffer;      // Callback function for filling the audio buffer
  reqSpec.userdata = NULL;
  /* Open the audio device */
  if ( SDL_OpenAudio(&reqSpec, &givenSpec) < 0 ){
    fprintf(stderr, "Couldn't open audio: %s\n", SDL_GetError());
    exit(-1);
  }

  // Start playing audio
  SDL_PauseAudio(0);
}

int 
SDL_main(int argc, char* argv[]) 
{  
  memcpy(rom, bios, sizeof(rom));
  memset(cart, 0, sizeof(cart));

  dve_initialize();

  osint_gencolors();
  osint_initsound();

  vecx_reset();

  psp_sdl_black_screen();

  vecx_emu_loop();

  return 0;
}
