/*
 *  seqinfo.c
 *
 *  Copyright (C) Thomas Oestreich - June 2001
 *
 *  This file is part of transcode, a video stream processing tool
 *
 *  transcode 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, or (at your option)
 *  any later version.
 *
 *  transcode 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "transcode.h"
#include "seqinfo.h"

pthread_mutex_t seq_list_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t seq_ctr_lock=PTHREAD_MUTEX_INITIALIZER;

seq_list_t *seq_list_head=NULL;
seq_list_t *seq_list_tail=NULL;

static int _sfd=0;

static double fps;
static int seq_ctr=0, drop_ctr=0;

seq_list_t *seq_register(int id)
{

  /* objectives:
     ===========

     register new seq

     allocate space for new seq and establish backward reference


  */

  seq_list_t *ptr;

  pthread_mutex_lock(&seq_list_lock);

  // retrive a valid pointer from the pool

  if((ptr = tc_malloc(sizeof(seq_list_t))) == NULL) {
    pthread_mutex_unlock(&seq_list_lock);
    return(NULL);
  }

  memset(ptr, 0, sizeof(seq_list_t));

  ptr->status = BUFFER_EMPTY;

  ptr->next = NULL;
  ptr->prev = NULL;

  ptr->id  = id;

  if(seq_list_tail != NULL)
  {
      seq_list_tail->next = ptr;
      ptr->prev = seq_list_tail;
  }

  seq_list_tail = ptr;

  /* first seq registered must set seq_list_head */

  if(seq_list_head == NULL) seq_list_head = ptr;

  pthread_mutex_unlock(&seq_list_lock);

  return(ptr);

}


/* ------------------------------------------------------------------ */


void seq_remove(seq_list_t *ptr)

{

  /* objectives:
     ===========

     remove seq from chained list

  */


  if(ptr == NULL) return;         // do nothing if null pointer

  pthread_mutex_lock(&seq_list_lock);

  if(ptr->prev != NULL) (ptr->prev)->next = ptr->next;
  if(ptr->next != NULL) (ptr->next)->prev = ptr->prev;

  if(ptr == seq_list_tail) seq_list_tail = ptr->prev;
  if(ptr == seq_list_head) seq_list_head = ptr->next;

  free(ptr);
  ptr=NULL;

  pthread_mutex_unlock(&seq_list_lock);

}


/* ------------------------------------------------------------------ */


seq_list_t *seq_retrieve()

{

  /* objectives:
     ===========

     get pointer to next full seq

  */

  seq_list_t *ptr;

  pthread_mutex_lock(&seq_list_lock);

  ptr = seq_list_head;

  /* move along the chain and check for status */

  while(ptr != NULL)
    {
      if(ptr->status == BUFFER_READY)
	{
	  pthread_mutex_unlock(&seq_list_lock);
	  return(ptr);
	}
      ptr = ptr->next;
    }

  pthread_mutex_unlock(&seq_list_lock);

  return(NULL);
}
/* ------------------------------------------------------------------ */


static void seq_flush_thread(void)
{

  seq_list_t *ptr, *tmp;

  ptr = seq_retrieve();

  if(ptr!=NULL) {

      if(verbose & TC_SYNC) {
          tc_log_msg(__FILE__, "syncinfo write (%d)", ptr->id);
      }

      seq_write(ptr);

      // release valid pointer to pool
      ptr->status = BUFFER_EMPTY;

      tmp=ptr->prev;
      seq_remove(tmp);

      pthread_mutex_lock(&seq_ctr_lock);
      --seq_ctr;
      pthread_mutex_unlock(&seq_ctr_lock);

  } else
     tc_log_error(__FILE__, "called but no work to do - this shouldn't happen");

  return;
}


static long frame_ctr=0;
static long check_ctr=0;


/* ------------------------------------------------------------------ */

