/* 
 * ola2pd - interface from Open Lighting Arquitecture to Pure Data
 * v 0.03
 *
 * Copyright (c) 2012-2013 Santiago Noreña <belfegor@gmail.com>
 *
 * ola2pd is an external for Pure Data and Max that reads one DMX512 
 * universe from the Open Lighting Arquitecture and output it like a list
 * of 512 channels.  
 *
 * Based on dmxmonitor Copyright (C) 2001 Dirk Jagdmann <doj@cubic.org>
 * and ola_dmxmonitor by Simon Newton (nomis52<AT>gmail.com)
 * 
 * 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 <m_pd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ola/BaseTypes.h>
#include <ola/Callback.h>
#include <ola/OlaCallbackClient.h>
#include <ola/OlaClientWrapper.h>
#include <ola/DmxBuffer.h>
#include <ola/io/SelectServer.h>
#include <ola/network/TCPSocket.h>

//static char *version = "$Revision: 0.01 $";

//#define DEBUG(x)


/*------------------------------------------------------------------------------
 *  CLASS DEF
 */
static t_class *ola2pd_class;

typedef struct _ola2pd {
	t_object            x_obj;
	t_outlet            *x_out;
   	unsigned int x_universe;
   	unsigned int x_counter;
	OlaCallbackClient *x_clientpointer;
} t_ola2pd;

struct timeval m_last_data;
OlaCallbackClientWrapper m_client;



/*------------------------------------------------------------------------------
 * IMPLEMENTATION                    
 */

static void ola2pd_open(t_ola2pd* x)
{
	if (x->x_clientpointer == NULL) 
	{     
	    // set up ola connection 
        if (!m_client.Setup()) {post("%s: %s",thisName(), strerror(errno));}
        x->x_clientpointer = m_client.GetClient();
        x->x_clientpointer->SetDmxCallback(ola::NewCallback(this, &ola2pd::NewDmx));
        x->xclientpointer->RegisterUniverse(m_universe,ola::REGISTER,ola::NewSingleCallback(this, &ola2pd::RegisterComplete));
        x->xclient.GetSelectServer()->RegisterRepeatingTimeout(5000,ola::NewCallback(this, &ola2pd::CheckDataLoss));
        post("ola2pd: Init complete. Start listening...");
	    x->x_client.GetSelectServer()->Run();
        }    
}

static void ola2pd_close(t_ola2pd* x) 
{
    if (x->x_clientpointer != NULL)
	{
   	x->x_clientpointer->RegisterUniverse(x->x_universe,ola::UNREGISTER,ola::NewSingleCallback(this, &ola2pd::RegisterComplete));        
    m_client.GetSelectServer()->Terminate();
	m_clientpointer = NULL;	
	post("ola2pd: Close complete");			
	}
}

static void ola2pd_universe(t_ola2pd* x, t_floatarg f)
{
	x->x_universe = f;
}

static void ola2pd_bang(t_ola2pd* x)
{
	if (x->x_clientpointer != NULL) {post("%s listening on universe %d",thisName(),x->x_universe);}
	else {post("%s configured on universe %d. Send open to start listening",thisName(),x->x_universe);}
}

/*
 * Called when there is new DMX data
 */

void NewDmx(unsigned int universe,const DmxBuffer &buffer,const string &error) {
  m_counter++;
  gettimeofday(&m_last_data, NULL);   
  int z;
  AtomList dmxlist;
  dmxlist(512);  
  for(z=0; z < 512; z++){SetFloat(dmxlist[z],(buffer.Get(z)));}
  ToOutList(0, dmxlist);
}

/*
 * Check for data loss.
 */

bool CheckDataLoss() {
  struct timeval now, diff;
  if (timerisset(&m_last_data)) {
    gettimeofday(&now, NULL);
    timersub(&now, &m_last_data, &diff);
    if (diff.tv_sec > 4 || (diff.tv_sec == 4 && diff.tv_usec > 4000000)) {
      // loss of data
      post("ola2pd: Can not read DMX!");
    }
  }
  return true;
}

/*
 * Control de errores en el registro de Universos en OLA
 */ 
void ola2pd::RegisterComplete(const string &error) {
  if (!error.empty()) {
    post("ola2pd:Register command failed");
    m_client.GetSelectServer()->Terminate();
  }
}

static void *ola2pd_new() 
{
	t_ola2pd *x = (t_ola2pd *)pd_new(ola2pd_class);
    x->x_out = outlet_new((t_object *)x, &s_symbol);
	x->x_pattern = gensym("empty");
	x->x_universe = 0;
	x->x_counter = 0;	
	return (x);
}

void ola2pd_setup(void) 
{
	ola2pd_class = class_new(gensym("ola2pd"),(t_newmethod)ola2pd_new,0,sizeof(t_ola2pd),0,A_DEFFLOAT, 0); 
	/* add inlet datatype methods */
	class_addbang(ola2pd_class,(t_method) ola2pd_output);
	class_addmethod(ola2pd_class,(t_method)ola2pd_file,gensym("open"),0);
	class_addmethod(ola2pd_class,(t_method)ola2pd_type,gensym("close"),0);
	class_addmethod(ola2pd_class,(t_method)ola2pd_type,gensym("universe"),A_FLOAT, 0);
}





