#define VERSION "0.99f"

/*

flow.c 22/1/93 (C) 2005 Terry Brown

  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

/*

flow - a flow-chart -> LaTeX generator.  Parses a file in a flow-chart
specification language, then produces a corresponding LaTeX
pic environment.

*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

typedef int bool;

#define TRUE 1
#define FALSE 0

typedef struct {float x,y;} coord;

typedef struct {char name[80];
               int Params;
               coord size;
               bool HasText;
              } FlowCom;

#define ParamLen 120
#define LineLen 120

typedef char param[ParamLen];

#define MaxBoxes 2048  /* could not possibly have more than this */

#define Commands 16

FlowCom fcom[Commands] = {
 { "SetTrack", 1, {0,0}, FALSE },
 { "Up",       1, {0,0}, FALSE },
 { "Down",     1, {0,0}, FALSE },
 { "Left",     1, {0,0}, FALSE },
 { "Right",    1, {0,0}, FALSE },
 { "Box",      0, {4,2}, TRUE  },
 { "Oval",     0, {4,2}, TRUE  },
 { "Choice",   4, {4,4}, TRUE  },
 { "Tag",      0, {0,0}, FALSE },
 { "ToTag",    0, {0,0}, FALSE },
 { "Scale",    2, {0,0}, FALSE },
 { "Tilt",     0, {4,2}, TRUE  },
 { "Text",     0, {4,2}, TRUE  },
 { "TxtPos",   4, {0,0}, FALSE },
 { "Skip",     2, {0,0}, FALSE },
 { "%",        0, {0,0}, FALSE }
};

typedef enum {SetTrack,
             Up,
             Down,
             Left,
             Right,
             Box,
             Oval,
             Choice,
             Tag,
             ToTag,
             Scale,
             Tilt,
             Text,
             TxtPos,
             Skip,
             Comment
            }  TheCommands;

typedef enum {ArrowS, LineS, NoneS} trackSymb;
typedef enum {UpD, DownD, LeftD, RightD} direcs;

typedef struct ATAG {coord pos;
               coord size;
               struct ATAG *next;
              } aTag;

typedef struct {
   coord min;
   coord max;
} boundingBox;


/* state / position tracking variables */

boundingBox pic = {{0,0},{0,0}};
coord Coords[MaxBoxes] = {{0,0}};  /* just initialise first coord */
int CurCoord = 0;
int CurTrack = ArrowS;
int CurDirec = DownD;
coord CurScale = {1.,1.};
coord CurSize = {0.,0.};
aTag *CurTag = NULL;
char CurPos[20] = "[c]";
char CurBoxPos[20] = "[c]";
char leader[20]="";
char tailer[20]="";
float TrackX = 1.;
float TrackY = 1.;
int InputLine = 0;
char line[LineLen];

float VertGap=1.;
float HorzGap=1.;

FILE *inFile, *outFile;  /* global input output streams */

int doText();

void checkBounds(boundingBox *b, coord *c) {

   if (b->min.x > c->x) b->min.x = c->x;
   if (b->max.x < c->x) b->max.x = c->x;
   if (b->min.y > c->y) b->min.y = c->y;
   if (b->max.y < c->y) b->max.y = c->y;
}
void checkBoundsRng(
   boundingBox *b,
   float x,
   float y,
   float sx,
   float sy
   ) {

   if (b->min.x > x) b->min.x = x;
   if (b->max.x < x+sx) b->max.x = x+sx;
   if (b->min.y > y) b->min.y = y;
   if (b->max.y < y+sy) b->max.y = y+sy;
}

int getCommand(char line[], param plist) {
/*
tries to interpret the next line of inFile as a command, returns -1 if it can't
*/

   int i=0, command = -1;

   while (i<Commands) {

       if ( strncmp(fcom[i].name,line,strlen(fcom[i].name)) == 0) {
          command = i;
           break;
       }
       i++;
   }

   if (command == -1) {
       if (!feof(inFile) && line[0] != '\n')
           fprintf(stderr,"flow error : can't interpret line %d\n",InputLine);
       return command;
   }

   strncpy(plist, line+strlen(fcom[command].name), ParamLen);

   return command;

}


