// ------------------------------------------------------------------------
// audioio-seqbase.cpp: Base class for audio sequencer objects
// Copyright (C) 1999,2002,2005,2008-2010,2013 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 3 (see Ecasound Programmer's Guide)
//
// 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 <algorithm>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <cmath>

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

#include "eca-object-factory.h"
#include "samplebuffer.h"
#include "audioio-seqbase.h"

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

using std::cout;
using std::endl;
using SAMPLE_SPECS::sample_pos_t;

/**
 * FIXME notes  (last update 2008-03-04)
 *
 *  - Add check for supports_sample_accurate_seek(). This could
 *    be used to warn users if EWF source file cannot provide
 *    accurate enough seeking (which will lead to unexpected
 *    results).
 *  - Remove write-support altogether (changes supported io_modes(),
 *    modify write_buffer(), and remove child_write_started.
 *  - Should extend the object length when looping is enabled.
 */

/**
 * Returns the child position in samples for the given 
 * public position.
 */
sample_pos_t AUDIO_SEQUENCER_BASE::priv_public_to_child_pos(sample_pos_t pubpos) const
{
  if (pubpos <= child_offset_rep.samples()) 
    return child_start_pos_rep.samples();

  if (child_looping_rep == true) {
    DBC_CHECK(child()->finite_length_stream() == true);
    sample_pos_t one_loop = 
      child_length_rep.samples() -
      child_start_pos_rep.samples();
    sample_pos_t res =
      pubpos - child_offset_rep.samples();
    DBC_CHECK(res > 0);
    if (one_loop > 0)
      res = res % one_loop;
    return res + child_start_pos_rep.samples();
  }
  else {
    /* not looping */
    return pubpos - child_offset_rep.samples() + child_start_pos_rep.samples();
  }
}

AUDIO_SEQUENCER_BASE::AUDIO_SEQUENCER_BASE (void)
  : child_looping_rep(false),
    child_length_set_by_client_rep(false),
    buffersize_rep(-1),
    child_write_started(false),
    init_rep(false)
{
}

AUDIO_SEQUENCER_BASE::~AUDIO_SEQUENCER_BASE(void)
{
}

AUDIO_SEQUENCER_BASE* AUDIO_SEQUENCER_BASE::clone(void) const
{
  AUDIO_SEQUENCER_BASE* target = new AUDIO_SEQUENCER_BASE();
  for(int n = 0; n < number_of_params(); n++) {
    target->set_parameter(n + 1, get_parameter(n + 1));
  }
  return target;
}

void AUDIO_SEQUENCER_BASE::change_child_name(const string& child_name) throw(AUDIO_IO::SETUP_ERROR &)
{
  AUDIO_IO* tmp = 0;

  if (child_name.size() > 0)
    tmp = ECA_OBJECT_FACTORY::create_audio_object(child_name);

  if (tmp == 0) 
    throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-SEQBASE: Could not create child object."));

  ECA_LOG_MSG(ECA_LOGGER::user_objects, "Creating audio sequencer file:" + tmp->label() + ".");

  set_child(tmp);
  init_rep = true;
}

