/*
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id$
 */


/*
 * This file is a shameless copy from the mp3-plugin
 * I have no idea what this code actually does ;)
 */

#include "sndplayer.h"

// --- cScale ----------------------------------------------------------------

enum eAudioMode { amRound, amDither };
#define OUT_BITS 16
#define OUT_FACT (OUT_BITS / 8 * 2)
#define LEN_CORR  3


class cResample {
private:
  mad_fixed_t ratio;
  mad_fixed_t step;
  mad_fixed_t last;
  mad_fixed_t resampled[MAX_NSAMPLES];
public:
  bool SetInputRate(unsigned int oldrate, unsigned int newrate);
  unsigned int ResampleBlock(unsigned int nsamples, const mad_fixed_t *old);
  const mad_fixed_t *Resampled(void) { return resampled; }
  };

bool cResample::SetInputRate(unsigned int oldrate, unsigned int newrate)
{
  if(oldrate<8000 || oldrate>newrate*6) { // out of range
    esyslog("WARNING: samplerate %d out of range 8000-%d\n",oldrate,newrate*6);
    return 0;
    }
  ratio=mad_f_tofixed((double)oldrate/(double)newrate);
  step=0; last=0;
  static mad_fixed_t oldratio=0;
  if(oldratio!=ratio) {
    isyslog("mad: new resample ratio %f (from %d kHz to %d kHz)\n",mad_f_todouble(ratio),oldrate,newrate);
    oldratio=ratio;
    }
  return ratio!=MAD_F_ONE;
}

unsigned int cResample::ResampleBlock(unsigned int nsamples, const mad_fixed_t *old)
{
  // This resampling algorithm is based on a linear interpolation, which is
  // not at all the best sounding but is relatively fast and efficient.
  //
  // A better algorithm would be one that implements a bandlimited
  // interpolation.

  mad_fixed_t *nsam=resampled;
  const mad_fixed_t *end=old+nsamples;
  const mad_fixed_t *begin=nsam;

  if(step < 0) {
    step = mad_f_fracpart(-step);

    while (step < MAD_F_ONE) {
      *nsam++ = step ? last+mad_f_mul(*old-last,step) : last;
      step += ratio;
      if(((step + 0x00000080L) & 0x0fffff00L) == 0)
	step = (step + 0x00000080L) & ~0x0fffffffL;
      }
    step -= MAD_F_ONE;
    }

  while (end - old > 1 + mad_f_intpart(step)) {
    old += mad_f_intpart(step);
    step = mad_f_fracpart(step);
    *nsam++ = step ? *old + mad_f_mul(old[1] - old[0], step) : *old;
    step += ratio;
    if (((step + 0x00000080L) & 0x0fffff00L) == 0)
      step = (step + 0x00000080L) & ~0x0fffffffL;
    }

  if (end - old == 1 + mad_f_intpart(step)) {
    last = end[-1];
    step = -step;
    }
  else step -= mad_f_fromint(end - old);

  return nsam-begin;
}

class cScale {
private:
  enum { MIN = -MAD_F_ONE, MAX = MAD_F_ONE - 1 };
  // dither
  struct dither {
    mad_fixed_t error[3];
    mad_fixed_t random;
  } leftD, rightD;
  //
  inline mad_fixed_t Clip(mad_fixed_t sample, bool stats = true);
  inline signed long LinearRound(mad_fixed_t sample);
  inline unsigned long Prng(unsigned long state);
  inline signed long LinearDither(mad_fixed_t sample, struct dither *dither);
public:
  void Init(void);
  unsigned int ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode);
};

void cScale::Init(void)
{
  memset(&leftD, 0, sizeof(leftD));
  memset(&rightD, 0, sizeof(rightD));
}

mad_fixed_t cScale::Clip(mad_fixed_t sample, bool stats)
{
  if (sample > MAX) sample = MAX;
  if (sample < MIN) sample = MIN;
  return sample;
}

signed long cScale::LinearRound(mad_fixed_t sample)
{
  // round
  sample += (1L << (MAD_F_FRACBITS - OUT_BITS));
  // clip
  sample = Clip(sample);
  // quantize and scale
  return sample >> (MAD_F_FRACBITS + 1 - OUT_BITS);
}

