/*  
    Equipotential graph
    William Benfold
    April 1999
*/

//Things to do:
//
//Make param interpretation more friendly
//Do something about scale





/*------------------------------ DEPENDENCIES ------------------------------*/

#include <stdio.h>
#include <allegro.h>
#include <assert.h>
#include <math.h>
#include <time.h>
#include <random.h>



/*------------------------------- DEFINITIONS ------------------------------*/

#define VERSION 1
#define SUBVERSION 31
#define DEBUG_ENABLE
#define MAX_BODIES 100

#define K 8987742437.98821638631600199754429



/*--------------------------------- MACROS ---------------------------------*/

#define FOR_EACH_BODY for(i=0; i<NUM_BODIES; i++)
#define square(X) ((X)*(X))
#define dist(a,b,c,d) sqrt(square((c)-(a)) + square((d)-(b)))
#ifdef DEBUG_ENABLE
  #define DEBUG_MSG if(DEBUG)
#else
  #define DEBUG_MSG //
#endif



/*--------------------------------- TYPES ----------------------------------*/

typedef struct{
  int x,y;
  double charge;
}TBody;



/*------------------------------- PROTOTYPES -------------------------------*/

TBody  *make_rnd_body(void);
void    init(void);
void    shutdown(void);
inline double  potential(int x, int y);
void    change_palette(void);
void    draw_map(void);
void    check(int cond, int errcode);
void    waitkey(int type);
void    disp_coords(void);
char   *get_arg(int argc, char *argv[], char code);
void    read_parms(int argc, char *argv[]);
void    rotate_pal(void);
void    handle_key(char k);
void    make_pretty_pal(void);
void    make_boring_pal(void);
void    screen_dump(void);
void    script_exec(char *command);
void    load_bodies(char *fname);
void    calc_user_potential(void);



/*---------------------------------- DATA ----------------------------------*/

TBody   *bodies[MAX_BODIES];
int     SCR_W=640;
int     SCR_H=480;
int     NUM_BODIES=3;
float   COLOUR_WIDTH=1;
PALETTE pal;
int     ROT_PAL=FALSE;
int     NEW_PAL=FALSE;
int     RND_BODIES=TRUE;
int     DEBUG=FALSE;
float   SCALE_FACTOR=1;



/*-------------------------------- FUNCTIONS -------------------------------*/

void init(void)
{
  int code,i;

  //: Init graphics engine
  code = allegro_init();
  check(code==NULL,0);
  code = install_keyboard();
  check(code==NULL,1);
  code = install_timer();
  check(code==NULL,5);
  code = install_mouse();
  check(code!=-1,6);
  code = set_gfx_mode(GFX_AUTODETECT,SCR_W,SCR_H,0,0);
  check(code==NULL,2);

  set_color_depth(8);
  //get_palette(pal);
  
  srandom(time(NULL));
}

void shutdown(void)
{
  int i;
  allegro_exit();
  disp_coords();
  FOR_EACH_BODY
    free(bodies[i]);
  printf("\nHave a nice day!\n\n");
}

TBody *make_rnd_body(void)
{
  TBody *temp;
  temp = malloc(sizeof(TBody));
  temp->x = random()%SCR_W;
  temp->y = random()%SCR_H;
  do{
    temp->charge = (random()%11 - 5) * 0.000001;
  }while (!temp->charge);
  return temp;
}

inline double potential(int x, int y)
{
  double pot=0;
  int i;
  FOR_EACH_BODY
  {
    pot += K * SCALE_FACTOR * bodies[i]->charge / dist(bodies[i]->x,bodies[i]->y,x,y);
  }
  return pot;
}
 
void change_palette(void)
{
  int i,r,g,b;
  PALETTE pal;
  for(i=0, r=255, g=255, b=0; i<255; i++, r--, g--, b++)
  {
    pal[i].r = r;
    pal[i].g = g;
    pal[i].b = b;
  }
  set_palette(pal);
}

void draw_map(void)
{
  int x,y;
  for(y=0; y<SCR_H; y++)
    for(x=0; x<SCR_W; x++)
      _putpixel(screen,x,y,(int)((potential(x,y)/COLOUR_WIDTH)));
}

int main(int argc,char *argv[])
{
  int i;
  printf("\nEquipotential Graph\nWilliam Benfold\n(C) 1999");
  read_parms(argc,argv);
  printf("\nType EG -? for help...\n");
  waitkey(FALSE);
  init();
  
  if (RND_BODIES)
    FOR_EACH_BODY
      bodies[i] = make_rnd_body();
    
  switch(NEW_PAL)
  {
    case 0: get_palette(pal);
            break;
    case 1: make_pretty_pal();
            set_palette(pal);
            break;
    case 2: make_boring_pal();
            set_palette(pal);
            break;
  }

  draw_map();
  
  do{
    if (keypressed())
      handle_key(readkey());
    if(ROT_PAL)
      rotate_pal();
    vsync();
  }while(!key[KEY_ESC]);
  
  shutdown();
  return 0;
}

