/*
 *  libzvbi -- Raw VBI sampling parameters
 *
 *  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: sampling_par.c,v 1.12 2013-08-28 14:45:00 mschimek Exp $ */

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

#include <errno.h>

#include "misc.h"
#include "raw_decoder.h"
#include "sampling_par.h"
#include "sliced.h"

#  define vbi_pixfmt_bytes_per_pixel VBI_PIXFMT_BPP
#  define sp_sample_format sampling_format

/**
 * @addtogroup Sampling Raw VBI sampling
 * @ingroup Raw
 * @brief Raw VBI data sampling interface.
 */

/**
 * @internal
 * Compatibility.
 */
vbi_videostd_set
_vbi_videostd_set_from_scanning (int scanning)
{
  switch (scanning) {
    case 525:
      return VBI_VIDEOSTD_SET_525_60;

    case 625:
      return VBI_VIDEOSTD_SET_625_50;

    default:
      break;
  }

  return 0;
}

_vbi_inline vbi_bool
range_check (unsigned int start,
    unsigned int count, unsigned int min, unsigned int max)
{
  /* Check bounds and overflow. */
  return (start >= min && (start + count) <= max && (start + count) >= start);
}

/**
 * @internal
 * @param sp Sampling parameters to verify.
 * 
 * @return
 * TRUE if the sampling parameters are valid (as far as we can tell).
 */
vbi_bool
_vbi_sampling_par_valid_log (const vbi_sampling_par * sp, _vbi_log_hook * log)
{
  vbi_videostd_set videostd_set;
  unsigned int bpp;

  assert (NULL != sp);

  switch (sp->sp_sample_format) {
    case VBI_PIXFMT_YUV420:
      /* This conflicts with the ivtv driver, which returns an
         odd number of bytes per line.  The driver format is
         _GREY but libzvbi 0.2 has no VBI_PIXFMT_Y8. */
      break;

    default:
      bpp = vbi_pixfmt_bytes_per_pixel (sp->sp_sample_format);
      if (0 != (sp->bytes_per_line % bpp))
        goto bad_samples;
      break;
  }

  if (0 == sp->bytes_per_line)
    goto no_samples;

  if (0 == sp->count[0]
      && 0 == sp->count[1])
    goto bad_range;

  videostd_set = _vbi_videostd_set_from_scanning (sp->scanning);

  if (VBI_VIDEOSTD_SET_525_60 & videostd_set) {
    if (VBI_VIDEOSTD_SET_625_50 & videostd_set)
      goto ambiguous;

    if (0 != sp->start[0]
        && !range_check (sp->start[0], sp->count[0], 1, 262))
      goto bad_range;

    if (0 != sp->start[1]
        && !range_check (sp->start[1], sp->count[1], 263, 525))
      goto bad_range;
  } else if (VBI_VIDEOSTD_SET_625_50 & videostd_set) {
    if (0 != sp->start[0]
        && !range_check (sp->start[0], sp->count[0], 1, 311))
      goto bad_range;

    if (0 != sp->start[1]
        && !range_check (sp->start[1], sp->count[1], 312, 625))
      goto bad_range;
  } else {
  ambiguous:
    info (log, "Ambiguous videostd_set 0x%lx.", (unsigned long) videostd_set);
    return FALSE;
  }

  if (sp->interlaced && (sp->count[0] != sp->count[1]
          || 0 == sp->count[0])) {
    info (log,
        "Line counts %u, %u must be equal and "
        "non-zero when raw VBI data is interlaced.",
        sp->count[0], sp->count[1]);
    return FALSE;
  }

  return TRUE;

no_samples:
  info (log, "samples_per_line is zero.");
  return FALSE;


bad_samples:
  info (log,
      "bytes_per_line value %u is no multiple of "
      "the sample size %u.",
      sp->bytes_per_line, vbi_pixfmt_bytes_per_pixel (sp->sp_sample_format));
  return FALSE;

bad_range:
  info (log,
      "Invalid VBI scan range %u-%u (%u lines), "
      "%u-%u (%u lines).",
      sp->start[0], sp->start[0] + sp->count[0] - 1,
      sp->count[0],
      sp->start[1], sp->start[1] + sp->count[1] - 1, sp->count[1]);
  return FALSE;
}

