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

char *progname = "-", *filename = "-";
int dflag, kflag, lines, hspaces, vdepth;

struct in {
  int innotes;
  FILE *file;
  int lineno, eoln, saved, spaces, prevspaces, fresh, depth, qdepth;
  char styles[3];
} in, ins[2];

char *header =
    "\\documentclass{article}\n%s\\usepackage{/home/freek/web/freek/books/sat}\n%s\n\\begin{document}\n",
  *dheader = "\\usepackage[dutch]{babel}\n",
  *kheader = "\\let\\textsc=\\MakeUppercase\n",
  *trailer = "\n\\end{document}\n";

char code[4];

char *codefrom[] =
{
  "''", "#", "$", "%", "&", "^", "_",
  "*", "<", ">", "[", "\\", "^", "{", "|", "}", "~",
  "**", "o", "L", "p", "-", "P", "ss", "(r)",
  "(c)", "TM", "AE", "oo", "+-", "<=", ">=", "Y",
  "mu", "d", "S", "Pi", "pi", "I", "a_", "o_",
  "O", "ae", "?", "!", "-.", "./", "f", "~~",
  "D", "OE", "oe", "/", "<>", ">0<", "++", ".",
  "%%",
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
  0
};

char *codeto[] =
{
  "''", "{\\#}", "{\\$}", "{\\%}", "{\\&}", "{\\^{}}", "{\\_}",
  "{$\\ast$}", "{$<$}", "{$>$}", "{[}", "{$\\backslash$}",
  "{$\\uparrow$}", "{$\\{$}", "{$|$}", "{$\\}$}", "{$\\sim$}",
  "{$\\dagger$}", "{$^{\\circ}$}", "{$\\pounds$}", "{$\\S$}",
  "{$\\bullet$}", "{$\\P$}", "{\\ss}", "{\\textregistered}",
  "{$\\copyright$}", "{\\texttrademark}", "{\\AE}", "{$\\infty$}",
  "{$\\pm$}", "{$\\le$}", "{$\\ge$}", "{\\rlap{=}Y}",
  "{$\\mu$}", "{$\\partial$}", "{$\\Sigma$}", "{$\\Pi$}",
  "{$\\pi$}", "{$\\int$}", "{$^{\\rm\\underbar{a}}$}", "{$^{\\rm\\underbar{o}}$}",
  "{$\\Omega$}", "{\\ae}", "{?`}", "{!`}",
  "{$\\neg$}", "{$\\surd$}", "{$f$}", "{$\\approx$}",
  "{$\\Delta$}", "{\\OE}", "{\\oe}", "{$\\div$}",
  "{$\\langle\\rangle$}", "{$\\rangle\\!\\!\\circ\\!\\!\\langle$}", "{$\\ddag$}", "{$\\cdot$}",
  "{$\\%_o$}",
  "${}^0$", "${}^1$", "${}^2$", "${}^3$", "${}^4$", "${}^5$", "${}^6$", "${}^7$", "${}^8$", "${}^9$"
};

char *accent[256];

void
error(s, h)
  char *s;
  int h;
{
  fprintf(stderr, "%s %s", progname, filename);
  if (in.lineno)
    fprintf(stderr, " %d", in.lineno);
  fprintf(stderr, ": ");
  if (h)
    perror(s);
  else
    fprintf(stderr, "%s\n", s);
  exit(1);
}

void
putverbcode(c)
  int c;
{
  if (c == '|')
    fputs("|\\verb-|-\\verb|", stdout);
  else
    putchar(c);
}

void
openin(innotes)
  int innotes;
{
  in.innotes = innotes;
  in.file = fopen(filename, "r");
  if (!in.file)
    error(filename, 1);
  in.lineno = 1;
  in.eoln = 1;
  in.saved = -2;
  in.fresh = 1;
  in.depth = 0;
  in.qdepth = 0;
  in.prevspaces = -1;
  ins[innotes] = in;
}

