/*
 *  libzvbi -- Raw VBI decoder
 *
 *  Copyright (C) 2000-2004 Michael H. Schimek
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the 
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 
 *  Boston, MA  02110-1301  USA.
 */

/* $Id: raw_decoder.c,v 1.24 2008-08-19 10:04:46 mschimek Exp $ */

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

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "misc.h"
#include "raw_decoder.h"

#ifndef RAW_DECODER_PATTERN_DUMP
#  define RAW_DECODER_PATTERN_DUMP 0
#endif

#  define sp_sample_format sampling_format

/**
 * $addtogroup RawDecoder Raw VBI decoder
 * $ingroup Raw
 * $brief Converting a raw VBI image to sliced VBI data.
 */

/* Missing:
   VITC PAL 6-22 11.2us 1.8125 Mbit NRZ two start bits + CRC
   VITC NTSC 10-21 ditto
   CGMS NTSC 20 11us .450450 Mbit NRZ ?
   MOJI
*/
const _vbi_service_par _vbi_service_table[] = {
  {
        VBI_SLICED_TELETEXT_A,  /* UNTESTED */
        "Teletext System A",
        VBI_VIDEOSTD_SET_625_50,
        {6, 318},
        {22, 335},
        10500, 6203125, 6203125,        /* 397 x FH */
        0x00AAAAE7, 0xFFFF, 18, 6, 37 * 8, VBI_MODULATION_NRZ_LSB,
        0,                      /* probably */
      }, {
        VBI_SLICED_TELETEXT_B_L10_625,
        "Teletext System B 625 Level 1.5",
        VBI_VIDEOSTD_SET_625_50,
        {7, 320},
        {22, 335},
        10300, 6937500, 6937500,        /* 444 x FH */
        0x00AAAAE4, 0xFFFF, 18, 6, 42 * 8, VBI_MODULATION_NRZ_LSB,
        0,
      }, {
        VBI_SLICED_TELETEXT_B,
        "Teletext System B, 625",
        VBI_VIDEOSTD_SET_625_50,
        {6, 318},
        {22, 335},
        10300, 6937500, 6937500,        /* 444 x FH */
        0x00AAAAE4, 0xFFFF, 18, 6, 42 * 8, VBI_MODULATION_NRZ_LSB,
        0,
      }, {
        VBI_SLICED_TELETEXT_C_625,      /* UNTESTED */
        "Teletext System C 625",
        VBI_VIDEOSTD_SET_625_50,
        {6, 318},
        {22, 335},
        10480, 5734375, 5734375,        /* 367 x FH */
        0x00AAAAE7, 0xFFFF, 18, 6, 33 * 8, VBI_MODULATION_NRZ_LSB,
        0,
      }, {
        VBI_SLICED_TELETEXT_D_625,      /* UNTESTED */
        "Teletext System D 625",
        VBI_VIDEOSTD_SET_625_50,
        {6, 318},
        {22, 335},
        10500,                  /* or 10970 depending on field order */
        5642787, 5642787,       /* 14/11 x FSC (color subcarrier) */
        0x00AAAAE5, 0xFFFF, 18, 6, 34 * 8, VBI_MODULATION_NRZ_LSB,
        0,
      }, {
        VBI_SLICED_VPS, "Video Program System",
        VBI_VIDEOSTD_SET_PAL_BG,
        {16, 0},
        {16, 0},
        12500, 5000000, 2500000,        /* 160 x FH */
        0xAAAA8A99, 0xFFFFFF, 32, 0, 13 * 8,
        VBI_MODULATION_BIPHASE_MSB,
        _VBI_SP_FIELD_NUM,
      }, {
        VBI_SLICED_VPS_F2, "Pseudo-VPS on field 2",
        VBI_VIDEOSTD_SET_PAL_BG,
        {0, 329},
        {0, 329},
        12500, 5000000, 2500000,        /* 160 x FH */
        0xAAAA8A99, 0xFFFFFF, 32, 0, 13 * 8,
        VBI_MODULATION_BIPHASE_MSB,
        _VBI_SP_FIELD_NUM,
      }, {
        VBI_SLICED_WSS_625, "Wide Screen Signalling 625",
        VBI_VIDEOSTD_SET_625_50,
        {23, 0},
        {23, 0},
        11000, 5000000, 833333, /* 160/3 x FH */
        /* ...1000 111 / 0 0011 1100 0111 1000 0011 111x */
        /* ...0010 010 / 0 1001 1001 0011 0011 1001 110x */
        0x8E3C783E, 0x2499339C, 32, 0, 14 * 1,
        VBI_MODULATION_BIPHASE_LSB,
        /* Hm. Too easily confused with caption?? */
        _VBI_SP_FIELD_NUM | _VBI_SP_LINE_NUM,
      }, {
        VBI_SLICED_CAPTION_625_F1, "Closed Caption 625, field 1",
        VBI_VIDEOSTD_SET_625_50,
        {22, 0},
        {22, 0},
        10500, 1000000, 500000, /* 32 x FH */
        0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB,
        _VBI_SP_FIELD_NUM,
      }, {
        VBI_SLICED_CAPTION_625_F2, "Closed Caption 625, field 2",
        VBI_VIDEOSTD_SET_625_50,
        {0, 335},
        {0, 335},
        10500, 1000000, 500000, /* 32 x FH */
        0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB,
        _VBI_SP_FIELD_NUM,
      }, {
        VBI_SLICED_VBI_625, "VBI 625",  /* Blank VBI */
        VBI_VIDEOSTD_SET_625_50,
        {6, 318},
        {22, 335},
        10000, 1510000, 1510000,
        0, 0, 0, 0, 10 * 8, 0,  /* 10.0-2 ... 62.9+1 us */
        0,
      }, {
        VBI_SLICED_TELETEXT_B_525,      /* UNTESTED */
        "Teletext System B 525",
        VBI_VIDEOSTD_SET_525_60,
        {10, 272},
        {21, 284},
        10500, 5727272, 5727272,        /* 364 x FH */
        0x00AAAAE4, 0xFFFF, 18, 6, 34 * 8, VBI_MODULATION_NRZ_LSB,
        0,
      }, {
        VBI_SLICED_TELETEXT_C_525,      /* UNTESTED */
        "Teletext System C 525",
        VBI_VIDEOSTD_SET_525_60,
        {10, 272},
        {21, 284},
        10480, 5727272, 5727272,        /* 364 x FH */
        0x00AAAAE7, 0xFFFF, 18, 6, 33 * 8, VBI_MODULATION_NRZ_LSB,
        0,
      }, {
        VBI_SLICED_TELETEXT_D_525,      /* UNTESTED */
        "Teletext System D 525",
        VBI_VIDEOSTD_SET_525_60,
        {10, 272},
        {21, 284},
        9780, 5727272, 5727272, /* 364 x FH */
        0x00AAAAE5, 0xFFFF, 18, 6, 34 * 8, VBI_MODULATION_NRZ_LSB,
        0,
      }, {
#if 0                           /* FIXME probably wrong */
        VBI_SLICED_WSS_CPR1204, /* NOT CONFIRMED (EIA-J CPR-1204) */
        "Wide Screen Signalling 525",
        VBI_VIDEOSTD_SET_NTSC_M_JP,
        {20, 283},
        {20, 283},
        11200, 1789773, 447443, /* 1/8 x FSC */
        0x000000F0, 0xFF, 8, 0, 20 * 1, VBI_MODULATION_NRZ_MSB,
        /* No useful FRC, but a six bit CRC */
        0,
      }, {
#endif
        VBI_SLICED_CAPTION_525_F1,
        "Closed Caption 525, field 1",
        VBI_VIDEOSTD_SET_525_60,
        {21, 0},
        {21, 0},
        10500, 1006976, 503488, /* 32 x FH */
#if 0
        /* Test of CRI bits has been removed to handle the
           incorrect signal observed by Rich Kandel (see
           _VBI_RAW_SHIFT_CC_CRI). */
        0x03, 0x0F, 4, 0, 2 * 8, VBI_MODULATION_NRZ_LSB,
#else
        0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB,
        /* I've seen CC signals on other lines and there's no
           way to distinguish from the transmitted data. */
#endif
        _VBI_SP_FIELD_NUM | _VBI_SP_LINE_NUM,
      }, {
        VBI_SLICED_CAPTION_525_F2,
        "Closed Caption 525, field 2",
        VBI_VIDEOSTD_SET_525_60,
        {0, 284},
        {0, 284},
        10500, 1006976, 503488, /* 32 x FH */
#if 0
        /* Test of CRI bits has been removed to handle the
           incorrect signal observed by Rich Kandel (see
           _VBI_RAW_SHIFT_CC_CRI). */
        0x03, 0x0F, 4, 0, 2 * 8, VBI_MODULATION_NRZ_LSB,
#else
        0x00005551, 0x7FF, 14, 2, 2 * 8, VBI_MODULATION_NRZ_LSB,
#endif
        _VBI_SP_FIELD_NUM | _VBI_SP_LINE_NUM,
      }, {
        VBI_SLICED_2xCAPTION_525,       /* NOT CONFIRMED */
        "2xCaption 525",
        VBI_VIDEOSTD_SET_525_60,
        {10, 0},
        {21, 0},
        10500, 1006976, 1006976,        /* 64 x FH */
        0x000554ED, 0xFFFF, 12, 8, 4 * 8,
        VBI_MODULATION_NRZ_LSB, /* Tb. */
        _VBI_SP_FIELD_NUM,
      }, {
        VBI_SLICED_VBI_525, "VBI 525",  /* Blank VBI */
        VBI_VIDEOSTD_SET_525_60,
        {10, 272},
        {21, 284},
        9500, 1510000, 1510000,
        0, 0, 0, 0, 10 * 8, 0,  /* 9.5-1 ... 62.4+1 us */
        0,
      }, {
        0, NULL,
        VBI_VIDEOSTD_SET_EMPTY,
        {0, 0},
        {0, 0},
        0, 0, 0,
        0, 0, 0, 0, 0, 0,
        0,
      }
};

