// -*- C++ -*-
/* Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc.
     Written by James Clark (jjc@jclark.uucp)

This file is part of groff.

groff 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 1, or (at your option) any later
version.

groff 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 groff; see the file LICENSE.  If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "eqn.h"
#include "pbox.h"
#include "ptable.h"

class char_box : public simple_box {
  unsigned char c;
  char next_is_italic;
  char prev_is_italic;
public:
  char_box(unsigned char);
  void debug_print();
  void output();
  int is_char();
  int left_is_italic();
  int right_is_italic();
  void hint(unsigned);
  void handle_char_type(int, int);
};

class special_char_box : public simple_box {
  char *s;
public:
  special_char_box(const char *);
  ~special_char_box();
  void output();
  void debug_print();
  int is_char();
  void handle_char_type(int, int);
};

const char *spacing_type_table[] = {
  "ordinary",
  "operator",
  "binary",
  "relation",
  "opening",
  "closing",
  "punctuation",
  "inner",
  "suppress",
  0,
};

const int DIGIT_TYPE = 0;
const int LETTER_TYPE = 1;

const char *font_type_table[] = {
  "digit",
  "letter",
  0,
};

struct char_info {
  int spacing_type;
  int font_type;
  char_info();
};

char_info::char_info()
: spacing_type(ORDINARY_TYPE), font_type(DIGIT_TYPE)
{
}

static char_info char_table[256];

declare_ptable(char_info)
implement_ptable(char_info)

PTABLE(char_info) special_char_table;

static int get_special_char_spacing_type(const char *ch)
{
  char_info *p = special_char_table.lookup(ch);
  return p ? p->spacing_type : ORDINARY_TYPE;
}

static int get_special_char_font_type(const char *ch)
{
  char_info *p = special_char_table.lookup(ch);
  return p ? p->font_type : DIGIT_TYPE;
}

static void set_special_char_type(const char *ch, int st, int ft)
{
  char_info *p = special_char_table.lookup(ch);
  if (!p) {
    p = new char_info;
    special_char_table.define(ch, p);
  }
  if (st >= 0)
    p->spacing_type = st;
  if (ft >= 0)
    p->font_type = ft;
}

void init_char_table()
{
  set_special_char_type("pl", 2, -1); // binary
  set_special_char_type("mi", 2, -1);
  set_special_char_type("eq", 3, -1); // relation
  set_special_char_type("<=", 3, -1);
  set_special_char_type(">=", 3, -1);
  char_table['}'].spacing_type = 5; // closing
  char_table[')'].spacing_type = 5;
  char_table[']'].spacing_type = 5;
  char_table['{'].spacing_type = 4; // opening
  char_table['('].spacing_type = 4;
  char_table['['].spacing_type = 4;
  char_table[','].spacing_type = 6; // punctuation
  char_table[';'].spacing_type = 6;
  char_table[':'].spacing_type = 6;
  char_table['.'].spacing_type = 6;
  char_table['>'].spacing_type = 3;
  char_table['<'].spacing_type = 3;
  char_table['*'].spacing_type = 2; // binary
  for (int i = 0; i < 256; i++)
    if (csalpha(i))
      char_table[i].font_type = LETTER_TYPE;
}

static int lookup_spacing_type(const char *type)
{
  for (int i = 0; spacing_type_table[i] != 0; i++)
    if (strcmp(spacing_type_table[i], type) == 0)
      return i;
  return -1;
}

static int lookup_font_type(const char *type)
{
  for (int i = 0; font_type_table[i] != 0; i++)
    if (strcmp(font_type_table[i], type) == 0)
      return i;
  return -1;
}

void box::set_spacing_type(char *type)
{
  int t = lookup_spacing_type(type);
  if (t < 0)
    error("unrecognised type `%1'", type);
  else
    spacing_type = t;
  delete type;
}

char_box::char_box(unsigned char cc)
: c(cc), prev_is_italic(0), next_is_italic(0)
{
  spacing_type = char_table[c].spacing_type;
}

void char_box::hint(unsigned flags)
{
  if (flags & HINT_PREV_IS_ITALIC)
    prev_is_italic = 1;
  if (flags & HINT_NEXT_IS_ITALIC)
    next_is_italic = 1;
}

void char_box::output()
{
  int font_type = char_table[c].font_type;
  if (font_type != LETTER_TYPE)
    printf("\\f[%s]", current_roman_font);
  if (!prev_is_italic)
    fputs("\\,", stdout);
  if (c == '\\')
    fputs("\\e", stdout);
  else
    putchar(c);
  if (!next_is_italic)
    fputs("\\/", stdout);
  else
    fputs("\\&", stdout);		// suppress ligaturing and kerning
  if (font_type != LETTER_TYPE)
    fputs("\\fP", stdout);
}

int char_box::left_is_italic()
{
  int font_type = char_table[c].font_type;
  return font_type == LETTER_TYPE;
}

int char_box::right_is_italic()
{
  int font_type = char_table[c].font_type;
  return font_type == LETTER_TYPE;
}

int char_box::is_char()
{
  return 1;
}

void char_box::debug_print()
{
  if (c == '\\') {
    putc('\\', stderr);
    putc('\\', stderr);
  }
  else
    putc(c, stderr);
}

special_char_box::special_char_box(const char *t)
{
  s = strsave(t);
  spacing_type = get_special_char_spacing_type(s);
}

special_char_box::~special_char_box()
{
  delete s;
}

void special_char_box::output()
{
  int font_type = get_special_char_font_type(s);
  if (font_type != LETTER_TYPE)
    printf("\\f[%s]", current_roman_font);
  printf("\\,\\[%s]\\/", s);
  if (font_type != LETTER_TYPE)
    printf("\\fP");
}

int special_char_box::is_char()
{
  return 1;
}

void special_char_box::debug_print()
{
  fprintf(stderr, "\\[%s]", s);
}


void char_box::handle_char_type(int st, int ft)
{
  if (st >= 0)
    char_table[c].spacing_type = st;
  if (ft >= 0)
    char_table[c].font_type = ft;
}

void special_char_box::handle_char_type(int st, int ft)
{
  set_special_char_type(s, st, ft);
}

void set_char_type(const char *type, char *ch)
{
  assert(ch != 0);
  int st = lookup_spacing_type(type);
  int ft = lookup_font_type(type);
  if (st < 0 && ft < 0) {
    error("bad character type `%1'", type);
    delete ch;
    return;
  }
  box *b = split_text(ch);
  b->handle_char_type(st, ft);
  delete b;
}

/* We give primes special treatment so that in ``x' sub 2'', the ``2''
will be tucked under the prime */

