// -*- 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 "driver.h"

#ifndef USHRT_MAX
#define USHRT_MAX 65535
#endif

#define DEFAULT_LINES_PER_PAGE 66

#define TAB_WIDTH 8

static int horizontal_tab_flag = 0;
static int form_feed_flag = 0;
static int bold_flag = 1;
static int underline_flag = 1;
static int overstrike_flag = 1;

enum { UNDERLINE_MODE = 01, BOLD_MODE = 02 };

// Mode to use for bold-underlining.
static unsigned char bold_underline_mode = BOLD_MODE|UNDERLINE_MODE;

class tty_font : public font {
  tty_font(const char *);
  unsigned char mode;
public:
  ~tty_font();
  unsigned char get_mode() { return mode; }
#if 0
  void handle_x_command(int argc, const char **argv);
#endif
  static tty_font *load_tty_font(const char *);
};

tty_font *tty_font::load_tty_font(const char *s)
{
  tty_font *f = new tty_font(s);
  if (!f->load()) {
    delete f;
    return 0;
  }
  const char *num = f->get_internal_name();
  long n;
  if (num != 0 && (n = strtol(num, 0, 0)) != 0)
    f->mode = int(n & (BOLD_MODE|UNDERLINE_MODE));
  if (!underline_flag)
    f->mode &= ~UNDERLINE_MODE;
  if (!bold_flag)
    f->mode &= ~BOLD_MODE;
  if ((f->mode & (BOLD_MODE|UNDERLINE_MODE)) == (BOLD_MODE|UNDERLINE_MODE))
    f->mode = (f->mode & ~(BOLD_MODE|UNDERLINE_MODE)) | bold_underline_mode;
  return f;
}

tty_font::tty_font(const char *nm)
: font(nm), mode(0)
{
}

tty_font::~tty_font()
{
}

#if 0
void tty_font::handle_x_command(int argc, const char **argv)
{
  if (argc >= 1 && strcmp(argv[0], "bold") == 0)
    mode |= BOLD_MODE;
  else if (argc >= 1 && strcmp(argv[0], "underline") == 0)
    mode |= UNDERLINE_MODE;
}
#endif

// hpos and vpos must be non-adjacent, to work round a bug in g++ 1.37.1

struct glyph {
  unsigned short hpos;
  unsigned short serial;
  unsigned short vpos;
  unsigned char code;
  unsigned char mode;
};

class tty_printer : public printer {
  enum { INITIAL_VEC_SIZE = 32 };
  glyph *vec;
  int vec_used;
  int vec_size;
  int lines_per_page;
  int columns_per_page;
public:
  tty_printer();
  ~tty_printer();
  void set_char(int, font *, const environment *, int);
  void begin_page(int) { }
  void end_page();
  font *make_font(const char *);
};

tty_printer::tty_printer()
: vec_used(0), vec_size(0), vec(0)
{
  if (font::paperlength == 0)
    lines_per_page = DEFAULT_LINES_PER_PAGE;
  else if (font::paperlength % font::vert != 0)
    fatal("paperlength not a multiple of vertical resolution");
  else
    lines_per_page = font::paperlength/font::vert;
  if (lines_per_page > USHRT_MAX || lines_per_page <= 0)
    fatal("ridiculous paperlength");
  columns_per_page = font::paperwidth/font::hor;
  // If columns_per_page is zero, we won't truncate.
  if (columns_per_page < 0)
    columns_per_page = 0;
}

tty_printer::~tty_printer()
{
  delete vec;
}

void tty_printer::set_char(int i, font *f, const environment *env, int w)
{
  int h = env->hpos;
  if (h % font::hor != 0)
    fatal("horizontal position not a multiple of horizontal resolution");
  h /= font::hor;
  if (h < 0) {
    error("character to the left of first column discarded");
    return;
  }
  if (columns_per_page != 0 && h >= columns_per_page) {
    error("character to the right of last column discarded");
    return;
  }
  if (h > USHRT_MAX) {
    error("character with ridiculously large horizontal position discarded");
    return;
  }
  int v = env->vpos;
  if (v % font::vert != 0)
    fatal("vertical position not a multiple of vertical resolution");
  v /= font::vert;
  // Note that the first output line corresponds to groff position font::vert.
  if (v <= 0) {
    error("character above first line discarded");
    return;
  }
  if (v > lines_per_page) {
    error("character below last line discarded");
    return;
  }
  if (w != font::hor)
    fatal("width of character not equal to horizontal resolution");
  if (vec_used >= vec_size) {
    if (vec_size == 0)
      vec_size = INITIAL_VEC_SIZE;
    else {
      if (vec_size > USHRT_MAX/2) {
	if (vec_size >= USHRT_MAX)
	  fatal("too many characters on the page");
	vec_size = USHRT_MAX;
      }
      else
	vec_size *= 2;
    }
    glyph *old_vec = vec;
    vec = new glyph [vec_size];
    if (vec_used)
      memcpy(vec, old_vec, vec_used*sizeof(glyph));
    delete old_vec;
  }
  // We need a stable sort, but qsort is not stable, so we fake it.
  vec[vec_used].serial = vec_used;
  vec[vec_used].hpos = h;
  vec[vec_used].vpos = v;
  vec[vec_used].code = f->get_code(i);
  vec[vec_used].mode = ((tty_font *)f)->get_mode();
  vec_used++;
}

