/*  
  inout.c  -- input/output module for interactive editor of prerequisite-chart 
              descriptions
  Copyright (c) 2005-08  R. D. Tennent   
  School of Computing, Queen's University, rdt@cs.queensu.ca 

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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

# include "prerex.h"

bool
gt (point p, point q)
/* ordering on boxes: top-to-bottom, left-to-right */
{
  return (p.y > q.y || (p.y == q.y && p.x < q.x));
}

bool
eq (point p, point q)
{
  return (p.x == q.x && p.y == q.y);
}

int
insert_node (element * pn)
{
  element *p;
  element *pt;

  /* ordered list */
  p = first_node;
  pt = NULL;
  while ((p != NULL) && gt (p->u.n.at, pn->u.n.at))
    {
      pt = p;
      p = p->next;
    }
  if ((p != NULL) && eq (p->u.n.at, pn->u.n.at))
    {
      return 1;
    }
  if (pt == NULL)
    first_node = pn;
  else
    pt->next = pn;
  pn->next = p;
  return 0;
}				/* insert_node */

element *
node_at (point p)
{
  element *pn = first_node;
  while (pn != NULL && !eq (p, pn->u.n.at))
    pn = pn->next;
  return pn;
}

int
insert_arrow (element * pa)
{
  element *p;
  if (pa->u.a.source == NULL)
    {
      return 1;
    }
  if (pa->u.a.target == NULL)
    {
      return 2;
    }
  p = first_arrow;
  while (p != NULL && (p->u.a.target != pa->u.a.target ||
		       p->u.a.source != pa->u.a.source))
    {
      p = p->next;
    }
  if (p != NULL)
    {
      return 3;
    }
  pa->next = first_arrow;
  first_arrow = pa;
  return 0;
}				/*insert_arrow */


PRIVATE coord
read_coord (void)
{
  coord c;
  while (!isdigit (*lp) && *lp != '-')
    lp++;
  c = atoi (lp);
  if (*lp == '-')
    lp++;
  while (isdigit (*lp))
    lp++;
  return c;
}

PRIVATE point
read_point (void)
{
  point pnt;
  pnt.x = read_coord ();
  pnt.y = read_coord ();
  return pnt;
}

PRIVATE void
read_textfield (char *limit)
{
  /* use recursion to allow for nested { ... }  */
  if (lp >= &line[LINE_LEN])
    error ("Missing }.");
  while (*lp != '}')
    {
      *cp = *lp;
      if (*lp == '{')
	{			/* nested {...} */
	  cp++;
	  lp++;
	  read_textfield (limit);
	  *cp = *lp;   /* '}' */
	}
      cp++;
      lp++;
      if (lp >= &line[LINE_LEN])
	error ("Missing }.");
      if (cp >= limit)
	{
	  puts (line);
	  error ("Text-field too long.");
	}
    }
}

PRIVATE void
read_bracketed_textfield (char *limit)
{
  while (*lp != '{')
    {
      lp++;
      if (lp >= &line[LINE_LEN])
	error ("Missing {.");
    }
  lp++;				/* '{'  */
  read_textfield (limit);
  lp++;				/* '}' */
  if (cp >= limit)
    {
      puts (line);
      error ("Text-field too long.");
    }
  *cp = '\0';
}


PRIVATE void
analyze_halfcourse (void)
{
  element *c;
  c = (element *) malloc (sizeof (element));
  if (c == NULL)
    error ("Out of memory.");
  c->tag = NODE;
  c->u.n.tag = BOX;
  lp = &line[10];
  c->u.n.u.b.required = false;
  c->u.n.u.b.half = true;
  c->u.n.at = read_point ();
  cp = &(c->u.n.code[0]);
  read_bracketed_textfield (&(c->u.n.code[CODE_LEN]));
  cp = &(c->u.n.u.b.title[0]);
  read_bracketed_textfield (&(c->u.n.u.b.title[TITLE_LEN]));
  cp = &(c->u.n.u.b.timetable[0]);
  read_bracketed_textfield (&(c->u.n.u.b.timetable[TIMETABLE_LEN]));
  insert_node (c);
}

