/*****************************************************
Scope Simulator
by Chris Merck <navaburo@gmail.com>
Compiles best on Linux (but may compile on other systems)

This software may be freely redistributed under the terms
 of the GNU public license.


This program reads 8-bit unsigned stereo PCM data from stdin 
 and displays it on the screen like an oscilliscope in XY mode.

A slow phosphor is simulated, poorly. But it works!
*****************************************************/

#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>
#include <SDL/SDL.h>

#define true 1
#define false 0
#define PI 3.14159


#define mode -1

#define SIXTEEN_BIT_WAV

//#define DOUBLE_SIZE
#define FLIP_XY

#ifdef FLIP_XY
#define Xp Y
#define Yp X
#else
#define Xp X
#define Yp Y
#endif

#define rd 3
#define gd 4

#if mode == -1
#define WMAKER 256+50
#else
#define WMAKER 0
#endif

#ifndef DOUBLE_SIZE
#define XMAX 256+WMAKER
#define YMAX 256
#else
#define XMAX 512
#define YMAX 512
#endif

#define DEPTH 1024  // size of circular buffer for simulating phosphor
#define BLOCK 1024    // number of samples read from stdin at a time

void
setpixel (SDL_Surface * screen, int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
  Uint8 *ubuff8;
  Uint16 *ubuff16;
  Uint32 *ubuff32;
  Uint32 color;
  char c1, c2, c3;

  /*
   * Lock the screen, if needed 
   */
  if (SDL_MUSTLOCK (screen))
    {
      if (SDL_LockSurface (screen) < 0)
        return;
    }

  /*
   * Get the color 
   */
  color = SDL_MapRGB (screen->format, r, g, b);

  /*
   * How we draw the pixel depends on the bitdepth 
   */
  switch (screen->format->BytesPerPixel)
    {
    case 1:
      ubuff8 = (Uint8 *) screen->pixels;
      ubuff8 += (y * screen->pitch) + x;
      *ubuff8 = (Uint8) color;
      break;

    case 2:
      ubuff8 = (Uint8 *) screen->pixels;
      ubuff8 += (y * screen->pitch) + (x * 2);
      ubuff16 = (Uint16 *) ubuff8;
      *ubuff16 = (Uint16) color;
      break;

    case 3:
      ubuff8 = (Uint8 *) screen->pixels;
      ubuff8 += (y * screen->pitch) + (x * 3);


      if (SDL_BYTEORDER == SDL_LIL_ENDIAN)
        {
          c1 = (color & 0xFF0000) >> 16;
          c2 = (color & 0x00FF00) >> 8;
          c3 = (color & 0x0000FF);
        }
      else
        {
          c3 = (color & 0xFF0000) >> 16;
          c2 = (color & 0x00FF00) >> 8;
          c1 = (color & 0x0000FF);
        }

      ubuff8[0] = c3;
      ubuff8[1] = c2;
      ubuff8[2] = c1;
      break;

    case 4:
      ubuff8 = (Uint8 *) screen->pixels;
      ubuff8 += (y * screen->pitch) + (x * 4);
      ubuff32 = (Uint32 *) ubuff8;
      *ubuff32 = color;
      break;

    default:
      fprintf (stderr, "Error: Unknown bitdepth!\n");
    }

  /*
   * Unlock the screen if needed 
   */
  if (SDL_MUSTLOCK (screen))
    {
      SDL_UnlockSurface (screen);
    }
}

void
iniprt (double *x, double *y)
{
  double tmp1, tmp2;

  /*
   * Erase old pixel 
   */

  /*
   * Initialize/reset the particle position 
   */
  // this tmp crap is unnessisary. but HOW DO I CONVERT int to double
  // otherwise???
reinit:
  tmp1 = rand ();
  tmp2 = RAND_MAX;
  tmp1 = tmp1 / tmp2;
  tmp1 = XMAX * tmp1;
  *x = tmp1;

  tmp1 = rand ();
  tmp2 = RAND_MAX;
  tmp1 = tmp1 / tmp2;
  tmp1 = YMAX * tmp1;
  *y = tmp1;

  if (abs (*x - *y) < 100)
    goto reinit;
}

int min(int a, int b) {
  if ( a < b )  return a;
  else          return b;
}

int max(int a, int b) {
  if ( a > b )  return a;
  else          return b;
}

