/*
 * Copyright (C) 2002-2003 Fhg Fokus
 *
 * This file is part of SEMS, a free SIP media server.
 *
 * SEMS 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. This program is released under
 * the GPL with the additional exemption that compiling, linking,
 * and/or using OpenSSL is allowed.
 *
 * For a license to use the SEMS software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * SEMS 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 this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "AmAudio.h"
#include "AmPlugIn.h"
#include "AmUtils.h"
#include "AmSdp.h"
#include "amci/codecs.h"
#include "log.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#include <typeinfo>

/** \brief structure to hold loaded codec instances */
struct CodecContainer
{
  amci_codec_t *codec;
  int frame_size;
  int frame_length;
  int frame_encoded_size;
  long h_codec;
};

AmAudioRtpFormat::AmAudioRtpFormat(const vector<SdpPayload *>& payloads)
  : AmAudioFormat(), m_payloads(payloads), m_currentPayload(-1)
{
  for (vector<SdpPayload *>::iterator it = m_payloads.begin();
	  it != m_payloads.end(); ++it)
  {
    m_sdpPayloadByPayload[(*it)->payload_type] = *it;
  }
  setCurrentPayload(m_payloads[0]->payload_type);
}

int AmAudioRtpFormat::setCurrentPayload(int payload)
{
  if (m_currentPayload != payload)
  {
    std::map<int, SdpPayload *>::iterator p = 
      m_sdpPayloadByPayload.find(payload);
    if (p == m_sdpPayloadByPayload.end())
    {
      ERROR("Could not find payload <%i>\n", payload);
      return -1;
    }
    std::map<int, amci_payload_t *>::iterator pp = 
      m_payloadPByPayload.find(payload);
    if (pp == m_payloadPByPayload.end())
    {
      m_currentPayloadP = AmPlugIn::instance()->payload(p->second->int_pt);
      if (m_currentPayloadP == NULL)
      {
	ERROR("Could not find payload <%i>\n", payload);
	return -1;
      }
      m_payloadPByPayload[payload] = m_currentPayloadP;
    }
    else
      m_currentPayloadP = pp->second;
    m_currentPayload = payload;
    sdp_format_parameters = p->second->sdp_format_parameters;

    std::map<int, CodecContainer *>::iterator c = 
      m_codecContainerByPayload.find(payload);
    if (c == m_codecContainerByPayload.end())
    {
      codec = NULL;
      getCodec();
      if (codec)
      {
	CodecContainer *cc = new CodecContainer();
	cc->codec = codec;
	cc->frame_size = frame_size;
	cc->frame_length = frame_length;
	cc->frame_encoded_size = frame_encoded_size;
	cc->h_codec = h_codec;
	m_codecContainerByPayload[payload] = cc;
      }
    }
    else
    {
      codec = c->second->codec;
      frame_size = c->second->frame_size;
      frame_length = c->second->frame_length;
      frame_encoded_size = c->second->frame_encoded_size;
      h_codec = c->second->h_codec;
    }
    if (m_currentPayloadP && codec) {
      channels = m_currentPayloadP->channels;
      rate = m_currentPayloadP->sample_rate;
    } else {
      ERROR("Could not find payload <%i>\n", payload);
      return -1;
    }
  }
  return 0;
}

AmAudioRtpFormat::~AmAudioRtpFormat()
{
  for (std::map<int, CodecContainer *>::iterator it = 
	 m_codecContainerByPayload.begin(); 
       it != m_codecContainerByPayload.end(); ++it)
    delete it->second;
}

AmAudioFormat::AmAudioFormat()
  : channels(-1), rate(-1), codec(NULL),
    frame_length(20), frame_size(20*SYSTEM_SAMPLERATE/1000), frame_encoded_size(320)
{

}

AmAudioSimpleFormat::AmAudioSimpleFormat(int codec_id)
  : AmAudioFormat(), codec_id(codec_id)
{
  codec = getCodec();
  rate = SYSTEM_SAMPLERATE;
  channels = 1;
}


AmAudioFormat::~AmAudioFormat()
{
  destroyCodec();
}

unsigned int AmAudioFormat::calcBytesToRead(unsigned int needed_samples) const
{
  if (codec && codec->samples2bytes)
    return codec->samples2bytes(h_codec, needed_samples) * channels; // FIXME: channels

  WARN("Cannot convert samples to bytes\n");
  return needed_samples * channels;
}

unsigned int AmAudioFormat::bytes2samples(unsigned int bytes) const
{
  if (codec && codec->bytes2samples)
    return codec->bytes2samples(h_codec, bytes) / channels;
  WARN("Cannot convert bytes to samples\n");
  return bytes / channels;
}