unsigned long cScale::Prng(unsigned long state)
{
  return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
}

signed long cScale::LinearDither(mad_fixed_t sample, struct dither *dither)
{
  unsigned int scalebits;
  mad_fixed_t output, mask, random;

  // noise shape
  sample += dither->error[0] - dither->error[1] + dither->error[2];
  dither->error[2] = dither->error[1];
  dither->error[1] = dither->error[0] / 2;
  // bias
  output = sample + (1L << (MAD_F_FRACBITS + 1 - OUT_BITS - 1));
  scalebits = MAD_F_FRACBITS + 1 - OUT_BITS;
  mask = (1L << scalebits) - 1;
  // dither
  random  = Prng(dither->random);
  output += (random & mask) - (dither->random & mask);
  dither->random = random;
  // clip
  output = Clip(output);
  sample = Clip(sample, false);
  // quantize
  output &= ~mask;
  // error feedback
  dither->error[0] = sample - output;
  // scale
  return output >> scalebits;
}

unsigned int cScale::ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode)
{
  signed int sample;
  unsigned int len, res;

  len = size / OUT_FACT; 
  res = size;
  
  if (len > nsamples) { 
    len = nsamples; 
    res = len * OUT_FACT; 
  }
  
  nsamples -= len;

  if (right) {  // stereo
    switch (mode) {
      case amRound:
        while (len--) {
          sample  = LinearRound(*left++);
          *data++ = sample >> 8;
          *data++ = sample >> 0;
          sample  = LinearRound(*right++);
          *data++ = sample >> 8;
          *data++ = sample >> 0;
        }
        break;
      case amDither:
        while (len--) {
	  sample  = LinearDither(*left++,&leftD);
	  *data++ = sample >> 8;
	  *data++ = sample >> 0;
	  sample  = LinearDither(*right++,&rightD);
	  *data++ = sample >> 8;
	  *data++ = sample >> 0;
        }
        break;
    }
  }
  else {  // mono, duplicate left channel
    switch (mode) {
      case amRound:
        while (len--) {
	  sample  = LinearRound(*left++);
	  *data++ = sample >> 8;
	  *data++ = sample >> 0;
	  *data++ = sample >> 8;
	  *data++ = sample >> 0;
        }
        break;
      case amDither:
        while (len--) {
	  sample  = LinearDither(*left++, &leftD);
	  *data++ = sample >> 8;
	  *data++ = sample >> 0;
	  *data++ = sample >> 8;
	  *data++ = sample >> 0;
        }
        break;
    }
  }
  
  return res;
}
 
// --- cDecoder -------------------------------------------------------------

cDecoder::cDecoder(const char *Filename)
{
  filename = strdup(Filename);
}

cDecoder::~cDecoder()
{
  free(filename);
}


// --- cSndFile ----------------------------------------------------------------

cSndFile::cSndFile(const char *Filename)
{
  sf = 0;
}

cSndFile::~cSndFile()
{
  Close();
}

bool cSndFile::Open(char *Filename)
{
  sf = sf_open(Filename, SFM_READ, &sfi);
  
  return sf != 0;
}

void cSndFile::Close(void)
{
  if (sf) { 
    sf_close(sf); 
    sf = 0;
  }
}

sf_count_t cSndFile::Stream(int *buffer, sf_count_t frames)
{
  sf_count_t n = sf_readf_int(sf, buffer, frames);
  return n;
}

// --- cSndDecoder -------------------------------------------------------------

cSndDecoder::cSndDecoder(const char *Filename):cDecoder(Filename),file(Filename)
{
  pcm = 0;
  Init();
}

cSndDecoder::~cSndDecoder()
{
  Clean();
}

bool cSndDecoder::Valid(void)
{
  bool res = false;

  
  if (file.Open(filename))
    res = true;

  return res;
}

void cSndDecoder::Init(void)
{
  Clean();
  pcm = new struct mad_pcm;
  index = 0;
}

