/* Hanoi Tower Demo 
   
   play hanoi(<number of disk between 1 and 9>) */


int win_hanoi = -1;			/* hanoi window */
int ndisk_hanoi;			/* total number of disks */
int tower_size_hanoi[3];		/* number of disks per tower */
int width_disk_hanoi[3][10];		/* disks'width per tower */

/* replace n characters by c */

void replace_chars_hanoi(int n, int c)
{
  while (n--) {
    replace_char(c);
    goto_next_char();
  }
}

/* insert n times c */

void insert_chars_hanoi(int n, int c)
{
  while (n--) insert_char(c);
}

/* clear one of the two lines of a disk */

void clear_disk_line_hanoi(int altitude)
{
  /* clear the left part */
  replace_chars_hanoi(ndisk_hanoi, ' ');
  
  if (altitude > ndisk_hanoi)
    /* at this altitude the tower is not drawed */
    replace_char(' ');
  goto_next_char();
  
  /* clear the right part */
  replace_chars_hanoi(ndisk_hanoi, ' ');
}

/* rub out the specified disk */

void remove_disk_hanoi(int tower, int altitude)
{
  int dx = /* horizontal position where the larger disk begins */
    tower * (2 * ndisk_hanoi + 3) + 1;
  
  /* clear upper line */
  
  goto_line((ndisk_hanoi - altitude) * 2 + 2);
  goto_beginning_of_line();
  goto_char(current_position() + dx);
  clear_disk_line_hanoi(altitude);
  
  /* clear lower line */
  
  goto_next_line();
  goto_beginning_of_line();
  goto_char(current_position() + dx);
  clear_disk_line_hanoi(altitude);
}

/* draw one of the two lines of a disk */

void draw_disk_line_hanoi(int altitude, int width)
{
  int n;
  
  /* clear the left part */
  replace_chars_hanoi(width - 1, '_');
  
  if (altitude > ndisk_hanoi)
    /* at this altitude the tower is not drawed */
    replace_char('_');
  goto_next_char();
  
  /* clear the right part */
  replace_chars_hanoi(width - 1, '_');
}

/* draw the specified disk */

void draw_disk_hanoi(int tower, int altitude, int width)
{
  int dx = /* horizontal position where the disk begins */
    tower * (2 * ndisk_hanoi + 3) + ndisk_hanoi - width + 1;

  /* draw the upper line */
  
  goto_line((ndisk_hanoi - altitude) * 2 + 2);
  goto_beginning_of_line();
  goto_char(current_position() + dx + 1);
  draw_disk_line_hanoi(altitude, width);
  
  /* draw the lower line with ( and ) */
  
  goto_next_line();
  goto_beginning_of_line();
  goto_char(current_position() + dx);
  replace_char('(');
  goto_next_char();
  draw_disk_line_hanoi(altitude, width);
  replace_char(')');
}

/* initial drawing of the hanoi towers */

void draw_hanoi()
{
  int width = 6 * ndisk_hanoi + 9;
  int n;

  /* reuse or create the hanoi window */
  
  if (((win_hanoi == -1) || (select_window(win_hanoi) == -1)) &&
      ((win_hanoi = new_window()) == -1))
    error("To many open window");

  select_window(win_hanoi);
  kill_current_buffer();
  raise_window();
  
  /* fill the two upper lines to move horizontaly a disk */
  
  insert_chars_hanoi(width, ' ');
  insert_char('\n');
  insert_chars_hanoi(width, ' ');
  insert_char('\n');
  
  /* draw all towers empty */

  /* the towers's body */
  for (n = ndisk_hanoi * 2 + 1; n; n -= 1) {
    int tower;
    
    for (tower = 0; tower != 3; tower += 1) {
      insert_chars_hanoi(ndisk_hanoi + 1, ' ');
      insert_char('|');
      insert_chars_hanoi(ndisk_hanoi + 1, ' ');
    }
    insert_char('\n');
  }
  
  /* the towers's foot */
  insert_chars_hanoi(width, '=');
  insert_char('\n');

  /* place the disks on the first tower */
  
  for (n = 1; n <= ndisk_hanoi; n += 1)
    draw_disk_hanoi(0, n, ndisk_hanoi - n + 1);

  /* to see the beautiful result before the movings */
  
  goto_char(0);
  redisplay();
  sleep(2);
}

/* initialize data */

void init_hanoi(int n)
{
  if (((ndisk_hanoi = n) < 0) || (n > 9))
    error("number of disk may be between 1 and 9");

  tower_size_hanoi[0] = n;
  tower_size_hanoi[1] = tower_size_hanoi[2] = 0;
  
  while (n) {
    width_disk_hanoi[0][n] = ndisk_hanoi - n + 1;
    width_disk_hanoi[1][n] = width_disk_hanoi[2][n] = 0;
    n -= 1;
  }
}

/* move a disk from src to dest */

void move_hanoi(int src, int dest)
{
  int src_size = tower_size_hanoi[src]--;
  int dest_size = ++(tower_size_hanoi[dest]);
  int width =
    width_disk_hanoi[dest][dest_size] = width_disk_hanoi[src][src_size];
  int speed = (ndisk_hanoi < 6) ? 1 : ndisk_hanoi/2;
  int alt;

  /* move up disk on src tower */
  
  alt = src_size;
  do {
    remove_disk_hanoi(src, alt);
    if ((alt += speed) > ndisk_hanoi) alt = ndisk_hanoi + 1;
    draw_disk_hanoi(src, alt, width);
    goto_char(0);
    redisplay();
  } while (alt <= ndisk_hanoi);
  
  /* move disk from src to dest */
  
  remove_disk_hanoi(src, alt);
  goto_char(0);
  redisplay();
  draw_disk_hanoi(dest, alt, width);
  goto_char(0);
  redisplay();
  
  /* move down disk on dest tower */
  
  do {
    remove_disk_hanoi(dest, alt);
    if ((alt -= speed) < dest_size) alt = dest_size;
    draw_disk_hanoi(dest, alt, width);
    goto_char(0);
    redisplay();
  } while (alt != dest_size);
}

/* the engine */

void internal_hanoi(int src, int inter, int dest, int ndisk)
{
  if (ndisk > 1)
    internal_hanoi(src, dest, inter, ndisk - 1);

  move_hanoi(src, dest);

  if (ndisk > 1)
    internal_hanoi(inter, src, dest, ndisk - 1);
}

/* write slowly a string at the end of line */

void write_string_hanoi(char *s)
{
  goto_end_of_line();
  while (*s) {
    insert_char(*s++);
    redisplay();
    usleep(10000);
  }
  goto_next_char();
}

/* write a message then edit this file */

void display_hanoi_source()
{
  char * dir;
  char * pathname;

  goto_line(1);
  write_string_hanoi("  This demo illustrates some of Smac possibilities.");
  write_string_hanoi("  Smac is the built-in Ansi C interpreter of Xcoral.");
  write_string_hanoi("  With Smac functions you can extend dynamically the");
  write_string_hanoi("  editor features (modes, key-bindings and text).");
  write_string_hanoi("  The source file of Hanoi demo will be display now.");
  sleep(2);
  
  dir = smaclib_dir();
  pathname = (char*) malloc(strlen(dir) + 10);
  sprintf(pathname, "%s/hanoi.sc", dir);
  read_file(pathname);
  free(pathname);
  color_buffer();
}

/* the top level function */

void hanoi(int n)
{
  init_hanoi(n);
  draw_hanoi();
  internal_hanoi(0, 1, 2, n);
  display_hanoi_source();
}
