/*-*- c++ -*-******************************************************************
 * GraceTMPL Library 
 * Copyright (C) 2001,2002  Andy Thaller
 * 
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *****************************************************************************/

#include "gracetmpl.h"
#include <vector>
#include <map>
#include <iostream>

extern "C" {
#include <math.h>
#include <float.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
}

using namespace GraceTMPL;
using namespace std;


/* ********************************************************************
 *
 * Global methods in namespace GraceTMPL
 *
 * *******************************************************************/

string GraceTMPL::smashVars(const string &from)
{
  string to(from), param, cont;
  typedef string::size_type ST;
  ST p1=0,p2;

  while ((p1=to.find("$",p1))!=string::npos) {
    if (to[p1+1]=='{') { // this is ${PARAMNAME}
      if ((p2= to.find("}",p1+1))==string::npos) {
	fprintf(stderr,"no closing bracket in template <%s>\n",to.c_str());
	return to;
      }
      param= to.substr(p1+2,p2-p1-2);
      ST pc=param.find("::",0);
      if (pc!=string::npos) {
	cont= param.substr(0,pc);
	param= param.substr(pc+2,param.length());
      }
    } else {             // this is $PARAMNAME
      p2= p1+1; 
      while (isalnum(to[p2]) || to[p2]=='_') ++p2;
      --p2;
      param= to.substr(p1+1,p2-p1);
    }

    // smash the $NAME or ${CONTEXT::NAME} occurence:
    to.replace(p1,p2-p1+1,"");
  }
  return to;
}


string GraceTMPL::stringNum(double d, const char *fmt) 
{
  char tmp[1000];
  snprintf(tmp,1000,fmt,d);
  return string(tmp);
}

string GraceTMPL::stringNum(float d, const char *fmt) 
{
  char tmp[1000];
  snprintf(tmp,1000,fmt,d);
  return string(tmp);
}

string GraceTMPL::stringNum(long i, const char *fmt) 
{
  char tmp[1000];
  snprintf(tmp,1000,fmt,i);
  return string(tmp);
}

string GraceTMPL::stringNum(int i, const char *fmt) 
{
  char tmp[1000];
  snprintf(tmp,1000,fmt,i);
  return string(tmp);
}

/* ********************************************************************
 *
 * some supplementary methods not exported by GraceTMPL
 *
 * *******************************************************************/


void appendStringVec(StringVec *a, 
		     StringVec *b)
{
  if (!a||!b) return;
  if (a==b) return;
  StringVec::iterator daStr;
  for (daStr= b->begin(); daStr!= b->end(); ++daStr) {
    a->push_back(*daStr);
  }
}

string findString(StringVec *a, const char *first)
{
  if (!a||!first) return string("");
  StringVec::iterator daStr;
  for (daStr= a->begin(); daStr!= a->end(); ++daStr) {
    // fprintf(stderr,"checking <%s> against <%s>\n",daStr->c_str(),first);
    if (!strncmp(daStr->c_str(),first,strlen(first))) 
      return *daStr;
  }
  return string("");
}


int replaceString(StringVec *a, const char *first, const string &next)
{
  if (!a||!first) return 0;
  StringVec::iterator daStr;
  for (daStr= a->begin(); daStr!= a->end(); ++daStr) {
    // fprintf(stderr,"checking <%s> against <%s>\n",daStr->c_str(),first);
    if (!strncmp(daStr->c_str(),first,strlen(first))) {
      *daStr= next;
      return 1;
    }
  }
  return 0;
}

int getScalar(StringVec *a, const char *first, double &x)
{
  if (!a || !first) return 0;
  StringVec::iterator daStr;
  for (daStr= a->begin(); daStr!= a->end(); ++daStr) {
    if (!strncmp(daStr->c_str(),first,strlen(first)))
      if (sscanf(daStr->c_str()+strlen(first),"%lg",&x)==1)
	return 1;
  }
  return 0;
}

int getLoc(StringVec *a, const char *first, double &x, double &y)
{
  if (!a || !first) return 0;
  StringVec::iterator daStr;
  for (daStr= a->begin(); daStr!= a->end(); ++daStr) {
    if (!strncmp(daStr->c_str(),first,strlen(first)))
      if (sscanf(daStr->c_str()+strlen(first),"%lg, %lg",&x,&y)==2)
	return 1;
  }
  return 0;
}

int replaceLoc(StringVec *a, const char *first, double x, double y)
{
  double tx, ty;
  if (!a || !first) return 0;
  StringVec::iterator daStr;
  for (daStr= a->begin(); daStr!= a->end(); ++daStr) {
    if (!strncmp(daStr->c_str(),first,strlen(first)))
      if (sscanf(daStr->c_str()+strlen(first),"%lg, %lg",&tx,&ty)==2) {
	// fprintf(stderr,"replaciong loc <%s> with",daStr->c_str());
	*daStr= string(first)+stringNum(x)+string(", ")+stringNum(y);
	// fprintf(stderr," <%s>\n",daStr->c_str());
	return 1;
      }
  }
  return 0;
}