_vbi_inline const _vbi_service_par *
find_service_par (unsigned int service)
{
  unsigned int i;

  for (i = 0; _vbi_service_table[i].id; ++i)
    if (service == _vbi_service_table[i].id)
      return _vbi_service_table + i;

  return NULL;
}

/**
 * $ingroup Sliced
 * $param service A data service identifier, for example from a
 *   vbi_sliced structure.
 *
 * $return
 * Name of the $a service, in ASCII, or $c NULL if unknown.
 */
const char *
vbi_sliced_name (vbi_service_set service)
{
  const _vbi_service_par *par;

  /* These are ambiguous */
  if (service == VBI_SLICED_CAPTION_525)
    return "Closed Caption 525";
  if (service == VBI_SLICED_CAPTION_625)
    return "Closed Caption 625";
  if (service == (VBI_SLICED_VPS | VBI_SLICED_VPS_F2))
    return "Video Program System";
  if (service == VBI_SLICED_TELETEXT_B_L25_625)
    return "Teletext System B 625 Level 2.5";

  /* Incorrect, no longer in table */
  if (service == VBI_SLICED_TELETEXT_BD_525)
    return "Teletext System B/D";

  if ((par = find_service_par (service)))
    return par->label;

  return NULL;
}

/**
 * @ingroup Sliced
 * @param service A data service identifier, for example from a
 *   vbi_sliced structure.
 *
 * @return
 * Number of payload bits, @c 0 if the service is unknown.
 */
