/*
   File: mpeg2buff.cc

   By: Alex Theo de Jong
   Created: February 1996

   Description:
   "Multi-Threading Save" read/write buffer
*/

#ifdef GNU
#pragma implementation
#endif

#include "athread.hh"

#include <stdio.h>
#include <string.h>
#include <String.h>
#include <sys/time.h>
#include <fcntl.h>

#ifdef HAVE_MMX
#include <mmx.h>
#endif

#include "error.hh"
#include "debug.hh"
#include "util.hh"

#include "mpeg2const.hh"
#include "mpeg2buff.hh"

/*
 *
 * Memory
 *
 */

Memory::Memory(int size) : end_ahead(1), mem_size(size) {
               mem_min=mem_begin=mem_end=new unsigned char[mem_size];
               mem_max=mem_min+mem_size;
}
Memory::~Memory(){ delete[] mem_min; }



int Memory::reset(){ mem_begin=mem_end=mem_min; end_ahead=1; return 0; }

unsigned char Memory::getchr(){
    if (mem_begin>=mem_max){
      mem_begin=mem_min;
      end_ahead=1;
    }
    return *mem_begin++;
}

unsigned int Memory::getbits32(){
#ifdef HAVE_MMX
      if (mem_begin+4>=mem_max)
#endif
        return ((getchr() << 24) | (getchr() << 16) | (getchr() << 8) | getchr());
#ifdef HAVE_MMX
      else {
        mmx_m2r(movl,mem_begin,eax);
        __asm__ ("movl (%eax),%eax");
        __asm__ ("bswap %eax");
        mem_begin += 4;
      }
#endif
}

int Memory::mem_skip(int size){
  if (end_ahead){
    mem_begin+=size;
  }
  else {
    if (size<(mem_max-mem_begin)){
      mem_begin+=size;
    }
    else {
      end_ahead=1;
      mem_begin=mem_min+size-(mem_max-mem_begin);
    }
  }
  return size;
}


int Memory::put(unsigned char* data, int size){
  if (!end_ahead){
    memcpy(mem_end, data, size);
    mem_end+=size; 
  }
  else {
    if (size<(mem_max-mem_end)){
      memcpy(mem_end, data, size);
      mem_end+=size;
    }
    else {
      unsigned char* dataptr=data;
      int bytes=mem_max-mem_end;
      memcpy(mem_end, dataptr, bytes);   
      dataptr+=bytes;
      mem_end=mem_min;
      end_ahead=0;
      int lastbytes=size-bytes;
      memcpy(mem_end, dataptr, lastbytes);
      mem_end+=lastbytes;
      return bytes+lastbytes;
    }
  }
  return size;
}

  int Memory::used(){ return (end_ahead) ? (mem_end-mem_begin) : (mem_size - (mem_begin - mem_end)); }
  int Memory::unused(){ return (end_ahead) ? (mem_size - (mem_end - mem_begin)) : (mem_begin - mem_end); }
  unsigned int Memory::getbyte(){ return getchr(); }



int Memory::get(unsigned char* data, int size){
  if (end_ahead){
    memcpy(data, mem_begin, size);
    mem_begin+=size;
  }
  else {
    if (size<(mem_max-mem_begin)){
      memcpy(data, mem_begin, size);
      mem_begin+=size;
    }
    else {
      unsigned char* dataptr=data;
      int bytes=mem_max-mem_begin;
      memcpy(dataptr, mem_begin, bytes);
      dataptr+=bytes;
      mem_begin=mem_min;
      end_ahead=1;
      int lastbytes=size-bytes;
      memcpy(dataptr, mem_begin, lastbytes);
      mem_begin+=lastbytes;
      return bytes+lastbytes;
    }
  }
  return size;
}


/*
 *
 *   Mpeg2Buffer
 *
 */

Mpeg2Buffer::Mpeg2Buffer(int size) : Memory(size), completed(0) {
  TRACER("Mpeg2Buffer::Mpeg2Buffer(int size)");
}


Mpeg2Buffer::~Mpeg2Buffer(){
  TRACER("Mpeg2Buffer::~Mpeg2Buffer()");
}