int get()
{
  int c;

  c = getc(in.file);
  if (c == EOF)
    return c;
  if (c == '\n')
    in.lineno++;
  if (in.eoln)
  {
    if (in.innotes)
    {
      while (c != '|')
      {
        while ((c = getc(in.file), 1) && c != EOF && c != '\n')
          ;
        if (c == EOF)
          return c;
        if (c == '\n')
          in.lineno++;
        c = getc(in.file);
        if (c == EOF)
          return c;
        if (c == '\n')
          in.lineno++;
      }
      c = getc(in.file);
      if (c == ' ')
        c = getc(in.file);
      if (c == EOF)
        return c;
      if (c == '\n')
        in.lineno++;
    }
    else
    {
      while (c == '|' || c == '\t')
      {
        while ((c = getc(in.file), 1) && c != EOF && c != '\n')
          ;
        if (c == EOF)
          return c;
        if (c == '\n')
          in.lineno++;
        c = getc(in.file);
        if (c == EOF)
          return c;
        if (c == '\n')
          in.lineno++;
      }
    }
  }
  in.eoln = (c == '\n');
  return c;
}

void putstyle(c)
  char c;
{
  switch (c)
  {
  case '_':
    printf("\\textit{");
    break;
  case '=':
    printf("\\textsc{");
    break;
  case '*':
    printf("\\textbf{");
    break;
  }
}

int
partype(n)
  int n;
{
  return n == 6 ? 4 : n;
}

void
closepar()
{
  if (in.prevspaces < 0) return;
  switch(in.prevspaces)
  {
  case 2:
    break;
  case 5:
    printf("\\end{quotation}\n");
    break;
  case 8:
    printf("\\end{center}\n");
    break;
  case 10:
    printf("\\end{flushright}\n");
    break;
  case 4:
  case 6:
    printf("\\end{verse}\n");
    break;
  default:
    ;
  }
  in.prevspaces = -1;
}

void
initaccents()
{
  accent[' '] = "";
  accent['-'] = "\\=";
  accent['_'] = "\\u";
  accent['o'] = "\\r";
  accent[','] = "\\c";
  accent['='] = "\\H";
  accent[';'] = "\\k";
}