unsigned int
vbi_sliced_payload_bits (unsigned int service)
{
  const _vbi_service_par *par;

  /* These are ambiguous */
  if (service == VBI_SLICED_CAPTION_525)
    return 16;
  if (service == VBI_SLICED_CAPTION_625)
    return 16;
  if (service == (VBI_SLICED_VPS | VBI_SLICED_VPS_F2))
    return 13 * 8;
  if (service == VBI_SLICED_TELETEXT_B_L25_625)
    return 42 * 8;

  /* Incorrect, no longer in table */
  if (service == VBI_SLICED_TELETEXT_BD_525)
    return 34 * 8;

  if ((par = find_service_par (service)))
    return par->payload;

  return 0;
}

static void
dump_pattern_line (const vbi3_raw_decoder * rd, unsigned int row, FILE * fp)
{
  const vbi_sampling_par *sp;
  unsigned int line;
  unsigned int i;

  sp = &rd->sampling;

  if (sp->interlaced) {
    unsigned int field = row & 1;

    if (0 == sp->start[field])
      line = 0;
    else
      line = sp->start[field] + (row >> 1);
  } else {
    if (row >= (unsigned int) sp->count[0]) {
      if (0 == sp->start[1])
        line = 0;
      else
        line = sp->start[1] + row - sp->count[0];
    } else {
      if (0 == sp->start[0])
        line = 0;
      else
        line = sp->start[0] + row;
    }
  }

  fprintf (fp, "scan line %3u: ", line);

  for (i = 0; i < _VBI3_RAW_DECODER_MAX_WAYS; ++i) {
    unsigned int pos;

    pos = row * _VBI3_RAW_DECODER_MAX_WAYS;
    fprintf (fp, "%02x ", (uint8_t) rd->pattern[pos + i]);
  }

  fputc ('\n', fp);
}

void
_vbi3_raw_decoder_dump (const vbi3_raw_decoder * rd, FILE * fp)
{
  const vbi_sampling_par *sp;
  unsigned int i;

  assert (NULL != fp);

  fprintf (fp, "vbi3_raw_decoder %p\n", rd);

  if (NULL == rd)
    return;

  fprintf (fp, "  services 0x%08x\n", rd->services);

  for (i = 0; i < rd->n_jobs; ++i)
    fprintf (fp, "  job %u: 0x%08x (%s)\n",
        i + 1, rd->jobs[i].id, vbi_sliced_name (rd->jobs[i].id));

  if (!rd->pattern) {
    fprintf (fp, "  no pattern\n");
    return;
  }

  sp = &rd->sampling;

  for (i = 0; i < ((unsigned int) sp->count[0]
          + (unsigned int) sp->count[1]); ++i) {
    fputs ("  ", fp);
    dump_pattern_line (rd, i, fp);
  }
}

#if 0                           /* @UNUSED */
_vbi_inline int
cpr1204_crc (const vbi_sliced * sliced)
{
  const int poly = (1 << 6) + (1 << 1) + 1;
  int crc, i;

  crc = (+(sliced->data[0] << 12)
      + (sliced->data[1] << 4)
      + (sliced->data[2]));

  crc |= (((1 << 6) - 1) << (14 + 6));

  for (i = 14 + 6 - 1; i >= 0; i--) {
    if (crc & ((1 << 6) << i))
      crc ^= poly << i;
  }

  return crc;
}
#endif

