/*
 * audio.c
 * Copyright (C) 2002 P.Everett <peter@everett9981.freeserve.co.uk>
 *
 * This file is part of KinoAMP, a free RISCOS MPEG program stream decoder.
 *
 * KinoAMP 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.
 *
 * KinoAMP 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
 */

/*
 * Uses the AMPlayer module to provide an audio output from an
 * MPEG encoded audio data elementary stream input.
 *
 * v0.00 13/10/01
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"
#include "timer1.h"
#include "audio.h"
#include "audiocodec.h"
#include "config.h"
#include "wimp.h"
#include "ka_error.h"
#include "ka_mem.h"
#include "ka_log.h"

struct audio_s
{
  ka_audiocodec_t*  pcodec;
  uint32_t       start_time;
  uint32_t       start_section;
  audio_info_t   info;
  ka_aparams_t   params;
};

/**
 * Returns a new audio instance.
 */
audio_t* audio_new(ka_error_t* pErrorBlock)
{
  audio_t* audio = ka_mem_calloc(sizeof(*audio));
  if (!audio)
  {
    // Out of memory
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return NULL;
  }

  // If not configured read current output level
  if (config.audio.volume < 0)
    config.audio.volume = audio_volume(audio, -1);

  // Till some data is received
  audio->info.status = 0;

  return audio;
}

/**
 * Releases an audio instance.
 *
 * @param  audio    A pointer to an audio instance.
 */
void audio_delete(audio_t** paudio)
{
  audio_t* audio = *paudio;
  *paudio = NULL;

  if (!audio)
    return;

  if (audio->pcodec)
  {
    audio->pcodec->vptr->stop(audio->pcodec);
    audio->pcodec->vptr->close(&audio->pcodec);
  }

  ka_mem_free(audio);
}

int audio_allow(audio_t* audio, const ka_aparams_t* params)
{
  const kav_audiocodec_t* vptr;
  audio = audio; // avoid warning

  if ((config.audio.module != 0)
  ||  (params->typ != KA_ABLOCK_MPEG))
    vptr = &kav_disk_codec;
  else
    vptr = &kav_amp_codec;

  return vptr->allow(params);
}

/**
 * Opens filewriter, initializes decoder for given type of audio input.
 *
 * @param  audio    A pointer to an audio instance.
 * @param  params   A pointer to the parameters of the input to decode.
 *
 * @returns 0 if the function succeeded, else pErrorBlock is filled with error.
 */
int audio_open(audio_t* audio, ka_error_t* pErrorBlock, const ka_aparams_t* params)
{
  int rc = 0;
  audio->params = *params;
  audio->info.name[0] = 0;
  audio->info.type = params->typ;
  audio->info.channels = 0;
  audio->info.samp_rate = 0;
  audio->info.bitrate = 0;
  audio->info.pts = 0;
  audio->info.cfg = config.audio.cfg;
  audio->info.status &= AUDIO_STATUS_CODECOPEN | AUDIO_STATUS_READYFORINPUT; // Loose all other flags
  audio->start_time = 0;
  audio->start_section = 0;

  // Stops current decoder
  if (audio->info.status & AUDIO_STATUS_READYFORINPUT)
  {
    if (audio->pcodec)
      audio->pcodec->vptr->stop(audio->pcodec);

    audio->info.status &= ~AUDIO_STATUS_READYFORINPUT;
  }

  // Initialize new codec
  if (audio->info.cfg & cfg_audio_play)
  {
    const kav_audiocodec_t* vptr;

    if ((config.audio.module != 0)
    ||  (audio->info.type != KA_ABLOCK_MPEG))
      vptr = &kav_disk_codec;
    else
      vptr = &kav_amp_codec;

    if (!audio->pcodec || (vptr != audio->pcodec->vptr))
    {
      if (audio->pcodec)
        audio->pcodec->vptr->close(&audio->pcodec);

      audio->info.status &= ~AUDIO_STATUS_CODECOPEN;

      audio->pcodec = vptr->open(pErrorBlock);
      if (!audio->pcodec)
        rc = 1;
      else
        audio->info.status |= AUDIO_STATUS_CODECOPEN;
    }

    if (!rc)
    {
      rc = audio->pcodec->vptr->start(audio->pcodec, params);

      if (!rc)
      {
        audio->info.status |= AUDIO_STATUS_READYFORINPUT;

        // Reuse last volume setting
        audio_volume(audio, config.audio.volume);
        if (audio->info.cfg & cfg_audio_mute)
          audio_volume(audio,-2); // mute

        if (config.debug & cfg_printaudiostats)
          ka_log(ka_log_audio, "Decoder started");
      }
    }

    if (!(audio->info.status & AUDIO_STATUS_READYFORINPUT))
    {
      audio->info.status |= AUDIO_STATUS_DISABLED;
      audio->info.pts = 0;
      audio->info.cfg &= ~cfg_audio_play; // for osd

      if (config.debug & (cfg_printwarnings | cfg_printaudiostats))
        ka_log(ka_log_error | ka_log_audio, "Decoder could not be started");

      return rc;
    }
  }
  else
  {
    audio->info.status |= AUDIO_STATUS_DISABLED;
    audio->info.pts = 0;
  }

  return 0;
}

