#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

#include "textom4.h"

#define TOK_LEN 50
#define DEF_LEN 50

char **toks = NULL;
char **defs = NULL;
char toks_siz;
char toks_top;


void
tm_init()
{
  
  assert(toks == NULL);
  assert(defs == NULL);

  toks_siz = 100;
  toks_top = 0;


  toks = (char**) malloc( sizeof(char*) * toks_siz);
  defs = (char**) malloc( sizeof(char*) * toks_siz);
  
} /* tm_init */


void
tm_add(char *tok, char *def)
{
  char *split;

  assert(*tok == '\\');

  if( toks_siz <= toks_top )
  {
    toks_siz *= 2;
    toks = (char**) realloc(toks, toks_siz * sizeof(char*));
    defs = (char**) realloc(defs, toks_siz * sizeof(char*));
  }

  assert(toks);
  assert(defs);

  toks[toks_top] = strdup(tok + 1);

  if( 0 == strcmp(def, ".") )
  { /* FIXME: pro prazdne makro se musi dat jak maska '.' */
    def = "";
  }
  
  if( 0 == strcmp(def, "#1\\n")  )
  { /* hack: pro vskipy a podobne nacitam vse do konce radku */
    def = "#1\n";
  }
 

  defs[toks_top] = strdup(def);

  toks_top++;
  
} /* tm_add */


void
tm_read(char *filename)
{
   FILE * file;
   /* FIXME:defs, toks  dlouhe max 20 char */
   char def[DEF_LEN + 1], tok[TOK_LEN+1];
   int nr=0;

   file = fopen(filename, "r");

   if( file == NULL )
   {
     printf("cannt fopen(%s)\n", filename);
     return;
   }

   /* FIXME: better parse */
   while( 2 == fscanf(file, "%s %s\n", tok, def) )
   {
     if( *tok != '\\' )
     {
       printf("tok nr. %i (%s) didnt start with \\\n", toks_top, tok);
     }

     tm_add(tok, def);
   }

} /* tm_rad */


char *
tm_find(char *tok)
{
  int i;

  for(i=0; i < toks_top; i++)
  {
    if( 0 == strcmp(tok, toks[i]) )
    {
      return defs[i]; 
    }
  }
  return NULL;
} /* tm_find */


void
tm_print_toks()
{
  int i;
  printf("<slovnik>\n");
  for(i=0; i< toks_top;i++)
  {
    printf("\\%s %s\n", toks[i], defs[i]);
  }
  printf("</slovnik>\n");

} /* tm_print_toks */
/****************************/

int tm_is_space = 1;

int
tm_getc()
{
  return getchar();
} /* tm_getc */

void
tm_ungetc(int c)
{
  int ret;
  if( c != EOF)
  {
    ret = ungetc(c, stdin);
    assert(ret != EOF);
  }
} /* tm_ungetc */


void
tm_space_check(int c)
{
  tm_is_space =  ! (isalpha(c) || isdigit(c) || c == '_' );
} /* tm_isspace */

void
tm_putc(char c)
{
  tm_space_check(c);
  putchar(c);
} /* tm_putc */

void
tm_puts(char *s)
{
  char last = s[strlen(s) - 1];

  tm_space_check(last);
  
  printf("%s", s);
} /* tm_puts */

void
tm_put_underscore()
{
  if(!tm_is_space) 
  {
    tm_puts("{}");
  }
  tm_putc('_');
    
} /* tm_put_underscore */

void tm_read_tok();
void tm_read_math();

void
tm_read_block(int eobchar)
{
  
  int z;
  int eols;
  while( eobchar !=  (z = tm_getc()) )
  {
    switch(z) 
    {
    case '{':
      tm_put_underscore();
      tm_puts("gr(");
      tm_read_block('}');
      tm_puts(")");
      break;
    case '%':
      tm_put_underscore();
      tm_puts("comment(");
      while( '\n' != ( z=  tm_getc()))
      {
         if( z == EOF)
         {
            printf("Error EOF in block (comment) (may end by `%c')\n", eobchar);
            return;
         }
         tm_putc(z);
      } 
      tm_puts(")\n");
      break;

    case '^':
    case '_':
    case '~': /* tak trosku hack: jako bych nacet \~ */
      tm_puts("{}");

      tm_ungetc(z);
      tm_read_tok();

      break;
    case '\\':
      tm_read_tok();
      break;
    case '$':
      tm_read_math();
      break;
    case '\n':
      tm_putc(z);
      for(eols=0; '\n' == (z = tm_getc()); eols++)
      {
        tm_putc(z);
      }
      if( eols && z != EOF)
      {
        tm_puts("_par\n");
      }
      tm_ungetc(z);
      break;
    case -1:
      printf("Error EOF in block (may end by `%c')\n", eobchar);
      return;
    default:

      tm_putc(z);
    }
  }
    
} /* tm_read_block */