static vbi_bool
slice (vbi3_raw_decoder * rd,
    vbi_sliced * sliced,
    _vbi3_raw_decoder_job * job, unsigned int i, const uint8_t * raw)
{
  if (rd->debug && NULL != rd->sp_lines) {
    return vbi3_bit_slicer_slice_with_points
        (&job->slicer,
        sliced->data,
        sizeof (sliced->data),
        rd->sp_lines[i].points,
        &rd->sp_lines[i].n_points, N_ELEMENTS (rd->sp_lines[i].points), raw);
  } else {
    return vbi3_bit_slicer_slice
        (&job->slicer, sliced->data, sizeof (sliced->data), raw);
  }
}

_vbi_inline vbi_sliced *
decode_pattern (vbi3_raw_decoder * rd,
    vbi_sliced * sliced, int8_t * pattern, unsigned int i, const uint8_t * raw)
{
  vbi_sampling_par *sp;
  int8_t *pat;

  sp = &rd->sampling;

  for (pat = pattern;; ++pat) {
    int j;

    j = *pat;                   /* data service n, blank 0, or counter -n */

    if (j > 0) {
      _vbi3_raw_decoder_job *job;

      job = rd->jobs + j - 1;

      if (!slice (rd, sliced, job, i, raw)) {
        continue;               /* no match, try next data service */
      }

      /* FIXME probably wrong */
      if (0 && VBI_SLICED_WSS_CPR1204 == job->id) {
        const int poly = (1 << 6) + (1 << 1) + 1;
        int crc, j;

        crc = (sliced->data[0] << 12)
            + (sliced->data[1] << 4)
            + sliced->data[2];
        crc |= (((1 << 6) - 1) << (14 + 6));

        for (j = 14 + 6 - 1; j >= 0; j--) {
          if (crc & ((1 << 6) << j))
            crc ^= poly << j;
        }

        if (crc)
          continue;             /* no match */
      }

      /* Positive match, output decoded line. */

      /* FIXME: if we have a field number we should
         really only set the service id of one field. */
      sliced->id = job->id;
      sliced->line = 0;

      if (i >= (unsigned int) sp->count[0]) {
        if (sp->synchronous && 0 != sp->start[1])
          sliced->line = sp->start[1]
              + i - sp->count[0];
      } else {
        if (sp->synchronous && 0 != sp->start[0])
          sliced->line = sp->start[0] + i;
      }

      if (0)
        fprintf (stderr, "%2d %s\n",
            sliced->line, vbi_sliced_name (sliced->id));

      ++sliced;

      /* Predict line as non-blank, force testing for
         all data services in the next 128 frames. */
      pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = -128;
    } else if (pat == pattern) {
      /* Line was predicted as blank, once in 16
         frames look for data services. */
      if (0 == rd->readjust) {
        unsigned int size;

        size = sizeof (*pattern)
            * (_VBI3_RAW_DECODER_MAX_WAYS - 1);

        j = pattern[0];
        memmove (&pattern[0], &pattern[1], size);
        pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = j;
      }

      break;
    } else if ((j = pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1]) < 0) {
      /* Increment counter, when zero predict line as
         blank and stop looking for data services until
         0 == rd->readjust. */
      /* Disabled because we may miss caption/subtitles
         when the signal inserter is disabled during silent
         periods for more than 4-5 seconds. */
      /* pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = j + 1; */
      break;
    } else {
      /* found nothing, j = 0 */
    }

    /* Try the found data service first next time. */
    *pat = pattern[0];
    pattern[0] = j;

    break;                      /* line done */
  }

  return sliced;
}

/**
 * $param rd Pointer to vbi3_raw_decoder object allocated with
 *   vbi3_raw_decoder_new().
 * $param sliced Buffer to store the decoded vbi_sliced data. Since every
 *   vbi scan line may contain data, this should be an array of vbi_sliced
 *   with the same number of elements as scan lines in the raw image
 *   (vbi_sampling_parameters.count[0] + .count[1]).
 * $param max_lines Size of $a sliced data array, in lines, not bytes.
 * $param raw A raw vbi image as described by the vbi_sampling_par
 *   associated with $a rd.
 * 
 * Decodes a raw vbi image, consisting of several scan lines of raw vbi data,
 * to sliced vbi data. The output is sorted by ascending line number.
 * 
 * Note this function attempts to learn which lines carry which data
 * service, or if any, to speed up decoding. You should avoid using the same
 * vbi3_raw_decoder object for different sources.
 *
 * $return
 * The number of lines decoded, i. e. the number of vbi_sliced records
 * written.
 */