int
main (int argc, char *argv[])
{
  /*
   * Declare Variables 
   */
  SDL_Surface *screen;
  SDL_Event event;

  int exitkey = 0;

  unsigned char x[DEPTH];
  unsigned char y[DEPTH];
  int value[256][256], value2[256][256];
  int i = 0;
  int ii,X,Y,j,k;
  unsigned char xscroll=0;

  unsigned int blockcount=0;

  srand (1);                    /* Fetch a set of fresh random numbers */// TODO: get a time based seed

  /*
   * Initialize SDL, exit if there is an error. 
   */
  if (SDL_Init (SDL_INIT_VIDEO) < 0)
    {
      fprintf (stderr, "Could not initialize SDL: %s\n", SDL_GetError ());
      return -1;
    }

  /*
   * When the program is through executing, call SDL_Quit 
   */
  atexit (SDL_Quit);

  /*
   * Grab a surface on the screen 
   */
  screen = SDL_SetVideoMode (XMAX, YMAX, 32, SDL_SWSURFACE | SDL_ANYFORMAT);
  if (!screen)
    {
      fprintf (stderr, "Couldn't create a surface: %s\n", SDL_GetError ());
      return -1;
    }

  /*
   * Print Some info 
   */
  printf ("Created SDL Surface: %x\n", screen);
  printf ("\t BPP \t\t : %d\n", screen->format->BytesPerPixel);
  printf ("\t XRes \t\t :%d\n", screen->w);
  printf ("\t YRes \t\t :%d\n", screen->h);

  // setpixel(screen,x,y,r,g,b); 
  //
  for (X=0;X<256;X++) {
   for (Y=0;Y<256;Y++) {
     value[X][Y]=0;
   }
  }

  printf("Initialized and ready to go!\n");


  while (1)
    {

      // ROLL RAW PCM DATA INTO BUFFER
      for (ii = 0; ii < BLOCK; ii++)
        {
          i = (i + 1) % DEPTH;
          if (mode==0) { //XY mode
            x[i] = getchar ();
#ifdef SIXTEEN_BIT_WAV
            x[i] = getchar() + 128;
#endif
          } else if (mode==2 || mode==-1) { //Crazy Scroll mode (Matrix-ish)
            x[i]++;
            //if (x[i]>=256) x[i]=0;
          } else if (mode==1) {
            if ((double) rand()/RAND_MAX > .9) xscroll++;
            x[i]=xscroll;
          }
          y[i] = getchar ();
#ifdef SIXTEEN_BIT_WAV
          y[i] = getchar() + 128;
#endif
          value[x[i]][y[i]]=min(value[x[i]][y[i]]+40,256*4);
          if (mode==-1) {
            if (ii % 2 == 1) {
              value2[y[i-1]][y[i]]=min(value2[y[i-1]][y[i]]+100,256*4);
            }
          }
          //printf("x[i]=%d,\t y[i]=%d,\t value=%d\n",(int) x[i], (int) y[i], value[x[i]][y[i]]);
        }

      // display the data on the "phosphor":
      /*for (ii = 0; ii < DEPTH; ii++)
        {
#define iii (i+ii+1)%DEPTH

#ifdef DOUBLE_SIZE
          setpixel (screen, 2 * x[iii], 2 * y[iii], 0,
                    256 * DEPTH / (DEPTH - ii), 0);
          setpixel (screen, 2 * x[iii] + 1, 2 * y[iii] + 1, 0,
                    256 * DEPTH / (DEPTH - ii), 0);
          setpixel (screen, 2 * x[iii] + 1, 2 * y[iii], 0,
                    256 * DEPTH / (DEPTH - ii), 0);
          setpixel (screen, 2 * x[iii], 2 * y[iii] + 1, 0,
                    256 * DEPTH / (DEPTH - ii), 0);
#else
          setpixel (screen, x[iii], y[iii], 0, 256 * DEPTH / (DEPTH - ii), 0);
#endif
        }*/

      blockcount++;

      if (blockcount % 100 == 0)
        printf("%1.1f MB decoded\n",(double) blockcount/1024.);


      for (X=0;X<256;X++) {
        for (Y=0;Y<256;Y++) {
#ifdef DOUBLE_SIZE
          j = 0; k = 0;
          setpixel (screen, 2*Xp+j, 2*Yp+k, min(value[X][Y]/rd,255), 
                                  min(value[X][Y],255), 
                                  min(value[X][Y]/gd,255)); 
          j = 1; k = 0;
          setpixel (screen, 2*Xp+j, 2*Yp+k, min(value[X][Y]/rd,255), 
                                  min(value[X][Y],255), 
                                  min(value[X][Y]/gd,255)); 
          j = 0; k = 1;
          setpixel (screen, 2*Xp+j, 2*Yp+k, min(value[X][Y]/rd,255), 
                                  min(value[X][Y],255), 
                                  min(value[X][Y]/gd,255)); 
          j = 1; k = 1;
          setpixel (screen, 2*Xp+j, 2*Yp+k, min(value[X][Y]/rd,255), 
                                  min(value[X][Y],255), 
                                  min(value[X][Y]/gd,255)); 
          
#else
          setpixel (screen, Xp, Yp, min(value[X][Y]/rd,255), 
                                  min(value[X][Y],255), 
                                  min(value[X][Y]/gd,255));
                                  
#endif
         if (mode==-1)
         setpixel (screen, Xp+256+50, Yp, min(value2[X][Y]/rd,255), 
                                  min(value2[X][Y],255), 
                                  min(value2[X][Y]/gd,255));

          value[X][Y]=max(value[X][Y]-2,0);
          value2[X][Y]=max(value2[X][Y]-10,0);
          //printf("LAG!\n");
        }
      }

      // make the changes to the screen visable
      SDL_Flip (screen);

      // clear screen
      SDL_FillRect( screen, NULL, SDL_MapRGB(screen->format,0,0,0) );

      SDL_PumpEvents ();

      while (SDL_PollEvent (&event))
        {
          switch (event.type)
            {
            case SDL_QUIT:
              printf ("Got quit event!\n");
              return 0;
              break;
            case SDL_KEYDOWN:
              switch (event.key.keysym.sym)
                {
                case SDLK_ESCAPE:
                  printf ("Got quit key!\n");
                  return 0;
                  break;
                }
            }
        }

    };

  printf ("exec should never reach here\n");

  return 0;
}