int doCommand(int command, param pList) {
/*
output the LaTeX bits for this command, updating the coords, coord list
etc. as required
*/

   static int init = 0;
   aTag *tempTag;
   char params[10][80];
   char *trackStr = "vector";
   float dimen,x,y,x1,y1;
   int i,xs,ys;
   coord t;

   dimen = 0.;

   params[0][0]=0;  /* so Up / Down / Left / Right can find *'s for line
                       drawing                                            */


   if (CurTrack == LineS) trackStr = "line";

   if (fcom[command].HasText) {
       if ((command != Choice && sscanf(pList,"%f %f",&x,&y) == 2) ||
           (command == Choice && sscanf(pList,"%s %s %s %s %f %f",
                                      params[0],
                                      params[0],
                                      params[0],
                                      params[0],
                                      &x,&y) == 6)) {
           fcom[command].size.x = x;
           fcom[command].size.y = y;
       }
   }

   if (!init && CurSize.x == 0. && CurSize.y == 0.) {
           CurSize.x = fcom[command].size.x*CurScale.x;
           CurSize.y = fcom[command].size.y*CurScale.y;
   }

   if ( init && (fcom[command].size.x != 0 || fcom[command].size.y != 0 )) {

       switch (CurDirec) {

       case DownD :

           t.x = Coords[CurCoord].x+CurSize.x/2;
           t.y = Coords[CurCoord].y-CurSize.y;

           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                              t.x,
                      t.y,
                      trackStr,
                      0,-1,
                      VertGap);

           checkBounds(&pic,&t);

           Coords[CurCoord+1].x = Coords[CurCoord].x + CurSize.x/2 -
                                  fcom[command].size.x*CurScale.x / 2;

           Coords[CurCoord+1].y = Coords[CurCoord].y - CurSize.y
                                  - VertGap;

           checkBounds(&pic,Coords+CurCoord+1);

           break;

       case UpD :

           t.x = Coords[CurCoord].x+CurSize.x/2;
           t.y = Coords[CurCoord].y;

           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                      t.x,
                      t.y,
                      trackStr,
                      0,1,
                      VertGap);

           checkBounds(&pic,&t);

           Coords[CurCoord+1].x = Coords[CurCoord].x + CurSize.x/2 -
                                  fcom[command].size.x*CurScale.x / 2;

           Coords[CurCoord+1].y = Coords[CurCoord].y +
                                  fcom[command].size.y*CurScale.y
                                  + VertGap;
           checkBounds(&pic,Coords+CurCoord+1);

           break;

       case RightD :

           t.x = Coords[CurCoord].x+CurSize.x;
           t.y = Coords[CurCoord].y-CurSize.y/2;

           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                      t.x,
                      t.y,
                      trackStr,
                      1,0,
                      HorzGap);

           checkBounds(&pic,&t);

           Coords[CurCoord+1].x = Coords[CurCoord].x + CurSize.x
                                  + HorzGap;
           Coords[CurCoord+1].y = Coords[CurCoord].y - CurSize.y/2 +
                                  fcom[command].size.y*CurScale.y / 2;

           checkBounds(&pic,Coords+CurCoord+1);

           break;

       case LeftD :

           t.x = Coords[CurCoord].x;
           t.y = Coords[CurCoord].y-CurSize.y/2;

           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                      t.x,
                      t.y,
                      trackStr,
                      -1,0,
                      HorzGap);

           checkBounds(&pic,&t);

           Coords[CurCoord+1].x = Coords[CurCoord].x -
                                  fcom[command].size.x*CurScale.x
                                  - HorzGap;
           Coords[CurCoord+1].y = Coords[CurCoord].y - CurSize.y/2 +
                                  fcom[command].size.y*CurScale.y / 2;

           checkBounds(&pic,Coords+CurCoord+1);

           break;

       default:
           break;
       }
       CurCoord++;
       CurSize.x = fcom[command].size.x*CurScale.x;
       CurSize.y = fcom[command].size.y*CurScale.y;

   }

   switch (command) {

   case Skip :
       if (sscanf(pList,"%f %f %f %f",&x,&y,&x1,&y1) == 4) {
           VertGap = y;
           HorzGap = x;
           TrackX = x1;
           TrackY = y1;
       }
       break;

   case TxtPos :
       CurPos[0]=CurBoxPos[0]=leader[0]=tailer[0]=0;
       sscanf(pList,"%s %s %s %s",CurPos,CurBoxPos,leader,tailer);
       break;

   case Box :
       init=1;
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\framebox(%3.4f,%3.4f)%s{\\shortstack%s{\n",
              Coords[CurCoord].x,
                      Coords[CurCoord].y-CurSize.y,
                      CurSize.x,
                      CurSize.y,
                      CurBoxPos,
                      CurPos);
           doText();
           fprintf(outFile,"}}}\n");

       checkBoundsRng(
           &pic,
           Coords[CurCoord].x,
           Coords[CurCoord].y-CurSize.y,
           CurSize.x,
           CurSize.y
       );

       break;

   case Tilt :
       init=1;
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(%3.4f,%3.4f)%s{\\shortstack%s{\n",
                      Coords[CurCoord].x,
                      Coords[CurCoord].y-CurSize.y,
                      CurSize.x,
                      CurSize.y,
                      CurBoxPos,
                      CurPos);
       doText();
       fprintf(outFile,"}}}\n");

       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x+1./6.*CurSize.y,
                      Coords[CurCoord].y,
                      1,0,
                      CurSize.x);
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x-1./6.*CurSize.y,
                      Coords[CurCoord].y-CurSize.y,
                      1,0,
                      CurSize.x);
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x-1./6.*CurSize.y,
                      Coords[CurCoord].y-CurSize.y,
                      1,3,
                      CurSize.y*1./3.);
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x-1./6.*CurSize.y+CurSize.x,
                      Coords[CurCoord].y-CurSize.y,
                      1,3,
                      CurSize.y*1./3.);

   checkBoundsRng(
       &pic,
       Coords[CurCoord].x-1./6.*CurSize.y,
       Coords[CurCoord].y-CurSize.y,
       CurSize.x + 1./6.*CurSize.y,
       CurSize.y
   );

       break;

   case Text :
       init=1;
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(%3.4f,%3.4f)%s{\\shortstack%s{\n",
                      Coords[CurCoord].x,
                      Coords[CurCoord].y-CurSize.y,
                      CurSize.x,
                      CurSize.y,
                      CurBoxPos,
                      CurPos);
       doText();
       fprintf(outFile,"}}}\n");

   checkBoundsRng(
       &pic,
       Coords[CurCoord].x,
       Coords[CurCoord].y-CurSize.y,
       CurSize.x,
       CurSize.y
   );
       break;

   case Oval :
       init=1;
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\oval(%3.4f,%3.4f)}\n",
                      Coords[CurCoord].x+CurSize.x/2,
                      Coords[CurCoord].y-CurSize.y/2,
                      CurSize.x,
                      CurSize.y );
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(%3.4f,%3.4f)%s{\\shortstack%s{\n",
                      Coords[CurCoord].x,
                      Coords[CurCoord].y-CurSize.y,
                      CurSize.x,
                      CurSize.y,
                      CurBoxPos,
                      CurPos );
       doText();
       fprintf(outFile,"}}}\n");
   checkBoundsRng(
       &pic,
       Coords[CurCoord].x,
       Coords[CurCoord].y-CurSize.y,
       CurSize.x,
       CurSize.y
   );

       break;

   case Choice :

       init=1;

   xs = (int)CurSize.x; ys = (int)CurSize.y;

   for (i = (xs>ys) ? xs : ys; i>1; i--) {
       if ( (xs % i) == 0 && (ys % i) == 0 ) {
           xs /= i;
           ys /= i;
           i = (xs>ys) ? xs : ys;
       }
   }

   if (xs>6) {
       fprintf(stderr,"Flow warning - illegal Choice aspect ratio\n");
       xs = 6;
   }
   if (ys>6) {
       fprintf(stderr,"Flow warning - illegal Choice aspect ratio\n");
       ys = 6;
   }

       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                     Coords[CurCoord].x,
                     Coords[CurCoord].y-CurSize.y/2,
                     xs,ys,CurSize.x/2
                     );
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                     Coords[CurCoord].x,
                     Coords[CurCoord].y-CurSize.y/2,
                     xs,-ys,CurSize.x/2
                     );
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                     Coords[CurCoord].x+CurSize.x,
                     Coords[CurCoord].y-CurSize.y/2,
                     -xs,-ys,CurSize.x/2
                     );
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\line(%d,%d){%3.4f}}\n",
                     Coords[CurCoord].x+CurSize.x,
                     Coords[CurCoord].y-CurSize.y/2,
                     -xs,ys,CurSize.x/2
                     );
       fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(%3.4f,%3.4f)%s{\\shortstack%s{\n",
                      Coords[CurCoord].x,
                      Coords[CurCoord].y-CurSize.y,
                      CurSize.x,
                      CurSize.y,
                      CurBoxPos,
                      CurPos );
       doText();
       fprintf(outFile,"}}}\n");

       sscanf(pList,"%s %s %s %s",params[0],params[1],params[2],params[3]);

       if (params[0][0] != '.')
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(0,0)[lt]{%s}}\n",
                          Coords[CurCoord].x+
                          CurSize.x*0.65,
                          Coords[CurCoord].y,
                          params[0]
                         );

       if (params[1][0] != '.')
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(0,0)[rt]{%s}}\n",
                          Coords[CurCoord].x,
                          Coords[CurCoord].y-
                          CurSize.y/2.*0.7,
                          params[1]
                         );

       if (params[2][0] != '.')
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(0,0)[lt]{%s}}\n",
                          Coords[CurCoord].x+
                          CurSize.x,
                          Coords[CurCoord].y-
                          CurSize.y/2.*0.7,
                          params[2]
                         );

       if (params[3][0] != '.')
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\makebox(0,0)[lb]{%s}}\n",
                          Coords[CurCoord].x+
                          CurSize.x*0.65,
                          Coords[CurCoord].y-
                          CurSize.y,
                          params[3]
                         );

   checkBoundsRng(
       &pic,
       Coords[CurCoord].x,
       Coords[CurCoord].y-CurSize.y,
       CurSize.x,
       CurSize.y
   );
       break;

   case SetTrack :
       sscanf(pList,"%s",params[0]);

       if ( strcmp("arrow",params[0]) == 0)
           CurTrack = ArrowS;
       if ( strcmp("line",params[0]) == 0)
           CurTrack = LineS;
       if ( strcmp("none",params[0]) == 0)
           CurTrack = NoneS;

       break;

   case Scale :
       sscanf(pList,"%f %f",&CurScale.x,&CurScale.y);
       break;

   case Tag :
       tempTag = (aTag*)malloc(sizeof(aTag));
       tempTag->size.x = CurSize.x;
       tempTag->size.y = CurSize.y;
       tempTag->pos.x = Coords[CurCoord].x;
       tempTag->pos.y = Coords[CurCoord].y;
       tempTag->next = CurTag;
       CurTag = tempTag;
       break;

   case ToTag :

       if (CurTag == NULL) {
           fprintf(stderr,"flow error - Tag stack empty \n");
           break;
       }
       tempTag = CurTag;
       CurSize.x = tempTag->size.x;
       CurSize.y = tempTag->size.y;
       Coords[CurCoord].x = tempTag->pos.x;
       Coords[CurCoord].y = tempTag->pos.y;

       CurTag = tempTag->next;

       free(tempTag);

       break;

   case Right :
       CurDirec = RightD;
       if (sscanf(pList,"%f %19s",&dimen,params[0])>=1) {
           init=1;
           dimen *= TrackX;
           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x+CurSize.x,
                      Coords[CurCoord].y-CurSize.y/2,
                      (params[0][0]=='*')?"vector":"line",
                      1,0,
                      dimen);

           Coords[CurCoord+1].x = Coords[CurCoord].x + CurSize.x +
                                  dimen;

           Coords[CurCoord+1].y = Coords[CurCoord].y - CurSize.y/2;

           CurCoord++;

           CurSize = fcom[command].size;
       }
       checkBounds(&pic,&(Coords[CurCoord]));
       break;

   case Down :
       CurDirec = DownD;

       if (sscanf(pList,"%f %19s",&dimen,params[0])>=1) {
           init=1;
           dimen *= TrackY;
           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x+CurSize.x/2,
                      Coords[CurCoord].y-CurSize.y,
                      (params[0][0]=='*')?"vector":"line",
                      0,-1,
                      dimen);

           Coords[CurCoord+1].x = Coords[CurCoord].x + CurSize.x/2 -
                                  fcom[command].size.x / 2;

           Coords[CurCoord+1].y = Coords[CurCoord].y - CurSize.y
                                  - dimen;

           CurCoord++;

           CurSize = fcom[command].size;
       }
       checkBounds(&pic,&(Coords[CurCoord]));

       break;

   case Left :
       CurDirec = LeftD;
       if (sscanf(pList,"%f %19s",&dimen,params[0])>=1) {
           init=1;
           dimen *= TrackX;
           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x,
                      Coords[CurCoord].y-CurSize.y/2,
                      (params[0][0]=='*')?"vector":"line",
                      -1,0,
                      dimen);

           Coords[CurCoord+1].x = Coords[CurCoord].x -
                                  dimen;

           Coords[CurCoord+1].y = Coords[CurCoord].y - CurSize.y/2;

           CurCoord++;

           CurSize = fcom[command].size;
       }
       checkBounds(&pic,&(Coords[CurCoord]));
       break;

   case Up :
       CurDirec = UpD;
       if (sscanf(pList,"%f %19s",&dimen,params[0])>=1) {
           init=1;
           dimen *= TrackY;
           if (CurTrack != NoneS)
           fprintf(outFile,"\\put(%3.4f,%3.4f){\\%s(%d,%d){%3.4f}}\n",
                      Coords[CurCoord].x+CurSize.x/2,
                      Coords[CurCoord].y,
                      (params[0][0]=='*')?"vector":"line",
                      0,1,
                      dimen);

           Coords[CurCoord+1].x = Coords[CurCoord].x + CurSize.x/2 -
                                  fcom[command].size.x / 2;

           Coords[CurCoord+1].y = Coords[CurCoord].y +
                                  dimen;

           CurCoord++;

           CurSize = fcom[command].size;
       }
       checkBounds(&pic,&(Coords[CurCoord]));
       break;

   case Comment :
       break;

   default:
       fprintf(stderr,"flow error : unknown command %4d\n",command);
       return 0;
   }

   if (command != Scale) CurScale.x = CurScale.y = 1.;

   if (fcom[command].HasText == FALSE) {
       line[0]=0;
       fgets(line,LineLen,inFile);
       InputLine++;
   }

   return 1;

}