void seq_write(seq_list_t *ptr)
{

  int i, k, clone[256];

  sync_info_t sync_info;

  int tmp;

  int inc=0;

  double ftot_pts=0.0f;

  // -----------------
  //
  // write to log file
  //
  // -----------------

  // set clone flag for each frame:
  // 0 = drop this frame
  // 1 = unique frame
  // N = use N copies of this frame


  //default
  for(i=0; i<ptr->enc_pics; ++i) clone[i]=1;

  if(ptr->adj_pics<0) {

      tmp =  ptr->adj_pics;
      inc = -ptr->enc_pics/ptr->adj_pics;

      while(-tmp>0) {
	k=(-tmp*inc)%ptr->enc_pics;
        clone[k]=0;

	++tmp;
      }
  }
  if(ptr->adj_pics>0) {

      tmp =  ptr->adj_pics;
      inc = (ptr->adj_pics<ptr->enc_pics) ? (int) ptr->enc_pics/ptr->adj_pics : 1;

      while(tmp>0) {

	  k=(tmp*inc)%ptr->enc_pics;
	  ++clone[k];
	  --tmp;
      }
  }

  //write out

  for(i=0; i<ptr->enc_pics; ++i) {

      //check for pulldown flag
      sync_info.pulldown = ptr->pulldown;

      //master flag makes final decision
      if(ptr->sync_active == 0) {
	  clone[i]=0;
	  sync_info.drop_seq=1;
	  sync_info.pulldown=0;
      } else
	  sync_info.drop_seq=0;

      if(verbose & TC_PRIVATE) {
          tc_log_msg(__FILE__, "[%ld] %d %d %d %ld",
                     frame_ctr, ptr->id, i, clone[i], check_ctr);
      }

      drop_ctr += (int) (clone[i]-1);

      sync_info.sequence = ptr->id;

      sync_info.enc_frame = (long) frame_ctr++;
      sync_info.adj_frame = (long) clone[i];

      ftot_pts=(double) ptr->tot_pts/90000;
      sync_info.dec_fps = (double) (ptr->tot_dec_pics)/ftot_pts;
      sync_info.enc_fps = (ptr->ptime) ? (double) (ptr->enc_pics*90000)/ptr->ptime:0.0f;

      sync_info.pts = (double) ptr->tot_pts/90000;

      if((tmp=tc_pwrite(_sfd, (uint8_t *) &sync_info, sizeof(sync_info_t)))!= sizeof(sync_info_t)) {
          tc_log_warn(__FILE__, "syncinfo write error (%d): %s",
                      tmp, strerror(errno));
      }
      check_ctr += clone[i];

      if(verbose & TC_SYNC && i==ptr->enc_pics-1) {
          tc_log_msg(__FILE__, "sync data for sequence %d flushed [%ld]",
                     ptr->id, sync_info.enc_frame);
      }
  }

  if(verbose & TC_PRIVATE) {
      tc_log_msg(__FILE__, "frames=%6ld seq=%4ld adj=%4d AV=%8.4f [fps] ratio= %.4f PTS= %.2f",
                 sync_info.enc_frame, sync_info.sequence, drop_ctr,
                 sync_info.dec_fps-fps, sync_info.enc_fps/fps, sync_info.pts);
  }

  return;

}


/* ------------------------------------------------------------------ */

