/*
 * Copyright (c) 2002 Billy Biggs <vektor@dumbterm.net>.
 * Copyright (c) 2002 Doug Bell <drbell@users.sourceforge.net>
 *
 * CC code from Nathan Laredo's ccdecode, used under the GPL.
 * Lots of 'hey what does this mean?' code from
 * Billy Biggs and Doug Bell, like all the crap with
 * XDS and stuff.  Some help from Zapping's vbi library by
 * Michael H. Schimek and others, released under the GPL.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include "vbidata.h"
#include "vbiscreen.h"
/*#include "tvtimeosd.h"*/

#define DO_LINE 11
static int pll = 0;

struct vbidata_s
{
  int fd;
  vbiscreen_t *vs;
  /*tvtime_osd_t *osd; */
  char buf[65536];
  int wanttop;
  int wanttext;

  unsigned int colour;
  int row, ital;
  int indent, ul;
  int chan;

  unsigned int current_colour;
  int current_row, current_ital;
  int current_indent, current_ul;
  int current_chan;
  int current_istext;

  int initialised;
  int enabled;
  int lastcode;
  int lastcount;
  int verbose;

  /* XDS data */
  char xds_packet[2048];
  int xds_cursor;

  char *program_name;
  char *network_name;
  char *call_letters;
  const char *rating;
  const char *program_type;
  int start_day;
  int start_month;
  int start_min;
  int start_hour;
  int length_hour;
  int length_min;
  int length_elapsed_hour;
  int length_elapsed_min;
  int length_elapsed_sec;
  char *program_desc[8];
};


/* this is NOT exactly right */
//static char ccode[] = " !\"#$%&'()\0341+,-./0123456789:;<=>?@"
static const char ccode[] = " !\"#$%&'()a+,-./0123456789:;<=>?@"
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
//                     "abcdefghijklmnopqrstuvwxyz"
//                     "[\0351]\0355\0363\0372abcdefghijklmnopqr"
    "[e]iouabcdefghijklmnopqr"
//                     "stuvwxyz\0347\0367\0245\0244\0240";
    "stuvwxyzcoNn ";
static const char wccode[] = "\0256\0260\0275\0277T\0242\0243#\0340 "
    "\0350\0354\0362\0371";

static const char extcode1[] = "\0301\0311\0323\0332\0334\0374"
    "`\0241*'-\0251S*\"\"\0300\0302"
    "\0307\0310\0312\0313\0353\0316\0317\0357" "\0324\0331\0371\0333\0253\0273";

static const char extcode2[] = "\0303\0343\0315\0314\0354\0322\0362\0325"
    "{}\\^_|~\0304\0344\0326\0366\0337\0245\0244|" "\0305\0345\0330\0370++++";

int
parityok (int n)
{                               /* check parity for 2 bytes packed in n */
  int j, k;

  for (k = 0, j = 0; j < 7; j++)
    if (n & (1 << j))
      k++;
  if ((k & 1) && (n & 0x80))
    return 0;
  for (k = 0, j = 8; j < 15; j++)
    if (n & (1 << j))
      k++;
  if ((k & 1) && (n & 0x8000))
    return 0;
  return 1;
}

int
decodebit (unsigned char *data, int threshold)
{
  return ((data[0] + data[1] + data[2] + data[3] + data[4] + data[5] +
          data[6] + data[7] + data[8] + data[9] + data[10] + data[11] +
          data[12] + data[13] + data[14] + data[15] + data[16] + data[17] +
          data[18] + data[19] + data[20] + data[21] + data[22] + data[23] +
          data[24] + data[25] + data[26] + data[27] + data[28] + data[29] +
          data[30] + data[31]) >> 5 > threshold);
}


int
ccdecode (unsigned char *vbiline)
{
  int max = 0, maxval = 0, minval = 255, i = 0, clk = 0, tmp = 0;
  int sample, packedbits = 0;

  for (i = 0; i < 250; i++) {
    sample = vbiline[i];
    if (sample - maxval > 10)
      (maxval = sample, max = i);
    if (sample < minval)
      minval = sample;
    if (maxval - sample > 40)
      break;
  }
  sample = ((maxval + minval) >> 1);
  pll = max;

  /* found clock lead-in, double-check start */
#ifndef PAL_DECODE
  i = max + 478;
#else
  i = max + 538;
#endif
  if (!decodebit (&vbiline[i], sample))
    return 0;
#ifndef PAL_DECODE
  tmp = i + 57;                 /* tmp = data bit zero */
#else
  tmp = i + 71;
#endif
  for (i = 0; i < 16; i++) {
#ifndef PAL_DECODE
    clk = tmp + i * 57;
#else
    clk = tmp + i * 71;
#endif
    if (decodebit (&vbiline[clk], sample)) {
      packedbits |= 1 << i;
    }
  }
  if (parityok (packedbits))
    return packedbits;
  return 0;
}                               /* ccdecode */