int Mpeg2Buffer::close(){
  TRACER("int Mpeg2Buffer::close()");
  lock();
  completed=1;
  reset();
  signal_read();
  unlock();
  return 0;
}

int Mpeg2Buffer::waitforbytes(int size){ 
  lock();
  signal_write();  // waitforbytes does not move the pointer, therefore
                   // this should be done afterwards which means at the 
                   // beginning of the next call (the first is redundant)
#ifdef TRACEDEEP
  while ((rsize=used())<size && !completed){
    if (rsize < (0.05*mem_size)) msg("Memory usage < 5%");
    wait_read();
  }
#else
  while ((rsize=used())<size && !completed) {
//  while ((rsize=used())<=0 && !completed) {
    wait_read();
  }
#endif

  unlock();
  return (!completed) ? ((rsize < size) ? rsize : size) : rsize; 
}


int Mpeg2Buffer::skipbytes(int size){
  DDEBUGGER("int Mpeg2Buffer::skipbytes(int size)");
  lock();
  while (used()<size && !completed) wait_read();
  size = (completed && used()<size)
    ? size=mem_skip(used())     // get what's available!
    : size=mem_skip(size); 
  signal_write();
  unlock();
  return size;
}


int Mpeg2Buffer::write(unsigned char* data, int size){
  DDEBUGGER("int Mpeg2Buffer::write(unsigned char* data, int size)");
  lock();
#ifdef TRACEDEEP
  if (unused() < 0.05*mem_size) msg("Memory usage > 95%");
#endif
  while (unused()<size && !completed){
    wait_write();
  }
  size = (completed && unused()<size)
    ? put(data, unused())          // put what's available
    : put(data, size);
  signal_read();
  unlock();
  return size;
}


int Mpeg2Buffer::read(unsigned char* data, int size){
  DDEBUGGER("int Mpeg2Buffer::read(unsigned char* data, int size)");
  lock();
  while (used()<size && !completed) wait_read();
  size = (completed && used()<size)
    ? get(data, used())          // get what's available!
    : get(data, size); 
  signal_write();
  unlock();
  return size;
}


  void Mpeg2Buffer::lock(){ mem_lock.lock(); }
  void Mpeg2Buffer::unlock(){ mem_lock.unlock(); }
  void Mpeg2Buffer::wait_write(){ memory_in_cond.wait(&mem_lock); }
  void Mpeg2Buffer::wait_read(){ memory_out_cond.wait(&mem_lock); }
  void Mpeg2Buffer::signal_write(){ memory_in_cond.signal(); }
  void Mpeg2Buffer::signal_read(){ memory_out_cond.signal(); }
  int Mpeg2Buffer::available(){ lock(); int b=used(); unlock(); return b; }
  int Mpeg2Buffer::maximum() { return mem_size; }
  void Mpeg2Buffer::signal_buffer(){ lock(); signal_write(); unlock(); }


/*
 *
 * Mpeg2Input
 *
 */

Mpeg2Input::Mpeg2Input(const char* filename, int size, int b) : 
  Mpeg2Buffer(size), buffer_size(size), bitrate(b)
{
  TRACER("Mpeg2Input::Mpeg2Input(const char* filename, int size, int b)");

  if ((fd=open(filename, O_RDONLY, 0))<=0){
    String err="could not open file `";
    err+=filename;
    err+="' for reading";
    cerr << err.chars();
  }
  else {
    if (athr_create((void*(*)(void*)) Mpeg2Input::filereader, this, &id)<0){
      error("could not create reader thread");
      athr_exit(0);
    }
  }
}


