// ------------------------------------------------------------------------
// eca-osc.cpp: Class implementing the Ecasound OSC interface
// Copyright (C) 2009,2012,2014 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 3
//
// References:
//     - liblo:
//       http://liblo.sourceforge.net/
//       http://liblo.sourceforge.net/docs/
//     - Open Source Control (OSC):
//       http://opensoundcontrol.org/
//       http://archive.cnmat.berkeley.edu/OpenSoundControl/
//       http://en.wikipedia.org/wiki/OpenSound_Control
//
// 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

#ifdef ECA_USE_LIBLO

#include <cstdio>
#include <cstdlib>
#include <string>

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

#include "eca-control-mt.h"
#include "eca-chainsetup-edit.h"
#include "eca-chainsetup.h"
#include "eca-logger.h"

#include "eca-osc.h"

// #define VERBOSE_FOR_DEBUGGING 1
#undef VERBOSE_FOR_DEBUGGING

using namespace std;

const std::string eca_osc_interface_namespace_const ("ecasound");

static void cb_lo_err_handler(int num, const char *msg, const char *where)
{
  std::fprintf(stderr, 
	       "lo_error: num=%d, msg=%s, where=%s.\n",
	       num, msg, where);
}

int cb_lo_method_handler(const char *path, const char *types, lo_arg **argv, int argc, lo_message msg, void *user_data)
{
  ECA_OSC_INTERFACE* self = 
    reinterpret_cast<ECA_OSC_INTERFACE*>(user_data);

#ifdef VERBOSE_FOR_DEBUGGING
  std::fprintf(stdout, 
	       "lo_method: path=%s, types=%s, args=%d, userdata=%p.\n",
	       path, types, argc, user_data);
#endif

  int retval = self->handle_osc_message(path, types, argv, argc);

#ifdef VERBOSE_FOR_DEBUGGING
  for(int n = 0; n < argc; n++) {
    switch (types[n]) {
    case 'i':
      std::fprintf(stdout, 
		   "lo_arg #%d %c=<%d>\n",
		   n, types[n], argv[n]->i);
      break;
    case 'f':
      std::fprintf(stdout, 
		   "lo_arg #%d %c=<%.03f>\n",
		   n, types[n], argv[n]->f);
      break;
    default:
      std::fprintf(stdout, 
		   "lo_arg #%d %c=<raw:%08X>\n",
		   n, types[n], *(uint32_t*)argv[n]);
      break;
    }
  }
#endif
   
  /* note: doesn't seem to work in 0.23 (at least when messages 
     are sent with oscsend from 0.26) */
  // lo_message_pp(msg);

  return retval;
}

/**
 * Constructor
 *
 * If any other object may use the control object, passed to 
 * ECA_OSC_INTERFACE, at the same time, ECA_CONTROL_MT should
 * be used to guarantee thread-safety access to control
 * functions.
 *
 * @param ecacontrol if the control object is u
 */
ECA_OSC_INTERFACE::ECA_OSC_INTERFACE(ECA_CONTROL_MT *ecacontrol, int port)
  : ec_repp(ecacontrol),
    running_rep(false),
    udp_port_rep(port),
    lo_thr_repp(0)
{

}

ECA_OSC_INTERFACE::~ECA_OSC_INTERFACE(void)
{
  if (is_running() == true) {
    stop();
  }
}

void ECA_OSC_INTERFACE::start(void)
{
  const char* port_str = 0;
  if (udp_port_rep > 0) {
    port_str = kvu_numtostr(udp_port_rep).c_str();
  }

  /* FIXME: move to after opening and acquire port
   *        with lo_server_thread_get_port */
  ECA_LOG_MSG(ECA_LOGGER::info,
	      "started OSC interface at UDP port "
	      + kvu_numtostr(udp_port_rep));

  lo_thr_repp =
    lo_server_thread_new(port_str, cb_lo_err_handler);
  if (lo_thr_repp) {

    /* note: in liblo 0.23 function returns voide, but 
     *       in 0.26 in returns an int */
    lo_server_thread_start(lo_thr_repp);

    method_all_repp 
      = lo_server_thread_add_method(lo_thr_repp,
				    NULL,
				    NULL,
				    cb_lo_method_handler,
				    this);
				
    running_rep = true;
  }

  ECA_LOG_MSG(ECA_LOGGER::user_objects, 
	      "server thread started with status " 
	      + kvu_numtostr(running_rep));
}

void ECA_OSC_INTERFACE::stop(void)
{
  if (running_rep == true) {
    /* note: in liblo 0.23 function returns voide, but 
     *       in 0.26 in returns an int */
    lo_server_thread_stop(lo_thr_repp);
    lo_server_thread_free(lo_thr_repp);
    method_all_repp = 0;
    lo_thr_repp = 0;
    running_rep = false;
  }
}

bool ECA_OSC_INTERFACE::is_running(void) const
{
  return running_rep;
}

/** For "foo/bar/yeah", returns "foo" */
static string priv_path_get_front(const string &path)
{
  return string (path, 0, path.find("/"));
}

/** For "foo/bar/yeah", returns "bar/yeah" */
static string priv_path_pop_front(const std::string &path)
{
  size_t marker = path.find("/");
  if (marker != string::npos)
    return string(path, marker + 1);

  return path;
}

