// ------------------------------------------------------------------------
// eca-control.cpp: Class for controlling the whole ecasound library
// Copyright (C) 1999-2005,2008,2009,2012 Kai Vehmanen
// Copyright (C) 2005 Stuart Allie
// Copyright (C) 2009 Adam Linson
//
// 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, see <http://www.gnu.org/licenses/>.
// ------------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <cassert>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <list>
#include <algorithm>
#include <unistd.h>

#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

#include <kvu_utils.h> /* string_to_vector(), string_to_int_vector() */
#include <kvu_value_queue.h>
#include <kvu_message_item.h>
#include <kvu_dbc.h>
#include <kvu_numtostr.h>

#include "audioio.h"
#include "eca-chain.h"
#include "eca-chainop.h"
#include "eca-chainsetup.h"
#include "eca-control.h"
#include "eca-control-main.h"
#include "eca-engine.h"
#include "eca-object-factory.h"
#include "eca-object-map.h"
#include "eca-preset-map.h"
#include "eca-session.h"

#include "generic-controller.h"
#include "eca-chainop.h"
#include "audiofx_ladspa.h"
#include "audiofx_lv2.h"
#include "preset.h"
#include "sample-specs.h"
#include "jack-connections.h"

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

/**
 * Import namespaces
 */
using std::string;
using std::list;
using std::vector;
using std::cerr;
using std::endl;
using namespace ECA;

/**
 * Declarations for private static functions
 */
static string eca_aio_register_sub(ECA_OBJECT_MAP& objmap);

/**
 * Definitions for member functions
 */

ECA_CONTROL::ECA_CONTROL (ECA_SESSION* psession) 
  : ctrl_dump_rep(this),
    wellformed_mode_rep(false)
{
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "ECA_CONTROL constructor");

  session_repp = psession;
  selected_chainsetup_repp = psession->selected_chainsetup_repp;
  engine_repp = 0;
  engine_pid_rep = -1;
  engine_exited_rep.set(0);
  float_to_string_precision_rep = 3;
  joining_rep = false;
  DBC_CHECK(is_engine_created() != true);

  selected_audio_object_repp = 0;
  selected_audio_input_repp = 0;
  selected_audio_output_repp = 0;
}

ECA_CONTROL::~ECA_CONTROL(void)
{
  ECA_LOG_MSG(ECA_LOGGER::system_objects, "ECA_CONTROL destructor");
  close_engine();
}

void ECA_CONTROL::fill_command_retval(struct eci_return_value *retval) const
{
  if (retval == 0)
    return;

  *retval = last_retval_rep;
}

void ECA_CONTROL::command(const string& cmd_and_args, struct eci_return_value *retval)
{
  clear_last_values();
  clear_action_arguments();

  ECA_LOG_MSG(ECA_LOGGER::user_objects, "processing cmd and arg: " + cmd_and_args);

  vector<string> tokens = kvu_string_to_tokens_quoted(cmd_and_args);
  vector<string>::iterator cmd = tokens.begin();
  if (cmd != tokens.end()) {
    const std::map<std::string,int>& cmdmap = ECA_IAMODE_PARSER::registered_commands();
    if (cmdmap.find(*cmd) == cmdmap.end()) {
      // ---
      // *p is not recognized as a iamode command
      // ---
      if (cmd->size() > 0 && (*cmd)[0] == '-') {
	//  std::cerr << "Note! Direct use of EOS-options (-prefix:arg1,...,argN)" << " as iactive-mode commands is considered deprecated. " << "\tUse the notation 'cs-option -prefix:a,b,x' instead." << std::endl;
	if (*cmd == "-i")
	  ECA_LOG_MSG(ECA_LOGGER::info, 
		      "WARNING: syntax variant '-i file.ext' not supported, please use 'ai-add file.ext' instead.");
	else if (*cmd == "-o")
	  ECA_LOG_MSG(ECA_LOGGER::info, 
		      "WARNING: syntax variant '-o file.ext' not supported, please use 'ai-add file.ext' instead.");
	else {
	  ECA_LOG_MSG(ECA_LOGGER::user_objects, "passiong to cs-option: " + cmd_and_args);
	  chainsetup_option(cmd_and_args);
	}
      }
      else {
	set_last_error("Unknown command!");
      }
    }
    else {
      int action_id = ECA_IAMODE_PARSER::command_to_action_id(*cmd);
      if (action_id == ec_help) {
	show_controller_help();
      }
      else {
	vector<string>::iterator args = cmd + 1;
	if (args != tokens.end()) {
	  set_action_argument(vector<string> (args, tokens.end()));
	}
	action(action_id);
      }
    }
  }

  fill_command_retval(retval);
}

void ECA_CONTROL::set_action_argument(const string& s)
{
  action_args_rep.resize(0);
  action_args_rep.push_back(s);
  action_arg_f_set_rep = false;
}

void ECA_CONTROL::set_action_argument(const std::vector<std::string>& s)
{
  action_args_rep = s;
  action_arg_f_set_rep = false;
}

void ECA_CONTROL::set_action_argument(double v)
{
  action_arg_f_rep = v;
  action_arg_f_set_rep = true;
}

void ECA_CONTROL::clear_action_arguments(void)
{
  // use resize() instead of clear(); clear() was a late
  // addition to C++ standard and not supported by all
  // compilers (for example egcs-2.91.66) 
  action_args_rep.resize(0);

  action_arg_f_rep = 0.0f;
  action_arg_f_set_rep = false;
}

double ECA_CONTROL::first_action_argument_as_float(void) const
{
  if (action_arg_f_set_rep == true)
    return action_arg_f_rep;
  
  if (action_args_rep.size() == 0)
    return 0.0f;

  return atof(action_args_rep[0].c_str());
}

string ECA_CONTROL::first_action_argument_as_string(void) const
{
  if (action_args_rep.size() == 0)
    return std::string();

  return action_args_rep[0];
}

const vector<string>& ECA_CONTROL::action_arguments_as_vector(void) const
{
  return action_args_rep;
}

int ECA_CONTROL::first_action_argument_as_int(void) const
{
  if (action_args_rep.size() == 0)
    return 0;

  return atoi(action_args_rep[0].c_str());
}

long int ECA_CONTROL::first_action_argument_as_long_int(void) const
{
  if (action_args_rep.size() == 0)
    return 0;

  return atol(action_args_rep[0].c_str());
}

SAMPLE_SPECS::sample_pos_t ECA_CONTROL::first_action_argument_as_samples(void) const
{
  if (action_args_rep.size() == 0)
    return 0;

#ifdef HAVE_ATOLL
  return atoll(action_args_rep[0].c_str());
#else
  return atol(action_args_rep[0].c_str());
#endif
}

void ECA_CONTROL::command_float_arg(const string& cmd, double arg, struct eci_return_value *retval)
{
  clear_action_arguments();
  set_action_argument(arg);
  int action_id = ec_unknown;
  action_id = ECA_IAMODE_PARSER::command_to_action_id(cmd);
  action(action_id);
  fill_command_retval(retval);
}

/**
 * Interprets an EOS (ecasound optiont syntax) token  (prefixed with '-').
 */