class prime_box : public pointer_box {
  box *pb;
public:
  prime_box(box *);
  ~prime_box();
  int compute_metrics(int style);
  void output();
  void compute_subscript_kern();
  void debug_print();
  void handle_char_type(int, int);
};

box *make_prime_box(box *pp)
{
  return new prime_box(pp);
}

prime_box::prime_box(box *pp) : pointer_box(pp)
{
  pb = new special_char_box("fm");
}

prime_box::~prime_box()
{
  delete pb;
}

int prime_box::compute_metrics(int style)
{
  int res = p->compute_metrics(style);
  pb->compute_metrics(style);
  printf(".nr " WIDTH_FORMAT " 0\\n[" WIDTH_FORMAT "]"
	 "+\\n[" WIDTH_FORMAT "]\n",
	 uid, p->uid, pb->uid);
  printf(".nr " HEIGHT_FORMAT " \\n[" HEIGHT_FORMAT "]"
	 ">?\\n[" HEIGHT_FORMAT "]\n",
	 uid, p->uid, pb->uid);
  printf(".nr " DEPTH_FORMAT " \\n[" DEPTH_FORMAT "]"
	 ">?\\n[" DEPTH_FORMAT "]\n",
	 uid, p->uid, pb->uid);
  return res;
}

void prime_box::compute_subscript_kern()
{
  p->compute_subscript_kern();
  printf(".nr " SUB_KERN_FORMAT " 0\\n[" WIDTH_FORMAT "]"
	 "+\\n[" SUB_KERN_FORMAT "]>?0\n",
	 uid, pb->uid, p->uid);
}