static vbi_bool
    _vbi_sampling_par_permit_service
    (const vbi_sampling_par * sp,
    const _vbi_service_par * par, unsigned int strict, _vbi_log_hook * log)
{
  const unsigned int unknown = 0;
  double signal;
  unsigned int field;
  unsigned int samples_per_line;
  vbi_videostd_set videostd_set;

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

  videostd_set = _vbi_videostd_set_from_scanning (sp->scanning);
  if (0 == (par->videostd_set & videostd_set)) {
    info (log,
        "Service 0x%08x (%s) requires "
        "videostd_set 0x%lx, "
        "have 0x%lx.",
        par->id, par->label,
        (unsigned long) par->videostd_set, (unsigned long) videostd_set);
    return FALSE;
  }

  if (par->flags & _VBI_SP_LINE_NUM) {
    if ((par->first[0] > 0 && unknown == (unsigned int) sp->start[0])
        || (par->first[1] > 0 && unknown == (unsigned int) sp->start[1])) {
      info (log,
          "Service 0x%08x (%s) requires known "
          "line numbers.", par->id, par->label);
      return FALSE;
    }
  }

  {
    unsigned int rate;

    rate = MAX (par->cri_rate, par->bit_rate);

    switch (par->id) {
      case VBI_SLICED_WSS_625:
        /* Effective bit rate is just 1/3 max_rate,
           so 1 * max_rate should suffice. */
        break;

      default:
        rate = (rate * 3) >> 1;
        break;
    }

    if (rate > (unsigned int) sp->sampling_rate) {
      info (log,
          "Sampling rate %f MHz too low "
          "for service 0x%08x (%s).",
          sp->sampling_rate / 1e6, par->id, par->label);
      return FALSE;
    }
  }

  signal = par->cri_bits / (double) par->cri_rate
      + (par->frc_bits + par->payload) / (double) par->bit_rate;

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

  if (0 && sp->offset > 0 && strict > 0) {
    double sampling_rate;
    double offset;
    double end;

    sampling_rate = (double) sp->sampling_rate;

    offset = sp->offset / sampling_rate;
    end = (sp->offset + samples_per_line) / sampling_rate;

    if (offset > (par->offset / 1e3 - 0.5e-6)) {
      info (log,
          "Sampling starts at 0H + %f us, too "
          "late for service 0x%08x (%s) at "
          "%f us.", offset * 1e6, par->id, par->label, par->offset / 1e3);
      return FALSE;
    }

    if (end < (par->offset / 1e9 + signal + 0.5e-6)) {
      info (log,
          "Sampling ends too early at 0H + "
          "%f us for service 0x%08x (%s) "
          "which ends at %f us",
          end * 1e6,
          par->id, par->label, par->offset / 1e3 + signal * 1e6 + 0.5);
      return FALSE;
    }
  } else {
    double samples;

    samples = samples_per_line / (double) sp->sampling_rate;

    if (strict > 0)
      samples -= 1e-6;          /* headroom */

    if (samples < signal) {
      info (log,
          "Service 0x%08x (%s) signal length "
          "%f us exceeds %f us sampling length.",
          par->id, par->label, signal * 1e6, samples * 1e6);
      return FALSE;
    }
  }

  if ((par->flags & _VBI_SP_FIELD_NUM)
      && !sp->synchronous) {
    info (log,
        "Service 0x%08x (%s) requires "
        "synchronous field order.", par->id, par->label);
    return FALSE;
  }

  for (field = 0; field < 2; ++field) {
    unsigned int start;
    unsigned int end;

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

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

    if (0 == sp->count[field]) {
      info (log,
          "Service 0x%08x (%s) requires "
          "data from field %u", par->id, par->label, field + 1);
      return FALSE;
    }

    /* (int) <= 0 for compatibility with libzvbi 0.2.x */
    if ((int) strict <= 0 || 0 == sp->start[field])
      continue;

    if (1 == strict && par->first[field] > par->last[field]) {
      /* May succeed if not all scanning lines
         available for the service are actually used. */
      continue;
    }

    if (start > par->first[field]
        || end < par->last[field]) {
      info (log,
          "Service 0x%08x (%s) requires "
          "lines %u-%u, have %u-%u.",
          par->id, par->label, par->first[field], par->last[field], start, end);
      return FALSE;
    }
  }

  return TRUE;
}

/**
 * @internal
 */
vbi_service_set
    _vbi_sampling_par_check_services_log
    (const vbi_sampling_par * sp,
    vbi_service_set services, unsigned int strict, _vbi_log_hook * log) {
  const _vbi_service_par *par;
  vbi_service_set rservices;

  assert (NULL != sp);

  rservices = 0;

  for (par = _vbi_service_table; par->id; ++par) {
    if (0 == (par->id & services))
      continue;

    if (_vbi_sampling_par_permit_service (sp, par, strict, log))
      rservices |= par->id;
  }

  return rservices;
}

/**
 * @internal
 */