PRIVATE void
analyze_reqhalfcourse (void)
{
  element *c;
  c = (element *) malloc (sizeof (element));
  if (c == NULL)
    error ("Out of memory.");
  c->tag = NODE;
  c->u.n.tag = BOX;
  lp = &line[13];
  c->u.n.u.b.required = true;
  c->u.n.u.b.half = true;
  c->u.n.at = read_point ();
  cp = &(c->u.n.code[0]);
  read_bracketed_textfield (&(c->u.n.code[CODE_LEN]));
  cp = &(c->u.n.u.b.title[0]);
  read_bracketed_textfield (&(c->u.n.u.b.title[TITLE_LEN]));
  cp = &(c->u.n.u.b.timetable[0]);
  read_bracketed_textfield (&(c->u.n.u.b.timetable[TIMETABLE_LEN]));
  insert_node (c);
}

PRIVATE void
analyze_fullcourse (void)
{
  element *c;
  c = (element *) malloc (sizeof (element));
  if (c == NULL)
    error ("Out of memory.");
  c->tag = NODE;
  c->u.n.tag = BOX;
  lp = &line[10];
  c->u.n.u.b.required = false;
  c->u.n.u.b.half = false;
  c->u.n.at = read_point ();
  cp = &(c->u.n.code[0]);
  read_bracketed_textfield (&(c->u.n.code[CODE_LEN]));
  cp = &(c->u.n.u.b.title[0]);
  read_bracketed_textfield (&(c->u.n.u.b.title[TITLE_LEN]));
  cp = &(c->u.n.u.b.timetable[0]);
  read_bracketed_textfield (&(c->u.n.u.b.timetable[TIMETABLE_LEN]));
  insert_node (c);
}

PRIVATE void
analyze_reqfullcourse (void)
{
  element *c;
  c = (element *) malloc (sizeof (element));
  if (c == NULL)
    error ("Out of memory.");
  c->tag = NODE;
  c->u.n.tag = BOX;
  lp = &line[13];
  c->u.n.u.b.required = true;
  c->u.n.u.b.half = false;
  c->u.n.at = read_point ();
  cp = &(c->u.n.code[0]);
  read_bracketed_textfield (&(c->u.n.code[CODE_LEN]));
  cp = &(c->u.n.u.b.title[0]);
  read_bracketed_textfield (&(c->u.n.u.b.title[TITLE_LEN]));
  cp = &(c->u.n.u.b.timetable[0]);
  read_bracketed_textfield (&(c->u.n.u.b.timetable[TIMETABLE_LEN]));
  insert_node (c);
}

PRIVATE void
analyze_mini (void)
{
  element *m;
  m = (element *) malloc (sizeof (element));
  if (m == NULL)
    error ("Out of memory.");
  m->tag = NODE;
  m->u.n.tag = MINI;
  lp = &line[4];
  m->u.n.at = read_point ();
  cp = &(m->u.n.code[0]);
  read_bracketed_textfield (&(m->u.n.code[CODE_LEN]));
  insert_node (m);
}

PRIVATE void
analyze_text (void)
{
  element *m;
  m = (element *) malloc (sizeof (element));
  if (m == NULL)
    error ("Out of memory.");
  m->tag = NODE;
  m->u.n.tag = TEXT;
  lp = &line[4];
  m->u.n.at = read_point ();
  m->u.n.code[0] = '\0';
  cp = &(m->u.n.u.t.txt[0]);
  read_bracketed_textfield (&(m->u.n.u.t.txt[LINE_LEN]));
  insert_node (m);
}

PRIVATE void
analyze_prereqc (void)
{
  element *a;
  a = (element *) malloc (sizeof (element));
  if (a == NULL)
    error ("Out of memory.");
  a->tag = ARROW;
  a->u.a.tag = PREREQ;
  lp = &line[7];
  a->u.a.source = node_at (read_point ());
  a->u.a.target = node_at (read_point ());
  a->u.a.curvature = read_coord ();
  if (a->u.a.curvature > 100) a->u.a.curvature = -1;
  insert_arrow (a);
}

