// -*- C++ -*-
/** 
    \file gnuplot.h
    simple C++ interface to gnuplot
    
    Taken from
    http://robot.informatik.uni-leipzig.de/~liebschr/projects/gnuplot/
*/

#ifndef GNUPLOT_H
#define GNUPLOT_H

#include <cstdio>
#include <map>
#include <utility>
#include <string>
#include <sstream>

#define GNUPLOT_ONLY_INCLUDES
// these includes define FILE* OpenGnuplot(void) and void CloseGnuplot(FILE*) 
#ifdef WINDOWS
#include "gnuplot_win32.h"
#else
#include "gnuplot_unix.h"
#endif 
#undef GNUPLOT_ONLY_INCLUDES

/**
   Open a Gnuplot window and always show last n values of arbitrary defined channels.
   T can be std::string or int or anything else supports the << of streams.
   (It is used as name for a channel.)
*/
template<class T> class Gnuplot {
/**
    \example example.cpp
*/

/// these includes define FILE* OpenGnuplot(void) and void CloseGnuplot(FILE*) 
#ifdef WINDOWS
#include "gnuplot_win32.h"
#else
#include "gnuplot_unix.h"
#endif 


private:
    /** data buffer for one channel, organized as ring buffer */
    class Dataset{
	double *buffer;
    public:
	std::string title;
	std::string style;
    private:
	int buffersize;
	int current;
    public:    
	/** contruct a data buffer with (gnuplot) title and style. */
	Dataset(const std::string& title="",const std::string& style="",int buffersize=256)
	    :title(title),style(style),buffersize(buffersize){
	    buffer=new double[buffersize];
	    for(int i=0;i<buffersize;++i)
		buffer[i]=0.0;
	    current=0;
	};
	~Dataset(){
	    delete []buffer;
	};
	/** put new data in (ring) buffer */
	void putData(double v){
	    buffer[current++]=v;
	    current=current%buffersize;
	};
	/** print buffer content to file pointer, usually a pipe */
	void plot(FILE* f){
	    for(int i=current;i<current+buffersize;++i)
		fprintf(f,"%f\n",buffer[i%buffersize]);
	};
	/** get buffer content */
	double getData(int i){
	    return buffer[(i+current)%buffersize];
	};

    };

    FILE* pipe;
    /** data type of container storing   channel->data  relation */
    typedef std::map<T, Dataset*> dataset_map;
    dataset_map datasets;
    int buffersize;
public:
    Gnuplot(int buffersize=256):buffersize(buffersize){
	pipe=OpenGnuplot();
    };
    ~Gnuplot(){
	for(typename dataset_map::iterator i=datasets.begin();i!=datasets.end();++i){
	    delete i->second;
	}
	if(pipe)CloseGnuplot(pipe);
    };
    /** send arbitrary command to gnuplot.
	like "set zeroaxis" or other stuff */
    void command(const std::string& cmd){
	fprintf(pipe,"%s\n",cmd.data());
	fflush(pipe);
    };
    /** add new channel with name. 
	currently uses name as title if not given.  */
    void addChannel(const T& name,const std::string& title="",const std::string& style="lines"){
	if(title != "")
	    datasets.insert(std::make_pair(name,new Dataset(title,style,buffersize)));
	else {
	    std::ostringstream str;
	    str << name;
	    datasets.insert(std::make_pair(name,new Dataset(str.str(),style,buffersize)));
	}
    };
    /** add new data value to buffer for channel.
        if channel not exists nothing happens.
	So you can select channels in your program by commenting out the addChannel() call */
    void putData(const T& channel, double data){
	typename dataset_map::iterator i = datasets.find(channel);
	if(i!=datasets.end())
	    i->second->putData(data);
    }; 
    /** make gnuplot plot content of all data buffers */
    void plot(){
	fprintf(pipe,"plot ");
	bool first=true;
	for(typename dataset_map::iterator i=datasets.begin();i!=datasets.end();++i){
	    if(first) first=false;
	    else fprintf(pipe,", ");
    	    Dataset* dataset=i->second;
	    fprintf(pipe,"'-'");    
	    fprintf(pipe," title '%s'",dataset->title.data());    
	    fprintf(pipe," with %s",dataset->style.data());    
	}
	fprintf(pipe,"\n");
	for(typename dataset_map::iterator i=datasets.begin();i!=datasets.end();++i){
	    i->second->plot(pipe);
	    fprintf(pipe,"e\n");
	}
	fflush(pipe);
    };    

    /** make gnuplot XY plot content of x against y data buffers 
	use it as follow:
	<pre>
	T x[]={a,b,c};
	T y[]={x,y,z};
	plotXY(x,y,3);
	</pre>
    */
    void plotXY(const T *x, const T *y,int size){
	fprintf(pipe,"plot ");
	bool first=true;
	for(int i=0;i < size; ++i){
	    typename dataset_map::iterator iX = datasets.find(x[i]);
	    typename dataset_map::iterator iY = datasets.find(y[i]);
	    // check if both channels exist
	    if(iX==datasets.end() || iY==datasets.end())
		continue;
	    if(first) first=false;
	    else fprintf(pipe,", ");
    	    Dataset* datasetX=iX->second;
    	    Dataset* datasetY=iY->second;
	    fprintf(pipe,"'-'");    
	    fprintf(pipe," title '%s'",std::string(datasetX->title + "<->" + datasetY->title).data());    
	    fprintf(pipe," with %s",datasetY->style.data());    
	}
	fprintf(pipe,"\n");
	for(int i=0;i < size; ++i){
	    typename dataset_map::iterator iX = datasets.find(x[i]);
	    typename dataset_map::iterator iY = datasets.find(y[i]);
	    // check if both channels exist
	    if(iX==datasets.end() || iY==datasets.end())
		continue;
	    for(int i2=0; i2<buffersize; ++i2)
		fprintf(pipe,"%f %f\n",iX->second->getData(i2),iY->second->getData(i2));
	    fprintf(pipe,"e\n");
	}
	fflush(pipe);
    };    

    /** make gnuplot XY plot content of x against y data buffers */
    void plotXY(const T& x, const T& y){
	plotXY(&x,&y,1);
    };    

    /** save content of all data buffers in files 
	( file names are: name + channel_name + ext )  */
    void printToFile(const std::string name="", const std::string ext=".log"){
	for(typename dataset_map::iterator i=datasets.begin();i!=datasets.end();++i){
	    std::string name2 = name + i->second->title + ext;
	    FILE* f=fopen(name2.data(),"w");    
	    i->second->plot(f);
	    fflush(f);
	    fclose(f);
	}
    };
};

#endif