bool cSndDecoder::Clean(void)
{
  playing = false;
  delete pcm; 
  pcm = 0;
  file.Close();
  return false;
}

bool cSndDecoder::Start(void)
{
  Init(); 
  playing = true;

  if (file.Open(filename)) {
    return true;
  }
  return Clean();
}


bool cSndDecoder::Stop(void)
{

  if (playing) 
    Clean();

  return true;
}

struct Decode *cSndDecoder::Done(eDecodeStatus status)
{
 
  ds.status = status;
  ds.index = index * 1000; // /info.SampleFreq;
  ds.pcm = pcm;
  
  return &ds;
}

struct Decode *cSndDecoder::Decode(void)
{

  if (playing) {
    int framebuff[2 * SF_SAMPLES];
    int n = file.Stream(framebuff, SF_SAMPLES);

    if (n < 0) 
      return Done(dsError);
    
    if (!n)
      return Done(dsEof);

    pcm->samplerate = file.sfi.samplerate;
    pcm->channels = file.sfi.channels;
    pcm->length = n;
    index += n;

    int *data = framebuff;
    mad_fixed_t *sam0 = pcm->samples[0], *sam1 = pcm->samples[1];

    const int s = (sizeof(int) * 8) -1 - MAD_F_FRACBITS; // shift value for mad_fixed conversion
    
    if (pcm->channels > 1) {
      for (; n>0 ; n--) {
        *sam0++ = (*data++) >> s;
        *sam1++ = (*data++) >> s;
      }
    }
    else {
      for (; n>0 ; n--)
        *sam0++ = (*data++) >> s;
    }
    
    return Done(dsPlay);
  }
  
  return Done(dsError);
}


cSndPlayer::cSndPlayer(const char *fn):cPlayer(pmAudioOnly)
{

  active = true; 
  started = false;
  ringBuffer = new cRingBufferFrame(MP3BUFSIZE);
  rframe = 0; 
  pframe = 0; 
  decoder = 0;
  playMode = pmStartup; 
  state = msStop;
  filename = strdup(fn); 
  index = 0;
}

cSndPlayer::~cSndPlayer()
{

  Detach();
  delete ringBuffer;
  free(filename);
}

void cSndPlayer::Activate(bool On)
{
  
  if (On) {
    if (!started) {
      playMode = pmStartup;
      Start(); 
      started = true;
      playModeMutex.Lock();
      WaitPlayMode(pmStartup, true); // wait for the decoder to become ready
      playModeMutex.Unlock();
    }
  }
  else if (started && active) {
    Lock();
    StopPlay();
    Unlock(); 
    active = false;
    SetPlayMode(pmStartup);
    Cancel(2);
  }
}

void cSndPlayer::SetPlayMode(ePlayMode mode)
{
  
  playModeMutex.Lock();
  
  if (mode != playMode) {
    playMode = mode;
    playModeCond.Broadcast();
  }
  playModeMutex.Unlock();
}

void cSndPlayer::WaitPlayMode(ePlayMode mode, bool inv)
{

  // must be called with playModeMutex LOCKED !!!

  return; //FIXME 
  while (active && ((!inv && mode != playMode) || (inv && mode == playMode))) {
    playModeCond.Wait(playModeMutex);
  }
}