bool AmAudioFormat::operator == (const AmAudioFormat& r) const
{
  return ( codec && r.codec
	   && (r.codec->id == codec->id) 
	   && (r.bytes2samples(1024) == bytes2samples(1024))
	   && (r.channels == channels)
	   && (r.rate == rate));
}

bool AmAudioFormat::operator != (const AmAudioFormat& r) const
{
  return !(this->operator == (r));
}

void AmAudioFormat::initCodec()
{
  amci_codec_fmt_info_t fmt_i[4];

  fmt_i[0].id=0;

  if( codec && codec->init ) {
    if ((h_codec = (*codec->init)(sdp_format_parameters.c_str(), fmt_i)) == -1) {
      ERROR("could not initialize codec %i\n",codec->id);
    } else {
      string s; 
      int i=0;
      while (fmt_i[i].id) {
	switch (fmt_i[i].id) {
	case AMCI_FMT_FRAME_LENGTH : {
	  frame_length=fmt_i[i].value; 
	} break;
	case AMCI_FMT_FRAME_SIZE: {
	  frame_size=fmt_i[i].value; 
	} break;
	case AMCI_FMT_ENCODED_FRAME_SIZE: {
	  frame_encoded_size=fmt_i[i].value; 
	} break;
	default: {
	  DBG("Unknown codec format descriptor: %d\n", fmt_i[i].id);
	} break;
	}
	i++;
      }
    }  
  } 
}

void AmAudioFormat::destroyCodec()
{
  if( codec && codec->destroy ){
    (*codec->destroy)(h_codec);
    h_codec = 0;
  }
  codec = NULL;
}

void AmAudioFormat::resetCodec() {
  codec = NULL;
  getCodec();
}

amci_codec_t* AmAudioFormat::getCodec()
{

  if(!codec){
    int codec_id = getCodecId();
    codec = AmPlugIn::instance()->codec(codec_id);

    initCodec();
  }
    
  return codec;
}

long AmAudioFormat::getHCodec()
{
  if(!codec)
    getCodec();
  return h_codec;
}

AmAudio::AmAudio()
  : fmt(new AmAudioSimpleFormat(CODEC_PCM16)),
    max_rec_time(-1),
    rec_time(0)
#ifdef USE_LIBSAMPLERATE 
  , resample_state(NULL),
    resample_buf_samples(0)
#endif
{
}

AmAudio::AmAudio(AmAudioFormat *_fmt)
  : fmt(_fmt),
    max_rec_time(-1),
    rec_time(0)
#ifdef USE_LIBSAMPLERATE 
  , resample_state(NULL),
    resample_buf_samples(0)
#endif
{
}

AmAudio::~AmAudio()
{
#ifdef USE_LIBSAMPLERATE 
  if (NULL != resample_state) 
    src_delete(resample_state);
#endif
}

void AmAudio::setFormat(AmAudioFormat* new_fmt) {
  fmt.reset(new_fmt);
  fmt->resetCodec();
}

void AmAudio::close()
{
}

// returns bytes read, else -1 if error (0 is OK)
int AmAudio::get(unsigned int user_ts, unsigned char* buffer, unsigned int nb_samples)
{
  int size = calcBytesToRead(nb_samples);

  size = read(user_ts,size);
  //DBG("size = %d\n",size);
  if(size <= 0){
    return size;
  }

  size = decode(size);
  if(size < 0) {
    DBG("decode returned %i\n",size);
    return -1; 
  }
  size = downMix(size);
    
  if(size>0)
    memcpy(buffer,(unsigned char*)samples,size);

  return size;
}

// returns bytes written, else -1 if error (0 is OK)
int AmAudio::put(unsigned int user_ts, unsigned char* buffer, unsigned int size)
{
  if(!size){
    return 0;
  }

  if(max_rec_time > -1 && rec_time >= max_rec_time)
    return -1;


  memcpy((unsigned char*)samples,buffer,size);

  int s = encode(size);
  if(s>0){
    //DBG("%s\n",typeid(this).name());
    incRecordTime(bytes2samples(size));
    return write(user_ts,(unsigned int)s);
  }
  else{
    return s;
  }
}

void AmAudio::stereo2mono(unsigned char* out_buf,unsigned char* in_buf,unsigned int& size)
{
  short* in  = (short*)in_buf;
  short* end = (short*)(in_buf + size);
  short* out = (short*)out_buf;
    
  while(in != end){
    *(out++) = (*in + *(in+1)) / 2;
    in += 2;
  }

  size /= 2;
}