unsigned int
vbi3_raw_decoder_decode (vbi3_raw_decoder * rd,
    vbi_sliced * sliced, unsigned int max_lines, const uint8_t * raw)
{
  vbi_sampling_par *sp;
  unsigned int scan_lines;
  unsigned int pitch;
  int8_t *pattern;
  const uint8_t *raw1;
  vbi_sliced *sliced_begin;
  vbi_sliced *sliced_end;
  unsigned int i;

  if (!rd->services)
    return 0;

  sp = &rd->sampling;

  scan_lines = sp->count[0] + sp->count[1];
  pitch = sp->bytes_per_line << sp->interlaced;

  pattern = rd->pattern;

  raw1 = raw;

  sliced_begin = sliced;
  sliced_end = sliced + max_lines;

  if (RAW_DECODER_PATTERN_DUMP)
    _vbi3_raw_decoder_dump (rd, stderr);

  for (i = 0; i < scan_lines; ++i) {
    if (sliced >= sliced_end)
      break;

    if (sp->interlaced && i == (unsigned int) sp->count[0])
      raw = raw1 + sp->bytes_per_line;

    sliced = decode_pattern (rd, sliced, pattern, i, raw);

    pattern += _VBI3_RAW_DECODER_MAX_WAYS;
    raw += pitch;
  }

  rd->readjust = (rd->readjust + 1) & 15;

  return sliced - sliced_begin;
}

/**
 * $param rd Pointer to vbi3_raw_decoder object allocated with
 *   vbi3_raw_decoder_new().
 * 
 * Resets a vbi3_raw_decoder object, removing all services added
 * with vbi3_raw_decoder_add_services().
 */
void
vbi3_raw_decoder_reset (vbi3_raw_decoder * rd)
{
  assert (NULL != rd);

  if (rd->pattern) {
    vbi_free (rd->pattern);
    rd->pattern = NULL;
  }

  rd->services = 0;
  rd->n_jobs = 0;

  rd->readjust = 1;

  CLEAR (rd->jobs);
}

static void
remove_job_from_pattern (vbi3_raw_decoder * rd, int job_num)
{
  int8_t *pattern;
  unsigned int scan_lines;

  job_num += 1;                 /* index into rd->jobs, 0 means no job */

  pattern = rd->pattern;
  scan_lines = rd->sampling.count[0] + rd->sampling.count[1];

  /* For each scan line. */
  while (scan_lines-- > 0) {
    int8_t *dst;
    int8_t *src;
    int8_t *end;

    dst = pattern;
    end = pattern + _VBI3_RAW_DECODER_MAX_WAYS;

    /* Remove jobs with job_num, fill up pattern with 0.
       Jobs above job_num move down in rd->jobs. */
    for (src = dst; src < end; ++src) {
      int8_t num = *src;

      if (num > job_num)
        *dst++ = num - 1;
      else if (num != job_num)
        *dst++ = num;
    }

    while (dst < end)
      *dst++ = 0;

    pattern = end;
  }
}

/**
 * $param rd Pointer to vbi3_raw_decoder object allocated with
 *   vbi3_raw_decoder_new().
 * $param services Set of data services.
 * 
 * Removes one or more data services to be decoded from the
 * vbi3_raw_decoder object.
 * 
 * $return 
 * Set describing the remaining data services $a rd will decode.
 */
vbi_service_set
    vbi3_raw_decoder_remove_services
    (vbi3_raw_decoder * rd, vbi_service_set services) {
  _vbi3_raw_decoder_job *job;
  unsigned int job_num;

  assert (NULL != rd);

  job = rd->jobs;
  job_num = 0;

  while (job_num < rd->n_jobs) {
    if (job->id & services) {
      if (rd->pattern)
        remove_job_from_pattern (rd, job_num);

      memmove (job, job + 1, (rd->n_jobs - job_num - 1) * sizeof (*job));

      --rd->n_jobs;

      CLEAR (rd->jobs[rd->n_jobs]);
    } else {
      ++job_num;
    }
  }

  rd->services &= ~services;

  return rd->services;
}

