/* ASCII Mystify
 *
 * Author: Kristian Gunstone
 *
 * 'gcc -Wall -lm -lncurses -o mystify mystify.c'
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
#include <stdio.h>
#include <unistd.h>
#include <ncurses.h>
#include <getopt.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#define DEFAULT_SHAPES		3 /* Max 6 due to the colorspace in console */
#define DEFAULT_POINTS		4
#define DEFAULT_DELAY		10000

#define VERSION			"0.3"

static struct option const long_options[] = {
    {"shapes",           required_argument, NULL, 's'},
    {"points",           required_argument, NULL, 'p'},
    {"delay",            required_argument, NULL, 'd'},
    {"help",             no_argument, NULL, 'h'},
    {NULL, 0, NULL, 0}
};

void usage(char *self){
  printf("ASCII Mystify v%s\n"
	 "Copyright (C) 2007 Kristian Gunstone\n\n"
	 "This is free software; see the source for copying conditions. "
	 "There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS "
	 "FOR A PARTICULAR PURPOSE.\n\n", VERSION);

  printf("Usage: %s [OPTION]...\n"
	 " -s, --shapes              number of shapes(1-6)   default=%d\n"
	 " -p, --points              points per shape(2-100) default=%d\n"
	 " -d, --delay               refresh delay in ns     default=%d\n\n",
	 self,
	 DEFAULT_SHAPES,
	 DEFAULT_POINTS,
	 DEFAULT_DELAY);


}
 
void line(float x1, float y1, float x2, float y2, int character) {
  float d, dx, dy, i;
  dx = (x2 - x1);
  dy = (y2 - y1);
  d = sqrt(dx * dx + dy * dy);

  for(i = 0; i <= d; i+= 0.5) 
    mvaddch(
	    (int)(y1 + dy * (i / d)), 
	    (int)(x1 + dx * (i / d)), 
	    character);  
} 

int main(int argc, char *argv[]){
  long delay;
  int shapes, points;
  int i, a;
  int term_w, term_h;

  typedef struct{
    float x1, x2, y1, y2;
    float xf1, xf2, yf1, yf2;
  }pointEx;

  typedef struct{
    pointEx *point;
    int color;
  }shapeEx;

  shapeEx *shape;
  
  shapes = DEFAULT_SHAPES;
  points = DEFAULT_POINTS;
  delay  = DEFAULT_DELAY;

  while( (i = getopt_long(argc, argv, "s:p:d:h", long_options, NULL) ) != -1 ){
    switch(i){
    case 's': shapes = atoi(optarg); break;
    case 'p': points = atoi(optarg); break;
    case 'd': delay  = atoi(optarg); break;
    case 'h': usage(argv[0]); exit(0); break;
    }
  }

  /* Input check */
  if(shapes < 1 || shapes > 6){
    fprintf(stderr, "Shapes must be 1-6.\n");
    exit(1);
  }
  if(points < 2 || points > 100){
    fprintf(stderr, "Points must be 1-100.\n");
    exit(1);
  }
  if(delay < 0 || delay > 5000000){
    fprintf(stderr, "Delay must be 0-5000000.\n");
    exit(1);
  }

  /* Allocate memory */
  shape = calloc(1, sizeof(shapeEx) * shapes);
  for(i = 0; i < shapes; i++)
    shape[i].point = calloc(1, sizeof(pointEx) * points);

  /* Init curses */
  initscr();
  curs_set(0);
  cbreak();
  nodelay(stdscr, TRUE);
  noecho();
  start_color();
    
  init_pair(0, COLOR_BLACK, COLOR_BLACK);

  term_w	= COLS - 1;
  term_h	= LINES;

  srand(time(NULL));

  /* Init shapes */
  for(i = 0; i < shapes; i++){
    shape[i].color = 1 + i;
    init_pair(1 + i, COLOR_BLACK, shape[i].color);

    shape[i].point[0].x1 = rand()%term_w;
    shape[i].point[0].y1 = rand()%term_h;
    shape[i].point[0].x2 = rand()%term_w;
    shape[i].point[0].y2 = rand()%term_h;

    for(a = 1; a < (points - 1); a++){
      shape[i].point[a].x1 = shape[i].point[a - 1].x2;
      shape[i].point[a].y1 = shape[i].point[a - 1].y2;
      shape[i].point[a].x2 = rand()%term_w;
      shape[i].point[a].y2 = rand()%term_h;
    }

    shape[i].point[points-1].x1 = shape[i].point[points-2].x2;
    shape[i].point[points-1].y1 = shape[i].point[points-2].y2;
    shape[i].point[points-1].x2 = shape[i].point[0].x1;
    shape[i].point[points-1].y2 = shape[i].point[0].y1;

    for(a = 0; a < points; a++){
      shape[i].point[a].xf1 = rand()%2?-.2:.2;
      shape[i].point[a].yf1 = rand()%2?-.2:.2;
      shape[i].point[a].xf2 = rand()%2?-.2:.2;
      shape[i].point[a].yf2 = rand()%2?-.2:.2;
    }
  }

  while(getch() != 27){

    for(i = 0; i < shapes; i++){

      attron(COLOR_PAIR(0));
      for(a = 0; a < points; a++)
	line(shape[i].point[a].x1,
	     shape[i].point[a].y1,
	     shape[i].point[a].x2,
	     shape[i].point[a].y2,
	     32);
      attroff(COLOR_PAIR(0));

      shape[i].point[0].x1 += shape[i].point[0].xf1;
      shape[i].point[0].y1 += shape[i].point[0].yf1;
      shape[i].point[0].x2 += shape[i].point[0].xf2;
      shape[i].point[0].y2 += shape[i].point[0].yf2;
      
      for(a = 1; a < (points - 1); a++){
	shape[i].point[a].x1 = shape[i].point[a - 1].x2;
	shape[i].point[a].y1 = shape[i].point[a - 1].y2;
	shape[i].point[a].x2+= shape[i].point[a].xf2;
	shape[i].point[a].y2+= shape[i].point[a].yf2;
      }

      shape[i].point[points-1].x1 = shape[i].point[points-2].x2;
      shape[i].point[points-1].y1 = shape[i].point[points-2].y2;
      shape[i].point[points-1].x2 = shape[i].point[0].x1;
      shape[i].point[points-1].y2 = shape[i].point[0].y1;

      for(a = 0; a < points; a++){
	if(shape[i].point[a].x1 < 0 || shape[i].point[a].x1 > term_w)
	  shape[i].point[a].xf1 = -shape[i].point[a].xf1;
	if(shape[i].point[a].y1 < 0 || shape[i].point[a].y1 > term_h)
	  shape[i].point[a].yf1 = -shape[i].point[a].yf1;
	if(shape[i].point[a].x2 < 0 || shape[i].point[a].x2 > term_w)
	  shape[i].point[a].xf2 = -shape[i].point[a].xf2;
	if(shape[i].point[a].y2 < 0 || shape[i].point[a].y2 > term_h)
	  shape[i].point[a].yf2 = -shape[i].point[a].yf2;
      }

      attron(COLOR_PAIR(i+1));
      for(a = 0; a < points; a++)
	line(shape[i].point[a].x1,
	     shape[i].point[a].y1,
	     shape[i].point[a].x2,
	     shape[i].point[a].y2,
	     32);
      attroff(COLOR_PAIR(1+i));
    }

    usleep(delay);
  }

  /* Deinit */
  endwin();
  for(i = 0; i < shapes; i++)
    free(shape[i].point);
  free(shape);

  return 0;
}