int
main(argc, argv)
  int argc;
  char **argv;
{
  int arg = 0, i, c, c1, un;
  char *s;

  initaccents();
  if (argc)
    progname = *argv;
  while (argc > ++arg)
  {
    s = argv[arg];
    if (*s == '-')
    {
      while (*++s)
        if (*s == 'd')
          dflag++;
        else
        if (*s == 'K')
          kflag++;
        else
        {
          filename = argv[arg];
          error("unknown flag (not -d or -K)", 0);
        }
    }
    else
      break;
  }
  if (argc != arg + 1)
    error("usage: sat2tex [-d] [-K] filename", 0);
  filename = argv[arg];
  openin(1);
  openin(0);
  printf(header, dflag ? dheader : "", kflag ? kheader : "");
  lines = 2;
  in.prevspaces = -1;
  in.spaces = 0;
  c = get();
  while (c != EOF)
  {
    while (c == '\n')
    {
      lines++;
      c = get();
    }
    while (c == ' ')
    {
      in.spaces++;
      c = get();
    }
    if ((partype(in.prevspaces) != 4 || partype(in.spaces) != 4) || lines)
      putchar('\n');
    if (lines && in.innotes)
    {
      in.fresh = 1;
      closepar();
      for (i = in.depth - 1; i >= 0; i--)
        putchar('}');
      in.depth = 0;
      putchar('}');
      in.saved = c;
      if (in.qdepth)
        error("quotes in footnote do not match", 0);
      ins[1] = in;
      in = ins[0];
      c = in.saved;
      goto OUT;
    }
    if (c == EOF)
    {
      closepar();
      break;
    }
    if (lines >= 2)
    {
      closepar();
      if (!in.fresh)
        printf("\n\\newpage\n");
      for (i = 0; i < 6; i++)
        printf("\\ \\par");
      printf("\n");
      in.fresh = 1;
    }
    if (partype(in.spaces) == 4 && partype(in.prevspaces) == 4 && !lines)
    {
      printf(" \\\\\n");
      if (in.spaces == 6)
        printf("\\quad ");
    }
    else
    if (partype(in.spaces) == 4 && partype(in.prevspaces) == 4 && lines == 1)
    {
      printf("\n");
      if (in.spaces == 6)
        printf("\\quad ");
    }
    else
    if (partype(in.spaces) == partype(in.prevspaces) && lines == 1)
      switch(in.spaces)
      {
      case 2:
        closepar();
        printf("\n\\ \\par\n\n\\noindent\n");
        break;
      case 5:
        closepar();
        printf("\n\\ \\par\n\n\\begin{quotation}\n");
        break;
      case 8:
        printf("\n\\ \\par\n\n");
        break;
      case 10:
        closepar();
        printf("\n\\ \\par\n\n\\begin{flushright}\n");
        break;
      default:
        error("unknown number of spaces", 0);
      }
    else
    {
      if (lines == 1)
      {
        closepar();
        printf("\n\\ \\par\n");
      }
      switch(in.spaces)
      {
      case 2:
        if (!in.fresh)
          printf("\n");
        if (in.prevspaces != 2)
        {
          closepar();
          if (!in.innotes)
            printf("\\noindent\n");
        }
        break;
      case 5:
        if (in.prevspaces != 5)
        {
          closepar();
          printf("\n\\begin{quotation}\n");
        }
        else
          printf("\n");
        break;
      case 8:
        if (in.prevspaces != 8)
        {
          closepar();
          printf("\n\\begin{center}\n");
        }
        else
          printf("\n");
        break;
      case 10:
        if (in.prevspaces != 10)
        {
          closepar();
          printf("\n\\begin{flushright}\n");
        }
        else
          printf("\n");
        break;
      case 4:
      case 6:
        closepar();
        printf("\n\\begin{verse}\n");
        if (in.spaces == 6)
          printf("\\quad ");
        break;
      default:
        error("unknown number of spaces", 0);
      }
    }
    in.fresh = 0;
    for (i = 0; i < in.depth; i++)
      putstyle(in.styles[i]);
    while (1)
    {
OUT:
      while (c != EOF && c != '\n')
      {
        switch (c)
        {
        case '-':
          c = get();
          if (c == EOF)
          {
            putchar('-');
            break;
          }
          else
          if (c == '-')
          {
            putchar('-');
            while (c == '-')
            {
              putchar(c);
              c = get();
            }
          }
          else
          if (c == ' ' || c == '\n')
          {
            hspaces = 0;
            while (c == ' ')
            {
              hspaces++;
              c = get();
            }
            if (c == '\n')
            {
              if (hspaces == 0)
                fputs("%-", stdout);
              else
              {
                putchar('-');
                if (hspaces == 1)
                  putchar('%');
              }
            }
            else
            {
              putchar('-');
              for (i = 0; i < hspaces; i++)
                putchar(' ');
            }
          }
          else
            putchar('-');
          break;
        case '<':
          while (1)
          {
            c = get();
            if (c == EOF || c == '\n' || c == '>')
              break;
            c1 = c;
            c = get();
            if (c == EOF || c == '\n')
              break;
            if (c == '/')
              switch (c1)
              {
              case 'o':
              case 'O':
                printf("{\\%c}", c1);
                break;
              default:
                if (c1 == ' ')
                  printf("{$/$}");
                if (c1 == '=')
                  printf("{$\\ne$}");
                else
                  printf("$\\not{\\rm %c}$", c1);
              }
            else
            {
              if (accent[c])
              	printf("%s", accent[c]);
              else
              	printf("\\%c", c);
              if (c1 == ' ')
                printf("{\\strut}");
              else
              if (c1 == 'i')
                printf("{\\i}");
              else
              if (c1 == 'j')
                printf("{\\j}");
              else
                printf("{%c}", c1);
            }
          }
          if (c == '>')
            c = get();
          break;
        case '_':
        case '=':
        case '*':
          for (i = in.depth - 1; i >= 0; i--)
            if (in.styles[i] == c)
              break;
          if (i >= 0)
          {
            for (i = --in.depth; in.styles[i] != c; i--)
              putchar('}');
            putchar('}');
            for (; i < in.depth; i++)
              putstyle((in.styles[i] = in.styles[i + 1]));
          }
          else
          {
            in.styles[in.depth++] = c;
            putstyle(c);
          }
          c = get();
          break;
        case '^':
          while ((c = get()) && c != '^' && c != '\n' && c != EOF)
            ;
          if (c == '^')
            c = get();
          if (in.innotes)
          {
            while (c == ' ')
              c = get();
          }
          else
          {
            fputs("\\footnote{%", stdout);
            in.saved = c;
            ins[0] = in;
            in = ins[1];
            c = in.saved;
            if (c == -2)
              while ((c = get()) && c == '\n')
                ;
            in.fresh = 1;
            goto IN;
          }
          break;
        case '"':
          c = get();
          for (i = 0; c != EOF && c != '\n' && c != '"' && i < 3; i++)
          {
            code[i] = c;
            c = get();
          }
          code[i] = 0;
          s = 0;
          if (c == EOF || c == '\n' || c == '"')
          {
            for (i = 0; (s = codefrom[i]); i++)
              if (strcmp(code, s) == 0)
              {
                fputs(codeto[i], stdout);
                break;
              }
            if (!s)
            {
              if ((c1 = *code) && !code[1] && isalnum(c))
                printf("{$^{\\rm %c}$}", *code);
              else
                fputs(code, stdout);
            }
          }
          else
          {
            fputs(code, stdout);
            putchar(c);
            while ((c = get()) != EOF && c != '\n' && c != '"')
              putchar(c);
          }
          if (c == '"')
            c = get();
          break;
        case '&':
          fputs("{\\&}", stdout);
          c = get();
          break;
        case '%':
          fputs("{\\%}", stdout);
          c = get();
          break;
        case '~':
          c = get();
          break;
        case '{':
          printf("\\verb|");
          vdepth = 1;
          while ((c = get()) == '{')
            vdepth++;
          if (vdepth > 1 && c == '[')
          {
            c = get();
            if (c == '\n')
              c = get();
          }
          else
          {
            for (i = 1; i < vdepth; i++)
              putverbcode('{');
            if (vdepth == 1 && c == '\n')
              c = get();
          }
          while (c != EOF)
          {
            un = 0;
            switch (c)
            {
            case '{':
              vdepth++;
              putverbcode(c);
              break;
            case ']':
              un = 1;
              for (i = 0; vdepth && (c = get()) == '}'; i++)
                vdepth--;
              if (vdepth)
              {
                putverbcode(']');
                while (i--)
                  putverbcode('}');
              }
              else
                c = get();
              break;
            case '}':
              if (--vdepth)
                putverbcode(c);
              else
                c = get();
              break;
            case '\n':
              un = 1;
              c = get();
              if (vdepth != 1 || c != '}')
                printf("|\\\\\n\\verb|");
              break;
            default:
              putverbcode(c);
            }
            if (vdepth <= 0)
              break;
            if (!un)
              c = get();
          }
          printf("|");
          break;
        case '`':
          c = get();
          if (c == '~')
            c = get();
          else
            in.qdepth++;
          fputs((in.qdepth - 1) & 1 ? "``" : "`", stdout);
          if (c == '`')
            fputs("\\,", stdout);
          break;
        case '\'':
          c = get();
          if (isalnum(c) || c == '<' || c == '"' || c == '~')
          {
            if (c == '~')
              c = get();
            putchar('\'');
          }
          else
          {
            in.qdepth--;
            fputs(in.qdepth & 1 ? "''" : "'", stdout);
          }
          if (c == '\'')
            fputs("\\,", stdout);
          break;
        case '[':
          printf("{}");
        default:
          putchar(c);
          c = get();
        }
      }
      if (c == EOF)
        break;
      c = get();
      if (c == ' ' || c == '\n')
        break;
      putchar('\n');
    }
    for (i = in.depth - 1; i >= 0; i--)
      putchar('}');
    in.prevspaces = in.spaces;
    in.spaces = 0;
IN:
    lines = 0;
  }
  in.fresh = 1;
  closepar();
  ins[in.innotes] = in;
  if (in.innotes)
    error("not enough notes", 0);
  if (ins[1].saved != -2 && ins[1].saved != EOF)
  {
    in = ins[1];
    error("too many notes", 0);
  }
  if (ins[0].qdepth)
    error("quotes do not match", 0);
  if (ins[1].qdepth)
    error("quotes in footnotes did not match", 0);
  printf(trailer);
  return 0;
}