static vbi_bool
add_job_to_pattern (vbi3_raw_decoder * rd,
    int job_num, unsigned int *start, unsigned int *count)
{
  int8_t *pattern_end;
  unsigned int scan_lines;
  unsigned int field;

  job_num += 1;                 /* index into rd->jobs, 0 means no job */

  scan_lines = rd->sampling.count[0]
      + rd->sampling.count[1];

  pattern_end = rd->pattern + scan_lines * _VBI3_RAW_DECODER_MAX_WAYS;

  for (field = 0; field < 2; ++field) {
    int8_t *pattern;
    unsigned int i;

    pattern = rd->pattern + start[field] * _VBI3_RAW_DECODER_MAX_WAYS;

    /* For each line where we may find the data. */
    for (i = 0; i < count[field]; ++i) {
      unsigned int free;
      int8_t *dst;
      int8_t *src;
      int8_t *end;

      assert (pattern < pattern_end);

      dst = pattern;
      end = pattern + _VBI3_RAW_DECODER_MAX_WAYS;

      free = 0;

      for (src = dst; src < end; ++src) {
        int8_t num = *src;

        if (num <= 0) {
          ++free;
          continue;
        } else {
          free += (num == job_num);
          *dst++ = num;
        }
      }

      while (dst < end)
        *dst++ = 0;

      if (free <= 1)            /* reserve a NULL way */
        return FALSE;

      pattern = end;
    }
  }

  for (field = 0; field < 2; ++field) {
    int8_t *pattern;
    unsigned int i;

    pattern = rd->pattern + start[field] * _VBI3_RAW_DECODER_MAX_WAYS;

    /* For each line where we may find the data. */
    for (i = 0; i < count[field]; ++i) {
      unsigned int way;

      for (way = 0; pattern[way] > 0; ++way)
        if (pattern[way] == job_num)
          break;

      pattern[way] = job_num;
      pattern[_VBI3_RAW_DECODER_MAX_WAYS - 1] = -128;

      pattern += _VBI3_RAW_DECODER_MAX_WAYS;
    }
  }

  return TRUE;
}

static void
lines_containing_data (unsigned int start[2],
    unsigned int count[2],
    const vbi_sampling_par * sp, const _vbi_service_par * par)
{
  unsigned int field;

  start[0] = 0;
  start[1] = sp->count[0];

  count[0] = sp->count[0];
  count[1] = sp->count[1];

  if (!sp->synchronous) {
    /* XXX Scanning all lines isn't always necessary. */
    return;
  }

  for (field = 0; field < 2; ++field) {
    unsigned int first;
    unsigned int last;

    if (0 == par->first[field]
        || 0 == par->last[field]) {
      /* No data on this field. */
      count[field] = 0;
      continue;
    }

    first = sp->start[field];
    last = first + sp->count[field] - 1;

    if (first > 0 && sp->count[field] > 0) {
      assert (par->first[field] <= par->last[field]);

      if ((unsigned int) par->first[field] > last
          || (unsigned int) par->last[field] < first)
        continue;

      first = MAX (first, (unsigned int) par->first[field]);
      last = MIN ((unsigned int) par->last[field], last);

      start[field] += first - sp->start[field];
      count[field] = last + 1 - first;
    }
  }
}

/**
 * $param rd Pointer to vbi3_raw_decoder object allocated with
 *   vbi3_raw_decoder_new().
 * $param services Set of data services.
 * $param strict A value of 0, 1 or 2 requests loose, reliable or strict
 *  matching of sampling parameters. For example if the data service
 *  requires knowledge of line numbers, $c 0 will always accept the
 *  service (which may work if the scan lines are populated in a
 *  non-confusing way) but $c 1 or $c 2 will not. If the data service
 *  might use more lines than are sampled, $c 1 will accept but $c 2
 *  will not. If unsure, set to $c 1.
 * 
 * Adds one or more data services to be decoded. Currently the libzvbi
 * raw vbi decoder can decode up to eight data services in parallel.
 * 
 * $return
 * Set describing the data services $a rd will decode. The function
 * eliminates services which cannot be decoded with the current
 * sampling parameters, or when they exceed the decoder capacity.
 */