vbi_service_set
    _vbi_sampling_par_from_services_log
    (vbi_sampling_par * sp,
    unsigned int *max_rate,
    vbi_videostd_set videostd_set_req,
    vbi_service_set services, _vbi_log_hook * log) {
  const _vbi_service_par *par;
  vbi_service_set rservices;
  vbi_videostd_set videostd_set;
  unsigned int rate;
  unsigned int samples_per_line;

  assert (NULL != sp);

  videostd_set = 0;

  if (0 != videostd_set_req) {
    if (0 == (VBI_VIDEOSTD_SET_ALL & videostd_set_req)
        || ((VBI_VIDEOSTD_SET_525_60 & videostd_set_req)
            && (VBI_VIDEOSTD_SET_625_50 & videostd_set_req))) {
      warn (log,
          "Ambiguous videostd_set 0x%lx.", (unsigned long) videostd_set_req);
      CLEAR (*sp);
      return 0;
    }

    videostd_set = videostd_set_req;
  }

  samples_per_line = 0;
  sp->sampling_rate = 27000000; /* ITU-R BT.601 */
  sp->offset = (int) (64e-6 * sp->sampling_rate);
  sp->start[0] = 30000;
  sp->count[0] = 0;
  sp->start[1] = 30000;
  sp->count[1] = 0;
  sp->interlaced = FALSE;
  sp->synchronous = TRUE;

  rservices = 0;
  rate = 0;

  for (par = _vbi_service_table; par->id; ++par) {
#if 0                           /* Set but unused */
    double margin;
#endif
    double signal;
    int offset;
    unsigned int samples;
    unsigned int i;

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

    if (0 == videostd_set_req) {
      vbi_videostd_set set;

      set = par->videostd_set | videostd_set;

      if (0 == (set & ~VBI_VIDEOSTD_SET_525_60)
          || 0 == (set & ~VBI_VIDEOSTD_SET_625_50))
        videostd_set |= par->videostd_set;
    }
#if 0                           /* Set but unused */
    if (VBI_VIDEOSTD_SET_525_60 & videostd_set)
      margin = 1.0e-6;
    else
      margin = 2.0e-6;
#endif

    if (0 == (par->videostd_set & videostd_set)) {
      info (log,
          "Service 0x%08x (%s) requires "
          "videostd_set 0x%lx, "
          "have 0x%lx.",
          par->id, par->label,
          (unsigned long) par->videostd_set, (unsigned long) videostd_set);
      continue;
    }

    rate = MAX (rate, par->cri_rate);
    rate = MAX (rate, par->bit_rate);

    signal = par->cri_bits / (double) par->cri_rate
        + ((par->frc_bits + par->payload) / (double) par->bit_rate);

    offset = (int) ((par->offset / 1e9) * sp->sampling_rate);
    samples = (int) ((signal + 1.0e-6) * sp->sampling_rate);

    sp->offset = MIN (sp->offset, offset);

    samples_per_line = MAX (samples_per_line + sp->offset,
        samples + offset) - sp->offset;

    for (i = 0; i < 2; ++i)
      if (par->first[i] > 0 && par->last[i] > 0) {
        sp->start[i] = MIN
            ((unsigned int) sp->start[i], (unsigned int) par->first[i]);
        sp->count[i] = MAX ((unsigned int) sp->start[i]
            + sp->count[i], (unsigned int) par->last[i] + 1)
            - sp->start[i];
      }

    rservices |= par->id;
  }

  if (0 == rservices) {
    CLEAR (*sp);
    return 0;
  }

  if (0 == sp->count[1]) {
    sp->start[1] = 0;

    if (0 == sp->count[0]) {
      sp->start[0] = 0;
      sp->offset = 0;
    }
  } else if (0 == sp->count[0]) {
    sp->start[0] = 0;
  }

  sp->scanning = (videostd_set & VBI_VIDEOSTD_SET_525_60)
      ? 525 : 625;
  sp->sp_sample_format = VBI_PIXFMT_YUV420;

  /* Note bpp is 1. */
  sp->bytes_per_line = MAX (1440U, samples_per_line);

  if (max_rate)
    *max_rate = rate;

  return rservices;
}

/**
 * @param sp Sampling parameters to check against.
 * @param services Set of data services.
 * @param strict See description of vbi_raw_decoder_add_services().
 *
 * Check which of the given services can be decoded with the given
 * sampling parameters at the given strictness level.
 *
 * @return
 * Subset of @a services decodable with the given sampling parameters.
 */
vbi_service_set
    vbi_sampling_par_check_services
    (const vbi_sampling_par * sp,
    vbi_service_set services, unsigned int strict) {
  return _vbi_sampling_par_check_services_log (sp, services, strict,
      /* log_hook */ NULL);
}

/**
 * @param sp Sampling parameters calculated by this function
 *   will be stored here.
 * @param max_rate If not NULL, the highest data bit rate in Hz of
 *   all services requested will be stored here. The sampling rate
 *   should be at least twice as high; @sp sampling_rate will
 *   be set to a more reasonable value of 27 MHz, which is twice
 *   the video sampling rate defined by ITU-R Rec. BT.601.
 * @param videostd_set Create sampling parameters matching these
 *   video standards. When 0 determine video standard from requested
 *   services.
 * @param services Set of VBI_SLICED_ symbols. Here (and only here) you
 *   can add @c VBI_SLICED_VBI_625 or @c VBI_SLICED_VBI_525 to include all
 *   vbi scan lines in the calculated sampling parameters.
 *
 * Calculate the sampling parameters required to receive and decode the
 * requested data @a services. The @a sp sampling_format will be
 * @c VBI_PIXFMT_Y8, offset and bytes_per_line will be set to
 * reasonable minimums. This function can be used to initialize hardware
 * prior to creating a vbi_raw_decoder object.
 * 
 * @return
 * Subset of @a services covered by the calculated sampling parameters.
 */
vbi_service_set
vbi_sampling_par_from_services (vbi_sampling_par * sp,
    unsigned int *max_rate,
    vbi_videostd_set videostd_set, vbi_service_set services)
{
  return _vbi_sampling_par_from_services_log (sp, max_rate,
      videostd_set, services,
      /* log_hook */ NULL);
}


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