string Environment::expand(const string &from, int nests) 
{
  string to(from), param, cont, fallback, replacement;
  typedef string::size_type ST;
  ST p1=0,p2;

  if ((p1=to.find("$",p1))==string::npos) return from;

  while ((p1=to.find("$",p1))!=string::npos) {
    if (to[p1+1]=='{') { // this is ${PARAMNAME}
      if ((p2= to.find("}",p1+1))==string::npos) {
	fprintf(stderr,"no closing bracket in template <%s>\n",to.c_str());
	return to;
      }
      fallback=string("${")+(param= to.substr(p1+2,p2-p1-2))+"}";
      if (param.find("$",0)!=string::npos) { // nested params...
	p1++; continue;
      }
      // search from right to left to find whole context!
      ST pc=param.rfind("::",param.length()-1); 
      if (pc!=string::npos) {
	cont= param.substr(0,pc);
	if (cont=="") cont= "std";
	param= param.substr(pc+2,param.length());
      }
      pc=param.find(":",0);
      if (pc!=string::npos) {
	fallback= param.substr(pc+1,param.length());
	param= param.substr(0,pc);
      } 
    } else {             // this is $PARAMNAME
      p2= p1+1; 
      while (isalnum(to[p2]) || to[p2]=='_') ++p2;
      --p2;
      param= to.substr(p1+1,p2-p1);
      fallback= string("$")+param;
    }

    replacement= substitute(cont,param,fallback);
    to.replace(p1,p2-p1+1,replacement);
    p1+= replacement.size();

  }
  // cerr << "status("<<name_<<"): "<<from<<" -> "<<to<<"\n";

  if (to == from || nests <=0) return to;
  return expand(to,--nests);
}

string Environment::substitute(const string &context,
			       const string &variable,
			       const string &fallback)
{
  /*
  int mydebug=0;
  if (mydebug) 
  cerr <<"** \""<<name_<<"\" entering"
       << " with (\""<<context<<"\",\""<<variable<<"\",\""<<fallback<<"\")\n";
  */
  if ((context=="" || context==name_) ||
      (!parent_ && context=="std")) {
    //if (mydebug) cerr <<"** \""<<name_<< "\" my task!\n";
    StringMap::iterator daString= variable_.find(variable);
    if (daString!=variable_.end()) {
      //if (mydebug) cerr << "** returning my variable\n";
      return daString->second;
    }
  }

  if (context!="") {
    string tcontext(context), ncontext;
    string::size_type tc=tcontext.find("::",0);
    if (tc!=string::npos) {
      ncontext= tcontext.substr(tc+2,tcontext.length());
      tcontext= tcontext.substr(0,tc);
    }
    /*
    if (mydebug) 
      cerr << "** \""<<name_<< "\" contexts = "<<tcontext<<","<<ncontext<<"\n";
    */
    std::map<std::string,Environment *>::iterator daEnv= context_.find(tcontext);
    if (daEnv!=context_.end() && daEnv->second) {
      return daEnv->second->substitute(ncontext,variable,fallback);
    }
    /*else {
       if (mydebug) cerr << "** \""<<name_<< "\" found no "<<tcontext<<"\n";
       }*/
  }

  if (parent_) { 
    //if (mydebug) cerr << "** \""<<name_<< "\" returning what parent says\n";
    return parent_->substitute(context,variable,fallback);
  }

  //if (mydebug) cerr << "** \""<<name_<< "\" returning fallback\n";
  return fallback;
}


#define DUMMY_NAME "<*=-dummy-=*>"


Data::Data() : 
  EnvironmentUser(),
  name_(DUMMY_NAME),
  n_(0),
  setnum_(0),
  x_(0),
  y_(0),
  dx_(0),
  dy_(0),
  xoffs_(0.0),
  yoffs_(0.0),
  scale_(1.0)
{
}

Data::Data(const string &name, int n,
	   const double *x, const double *y,
	   const double *dx, const double *dy) :
  EnvironmentUser(),
  name_(name),
  n_(n),
  setnum_(0),
  x_(0),
  y_(0),
  dx_(0),
  dy_(0),
  xoffs_(0.0),
  yoffs_(0.0),
  scale_(1.0)
{
  if (n) {
    if (x) {
      x_= new double[n];
      memcpy(x_,x,sizeof(double)*n);
    }
    if (y) {
      y_= new double[n];
      memcpy(y_,y,sizeof(double)*n);
    }
    if (dx) {
      dx_= new double[n];
      memcpy(dx_,dx,sizeof(double)*n);
    }
    if (dy) {
      dy_= new double[n];
      memcpy(dy_,dy,sizeof(double)*n);
    }
  }
}

#if 0
void Data::autoscale(double &xmin, double &xmax, 
		     double &ymin, double &ymax, 
		     double errorfac) 
{
  if (x_) for (int i= 0; i<n_; i++) {
    double xerr = (dx_) ? dx_[i] * errorfac : 0.0;
    if (xmin>(x_[i]-xoffs_-fabs(xerr))) xmin= (x_[i]-xoffs_-fabs(xerr));
    if (xmax<(x_[i]-xoffs_+fabs(xerr))) xmax= (x_[i]-xoffs_+fabs(xerr));
  }
  if (y_) for (int i= 0; i<n_; i++) {
    double yerr = (dy_) ? dy_[i] * errorfac : 0.0;
    if (ymin>(y_[i]-yoffs_-fabs(yerr))*scale_) 
      ymin=  (y_[i]-yoffs_-fabs(yerr))*scale_;
    if (ymax<(y_[i]-yoffs_+fabs(yerr))*scale_) 
      ymax=  (y_[i]-yoffs_+fabs(yerr))*scale_;
  }
}
#endif