void ECA_CONTROL::chainsetup_option(const string& cmd)
{
  string prefix = kvu_get_argument_prefix(cmd);
  if (prefix == "el" || prefix == "pn") { // --- LADSPA plugins and presets
    if (selected_chains().size() == 1) 
      add_chain_operator(cmd);
    else
      set_last_error("When adding chain operators, only one chain can be selected.");
  }
  else if (ECA_OBJECT_FACTORY::chain_operator_map().object(prefix) != 0) {
    if (selected_chains().size() == 1) 
      add_chain_operator(cmd);
    else
      set_last_error("When adding chain operators, only one chain can be selected.");
  }
  else if (ECA_OBJECT_FACTORY::controller_map().object(prefix) != 0) {
    if (selected_chains().size() == 1) 
      add_controller(cmd);
    else
      set_last_error("When adding controllers, only one chain can be selected.");
  }
  else {
    set_action_argument(cmd);
    action(ec_cs_option);
  }
}

/**
 * Checks action preconditions.
 *
 * @return Sets status of private data members 'action_ok', 
 *         'action_restart', and 'action_reconnect'.
 */
void ECA_CONTROL::check_action_preconditions(int action_id)
{
  action_ok = true;
  action_reconnect = false;
  action_restart = false;

  /* case 1: action requiring arguments, but not arguments available */
  if (action_arg_f_set_rep == false && 
      first_action_argument_as_string().size() == 0 &&
      action_requires_params(action_id)) {
    set_last_error("Can't perform requested action; argument omitted.");
    action_ok = false;
  }
  /* case 2: action requires an audio input, but no input available */
  else if (is_selected() == true &&
	   get_audio_input() == 0 &&
	   action_requires_selected_audio_input(action_id)) {
    set_last_error("Can't perform requested action; no audio input selected.");
    action_ok = false;
  }
  /* case 3: action requires an audio output, but no output available */
  else if (is_selected() == true &&
	   get_audio_output() == 0 &&
	   action_requires_selected_audio_output(action_id)) {
    set_last_error("Can't perform requested action; no audio output selected.");
    action_ok = false;
  }
  /* case 4: action requires a select chainsetup, but none selected */
  else if (is_selected() == false &&
	   action_requires_selected(action_id)) {
    if (!is_connected()) {
      set_last_error("Can't perform requested action; no chainsetup selected.");
      action_ok = false;
    }
    else {
      ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: No chainsetup selected. Connected chainsetup will be selected.");
      select_chainsetup(connected_chainsetup());
    }
  }
  /* case 5: action requires a connected chainsetup, but none connected */
  else if (is_connected() == false &&
	   action_requires_connected(action_id)) {
    if (!is_selected()) {
      set_last_error("Can't perform requested action; no chainsetup connected.");
      action_ok = false;
    }
    else {
      if (is_valid() == true) {
	ECA_LOG_MSG(ECA_LOGGER::info, 
		    "NOTE: No chainsetup connected. Trying to connect currently selected chainsetup \""
		    + selected_chainsetup_repp->name() 
		    + "\"");
	connect_chainsetup(0);
      }
      if (is_connected() != true) {
	/* connect_chainsetup() sets last_error() so we just add to it */
	set_last_error(last_error() + " Selected chainsetup cannot be connected. Can't perform requested action. ");
	action_ok = false;
      }
    }
  }
  /* case 6: action can't be performed on a connected setup, 
   *         but selected chainsetup is also connected */
  else if (selected_chainsetup() == connected_chainsetup() &&
	   action_requires_selected_not_connected(action_id)) {
    ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: This operation requires that chainsetup is disconnected. Temporarily disconnecting...");
    if (is_running()) action_restart = true;
    disconnect_chainsetup();
    action_reconnect = true;
  }
}

void ECA_CONTROL::action(int action_id, 
			 const vector<string>& args) 
{
  ECA_LOG_MSG(ECA_LOGGER::info, "WARNING: ECA_CONTROL::action() method is obsolete.\n");
  clear_action_arguments();
  set_action_argument(kvu_vector_to_string(args, " "));
  action(action_id);
}

bool ECA_CONTROL::action_helper_check_cop_op_args(int copid, int coppid)
{
  DBC_REQUIRE(is_selected() == true);

  const vector<string>& selchains =
    selected_chainsetup_repp->selected_chains();
  bool res = false;

  if (selchains.size() == 0) {
    set_last_error("No chain selected, unable to identify chainop");
  }
  else if (selchains.size() > 1) {
    set_last_error("More than one chain selected, unable to identify chainop");
  }
  else {
    const CHAIN* selch = 
      selected_chainsetup_repp->get_chain_with_name(selchains[0]);
    if (copid < 1 || copid > selch->number_of_chain_operators()) {
      set_last_error("Invalid chainop-id, unable to identify chainop");
    }
    else if (coppid < 1) {
      set_last_error("Invalid copp-id, indexing starts from 1.");
    }
    else {
      res = true;
    }
  }

  return res;
}