const char *movies[] = { "N/A", "G", "PG", "PG-13", "R",
  "NC-17", "X", "Not Rated"
};

const char *usa_tv[] = { "Not Rated", "TV-Y", "TV-Y7", "TV-G",
  "TV-PG", "TV-14", "TV-MA", "Not Rated"
};

const char *cane_tv[] = { "Exempt", "C", "C8+", "G", "PG",
  "14+", "18+", "Reserved"
};

const char *canf_tv[] = { "Exempt", "G", "8 ans +", "13 ans +",
  "16 ans +", "18 ans +", "Reserved",
  "Reserved"
};

const char *months[] = { 0, "Jan", "Feb", "Mar", "Apr", "May",
  "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

static const char *eia608_program_type[96] = {
  "education", "entertainment", "movie", "news", "religious", "sports",
  "other", "action", "advertisement", "animated", "anthology",
  "automobile", "awards", "baseball", "basketball", "bulletin", "business",
  "classical", "college", "combat", "comedy", "commentary", "concert",
  "consumer", "contemporary", "crime", "dance", "documentary", "drama",
  "elementary", "erotica", "exercise", "fantasy", "farm", "fashion",
  "fiction", "food", "football", "foreign", "fund raiser", "game/quiz",
  "garden", "golf", "government", "health", "high school", "history",
  "hobby", "hockey", "home", "horror", "information", "instruction",
  "international", "interview", "language", "legal", "live", "local",
  "math", "medical", "meeting", "military", "miniseries", "music", "mystery",
  "national", "nature", "police", "politics", "premiere", "prerecorded",
  "product", "professional", "public", "racing", "reading", "repair", "repeat",
  "review", "romance", "science", "series", "service", "shopping",
  "soap opera", "special", "suspense", "talk", "technical", "tennis",
  "travel", "variety", "video", "weather", "western"
};


static void
parse_xds_packet (vbidata_t * vbi, char *packet, int length)
{
  int sum = 0;
  int i;

  if (!vbi)
    return;

  /* Check the checksum for validity of the packet. */
  for (i = 0; i < length - 1; i++) {
    sum += packet[i];
  }
  if ((((~sum) & 0x7f) + 1) != packet[length - 1]) {
    return;
  }

  /* Stick a null at the end, and cut off the last two characters. */
  packet[length - 2] = '\0';
  length -= 2;

  if (packet[0] == 0x01 && packet[1] == 0x03) {
    if (vbi->program_name && !strcmp (vbi->program_name, packet + 2)) {
      return;
    }
    if (vbi->verbose)
      fprintf (stderr, "Current program name: '%s'\n", packet + 2);
    if (vbi->program_name)
      free (vbi->program_name);
    vbi->program_name = strdup (packet + 2);
    /*tvtime_osd_set_show_name( vbi->osd, vbi->program_name ); */
  } else if (packet[0] == 0x03 && packet[1] == 0x03) {
    if (vbi->verbose)
      fprintf (stderr, "Future program name: '%s'\n", packet + 2);
  } else if (packet[0] == 0x05 && packet[1] == 0x01) {
    if (vbi->network_name && !strcmp (vbi->network_name, packet + 2)) {
      return;
    }

    if (vbi->verbose)
      fprintf (stderr, "Network name: '%s'\n", packet + 2);
    if (vbi->network_name)
      free (vbi->network_name);
    vbi->network_name = strdup (packet + 2);
    /*tvtime_osd_set_network_name( vbi->osd, vbi->network_name ); */
  } else if (packet[0] == 0x01 && packet[1] == 0x05) {
    int movie_rating = packet[2] & 7;
    int scheme = (packet[2] & 56) >> 3;
    int tv_rating = packet[3] & 7;
    int VSL = packet[3] & 56;
    const char *str;

    switch (VSL | scheme) {
      case 3:                  /* Canadian English TV */
        str = cane_tv[tv_rating];
        break;
      case 7:                  /* Canadian French TV */
        str = canf_tv[tv_rating];
        break;
      case 19:                 /* Reserved */
      case 31:
        str = "";
        break;
      default:
        if (((VSL | scheme) & 3) == 1) {
          /* USA TV */
          str = usa_tv[tv_rating];
        } else {
          /* MPAA Movie Rating */
          str = movies[movie_rating];
        }
        break;
    }

    if (vbi->rating && !strcmp (vbi->rating, str)) {
      return;
    }

    if (vbi->verbose)
      fprintf (stderr, "Show rating: %s", str);
    if (((VSL | scheme) & 3) == 1 || ((VSL | scheme) & 3) == 0) {
      /* show VSLD for the americans */
      if ((VSL | scheme) & 32) {
        if (vbi->verbose)
          fprintf (stderr, " V");
      }
      if ((VSL | scheme) & 16) {
        if (vbi->verbose)
          fprintf (stderr, " S");
      }
      if ((VSL | scheme) & 8) {
        if (vbi->verbose)
          fprintf (stderr, " L");
      }
      if ((VSL | scheme) & 4) {
        if (vbi->verbose)
          fprintf (stderr, " D");
      }
    }
    if (vbi->verbose)
      fprintf (stderr, "\n");
    vbi->rating = str;
    /*tvtime_osd_set_show_rating( vbi->osd, vbi->rating ); */
  } else if (packet[0] == 0x05 && packet[1] == 0x02) {
    if (vbi->call_letters && !strcmp (vbi->call_letters, packet + 2)) {
      return;
    }

    if (vbi->verbose)
      fprintf (stderr, "Network call letters: '%s'\n", packet + 2);
    if (vbi->call_letters)
      free (vbi->call_letters);
    vbi->call_letters = strdup (packet + 2);
    /*tvtime_osd_set_network_call( vbi->osd, vbi->call_letters ); */
  } else if (packet[0] == 0x01 && packet[1] == 0x01) {
    int month = packet[5];      // & 15;
    int day = packet[4];        // & 31;
    int hour = packet[3];       // & 31;
    int min = packet[2];        // & 63;
    char str[33];

    if (vbi->verbose)
      fprintf (stderr, "Program Start: %02d %s, %02d:%02d\n",
          day & 31, months[month & 15], hour & 31, min & 63);
    // packet[ 3 ], packet[ 4 ], packet[ 5 ], packet[ 6 ] );
    //packet[ 5 ] & 31, packet[ 6 ], packet[ 4 ] & 31, packet[ 3 ] & 63 );
    vbi->start_month = month & 15;
    vbi->start_day = day & 31;
    vbi->start_hour = hour & 31;
    vbi->start_min = hour & 63;
    snprintf (str, 32, "%02d %s, %02d:%02d",
        day & 31, months[month & 15], hour & 31, min & 63);
    /*tvtime_osd_set_show_start( vbi->osd, str ); */
  } else if (packet[0] == 0x01 && packet[1] == 0x04) {
    if (vbi->verbose)
      fprintf (stderr, "Program type: ");
    for (i = 0; i < length - 2; i++) {
      int cur = packet[2 + i] - 0x20;

      if (cur >= 0 && cur < 96) {
        if (vbi->verbose)
          fprintf (stderr, "%s%s", i ? ", " : "", eia608_program_type[cur]);
        /* this will cause us to keep only the last type we check */
        vbi->program_type = eia608_program_type[cur];
      }
    }
    if (vbi->verbose)
      fprintf (stderr, "\n");
  } else if (packet[0] < 0x03 && packet[1] >= 0x10 && packet[1] <= 0x17) {

    if (vbi->program_desc[packet[1] & 0xf] &&
        !strcmp (vbi->program_desc[packet[1] & 0xf], packet + 2)) {
      return;
    }

    if (vbi->verbose)
      fprintf (stderr, "Program Description: Line %d", packet[1] & 0xf);
    if (vbi->verbose)
      fprintf (stderr, "%s\n", packet + 2);
    if (vbi->program_desc[packet[1] & 0xf])
      free (vbi->program_desc[packet[1] & 0xf]);
    vbi->program_desc[packet[1] & 0xf] = strdup (packet + 2);
  } else if (packet[0] == 0x01 && packet[1] == 0x02) {
    char str[33];

    str[0] = 0;
    if (vbi->verbose)
      fprintf (stderr, "Program Length: %02d:%02d",
          packet[3] & 63, packet[2] & 63);
    vbi->length_hour = packet[3] & 63;
    vbi->length_min = packet[2] & 63;
    snprintf (str, 32, "%02d:%02d", packet[3] & 63, packet[2] & 63);
    if (length > 4) {
      if (vbi->verbose)
        fprintf (stderr, " Elapsed: %02d:%02d", packet[5] & 63, packet[4] & 63);
      vbi->length_elapsed_hour = packet[5] & 63;
      vbi->length_elapsed_min = packet[4] & 63;
      snprintf (str, 32, "%02d:%02d/%02d:%02d",
          packet[5] & 63, packet[4] & 63, packet[3] & 63, packet[2] & 63);
    } else {
      vbi->length_elapsed_hour = 0;
      vbi->length_elapsed_min = 0;
    }

    if (length > 6) {
      if (vbi->verbose)
        fprintf (stderr, ".%02d", packet[6] & 63);
      vbi->length_elapsed_hour = packet[6] & 63;
      snprintf (str, 32, "%02d:%02d.%02d/%02d:%02d",
          packet[5] & 63, packet[4] & 63, packet[6] & 63,
          packet[3] & 63, packet[2] & 63);
    } else {
      vbi->length_elapsed_hour = 0;
    }
    /*tvtime_osd_set_show_length( vbi->osd, str ); */
    if (vbi->verbose)
      fprintf (stderr, "\n");
  } else if (packet[0] == 0x05 && packet[1] == 0x04) {
    if (vbi->verbose)
      fprintf (stderr, "Transmission Signal Identifier (TSID): 0x%04x\n",
          packet[2] << 24 | packet[3] << 16 | packet[4] << 8 | packet[5]);
  } else {
    /* unknown */

    if (vbi->verbose)
      fprintf (stderr, "Unknown XDS packet, class ");
    switch (packet[0]) {
      case 0x1:
        if (vbi->verbose)
          fprintf (stderr, "CURRENT start\n");
        break;
      case 0x2:
        if (vbi->verbose)
          fprintf (stderr, "CURRENT continue\n");
        break;

      case 0x3:
        if (vbi->verbose)
          fprintf (stderr, "FUTURE start\n");
        break;
      case 0x4:
        if (vbi->verbose)
          fprintf (stderr, "FUTURE continue\n");
        break;

      case 0x5:
        if (vbi->verbose)
          fprintf (stderr, "CHANNEL start\n");
        break;
      case 0x6:
        if (vbi->verbose)
          fprintf (stderr, "CHANNEL continue\n");
        break;

      case 0x7:
        if (vbi->verbose)
          fprintf (stderr, "MISC start\n");
        break;
      case 0x8:
        if (vbi->verbose)
          fprintf (stderr, "MISC continue\n");
        break;

      case 0x9:
        if (vbi->verbose)
          fprintf (stderr, "PUB start\n");
        break;
      case 0xa:
        if (vbi->verbose)
          fprintf (stderr, "PUB continue\n");
        break;

      case 0xb:
        if (vbi->verbose)
          fprintf (stderr, "RES start\n");
        break;
      case 0xc:
        if (vbi->verbose)
          fprintf (stderr, "RES continue\n");
        break;

      case 0xd:
        if (vbi->verbose)
          fprintf (stderr, "UNDEF start\n");
        break;
      case 0xe:
        if (vbi->verbose)
          fprintf (stderr, "UNDEF continue\n");
        break;
    }
    for (i = 0; i < length; i++) {
      if (vbi->verbose)
        fprintf (stderr, "0x%02x ", packet[i]);
    }
    if (vbi->verbose)
      fprintf (stderr, "\n");
  }
}

static int
xds_decode (vbidata_t * vbi, int b1, int b2)
{
  if (!vbi)
    return 0;
  if (vbi->xds_cursor > 2046) {
    vbi->xds_cursor = 0;
  }

  if (!vbi->xds_cursor && b1 > 0xf) {
    return 0;
  }


  if (b1 < 0xf && (b1 & 0x2)) {
    /* ignore the continue and thus 'support' continuation of
       a single packet */
    return 1;
  } else if (b1 < 0xf) {
    /* kill old packet cause we got a new one */
    vbi->xds_cursor = 0;
  }

  vbi->xds_packet[vbi->xds_cursor] = b1;
  vbi->xds_packet[vbi->xds_cursor + 1] = b2;
  vbi->xds_cursor += 2;

  if (b1 == 0xf) {
    parse_xds_packet (vbi, vbi->xds_packet, vbi->xds_cursor);
    vbi->xds_cursor = 0;
  }

  return 1;
}

#define NOMODE  0

#define CC1     1
#define CC2     2
#define T1      3
#define T2      4

#define CC3     1
#define CC4     2
#define T3      3
#define T4      4

const unsigned int colours[] = {
  0xFFFFFFFFU,                  /* white */
  0xFF00FF00U,                  /* green */
  0xFF0000FFU,                  /* blue */
  0xFF00C7C7U,                  /* cyan */
  0xFFFF0000U,                  /* red */
  0xFFFFFF00U,                  /* yellow */
  0xFFC700C7U                   /* magenta */
};

const int rows[] = {
  11,
  0,                            /* unused */
  1,
  2,
  3,
  4,
  12,
  13,
  14,
  15,
  5,
  6,
  7,
  8,
  9,
  10
};

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


static int
Process16b (vbidata_t * vbi, int bottom, int w1)
{
  int b1, b2;

  b1 = w1 & 0x7f;
  b2 = (w1 >> 8) & 0x7f;

  if (!b1 && !b2) {
    return 0;
  }

  if (vbi->enabled && b1 >= 0x10 && b1 <= 0x1F && b2 >= 0x20 && b2 <= 0x7F) {
    int code;

    if ((b2 & 64)) {
      /* Preamble Code */
      /* This sets up colors and indenting */

      if (!bottom && vbi->lastcode == ((b1 << 8) | b2)) {
        vbi->lastcount = (vbi->lastcount + 1) % 2;
        return 1;
      }

      vbi->current_chan = (b1 & 8) >> 3;
      if (!bottom == vbi->wanttop) {
        if (vbi->chan != vbi->current_chan)
          return 0;
      } else
        return 0;

      vbi->current_ital = (b2 & 1);
      if (!(b2 & 16)) {
        vbi->current_colour = colours[(b2 & 30) >> 1];
        vbi->current_indent = 0;
      } else {
        vbi->current_colour = 0xFFFFFFFFU;      /* white */
        vbi->current_indent = 4 * ((b2 & 14) >> 1);
      }
      vbi->current_row = rows[((b1 & 7) << 1) | ((b2 & 32) >> 5)];
      vbi->current_ul = b2 & 1;

      if (vbi->verbose)
        fprintf (stderr, "field: %d chan %d, ital %d, ul %d, colour 0x%x, "
            "indent %d, row %d\n", bottom, vbi->current_chan,
            vbi->current_ital, vbi->current_ul, vbi->current_colour,
            vbi->current_indent, vbi->current_row);

      if (!bottom == vbi->wanttop &&
          vbi->current_chan == vbi->chan &&
          vbi->current_istext == vbi->wanttext) {

        vbi->indent = vbi->current_indent;
        vbi->ital = vbi->current_ital;
        vbi->colour = vbi->current_colour;
        vbi->row = vbi->current_row;
        vbi->current_istext = 0;

        vbiscreen_new_caption (vbi->vs, vbi->indent, vbi->ital,
            vbi->colour, vbi->row);

      }

      vbi->lastcode = (b1 << 8) | b2;
      vbi->lastcount = 0;
      return 1;
    }

    if ((b1 & 8) == 1) {
      /* Midrow code */
      if (!vbi->initialised)
        return 0;

      if (!bottom && vbi->lastcode == ((b1 << 8) | b2)) {
        vbi->lastcount = (vbi->lastcount + 1) % 2;
        return 1;
      }

      if (vbi->verbose)
        fprintf (stderr, "Midrow TODO: Add me.\n");

      vbi->lastcode = (b1 << 8) | b2;
      return 1;
    }

    if ((b1 & 2) && !(b2 & 64)) {
      if (!vbi->initialised)
        return 0;

      if (!bottom && vbi->lastcode == ((b1 << 8) | b2)) {
        vbi->lastcount = (vbi->lastcount + 1) % 2;
        return 1;
      }

      if (vbi->verbose)
        fprintf (stderr, "Tab Offset: %d columns\n", b2 & 3);
      if (vbi->wanttext && vbi->current_istext &&
          vbi->current_chan == vbi->chan && !bottom == vbi->wanttop) {
        vbiscreen_tab (vbi->vs, b2 & 3);
      }
      vbi->lastcode = (b1 << 8) | b2;
      return 1;
    }

    switch ((code = b2 & 15)) {
      case 0:                  /* POP-UP */
      case 5:                  /* ROLL UP 2 */
      case 6:                  /* ROLL UP 3 */
      case 7:                  /* ROLL UP 4 */
      case 9:                  /* PAINT-ON */
      case 10:                 /* TEXT */
      case 11:                 /* TEXT */
        vbi->initialised = 1;
        if (!bottom && vbi->lastcode == ((b1 << 8) | b2)) {
          /* This is the repeated Control Code */
          vbi->lastcount = (vbi->lastcount + 1) % 2;
          return 1;
        }
        switch (code) {
          case 0:              /* POP-UP */
            if (!vbi->wanttext && vbi->current_chan == vbi->chan &&
                !bottom == vbi->wanttop) {
              if (vbi->verbose)
                fprintf (stderr, "Pop-Up\n");
              vbi->indent = vbi->current_indent;
              vbi->ital = vbi->current_ital;
              vbi->colour = vbi->current_colour;
              vbi->row = vbi->current_row;
              vbi->current_istext = 0;
              vbiscreen_set_mode (vbi->vs, 1, POP_UP);
            }
            break;
          case 5:              /* ROLL UP 2 */
            if (!vbi->wanttext && vbi->current_chan == vbi->chan &&
                !bottom == vbi->wanttop) {
              if (vbi->verbose)
                fprintf (stderr, "Roll-Up 2 (RU2)\n");
              vbi->indent = vbi->current_indent;
              vbi->ital = vbi->current_ital;
              vbi->colour = vbi->current_colour;
              vbi->row = vbi->current_row;
              vbi->current_istext = 0;
              vbiscreen_set_mode (vbi->vs, 1, ROLL_2);
            }
            break;
          case 6:              /* ROLL UP 3 */
            if (!vbi->wanttext && vbi->current_chan == vbi->chan &&
                !bottom == vbi->wanttop) {
              if (vbi->verbose)
                fprintf (stderr, "Roll-Up 3 (RU3)\n");
              vbi->indent = vbi->current_indent;
              vbi->ital = vbi->current_ital;
              vbi->colour = vbi->current_colour;
              vbi->row = vbi->current_row;
              vbi->current_istext = 0;
              vbiscreen_set_mode (vbi->vs, 1, ROLL_3);
            }
            break;
          case 7:              /* ROLL UP 4 */
            if (!vbi->wanttext && vbi->current_chan == vbi->chan &&
                !bottom == vbi->wanttop) {
              if (vbi->verbose)
                fprintf (stderr, "Roll-Up 4 (RU4)\n");
              vbi->indent = vbi->current_indent;
              vbi->ital = vbi->current_ital;
              vbi->colour = vbi->current_colour;
              vbi->row = vbi->current_row;
              vbi->current_istext = 0;
              vbiscreen_set_mode (vbi->vs, 1, ROLL_4);
            }
            break;
          case 9:              /* PAINT-ON */
            if (!vbi->wanttext && vbi->current_chan == vbi->chan &&
                !bottom == vbi->wanttop) {
              if (vbi->verbose)
                fprintf (stderr, "Paint-On\n");
              vbi->indent = vbi->current_indent;
              vbi->ital = vbi->current_ital;
              vbi->colour = vbi->current_colour;
              vbi->row = vbi->current_row;
              vbi->current_istext = 0;
              vbiscreen_set_mode (vbi->vs, 1, PAINT_ON);
            }
            break;
          case 10:             /* TEXT */
            if (vbi->wanttext && vbi->current_chan == vbi->chan &&
                !bottom == vbi->wanttop) {
              if (vbi->verbose)
                fprintf (stderr, "Text Restart\n");
              vbi->indent = vbi->current_indent;
              vbi->ital = vbi->current_ital;
              vbi->colour = vbi->current_colour;
              vbi->row = vbi->current_row;
              vbi->current_istext = 1;
              vbiscreen_set_mode (vbi->vs, 0, 0);
            }
            break;
          case 11:             /* TEXT */
            if (vbi->wanttext && vbi->current_chan == vbi->chan &&
                !bottom == vbi->wanttop) {
              if (vbi->verbose)
                fprintf (stderr, "Resume Text Display\n");
              vbi->indent = vbi->current_indent;
              vbi->ital = vbi->current_ital;
              vbi->colour = vbi->current_colour;
              vbi->row = vbi->current_row;
              vbi->current_istext = 1;
              vbiscreen_set_mode (vbi->vs, 0, 0);
            }
            break;
          default:             /* impossible */
            break;
        }
        break;
      case 1:
        if (!vbi->initialised)
          return 0;
        if (!bottom && vbi->lastcode == ((b1 << 8) | b2)) {
          vbi->lastcount = (vbi->lastcount + 1) % 2;
        }
        if (!bottom == vbi->wanttop && vbi->current_chan == vbi->chan &&
            vbi->current_istext == vbi->wanttext) {
          if (vbi->verbose)
            fprintf (stderr, "Backspace\n");
          vbiscreen_backspace (vbi->vs);
        }
        break;
      case 2:
      case 3:
        if (!vbi->initialised)
          return 0;
        fprintf (stderr, "Reserved\n");
        break;
      case 4:
        if (!vbi->initialised)
          return 0;
        if (!bottom && vbi->lastcode == ((b1 << 8) | b2)) {
          vbi->lastcount = (vbi->lastcount + 1) % 2;
        }
        if (!bottom == vbi->wanttop && vbi->current_chan == vbi->chan &&
            vbi->current_istext == vbi->wanttext) {
          if (vbi->verbose)
            fprintf (stderr, "Delete to End of Row\n");
          vbiscreen_delete_to_end (vbi->vs);
        }
        break;
      case 8:
        if (!vbi->initialised)
          return 0;
        if (vbi->verbose)
          fprintf (stderr, "Flash On\n");
        break;
      case 12:
      case 13:
      case 14:
      case 15:
        if (!vbi->initialised)
          return 0;
        if (!bottom && vbi->lastcode == ((b1 << 8) | b2)) {
          vbi->lastcount = (vbi->lastcount + 1) % 2;
          return 1;
        }

        switch (code) {
          case 12:
            /* Show buffer 1, Fill buffer 2 */
            if (!bottom == vbi->wanttop &&
                vbi->current_chan == vbi->chan &&
                vbi->current_istext == vbi->wanttext) {
              if (vbi->verbose)
                fprintf (stderr, "Erase Displayed Memory\n");
              vbiscreen_erase_displayed (vbi->vs);
            }
            break;
          case 13:
            if (!bottom == vbi->wanttop &&
                vbi->current_chan == vbi->chan &&
                vbi->current_istext == vbi->wanttext) {
              if (vbi->verbose)
                fprintf (stderr, "Carriage Return\n");
              vbiscreen_carriage_return (vbi->vs);
            }
            break;
          case 14:
            if (!bottom == vbi->wanttop &&
                vbi->current_chan == vbi->chan &&
                vbi->current_istext == vbi->wanttext) {
              if (vbi->verbose)
                fprintf (stderr, "Erase Non-Displayed\n");
              vbiscreen_erase_non_displayed (vbi->vs);
            }
            break;
          case 15:
            /* Show buffer 2, Fill Buffer 1 */
            if (!bottom == vbi->wanttop &&
                vbi->current_chan == vbi->chan &&
                vbi->current_istext == vbi->wanttext) {
              if (vbi->verbose)
                fprintf (stderr, "End Of Caption\n");
              vbiscreen_end_of_caption (vbi->vs);
            }
            break;
          default:             /* impossible */
            return 0;
            break;
        }
        break;
      default:                 /* Impossible */
        return 0;
        break;
    }

    if (vbi->lastcode != ((b1 << 8) | b2)) {
      vbi->lastcount = 0;
    }

    vbi->lastcode = (b1 << 8) | b2;
    return 1;
  }

  if (bottom && xds_decode (vbi, b1, b2)) {
    return 1;
  }

  if (!vbi->enabled)
    return 0;

  vbi->lastcode = 0;
  vbi->lastcount = 0;

  if (!vbi->initialised)
    return 0;

  if (!bottom != vbi->wanttop || vbi->current_chan != vbi->chan ||
      vbi->current_istext != vbi->wanttext) {
    return 0;
  }

  if (b1 == 0x11 || b1 == 0x19 ||
      b1 == 0x12 || b1 == 0x13 || b1 == 0x1A || b1 == 0x1B) {
    switch (b1) {
      case 0x1A:
      case 0x12:
        /* use extcode1 */
        if (b1 > 31 && b2 > 31 && b1 <= 0x3F && b2 <= 0x3F)
          if (vbi->verbose)
            fprintf (stderr, "char %d (%c),  char %d (%c)\n", b1,
                extcode1[b1 - 32], b2, extcode1[b2 - 32]);

        break;
      case 0x13:
      case 0x1B:
        /* use extcode2 */
        if (b1 > 31 && b2 > 31 && b1 <= 0x3F && b2 <= 0x3F)
          if (vbi->verbose)
            fprintf (stderr, "char %d (%c),  char %d (%c)\n", b1,
                extcode2[b1 - 32], b2, extcode2[b2 - 32]);

        break;
      case 0x11:
      case 0x19:
        /* use wcode */
        if (b1 > 31 && b2 > 31 && b1 <= 0x3F && b2 <= 0x3F)
          if (vbi->verbose)
            fprintf (stderr, "char %d (%c),  char %d (%c)\n", b1,
                wccode[b1 - 32], b2, wccode[b2 - 32]);

        break;
      default:
        break;
    }
  } else if (b1) {
    /* use ccode */
    if (b1 < 32)
      b1 = 32;
    if (b2 < 32)
      b2 = 32;
    if (vbi->verbose)
      fprintf (stderr, "vbidata: data: %c %c\n", ccode[b1 - 32],
          ccode[b2 - 32]);
    vbiscreen_print (vbi->vs, ccode[b1 - 32], ccode[b2 - 32]);
  }


  return 1;
}                               /* Process16b */

int
ProcessLine (vbidata_t * vbi, unsigned char *s, int bottom)
{
  int w1;

  /*char *outbuf = NULL; */

  if (!vbi)
    return 0;

  w1 = ccdecode (s);

  return Process16b (vbi, bottom, w1);
}                               /* ProcessLine */



vbidata_t *
vbidata_new_file (const char *filename, vbiscreen_t * vs,
    /*tvtime_osd_t* osd, */ int verbose)
{
  vbidata_t *vbi = (vbidata_t *) malloc (sizeof (vbidata_t));

  if (!vbi) {
    return 0;
  }

  vbi->fd = open (filename, O_RDONLY);
  if (vbi->fd < 0) {
    fprintf (stderr, "vbidata: Can't open %s: %s\n",
        filename, strerror (errno));
    free (vbi);
    return 0;
  }

  vbi->vs = vs;
  /*vbi->osd = osd; */
  vbi->verbose = verbose;

  vbidata_reset (vbi);

  return vbi;
}

vbidata_t *
vbidata_new_line (vbiscreen_t * vs, int verbose)
{
  vbidata_t *vbi = (vbidata_t *) malloc (sizeof (vbidata_t));

  if (!vbi) {
    return 0;
  }

  vbi->vs = vs;
  /*vbi->osd = osd; */
  vbi->verbose = verbose;

  vbidata_reset (vbi);

  return vbi;
}

void
vbidata_delete (vbidata_t * vbi)
{
  if (!vbi)
    return;
  if (vbi->fd != 0) {
    close (vbi->fd);
  }
  free (vbi);
}

void
vbidata_reset (vbidata_t * vbi)
{
  if (!vbi)
    return;

  vbi->wanttop = 0;
  vbi->wanttext = 0;
  vbi->colour = 0xFFFFFFFFU;
  vbi->row = 0;

  vbi->ital = 0;
  vbi->indent = 0;
  vbi->ul = 0;

  vbi->chan = 0;

  vbi->initialised = 0;
  vbi->enabled = 0;

  memset (vbi->program_desc, 0, 8 * sizeof (char *));
  vbi->program_name = NULL;
  vbi->network_name = NULL;
  vbi->call_letters = NULL;
  vbi->rating = NULL;
  vbi->program_type = NULL;

  vbi->start_day = 0;
  vbi->start_month = 0;
  vbi->start_min = 0;
  vbi->start_hour = 0;
  vbi->length_hour = 0;
  vbi->length_min = 0;
  vbi->length_elapsed_hour = 0;
  vbi->length_elapsed_min = 0;
  vbi->length_elapsed_sec = 0;

  /*
     tvtime_osd_set_network_call( vbi->osd, "" );
     tvtime_osd_set_network_name( vbi->osd, "" );
     tvtime_osd_set_show_name( vbi->osd, "" );
     tvtime_osd_set_show_rating( vbi->osd, "" );
     tvtime_osd_set_show_start( vbi->osd, "" );
     tvtime_osd_set_show_length( vbi->osd, "" );
   */



  vbi->lastcode = 0;
  vbi->lastcount = 0;
  vbi->xds_packet[0] = 0;
  vbi->xds_cursor = 0;
  vbiscreen_reset (vbi->vs);
}

void
vbidata_set_verbose (vbidata_t * vbi, int verbose)
{
  vbi->verbose = verbose;
}

void
vbidata_capture_mode (vbidata_t * vbi, int mode)
{
  if (!vbi)
    return;
  switch (mode) {
    case CAPTURE_OFF:
      vbi->enabled = 0;
      break;
    case CAPTURE_CC1:
      vbi->wanttop = 1;
      vbi->wanttext = 0;
      vbi->chan = 0;
      vbi->enabled = 1;
      break;
    case CAPTURE_CC2:
      vbi->wanttop = 1;
      vbi->wanttext = 0;
      vbi->chan = 1;
      vbi->enabled = 1;
      break;
    case CAPTURE_CC3:
      vbi->wanttop = 0;
      vbi->wanttext = 0;
      vbi->chan = 0;
      vbi->enabled = 1;
      break;
    case CAPTURE_CC4:
      vbi->wanttop = 0;
      vbi->wanttext = 0;
      vbi->chan = 1;
      vbi->enabled = 1;
      break;
    case CAPTURE_T1:
      vbi->wanttop = 1;
      vbi->wanttext = 1;
      vbi->chan = 0;
      vbi->enabled = 1;
      break;
    case CAPTURE_T2:
      vbi->wanttop = 1;
      vbi->wanttext = 1;
      vbi->chan = 1;
      vbi->enabled = 1;
      break;
    case CAPTURE_T3:
      vbi->wanttop = 0;
      vbi->wanttext = 1;
      vbi->chan = 0;
      vbi->enabled = 1;
      break;
    case CAPTURE_T4:
      vbi->wanttop = 0;
      vbi->wanttext = 1;
      vbi->chan = 1;
      vbi->enabled = 1;
      break;
    default:
      vbi->enabled = 0;
      break;
  }
}

void
vbidata_process_frame (vbidata_t * vbi, int printdebug)
{
  if (read (vbi->fd, vbi->buf, 65536) < 65536) {
    fprintf (stderr, "error, can't read vbi data.\n");
    return;
  }

  ProcessLine (vbi, &vbi->buf[DO_LINE * 2048], 0);
  ProcessLine (vbi, &vbi->buf[(16 + DO_LINE) * 2048], 1);
}

void
vbidata_process_line (vbidata_t * vbi, unsigned char *s, int bottom)
{
  ProcessLine (vbi, s, bottom);
}

void
vbidata_process_16b (vbidata_t * vbi, int bottom, int w)
{
  Process16b (vbi, bottom, w);
}