void Data::autoscale(double &xmin, double &xmax, 
		     double &ymin, double &ymax,
		     double XMIN,  double XMAX, 
		     double YMIN,  double YMAX,
		     double errorfac) 
{
  if (x_ && y_) for (int i= 0; i<n_; i++) {
    double xerr = (dx_) ? fabs(dx_[i] * errorfac) : 0.0;
    double yerr = (dy_) ? fabs(dy_[i] * errorfac) : 0.0;
    double xl=x_[i]-xoffs_-xerr;
    double xh=x_[i]-xoffs_+xerr;
    double yl=(y_[i]-yoffs_-4*yerr)*scale_;
    double yh=(y_[i]-yoffs_+4*yerr)*scale_;
    if (xh<XMIN || xl>XMAX || yh<YMIN || yl>YMAX) { 
      continue;
    } 
    if (xmin>xl) xmin= xl;
    if (xmax<xh) xmax= xh;
    if (ymin>yl) ymin= yl;
    if (ymax<yh) ymax= yh;
  } else {
    cerr << "GraceTMPL::Data::autoscale() internal error - no data!\n";
  }
}


void Data::saveinfo(FILE *f, const StringVec *daSet0) 
{
  if (!daSet0) return;

  // under any circumstances avoid altering daSet0
  StringVec mySet(*daSet0);
  StringVec *daSet= &mySet;

  string legend= findString(daSet,"legend \"");
  if (!legend.length()) legend= findString(daSet,"legend  \"");

  if (legend.find("$'\"",0)!=string::npos) { // literal legend string
    //new copydata fprintf(stderr,"oh yes(%d), literal legend! %s\n",setnum_,legend.c_str());
    legend.replace(legend.find("$'"),2,string(""));
    legend= expand(legend);
    replaceString(daSet,"legend \"",legend);
    replaceString(daSet,"legend  \"",legend);

  } else if (legend.find("$!\"",0)!=string::npos) { // literal data string
    legend.replace(legend.find("$!"),2,string(""));
    legend= expand(legend);
    replaceString(daSet,"legend \"",legend);
    replaceString(daSet,"legend  \"",legend);

  } else { // no literal legend string
    legend= expand(name_);
    if (!replaceString(daSet,"legend \"",
		       string("legend \"")+legend+string("\"")) && 
	!replaceString(daSet,"legend  \"",
		       string("legend \"")+legend+string("\"")))
      daSet->push_back(string("legend \"")+legend+string("\""));
  }

  StringVec::iterator daStr;
  for (daStr= daSet->begin(); daStr!= daSet->end(); ++daStr) {
    fprintf(f,"@    s%d %s\n",setnum_,daStr->c_str()); 
  }
}

void Data::savedata(FILE *f, int correctLog) 
{
  if (!x_ || !y_) return;

  fprintf(f,"@type xy%s%s\n",(dx_)?"dx":"",(dy_)?"dy":"");
  
  if (!correctLog) {
    for (int hi=0; hi<n_; ++hi) {
      fprintf(f,"%16.8g %16.8g",x_[hi]-xoffs_,(y_[hi]-yoffs_)*scale_);
      if (dx_) fprintf(f," %16.8g",dx_[hi]);
      if (dy_) fprintf(f," %16.8g",dy_[hi]*scale_);
      fprintf(f,"\n");
    }
  } else {
    for (int hi=0; hi<n_; ++hi) {
      if (dy_) {
	if ((y_[hi]-yoffs_-dy_[hi])*scale_ > 0) {
	  fprintf(f,"%e\t%e",x_[hi]-xoffs_, (y_[hi]-yoffs_)*scale_);
	  if (dx_) fprintf(f,"\t%e",dx_[hi]);
	  fprintf(f,"\t%e",dy_[hi]*scale_);
	  fprintf(f,"\n");
	}
      } else {
	if ((y_[hi]-yoffs_)*scale_ > 0) {
	  fprintf(f,"%e\t%e",x_[hi]-xoffs_,(y_[hi]-yoffs_)*scale_);
	  if (dx_) fprintf(f,"\t%e",dx_[hi]);
	  fprintf(f,"\n");
	}
      }
    }
  }
  fprintf(f,"&\n");
}

/************************************************************************/
/************************************************************************/
/************************************************************************/
/************************************************************************/
/************************************************************************/

Graph::Graph(Save *saver, int logplot) : 
  EnvironmentUser(),
  graphnum_(0),
  saver_(saver),
  xoffs_(0.0),
  yoffs_(0.0),
  scale_(1.0),
  correctLog_(logplot)
{
}

void Graph::addParam(const string &name,double value) 
{ 
  params_.push_back(name+string(" = ")+stringNum(value)); 
}