/* Attn: strict must be int for compatibility with libzvbi 0.2 (-1 == 0) */
vbi_service_set
vbi3_raw_decoder_add_services (vbi3_raw_decoder * rd,
    vbi_service_set services, int strict)
{
  const _vbi_service_par *par;
  double min_offset;

  assert (NULL != rd);

  services &= ~(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625);

  if (rd->services & services) {
    info (&rd->log,
        "Already decoding services 0x%08x.", rd->services & services);
    services &= ~rd->services;
  }

  if (0 == services) {
    info (&rd->log, "No services to add.");
    return rd->services;
  }

  if (!rd->pattern) {
    unsigned int scan_lines;
    unsigned int scan_ways;
    unsigned int size;

    scan_lines = rd->sampling.count[0] + rd->sampling.count[1];
    scan_ways = scan_lines * _VBI3_RAW_DECODER_MAX_WAYS;

    size = scan_ways * sizeof (rd->pattern[0]);
    rd->pattern = (int8_t *) vbi_malloc (size);
    if (NULL == rd->pattern) {
      error (&rd->log, "Out of memory.");
      return rd->services;
    }

    memset (rd->pattern, 0, scan_ways * sizeof (rd->pattern[0]));
  }

  if (525 == rd->sampling.scanning) {
    min_offset = 7.9e-6;
  } else {
    min_offset = 8.0e-6;
  }

  for (par = _vbi_service_table; par->id; ++par) {
    vbi_sampling_par *sp;
    _vbi3_raw_decoder_job *job;
    unsigned int start[2];
    unsigned int count[2];
    unsigned int sample_offset;
    unsigned int samples_per_line;
    unsigned int cri_end;
    unsigned int j;

    if (0 == (par->id & services))
      continue;

    job = rd->jobs;

    /* Some jobs can be merged, otherwise we add a new job. */
    for (j = 0; j < rd->n_jobs; ++j) {
      unsigned int id = job->id | par->id;

      /* Level 1.0 and 2.5 */
      if (0 == (id & ~VBI_SLICED_TELETEXT_B)
          /* Field 1 and 2 */
          || 0 == (id & ~VBI_SLICED_CAPTION_525)
          || 0 == (id & ~VBI_SLICED_CAPTION_625)
          || 0 == (id & ~(VBI_SLICED_VPS | VBI_SLICED_VPS_F2)))
        break;

      ++job;
    }

    if (j >= _VBI3_RAW_DECODER_MAX_JOBS) {
      error (&rd->log,
          "Set 0x%08x exceeds number of "
          "simultaneously decodable "
          "services (%u).", services, _VBI3_RAW_DECODER_MAX_WAYS);
      break;
    } else if (j >= rd->n_jobs) {
      job->id = 0;
    }


    sp = &rd->sampling;

    if (!_vbi_sampling_par_check_services_log (sp, par->id, strict, &rd->log))
      continue;


    sample_offset = 0;

    /* Skip color burst. */
    /* Offsets aren't that reliable, sigh. */
    if (0 && sp->offset > 0 && strict > 0) {
      double offset;

      offset = sp->offset / (double) sp->sampling_rate;
      if (offset < min_offset)
        sample_offset = (int) (min_offset * sp->sampling_rate);
    }

    if (VBI_SLICED_WSS_625 & par->id) {
      /* TODO: WSS 625 occupies only first half of line,
         we can abort earlier. */
      cri_end = ~0;
    } else {
      cri_end = ~0;
    }

    samples_per_line = sp->bytes_per_line
        / VBI_PIXFMT_BPP (sp->sp_sample_format);

    if (!_vbi3_bit_slicer_init (&job->slicer)) {
      assert (!"bit_slicer_init");
    }

    if (!vbi3_bit_slicer_set_params
        (&job->slicer,
            sp->sp_sample_format,
            sp->sampling_rate,
            sample_offset,
            samples_per_line,
            par->cri_frc >> par->frc_bits,
            par->cri_frc_mask >> par->frc_bits,
            par->cri_bits,
            par->cri_rate,
            cri_end,
            (par->cri_frc & ((1U << par->frc_bits) - 1)),
            par->frc_bits, par->payload, par->bit_rate,
            (vbi3_modulation) par->modulation)) {
      assert (!"bit_slicer_set_params");
    }

    vbi3_bit_slicer_set_log_fn (&job->slicer,
        rd->log.mask, rd->log.fn, rd->log.user_data);

    lines_containing_data (start, count, sp, par);

    if (!add_job_to_pattern (rd, job - rd->jobs, start, count)) {
      error (&rd->log,
          "Out of decoder pattern space for "
          "service 0x%08x (%s).", par->id, par->label);
      continue;
    }

    job->id |= par->id;

    if (job >= rd->jobs + rd->n_jobs)
      ++rd->n_jobs;

    rd->services |= par->id;
  }

  return rd->services;
}

vbi_bool
vbi3_raw_decoder_sampling_point (vbi3_raw_decoder * rd,
    vbi3_bit_slicer_point * point, unsigned int row, unsigned int nth_bit)
{
  assert (NULL != rd);
  assert (NULL != point);

  if (row >= rd->n_sp_lines)
    return FALSE;

  if (nth_bit >= rd->sp_lines[row].n_points)
    return FALSE;

  *point = rd->sp_lines[row].points[nth_bit];

  return TRUE;
}

vbi_bool
vbi3_raw_decoder_debug (vbi3_raw_decoder * rd, vbi_bool enable)
{
#if 0                           /* Set but unused */
  _vbi3_raw_decoder_sp_line *sp_lines;
#endif
  unsigned int n_lines;
  vbi_bool r;

  assert (NULL != rd);

#if 0                           /* Set but unused */
  sp_lines = NULL;
#endif
  r = TRUE;

  rd->debug = ! !enable;

  n_lines = 0;
  if (enable) {
    n_lines = rd->sampling.count[0] + rd->sampling.count[1];
  }

  switch (rd->sampling.sp_sample_format) {
    case VBI_PIXFMT_YUV420:
      break;

    default:
      /* Not implemented. */
      n_lines = 0;
      r = FALSE;
      break;
  }

  if (rd->n_sp_lines == n_lines)
    return r;

  vbi_free (rd->sp_lines);
  rd->sp_lines = NULL;
  rd->n_sp_lines = 0;

  if (n_lines > 0) {
    rd->sp_lines = calloc (n_lines, sizeof (*rd->sp_lines));
    if (NULL == rd->sp_lines)
      return FALSE;

    rd->n_sp_lines = n_lines;
  }

  return r;
}

vbi_service_set
vbi3_raw_decoder_services (vbi3_raw_decoder * rd)
{
  assert (NULL != rd);

  return rd->services;
}

