// ------------------------------------------------------------------------
// audioio.cpp: Routines common for all audio IO-devices.
// Copyright (C) 1999-2004,2008,2009,2010 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 3
//
// This program 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 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 <cmath>
#include <string>

#include <kvu_dbc.h>
#include <kvu_message_item.h>
#include <kvu_numtostr.h>

#include "eca-error.h"
#include "audioio.h"
#include "eca-logger.h"

/**
 * FIXME notes  (last update 2008-03-11)
 *
 * - Modify default implementation of set_label()/label() so
 *   that is mapped directly to set/get_parameter(1, ...).
 */


const string& AUDIO_IO::SETUP_ERROR::message(void) const { return message_rep; }
AUDIO_IO::SETUP_ERROR::Error_type AUDIO_IO::SETUP_ERROR::type(void) const { return type_rep; }
AUDIO_IO::SETUP_ERROR::SETUP_ERROR(AUDIO_IO::SETUP_ERROR::Error_type type, 
				   const string& message) 
  : type_rep(type), message_rep(message) { }


// ===================================================================
// Constructors and destructors

AUDIO_IO::~AUDIO_IO(void)
{
  if (is_open() == true)
    close();
}

AUDIO_IO::AUDIO_IO(const string& name, 
		   int mode)
{
  set_label(name);
  set_io_mode(mode);
 
  nonblocking_rep = false; 
  open_rep = false;
}

// ===================================================================
// Attributes

/**
 * Returns info about supported I/O modes (bitwise-OR)
 *
 * Note that return value may change after device is
 * opened (some objects will refine their attributes after
 * external resources are acquired).
 *
 * By default, all I/O modes are supported.
 */
int AUDIO_IO::supported_io_modes(void) const { return (io_read | io_readwrite | io_write); }

/**
 * Whether device supports non-blocking I/O mode.
 *
 * Note that return value may change after device is
 * opened (some objects will refine their attributes after
 * external resources are acquired).
 *
 * By default, nonblocking mode is not supported.
 */
bool AUDIO_IO::supports_nonblocking_mode(void) const { return false; }

/**
 * Whether device supports seeking.
 *
 * Note that return value may change after device is
 * opened (some objects will refine their attributes after
 * external resources are acquired).
 *
 * By default, seeking is supported.
 *
 * @see supports_seeking_sample_accurate()
 */
bool AUDIO_IO::supports_seeking(void) const { return true; }

/**
 * Whether device supports sample accurate seeking.
 *
 * @see supports_seeking()
 *
 * Note that return value may change after device is
 * opened (some objects will refine their attributes after
 * external resources are acquired).
 *
 * By default, sample accurate seeking is supported if 
 * supports_seeking() returns 'true'.
 */
bool AUDIO_IO::supports_seeking_sample_accurate(void) const { return AUDIO_IO::supports_seeking(); }

/**
 * Whether audio stream has a distinct length. It's important
 * to note the difference between this attribute and 
 * 'supports_seeking()'. For example, a file read through 
 * a pipe mechanism is not seekable and its length is not 
 * known until 'finished()´ becomes true, but still, it is 
 * of finite length. A sine oscillator on the other hand 
 * can go on producing a signal forever, and is thus infinite.
 *
 * This attributes directly affects how 'finished()' should
 * to be interpreted. @see finished().
 *
 * Note that return value may change after device is
 * opened (some objects will refine their attributes after
 * external resources are acquired).
 *
 * By default, audio streams are finite length.
 */
bool AUDIO_IO::finite_length_stream(void) const { return true; }

/**
 * Whether audio format is fully or partially locked. If this is true,
 * audio object has a known audio format, and may not allow to
 * override some or all of the values.
 *
 * Example 1: When working with existing audio files (reading
 *            or updating), the audio parameters are locked to values
 *            specified in the file header.
 *
 * Example 2: File format (or audio subsystem interface specification)
 *            may hardcode some of the parameters (e.g. only support
 *            float samples). In these cases locked_audio_format()
 *            will return true, although some of the parameters may
 *            still be freely set. Client should check after open()
 *            which values were set according to its wishes, and which
 *            were limited by the audio object implementation.
 *
 * By default, audio format is not locked.
 */