int isNoscale(StringVec *a,const string &which)
{
  string label= findString(a, string(which+string(" label")).c_str());
  if (!label.length()) 
    label= findString(a, string(which+string("  label")).c_str());
  typedef string::size_type ST;
  ST p1,p2;
  p1=label.find("\"",0); if (p1==string::npos) return 0;
  p2=label.find("\"",p1+1); if (p2==string::npos) return 0;
  if ( /*p2-p1>5 && */ // we allow "  " to be valid, too
      label[p1+1]==' ' && label[p1+2]==' ' /*&& label[p1+3]!=' '*/ &&
      label[p2-1]==' ' && label[p2-2]==' ' /*&& label[p2-3]!=' '*/)
    return 1;
  return 0;
}

void Graph::saveprep(const StringVecMap *daSets0)
{
  StringVecMap mySets(*daSets0);
  StringVecMap *daSets= &mySets;

  for (int i=0; i<int(daSets->size()); i++) { // better than "<1000" !!
    string legend= findString(&((*daSets)[i]),"legend \"");
    if (!legend.length()) legend= findString(&((*daSets)[i]),"legend  \"");
    /*  new copydata
    fprintf(stderr,"blabla %d %d (%d) (%s) %d\n",graphnum_,i,dataVec_.size(),
	    legend.c_str(),saver_->isCopydata(graphnum_,i));
    */
    if (legend.find("$!\"",0)!=string::npos) {
      /* if this method is called more than once, avoid inserting dummies
       * all over again 
       */
      if (i<int(dataVec_.size())) {
	if (dataVec_[i]->name()!=string(DUMMY_NAME)) {
	  vector<Data *>::iterator insPos(&(dataVec_[i]));
	  dataVec_.insert(insPos,new Data());
	}
	//new copydata fprintf(stderr,"            inserting literal data\n");
      } else {
	dataVec_.push_back(new Data());
	//new copydata fprintf(stderr,"            appending literal data\n");
      }
      // set this graph's environment as parent for a dummy env in dummy set
      dataVec_[i]->setenv(new Environment(env())); 
      literalData_[i]= 1;
    } else if (saver_->isCopydata(graphnum_,i)) {
      /* if this method is called more than once, avoid inserting dummies
       * all over again 
       */
      if (i<int(dataVec_.size())) {
	if (dataVec_[i]->name()!=string(DUMMY_NAME)) {
      vector<Data *>::iterator insPos(&(dataVec_[i]));
	  dataVec_.insert(insPos,new Data());
    }
	//new copydata fprintf(stderr,"            inserting copy data\n");
      } else {
	dataVec_.push_back(new Data());
	//new copydata fprintf(stderr,"            appending copy data\n");
      }
      // set this graph's environment as parent for a dummy env in dummy set
      dataVec_[i]->setenv(new Environment(env())); 
      literalData_[i]= 2;
    }
    if (i<int(dataVec_.size())) 
      saver_->regCopydata(graphnum_,i,dataVec_[i]);
  }
  
  std::vector<Data *>::iterator daData;
  int snum= 0;
  for (daData=dataVec_.begin(); daData!=dataVec_.end(); ++daData, ++snum) {
    Data *d= *daData;
    d->setNum(snum); // is set in dummy sets for literal or copy data!!
    d->env()->setName(string("s")+stringNum(snum));
  }

}