int doText() {
/*
output text for those commands that require it, spit out all lines that start
with white space (0x20, 0x09)
*/

   int i;

   line[0]=0;
   fgets(line,LineLen,inFile);
   InputLine++;

   while (!feof(inFile) && (
          line[0] == 0x20 ||
          line[0] == 0x09 )
         ) {

           line[strlen(line)-1]=0;
           for (i=0; i<strlen(line) &&
                     (line[i] == 0x09 || line[i] == 0x20); i++);

          fprintf(outFile,"%s%s%s",leader,line+i,tailer);
           line[0]=0;
           fgets(line,LineLen,inFile);
           InputLine++;

          if (line[0] == 0x20 || line[0] == 0x09 )
              fprintf(outFile,"\\\\\n");
          else
              fprintf(outFile,"\n");

      }

   return 1;
}

void applyPicWrapper(FILE *inFile, FILE *outFile) {

   char buf[1024];

   fprintf(outFile,"\\begin{picture}(%f,%f)(%f,%f)\n",
           pic.max.x - pic.min.x,
           pic.max.y - pic.min.y,
           pic.min.x,
           pic.min.y
         );

   while (!feof(inFile)) {

       fwrite(buf,fread(buf,1,1024,inFile),1,outFile);

   }

   fprintf(outFile,"\\end{picture}\n");
}