bool AUDIO_IO::locked_audio_format(void) const { return false; }

// ===================================================================
// Configuration (setting and getting configuration parameters)

/**
 * Returns info about the current I/O mode.
 */
int AUDIO_IO::io_mode(void) const { return io_mode_rep; }

/**
 * Set object input/output-mode. If the requested mode isn't
 * supported, the nearest supported mode is used. Because 
 * of this, it's wise to afterwards check whether the requested
 * mode was accepted.
 *
 * require:
 *  is_open() != true
 */
void AUDIO_IO::set_io_mode(int mode) { io_mode_rep = mode; }

/**
 * Sets the object label. Label is used to identify the object instance.
 * Unlike ECA_OBJECT::name(), which is typically the same for all 
 * instances of the class, label() is instance specific. Still it
 * is not guaranteed to be unique for each object. Device and file 
 * names are typical label values.
 *
 * require:
 *  is_open() != true
 */
void AUDIO_IO::set_label(const string& id_label) { id_label_rep = id_label; }

/**
 * Enable/disbale nonblocking mode.
 *
 * require:
 *  is_open() != true
 */
void AUDIO_IO::toggle_nonblocking_mode(bool value)
{ 
  nonblocking_rep = value;
}

/**
 * Returns the current label. See documentation for 
 * label(const string&).
 */
const string& AUDIO_IO::label(void) const { return id_label_rep; }


/**
 * Returns a string containing info about sample format parameters.
 */
string AUDIO_IO::format_info(void) const
{
  MESSAGE_ITEM otemp;
  if (locked_audio_format() == true && is_open() != true) {
    otemp << "Using audio format specified in file header data (file not yet opened).";
  } else {
    otemp << "Format: " << format_string();
    otemp << ", channels " << channels();
    otemp << ", srate " << samples_per_second();

    if (interleaved_channels() == true) 
      otemp << ", interleaved";
    else
      otemp << ", noninterleaved";

    if (locked_audio_format() == true)
      otemp << " (locked params).";
    else
      otemp << ".";
  }
  return otemp.to_string();
}

void AUDIO_IO::set_parameter(int param, 
			     string value)
{
  ECA_LOG_MSG(ECA_LOGGER::user_objects, 
	      AUDIO_IO::parameter_set_to_string(param, value));
  if (param == 1) set_label(value);
}

string AUDIO_IO::get_parameter(int param) const
{
  ECA_LOG_MSG(ECA_LOGGER::system_objects, 
	      AUDIO_IO::parameter_get_to_string(param));

  if (param == 1) return label();
  return "";
}

/** 
 * Returns a debugging string for a parameter value change.
 */
string AUDIO_IO::parameter_get_to_string(int param) const
{
  return string("get param ")
    + kvu_numtostr(param) + " of \"" + label() + "\": \"" 
    + kvu_numtostr(param) + "\"";
}

/** 
 * Returns a debugging string for a parameter value change.
 */
string AUDIO_IO::parameter_set_to_string(int param, string value) const
{
  return string("set param ")
    + kvu_numtostr(param) + " of \"" 
    + label() + "\" to \"" + value + "\"";
}

// ===================================================================
// Main functionality

void AUDIO_IO::open(void) throw (AUDIO_IO::SETUP_ERROR &)
{
  DBC_REQUIRE(is_open() != true);

  DBC_CHECK(channels() > 0);
  DBC_CHECK(sample_format() != ECA_AUDIO_FORMAT::sfmt_none);
  DBC_CHECK(samples_per_second() > 0);

  open_rep = true;
  if (supports_seeking() == true)
    seek_position(position_in_samples());
  else
    /* note: if seeking is not supported, object always starts 
     *       at position 0 when opened */
    set_position_in_samples(0);
}

void AUDIO_IO::close(void)
{
  DBC_REQUIRE(is_open() == true);
  open_rep = false;
}

// ===================================================================
// Runtime information

/**
 * If applicable, returns total length of the audio data stored
 * into current audio object. In many situations it's impossible
 * enquire the whole length of the object. For instance, if the 
 * object is streaming a finite length audio stream audio object
 * from other applications using some type of standard IPC, 
 * the actual length won't be known until the whole stream has
 * been read. As a general rule, if 'supports_seeking() == true', 
 * length can be known right after initialization. Then again,
 * if 'finite_length_stream() == true', the whole stream must
 * be processed before we know the actual length. In other
 * cases, length is unknown or infinite.
 */