void Graph::saveinfo(FILE *f,
		     const StringVec *daGraph0, 
		     const StringVecMap *daSets0,
		     const StringVecMap *daStrings) 
{
  StringVec myGraph(*daGraph0);
  StringVec *daGraph= &myGraph;

  StringVecMap mySets(*daSets0);
  StringVecMap *daSets= &mySets;

  std::vector<Data *>::iterator daData;
  double xmin= DBL_MAX, xmax=-DBL_MAX, ymin= DBL_MAX, ymax=-DBL_MAX;
  double XMIN=-DBL_MAX, XMAX= DBL_MAX, YMIN=-DBL_MAX, YMAX= DBL_MAX;
  if (isNoscale(daGraph,"xaxis")) {
    double lower,upper;
    if (getScalar(daGraph,"world xmin ",lower) &&
	getScalar(daGraph,"world xmax ",upper)) {
      //cerr << "no autoscale on xaxis ("<<lower<<":"<<upper<<")\n";
      XMIN=lower;
      XMAX=upper;
    }
  }
  if (isNoscale(daGraph,"yaxis")) {
    double lower,upper;
    if (getScalar(daGraph,"world ymin ",lower) &&
	getScalar(daGraph,"world ymax ",upper)) {
      //cerr << "no autoscale on yaxis ("<<lower<<":"<<upper<<")\n";
      YMIN=lower;
      YMAX=upper;
    }
  }
  int snum= 0;
  for (daData=dataVec_.begin(); daData!=dataVec_.end(); ++daData, ++snum) {
    /* avoid autoscaling hidden data sets: */
    StringVec *daSet= &((*daSets)[snum]);
    string state= findString(daSet,"hidden ");
    if (state=="hidden true") continue;

    if (saver_->isCopydata(graphnum_,snum) && 
	saver_->copydata(graphnum_,snum) &&
	saver_->copydata(graphnum_,snum)->name()!=DUMMY_NAME) {
      // the name check avoids copying from dummy sets!
      /* new copydata 
	 fprintf(stderr,"wow, %d/%d must be copied from %x\n",graphnum_,snum,
	      saver_->copydata(graphnum_,snum));
      */
      dataVec_[snum]= saver_->copydata(graphnum_,snum);
      dataVec_[snum]->autoscale(xmin,xmax,ymin,ymax,XMIN,XMAX,YMIN,YMAX,0.3);
    } else if (literalData_[snum]) {
      //new copydata fprintf(stderr,"lit, %d/%d...\n",graphnum_,snum);
    } else  {
      //new copydata fprintf(stderr,"hmm, %d/%d...\n",graphnum_,snum);
      Data *d= *daData;
      d->setXOffset(xoffset());
      d->setYOffset(yoffset());
      d->setScaling(scale());
      d->autoscale(xmin,xmax,ymin,ymax,XMIN,XMAX,YMIN,YMAX,0.3);
      //cerr << "scaled "<<snum<<" to "<<xmin<<":"<<xmax<<" "<<ymin<<":"<<ymax<<"\n";
    }
  }
  if (daGraph) {
    if (xmin==xmax) { xmin= xmin-1.0; xmax= xmax+1.0; }
    if (ymin==ymax) { ymin= ymin-1.0; ymax= ymax+1.0; }
    double dx= (xmax-xmin)/5; if (dx<0.0) dx= 1.0;
    double dy= (ymax-ymin)/5; if (dy<0.0) dy= 1.0;
    int xmag= int(floor(log10(dx)));
    int ymag= int(floor(log10(dy)));
    dx= ceil(dx/pow(10.0,xmag))*pow(10.0,xmag);
    dy= ceil(dy/pow(10.0,ymag))*pow(10.0,ymag);

    if (!isNoscale(daGraph,"xaxis") && xmin!=DBL_MAX){
      replaceString(daGraph,"world xmin ",
		    string ("world xmin ")+stringNum(xmin));
      replaceString(daGraph,"world xmax ",
		    string ("world xmax ")+stringNum(xmax));
      replaceString(daGraph,"xaxis tick major ",
		    string ("xaxis tick major ")+stringNum(dx));
      replaceString(daGraph,"xaxis  tick major ",
		    string ("xaxis  tick major ")+stringNum(dx));
    }
    if (!isNoscale(daGraph,"yaxis") && ymin!=DBL_MAX){
      replaceString(daGraph,"world ymin ",
		    string ("world ymin ")+stringNum(ymin));
      replaceString(daGraph,"world ymax ",
		    string ("world ymax ")+stringNum(ymax));
      replaceString(daGraph,"yaxis tick major ",
		    string ("yaxis tick major ")+stringNum(dy));
      replaceString(daGraph,"yaxis  tick major ",
		    string ("yaxis  tick major ")+stringNum(dy));
    }

    // Work on automatically expanding string fields:
    StringVec::iterator daStr;
    if (daStrings && daStrings->size()) {
      StringVecMap myStrings= *daStrings;
      int str= 0;
      double x0= 0, y0= 0, x= 0, y= 0, dx= 0.0, dy= 0.0;

      if ((myStrings)[0].size()) {
	if (!getLoc(&(myStrings)[0],"string ",x0,y0)) {
	  fprintf(stderr,"didn't find location of string 0\n");
	} else {	  
	  for (daStr= params_.begin(); daStr!= params_.end(); 
	       ++daStr, ++str) {
	    StringVec strTmpl= (myStrings)[str];

	    if (strTmpl.begin()==strTmpl.end() || 
		!getLoc(&strTmpl,"string ",x,y)) {

	      (myStrings)[str]= (myStrings)[str-1];
	      strTmpl= (myStrings)[str];
	      x= x0+dx; 
	      y= y0+dy;
	      replaceLoc(&strTmpl,"string ",x,y);

	    } else {
	      getLoc(&strTmpl,"string ",x,y);
	    }
	    
	    replaceString(&strTmpl,"string def \"",
			  string  ("string def \"")+(*daStr)+string("\""));

	    fprintf(f,"@with string\n");
	    StringVec::iterator da2Str;
	    for (da2Str= strTmpl.begin(); da2Str!= strTmpl.end(); ++da2Str) {
	      fprintf(f,"@    %s\n",da2Str->c_str());
	    }
	    dx= x-x0;
	    dy= y-y0;
	    x0= x;
	    y0= y;
	  }
	}
      }
    } // if (daStrings && daStrings->size())

    // if we have NO definitions in daGraph, don't even use '@with ...'
    if (daGraph->begin()!=daGraph->end()) {
      fprintf(f,"@with g%d\n",graphnum_); 
      for (daStr= daGraph->begin(); daStr!= daGraph->end(); ++daStr) {
	fprintf(f,"@    %s\n",expand(*daStr).c_str());
      }
    }
  }
  snum= 0;
  for (daData=dataVec_.begin(); daData!=dataVec_.end(); ++daData, ++snum) {
    Data *d= *daData;
    // The next line is a dirty hack to make copy data work
    // It does no harm however since the original setnum will not be needed
    // afterwards or can be set again.
    d->setNum(snum);
    if (daSets)
      d->saveinfo(f,&((*daSets)[d->num()]));
    else 
      d->saveinfo(f,0);
  }
}

