/*
 * Copyright (c) 2002 Billy Biggs <vektor@dumbterm.net>.
 * Copyright (c) 2002 Doug Bell <drbell@users.sourceforge.net>.
 *
 * Modified and adapted to GStreamer by
 * David I. Lehn <dlehn@users.sourceforge.net>
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
/*#include "osdtools.h"*/
/*#include "speedy.h"*/
#include <glib.h>
#include "vbiscreen.h"
#include "gstvbidec.h"

#define ROLL_2      6
#define ROLL_3      7
#define ROLL_4      8
#define POP_UP      9
#define PAINT_ON    10


#define NUM_LINES  15
#define ROWS       15
#define COLS       32
#define FONT_SIZE  20

typedef struct osd_string_s osd_string_t;
struct osd_string_s
{
  int width;
  int height;
  int r, g, b;
  int visible;
  GstVBIDec *vbidec;
};

osd_string_t *
osd_string_new (char *c, int s, int w, int h, int a, void *user_data)
{
  osd_string_t *os;

  os = (osd_string_t *) malloc (sizeof (osd_string_t));
  if (!os)
    return NULL;
  os->width = 0;
  os->height = 0;
  os->r = os->g = os->b = 0;
  os->visible = 1;
  os->vbidec = (GstVBIDec *) user_data;
  return os;
}

void
osd_string_show_text (osd_string_t * os, char *s, int len)
{
  /* FIXME: just print data when it gets here */
  if (len > 0) {
    gst_vbidec_show_text (os->vbidec, s, len);
  }
}
int
osd_string_get_height (osd_string_t * os)
{
  return os->height;
}

int
osd_string_get_width (osd_string_t * os)
{
  return os->width;
}

void
osd_string_delete (osd_string_t * os)
{
  free (os);
}

void
osd_string_set_colour_rgb (osd_string_t * os, int r, int g, int b)
{
  os->r = r;
  os->g = g;
  os->b = b;
}

void
blit_colour_packed422_scanline (unsigned char *d, int w, int luma, int cb,
    int cr)
{
}
int
osd_string_visible (osd_string_t * os)
{
  return os->visible;
}

void
osd_string_composite_packed422_scanline (osd_string_t * os, unsigned char *a,
    unsigned char *b, int w, int x, int y)
{
}

struct vbiscreen_s
{

  osd_string_t *line[ROWS];

  char buffers[ROWS * COLS * 2];
  char text[2 * ROWS * COLS];
  char hiddenbuf[COLS];
  char paintbuf[ROWS * COLS];

  unsigned int fgcolour;
  unsigned int bgcolour;
  int bg_luma, bg_cb, bg_cr;

  int frame_width;
  int frame_height;
  int frame_aspect;

  int x, y;                     /* where to draw console */
  int width, height;            /* the size box we have to draw in */
  int rowheight, charwidth;

  int curx, cury;               /* cursor position */
  int rows, cols;               /* 32 cols 15 rows */
  int captions, style;          /* CC (1) or Text (0), RU2 RU3 RU4 POP_UP PAINT_ON */
  int first_line;               /* where to start drawing */
  int curbuffer;
  int top_of_screen;            /* a pointer into line[] */
  int indent;
  int got_eoc;
  int scroll;

  char *fontfile;
  int fontsize;
  int verbose;

  void *user_data;
};