ECA_AUDIO_TIME AUDIO_IO::length(void) const
{
  return ECA_AUDIO_TIME(length_in_samples(), samples_per_second());
}

/**
 * Returns the current position.
 */
ECA_AUDIO_TIME AUDIO_IO::position(void) const
{
  return ECA_AUDIO_TIME(position_in_samples(), samples_per_second());
}

/**
 * Is nonblocking mode is enabled?
 */
bool AUDIO_IO::nonblocking_mode(void) const { return nonblocking_rep; }

/**
 * Is the audio object ready for reading? 
 */
bool AUDIO_IO::readable(void) const { return is_open() && io_mode() != io_write; }

/**
 * Is the audio object ready for writing? 
 */
bool AUDIO_IO::writable(void) const { return is_open() && io_mode() != io_read; }

/**
 * Sets the total length of audio object data.
 */
void AUDIO_IO::length(const ECA_AUDIO_TIME& v)
{
  set_length_in_samples(v.samples());
}

/**
 * Sets the current position.
 */
void AUDIO_IO::position(const ECA_AUDIO_TIME& v)
{
  if (v.samples_per_second() == samples_per_second())
    set_position_in_samples(v.samples());
  else {
    ECA_AUDIO_TIME modified (v);
    modified.set_samples_per_second_keeptime(samples_per_second());
    set_position_in_samples(modified.samples());
  }
}

/**
 * Optional status string
 *
 * An unformatted text string describing the state and status of 
 * the current object.
 */
string AUDIO_IO::status(void) const {
  MESSAGE_ITEM mitem;
  mitem.setprecision(3);

  mitem << "position (" << position_in_seconds_exact() << "/";
  if (finite_length_stream() == true)
    mitem << length_in_seconds_exact();
  else
    mitem << "inf";
  mitem << ") seconds.\n -> ";
  
  if (is_open() == true) 
    mitem << "open, ";
  else 
    mitem << "closed";

  if (locked_audio_format() == true &&
      is_open() != true) {
    mitem << ", audio format not available until object is opened.";
  }
  else {
    mitem << ", " << format_string() << "/" << channels() << "ch/" << samples_per_second();
    mitem << "Hz, buffer " << buffersize() << ".";
  }

  return mitem.to_string();
}

/**
 * Overrides the non-virtaul function 
 * ECA_SAMPLERATE_AWARE::samples_per_second(), that is 
 * present (through inheritance) in both ECA_AUDIO_FORMAT
 * and ECA_AUDIO_POSITION.
 */
SAMPLE_SPECS::sample_rate_t AUDIO_IO::samples_per_second(void) const
{
  DBC_CHECK(ECA_AUDIO_FORMAT::samples_per_second() == 
	    ECA_AUDIO_POSITION::samples_per_second());
  return ECA_AUDIO_FORMAT::samples_per_second();
}

void AUDIO_IO::set_samples_per_second(SAMPLE_SPECS::sample_rate_t v)
{
  ECA_AUDIO_FORMAT::set_samples_per_second(v);
  ECA_AUDIO_POSITION::set_samples_per_second(v);
  ECA_LOG_MSG(ECA_LOGGER::system_objects,
		"set srate, aobj \"" +
		name() + ":" + label() +
		"\" to " + 
		kvu_numtostr(v) + ".");
}

void AUDIO_IO::set_audio_format(const ECA_AUDIO_FORMAT& f_str)
{
  ECA_AUDIO_FORMAT::set_audio_format(f_str);
  ECA_AUDIO_POSITION::set_samples_per_second(f_str.samples_per_second());
}

SAMPLE_SPECS::sample_pos_t AUDIO_IO::seek_position(SAMPLE_SPECS::sample_pos_t pos)
{
  if (supports_seeking() != true &&
      pos != 0) {
    ECA_LOG_MSG(ECA_LOGGER::info,
		"WARNING: seeking not supported by audio objects of type \"" +
		name() + "\".");
    return position_in_samples();
  }

  return pos;
}