void prime_box::output()
{
  p->output();
  pb->output();
}

void prime_box::handle_char_type(int st, int ft)
{
  p->handle_char_type(st, ft);
  pb->handle_char_type(st, ft);
}

void prime_box::debug_print()
{
  p->debug_print();
  putc('\'', stderr);
}

box *split_text(char *text)
{
  list_box *lb = 0;
  box *fb = 0;
  char *s = text;
  while (*s != '\0') {
    char c = *s++;
    box *b = 0;
    switch (c) {
    case '+':
      b = new special_char_box("pl");
      break;
    case '-':
      b = new special_char_box("mi");
      break;
    case '=':
      b = new special_char_box("eq");
      break;
    case '\'':
      b = new special_char_box("fm");
      break;
    case '<':
      if (*s == '=') {
	b = new special_char_box("<=");
	s++;
	break;
      }
      goto normal_char;
    case '>':
      if (*s == '=') {
	b = new special_char_box(">=");
	s++;
	break;
      }
      goto normal_char;
    case '\\':
      if (*s == '\0') {
	lex_error("bad escape");
	break;
      }
      c = *s++;
      switch (c) {
      case '(':
	{
	  char buf[3];
	  if (*s != '\0') {
	    buf[0] = *s++;
	    if (*s != '\0') {
	      buf[1] = *s++;
	      buf[2] = '\0';
	      b = new special_char_box(buf);
	    }
	    else {
	      lex_error("bad escape");
	    }
	  }
	  else {
	    lex_error("bad escape");
	  }
	}
	break;
      case '[':
	char *ch = s;
	while (*s != ']' && *s != '\0')
	  s++;
	if (*s == '\0')
	  lex_error("bad escape");
	else {
	  *s++ = '\0';
	  b = new special_char_box(ch);
	}
	break;
      case 'f':
      case 'g':
      case 'k':
      case 'n':
      case '*':
	{
	  char *escape_start = s - 2;
	  switch (*s) {
	  case '(':
	    if (*++s != '\0')
	      ++s;
	    break;
	  case '[':
	    for (++s; *s != '\0' && *s != ']'; s++)
	      ;
	    break;
	  }
	  if (*s == '\0')
	    lex_error("bad escape");
	  else {
	    ++s;
	    char *buf = new char[s - escape_start + 1];
	    memcpy(buf, escape_start, s - escape_start);
	    buf[s - escape_start] = '\0';
	    b = new quoted_text_box(buf);
	  }
	}
	break;
      case '-':
      case '_':
	{
	  char buf[2];
	  buf[0] = c;
	  buf[1] = '\0';
	  b = new special_char_box(buf);
	}
	break;
      case '`':
	b = new special_char_box("ga");
	break;
      case '\'':
	b = new special_char_box("aa");
	break;
      case 'e':
      case '\\':
	b = new char_box('\\');
	break;
      case '^':
      case '|':
      case '0':
	{
	  char buf[3];
	  buf[0] = '\\';
	  buf[1] = c;
	  buf[2] = '\0';
	  b = new quoted_text_box(strsave(buf));
	  break;
	}
      default:
	lex_error("unquoted escape");
	b = new quoted_text_box(strsave(s - 2));
	s = strchr(s, '\0');
	break;
      }
      break;
    default:
    normal_char:
      b = new char_box(c);
      break;
    }
    while (*s == '\'') {
      if (b == 0)
	b = new quoted_text_box(0);
      b = new prime_box(b);
      s++;
    }
    if (b != 0) {
      if (lb != 0)
	lb->append(b);
      else if (fb != 0) {
	lb = new list_box(fb);
	lb->append(b);
      }
      else
	fb = b;
    }
  }
  delete text;
  if (lb != 0)
    return lb;
  else if (fb != 0)
    return fb;
  else
    return new quoted_text_box(0);
}