PRIVATE void
analyze_prereq (void)
{
  element *a;
  a = (element *) malloc (sizeof (element));
  if (a == NULL)
    error ("Out of memory.");
  a->tag = ARROW;
  a->u.a.tag = PREREQ;
  a->u.a.curvature = -1;
  lp = &line[6];
  a->u.a.source = node_at (read_point ());
  a->u.a.target = node_at (read_point ());
  insert_arrow (a);
}

PRIVATE void
analyze_coreq (void)
{
  element *a;
  a = (element *) malloc (sizeof (element));
  if (a == NULL)
    error ("Out of memory.");
  a->tag = ARROW;
  a->u.a.tag = COREQ;
  a->u.a.curvature = -1;
  lp = &line[6];
  a->u.a.source = node_at (read_point ());
  a->u.a.target = node_at (read_point ());
  insert_arrow (a);
}

PRIVATE void
analyze_coreqc (void)
{
  element *a;
  a = (element *) malloc (sizeof (element));
  if (a == NULL)
    error ("Out of memory.");
  a->tag = ARROW;
  a->u.a.tag = COREQ;
  lp = &line[7];
  a->u.a.source = node_at (read_point ());
  a->u.a.target = node_at (read_point ());
  a->u.a.curvature = read_coord ();
  if (a->u.a.curvature > 100) a->u.a.curvature = -1;
  insert_arrow (a);
}

PRIVATE void
analyze_recomm (void)
{
  element *a;
  a = (element *) malloc (sizeof (element));
  if (a == NULL)
    error ("Out of memory.");
  a->tag = ARROW;
  a->u.a.tag = RECOMM;
  a->u.a.curvature = -1;
  lp = &line[6];
  a->u.a.source = node_at (read_point ());
  a->u.a.target = node_at (read_point ());
  insert_arrow (a);
}

PRIVATE void
analyze_recommc (void)
{
  element *a;
  a = (element *) malloc (sizeof (element));
  if (a == NULL)
    error ("Out of memory.");
  a->tag = ARROW;
  a->u.a.tag = RECOMM;
  lp = &line[7];
  a->u.a.source = node_at (read_point ());
  a->u.a.target = node_at (read_point ());
  a->u.a.curvature = read_coord ();
  if (a->u.a.curvature > 100) a->u.a.curvature = -1;
  insert_arrow (a);
}


PRIVATE bool
analyze_tex_command (void)
{
  if (fgets (line, LINE_LEN, tex_file) == NULL)
    error ("Can't read LaTeX command.");
  if (prefix ("halfcourse", line))
    analyze_halfcourse ();
  else if (prefix ("reqhalfcourse", line))
    analyze_reqhalfcourse ();
  else if (prefix ("fullcourse", line))
    analyze_fullcourse ();
  else if (prefix ("reqfullcourse", line))
    analyze_reqfullcourse ();
  else if (prefix ("mini", line))
    analyze_mini ();
  else if (prefix ("text", line))
    analyze_text ();
  else if (prefix ("prereqc", line))
    analyze_prereqc ();
  else if (prefix ("prereq", line))
    analyze_prereq ();
  else if (prefix ("coreqc", line))
    analyze_coreqc ();
  else if (prefix ("coreq", line))
    analyze_coreq ();
  else if (prefix ("recommc", line))
    analyze_recommc ();
  else if (prefix ("recomm", line))
    analyze_recomm ();
  else if (prefix ("grid", line))
    grid = true;
  else if (prefix ("end{chart}", line))
    {
      return true;
    }
  else
    {
      char msg[LINE_LEN + 24] = "Illegal command:\n ";
      strlcat (msg, line, sizeof (msg));
      error (msg);
    }
  return false;
}

PRIVATE void
free_elements (element ** pb)
{
  if (*pb != NULL)
    {
      element *pbb = *pb;
      free_elements (&(pbb->next));
      free (pbb);
      *pb = NULL;
    }
}