void AUDIO_SEQUENCER_BASE::open(void) throw(AUDIO_IO::SETUP_ERROR &)
{
  if (init_rep != true)
    change_child_name(child_object_str_rep);

  pre_child_open();
  child()->open();

  if (child()->finite_length_stream() != true &&
      child_looping_rep) {
    child()->close();
    throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-SEQBASE: Unable to loop an object with infinite length."));
  }

  if (child()->supports_seeking_sample_accurate() != true &&
      child_start_pos_rep.samples() > 0) {
    child()->close();
    throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-SEQBASE: Unable use sections of an object that does not support seeking."));
  }

  /* step: set srate for audio time variable */
  child_offset_rep.set_samples_per_second_keeptime(child()->samples_per_second());
  child_start_pos_rep.set_samples_per_second_keeptime(child()->samples_per_second());
  child_length_rep.set_samples_per_second_keeptime(child()->samples_per_second());

  post_child_open();

  /* step: unless overridden by the client, store the length
   *       of child object */
  ECA_AUDIO_TIME len_tmp (child()->length_in_samples(),
			  child()->samples_per_second());
  if (child_length_set_by_client_rep != true)
    set_child_length_private(len_tmp);
  else if (len_tmp.valid() == true &&
	   child_length_rep.valid() == true) {
    if (len_tmp.seconds() < 
	child_length_rep.seconds()) 
      ECA_LOG_MSG(ECA_LOGGER::info, 
		  "WARNING: object \"" + child()->label() +
		  "\" is too short, reducing segment length to '" 
		  + kvu_numtostr(len_tmp.seconds()) + "'sec.");
  }

  /* step: set public length of the EWF object;
   *       child length is infinite, we will extend our object
   *       length in read_buffer(), see also
   *       AUDIO_SEQUENCER_BASE::finite_length_stream() */
  if (child_looping_rep != true)
    set_length_in_samples(child_offset_rep.samples() + child_length_rep.samples());

  //dump_child_debug("open");

  tmp_buffer.number_of_channels(child()->channels());
  tmp_buffer.length_in_samples(child()->buffersize());

  AUDIO_IO_PROXY::open();
}

void AUDIO_SEQUENCER_BASE::close(void)
{
  if (child()->is_open() == true)
    child()->close();

  AUDIO_IO_PROXY::close();
}