void Graph::savedata(FILE *f, StringMap *literalData, int dataonly) 
{
  std::vector<Data *>::iterator daData;
  for (daData=dataVec_.begin(); daData!=dataVec_.end(); ++daData) {
    Data *d= *daData;
    if (d->name()==string(DUMMY_NAME)) {
      if (!dataonly)
	fprintf(f,"@target G%d.S%d\n",graphnum_,d->num());
      string name(string("G")+stringNum(graphnum_)+
		  string(".S")+stringNum(d->num()));
      // fprintf(stderr,"%s is literal data\n",name.c_str());
      fprintf(f,"%s",(*literalData)[name].c_str());
    } else {
      if (!dataonly)
	fprintf(f,"@target G%d.S%d\n",graphnum_,d->num());
      d->savedata(f,correctLog());
    }
  }
}

/************************************************************************/
/************************************************************************/
/************************************************************************/
/************************************************************************/
/************************************************************************/

Save::Save() 
  : EnvironmentUser(),
    nameTmpl_("output-$pz.xmgrace"),
    allowPipe_(1)
{
  tmpl_.gpp_= 1;
  tmpl_.valid_= 0;
  tmpl_.ignores_= 0;
}
 
int Save::loadTemplate(const char *filename, int useG0)
{
  FILE *f= fopen(filename,"r");
  if (!f) return 0;

  Template tmpl;
  tmpl.filename_= string(filename);
  StringVec tmpString;

  int context=0;
  enum Context { GLOBAL,GRAPH,STRING };
  int graph;
  tmpl.gpp_= 0;
  tmpl.useG0_= useG0;
  std::map<int,std::map<int,std::string > > setRequests;

  char line[10000], *ptr;
  int maxgraph= 0;
  
  while (!feof(f) && fgets(line,10000,f)) {
    ptr= line;
    if (*ptr=='#') {
      tmpl.header_.append(string(line));
      continue;
    }
    // now strip linebreak;
    if (*ptr) ptr[strlen(ptr)-1]=0;
    if (*ptr!='@') continue;
    ptr++;
    if ((*ptr=='g'|| *ptr=='G') && (isspace(ptr[2]) || isspace(ptr[3]))) {
#ifndef AVOID_XMGRACE_MAGIC_HACK
      // this 'magic hack' makes unused graphs disappear but may break xmgrace
      context= GRAPH;
      graph= atoi(ptr+1);
      if (tmpl.gpp_<graph+1) tmpl.gpp_=graph+1;
#endif
    } else if (*ptr!=' ') { // endof context, if any
      if (context==STRING) { // is it a param template?
	string a;
	if ((a= findString(&tmpString,"string def \"PARAMg"))!=string("")) {
	  string b(a,18);
	  int g,s;
	  sscanf(b.c_str(),"%d:%d",&g,&s);
	  fprintf(stderr,"found string template for graph %d, #%d!!!\n",g,s);
	  tmpl.params_[g][s]= tmpString;
	} else {
	  tmpl.strings_.push_back(tmpString);
	}
	tmpString.clear();
      } 
      context= GLOBAL;
    }
    if (!strncmp(ptr,"target ",7)) {
      string name(ptr+7);
      if (name.find("g",0)!=string::npos) 
	name.replace(name.find("g",0),1,"G");
      if (name.find("s",0)!=string::npos) 
	name.replace(name.find("s",0),1,"S");
      string data;
      while(*line!='&' && !feof(f) && fgets(line,10000,f)) {
	data+= string(line);
      }
      tmpl.data_[name]= data;
      continue;
    }
    if (!strncmp(ptr,"type ",5)) continue;
    if (!strncmp(ptr,"with ",5)) {
      if (!strncmp(ptr+5,"string",6)) context=STRING;
      if (ptr[5]=='g' || ptr[5]=='G') {
	context= GRAPH;
	graph= atoi(ptr+6); 
	if (graph>maxgraph) maxgraph= graph;
	if (tmpl.gpp_<maxgraph+1) tmpl.gpp_=maxgraph+1;
      } else {
	graph=-1;
      }   
      // fprintf(stderr,"switching to context %d, graph=%d\n",context,graph);
      continue;
    }
    while (isspace(*ptr)) ++ptr;
    // fprintf(stderr,"%d::: %s\n",context,ptr);

    switch (context) {
    case GLOBAL: tmpl.common_.push_back(string(ptr)); break;
    case STRING: tmpString.push_back(string(ptr)); break;
    case GRAPH: 
      if ((*ptr=='s' || *ptr=='S') && ptr[1]>='0' && ptr[1]<='9') {
	// we have a set definition here:
	ptr++; int set= atoi(ptr); 
	while (*ptr>='0'&&*ptr<='9') ++ptr; while (isspace(*ptr)) ++ptr;
	string setInfo= ptr;

	/* test for data requests in legend strings: */
	if (setInfo.find("legend ",0)!=string::npos) {
	  string::size_type dPos;
	  string info;
	  if ((dPos= setInfo.find("$'",7))!=string::npos ||
	      (dPos= setInfo.find("$=",7))!=string::npos) {
	    dPos+=2;
	    info=setInfo.substr(dPos,setInfo.length());
	    // strip the request to not create great confusion
	    setInfo.replace(dPos,setInfo.length(),"");
	    if (setInfo.substr(setInfo.length()-2,2)=="$=")
	      setInfo.replace(setInfo.length()-2,2,"");
	    setInfo+= "\"";
	    // strip whitespace at end of info string
	    if (info.length()) {
	      int s= info.length()-1;
	      while (s && isspace(info[s])) s--;
	      info.replace(s,info.length(),"");
	    }
	  } else if (setInfo.find("$!",0)!=string::npos) {
	    info= "!!!literal_data!!!";
	  }
	  setRequests[graph][set]= info;
	  // fprintf(stderr,"info: <%s><%s>\n",setInfo.c_str(),info.c_str());
	}

	tmpl.sets_[graph][set].push_back(setInfo);
      } else {
	// settings for the graph 
	tmpl.graphs_[graph].push_back(ptr);
      }
      break; 
    default:
      if (0) fprintf(stderr,"unknown context!!\n");
    }
  }

  // process all set requests
  tmpl.ignores_= 0;
  for (int g= 0; g<=maxgraph; g++) {
    StringVec v;
    std::map<int,std::string>::iterator daReq;
    int s;

    string subtitle= findString(&(tmpl.graphs_[g]),"subtitle \"");
    size_t p1=subtitle.find("\"",0)+1;
    size_t p2=subtitle.rfind("\"",subtitle.length())-1;

    if (subtitle[p1]==' ' && subtitle[p1+1]==' ' && 
	subtitle[p2]==' ' && subtitle[p2-1]==' ' ) {
      tmpl.ignore_[g]= 1;
      tmpl.ignores_++;
      tmpl.gpp_--;
    }

    if (setRequests[g].size()) {
      for (s=0, daReq=setRequests[g].begin();
	   daReq != setRequests[g].end();
	   ++daReq, ++s) {
	if (daReq->second == "!!!literal_data!!!") {
	  // to be ignored..., don't add to request list
	} else if (daReq->second[0] == '<') {
	  // ah well, we should copy data from another set!
	  int  copyG, copyS;
	  char charG, charS;
	  if (sscanf(daReq->second.c_str(),"<%c%d.%c%d",
		     &charG,&copyG,&charS,&copyS)==4 &&
	      (charG=='g' || charG=='G') &&
	      (charS=='s' || charS=='S')) {
	    /* new copydata
	    fprintf(stderr,"in set G%d.S%d  : <%s> -> %c %d . %c %d\n",
		    g,s,daReq->second.c_str(),charG,copyG,charS,copyS);
	    */
	    tmpl.copy_[g][s].g= copyG;
	    tmpl.copy_[g][s].s= copyS;
	  } else {
	    fprintf(stderr,"Error in <copyset>, G%d.S%d: invalid \"%s\"!!\n",
		    g,s,daReq->second.c_str());
	  }
	} else {
	  v.push_back(daReq->second);
	  // fprintf(stderr,"  : <%s>\n",daReq->second.c_str());
	}
      }
    }
    // only include non-ignored graphs in the request list!
    if (!tmpl.ignore_[g])
      tmpl.request_.push_back(v);
  }

  tmpl_= tmpl;
  fclose(f);
  return 1;
}

