/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/***************************************************************************
 *            multiplexer.cc
 *
 *  Wed Aug 31 13:05:18 CEST 2005
 *  Copyright  2005 Bent Bisballe Nyeng
 *  deva@aasimon.org
 ****************************************************************************/

/*
 *  This file is part of MIaV.
 *
 *  MIaV 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 of the License, or
 *  (at your option) any later version.
 *
 *  MIaV 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 MIaV; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */

#include "config.h"
#include "multiplexer.h"

#include <netinet/in.h>
#include <math.h>

#define SIZEOF(x) (sizeof(x)-1)

// Audio index lists
/*
static unsigned int frequency_index[4] = {44100, 48000, 32000, 0};
//static unsigned int slots [4] = {12, 144, 0, 0};
//static unsigned int slot_index [4] = {144, 144, 144, 0};
//static unsigned int sample_index [4] = {384, 1152, 0, 0};
static unsigned int bitrate_index [4][16] = {
  {0, 0, 0, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,0}, // Reserved
  {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, // Layer III
  {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, // Layer II
  {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}  // Layer I
};
static char layer_index[4][12] = { "Reserved", "Layer III", "Layer II", "Layer I" };
static char mode_index[4][32] = { "Stereo", "Joint Stereo", "Dual Channel", "Single Channel"};
static char protection_index[2][32] = { "CRC check enabled", "CRC check disabled" };
*/
//static unsigned short int syncword = 0xFFF;

// Video index lists
/*
#define FORBIDDEN -1.0
#define RESERVED -2.0
static double picture_rate_index[16] = {
  FORBIDDEN, 23.976, 24.0, 25.0, 29.97, 30.0, 50.0, 59.94, 60,
  RESERVED, RESERVED, RESERVED, RESERVED, RESERVED, RESERVED, RESERVED
};
*/

Multiplexer::Multiplexer(File *f, Info *i, volatile bool *r,
                         FramePriorityQueue *v_q, pthread_mutex_t *v_m, sem_t *v_s, 
                         FramePriorityQueue *a_q, pthread_mutex_t *a_m, sem_t *a_s)
{
  running = r;
  file = f;
  info = i;

  queue[TYPE_VIDEO] = v_q;
  queue[TYPE_AUDIO] = a_q;

  sem[TYPE_VIDEO] = v_s;
  sem[TYPE_AUDIO] = a_s;

  mutex[TYPE_VIDEO] = v_m;
  mutex[TYPE_AUDIO] = a_m;

  frame[TYPE_VIDEO] = NULL;
  frame[TYPE_AUDIO] = NULL;

  frame_number[TYPE_VIDEO] = 0;
  frame_number[TYPE_AUDIO] = 0;

  write_system_header = 0;
  write_audio_packet = 0;

  audio_header_read = false;

  written[TYPE_VIDEO] = 0.0;
  written[TYPE_AUDIO] = 0.0;
}

Multiplexer::~Multiplexer()
{
}


int Multiplexer::read_stream(char *buf, unsigned int size, StreamType type)
{
  Frame *tmpframe;
  unsigned int copied = 0;

  while(copied < size && (*running) ) {

    // If we read the entire frame, prepare to get a new one
    if(frame[type] && read[type] == frame[type]->size) {
      delete frame[type];
      frame[type] = NULL;
    }

    // If no frame is in the buffer, get one from the queue
    while( frame[type] == NULL && (*running) ) {
      sem_wait(sem[type]);

      // Lock output mutex
      pthread_mutex_lock( mutex[type] );
      tmpframe = queue[type]->top();
      if(tmpframe && tmpframe->number == frame_number[type] ) {
        queue[type]->pop();
        frame[type] = tmpframe;
        frame_number[type]++;
        read[type] = 0;
      }
      pthread_mutex_unlock( mutex[type] );
      // Unlock output mutex
    }

    // If a frame exists in the buffer copy it to the output buffer
    // (No frame ocurres when *running is set to false)
    if( frame[type] ) {
      unsigned int doread = (size - copied) < (frame[type]->size - read[type]) ?
        size - copied : (frame[type]->size - read[type]);
      
      //info->info("Requested: %d. Read: %d. Doread: %d. In buffer %d", size, (*read), doread, (*frame)->size);
      
      memcpy(buf + copied, frame[type]->data + read[type], doread);
      read[type] += doread;
      copied += doread;
    }
  }

  return copied;
}

void Multiplexer::packet(StreamType type)
{
  char buf[1000000];

  file->Write((void*)ISO11172_1::packet_start_code_prefix, SIZEOF(ISO11172_1::packet_start_code_prefix));
  switch(type) {
  case TYPE_VIDEO:
    file->Write((void*)ISO11172_1::stream_id_video1, SIZEOF(ISO11172_1::stream_id_video1));
    break;
  case TYPE_AUDIO:
    file->Write((void*)ISO11172_1::stream_id_audio1, SIZEOF(ISO11172_1::stream_id_audio1));
    break;
  }

  // Write data
  info->info("\t\t[%sPacket]", type==TYPE_AUDIO?"Audio\0":"Video\0");

  unsigned short int hton_framesize = PACKET_SIZE + 1;
  hton_framesize = htons(hton_framesize);
  file->Write((char*)&hton_framesize, sizeof(hton_framesize));

  char dims[] = "\x0F";
  file->Write(dims, 1);

  file->Write(buf, read_stream(buf, PACKET_SIZE, type));

  written[type] += (double)PACKET_SIZE / (double)frame[type]->bitrate;
}