vbiscreen_t *
vbiscreen_new (int video_width, int video_height,
    double video_aspect, int verbose, void *user_data)
{
  int i = 0, fontsize = FONT_SIZE;
  vbiscreen_t *vs = (vbiscreen_t *) malloc (sizeof (struct vbiscreen_s));

  if (!vs) {
    return NULL;
  }

  vs->verbose = verbose;
  vs->x = 0;
  vs->y = 0;
  vs->frame_width = video_width;
  vs->frame_height = video_height;
  vs->frame_aspect = video_aspect;
  vs->curx = 0;
  vs->cury = 0;
  vs->fgcolour = 0xFFFFFFFFU;   /* white */
  vs->bgcolour = 0xFF000000U;   /* black */
  vs->bg_luma = 16;
  vs->bg_cb = 128;
  vs->bg_cr = 128;
  vs->rows = ROWS;
  vs->cols = COLS;
  /*vs->fontfile = DATADIR "/FreeMonoBold.ttf"; */
  vs->fontfile = NULL;
  vs->fontsize = fontsize;
  vs->width = video_width;
  vs->height = video_height;
  vs->first_line = 0;
  vs->captions = 0;
  vs->style = 0;
  vs->curbuffer = 0;
  vs->top_of_screen = 0;
  vs->indent = 0;
  memset (vs->buffers, 0, 2 * COLS * ROWS);
  memset (vs->hiddenbuf, 0, COLS);
  memset (vs->paintbuf, 0, ROWS * COLS);
  vs->scroll = 0;

  vs->user_data = user_data;

  vs->line[0] = osd_string_new (vs->fontfile, fontsize, video_width,
      video_height, video_aspect, user_data);

  if (!vs->line[0]) {
    vs->fontfile = "./FreeMonoBold.ttf";

    vs->line[0] = osd_string_new (vs->fontfile, fontsize,
        video_width, video_height, video_aspect, user_data);
  }

  if (!vs->line[0]) {
    fprintf (stderr, "vbiscreen: Could not find my font (%s)!\n", vs->fontfile);
    vbiscreen_delete (vs);
    return NULL;
  }

  osd_string_show_text (vs->line[0], "W", 0);
  vs->rowheight = osd_string_get_height (vs->line[0]);
  vs->charwidth = osd_string_get_width (vs->line[0]);
  osd_string_delete (vs->line[0]);

  for (i = 0; i < ROWS; i++) {
    vs->line[i] = osd_string_new (vs->fontfile, fontsize,
        video_width, video_height, video_aspect, user_data);
    if (!vs->line[i]) {
      fprintf (stderr, "vbiscreen: Could not allocate a line.\n");
      vbiscreen_delete (vs);
      return NULL;
    }
    osd_string_set_colour_rgb (vs->line[i],
        (vs->fgcolour & 0xff0000) >> 16,
        (vs->fgcolour & 0xff00) >> 8, (vs->fgcolour & 0xff));
    osd_string_show_text (vs->line[i], " ", 0);
  }
  memset (vs->text, 0, 2 * ROWS * COLS);
  return vs;
}

void
blank_screen (vbiscreen_t * vs)
{
  int i;

  if (vs->verbose)
    fprintf (stderr, "in blank\n");
  for (i = 0; i < ROWS; i++) {
    osd_string_show_text (vs->line[i], " ", 0);
  }
}

void
clear_screen (vbiscreen_t * vs)
{
  int base, i;

  if (!vs)
    return;

  base = vs->top_of_screen * COLS;
  for (i = 0; i < ROWS * COLS; i++) {
    vs->text[base] = 0;
    base++;
    base %= 2 * ROWS * COLS;
  }
  blank_screen (vs);
}

void
clear_hidden_roll (vbiscreen_t * vs)
{
  if (!vs)
    return;
  memset (vs->hiddenbuf, 0, COLS);
}

void
clear_hidden_pop (vbiscreen_t * vs)
{
  if (!vs)
    return;
  memset (vs->buffers + vs->curbuffer * COLS * ROWS, 0, COLS * ROWS);
}

void
clear_hidden_paint (vbiscreen_t * vs)
{
  if (!vs)
    return;
  memset (vs->paintbuf, 0, COLS * ROWS);
}

void
clear_displayed_pop (vbiscreen_t * vs)
{
  if (!vs)
    return;
  memset (vs->buffers + (vs->curbuffer ^ 1) * COLS * ROWS, 0, COLS * ROWS);
}

void
vbiscreen_dump_screen_text (vbiscreen_t * vs)
{
  int i, offset;

  if (!vs)
    return;
  offset = vs->top_of_screen * COLS;

  fprintf (stderr, "\n   0123456789abcdefghij012345678901");
  for (i = 0; i < ROWS * COLS; i++) {
    if (!(i % COLS))
      fprintf (stderr, "\n%.2d ", i / COLS);
    fprintf (stderr, "%c", vs->text[offset] ? vs->text[offset] : ' ');
    offset++;
    offset %= 2 * ROWS * COLS;
  }
  fprintf (stderr, "\n   0123456789abcdefghij012345678901\n   ");
  for (i = 0; i < COLS; i++) {
    fprintf (stderr, "%c", vs->text[offset] ? vs->text[offset] : ' ');
    offset++;
    offset %= 2 * ROWS * COLS;
  }
  fprintf (stderr, "\n   0123456789abcdefghij012345678901\n");
}