void ECA_CONTROL::action(int action_id)
{
  clear_last_values();
  check_action_preconditions(action_id);

  if (action_ok != true) return;

  switch(action_id) {
  case ec_unknown: { set_last_error("Unknown command!"); break; }

    // ---
    // General
    // ---
  case ec_exit: { quit(); break; }
  case ec_start: 
    { 
      if (is_running() != true) {
	int result = start();
	if (result < 0) {
	  set_last_error("Error, unable to start processing");
	}
      }
      // ECA_LOG_MSG(ECA_LOGGER::info, "Can't perform requested action; no chainsetup connected.");
      break; 
    }
  case ec_stop: { if (is_engine_created()) stop(); break; }
  case ec_stop_sync: { if (is_engine_created()) stop_on_condition(); break; }
  case ec_run: 
    { 
      int result = run(); 
      if (result < 0) {
	set_last_error("Errors during processing");
      }
      break; 
    }
  case ec_debug:
    {
      int level = first_action_argument_as_int();
      ECA_LOGGER::instance().set_log_level_bitmask(level);
      set_last_string("Debug level set to " + kvu_numtostr(level) + ".");
      break;
    }
  case ec_resource_file:
    {
      session_repp->interpret_general_option(string("-R:") + 
					     first_action_argument_as_string());
      break;
    }

    // ---
    // Chainsetups
    // ---
  case ec_cs_add:
    {
      add_chainsetup(first_action_argument_as_string());
      break;
    }
  case ec_cs_remove: { remove_chainsetup(); break; }
  case ec_cs_list: { set_last_string_list(chainsetup_names()); break; }
  case ec_cs_select: { select_chainsetup(first_action_argument_as_string()); break; }
  case ec_cs_selected: { set_last_string(selected_chainsetup()); break; }
  case ec_cs_index_select: { 
    if (first_action_argument_as_string().size() > 0) {
      select_chainsetup_by_index(first_action_argument_as_int());
    }
    break; 
  }
  case ec_cs_edit: { edit_chainsetup(); break; }
  case ec_cs_load: { load_chainsetup(first_action_argument_as_string()); break; }
  case ec_cs_save: { save_chainsetup(""); break; }
  case ec_cs_save_as: { save_chainsetup(first_action_argument_as_string()); break; }
  case ec_cs_is_valid: { 
    if (is_valid() == true) 
      set_last_integer(1);
    else
      set_last_integer(0);
    break;
  }
  case ec_cs_connect: 
    { 
      if (is_valid() != false) {
	connect_chainsetup(0); 
      }
      else {
	set_last_error("Can't connect; chainsetup not valid!");
      }
      break; 
    }
  case ec_cs_connected: { set_last_string(connected_chainsetup()); break; }
  case ec_cs_disconnect: { disconnect_chainsetup(); break; }
  case ec_cs_set_param: { set_chainsetup_parameter(first_action_argument_as_string()); break; }
  case ec_cs_set_audio_format: { set_chainsetup_sample_format(first_action_argument_as_string()); break; }
  case ec_cs_status: { 
    set_last_string(chainsetup_status()); 
    break; 
  }
  case ec_cs_rewind: { change_chainsetup_position(-first_action_argument_as_float()); break; }
  case ec_cs_forward: { change_chainsetup_position(first_action_argument_as_float()); break; }
  case ec_cs_set_position: { set_chainsetup_position(first_action_argument_as_float()); break; }
  case ec_cs_set_position_samples: { set_chainsetup_position_samples(first_action_argument_as_samples()); break; }
  case ec_cs_get_position: { set_last_float(position_in_seconds_exact()); break; }
  case ec_cs_get_position_samples: { set_last_long_integer(selected_chainsetup_repp->position_in_samples()); break; }
  case ec_cs_get_length: { set_last_float(length_in_seconds_exact()); break; }
  case ec_cs_get_length_samples: { set_last_long_integer(length_in_samples()); break; }
  case ec_cs_set_length: 
    { 
      set_chainsetup_processing_length_in_seconds(first_action_argument_as_float()); 
      break; 
    }
  case ec_cs_set_length_samples:
    {
      set_chainsetup_processing_length_in_samples(first_action_argument_as_samples());
      break;
    }
  case ec_cs_toggle_loop: { toggle_chainsetup_looping(); break; } 
  case ec_cs_option: 
    {
      selected_chainsetup_repp->interpret_options(action_arguments_as_vector());
      if (selected_chainsetup_repp->interpret_result() != true) {
	set_last_error(selected_chainsetup_repp->interpret_result_verbose());
      }
      break;
    }

  // ---
  // Chains
  // ---
  case ec_c_add: { add_chains(kvu_string_to_vector(first_action_argument_as_string(), ',')); break; }
  case ec_c_remove: { remove_chains(); break; }
  case ec_c_list: { set_last_string_list(chain_names()); break; }
  case ec_c_select: { select_chains(kvu_string_to_vector(first_action_argument_as_string(), ',')); break; }
  case ec_c_selected: { set_last_string_list(selected_chains()); break; }
  case ec_c_index_select: { select_chains_by_index(kvu_string_to_int_vector(first_action_argument_as_string(), ',')); break; }
  case ec_c_deselect: { deselect_chains(kvu_string_to_vector(first_action_argument_as_string(), ',')); break; }
  case ec_c_select_add: 
    { 
      select_chains(kvu_string_to_vector(first_action_argument_as_string() + "," +
					 kvu_vector_to_string(selected_chains(), ","), ',')); 
      break; 
    }
  case ec_c_select_all: { select_all_chains(); break; }
  case ec_c_clear: { clear_chains(); break; }
  case ec_c_rename: 
    { 
      if (selected_chains().size() != 1) {
	set_last_error("When renaming chains, only one chain canbe selected.");
      }
      else {
	rename_chain(first_action_argument_as_string()); 
      }
      break;
    }
  case ec_c_muting: { set_chain_muting(first_action_argument_as_string()); break; }
  case ec_c_bypass: { set_chain_bypass(first_action_argument_as_string()); break; }
  case ec_c_status: 
    { 
      set_last_string(chain_status()); 
      break; 
    }
  case ec_c_is_bypassed: { set_last_integer(chain_is_bypassed()); break; }
  case ec_c_is_muted: { set_last_integer(chain_is_muted()); break; }

    // ---
    // Actions common to audio inputs and outputs
    // ---
  case ec_aio_status:
  case ec_ai_status:
  case ec_ao_status:
    { 
      set_last_string(aio_status()); 
      break; 
    }
  case ec_aio_register: { aio_register(); break; }

    // ---
    // Audio input objects
    // ---
  case ec_ai_add: { add_audio_input(first_action_argument_as_string()); break; }
  case ec_ai_describe: { set_last_string(ECA_OBJECT_FACTORY::audio_object_to_eos(selected_audio_input_repp, "i")); break; }
  case ec_ai_remove: { remove_audio_input(); break; }
  case ec_ai_list: { set_last_string_list(audio_input_names()); break; }
  case ec_ai_select: { select_audio_input(first_action_argument_as_string()); break; }
  case ec_ai_selected: { set_last_string(get_audio_input()->label()); break; }
  case ec_ai_index_select: { 
    if (first_action_argument_as_string().size() > 0) {
	select_audio_input_by_index(first_action_argument_as_int());
    }
    break; 
  }
  case ec_ai_attach: { attach_audio_input(); break; }
  case ec_ai_forward: 
    { 
      audio_input_as_selected();
      forward_audio_object(first_action_argument_as_float()); 
      break; 
    }
  case ec_ai_rewind: 
    { 
      audio_input_as_selected();
      rewind_audio_object(first_action_argument_as_float()); 
      break; 
    }
  case ec_ai_set_position: { audio_input_as_selected(); set_audio_object_position(first_action_argument_as_float()); break; }
  case ec_ai_set_position_samples: { audio_input_as_selected(); set_audio_object_position_samples(first_action_argument_as_long_int()); break; }
  case ec_ai_get_position: { set_last_float(get_audio_input()->position().seconds()); break; }
  case ec_ai_get_position_samples: { set_last_long_integer(get_audio_input()->position().samples()); break; }
  case ec_ai_get_length: { set_last_float(get_audio_input()->length().seconds()); break; }
  case ec_ai_get_length_samples: { set_last_long_integer(get_audio_input()->length().samples()); break; }
  case ec_ai_get_format: {
    set_last_string(get_audio_input()->format_string() + "," +
		    kvu_numtostr(get_audio_input()->channels()) + "," +
		    kvu_numtostr(get_audio_input()->samples_per_second())); 

    break; 
  }

  case ec_ai_wave_edit: { audio_input_as_selected(); wave_edit_audio_object(); break; }

    // ---
    // Audio output objects
    // ---
  case ec_ao_add: { if (first_action_argument_as_string().size() == 0) add_default_output(); else add_audio_output(first_action_argument_as_string()); break; }
  case ec_ao_add_default: { add_default_output(); break; }
  case ec_ao_describe: { set_last_string(ECA_OBJECT_FACTORY::audio_object_to_eos(selected_audio_output_repp, "o")); break; }
  case ec_ao_remove: { remove_audio_output(); break; }
  case ec_ao_list: { set_last_string_list(audio_output_names()); break; }
  case ec_ao_select: { select_audio_output(first_action_argument_as_string()); break; }
  case ec_ao_selected: { set_last_string(get_audio_output()->label()); break; }
  case ec_ao_index_select: { 
    select_audio_output_by_index(first_action_argument_as_int());
    break; 
  }
  case ec_ao_attach: { attach_audio_output(); break; }
  case ec_ao_forward: 
    { 
      audio_output_as_selected();
      forward_audio_object(first_action_argument_as_float()); 
      break; 
    }
  case ec_ao_rewind: 
    { 
      audio_output_as_selected();
      rewind_audio_object(first_action_argument_as_float()); 
      break; 
    }
  case ec_ao_set_position: { audio_output_as_selected(); set_audio_object_position(first_action_argument_as_float()); break; }
  case ec_ao_set_position_samples: { audio_output_as_selected(); set_audio_object_position_samples(first_action_argument_as_long_int()); break; }
  case ec_ao_get_position: { set_last_float(get_audio_output()->position().seconds()); break; }
  case ec_ao_get_position_samples: { set_last_long_integer(get_audio_output()->position().samples()); break; }
  case ec_ao_get_length: { set_last_float(get_audio_output()->length().seconds()); break; }
  case ec_ao_get_length_samples: { set_last_long_integer(get_audio_output()->length().samples()); break; }
  case ec_ao_get_format: { 
    set_last_string(get_audio_output()->format_string() + "," +
		    kvu_numtostr(get_audio_output()->channels()) + "," +
		    kvu_numtostr(get_audio_output()->samples_per_second())); 
    break; 
  }
  case ec_ao_wave_edit: { audio_output_as_selected(); wave_edit_audio_object(); break; }

    // ---
    // Chain operators
    // ---
  case ec_cop_add: { add_chain_operator(first_action_argument_as_string()); break; }
  case ec_cop_bypass: { bypass_chain_operator(first_action_argument_as_string()); break; }
  case ec_cop_describe: 
    { 
      const CHAIN_OPERATOR *t = get_chain_operator();
      set_last_string(t == 0 ? "" : ECA_OBJECT_FACTORY::chain_operator_to_eos(t)); 
      break;
    }
  case ec_cop_remove: { remove_chain_operator(); break; }
  case ec_cop_list: { set_last_string_list(chain_operator_names()); break; }
  case ec_cop_is_bypassed: { set_last_integer(chain_operator_is_bypassed()); break; }
  case ec_cop_select: { select_chain_operator(first_action_argument_as_int()); break; }
  case ec_cop_selected: { set_last_integer(selected_chain_operator()); break; }
  case ec_cop_set: 
    { 
      vector<string> a = kvu_string_to_vector(first_action_argument_as_string(), ',');
      if (a.size() < 3) {
	set_last_error("Not enough parameters!");
	break;
      }
      int id1 = atoi(a[0].c_str());
      int id2 = atoi(a[1].c_str());
      CHAIN_OPERATOR::parameter_t v = atof(a[2].c_str());

      bool valid = 
	action_helper_check_cop_op_args(id1, id2);
      if (valid == true) {
	select_chain_operator(id1);
	select_chain_operator_parameter(id2);
	set_chain_operator_parameter(v);
      }
      // note: helper func sets the error string if needed
      break; 
    }
  case ec_cop_get:
    {
      vector<string> a =
	kvu_string_to_vector(first_action_argument_as_string(), ',');
      if (a.size() < 2) {
        set_last_error("Not enough parameters!");
        break;
      }
      int id1 = atoi(a[0].c_str());
      int id2 = atoi(a[1].c_str());
      bool valid = 
	action_helper_check_cop_op_args(id1, id2);
      if (valid == true) {
        select_chain_operator(id1);
        select_chain_operator_parameter(id2);
        set_last_float(get_chain_operator_parameter());
      }
      // note: helper func sets the error string if needed
      break;
    }

  case ec_cop_status: 
    { 
      set_last_string(chain_operator_status()); 
      break; 
    }

    // ---
    // Chain operator parameters
    // ---
  case ec_copp_list: { set_last_string_list(chain_operator_parameter_names()); break; }
  case ec_copp_select: { select_chain_operator_parameter(first_action_argument_as_int()); break; }
  case ec_copp_selected: { set_last_integer(selected_chain_operator_parameter()); break; }
  case ec_copp_set: { set_chain_operator_parameter(first_action_argument_as_float()); break; }
  case ec_copp_get: { set_last_float(get_chain_operator_parameter()); break; }

    // ---
    // Controllers
    // ---
  case ec_ctrl_add: { add_controller(first_action_argument_as_string()); break; }
  case ec_ctrl_describe: 
    { 
      const GENERIC_CONTROLLER *t = get_controller();
      set_last_string(t == 0 ? "" : ECA_OBJECT_FACTORY::controller_to_eos(t)); 
      break;
    }
  case ec_ctrl_remove: { remove_controller(); break; }
  case ec_ctrl_list: { set_last_string_list(controller_names()); break; }
  case ec_ctrl_select: { select_controller(first_action_argument_as_int()); break; }
  case ec_ctrl_selected: { set_last_integer(selected_controller()); break; }
  case ec_ctrl_status: 
    { 
      set_last_string(controller_status()); 
      break; 
    }
  case ec_ctrl_get_target: { set_last_integer(selected_controller_target()); break; }
  
    // ---
    // Controller parameters
    // ---
  case ec_ctrlp_list: { set_last_string_list(controller_parameter_names()); break; }
  case ec_ctrlp_select: { select_controller_parameter(first_action_argument_as_int()); break; }
  case ec_ctrlp_selected: { set_last_integer(selected_controller_parameter()); break; }
  case ec_ctrlp_get: { set_last_float(get_controller_parameter()); break; }
  case ec_ctrlp_set: { set_controller_parameter(first_action_argument_as_float()); break; }

  case ec_cop_register: { cop_register(); break; }
  case ec_preset_register: { preset_register(); break; }
  case ec_ladspa_register: { ladspa_register(); break; }
  case ec_lv2_register: { lv2_register(); break; }
  case ec_ctrl_register: { ctrl_register(); break; }

  case ec_map_cop_list: { cop_descriptions(); break; }
  case ec_map_preset_list: { preset_descriptions(); break; }
  case ec_map_ladspa_list: { ladspa_descriptions(false); break; }
  case ec_map_ladspa_id_list: { ladspa_descriptions(true); break; }
  case ec_map_lv2_list: { lv2_descriptions(); break; }
  case ec_map_ctrl_list: { ctrl_descriptions(); break; }

  // ---
  // Engine commands
  // ---
  case ec_engine_launch: { 
    if (is_engine_ready_for_commands() != true) 
      engine_start(); 
    else
      set_last_error("Engine already running, use 'engine-halt' first.");
    break; 
  }
  case ec_engine_halt: {
    if (is_engine_ready_for_commands() == true)
      close_engine(); 
    else
      set_last_error("Engine not running, use 'engine-launch' first.");
    break; 
  }
  case ec_engine_status: { set_last_string(engine_status()); break; }

  // ---
  // Internal commands
  // ---
  case ec_int_cmd_list: { set_last_string_list(registered_commands_list()); break; }
  case ec_int_log_history: { set_last_string(ECA_LOGGER::instance().log_history()); break; }
  case ec_int_output_mode_wellformed: { 
    ECA_LOGGER::attach_logger(new ECA_LOGGER_WELLFORMED()); 
    wellformed_mode_rep = true;
    break; 
  }
  case ec_int_set_float_to_string_precision: { set_float_to_string_precision(first_action_argument_as_int()); break; }
  case ec_int_set_log_history_length: { ECA_LOGGER::instance().set_log_history_length(first_action_argument_as_int()); break; }
  case ec_int_version_string: { set_last_string(ecasound_library_version); break; }
  case ec_int_version_lib_current: { set_last_integer(ecasound_library_version_current); break; }
  case ec_int_version_lib_revision: { set_last_integer(ecasound_library_version_revision); break; }
  case ec_int_version_lib_age: { set_last_integer(ecasound_library_version_age); break; }

  // ---
  // Dump commands
  // ---
  case ec_dump_target: { ctrl_dump_rep.set_dump_target(first_action_argument_as_string()); break; }
  case ec_dump_status: { ctrl_dump_rep.dump_status(); break; }
  case ec_dump_position: { ctrl_dump_rep.dump_position(); break; }
  case ec_dump_length: { ctrl_dump_rep.dump_length(); break; }
  case ec_dump_cs_status: { ctrl_dump_rep.dump_chainsetup_status(); break; }
  case ec_dump_c_selected: { ctrl_dump_rep.dump_selected_chain(); break; }
  case ec_dump_ai_selected: { ctrl_dump_rep.dump_selected_audio_input(); break; }
  case ec_dump_ai_position: { ctrl_dump_rep.dump_audio_input_position(); break; }
  case ec_dump_ai_length: { ctrl_dump_rep.dump_audio_input_length(); break; }
  case ec_dump_ai_open_state: { ctrl_dump_rep.dump_audio_input_open_state(); break; }
  case ec_dump_ao_selected: { ctrl_dump_rep.dump_selected_audio_output(); break; }
  case ec_dump_ao_position: { ctrl_dump_rep.dump_audio_output_position(); break; }
  case ec_dump_ao_length: { ctrl_dump_rep.dump_audio_output_length(); break; }
  case ec_dump_ao_open_state: { ctrl_dump_rep.dump_audio_output_open_state(); break; }
  case ec_dump_cop_value: 
    { 
      vector<string> temp = kvu_string_to_vector(first_action_argument_as_string(), ',');
      if (temp.size() > 1) {
	ctrl_dump_rep.dump_chain_operator_value(atoi(temp[0].c_str()),
						atoi(temp[1].c_str()));
      }
      break; 
    }

    // ---
    // Commands with external dependencies
    // ---
#if ECA_COMPILE_JACK
  case ec_jack_connect: 
    {
      const vector<string>& params = action_arguments_as_vector();
      if (params.size() >= 2) 
	JACK_CONNECTIONS::connect(params[0].c_str(), params[1].c_str());
      break;
    }
  case ec_jack_disconnect:   
    {
      const vector<string>& params = action_arguments_as_vector();
      if (params.size() >= 2) 
	JACK_CONNECTIONS::disconnect(params[0].c_str(), params[1].c_str());
      break;
    }
  case ec_jack_list_connections:   
    {
      string foo;
      if (JACK_CONNECTIONS::list_connections(&foo) == true)
	set_last_string(foo);
      else
	set_last_error("Unable to a list of JACK connections.");
      break;
    }
#endif

  } // <-- switch-case


  if (action_reconnect == true) {
    if (is_selected() == false ||
	is_valid() == false) {
      set_last_error("Can't reconnect chainsetup.");
    }
    else {
      connect_chainsetup(0);
      if (selected_chainsetup() != connected_chainsetup()) {
	set_last_error("Can't reconnect chainsetup.");
      }
      else {
	if (action_restart == true) {
	  DBC_CHECK(is_running() != true);
	  start();
	}
      }
    }
  }
}