int AmAudio::decode(unsigned int size)
{
  int s = size;

  if(!fmt.get()){
    DBG("no fmt !\n");
    return s;
  }

  amci_codec_t* codec = fmt->getCodec();
  long h_codec = fmt->getHCodec();

  if(!codec){
    ERROR("audio format set, but no codec has been loaded\n");
    return -1;
  }

  if(codec->decode){
    s = (*codec->decode)(samples.back_buffer(),samples,s,
			 fmt->channels,fmt->rate,h_codec);
    if(s<0) return s;
    samples.swap();
  }
    
  return s;
}

int AmAudio::encode(unsigned int size)
{
  int s = size;

//   if(!fmt.get()){
//     DBG("no encode fmt\n");
//     return 0;
//   }

  amci_codec_t* codec = fmt->getCodec();
  long h_codec = fmt->getHCodec();

  if(codec->encode){
    s = (*codec->encode)(samples.back_buffer(),samples,(unsigned int) size,
			 fmt->channels,fmt->rate,h_codec);
    if(s<0) return s;
    samples.swap();
  }
    
  return s;
}

unsigned int AmAudio::downMix(unsigned int size)
{
  unsigned int s = size;
  if(fmt->channels == 2){
    stereo2mono(samples.back_buffer(),(unsigned char*)samples,s);
    samples.swap();
  }

#ifdef USE_LIBSAMPLERATE 
  if (fmt->rate != SYSTEM_SAMPLERATE) {
    if (!resample_state) {
      int src_error;
      // for better quality but more CPU usage, use SRC_SINC_ converters
      resample_state = src_new(SRC_LINEAR, 1, &src_error);
      if (!resample_state) {
	ERROR("samplerate initialization error: ");
      }
    }

    if (resample_state) {
      if (resample_buf_samples + PCM16_B2S(s) > PCM16_B2S(AUDIO_BUFFER_SIZE) * 2) {
	WARN("resample input buffer overflow! (%d)\n",
	     resample_buf_samples + PCM16_B2S(s));
      } else {
	signed short* samples_s = (signed short*)(unsigned char*)samples;
	src_short_to_float_array(samples_s, &resample_in[resample_buf_samples], PCM16_B2S(s));
	resample_buf_samples += PCM16_B2S(s);
      }
      
      SRC_DATA src_data;
      src_data.data_in = resample_in;
      src_data.input_frames = resample_buf_samples;
      src_data.data_out = resample_out;
      src_data.output_frames = PCM16_B2S(AUDIO_BUFFER_SIZE);
      src_data.src_ratio = (double)SYSTEM_SAMPLERATE / (double)fmt->rate;
      src_data.end_of_input = 0;

      int src_err = src_process(resample_state, &src_data);
      if (src_err) {
	DBG("resample error: '%s'\n", src_strerror(src_err));
      }else {
	signed short* samples_s = (signed short*)(unsigned char*)samples;
	src_float_to_short_array(resample_out, samples_s, src_data.output_frames_gen);
	s = PCM16_S2B(src_data.output_frames_gen);

	if (resample_buf_samples !=  (unsigned int)src_data.input_frames_used) {
	  memmove(resample_in, &resample_in[src_data.input_frames_used], 
		  (resample_buf_samples - src_data.input_frames_used) * sizeof(float));
	}
	resample_buf_samples = resample_buf_samples - src_data.input_frames_used;
      }
    }
  }
#endif
 

  return s;
}

unsigned int AmAudio::getFrameSize()
{

  if (!fmt.get())
    fmt.reset(new AmAudioSimpleFormat(CODEC_PCM16));

  return fmt->frame_size;
}

unsigned int AmAudio::calcBytesToRead(unsigned int nb_samples) const
{
  return fmt->calcBytesToRead(nb_samples);
}

unsigned int AmAudio::bytes2samples(unsigned int bytes) const
{
  return fmt->bytes2samples(bytes);
}

void AmAudio::setRecordTime(unsigned int ms)
{
  max_rec_time = ms * (fmt->rate / 1000);
}

int AmAudio::incRecordTime(unsigned int samples)
{
  return rec_time += samples;
}


DblBuffer::DblBuffer()
  : active_buf(0)
{ 
}

DblBuffer::operator unsigned char*()
{
  return samples + (active_buf ? AUDIO_BUFFER_SIZE : 0);
}

unsigned char* DblBuffer::back_buffer()
{
  return samples + (active_buf ? 0 : AUDIO_BUFFER_SIZE);
}

void DblBuffer::swap()
{
  active_buf = !active_buf;
}

int AmAudioRtpFormat::getCodecId()
{
  if(!m_currentPayloadP){
    ERROR("AmAudioRtpFormat::getCodecId: could not find payload %i\n", m_currentPayload);
    return -1;
  }
  else 
    return m_currentPayloadP->codec_id;
}