int
update_row_x (vbiscreen_t * vs, int row)
{
  char text[COLS + 1];
  int i, j, haschars = 0, base;

  if (!vs)
    return 0;

  text[COLS] = 0;
  base = ((vs->top_of_screen + row) % (2 * ROWS)) * COLS;
  for (j = 0, i = base; i < base + COLS; i++, j++) {
    if (vs->text[i]) {
      text[j] = vs->text[i];
      haschars = 1;
    } else {
      text[j] = ' ';
    }
  }

  osd_string_set_colour_rgb (vs->line[row],
      (vs->fgcolour & 0xff0000) >> 16,
      (vs->fgcolour & 0xff00) >> 8, (vs->fgcolour & 0xff));
  if (!haschars)
    osd_string_show_text (vs->line[row], " ", 0);
  else
    osd_string_show_text (vs->line[row], text, 51);

  return haschars;
}

void
update_row (vbiscreen_t * vs)
{
  if (!vs)
    return;

  update_row_x (vs, vs->cury);
  //vbiscreen_dump_screen_text( vs );
}

void
update_all_rows (vbiscreen_t * vs)
{
  int row = 0;

  if (!vs)
    return;

  for (row = 0; row < ROWS; row++) {
    update_row_x (vs, row);
  }
  //vbiscreen_dump_screen_text( vs );
}

void
vbiscreen_delete (vbiscreen_t * vs)
{
  free (vs);
}

void
copy_row_to_screen (vbiscreen_t * vs, char *row)
{
  int base, i, j;

  base = ((vs->top_of_screen + vs->cury) % (2 * ROWS)) * COLS;
  for (j = 0, i = base; i < base + COLS; j++, i++) {
    vs->text[i] = row[j];
  }
  update_row (vs);
}

void
scroll_screen (vbiscreen_t * vs)
{
  int start_row;

  if (!vs || !vs->captions || !vs->style || vs->style > ROLL_4)
    return;

  start_row = (vs->first_line + vs->top_of_screen) % (2 * ROWS);
  if (vs->verbose)
    fprintf (stderr, "start row : %d first line %d\n ", start_row,
        vs->first_line);

  /* zero out top row */
  memset ((char *) (vs->text + start_row * COLS), 0, COLS);
  vs->top_of_screen = (vs->top_of_screen + 1) % (2 * ROWS);
  vs->curx = vs->indent;
  update_all_rows (vs);
  copy_row_to_screen (vs, vs->hiddenbuf);
  clear_hidden_roll (vs);
  vs->scroll = 26;
}

void
vbiscreen_set_verbose (vbiscreen_t * vs, int verbose)
{
  vs->verbose = verbose;
}

void
vbiscreen_new_caption (vbiscreen_t * vs, int indent, int ital,
    unsigned int colour, int row)
{
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "indent: %d, ital: %d, colour: 0x%x, row: %d\n", indent,
        ital, colour, row);

  if (0 && vs->captions && vs->style <= ROLL_4 && vs->style) {
    if (row != vs->cury + 1) {
      vs->cury = row - 1;
      clear_hidden_roll (vs);
    } else {
//            scroll_screen( vs );
    }
  }

  if (vs->style > ROLL_4) {
    vs->cury = ((row > 0) ? row - 1 : 0);
  }

  vs->fgcolour = colour;
  vs->indent = indent;
  vs->curx = indent;
}