/**
 * $param rd Pointer to a vbi3_raw_decoder object allocated with
 *   vbi3_raw_decoder_new().
 * $param sp New sampling parameters.
 * $param strict See vbi3_raw_decoder_add_services().
 *
 * Changes the sampling parameters used by $a rd. This will
 * remove all services which have been added with
 * vbi3_raw_decoder_add_services() but cannot be decoded with
 * the new sampling parameters.
 *
 * $return
 * Set of data services $rd will be decode after the change.
 * Can be zero if the sampling parameters are invalid or some
 * other error occured.
 */
/* Attn: strict must be int for compatibility with libzvbi 0.2 (-1 == 0) */
vbi_service_set
    vbi3_raw_decoder_set_sampling_par
    (vbi3_raw_decoder * rd, const vbi_sampling_par * sp, int strict) {
  unsigned int services;

  assert (NULL != rd);
  assert (NULL != sp);

  services = rd->services;

  vbi3_raw_decoder_reset (rd);

  if (!_vbi_sampling_par_valid_log (sp, &rd->log)) {
    CLEAR (rd->sampling);
    return 0;
  }

  rd->sampling = *sp;

  /* Error ignored. */
  vbi3_raw_decoder_debug (rd, rd->debug);

  return vbi3_raw_decoder_add_services (rd, services, strict);
}

/**
 * $param rd Pointer to a vbi3_raw_decoder object allocated with
 *   vbi3_raw_decoder_new().
 * $param sp Sampling parameters will be stored here.
 *
 * Returns sampling parameters used by $a rd.
 */
void vbi3_raw_decoder_get_sampling_par
    (const vbi3_raw_decoder * rd, vbi_sampling_par * sp)
{
  assert (NULL != rd);
  assert (NULL != sp);

  *sp = rd->sampling;
}

void
vbi3_raw_decoder_set_log_fn (vbi3_raw_decoder * rd,
    vbi_log_fn * log_fn, void *user_data, vbi_log_mask mask)
{
  unsigned int i;

  assert (NULL != rd);

  if (NULL == log_fn)
    mask = 0;

  rd->log.mask = mask;
  rd->log.fn = log_fn;
  rd->log.user_data = user_data;

  for (i = 0; i < _VBI3_RAW_DECODER_MAX_JOBS; ++i) {
    vbi3_bit_slicer_set_log_fn (&rd->jobs[i].slicer, mask, log_fn, user_data);
  }
}

/**
 * @internal
 *
 * Free all resources associated with @a rd.
 */
void
_vbi3_raw_decoder_destroy (vbi3_raw_decoder * rd)
{
  vbi3_raw_decoder_reset (rd);

  vbi3_raw_decoder_debug (rd, FALSE);

  /* Make unusable. */
  CLEAR (*rd);
}

/**
 * @internal
 * 
 * See vbi3_raw_decoder_new().
 */
vbi_bool
_vbi3_raw_decoder_init (vbi3_raw_decoder * rd, const vbi_sampling_par * sp)
{
  CLEAR (*rd);

  vbi3_raw_decoder_reset (rd);

  if (NULL != sp) {
    if (!_vbi_sampling_par_valid_log (sp, &rd->log))
      return FALSE;

    rd->sampling = *sp;
  }

  return TRUE;
}

/**
 * $param rd Pointer to a vbi3_raw_decoder object allocated with
 *   vbi3_raw_decoder_new(), can be NULL
 *
 * Deletes a vbi3_raw_decoder object.
 */
void
vbi3_raw_decoder_delete (vbi3_raw_decoder * rd)
{
  if (NULL == rd)
    return;

  _vbi3_raw_decoder_destroy (rd);

  vbi_free (rd);
}

/**
 * $param sp VBI sampling parameters describing the raw VBI image
 *   to decode, can be $c NULL. If they are negotiatable you can determine
 *   suitable parameters with vbi_sampling_par_from_services(). You can
 *   change the sampling parameters later with
 *   vbi3_raw_decoder_set_sampling_par().
 *
 * Allocates a vbi3_raw_decoder object. To actually decode data
 * services you must request the data with vbi3_raw_decoder_add_services().
 *
 * $returns
 * NULL when out of memory or the sampling parameters are invalid,
 * Otherwise a pointer to an opaque vbi3_raw_decoder object which must
 * be deleted with vbi3_raw_decoder_delete() when done.
 */
vbi3_raw_decoder *
vbi3_raw_decoder_new (const vbi_sampling_par * sp)
{
  vbi3_raw_decoder *rd;

  rd = vbi_malloc (sizeof (*rd));
  if (NULL == rd) {
    errno = ENOMEM;
    return NULL;
  }

  if (!_vbi3_raw_decoder_init (rd, sp)) {
    vbi_free (rd);
    rd = NULL;
  }

  return rd;
}

/*
Local variables:
c-set-style: K&R
c-basic-offset: 8
End:
*/