extern "C" {
static int compare_glyph(void *p1, void *p2)
{
  int v1 = ((glyph *)p1)->vpos;
  int v2 = ((glyph *)p2)->vpos;
  if (v1 < v2)
    return -1;
  if (v1 > v2)
    return 1;
  int h1 = ((glyph *)p1)->hpos;
  int h2 = ((glyph *)p2)->hpos;
  if (h1 < h2)
    return -1;
  if (h1 > h2)
    return 1;
  if (((glyph *)p1)->serial < ((glyph *)p2)->serial)
    return -1;
  return 1;
}
}

void tty_printer::end_page()
{
  qsort(vec, vec_used, sizeof(glyph), compare_glyph);
  int hpos = 0;
  int vpos = 1;
  // We have already discarded characters with vpos < 1 or > lines_per_page.
  for (int i = 0; i < vec_used; i++) {
    assert(vpos <= vec[i].vpos);
    if (!overstrike_flag
	&& i + 1 < vec_used
	&& vec[i].hpos == vec[i + 1].hpos
	&& vec[i].vpos == vec[i + 1].vpos)
      continue;
    for (; vpos < vec[i].vpos; vpos++) {
      putchar('\n');
      hpos = 0;
    }
    if (hpos > vec[i].hpos) {
      putchar('\b');
      hpos--;
    }
    else {
      if (horizontal_tab_flag) {
	for (;;) {
	  int next_tab_pos = ((hpos + TAB_WIDTH) / TAB_WIDTH) * TAB_WIDTH;
	  if (next_tab_pos > vec[i].hpos)
	    break;
	  putchar('\t');
	  hpos = next_tab_pos;
	}
      }
      for (; hpos < vec[i].hpos; hpos++)
	putchar(' ');
    }
    assert(hpos == vec[i].hpos && vpos == vec[i].vpos);
    if (vec[i].mode & UNDERLINE_MODE) {
      putchar('_');
      putchar('\b');
    }
    if (vec[i].mode & BOLD_MODE) {
      putchar(vec[i].code);
      putchar('\b');
    }
    putchar(vec[i].code);
    hpos++;
  }
  if (form_feed_flag) {
    if (hpos != 0) {
      putchar('\n');
      vpos++;
    }
    if (vpos <= lines_per_page)
      putchar('\f');
  }
  else {
    for (; vpos <= lines_per_page; vpos++)
      putchar('\n');
  }
  vec_used = 0;
}

font *tty_printer::make_font(const char *nm)
{
  return tty_font::load_tty_font(nm);
}

printer *make_printer()
{
  return new tty_printer;
}

static void usage();

int main(int argc, char **argv)
{
  program_name = argv[0];
  static char stderr_buf[BUFSIZ];
  setbuf(stderr, stderr_buf);
  int c;
  while ((c = getopt(argc, argv, "F:vhfbuoBU")) != EOF)
    switch(c) {
    case 'v':
      {
	extern const char *version_string;
	fprintf(stderr, "grotty version %s\n", version_string);
	fflush(stderr);
	break;
      }
    case 'b':
      // Do not embolden by overstriking.
      bold_flag = 0;
      break;
    case 'u':
      // Do not underline.
      underline_flag = 0;
      break;
    case 'o':
      // Do not overstrike (other than emboldening and underlining).
      overstrike_flag = 0;
      break;
    case 'B':
      // Do bold-underlining as bold.
      bold_underline_mode = BOLD_MODE;
      break;
    case 'U':
      // Do bold-underlining as underlining.
      bold_underline_mode = UNDERLINE_MODE;
      break;
    case 'h':
      // Use horizontal tabs.
      horizontal_tab_flag = 1;
      break;
    case 'f':
      form_feed_flag = 1;
      break;
    case 'F':
      font::command_line_font_dir(optarg);
      break;
    case '?':
      usage();
      break;
    default:
      assert(0);
    }
  if (optind >= argc)
    do_file("-");
  else {
    for (int i = optind; i < argc; i++)
      do_file(argv[i]);
  }
  delete pr;
  exit(0);
}

static void usage()
{
  fprintf(stderr, "usage: %s [-hfvbuoBU] [-F dir] [files ...]\n",
	  program_name);
  exit(1);
}