void check(int cond, int errcode)
{
  if (!cond)
  {
    printf("\n\nError (%d):\n  ",errcode);
    switch(errcode)
    {
      case 0:	printf("Misc error - Allegro reported: %s",allegro_error);
      		break;
      case 1:	printf("Could not initialise keyboard handler");
      		break;
      case 2:	printf("Could not switch to required screen mode");
      		break;
      case 3:	printf("Colour depth not available");
      		break;
      case 4: printf("File not found");
      		break;
      case 5: printf("Could not reprogram interval timer");
      		break;
      case 6: printf("Could not find mouse driver");
      		break;

    }
    printf("\n\nUnable to continue (see above)\n\n\n");
    allegro_exit();
    exit(errcode);
  }
}

void waitkey(int type)
{
  printf("\n--- Press any key ---");
  if (type)
  {
    do{}while(keypressed());
    do{}while(!keypressed());
    readkey();
  }
  else
  {
    do{}while(kbhit());
    do{}while(!kbhit());
    getch();
  }
}

void disp_coords(void)
{
  int i;
  printf("\nBody   X    Y    Charge\n-----------------------\n");
  FOR_EACH_BODY
    printf(" %2d   %3d  %3d  %8f\n",i,bodies[i]->x,bodies[i]->y,bodies[i]->charge);
}

char *get_arg(int argc, char *argv[], char code)
{
  int i, which=NULL;
  for(i=1; i<argc; i++)
    if((argv[i][0]=='-') && (argv[i][1]==code))
      which=i;
  if (which!=NULL)
    return argv[which]+2;
  else
    return NULL;
}

void read_parms(int argc, char *argv[])
{
  char *arg;

  arg = get_arg(argc,argv,'d');
  if (arg!=NULL)
    DEBUG = TRUE;
  
  arg = get_arg(argc,argv,'f');
  if (arg!=NULL)
  {
    char *temp;
    temp = malloc(256*sizeof(char));
    RND_BODIES = FALSE;
    strcpy(temp,arg);
    if (strchr(arg,'.')==NULL)
      strcat(temp,".ini");
    load_bodies(temp);
    free(temp);
  }

  arg = get_arg(argc,argv,'w');
  if (arg!=NULL)
    SCR_W = atoi(arg);

  arg = get_arg(argc,argv,'h');
  if (arg!=NULL)
    SCR_H = atoi(arg);

  arg = get_arg(argc,argv,'n');
  if (arg!=NULL)
  {
    NUM_BODIES = atoi(arg);
    RND_BODIES = TRUE;
  }

  arg = get_arg(argc,argv,'c');
  if (arg!=NULL)
    COLOUR_WIDTH = atof(arg);

  arg = get_arg(argc,argv,'r');
  if (arg!=NULL)
    ROT_PAL = TRUE;

  arg = get_arg(argc,argv,'p');
  if (arg!=NULL)
    NEW_PAL = 1;

  arg = get_arg(argc,argv,'b');
  if (arg!=NULL)
    NEW_PAL = 2;
  
  arg = get_arg(argc,argv,'s');
  if (arg!=NULL)
    SCALE_FACTOR = atof(arg);
      
  arg = get_arg(argc,argv,'?');
  if (arg!=NULL)
  {
    printf("\n\nUsage: EG [options]\n\nOptions are:\n");
    printf("  -f<filename>: Load \'scenario\' from file\n");
    printf("  -w<n>: Screen width = n\n");
    printf("  -h<n>: Screen height = n\n  -n<n>: Number of bodies = n\n");
    printf("  -c<n>: Set colour band range to n (eg -c0.01)\n  -r   : Rotate palette\n");
    printf("  -p   : Smooth colour palette\n  -b   : Boring colour palette\n  -v   : Display version info\n  -?   : Display this information\n");
    printf("\nWhen program is running:\n  r: Rotate palette\n  p: Pretty palette\n  b: Boring palette\n  s: Save image\n  c: Calc potential at a point\n");
    exit(0);
  }

  arg = get_arg(argc,argv,'v');
  if (arg!=NULL)
  {  
    printf("\nVersion %d.%d\n",VERSION,SUBVERSION);
    printf("\nCompiled on %s at %s; source was %s\n",__DATE__,__TIME__,__FILE__);
    exit(0);
  }
}

