/* GStreamer
 * Copyright (C) 2009 David A. Schleef <ds@schleef.org>
 *
 * 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 St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/*
 * Utility functions for handing SMPTE Time Codes, as described in
 * SMPTE Standard 12M-1999.
 */

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

#include "gstsmptetimecode.h"

#define NTSC_FRAMES_PER_10_MINS (10*60*30 - 10*2 + 2)
#define NTSC_FRAMES_PER_HOUR (6*NTSC_FRAMES_PER_10_MINS)

/**
 * gst_smpte_time_code_from_frame_number:
 * @system: SMPTE Time Code system
 * @time_code: pointer to time code structure
 * @frame_number: integer frame number
 *
 * Converts a frame number to a time code.
 *
 * Returns: TRUE if the conversion was successful
 */
gboolean
gst_smpte_time_code_from_frame_number (GstSMPTETimeCodeSystem system,
    GstSMPTETimeCode * time_code, int frame_number)
{
  int ten_mins;
  int n;

  g_return_val_if_fail (time_code != NULL, FALSE);
  g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system), FALSE);

  time_code->hours = 99;
  time_code->minutes = 99;
  time_code->seconds = 99;
  time_code->frames = 99;

  if (frame_number < 0)
    return FALSE;

  switch (system) {
    case GST_SMPTE_TIME_CODE_SYSTEM_30:
      if (frame_number >= 24 * NTSC_FRAMES_PER_HOUR)
        return FALSE;

      ten_mins = frame_number / NTSC_FRAMES_PER_10_MINS;
      frame_number -= ten_mins * NTSC_FRAMES_PER_10_MINS;

      time_code->hours = ten_mins / 6;
      time_code->minutes = 10 * (ten_mins % 6);

      if (frame_number < 2) {
        /* treat the first two frames of each ten minutes specially */
        time_code->seconds = 0;
        time_code->frames = frame_number;
      } else {
        n = (frame_number - 2) / (60 * 30 - 2);
        time_code->minutes += n;
        frame_number -= n * (60 * 30 - 2);

        time_code->seconds = frame_number / 30;
        time_code->frames = frame_number % 30;
      }
      break;
    case GST_SMPTE_TIME_CODE_SYSTEM_25:
      if (frame_number >= 24 * 60 * 60 * 25)
        return FALSE;

      time_code->frames = frame_number % 25;
      frame_number /= 25;
      time_code->seconds = frame_number % 60;
      frame_number /= 60;
      time_code->minutes = frame_number % 60;
      frame_number /= 60;
      time_code->hours = frame_number;
      break;
    case GST_SMPTE_TIME_CODE_SYSTEM_24:
      if (frame_number >= 24 * 60 * 60 * 24)
        return FALSE;

      time_code->frames = frame_number % 24;
      frame_number /= 24;
      time_code->seconds = frame_number % 60;
      frame_number /= 60;
      time_code->minutes = frame_number % 60;
      frame_number /= 60;
      time_code->hours = frame_number;
      break;
  }

  return TRUE;
}

/**
 * gst_smpte_time_code_is_valid:
 * @system: SMPTE Time Code system
 * @time_code: pointer to time code structure
 *
 * Checks that the time code represents a valid time code.
 *
 * Returns: TRUE if the time code is valid
 */
gboolean
gst_smpte_time_code_is_valid (GstSMPTETimeCodeSystem system,
    GstSMPTETimeCode * time_code)
{
  g_return_val_if_fail (time_code != NULL, FALSE);
  g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system), FALSE);

  if (time_code->hours < 0 || time_code->hours >= 24)
    return FALSE;
  if (time_code->minutes < 0 || time_code->minutes >= 60)
    return FALSE;
  if (time_code->seconds < 0 || time_code->seconds >= 60)
    return FALSE;
  if (time_code->frames < 0)
    return FALSE;

  switch (system) {
    case GST_SMPTE_TIME_CODE_SYSTEM_30:
      if (time_code->frames >= 30)
        return FALSE;
      if (time_code->frames >= 2 || time_code->seconds > 0)
        return TRUE;
      if (time_code->minutes % 10 != 0)
        return FALSE;
      break;
    case GST_SMPTE_TIME_CODE_SYSTEM_25:
      if (time_code->frames >= 25)
        return FALSE;
      break;
    case GST_SMPTE_TIME_CODE_SYSTEM_24:
      if (time_code->frames >= 24)
        return FALSE;
      break;
  }
  return TRUE;
}

/**
 * gst_smpte_time_get_frame_number:
 * @system: SMPTE Time Code system
 * @frame_number: pointer to frame number
 * @time_code: pointer to time code structure
 *
 * Converts the time code structure to a linear frame number.
 *
 * Returns: TRUE if the time code could be converted
 */
gboolean
gst_smpte_time_code_get_frame_number (GstSMPTETimeCodeSystem system,
    int *frame_number, GstSMPTETimeCode * time_code)
{
  int frame = 0;

  g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system), FALSE);
  g_return_val_if_fail (time_code != NULL, FALSE);

  if (!gst_smpte_time_code_is_valid (system, time_code)) {
    return FALSE;
  }

  switch (system) {
    case GST_SMPTE_TIME_CODE_SYSTEM_30:
      frame = time_code->hours * NTSC_FRAMES_PER_HOUR;
      frame += (time_code->minutes / 10) * NTSC_FRAMES_PER_10_MINS;
      frame += (time_code->minutes % 10) * (30 * 60 - 2);
      frame += time_code->seconds * 30;
      break;
    case GST_SMPTE_TIME_CODE_SYSTEM_25:
      time_code->frames =
          25 * ((time_code->hours * 60 + time_code->minutes) * 60 +
          time_code->seconds);
      break;
    case GST_SMPTE_TIME_CODE_SYSTEM_24:
      time_code->frames =
          24 * ((time_code->hours * 60 + time_code->minutes) * 60 +
          time_code->seconds);
      break;
  }
  frame += time_code->frames;

  if (frame_number) {
    *frame_number = frame;
  }

  return TRUE;
}

/**
 * gst_smpte_time_get_timestamp:
 * @system: SMPTE Time Code system
 * @time_code: pointer to time code structure
 *
 * Converts the time code structure to a timestamp.
 *
 * Returns: Time stamp for time code, or GST_CLOCK_TIME_NONE if time
 *   code is invalid.
 */
GstClockTime
gst_smpte_time_code_get_timestamp (GstSMPTETimeCodeSystem system,
    GstSMPTETimeCode * time_code)
{
  int frame_number;

  g_return_val_if_fail (GST_SMPTE_TIME_CODE_SYSTEM_IS_VALID (system),
      GST_CLOCK_TIME_NONE);
  g_return_val_if_fail (time_code != NULL, GST_CLOCK_TIME_NONE);

  if (gst_smpte_time_code_get_frame_number (system, &frame_number, time_code)) {
    static const int framerate_n[3] = { 3000, 25, 24 };
    static const int framerate_d[3] = { 1001, 1, 1 };

    return gst_util_uint64_scale (frame_number,
        GST_SECOND * framerate_d[system], framerate_n[system]);
  }

  return GST_CLOCK_TIME_NONE;
}