/**
 * Executes chainsetup edit on connect chainsetup.
 * 
 * @pre is_connected()
 */
bool ECA_CONTROL::execute_edit_on_connected(const chainsetup_edit_t& edit)
{
  DBC_REQUIRE(is_connected() == true);

  bool retval = false;
  
  if (is_engine_ready_for_commands() == true) {
    ECA_ENGINE::complex_command_t engine_cmd;
    engine_cmd.type = ECA_ENGINE::ep_exec_edit;
    engine_cmd.cs = edit;
    engine_repp->command(engine_cmd);
    retval = true;
  }
  else {
    /* note: engine not yet running, execute edit directly */
    retval = session_repp->connected_chainsetup_repp->execute_edit(edit);
  }

  return retval;
}

/**
 * Executes chainsetup edit on selected chainsetup.
 * 
 * @param edit object specifying the edit action
 * @param index if non-negative, override the chainsetup selection
 */
bool ECA_CONTROL::execute_edit_on_selected(const chainsetup_edit_t& edit, int index)
{
  bool retval = false;

  ECA_CHAINSETUP *csetup = 0;

  if (index < 0) {
    csetup = selected_chainsetup_repp;
  }
  else {
    if (index >= 0 &&
	index < static_cast<int>(session_repp->chainsetups_rep.size())) {
      csetup = session_repp->chainsetups_rep[index];
    }
  }

  /* note: make sure that if the selected chainsetup is 
   *       in use by the engine, the edit is performed 
   *       by the engine thread! 
   */
  if (csetup != 0) {
    if (csetup->is_enabled() == true &&
	is_engine_ready_for_commands() == true) {
      execute_edit_on_connected(edit);
    }
    else {
      csetup->execute_edit(edit);
    }
  }

  return retval;
}