void seq_update(seq_list_t *ptr, int end_pts, int pictures, int packets, int flag, int hard_fps)
{

  int tmp;

  long int adj=0;

  long int request_pics=0, delay=0;

  double ftot_pts=0.0;

  //set basic parameter from inout variables

  ptr->seq_pics    = pictures + ptr->pics_first_packet;
  ptr->enc_pics    = ptr->seq_pics;
  ptr->packet_ctr  = packets;
  ptr->ptime       = end_pts - ptr->pts;
  ptr->sync_active = flag;  //master flag

  if(ptr->ptime<=0 || ptr->id == 0) {
    adj=0;
    delay=0;
    goto skip;
  }

  if(ptr->id && ptr->sync_reset==0) {

      // (1) calculate total encoded frames:
      ptr->tot_enc_pics = ptr->prev->tot_enc_pics + ptr->enc_pics;

      // (2) calculate total decoded frames:
      ptr->tot_dec_pics = ptr->prev->tot_dec_pics + ptr->seq_pics;

      // (3) total number of packets:
      ptr->tot_packet_ctr = ptr->prev->tot_packet_ctr + ptr->packet_ctr;

      // (4) total time since first sequence
      ptr->tot_pts = ptr->prev->tot_pts + ptr->ptime;

  } else {

      // first sequence
      ptr->tot_enc_pics = ptr->enc_pics;
      ptr->tot_dec_pics = ptr->seq_pics;
      ptr->tot_packet_ctr = ptr->packet_ctr;
      ptr->tot_pts = ptr->ptime;
  }

  // (5) total frames as requested by transcode
  ftot_pts=(double) ptr->tot_pts/90000.;
  request_pics = (long) (fps * ftot_pts);

  // (6) drop or clone frames of this sequence?
  delay = request_pics - ptr->tot_dec_pics;

  adj=0;

  if(delay>0) { //frame cloning no limit yet

      if(delay<ptr->seq_pics) {
	  adj = delay;
      } else {
	  adj = delay - (delay%ptr->seq_pics);
      }
  }

  if(delay<0) { //frame dropping, maximum seq_pics/2

      tmp=-delay;

      if ( (-delay) >= (ptr->seq_pics/2)) {
	  adj = -(ptr->seq_pics/2);
      } else {
	  adj = delay;
      }
  }

  //3:2 pulldown?

  // disable smooth dropping, pulldown, etc. This makes sense for variable
  // framerates when the fps changes back and forth from 29.9 to 23.9 fps. The
  // smooth dropping will work very badly then.
 if (hard_fps == 0) {

  ptr->pulldown=0;

  if(adj == -3 && ptr->ptime == 45045 && ptr->seq_pics==15) ptr->pulldown = 1;
  if(adj == -4 && ptr->ptime == 45045 && ptr->seq_pics==15) ptr->pulldown = 2;
  if(adj == -2 && ptr->ptime ==  6006 && ptr->seq_pics== 4) ptr->pulldown = 3;
  if(adj == -1 && ptr->ptime == 39039 && ptr->seq_pics==11) ptr->pulldown = 4;

  //smooth drop/copy algorithm 2002-08-21
  if(ptr->pulldown==0) {
    if(adj==-1 || adj==1 || adj==2) adj=0;
    if(adj== 3) adj=1;
  }
 }

 skip:

  if(verbose & TC_PRIVATE) {

    tc_log_msg(__FILE__, "---------------------------------------------------------");
    tc_log_msg(__FILE__, "MPEG sequence: %d (reset=%d)", ptr->id, ptr->sync_reset);
    tc_log_msg(__FILE__, "2k packets: %d (%d) | stream size %.2f MB", ptr->packet_ctr, ptr->tot_packet_ctr, (double) 2*ptr->tot_packet_ctr/(1<<10));
    tc_log_msg(__FILE__, "PTS: %f (abs) --> runtime=%f (sec)", (double) ptr->pts/90000, ftot_pts);
    tc_log_msg(__FILE__, "sequence length: %f | ftime: %.4f (sec)", (double) ptr->ptime/90000, (double) ptr->ptime/90000/ptr->seq_pics);
    tc_log_msg(__FILE__, "sequence frames: %2d (current=%.3f fps) %ld (average=%.3f fps)", ptr->seq_pics, (double) ptr->seq_pics*90000/ptr->ptime, ptr->ptime, (double) ptr->tot_dec_pics/ftot_pts);
    tc_log_msg(__FILE__, "3:2 pulldown flag: %d (%f) | master_flag = %d", ptr->pulldown, fps * ftot_pts - ptr->tot_dec_pics, flag);
    tc_log_msg(__FILE__, "total frames (encoded in sequence 0-%d): %d (requested=%ld) %ld --> adjust: %ld", ptr->id, ptr->tot_enc_pics, request_pics, delay, adj);

  }

  //save the adjustment:
  ptr->tot_dec_pics +=adj;
  ptr->seq_pics     +=adj;
  ptr->adj_pics      =adj;

  // A-V shift at end of this sequence
  ptr->av_sync = (ptr->tot_dec_pics - request_pics)/fps;


  if(verbose & TC_PRIVATE) {

    tc_log_msg(__FILE__, "adjusted frames (decoded in sequence 0-%d): %d --> A-V: %.4f", ptr->id, ptr->tot_dec_pics, ptr->av_sync);
    tc_log_msg(__FILE__, "---------------------------------------------------------");
  }


  // -----------------
  //
  // write to log file
  //
  // -----------------

  ptr->status = BUFFER_READY;

  pthread_mutex_lock(&seq_ctr_lock);
  ++seq_ctr;
  pthread_mutex_unlock(&seq_ctr_lock);

  seq_flush_thread();

  return;

}

/* ------------------------------------------------------------------ */

/********
 * FIXME: these two functions, and the printf()s below, are only used
 * with the undocumented TC_DEMUX_SEQ_LIST (-M 5) mode.  Is printf()
 * appropriate, or for that matter is this even needed at all?  --AC
 ********/

static int seq_offset=0, unit_ctr=-1;

void seq_list_frames()
{
  if(unit_ctr==-1) return;
  tc_log_info(__FILE__, "%8ld video frame(s) in unit %d detected",
              (long) frame_ctr, unit_ctr);
}

/* ------------------------------------------------------------------ */