/**
 * Parses path with syntax "param/X" and writes X to 'param'.
 *
 * @return 0 on success, negative error code otherwise
 */ 
int ECA_OSC_INTERFACE::parse_path_param(const std::string &path, int *param)
{
  if (priv_path_get_front(string(path)) == "param") {
    string left_s = priv_path_pop_front(path);
    string param_s = priv_path_get_front(left_s);
    if (param_s.size() > 0 &&
	param != 0) {
      *param = std::atoi(param_s.c_str());
      return 0;
    }
  }
  return -1;
}

int ECA_OSC_INTERFACE::handle_chain_message(const std::string &path, const char *types, lo_arg **argv, int argc)
{
  int retval = -1;
  string chain_s = priv_path_get_front(path);
  
  ec_repp->lock_control();

  const ECA_CHAINSETUP *cs = ec_repp->get_connected_chainsetup();
  int c_index = cs ? cs->get_chain_index(chain_s) : -1;
  if (c_index > 0) {
    ECA::chainsetup_edit_t edit;
    edit.cs_ptr = cs;

    string left_s = priv_path_pop_front(path);
    string action = priv_path_get_front(left_s);

    if (action == "op") {
      left_s = priv_path_pop_front(left_s);
      string op_s = priv_path_get_front(left_s);
      int param = -1;
      int p_res =
	parse_path_param(priv_path_pop_front(left_s), &param);

      if (argc > 0 &&
	  p_res == 0) {
	DBC_CHECK(types[0] == 'f');
	edit.type = ECA::edit_cop_set_param;
        edit.need_chain_reinit = false;

	edit.m.cop_set_param.chain = c_index;
	edit.m.cop_set_param.op = std::atoi(op_s.c_str());
	edit.m.cop_set_param.param = param;
	edit.m.cop_set_param.value = argv[0]->f;
	
#ifdef VERBOSE_FOR_DEBUGGING
	std::fprintf(stdout, 
		     "chain=%s (id=%d), op=%d, param=%d to %.03f\n",
		     chain_s.c_str(), 
		     c_index,
		     edit.m.cop_set_param.op,
		     edit.m.cop_set_param.param,
		     edit.m.cop_set_param.value);
#endif

	ec_repp->execute_edit_on_connected(edit);
	
	retval = 0;
      }
    }
    else if (action == "ctrl") {
      left_s = priv_path_pop_front(left_s);
      string ctrl_s = priv_path_get_front(left_s);
      int param = -1;
      int p_res =
	parse_path_param(priv_path_pop_front(left_s), &param);
      if (argc > 0 &&
	  p_res == 0) {
	DBC_CHECK(types[0] == 'f');
	edit.type = ECA::edit_ctrl_set_param;
        edit.need_chain_reinit = false;
  
	edit.m.ctrl_set_param.chain = c_index;
	edit.m.ctrl_set_param.op = std::atoi(ctrl_s.c_str());
	edit.m.ctrl_set_param.param = param;
	edit.m.ctrl_set_param.value = argv[0]->f;
	
#ifdef VERBOSE_FOR_DEBUGGING
	std::fprintf(stdout, 
		     "chain=%s (id=%d), ctrl=%d, param=%d to %.03f\n",
		     chain_s.c_str(), 
		     c_index,
		     edit.m.ctrl_set_param.op,
		     edit.m.ctrl_set_param.param,
		     edit.m.ctrl_set_param.value);
#endif

	ec_repp->execute_edit_on_connected(edit);
	
	retval = 0;
      }
    }
    else {
      DBC_NEVER_REACHED();
    }
  }
  else {
#ifdef VERBOSE_FOR_DEBUGGING
    std::fprintf(stdout, 
		 "full=%s, chain=%s (id=%d), op=-, param=- to -\n",
		 path.c_str(),chain_s.c_str(), 
		 c_index);
#endif
  }

  ec_repp->unlock_control();    

  if (cs == 0) {
    ECA_LOG_MSG(ECA_LOGGER::info,
		"WARNING: no chainsetup connected, so ignoring OSC message \""
		+ path + "\"");
  }	
  
  return retval;
}

int ECA_OSC_INTERFACE::handle_osc_message(const char *path, const char *types, lo_arg **argv, int argc)
{
  int retval = -1;
  
  DBC_CHECK(path[0] == '/');
  string left_s = priv_path_pop_front(string(path));
  string namespace_s = priv_path_get_front(left_s);
  if (namespace_s == eca_osc_interface_namespace_const) {
    left_s = priv_path_pop_front(left_s);
    string component_s = priv_path_get_front(left_s);

#ifdef VERBOSE_FOR_DEBUGGING
    std::fprintf(stdout, 
		 "full=%s, left=%s, component=%s\n",
		 path, left_s.c_str(), component_s.c_str());
#endif

    if (component_s == "chain") {
      left_s = priv_path_pop_front(left_s);
      retval = handle_chain_message(left_s, types, argv, argc);
    }
  }
  else {
#ifdef VERBOSE_FOR_DEBUGGING
    std::fprintf(stdout, 
		 "full=%s, left=%s, ignoring...\n",
		 path, left_s.c_str());
#endif

    retval = -1;
  }

  return retval;
}

#endif /* ECA_USE_LIBLO */