int main(int argc, char **argv) {

   param params;
   int command=0;
   char tmpfileNm[80];


   inFile = stdin; outFile = stdout;

   tmpnam(tmpfileNm);
   outFile = fopen(tmpfileNm,"w");

   if (argc > 1) {
       inFile = fopen(argv[1],"r");
       if (inFile == NULL) {
           fprintf(stderr,"Couldn't open input file %s\n",argv[1]);
           exit(0);
       }
   }

   line[0]=0;
   fgets(line,LineLen,inFile);
   InputLine++;

   fprintf(outFile,"%% picture environment flowchart generated by flow ");
   fprintf(outFile,"%s\n",VERSION);

   while (command != -1) {

       command = getCommand(line,params);

       if (command != -1)
            if (!doCommand(command, params)) return 10;
   }

   fclose(inFile);
   fclose(outFile);

   outFile = stdout;
   if (argc > 2) {
       outFile = fopen(argv[2],"w");
       if (outFile == NULL) {
           fprintf(stderr,"Couldn't open output file %s\n",argv[2]);
           fclose(inFile);
           exit(0);
       }
   }

   inFile = fopen(tmpfileNm,"r");

   applyPicWrapper(inFile,outFile);
   if (outFile != stdout) fclose(outFile);
   fclose(inFile);
   remove(tmpfileNm);

   exit(0);

   return 0;    /* just to suppress the warning */

}