void AUDIO_SEQUENCER_BASE::read_buffer(SAMPLE_BUFFER* sbuf)
{
  /**
   * implementation notes:
   * 
   * position:             the current global position (note, this
   *                       can exceed child_offset+child_length when
   *                       looping is used.
   * child_offset:         global position when child is activated
   * child_start_position: position inside the child-object where
   *                       input is started (data between child
   *                       beginning and child_start_pos is not used)
   * child_length:         amount of child data between start and end
   *                       positions (end can in middle of stream or
   *                       at end-of-file position)
   * child_looping:        when child end is reached, whether to jump 
   *                       back to start position?
   * 
   * note! all cases (if-else blocks) end to setting a new 
   *       position_in_samples value
   */

  //dump_child_debug("read_buffer");

  if (position_in_samples() + buffersize() <= child_offset_rep.samples()) {
    // ---
    // case 1: child not active yet
    // ---
    /* ensure that channel count matches that of child */
    sbuf->number_of_channels(child()->channels());
    sbuf->length_in_samples(buffersize());
    sbuf->make_silent();
    change_position_in_samples(buffersize());
    //dump_child_debug("case1-out");
  }
  else if (position_in_samples() < child_offset_rep.samples()) {
    // ---
    // case 2: next read will bring in the first child samples
    // ---
    
    DBC_CHECK(position_in_samples() + buffersize() > child_offset_rep.samples());

    /* make sure child position is correct at start */
    sample_pos_t chipos = 
      priv_public_to_child_pos(position_in_samples());
    if (chipos != child()->position_in_samples())
      child()->seek_position_in_samples(chipos);
    
    sample_pos_t segment = 
      position_in_samples()
      + buffersize() 
      - child_offset_rep.samples();

    if (segment > buffersize())
      segment = buffersize();

    // dump_child_debug("case2-in");
    ECA_LOG_MSG(ECA_LOGGER::user_objects, 
		"child-object \"" + child()->label() + "\" activated.");

    /* step: read segment of audio and copy it to the correct 
     *       location */
    long int save_bsize = buffersize();
    DBC_CHECK(save_bsize == buffersize());
    child()->set_buffersize(segment);
    child()->read_buffer(&tmp_buffer);
    /* ensure that channel count matches that of child */
    sbuf->number_of_channels(child()->channels());
    sbuf->length_in_samples(buffersize());
    sbuf->copy_range(tmp_buffer, 
		     0,
		     tmp_buffer.length_in_samples(), 
		     buffersize() - segment);
    child()->set_buffersize(save_bsize);
    change_position_in_samples(buffersize());
    //dump_child_debug("case2-out");
  }
  else {
    // ---
    // case 3: child is active
    // ---

    sample_pos_t chipos1 = 
      priv_public_to_child_pos(position_in_samples());
    sample_pos_t chipos2 = 
      priv_public_to_child_pos(position_in_samples() + buffersize());

    if (chipos2 >= 
	(child_length_rep.samples() + child_start_pos_rep.samples()) &&
	child_looping_rep != true &&
	child()->finite_length_stream() == true) {
      // ---
      // case 3a: not looping, reaching child file end during the next read
      // ---
      
      /* note: max samples to included (assuming child object does
       *       not reach end-of-stream */
      sample_pos_t samples_to_include =
	(child_length_rep.samples() + child_start_pos_rep.samples()) 
	- chipos1;

      //dump_child_debug("case3a-in");

      child()->set_buffersize(buffersize());
      child()->read_buffer(sbuf);

      /* resize the sbuf if needed: either EOF was encountered
       * and sbuf is shorter than buffersize() or otherwise we
       * need to drop some of the samples read so at to not
       * go beyond set length */
      if (sbuf->length_in_samples() > 
	  samples_to_include) {
	sbuf->length_in_samples(samples_to_include);
      }

      change_position_in_samples(sbuf->length_in_samples());

      //dump_child_debug("case3a-out");
    }
    else if (chipos2 < chipos1 &&
	     child_looping_rep == true) {
      // ---
      // case 3b: looping, we will run out of data during read
      // ---

      //dump_child_debug("case3b-in");

      child()->set_buffersize(buffersize());
      child()->read_buffer(sbuf);
      
      sample_pos_t over_child_eof = chipos2 - child_start_pos_rep.samples();

      /* step: copy segment 1 from loop end, and segment 2 from
       *       loop start point */
      sample_pos_t chistartpos = 
	priv_public_to_child_pos(position_in_samples() + 
				 buffersize() - over_child_eof);

      DBC_CHECK(chistartpos == child_start_pos_rep.samples());
      child()->seek_position_in_samples(chistartpos);

      if (over_child_eof > 0) {
	long int save_bsize = buffersize();
	DBC_CHECK(save_bsize == buffersize());
	child()->set_buffersize(over_child_eof);
	child()->read_buffer(&tmp_buffer);
	DBC_CHECK(tmp_buffer.length_in_samples() == over_child_eof);
	DBC_CHECK((buffersize() - over_child_eof) < buffersize());
	sbuf->length_in_samples(buffersize());
	sbuf->number_of_channels(channels());
	sbuf->copy_range(tmp_buffer, 
			 0,
			 tmp_buffer.length_in_samples(), 
			 buffersize() - over_child_eof);
	child()->set_buffersize(save_bsize);
      }
      change_position_in_samples(buffersize());

      //dump_child_debug("case3b-out");
    }
    else {
      // ---
      // case 3c: normal case, read samples from child
      // ---

      child()->set_buffersize(buffersize());      
      child()->read_buffer(sbuf);
      /* note: if the 'length' parameter value is longer than 
       *       actual child object length, less than buffersize()
       *       samples will be read */

      change_position_in_samples(sbuf->length_in_samples());

      //dump_child_debug("case3c-out");
    }

    // dump_child_debug("case3-e");
  }

  /* note: as we are looping indefinitely, so our length must
   *       be continuously updated (see child_lengt() implementation) */
  if (child_looping_rep == true ||
      (child_length_set_by_client_rep != true &&
      child()->finite_length_stream() != true))
    extend_position();

  DBC_ENSURE(channels() == child()->channels());
  DBC_ENSURE(sbuf->number_of_channels() == channels());
  DBC_ENSURE(sbuf->length_in_samples() <= buffersize());
}

void AUDIO_SEQUENCER_BASE::dump_child_debug(const char *tag)
{
  sample_pos_t chipos1 = 
    priv_public_to_child_pos(position_in_samples());
  cout << "TAG:" << tag << endl;
  cout << "global position (in samples): " << position_in_samples() << endl;
  cout << "child-pos: " << child()->position_in_samples() << endl;
  cout << "child-derived-pos: " << chipos1 << endl;
  cout << "child-offset: " << child_offset_rep.samples() << endl;
  cout << "child-startpos: " << child_start_pos_rep.samples() << endl;
  cout << "child-length: " << child_length_rep.samples() << endl;
}