/**
 * Checks the status and position of the data.
 *
 * @param  audio    A pointer to an audio instance.
 */
void audio_checkState(audio_t* audio)
{
  int state;

  if (!(audio->info.status & AUDIO_STATUS_DECODINGINPUT))
    return;

  if (audio->info.status & AUDIO_STATUS_DISABLED)
    return;

  state = audio->pcodec->vptr->getPos(audio->pcodec, &audio->info);

  if (state & AUDIOCODEC_STATE_INERROR)
  {
    if (config.debug & (cfg_printwarnings | cfg_printaudiostats))
      ka_log(ka_log_error | ka_log_audio, "Codec stopped due to error");

    audio->pcodec->vptr->stop(audio->pcodec);
    audio->info.status &= ~AUDIO_STATUS_ERRORMASK;
    audio->info.status |= AUDIO_STATUS_DISABLED;
    audio->info.pts = 0;

    return;
  }

  if (state & AUDIOCODEC_STATE_NOWORK)
    audio->info.status |= AUDIO_STATUS_DONEOUTPUT;

  if (state & AUDIOCODEC_STATE_COMPLETED)
  {
    if (config.debug & (cfg_printaudiostats))
      ka_log(ka_log_audio, "Output completed");

//    audio->pcodec->vptr->stop(audio->pcodec);
//    audio->info.status &= ~AUDIO_STATUS_STOPMASK;
    audio->info.status &= ~AUDIO_STATUS_BLOCKPROCESSMASK;
    audio->info.status |= AUDIO_STATUS_COMPLETED;

    return;
  }

  if (state & AUDIOCODEC_STATE_READY)
    audio->info.status |= AUDIO_STATUS_READYFOROUTPUT;
}

/**
 * Gives the supplied data to the audio decoder module and to the filewriter.
 * Checks the status and position of the data. The latest cannot be done at
 * another time since it requires the block's timestamp.
 *
 * @param  audio    A pointer to an audio instance.
 * @param  block    A pointer to a block of audio data or NULL if no
 *
 * @returns  1 if block can be discarded, 0 if block was delayed (buffer full).
 */
int audio_decode(audio_t* audio, ka_block_t* block)
{
  if (block)
  {
    // Don't mix audio types
    if (audio->info.type != (block->typ & ~KA_BLOCK_STREAMMASK))
    {
      if (config.debug & (cfg_printwarnings | cfg_printaudiostats))
        ka_log(ka_log_error | ka_log_audio, "Mix of audio types %08x vs %08x, skip packet", audio->info.type, (block->typ & ~KA_BLOCK_STREAMMASK));

      // Discard it to avoid blocking the stack.
      return 1;
    }

    if ((audio->info.status & (AUDIO_STATUS_READYFORINPUT | AUDIO_STATUS_DECODINGINPUT)) == AUDIO_STATUS_READYFORINPUT)
    {
      if (config.debug & cfg_printaudiostats)
        ka_log(ka_log_audio, "Start time %10u", block->pts);
      audio->start_time = block->pts;
      audio->start_section = block->section;
      audio->info.status |= AUDIO_STATUS_DECODINGINPUT;
      audio->info.status &= ~(AUDIO_STATUS_DONEOUTPUT | AUDIO_STATUS_COMPLETED);
    }
  }

  // Check the player's status
  audio_checkState(audio);

  if (block)
  {
    if (audio->info.status & (AUDIO_STATUS_DONEOUTPUT | AUDIO_STATUS_COMPLETED))
    {
      audio->info.status &= ~(AUDIO_STATUS_DONEOUTPUT | AUDIO_STATUS_COMPLETED);
      audio->info.status |= AUDIO_STATUS_DECODINGINPUT;
    }
  }

  // Try to put block into decoder buffer
  if (audio->info.status & AUDIO_STATUS_DECODINGINPUT)
  {
    ka_eaudiocodec_decode ret;

    if (block)
    {
      audio->info.section = block->section;
      ret = audio->pcodec->vptr->decode(audio->pcodec, block);
    }
    else
      ret = audio->pcodec->vptr->decode(audio->pcodec, NULL);

    if (ret == EAUDIOCODEC_DECODE_TRYLATER)
      return 0;

    if (ret == EAUDIOCODEC_DECODE_INERROR)
    {
      if (config.debug & (cfg_printwarnings | cfg_printaudiostats))
        ka_log(ka_log_error | ka_log_audio, "Codec stopped due to error");

      audio->pcodec->vptr->stop(audio->pcodec);
      audio->info.status &= ~AUDIO_STATUS_ERRORMASK;
      audio->info.status |= AUDIO_STATUS_DISABLED;
      audio->info.pts = 0;
    }
  }

  // Discard block
  return 1;
}