void seq_list(seq_list_t *ptr, int end_pts, int pictures, int packets, int flag)
{

  int n, id;

  int tmp;

  long int adj=0;

  long int request_pics=0, delay=0;

  double ftot_pts=0.0;

  //set basic parameter from inout variables

  //-------------------------------------------------------
  //for NTSC film
  //we need to recalculate ptr->seq_pics
  //-------------------------------------------------------

  id=ptr->id-seq_offset;

  ptr->seq_pics    = pictures + ptr->pics_first_packet;
  ptr->enc_pics    = ptr->seq_pics;

  ptr->ptime       = end_pts - ptr->pts;
  ptr->sync_active = flag;  //master flag

  //first sequence timing info may be wrong
  if(ptr->ptime<=0 || id == 0 || ptr->sync_reset) {
    adj=0;
    delay=0;
    goto skip;
  }

  if(ptr->id && ptr->sync_reset==0) {

      // (1) calculate total encoded frames:
      ptr->tot_enc_pics = ptr->prev->tot_enc_pics + ptr->enc_pics;

      // (2) calculate total decoded frames:
      ptr->tot_dec_pics = ptr->prev->tot_dec_pics + ptr->seq_pics;

      // (3) total number of packets:
      ptr->tot_packet_ctr = ptr->prev->tot_packet_ctr + ptr->packet_ctr;

      // (4) total time since first sequence
      ptr->tot_pts = ptr->prev->tot_pts + ptr->ptime;

  } else {

      ptr->tot_enc_pics = ptr->enc_pics;
      ptr->tot_dec_pics = ptr->seq_pics;
      ptr->tot_packet_ctr = ptr->packet_ctr;
      ptr->tot_pts = ptr->ptime;
  }

  // (5) total frames as requested by transcode
  ftot_pts=(double) ptr->tot_pts/90000;
  request_pics = (long) (fps * ftot_pts);

  // (6) drop or clone frames of this sequence?
  delay = request_pics - ptr->tot_dec_pics;

  adj=0;

  if(delay>0) { //frame cloning no limit yet

      if(delay<ptr->seq_pics) {
	  adj = delay;
      } else {
	  adj = delay - (delay%ptr->seq_pics);
      }
  }

  if(delay<0) { //frame dropping, maximum seq_pics/2

      tmp=-delay;

      if ( (-delay) >= (ptr->seq_pics/2)) {
	  adj = -(ptr->seq_pics/2);
      } else {
	  adj = delay;
      }
  }

  ptr->pulldown=0;

  //smooth drop/copy algorithm 2002-06-04
  if((adj==1 || adj == -1 || adj==2 || adj== -2 || adj== -3 || adj== 3)
     && ptr->pulldown==0) adj=0;
  if(adj >  3 && ptr->pulldown==0) adj= 1;
  if(adj < -3 && ptr->pulldown==0) adj=-1;

  //FIXME: drop all NTSC stuff, let transcode handle frame count
  adj=0;

 skip:

  //save the adjustment:
  ptr->tot_dec_pics +=adj;
  ptr->seq_pics     +=adj;
  ptr->adj_pics      =adj;

  // A-V shift at end of this sequence
  ptr->av_sync = (ptr->tot_dec_pics - request_pics)/fps;


  // -----------------
  //
  // write to log file
  //
  // -----------------

  ptr->status = BUFFER_READY;

  pthread_mutex_lock(&seq_ctr_lock);
  ++seq_ctr;
  pthread_mutex_unlock(&seq_ctr_lock);

  //print frame navigation information:

  if(ptr->sync_reset) {

    seq_list_frames();

    frame_ctr=0;
    seq_offset=ptr->id;
    ++unit_ctr;

    id=0;

  }

  //  tc_log_msg(__FILE__, "--- %d %d %d %d %d %d", delay, adj, ptr->seq_pics , ptr->enc_pics, ptr->pics_first_packet, pictures);

  //
  // first sequence of stream or new unit
  //

  if(id==0 || ptr->sync_reset) {

    for(n=0; n<ptr->enc_pics; ++n)
      printf("%2d %6ld %5d %5d %6ld %3d\n", unit_ctr, (long) frame_ctr++, id, id, (long) ptr->packet_ctr, n);

    return;
  }

  //
  // regular sequence
  //

  for(n=0; n<ptr->enc_pics; ++n) {

    if(n==0 || n==1) {
      printf("%2d %6ld %5d %5d %6ld %3d\n", unit_ctr, (long) frame_ctr++, id, id-1, (long) ptr->prev->packet_ctr, ptr->prev->seq_pics+n);
    } else {
      printf("%2d %6ld %5d %5d %6ld %3d\n", unit_ctr, (long) frame_ctr++, id, id, (long) ptr->packet_ctr, n);
    }
  }
  return;
}

/* ------------------------------------------------------------------ */

int seq_init(const char *logfile, int _ext_sfd, double _fps, int _verbose)
{

  //need to open the file

  if(logfile != NULL) {
    if((_sfd = open(logfile, O_WRONLY|O_CREAT, 0666))<0) {
      tc_log_error(__FILE__, "open logfile: %s", strerror(errno));
      return(-1);
    }
  }

  fps = _fps;

  //done

  if(_verbose & TC_DEBUG)
      tc_log_msg(__FILE__, "open %s for frame sync information", logfile);

  return(0);
}

/* ------------------------------------------------------------------ */

void seq_close()
{

  if(_sfd != 0) close(_sfd);
  _sfd=0;

  return;
}