void ECA_CONTROL::print_last_value(struct eci_return_value *retval) const
{
  std::string result;

  if (retval->type == eci_return_value::retval_error) {
    result += "ERROR: ";
  }

  result += ECA_CONTROL_MAIN::return_value_to_string(retval);

  if (wellformed_mode_rep != true) {
    if (result.size() > 0) 
      ECA_LOG_MSG(ECA_LOGGER::eiam_return_values, result);
  }
  else {
    /* in wellformed-output-mode we always create return output */
    ECA_LOG_MSG(ECA_LOGGER::eiam_return_values, 
		std::string(return_value_type_to_string(retval)) +
		" " + result);
  }
}

string ECA_CONTROL::chainsetup_details_to_string(const ECA_CHAINSETUP* cs) const
{
  string result;
  vector<CHAIN*>::const_iterator chain_citer;

  result += "\n -> Objects..: " + kvu_numtostr(cs->inputs.size());
  result += " inputs, " + kvu_numtostr(cs->outputs.size());
  result += " outputs, " + kvu_numtostr(cs->chains.size());
  result += " chains";

  // FIXME: add explanations on why the chainsetup cannot be
  //        connected
  
  result += "\n -> State....: ";

  if (cs->is_locked()) {
    result += "connected to engine (engine status: ";
    result += engine_status() + ")";
  }
  else if (cs->is_enabled() && is_engine_created() == true) {
    result += "connected (engine status: ";
    result += engine_status() + ")";
  }
  else if (cs->is_enabled())
    result += "connected (engine not yet running)";
  else if (cs->is_valid()) 
    result += "valid (can be connected)";
  else
    result += "not valid (cannot be connected)";
  
  result += "\n -> Position.:  ";
  result += kvu_numtostr(cs->position_in_seconds_exact(), 3);
  result += " / ";
  if (cs->length_set())
    result += kvu_numtostr(cs->length_in_seconds_exact(), 3);
  else
    result += "inf";

  result += "\n -> Options..: ";
  result += cs->options_to_string();

  for(chain_citer = cs->chains.begin();
      chain_citer != cs->chains.end();) {
    result += "\n -> Chain \"" + (*chain_citer)->name() + "\": ";
    int idx =
      (*chain_citer)->connected_input();
    if (idx >= 0)
      result += ECA_OBJECT_FACTORY::audio_object_to_eos(cs->inputs[idx], "i");
    result += " ";
    result += (*chain_citer)->to_string();
    idx = (*chain_citer)->connected_output();
    if (idx >= 0)
      result += ECA_OBJECT_FACTORY::audio_object_to_eos(cs->outputs[idx], "o");

    ++chain_citer;
  }
  
  return result;
}