void make_pretty_pal(void)
{
  int i;
  for(i=0; i<64; i++)
  {
    pal[i].r = i;
    pal[i].g = 0;
    pal[i].b = 63;

    pal[i+64].r = 63;
    pal[i+64].g = i;
    pal[i+64].b = 63-i;

    pal[i+128].r = 63-i;
    pal[i+128].g = 63;
    pal[i+128].b = 0;

    pal[i+192].r = 0;
    pal[i+192].g = 63-i;
    pal[i+192].b = i;
  }
}



void rotate_pal(void)
{
  int i;
  RGB col;
  col = pal[0];
  for(i=0; i<255; i++)
    pal[i] = pal[i+1];
  pal[255] = col;
  set_palette(pal);
}

void handle_key(char k)
{
  switch (k)
  {
    case 'r': if(ROT_PAL)
                ROT_PAL = FALSE;
              else
                ROT_PAL = TRUE;
              break;
    case 'p': make_pretty_pal();
              NEW_PAL = TRUE;
              set_palette(pal);
              break;
    case 'b': make_boring_pal();
              NEW_PAL = TRUE;
              set_palette(pal);
              break;
    case 's': screen_dump();
              break;
    case 'c': calc_user_potential();
  }
  clear_keybuf();
}

void screen_dump(void)
{
  BITMAP *b;
  PALETTE p;
  int num=-1;
  char fname[13]="Dump__00.bmp";
  do{
    num++;
    if(num==100)
    	return;
    fname[6] = floor(num / 10) + '0';
    fname[7] = (num  %  10) + '0';
  }while(exists(fname));
  get_palette(p);
  b = create_sub_bitmap(screen,0,0,SCR_W,SCR_H);
  save_bitmap(fname,b,p);
  destroy_bitmap(b);
}

void load_bodies(char *fname)
{
  FILE *fp;
  char *command;
  char *code;
  command = malloc(257*sizeof(char));
  NUM_BODIES = 0;
  fp = fopen(fname,"r");
  check(fp!=NULL,4);
  while(!feof(fp))
  {
    fgets(command,256,fp);
    if (code!=NULL)
      script_exec(command);
  }
  fclose(fp);
  free(command);
}

void script_exec(char *command)
{
  DEBUG_MSG printf("\nscript_exec(\"%s\")",command);
  if(strncmp(command,"BODY",4)==0)
  {
    NUM_BODIES++;
    bodies[NUM_BODIES-1] = malloc(sizeof(TBody));
  }

  if(strncmp(command,"X=",2)==0)
    bodies[NUM_BODIES-1]->x = atoi(command+2);
  
  if(strncmp(command,"Y=",2)==0)
    bodies[NUM_BODIES-1]->y = atoi(command+2);

  if(strncmp(command,"C=",2)==0)
    bodies[NUM_BODIES-1]->charge = atof(command+2);

  if(strncmp(command,"PARAM ",6)==0)
  {
    char *argptr[2];
    argptr[1] = command+6;
    read_parms(2,argptr);
  }
}

void calc_user_potential(void)
{
  float pot;
  int x,y,h;
  BITMAP *temp,*backup;
  backup = create_bitmap(SCR_W,SCR_H);
  temp = create_bitmap(SCR_W,SCR_H);
  blit(screen,backup,0,0,0,0,SCR_W,SCR_H);
  blit(screen,temp,0,0,0,0,SCR_W,SCR_H);
  show_mouse(backup);
  h = floor(SCR_H/4);
  do
  {
    {
      blit(backup,temp,0,0,0,0,SCR_W,SCR_H);
      x = mouse_x;
      y = mouse_y;
      pot = potential(x,y);
      scare_mouse();
      if (abs(y-h) < floor(SCR_W/16))
      {
        if (2*y>SCR_H)
          h = floor(SCR_H/4);
        else
          h = floor(SCR_H*3/4);
      }
      textprintf_centre(temp, font, floor(SCR_W/2), h, 128 ,"Potential at (%d,%d) = %f",x,y,pot);
      textprintf_centre(temp, font, floor(SCR_W/2), h+20, 128 ,"RMB to end");
      blit(temp,screen,0,0,0,0,SCR_W,SCR_H);
      unscare_mouse();
      vsync();
    }
  }while(!(mouse_b&2));
  show_mouse(NULL);
  blit(backup,screen,0,0,0,0,SCR_W,SCR_H);
}

void make_boring_pal(void)
{
  int i;
  for(i=0; i<256; i++)
  {
    if (i%64<4)
      pal[i].r = pal[i].g = pal[i].b = 0;
    else
      pal[i].r = pal[i].g = pal[i].b = 63;
  }
}