/**
 * Starts or pauses the player.
 *
 * @param  audio    A pointer to an audio instance.
 * @param  pause    Non-zero to pause, zero to resume playing.
 */
void audio_pause(audio_t* audio, int pause)
{
  if (audio->info.status & AUDIO_STATUS_REPLAYOUTPUT)
  {
    if (pause) pause = AUDIO_STATUS_PAUSED;

    if ((audio->info.status & AUDIO_STATUS_PAUSED) != pause)
    {
      audio->info.status ^= AUDIO_STATUS_PAUSED;

      audio->pcodec->vptr->pause(audio->pcodec, pause ? 1 : 0);
    }
  }
}

/**
 * Alters or reads the audio volume [0 - 127].
 *
 * @param  audio    A pointer to an audio instance.
 * @param  vol      If >= 0, volume to set.
 *                  -1 = read current value.
 *                  -2 = mute audio.
 *                  -3 = unmute audio.
 *
 * @returns  New current volume.
 */
int audio_volume(audio_t* audio, int vol)
{
  static int stored_vol = 127, muted = 0;

  if (vol == -2)
  {
    muted = 1;
    vol = 0;
  }
  else if (vol == -3)
  {
    muted = 0;
    vol = stored_vol;
  }
  else if (vol >= 0)
  {
    stored_vol = vol;
    if (muted)
      return stored_vol;
  }

  if (audio->pcodec)
    return audio->pcodec->vptr->volume(audio->pcodec, vol);

  return 127;
}

/**
 * Similar to audio_delete but without destroying the instance or
 * dynamic area. Called when seeking.
 * Just close the stream and reset the timing flags.
 *
 * @param  audio    A pointer to an audio instance.
 */
void audio_close(audio_t* audio)
{
  if (audio->info.status & AUDIO_STATUS_READYFORINPUT)
  {
    audio->pcodec->vptr->stop(audio->pcodec);
    audio->info.status &= ~AUDIO_STATUS_STOPMASK;
    audio->info.status |= (AUDIO_STATUS_DONEOUTPUT | AUDIO_STATUS_COMPLETED);
  }
}

/**
 * Starts audio at correct time.
 *
 * @param  audio    A pointer to an audio instance.
 * @param  reftime  Current rtc time.
 */
void audio_checkStart(audio_t* audio, uint32_t reftime, uint32_t section)
{
  if ((audio->info.status & ( AUDIO_STATUS_DECODINGINPUT
                            | AUDIO_STATUS_READYFOROUTPUT
                            | AUDIO_STATUS_REPLAYOUTPUT
                            | AUDIO_STATUS_PAUSED))
      != (AUDIO_STATUS_DECODINGINPUT | AUDIO_STATUS_READYFOROUTPUT))
    return;

  if ((reftime >= audio->start_time) && (section >= audio->start_section))
  {
    audio->info.status |= (AUDIO_STATUS_REPLAYOUTPUT | AUDIO_STATUS_PAUSED);
    audio_pause(audio, 0);
    if (config.debug & (cfg_printaudioall | cfg_printreplaystats))
      ka_log(ka_log_audio, "Replay started, pts %10u", audio->start_time);
  }
}

/**
 * Returns the audio info block.
 *
 * @param  audio    A pointer to an audio instance.
 */
const audio_info_t* audio_info(audio_t* audio)
{
  return &audio->info;
}

/**
 * Returns the audio params block.
 *
 * @param  audio    A pointer to an audio instance.
 */
const ka_aparams_t* audio_getParams(audio_t* audio)
{
  return &audio->params;
}