void AUDIO_SEQUENCER_BASE::write_buffer(SAMPLE_BUFFER* sbuf)
{
  if (child_write_started != true) {
    child_write_started = true;
    child_offset_rep.set_samples(position_in_samples());
    MESSAGE_ITEM m;
    m << "found child_offset_rep " << child_offset_rep.seconds() << ".";
    ECA_LOG_MSG(ECA_LOGGER::user_objects, m.to_string());
  }
  
  child()->write_buffer(sbuf);
  change_position_in_samples(sbuf->length_in_samples());
  extend_position();
}

SAMPLE_SPECS::sample_pos_t AUDIO_SEQUENCER_BASE::seek_position(SAMPLE_SPECS::sample_pos_t pos)
{
  /* in write mode, seek can be only performed once
   * the initial write has been performed to the child */
  if (is_open() == true &&
      (io_mode() == AUDIO_IO::io_read ||
       (io_mode() != AUDIO_IO::io_read &&
	child_write_started == true))) {
    sample_pos_t chipos = 
      priv_public_to_child_pos(pos);

    child()->seek_position_in_samples(chipos);
  }

  return pos;
}

void AUDIO_SEQUENCER_BASE::set_child_object_string(const std::string& v)
{
  child_object_str_rep = v;
  change_child_name(child_object_str_rep);
}

void AUDIO_SEQUENCER_BASE::set_child_offset(const ECA_AUDIO_TIME& v)
{
  child_offset_rep = v;
}

void AUDIO_SEQUENCER_BASE::set_child_start_position(const ECA_AUDIO_TIME& v)
{
  if (is_open() == true &&
      child()->supports_seeking_sample_accurate() != true) {
    ECA_LOG_MSG(ECA_LOGGER::errors, 
		"ERROR: object \"" + child()->label() +
		"\" does not support sample accurate seeking, unable to set start position.");
    return;
  }

  child_start_pos_rep = v;
}

void AUDIO_SEQUENCER_BASE::set_child_length_private(const ECA_AUDIO_TIME& v)
{
  child_length_rep = v;
}

void AUDIO_SEQUENCER_BASE::set_child_length(const ECA_AUDIO_TIME& v)
{
  set_child_length_private(v);
  child_length_set_by_client_rep = true;
}

ECA_AUDIO_TIME AUDIO_SEQUENCER_BASE::child_length(void) const
{
  /* case: infinite child length */
  if (child_length_set_by_client_rep != true &&
      child()->finite_length_stream() != true) {
    ECA_AUDIO_TIME tmp;
    tmp.mark_as_invalid();
    return tmp;
  }

  return child_length_rep;
} 

bool AUDIO_SEQUENCER_BASE::finite_length_stream(void) const
{
  if (child_looping_rep == true)
    return false;

  return child()->finite_length_stream();
}

bool AUDIO_SEQUENCER_BASE::finished(void) const
{
  /** 
   * File is finished if...
   *  1) the child object is out of data (implies that looping
   *     is disabled), or
   *  2) file is open in read mode, looping is disabled, child is
   *     a finite length stream and its position has gone beyond 
   *     the requested child_length
   */
  if (child()->finished()) {
    ECA_LOG_MSG(ECA_LOGGER::user_objects, 
		"Child object " + child()->label() + " finished.");
    return true;
  }

  if (io_mode() != AUDIO_IO::io_read) {
    return false;
  }

  if (child_looping_rep != true &&
      child()->finite_length_stream() == true && 
      priv_public_to_child_pos(position_in_samples()) 
      >= child_length_rep.samples() + child_start_pos_rep.samples()) {
    ECA_LOG_MSG(ECA_LOGGER::user_objects, 
		"Finite length child object " + child()->label() + 
		" finished. All samples from the requested range have been read.");
    return true;
  }

  return false;
}