string ECA_CONTROL::chainsetup_status(void) const 
{
  vector<ECA_CHAINSETUP*>::const_iterator cs_citer = session_repp->chainsetups_rep.begin();
  int index = 0;
  string result ("### Chainsetup status ###\n");

  while(cs_citer != session_repp->chainsetups_rep.end()) {
    result += "Chainsetup ("  + kvu_numtostr(++index) + ") \"";
    result += (*cs_citer)->name() + "\" ";

    if (*cs_citer ==
	selected_chainsetup_repp)
      result += "[selected] ";
    if (*cs_citer ==
	session_repp->connected_chainsetup_repp)
      result += "[connected] ";

    if ((*cs_citer == selected_chainsetup_repp) ||
	(*cs_citer == session_repp->connected_chainsetup_repp))
      result += chainsetup_details_to_string((*cs_citer));
    else
      result += ": <detailed status omitted -- set as selected to see full status>";

    ++cs_citer;
    if (cs_citer != session_repp->chainsetups_rep.end()) result += "\n";
  }

  return result;
}

string ECA_CONTROL::chain_status(void) const
{
  // --------
  DBC_REQUIRE(is_selected() == true);
  // --------
  
  MESSAGE_ITEM mitem;
  vector<CHAIN*>::const_iterator chain_citer;
  const vector<string>& schains = selected_chainsetup_repp->selected_chains();
  mitem << "### Chain status (chainsetup '" 
	<< selected_chainsetup()
	<< "') ###\n";

  for(chain_citer = selected_chainsetup_repp->chains.begin(); chain_citer != selected_chainsetup_repp->chains.end();) {
    mitem << "Chain \"" << (*chain_citer)->name() << "\" ";
    if ((*chain_citer)->is_muted()) mitem << "[muted] ";
    if ((*chain_citer)->is_bypassed()) mitem << "[bypassed] ";
    if (find(schains.begin(), schains.end(), (*chain_citer)->name()) != schains.end()) mitem << "[selected] ";
    for(int n = 0; n < (*chain_citer)->number_of_chain_operators(); n++) {
      mitem << "\"" << (*chain_citer)->get_chain_operator(n)->name() << "\"";
      if (n == (*chain_citer)->number_of_chain_operators()) mitem << " -> ";
    }
    ++chain_citer;
    if (chain_citer != selected_chainsetup_repp->chains.end()) mitem << "\n";
  }

  return mitem.to_string();
}

string ECA_CONTROL::chain_operator_status(void) const
{
  // --------
  DBC_REQUIRE(is_selected() == true);
  // --------

  MESSAGE_ITEM msg;
  string st_info_string;
  vector<CHAIN*>::const_iterator chain_citer = selected_chainsetup_repp->chains.begin();

  msg << "### Chain operator status (chainsetup '" 
      << selected_chainsetup() 
      << "') ###\n";

  while(chain_citer != selected_chainsetup_repp->chains.end()) {
    msg << "Chain \"" << (*chain_citer)->name() << "\":\n";
    for(int p = 0; p < (*chain_citer)->number_of_chain_operators(); p++) {
      const CHAIN_OPERATOR* cop = (*chain_citer)->get_chain_operator(p);
      msg << "\t";
      msg << p + 1 << ". ";
      msg << cop->name();
      if ((*chain_citer)->is_operator_bypassed(p + 1)) 
	msg << " (BYPASSED)";
      for(int n = 0; n < cop->number_of_params(); n++) {
	if (n == 0) msg << ": ";
	msg << "[" << n + 1 << "] ";
	msg << cop->get_parameter_name(n + 1);
	msg << " ";
	msg << float_to_string(cop->get_parameter(n + 1));
	if (n + 1 < cop->number_of_params()) msg <<  ", ";
      }
      st_info_string = cop->status();
      if (st_info_string.empty() == false) {
	msg << "\n\tStatus info:\n" << st_info_string;
      }
      if (p + 1 < (*chain_citer)->number_of_chain_operators()) msg << "\n";
    }
    ++chain_citer;
    if (chain_citer != selected_chainsetup_repp->chains.end()) msg << "\n";
  }
  return msg.to_string();
}

string ECA_CONTROL::controller_status(void) const
{
  // --------
  DBC_REQUIRE(is_selected() == true);
  // --------

  MESSAGE_ITEM mitem;
  string st_info_string;
  vector<CHAIN*>::const_iterator chain_citer;

  mitem << "### Controller status (chainsetup '"
	<< selected_chainsetup()
	<< "') ###\n";

  for(chain_citer = selected_chainsetup_repp->chains.begin(); chain_citer != selected_chainsetup_repp->chains.end();) {
    mitem << "Chain \"" << (*chain_citer)->name() << "\":\n";
    for(int p = 0; p < (*chain_citer)->number_of_controllers(); p++) {
      const GENERIC_CONTROLLER* gtrl = (*chain_citer)->get_controller(p);
      mitem << "\t" << p + 1 << ". " << gtrl->name() << ": ";
      for(int n = 0; n < gtrl->number_of_params(); n++) {
	mitem << "\n\t\t[" << n + 1 << "] ";
	mitem << gtrl->get_parameter_name(n + 1);
	mitem << " ";
	mitem << float_to_string(gtrl->get_parameter(n + 1));
	if (n + 1 < gtrl->number_of_params()) mitem <<  ", ";
      }
      st_info_string = gtrl->status();
      if (st_info_string.empty() == false) {
	mitem << "\n\t -- Status info: " << st_info_string;
      }
      if (p + 1 < (*chain_citer)->number_of_controllers()) mitem << "\n";
    }
    ++chain_citer;
    if (chain_citer != selected_chainsetup_repp->chains.end()) mitem << "\n";
  }
  return mitem.to_string();
}