/**
 * Create and write a packet
 */
void Multiplexer::packet()
{
  info->info("\t\tWritten[A]: %f, Written[V]: %f", written[TYPE_AUDIO], written[TYPE_VIDEO]);

  // New switching mechanism
  if(written[TYPE_AUDIO] < written[TYPE_VIDEO]) {
    packet(TYPE_AUDIO);
  } else {
    packet(TYPE_VIDEO);
  }


  // Count this up here, we want audio packets in packet 4, 9, ... NOT 0, 3, ...
  /*
  write_audio_packet++;
  if(write_audio_packet % AUDIO_PACKET_FREQUENCY == 0) {
    packet(TYPE_AUDIO);
  } else {
    packet(TYPE_VIDEO);
  }
  */
}

/**
 * Create and write the system header
 */
void Multiplexer::system_header()
{
  info->info("\t\t[System Header]");
  
  // system_header_start_code (32 bits)
  file->Write((void*)ISO11172_1::system_header_start_code, SIZEOF(ISO11172_1::system_header_start_code));
  
  // header_length (16 bits)
  char system_header_length[]     = "\x00\x0C";
  file->Write(system_header_length, SIZEOF(system_header_length));
  
  // marker_bit (1 bit)   \.
  // rate_bound (22 bits)  ) (24 bits)
  // marker_bit (1 bit)   /
  char rate_bound[]     = "\x80\x1B\x83";
  file->Write(rate_bound, SIZEOF(rate_bound));
  
  // audio_bound (6 bits) \.
  // fixed_flag (1 bit)    ) (8 bits)
  // CSPS_flag (1 bit)    /
  char audio_bound[]     = "\x06"; // One audio stream, fixed bitrate and not iso costraint compliant
  file->Write(audio_bound, SIZEOF(audio_bound));

  // system_audio_lock_flag (1 bit) \.
  // system_video_lock_flag (1 bit)  \.
  // marker_bit (1 bit)               ) (8 bits)
  // video_bound (5 bits)           _/
  char video_bound[]     = "\xE1"; // Audio and Video are locked and there are only one video stream
  file->Write(video_bound, SIZEOF(video_bound));
 
  // reserved_byte (8 bit)
  char reserved_byte[]     = "\xFF";
  file->Write(reserved_byte, SIZEOF(reserved_byte));

  { // Audio
    // stream_id (8 bit)
    char stream_id[]     = "\xC0";
    file->Write(stream_id, SIZEOF(stream_id));
    
    // '11' (2 bits)                   \.
    // STD_buffer_bound_scale (1 bit)   ) (24 bits)
    // STD_buffer_size_bound (13 bits) /
    char reserved_byte[]     = "\xC0\x20";
    file->Write(reserved_byte, SIZEOF(reserved_byte));
  }

  { // Video
    // stream_id (8 bit)
    char stream_id[]     = "\xE3";
    file->Write(stream_id, SIZEOF(stream_id));
    
    // '11' (2 bits)                   \.
    // STD_buffer_bound_scale (1 bit)   ) (24 bits)
    // STD_buffer_size_bound (13 bits) /
    char reserved_byte[]     = "\xE0\x2E";
    file->Write(reserved_byte, SIZEOF(reserved_byte));
  }
}

/**
 * Create and write a pack
 */
void Multiplexer::pack()
{
  info->info("\t[Pack");

  file->Write((void*)ISO11172_1::pack_start_code, SIZEOF(ISO11172_1::pack_start_code));
  
  // Stuff! FIXME: Change this
  char stuff[]     = "\x21\x00\x01\x1E\x81\x80\x1B\x83";
  file->Write(stuff, SIZEOF(stuff));

  if(write_system_header % SYSTEM_HEADER_FREQUENCY == 0) system_header();
  // Count this up here, we want a system header in pack 0, 5, ... NOT 4, 9, ...
  write_system_header++;

  for(int cnt = 0; cnt < NUMBER_OF_PACKETS_IN_A_PACK; cnt++) packet();
  info->info("\t]");
}

/**
 *
 */
void Multiplexer::iso11172_stream()
{
  info->info("[iso11172_stream");
  while(*running) {
    pack();
  }
  info->info("]");
  info->info("[iso11172_end_code]");
  file->Write((void*)ISO11172_1::end_code, SIZEOF(ISO11172_1::end_code));
}

//#define BYPASS TYPE_VIDEO
//#define BYPASS TYPE_AUDIO
void Multiplexer::multiplex()
{
#ifdef BYPASS

  char buf[1024];
  while(*running) file->Write(buf, read_stream(buf, sizeof(buf), BYPASS));
  return;

#else/*BYPASS*/

  iso11172_stream();

#endif/*BYPASS*/
}