int Save::isCopydata(int g, int s) 
{
  /* new copydata
  fprintf(stderr,"copy g%d.s%d from g%d.s%d ?\n",g,s,
	  tmpl_.copy_[g][s].g,tmpl_.copy_[g][s].s);
  */
  return ((tmpl_.copy_[g][s].g>=0)  &&  (tmpl_.copy_[g][s].s>=0));
}

void Save::regCopydata(int g, int s, Data* d) 
{
  Copy2Map::iterator gi;
  CopyMap::iterator si;
  for (gi=tmpl_.copy_.begin(); gi!=tmpl_.copy_.end(); ++gi)
    for (si=gi->second.begin(); si!=gi->second.end(); ++si) 
      if (si->second.g==g && si->second.s==s)
	si->second.src= d;
}

void Save::clearCopydata() 
{
  Copy2Map::iterator gi;
  CopyMap::iterator si;
  for (gi=tmpl_.copy_.begin(); gi!=tmpl_.copy_.end(); ++gi)
    for (si=gi->second.begin(); si!=gi->second.end(); ++si)
      si->second.src= 0;
}


String2Vec Save::templateDataRequestInfo()
{
  return tmpl_.request_;
}

/// docs can be found in manual page "strftime(3)"
string time2string (const string &format, const struct tm *tm=0)
{
  char buffer[1000];
  int size;
  struct timeval tv;
  gettimeofday(&tv,0);

  size= (tm==0) ? 
    strftime(buffer,999,format.c_str(),localtime((const time_t *) &tv.tv_sec)):
    strftime(buffer,999,format.c_str(),tm);
  buffer[999]= 0;
  return string(buffer);
}