string ECA_CONTROL::aio_status(void) const
{
  // --------
  DBC_REQUIRE(is_selected() == true);
  // --------

  string st_info_string;
  vector<AUDIO_IO*>::size_type adev_sizet = 0;
  vector<AUDIO_IO*>::const_iterator adev_citer = selected_chainsetup_repp->inputs.begin();

  st_info_string += "### Audio input/output status (chainsetup '" +
    selected_chainsetup() + "') ###\n";

  while(adev_citer != selected_chainsetup_repp->inputs.end()) {
    st_info_string += "Input (" + kvu_numtostr(adev_sizet + 1) + "): \"";
    for(int n = 0; n < (*adev_citer)->number_of_params(); n++) {
      st_info_string += (*adev_citer)->get_parameter(n + 1);
      if (n + 1 < (*adev_citer)->number_of_params()) st_info_string += ",";
    }
    st_info_string += "\" - [" + (*adev_citer)->name() + "]";
    if ((*adev_citer) == selected_audio_input_repp) st_info_string += " [selected]";
    st_info_string += "\n -> connected to chains \"";
    vector<string> temp = selected_chainsetup_repp->get_attached_chains_to_input((selected_chainsetup_repp->inputs)[adev_sizet]);
    vector<string>::const_iterator p = temp.begin();
    while (p != temp.end()) {
      st_info_string += *p; 
      ++p;
      if (p != temp.end())  st_info_string += ",";
    }
    st_info_string += "\": " + (*adev_citer)->status() + "\n";
    ++adev_sizet;
    ++adev_citer;
  }

  adev_sizet = 0;
  adev_citer = selected_chainsetup_repp->outputs.begin();
  while(adev_citer != selected_chainsetup_repp->outputs.end()) {
    st_info_string += "Output (" + kvu_numtostr(adev_sizet + 1) + "): \"";
    for(int n = 0; n < (*adev_citer)->number_of_params(); n++) {
      st_info_string += (*adev_citer)->get_parameter(n + 1);
      if (n + 1 < (*adev_citer)->number_of_params()) st_info_string += ",";
    }
    st_info_string += "\" - [" + (*adev_citer)->name() + "]";
    if ((*adev_citer) == selected_audio_output_repp) st_info_string += " [selected]";
    st_info_string += "\n -> connected to chains \"";
    vector<string> temp = selected_chainsetup_repp->get_attached_chains_to_output((selected_chainsetup_repp->outputs)[adev_sizet]);
    vector<string>::const_iterator p = temp.begin();
    while (p != temp.end()) {
      st_info_string += *p; 
      ++p;
      if (p != temp.end())  st_info_string += ",";
    }
    st_info_string += "\": ";
    st_info_string += (*adev_citer)->status();
    ++adev_sizet;
    ++adev_citer;
    if (adev_sizet < selected_chainsetup_repp->outputs.size()) st_info_string += "\n";
  }
  return st_info_string;
}

void ECA_CONTROL::aio_register(void)
{
  ECA_LOG_MSG(ECA_LOGGER::info, "Registered audio object types:\n");
  string result (eca_aio_register_sub(ECA_OBJECT_FACTORY::audio_io_nonrt_map()));

  result += "\n";

  result += eca_aio_register_sub(ECA_OBJECT_FACTORY::audio_io_rt_map());

  set_last_string(result);  
}

static string eca_aio_register_sub(ECA_OBJECT_MAP& objmap)
{
  string result;
  const list<string>& objlist = objmap.registered_objects();
  list<string>::const_iterator p = objlist.begin();
  int count = 1;
  while(p != objlist.end()) {
    string temp;
    const AUDIO_IO* q = dynamic_cast<const AUDIO_IO*>(objmap.object_expr(*p));
    
    DBC_CHECK(q != 0);

    if (q != 0) {
      int params = q->number_of_params();
      if (params > 0) {
	temp += ": ";
	for(int n = 0; n < params; n++) {
	  temp += q->get_parameter_name(n + 1);
	  if (n + 1 < params) temp += ",";
	}
      }

      result += kvu_numtostr(count) + ". " + q->name() + ", regex: " + 
	        objmap.keyword_to_expr(*p) + ", params" + temp;
      result += "\n";

      ++count;
    }
    //  else std::cerr << "Failed obj: " << *p << "." << std::endl;

    ++p;
  }
  return result;
}

void ECA_CONTROL::cop_register(void)
{
  ECA_LOG_MSG(ECA_LOGGER::info, "Registered chain operators:\n");
  string result;
  const list<string>& objlist = ECA_OBJECT_FACTORY::chain_operator_map().registered_objects();
  list<string>::const_iterator p = objlist.begin();
  int count = 1;
  while(p != objlist.end()) {
    string temp;
    const CHAIN_OPERATOR* q = dynamic_cast<const CHAIN_OPERATOR*>(ECA_OBJECT_FACTORY::chain_operator_map().object(*p));
    if (q != 0) {
      int params = q->number_of_params();
      for(int n = 0; n < params; n++) {
	if (n == 0) temp += ":";
	temp += q->get_parameter_name(n + 1);
	if (n + 1 < params) temp += ",";
      }
      result += kvu_numtostr(count) + ". " + q->name() + ", -" + *p + temp;
      result += "\n";
      ++count;
    }
    ++p;
  }
  set_last_string(result);
}

void ECA_CONTROL::preset_register(void)
{
  ECA_LOG_MSG(ECA_LOGGER::info, "Registered effect presets:\n");
  string result;
#ifndef ECA_DISABLE_EFFECTS
  const list<string>& objlist = ECA_OBJECT_FACTORY::preset_map().registered_objects();
  list<string>::const_iterator p = objlist.begin();
  int count = 1;
  while(p != objlist.end()) {
    string temp;
    const PRESET* q = dynamic_cast<const PRESET*>(ECA_OBJECT_FACTORY::preset_map().object(*p));
    if (q != 0) {
      int params = q->number_of_params();
      for(int n = 0; n < params; n++) {
	if (n == 0) temp += ":";
	temp += q->get_parameter_name(n + 1);
	if (n + 1 < params) temp += ",";
      }

      result += kvu_numtostr(count) + ". " + q->name() + ", -pn:" + *p + temp;
      result += "\n";

      ++count;
    }
    ++p;
  }
#endif
  set_last_string(result);
}


void ECA_CONTROL::ladspa_register(void)
{
  ECA_LOG_MSG(ECA_LOGGER::info, "Registered LADSPA plugins:\n");
  string result;
#ifndef ECA_DISABLE_EFFECTS
  const list<string>& objlist = ECA_OBJECT_FACTORY::ladspa_plugin_map().registered_objects();
  list<string>::const_iterator p = objlist.begin();
  int count = 1;
  while(p != objlist.end()) {
    const EFFECT_LADSPA* q = dynamic_cast<const EFFECT_LADSPA*>(ECA_OBJECT_FACTORY::ladspa_plugin_map().object(*p));
    if (q != 0) {
      string temp = "\n\t-el:" + q->unique() + ",";
      int params = q->number_of_params();
      for(int n = 0; n < params; n++) {
	temp += "'" + q->get_parameter_name(n + 1) + "'";
	if (n + 1 < params) temp += ",";
      }
      
      result += kvu_numtostr(count) + ". " + q->name() + "" + temp;
      result += "\n";

      ++count;
    }
    ++p;
  }
#endif
  set_last_string(result);
}