void
free_lists (void)
{
  free_elements (&first_node);
  free_elements (&first_arrow);
  free_elements (&first_cut);
  free_elements (&first_deletion);
}

void
analyze_tex_file (void)
{
  int ch;
  bool flag;			/* chart detected? */
  fflush (tex_file);
  rewind (tex_file);
  printf ("Analyzing %s.\n", filename);
  ch = getc (tex_file);
  while (ch != EOF)
    {
      if (ch == '\\')
	{
	  fgets (line, LINE_LEN, tex_file);
	  flag = prefix ("begin{chart}", line);
	  putc ('\\', pretext);
	  fputs (line, pretext);
	  if (flag)
	    {
	      do
		ch = getc (tex_file);
	      while (isspace (ch));
	      while (true)
		{
		  if (ch == '\\')
		    {
		      if (analyze_tex_command ())
			{
			  fprintf (posttext, "%s\n", "\\end{chart}");
			  break;
			}
		    }
		  else if (ch == '%')
		    {
		      fgets (line, LINE_LEN, tex_file);
		      putc ('%', comments);
		      fputs (line, comments);
		    }
		  else
		    error
		      ("Unexpected 1st non-whitespace character in line.");
		  do
		    ch = getc (tex_file);
		  while (isspace (ch));
		}
	      ch = getc (tex_file);
	      while (ch != EOF)
		{
		  putc (ch, posttext);
		  ch = getc (tex_file);
		}
	    }
	}
      else
	{
	  putc (ch, pretext);
	}
      ch = getc (tex_file);
    }
}				/* analyze_tex_file */

PRIVATE void
output_node (element * p)
{
  if (p->u.n.tag == BOX)
    {
      putc ('\\', tex_file);
      if (p->u.n.u.b.required)
	fprintf (tex_file, "req");
      if (p->u.n.u.b.half)
	{
	  fprintf (tex_file, "halfcourse ");
	}
      else
	{
	  fprintf (tex_file, "fullcourse ");
	}
      fprintf (tex_file,
	       "%i,%i:{%s}{%s}{%s}\n",
	       p->u.n.at.x, p->u.n.at.y, p->u.n.code,
	       p->u.n.u.b.title, p->u.n.u.b.timetable);
    }
  else if (p->u.n.tag == MINI)
    {
      fprintf (tex_file,
	       "\\mini %i,%i:{%s}\n", p->u.n.at.x, p->u.n.at.y, p->u.n.code);
    }
  else if (p->u.n.tag == TEXT)
    {
      fprintf (tex_file,
	       "\\text %i,%i:{%s}\n", p->u.n.at.x, p->u.n.at.y, p->u.n.u.t.txt);
    }
  else
    error ("Undefined node type.");
}

PRIVATE void
output_arrow (element * p)
{
  switch (p->u.a.tag)
    {
    case PREREQ:
      if (p->u.a.curvature < 0)
	{
	  fprintf (tex_file,
		   "  \\prereq %i,%i,%i,%i:\n",
		   p->u.a.source->u.n.at.x, p->u.a.source->u.n.at.y,
		   p->u.a.target->u.n.at.x, p->u.a.target->u.n.at.y);
	}
      else
	{
	  fprintf (tex_file,
		   "  \\prereqc %i,%i,%i,%i;%i:\n",
		   p->u.a.source->u.n.at.x, p->u.a.source->u.n.at.y,
		   p->u.a.target->u.n.at.x, p->u.a.target->u.n.at.y,
		   p->u.a.curvature);
	}
      break;
    case COREQ:
      if (p->u.a.curvature < 0)
	{
	  fprintf (tex_file,
		   "  \\coreq %i,%i,%i,%i:\n",
		   p->u.a.source->u.n.at.x, p->u.a.source->u.n.at.y,
		   p->u.a.target->u.n.at.x, p->u.a.target->u.n.at.y);
	}
      else
	{
	  fprintf (tex_file,
		   "  \\coreqc %i,%i,%i,%i;%i:\n",
		   p->u.a.source->u.n.at.x, p->u.a.source->u.n.at.y,
		   p->u.a.target->u.n.at.x, p->u.a.target->u.n.at.y,
		   p->u.a.curvature);
	}
      break;
    case RECOMM:
      if (p->u.a.curvature < 0)
	{
	  fprintf (tex_file,
		   "  \\recomm %i,%i,%i,%i:\n",
		   p->u.a.source->u.n.at.x, p->u.a.source->u.n.at.y,
		   p->u.a.target->u.n.at.x, p->u.a.target->u.n.at.y);
	}
      else
	{
	  fprintf (tex_file,
		   "  \\recommc %i,%i,%i,%i;%i:\n",
		   p->u.a.source->u.n.at.x, p->u.a.source->u.n.at.y,
		   p->u.a.target->u.n.at.x, p->u.a.target->u.n.at.y,
		   p->u.a.curvature);
	}
      break;
    default:
      error ("Undefined arrow kind.");
    }
}