void
vbiscreen_set_mode (vbiscreen_t * vs, int caption, int style)
{
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in set mode\n");

  if (vs->verbose) {
    fprintf (stderr, "Caption: %d ", caption);
    switch (style) {
      case ROLL_2:
        fprintf (stderr, "ROLL 2\n");
        break;
      case ROLL_3:
        fprintf (stderr, "ROLL 3\n");
        break;
      case ROLL_4:
        fprintf (stderr, "ROLL 4\n");
        break;
      case POP_UP:
        fprintf (stderr, "POP UP\n");
        break;
      case PAINT_ON:
        fprintf (stderr, "PAINT ON\n");
        break;
      default:
        break;
    }
  }
  if (!caption) {
    /* text mode */
    vs->cury = 0;
  } else {
    /* captioning mode */
    /* styles: ru2 ru3 ru4 pop paint
     */
    if (style != POP_UP && vs->style == POP_UP && !vs->got_eoc) {
      /* stupid that sometimes they dont send a EOC */
      vbiscreen_end_of_caption (vs);
    }

    switch (style) {
      case ROLL_2:
      case ROLL_3:
      case ROLL_4:
        if (vs->style == style) {
          return;
        }
        vs->first_line = ROWS - (style - 4);

        if (vs->verbose)
          fprintf (stderr, "first_line %d\n", vs->first_line);

        vs->cury = ROWS - 1;
        break;
      case POP_UP:
        vs->got_eoc = 0;
        break;
      case PAINT_ON:
        break;
    }
  }

  vs->captions = caption;
  vs->style = style;
}

void
vbiscreen_tab (vbiscreen_t * vs, int cols)
{
  if (!vs)
    return;
  if (cols < 0 || cols > 3)
    return;
  vs->curx += cols;
  if (vs->curx > 31)
    vs->curx = 31;
}

void
vbiscreen_set_colour (vbiscreen_t * vs, unsigned int col)
{
  if (!vs)
    return;
  vs->fgcolour = col;
}

void
vbiscreen_clear_current_cell (vbiscreen_t * vs)
{
  vs->text[((vs->top_of_screen + vs->cury) % (2 * ROWS)) * COLS
      + vs->curx + vs->indent] = 0;
}

void
vbiscreen_set_current_cell (vbiscreen_t * vs, char text)
{
  int base;

  if (!vs)
    return;
  base = ((vs->top_of_screen + vs->cury) % (2 * ROWS)) * COLS;
  if (g_ascii_isprint (text))
    vs->text[base + vs->curx + vs->indent] = text;
  else
    vs->text[base + vs->curx + vs->indent] = ' ';
}

void
vbiscreen_delete_to_end (vbiscreen_t * vs)
{
  int i;

  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in del to end\n");
  for (i = vs->curx; i < COLS; i++) {
    vbiscreen_clear_current_cell (vs);
    vs->curx++;
  }
  vs->curx = COLS - 1;          /* is this right ? */
  if (vs->captions && vs->style && vs->style != POP_UP)
    update_row (vs);
}

void
vbiscreen_backspace (vbiscreen_t * vs)
{
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in backspace\n");
  if (!vs->curx)
    return;
  vs->curx--;
  vbiscreen_clear_current_cell (vs);
  update_row (vs);
}

void
vbiscreen_erase_displayed (vbiscreen_t * vs)
{
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in erase disp\n");

  if (vs->captions && vs->style && vs->style <= ROLL_4) {
    clear_hidden_roll (vs);
  }

  clear_displayed_pop (vs);
  clear_screen (vs);
}

void
vbiscreen_erase_non_displayed (vbiscreen_t * vs)
{
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in erase non disp\n");

  if (vs->captions && vs->style == POP_UP) {
    memset (vs->buffers + vs->curbuffer * COLS * ROWS + vs->cury * COLS, 0,
        COLS);
//        clear_hidden_pop( vs );
  } else if (vs->captions && vs->style && vs->style <= ROLL_4) {
    clear_hidden_roll (vs);
  }
}

void
vbiscreen_carriage_return (vbiscreen_t * vs)
{
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in CR\n");
  if (vs->style != POP_UP) {
    /* not sure if this is right for text mode */
    /* in text mode, perhaps a CR on last row clears screen and goes
     * to (0,0) */
    scroll_screen (vs);
  }

  /* keep cursor on bottom for rollup */
  if (vs->captions && vs->style && vs->style <= ROLL_4)
    vs->cury--;

  vs->cury++;
  vs->curx = 0;
}

void
copy_buf_to_screen (vbiscreen_t * vs, char *buf)
{
  int base, i, j;

  if (!vs)
    return;

  base = vs->top_of_screen * COLS;
  for (j = 0, i = 0; i < ROWS * COLS; i++, j++) {
    vs->text[base] = buf[j];
    base++;
    base %= 2 * ROWS * COLS;
  }
  update_all_rows (vs);
}