void ECA_CONTROL::lv2_register(void)
{
  ECA_LOG_MSG(ECA_LOGGER::info, "Registered LV2 plugins:\n");
  string result;
#if !defined(ECA_DISABLE_EFFECTS) && defined(ECA_USE_LIBLILV)
  const list<string>& objlist = ECA_OBJECT_FACTORY::lv2_plugin_map().registered_objects();
  list<string>::const_iterator p = objlist.begin();
  int count = 1;
  while(p != objlist.end()) {
    const EFFECT_LV2* q = dynamic_cast<const EFFECT_LV2*>(ECA_OBJECT_FACTORY::lv2_plugin_map().object(*p));
    if (q != 0) {
      string temp = "\n\t-elv2:" + q->unique() + ",";
      int params = q->number_of_params();
      for(int n = 0; n < params; n++) {
	temp += "'" + q->get_parameter_name(n + 1) + "'";
	if (n + 1 < params) temp += ",";
      }
      
      result += kvu_numtostr(count) + ". " + q->name() + "" + temp;
      result += "\n";

      ++count;
    }
    ++p;
  }
#endif
  set_last_string(result);
}


void ECA_CONTROL::ctrl_register(void)
{
  ECA_LOG_MSG(ECA_LOGGER::info, "Registered controllers:\n");
  string result;
  const list<string>& objlist = ECA_OBJECT_FACTORY::controller_map().registered_objects();
  list<string>::const_iterator p = objlist.begin();
  int count = 1;
  while(p != objlist.end()) {
    string temp;
    const GENERIC_CONTROLLER* q = dynamic_cast<const GENERIC_CONTROLLER*>(ECA_OBJECT_FACTORY::controller_map().object(*p));
    if (q != 0) {
      int params = q->number_of_params();
      for(int n = 0; n < params; n++) {
	if (n == 0) temp += ":";
	temp += q->get_parameter_name(n + 1);
	if (n + 1 < params) temp += ",";
      }

      result += kvu_numtostr(count) + ". " + q->name() + ", -" + *p +
	temp;
      result += "\n";

      ++count;
    }
    ++p;
  }
  set_last_string(result);
}

/**
 * Print description of all chain operators and 
 * their parameters.
 */
void ECA_CONTROL::operator_descriptions_helper(const ECA_OBJECT_MAP& arg, string* result)
{
  /* switch to "C" locale to avoid strange floating point 
   * presentations that could break the output format
   * (for example "a,b" insteof of "a.b" */
#if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE)
  string old_locale (setlocale(LC_ALL, "C"));
#endif

  const list<string>& objlist = arg.registered_objects();
  list<string>::const_iterator p = objlist.begin();
  int count = 1;
  while(p != objlist.end()) {
    string temp;
    const OPERATOR* q = dynamic_cast<const OPERATOR*>(arg.object(*p));
    if (q != 0) {
      /* FIXME: prefer backslash escaped over '_' to handle commas */
      
      /* 1. keyword */
      *result += kvu_string_search_and_replace(*p, ',', '_');
      /* 2. name */
      *result += "," + kvu_string_search_and_replace(q->name(), ',', '_');
      /* 3. description */
      *result += "," + kvu_string_search_and_replace(q->description(), ',', '_');

      int params = q->number_of_params();

      /* 4. number of params */
      *result += "," + kvu_numtostr(params);

      /* 5. description of params (for all params) */
      for(int n = 0; n < params; n++) {
	struct OPERATOR::PARAM_DESCRIPTION pd;
	q->parameter_description(n + 1, &pd);

	/* 5.1 name of param */
	*result += "," + kvu_string_search_and_replace(q->get_parameter_name(n + 1), ',', '_');
	/* 5.2 description */
	*result += "," + kvu_string_search_and_replace(pd.description, ',', '_');
	/* 5.3 default value */
	*result += "," + float_to_string(pd.default_value);
	/* 5.4 is bounded above (1=yes, 0=no) */
	*result += ",above=" + kvu_numtostr(static_cast<int>(pd.bounded_above));
	/* 5.5 upper bound */
	*result += ",upper=" + float_to_string(pd.upper_bound);
	/* 5.6 is bounded below (1=yes, 0=no) */
	*result += ",below=" + kvu_numtostr(static_cast<int>(pd.bounded_below));
	/* 5.7 lower bound */
	*result += ",lower=" + float_to_string(pd.lower_bound);
	/* 5.8. is toggled (1=yes, 0=no) */
	*result += "," + kvu_numtostr(static_cast<int>(pd.toggled));
	/* 5.9. is integer value (1=yes, 0=no) */
	*result += "," + kvu_numtostr(static_cast<int>(pd.integer));
	/* 5.10. is logarithmis value (1=yes, 0=no) */
	*result += "," + kvu_numtostr(static_cast<int>(pd.logarithmic));
	/* 5.11. is output value (1=yes, 0=no) */
	*result += ",output=" + kvu_numtostr(static_cast<int>(pd.output));
      }
      *result += "\n";
      ++count;
    }
    ++p;
  }

  /* see above */
#if defined(HAVE_LOCALE_H) && defined(HAVE_SETLOCALE)
  setlocale(LC_ALL, old_locale.c_str());
#endif
}

/**
 * Print the description of all chain operators and 
 * their parameters.
 */
void ECA_CONTROL::cop_descriptions(void)
{
  string result;
  operator_descriptions_helper(ECA_OBJECT_FACTORY::chain_operator_map(), &result);
  set_last_string(result);
}

/**
 * Prints the description of all effect presets and 
 * their parameters.
 */
void ECA_CONTROL::preset_descriptions(void)
{
  string result;
  operator_descriptions_helper(ECA_OBJECT_FACTORY::preset_map(), &result);
  set_last_string(result);
}

/**
 * Prints the description of all LADSPA plugins and 
 * their parameters.
 */
void ECA_CONTROL::ladspa_descriptions(bool use_id)
{
  string result;
  if (use_id) {
    operator_descriptions_helper(ECA_OBJECT_FACTORY::ladspa_plugin_id_map(), &result);
  }
  else {
    operator_descriptions_helper(ECA_OBJECT_FACTORY::ladspa_plugin_map(), &result);
  }
  set_last_string(result);
}

/**
 * Prints the description of all LV2 plugins and 
 * their parameters.
 */
void ECA_CONTROL::lv2_descriptions()
{
  string result;
  operator_descriptions_helper(ECA_OBJECT_FACTORY::lv2_plugin_map(), &result);
  set_last_string(result);
}

/**
 * Print the description of all controllers and 
 * their parameters.
 */
void ECA_CONTROL::ctrl_descriptions(void)
{
  string result;
  operator_descriptions_helper(ECA_OBJECT_FACTORY::controller_map(), &result);
  set_last_string(result);
}