PRIVATE void
merge_lists (void)
{
  /* 
     Output nodes in order (top-to-bottom, left-to-right).
     For each node, search for and output every arrow that targets that node, 
     provided its source node has already been output.
     After output of an arrow, remove it from the arrow list and save it on the 
     save_arrow list (so we can tell which arrows haven't yet been output).
     Process the remaining arrows, verifying their sources/targets haven't
     been cut or deleted.
     Restore the saved arrows.
   */
  element *fn = first_node;
  element *fa, *fat;		/* fat is a trailing pointer */
  element *save_arrow = NULL;	/* arrows already output  */
  while (fn != NULL)
    {
      output_node (fn);
      fat = NULL;
      fa = first_arrow;
      while (fa != NULL)
	{
	  if (fa->u.a.target == fn)
	    {
	      element *ffn = first_node;
	      /* verify that a source node has already been output: */
	      while (ffn != fn && ffn != fa->u.a.source)
		{
		  ffn = ffn->next;
		}
	      if (ffn == fn)
		{
		  /* source node not yet output; defer arrow output */
		  fat = fa;
		  fa = fa->next;
		  break;
		}
	      output_arrow (fa);
	      /* move arrow to save_arrow list: */
	      if (fat == NULL)
		first_arrow = fa->next;
	      else
		fat->next = fa->next;
	      fa->next = save_arrow;
	      save_arrow = fa;
	      if (fat == NULL)
		fa = first_arrow;
	      else
		fa = fat->next;
	    }
	  else
	    {
	      fat = fa;
	      fa = fa->next;
	    }
	}
      fn = fn->next;
    }
  /* now output any remaining arrows (with source and target): */
  fa = first_arrow;
  while (fa != NULL)
    {
      /* verify that there is a source node:  */
      element *ffn = first_node;
      while (ffn != NULL && ffn != fa->u.a.source)
	{
	  ffn = ffn->next;
	}
      if (ffn == NULL)
	{
	  fa = fa->next;
	  continue;
	}
      /* verify that there is a target node:  */
      ffn = first_node;
      while (ffn != NULL && ffn != fa->u.a.target)
	{
	  ffn = ffn->next;
	}
      if (ffn == NULL)
	{
	  fa = fa->next;
	  continue;
	}
      output_arrow (fa);
      fa = fa->next;
    }
  /* restore saved arrows: */
  fa = save_arrow;
  while (fa != NULL)
    {
      save_arrow = fa->next;
      fa->next = first_arrow;
      first_arrow = fa;
      fa = save_arrow;
    }
}

void
copy (FILE * f, FILE * g)
{
  int c;
  rewind (f);
  c = getc (f);
  while (c != EOF)
    {
      putc (c, g);
      c = getc (f);
    }
}

void
regenerate_tex_file (void)
{
  restore_write_access ();
  tex_file = fopen (filename, "w+");
  if (tex_file == NULL)
    error ("Can't open file for writing.");
  printf ("Saving to %s.\n", filename);
  copy (pretext, tex_file);
  if (grid)
    fprintf (tex_file, "\\grid\n");
  merge_lists ();
  copy (comments, tex_file);
  copy (posttext, tex_file);
  remove_write_access ();
}