void Save::save()
{
  // fprintf(stderr,"saving templated xmgr files...\n");
  int graphs= graphsVec_.size();
  int gps= tmpl_.gpp_;
  int sheets= (graphs-1)/gps+1; // honours only application graphs
  gps+= tmpl_.ignores_; // add ignored graphs to graphs per page

  FILE *f= 0;
  
  setenv("P",stringNum(sheets));
  setenv("date",time2string("%d.%m.%Y"));

  int isPipe = (nameTmpl_[0]=='|');
  
  //cerr << nameTmpl_ << ": isPipe="<<isPipe<<"\n";

  if (isPipe && !allowPipe_) {
    isPipe= 0;
    cerr <<"\n"
	 <<"GraceTMPL::Save::save(): Pipe output not enabled - reverting to\n"
	 <<"                         \"output-$pz.xmgrace\" for data saving\n"
	 <<"\n";
    nameTmpl_= "output-$pz.xmgrace";
  }

  /*
  fprintf(stderr,"now (%d graphs on %d sheets with %d per sheet)...\n",
	  graphs,sheets,gps);
  */

  // fprintf(stderr,"inserting missing ignored graphs...\n");

  int graph= 0, sheet= 0;
  for (sheet= 0; sheet<sheets; sheet++) {
    for (int i=0; i<gps; i++) {
      Graph *g;
      //new-gps fprintf(stderr,"testing graph %d on sheet %d\n",i,sheet);
      if (!tmpl_.ignore_[i]) {
        //new-gps fprintf(stderr,"g is graph %d/%d\n",graph,graphsVec_.size());
        g= graphsVec_[graph];
      } else {
        g= new Graph(this);
        //new-gps fprintf(stderr,"new @graph %d/%d\n",graph,graphsVec_.size());
        if (graph<int(graphsVec_.size())) {
          vector<Graph*>::iterator insPos(&(graphsVec_[graph]));
          graphsVec_.insert(insPos,g);
        } else {
          graphsVec_.push_back(g);
        }
      }
      graph++;
    }
  }
  graphs= graphsVec_.size();
  /*
  fprintf(stderr,"now (%d graphs on %d sheets with %d per sheet)...\n",
	  graphs,sheets,gps);
  */
  for (sheet= 0; sheet<sheets; sheet++) {
    clearCopydata();
    //env_->clear();
    for (graph= 0; graph<gps && graph+sheet*gps<graphs; ++graph) {
      Graph *g= graphsVec_[graph+sheet*gps];
      g->setGraph(graph);
      env_->add(string("g")+stringNum(graph),g->env());
      if(tmpl_.useG0_)
	g->saveprep(&(tmpl_.sets_[0]));
      else
	g->saveprep(&(tmpl_.sets_[graph]));
    }

    char fmt[15]; 
    snprintf(fmt,15,"%%0%dd",int(ceil(log10(1.0*sheets))));

    setenv("pz",stringNum(sheet+1,fmt));
    setenv("p",stringNum(sheet+1));

    string filename(expand(nameTmpl_));
    setenv("filename",filename);

    if (isPipe) {
      filename= filename.substr(1,filename.length());
      f= popen(filename.c_str(),"w");
    } else {
      f= fopen(filename.c_str(),"w");
    }
    if (!f) return;

    // fprintf(stderr,"opening sheet \"%s\"\n",filename.c_str());
    fprintf(f,"%s",tmpl_.header_.c_str());      
    StringVec::iterator daStr;
    for (daStr= tmpl_.common_.begin(); daStr!= tmpl_.common_.end(); ++daStr) {
      if (!strncmp(daStr->c_str(),"line on",7))
	fprintf(f,"@with line\n@    %s\n",daStr->c_str());
      else if (!strncmp(daStr->c_str(),"line def",8))
	fprintf(f,"@%s\n",daStr->c_str()); 
      else if (!strncmp(daStr->c_str(),"line ",5))
	fprintf(f,"@    %s\n",daStr->c_str());
      else 
	fprintf(f,"@%s\n",daStr->c_str()); 
    }

    String2Vec::iterator daString;
    for (daString= tmpl_.strings_.begin(); daString!= tmpl_.strings_.end(); 
	 ++daString) {
      fprintf(f,"@with string\n"); 
      StringVec::iterator daStr;
      for (daStr= daString->begin(); daStr!= daString->end(); ++daStr) {
	fprintf(f,"@    %s\n",expand(*daStr).c_str());
      }
    }
    
    for (int graph= 0; graph<gps && graph+sheet*gps<graphs; ++graph) {
      Graph *g= graphsVec_[graph+sheet*gps];
      if(tmpl_.useG0_)
	g->saveinfo(f,&(tmpl_.graphs_[graph]),&(tmpl_.sets_[0]),
		    &(tmpl_.params_[graph]));
      else
	g->saveinfo(f,&(tmpl_.graphs_[graph]),&(tmpl_.sets_[graph]),
		    &(tmpl_.params_[graph]));
    }
    
    for (int graph= 0; graph<gps && graph+sheet*gps<graphs; ++graph) {
      Graph *g= graphsVec_[graph+sheet*gps];
      g->savedata(f,&tmpl_.data_,!tmpl_.valid_);
    }
    
    if (isPipe) {
      pclose(f);
    } else {
      fclose(f);
    }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1