char *
tm_onecharname(char c)
{
  switch(c)
  {
  case '\'': return "apostrof";
  case ',': return "tilda";
  case '.': return "tilda";
  case '/': return "tilda";
  case ' ': return "tilda";
  case '~': return "tilda";
  case '!': return "exlam";
  case '@': return "at";
  case '%': return "percent";
  case '^': return "sup";
  case '_': return "sub";
  case '=': return "eq";
  case '-': return "minus";
  case '\\': return "bbackslash";
  case '\n': return "bbackslash";
  default:  return "UNKNOWN";
  }
} /* tm_onecharname */ 

void
tm_read_tok()
{
  char z;
  char tok[TOK_LEN + 1];
  char i = 0;
  char *def;
  int print_comma = 0;
  int print_nl = 0;


  z = tm_getc();

  if( isalpha(z) )
  {

    /* read tok */
    for(i = 0;isalpha(z); i++)
    {
      assert(i < TOK_LEN);
    
      tok[i] = z;
    
      z = tm_getc();
    }
    tok[i] = '\0';
  }
  else
  {
    /* pozue jednoznakovy tok */
    strcpy(tok, tm_onecharname(z));
    z = tm_getc();
  }
 
  def = tm_find(tok);

  if(def == NULL)
  {
    printf("\\%s ", tok);
    tm_ungetc(z);
    return;
  }

  /* v `z' prvni znak po tokenu */

  /* FIXME: space skiping only */
  for(;' ' == z; z= tm_getc());



  /* proces args (v `z' prvni znak argumentu */
  tm_put_underscore();
  tm_puts(tok);
  tm_putc('(');

  print_comma = 0;
  for(i = 0; def[i]; i++) 
  {
    if( def[i] == '#' )
    { /* argument */
      if( print_comma ) tm_puts("}, ");
      tm_putc('{');
      assert(isdigit(def[i+1]));
      if( def[i+2] == '#' || def[i+2] == '\0' )
      { /*nacti pouze jeden 'znak' typu: #1#2 */
        if(z == '{' )
        { /* je to cely blok */
          tm_read_block('}');
        }
        else
        {
          tm_putc(z);
        }
        /* skip arg num */
        i++;
      }
      else 
      { /* zarazka  typu #1: */
	tm_ungetc(z);
        tm_read_block(def[i+2]);
	/* hack: kdyz je zarazka \n (pouzve  v pripade ze def bylo
	 * #1\n */
	if( def[i+2] == '\n') print_nl = 1;

        /* skip argnum a zarazku */
        i += 2;
      }
      print_comma = 1;

    }
    else
    { /*kontroluj jestli odpovida maskce */
      if( z != def[i] )
      {
        printf("Error parsing \\%s args(i want `%c' but `%c' fond.\n",
               tok, def[i], z);
        return;
      }
    }
    z = tm_getc();
  }

  if( print_comma) tm_putc('}');
  tm_putc(')');

  tm_ungetc(z);

  if( print_nl )
  {
    tm_ungetc('\n');
  }
    
} /* tm_read_tok */


void
tm_read_math()
{
  int z;
  int is_display = 0;

  z = tm_getc(); 

  if( z == '$' )
  {
    is_display = 1;
    z = tm_getc();
    tm_puts("_dm({");
  }
  else
  {
    tm_puts("_m({");
  }

#if 0
  for(; '$' != z; z = tm_getc())
  {
    if( z == EOF )
    {
      printf("EOF in '%s'\n", is_display?"$$":"$");
    }
    tm_putc(z);
  }
#endif

  tm_ungetc(z);
  tm_read_block('$');

  if( is_display )
  {
    z = tm_getc();
    if( z != '$' )
    {
       printf("Error searching '$$'  '$' found only\n");
    }
  }

  tm_puts("})");
} /* tm_read_tok */

void
usage()
{
  printf(
    "usage: textom4 [-s] [-t toks.def] < file.tex >file.m4\n"
    "  -s print voceblurary\n"
    "  -t toks.def define tokens (not now) [toks.def]\n\n");
} /* usage */

int
main(int argc, char *argv[])
{
  int c;
  int want_print_slovnik = 0;
  char *tok_filename = "toks.def";
 
  
  while ( -1 != (c = getopt(argc, argv, "t:sh")) )
  {
    switch(c)
    {
    case 's':
      want_print_slovnik = 1;
      break;
    case 't':
      tok_filename = optarg;
      break;
      
    default:
       usage(); return 0;
    }
  }
     

  tm_init();
  tm_read(tok_filename);

  if( want_print_slovnik )
  {
    tm_print_toks();
  }
  
  tm_read_block(-1);

  return 0;
} /* ahoj */