void cSndPlayer::Action(void)
{
  struct Decode *ds = 0;
  struct mad_pcm *pcm = 0;
  cResample resample[2];
  unsigned int nsamples[2];
  const mad_fixed_t *data[2];
  cScale scale;
  bool haslevel = false;
  struct LPCMFrame lpcmFrame;
  const unsigned char *p = 0;
  int pc = 0, only48khz = 0, n;
  cPoller poll;

  n = 0; 


  memset(&lpcmFrame, 0, sizeof(lpcmFrame));
  lpcmFrame.PES[2] = 0x01;
  lpcmFrame.PES[3] = 0xbd;
  lpcmFrame.PES[6] = 0x87;
  lpcmFrame.LPCM[0] = 0xa0; // substream ID
  lpcmFrame.LPCM[1] = 0xff;
  lpcmFrame.LPCM[5] = 0x01;
  lpcmFrame.LPCM[6] = 0x80;

  dvbSampleRate = 48000;
  // FIXME:
  // state = msStop;
  // SetPlayMode(pmStopped);
  state = msStart;
  SetPlayMode(pmPlay);
 
  while (active) {
    Lock();
    if (!rframe && playMode == pmPlay) {
      switch (state) {
        case msStart     : d(printf("msStart\n")) break;
        case msStop      : d(printf("msStop\n"))  break; 
        case msDecode    : d(printf("msDecode\n")) break;
        case msNormalize : d(printf("msNormalize\n")) break;
        case msResample  : d(printf("msResample\n")) break;
        case msOutput    : d(printf("msOutput\n")) break;
        case msError     : d(printf("msError\n")) break;
        case msEof       : d(printf("msEof\n")) break;
        case msWait      : d(printf("msWait\n")) break;
        default		 : d(printf("state UNKNOWN\n")) break; 
      }

      switch (state) {
        case msStart :
          index = 0;
          
          if ((decoder = new cSndDecoder(filename)) && decoder->Start()) {
            levelgood = 1; 
            haslevel = false;
            scale.Init();
            state = msDecode;
            break;
          }
          state = msEof;
          break;
        case msDecode :
          ds = decoder->Decode();
          isyslog("KWASI-DEBUG: ds->status=%d\n", ds->status);
          switch (ds->status) {
            case dsPlay :
              pcm = ds->pcm;
              index = ds->index / 1000;
              state = msNormalize;
              break;
            case dsSoftError:
              break;
            case dsEof:
              state = msEof;
              break;
            case dsOK:
            case dsError:
              state = msError;
              break;
          }
          break;
        case msNormalize :
          state = msResample;
          break;
        case msResample :
          nsamples[0] = nsamples[1] = pcm->length;
          data[0] = pcm->samples[0];
          data[1] = pcm->channels > 1 ? pcm->samples[1] : 0;

          lpcmFrame.LPCM[5] &= 0xcf;
          dvbSampleRate = 48000;
	            
          if (!only48khz) {
            isyslog("KWASI-DEBUG: pcm->samplerate=%d\n", pcm->samplerate);
          
            switch (pcm->samplerate) {    // If one of the supported frequencies, do it without resampling.
              case 96000:                 // Select a "even" upsampling frequency if possible, too.
                lpcmFrame.LPCM[5] |= 1 << 4;
                dvbSampleRate = 96000;
                break;
              case 11025:
              case 22050:
              case 44100:
                lpcmFrame.LPCM[5] |= 2 << 4;
                dvbSampleRate = 44100;
                break;
              case 8000:
              case 16000:
              case 32000:
                lpcmFrame.LPCM[5] |= 3 << 4;
                dvbSampleRate = 32000;
                break;
            }
          }
          if(dvbSampleRate!=pcm->samplerate) {
            if(resample[0].SetInputRate(pcm->samplerate,dvbSampleRate)) {
              nsamples[0]=resample[0].ResampleBlock(nsamples[0],data[0]);
              data[0]    =resample[0].Resampled();
              }
            if(data[1] && resample[1].SetInputRate(pcm->samplerate,dvbSampleRate)) {
              nsamples[1]=resample[1].ResampleBlock(nsamples[1],data[1]);
              data[1]    =resample[1].Resampled();
              }
          }
          state = msOutput;
          break;
        case msOutput :
          if (nsamples[0] > 0) {
            unsigned int outlen = scale.ScaleBlock(lpcmFrame.Data, sizeof(lpcmFrame.Data), nsamples[0], data[0], data[1], amRound);

            if (outlen) { 
              outlen += sizeof(lpcmFrame.LPCM) + LEN_CORR;
              lpcmFrame.PES[4] = outlen >> 8;
              lpcmFrame.PES[5] = outlen;
              rframe = new cFrame((unsigned char *)&lpcmFrame, outlen + sizeof(lpcmFrame.PES) - LEN_CORR);
            }
          }
          else 
            state = msDecode;
          break;
        case msError :
        case msEof :
          state = msWait;
          // fall through
        case msStop :
          if (decoder) { 
            decoder->Stop(); 
            decoder = 0;
          }
          levelgood = false;
          
          if (state == msStop) 
            SetPlayMode(pmStopped);
          break;
        case msWait :
          if (ringBuffer->Available() == 0) {
            active = false;
            SetPlayMode(pmStopped);
          }
          break;
      }
    }

    if (rframe && ringBuffer->Put(rframe)) 
      rframe = 0;

    if (!pframe && playMode == pmPlay) {
      pframe = (cFrame*) ringBuffer->Get(); // cast is done to let plugin compile for vdr < 1.2.29, for greater versions it has no effect
      
      if (pframe) {
        p = pframe->Data();
        pc = pframe->Count();
      }
    }

    if (pframe) {
      n += pc;
      isyslog("KWASI-DEBUG: PlayVideo(%d Byte, %d Byte played)\n", pc, n);

      int w = PlayPes(p, pc,false);
      if(w > 0) {
        p += w; 
        pc -= w;
        if (pc <= 0) {
          ringBuffer->Drop(pframe);
          pframe = 0;
        }
      }
      else if (w < 0 && FATALERRNO) {
        LOG_ERROR;
        break;
      }
    }
    Unlock();
    if ((rframe || state == msWait) && pframe)
      DevicePoll(poll, 500); // Wait for output to become ready
    else if (playMode != pmPlay) {
      playModeMutex.Lock();
      if (playMode != pmPlay) 
        WaitPlayMode(playMode, true);  // Wait on playMode change
      playModeMutex.Unlock();
    }
  }

  Lock(); 
  delete rframe; 
  rframe = 0;
  
  if (decoder) { 
    decoder->Stop(); 
    decoder = 0; 
  }
  Unlock();
  SetPlayMode(pmStopped);
  active = false;

}