void* Mpeg2Input::filereader(Mpeg2Input* base){
  TRACER("void* Mpeg2Input::filereader(Mpeg2Input* base)");

  int rsize=0, wsize=base->buffer_size/4;
  unsigned char* data=new unsigned char[wsize];
  int interval_usec=base->bitrate*wsize, time_real_usec;
  Mpeg2Timer timer;
  timeval tstart, tstop;

  if (base->bitrate)
    gettimeofday(&tstart, 0);
TRACER("Bitrate " << itoa(base->bitrate));
  int eof=0;

  while (base->fd && !eof){

    if ((rsize=::read(base->fd, data, wsize))<wsize) {
      eof=1;
    }

#ifdef DEBUG
    cout << "FileData: " << data << "\n";
    uint32 value;
    value=((data[0] << 24) | (data[1] << 16) |  (data[2] << 8) |  data[3]);
    cout << "FileValue: " << value << "\n";
#endif

    if (base->write(data, rsize)!=rsize)
      error("could not write file data in buffer");

    if (base->bitrate>0){
      gettimeofday(&tstop, 0);
      time_real_usec=(tstop.tv_sec-tstart.tv_sec)*1000000+(tstop.tv_usec-tstart.tv_usec);   
      if (time_real_usec<interval_usec)
	timer.wait(interval_usec - time_real_usec);
      gettimeofday(&tstart, 0);
    }
  }

  delete data;
  base->close();

  athr_exit(0);

  return 0;
}


/*
 *
 * Mpeg2Timer
 *
 */

Mpeg2Timer::Mpeg2Timer(){
}


Mpeg2Timer::~Mpeg2Timer(){
}

void Mpeg2Timer::lock(){ timer_lock.lock(); }
void Mpeg2Timer::unlock(){ timer_lock.unlock(); }


int Mpeg2Timer::wait(int interval_usec){ 
  if (interval_usec<0) error("invalid interval for timer to wait");

//  msg("!"); 
// This is very primitive!!

  gettimeofday(&time_new, 0);
  time_out=time_new;
  time_out.tv_usec+=interval_usec;
  if (time_out.tv_usec>1000000){
    time_out.tv_sec++;
    time_out.tv_usec-=1000000;
  }
  while (time_new.tv_sec<time_out.tv_sec || 
         (time_new.tv_sec==time_out.tv_sec && time_new.tv_usec<time_out.tv_usec)){
    athr_yield();
    gettimeofday(&time_new, 0);
  }
  return 1;
} 


int Mpeg2Timer::waitcond(int interval_usec){
  lock();
  gettimeofday(&time_new, 0);
  time.tv_sec=time_new.tv_sec;
  time.tv_nsec=(time_new.tv_usec+interval_usec)*1000;
  if (time.tv_nsec>1000000000){
    time.tv_sec+=(int)(time.tv_nsec/1000000000);
    time.tv_nsec-=(time.tv_nsec - (time.tv_nsec % 1000000000));
  }
  int val=timer_cond.timedwait(&timer_lock, &time);
  unlock();
  return val;
}

/*
  static sigset_t set;
  static siginfo_t info;
  info.si_signo=SIGALRM;
  sigemptyset(&set);
  sigaddset(&set, SIGALRM);
  thr_sigsetmask(SIG_UNBLOCK, &set, 0);
  int val=sigtimedwait(&set, &info, &time);
 
*/
/*
  Static sigset_t set;
  static itimerval val;

  sigemptyset(&set);
  sigaddset(&set, SIGVTALRM);
  thr_sigsetmask(SIG_BLOCK, &set, NULL);
  getitimer(ITIMER_VIRTUAL, &val);
  gettimeofday(&time_new, 0);
  val.it_interval.tv_usec=interval_usec;
  val.it_value.tv_sec=time_new.tv_sec;
  val.it_value.tv_usec=time_new.tv_usec + interval_usec;
  if (val.it_value.tv_usec>1000000){
    val.it_value.tv_sec++;
    val.it_value.tv_usec-=1000000;
  }
  setitimer(ITIMER_VIRTUAL, &val, 0);
  sigwait(&set);
*/


#ifdef MAIN

main(int argc, char** argv){
  TRACER("main(int argc, char** argv)");
  Mpeg2Input* myfile=new Mpeg2Input(argv[1], 10*418);

  unsigned char* data=new unsigned char[418];

  fflush(stdout);
  cout.flush();
  cerr.flush();

  bool eof=False;
  while (!eof){
    myfile->waitforbytes(418);
    //    TRACER("Data=" << data);
    TRACER("Value=" << myfile->getbits32());

    break;
  }
  fflush(stdout);
  cout.flush();
  cerr.flush();

  delete myfile;
  delete data;
}

#endif