void
vbiscreen_end_of_caption (vbiscreen_t * vs)
{
  /*int i; */
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in end of caption\n");

  if (vs->style == PAINT_ON) {
    copy_buf_to_screen (vs, vs->paintbuf);
    clear_hidden_paint (vs);
  } else if (vs->style == POP_UP) {
    copy_buf_to_screen (vs, vs->buffers + vs->curbuffer * COLS * ROWS);
    vs->curbuffer ^= 1;
  }

  /* to be safe? */
  vs->curx = 0;
  vs->cury = ROWS - 1;
  vs->got_eoc = 1;
}

void
vbiscreen_print (vbiscreen_t * vs, char c1, char c2)
{
  if (!vs)
    return;
  if (vs->verbose)
    fprintf (stderr, "in print (%d, %d)[%c %c]\n", vs->curx, vs->cury, c1, c2);
  if (vs->captions && vs->style == POP_UP) {
    /* this all gets displayed at another time */
    if (vs->curx != COLS - 1) {
      *(vs->buffers + vs->curx + vs->curbuffer * ROWS * COLS +
          vs->cury * COLS) = c1;
      vs->curx++;
    }

    if (vs->curx != COLS - 1 && c2) {
      *(vs->buffers + vs->curx + vs->curbuffer * ROWS * COLS +
          vs->cury * COLS) = c2;
      vs->curx++;
    } else if (c2) {
      *(vs->buffers + vs->curx + vs->curbuffer * ROWS * COLS +
          vs->cury * COLS) = c2;
    }
  }

  if (vs->captions && vs->style == PAINT_ON) {
    if (vs->curx != COLS - 1) {
      vs->paintbuf[vs->curx + vs->cury * COLS] = c1;
      vs->curx++;
    }

    if (vs->curx != COLS - 1 && c2) {
      vs->paintbuf[vs->curx + vs->cury * COLS] = c2;
      vs->curx++;
    } else if (c2) {
      vs->paintbuf[vs->curx + vs->cury * COLS] = c2;
    }
  }

  if (vs->captions && vs->style && vs->style <= ROLL_4) {
    if (vs->curx != COLS - 1) {
      vs->hiddenbuf[vs->curx] = c1;
      vs->curx++;
    } else {
      vs->hiddenbuf[vs->curx] = c1;
    }

    if (vs->curx != COLS - 1 && c2) {
      vs->hiddenbuf[vs->curx] = c2;
      vs->curx++;
    } else if (c2) {
      vs->hiddenbuf[vs->curx] = c2;
    }
  }
}

void
vbiscreen_reset (vbiscreen_t * vs)
{
  if (!vs)
    return;
  clear_screen (vs);
  clear_hidden_pop (vs);
  clear_displayed_pop (vs);
  clear_hidden_roll (vs);
  vs->captions = 0;
  vs->style = 0;
}

void
vbiscreen_composite_packed422_scanline (vbiscreen_t * vs,
    unsigned char *output, int width, int xpos, int scanline)
{
  int x = 0, y = 0, row = 0, index = 0;

  if (!vs)
    return;
  if (!output)
    return;
  if (scanline >= vs->y && scanline < vs->y + vs->height) {

    if (0 && !vs->captions)
      blit_colour_packed422_scanline (output + (vs->x * 2), vs->width,
          vs->bg_luma, vs->bg_cb, vs->bg_cr);

    index = vs->top_of_screen * COLS;
    x = (vs->x + vs->charwidth) & ~1;
    for (row = 0; row < ROWS; row++) {
      y = vs->y + row * vs->rowheight + vs->rowheight;
      if (osd_string_visible (vs->line[row])) {
        if (scanline >= y && scanline < y + vs->rowheight) {

          int startx;
          int strx;

          startx = x - xpos;
          strx = 0;

          if (startx < 0) {
            strx = -startx;
            startx = 0;
          }


          if (startx < width) {

            if (vs->captions)
              blit_colour_packed422_scanline (output + (startx * 2),
                  osd_string_get_width (vs->line[row]),
                  vs->bg_luma, vs->bg_cb, vs->bg_cr);

            osd_string_composite_packed422_scanline (vs->line[row],
                output + (startx * 2),
                output + (startx * 2), width - startx, strx, scanline - y);
          }
        }
        index++;
      }
    }
  }
}