void cSndPlayer::Empty(void)
{
  Lock();
  delete rframe; 
  rframe = 0; 
  pframe = 0;
  ringBuffer->Clear();
  DeviceClear();
  Unlock();
}


void cSndPlayer::StopPlay(void) // StopPlay() must be called in locked state!!!
{

  if (playMode != pmStopped) {
    Empty();
    state = msStop;
    SetPlayMode(pmPlay);
    Unlock();
    playModeMutex.Lock();
    WaitPlayMode(pmStopped, false);
    playModeMutex.Unlock();
    Lock();
  }
}

void cSndPlayer::Play(void)
{
  Lock();
  if (playMode != pmPlay) {
    if (playMode == pmStopped) 
      state = msStart;
    SetPlayMode(pmPlay);
  }
  Unlock();	
 
}
// --- cMP3PlayerControl -------------------------------------------------------

cSndPlayerControl::cSndPlayerControl(const char *fn)
:cControl(sndplayer = new cSndPlayer(fn))
{
    isyslog("KWASI-DEBUG: fn=%s",fn);
    Play();
}

cSndPlayerControl::~cSndPlayerControl()
{
  Stop();
}

bool cSndPlayerControl::Active(void)
{
  return sndplayer && sndplayer->Active();
}

void cSndPlayerControl::Stop(void)
{
  delete sndplayer; sndplayer=0;
}

void cSndPlayerControl::Play(void)
{
  if(sndplayer) sndplayer->Play();
}
eOSState cSndPlayerControl::ProcessKey(eKeys Key)
{
  if(!Active()) 
  {
//    Interface->Status(NULL); // clear status message, if any.
	  // instead of returning osEnd,
	  // we shutdown ourself (else vdr thinks we are still replaying) and return osContinue, this way our menu stays alive
	  
	  cControl::Shutdown();
	  return osContinue;
	  //return osEnd;
	  
  }
  switch(Key) {
    case kStop:
    case kBlue:    //Hide();
                   Stop();
                   //return osEnd;
				   break;
    case kBack:    //Hide();
				   Stop();
				   //return osEnd; //XXX osMP3;
				   break;
	case kNone:
				break;
    default:      Stop(); return osContinue;
    }
  return osContinue;

}
