/*-*- 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
 *****************************************************************************/

#ifndef __GRACETMPL_H__
#define __GRACETMPL_H__

#include <vector>
#include <string>
#include <map>

extern "C" {
#include <stdio.h>
}


/** @brief Namespace for classes used to store datasets in xmgrace files.
 * 
 * The datasets are organized in graphs. At least one graph is saved per file
 * depending on the template used (if any). 
 *
 */
namespace GraceTMPL {

  /** @brief convenience type definition */
  typedef std::vector<std::string> StringVec;

  /** @brief convenience type definition */
  typedef std::vector<StringVec> String2Vec;

  /** @brief convenience type definition */
  typedef std::map<int,StringVec> StringVecMap;

  /** @brief convenience type definition */
  typedef std::map<int,StringVecMap> StringVec2Map;

  /** @brief convenience type definition */
  typedef std::map<std::string,std::string> StringMap;

  /** @brief convenience type definition */
  typedef std::map<std::string,StringMap*> StringStringMap;

  /** @brief Removes any occurences of $NAME or ${CONTEXT::NAME} in a string. 
   */
  std::string smashVars(const std::string &from);

  /** @brief String factory formatting double values */
  std::string stringNum(double d, const char *fmt= "%lg");
  /** @brief String factory formatting double values */
  std::string stringNum(float d, const char *fmt= "%g");
  /** @brief String factory formatting double values */
  std::string stringNum(long i, const char *fmt= "%ld");
  /** @brief String factory formatting double values */
  std::string stringNum(int i, const char *fmt= "%d");
 
  /** @brief Hierarchical environment system.
   *
   * A (named) Environment holds a number of variablename/value pairs.
   * The environment's name can be seen as a context. As such, it can be 
   * contained in other Environment objects as a contextname/Environment
   * pair. 
   */
  class Environment {
    std::string name_; //< Context name
    Environment *parent_; //< The enclosing Environment
    std::map<std::string,std::string> variable_; //< Variablename/value pairs
    std::map<std::string,Environment *> context_; //< Contextname/Environment pairs
    int usage_; //< Usage count (possibly useless?)

  public:
    /** @brief Default constructor. */
    Environment(Environment *parent= 0) 
      : name_(""), parent_(parent), usage_(1) {};

    /** @brief Clears all variables - not the contexts */
    void clear() {
      variable_.clear();
      //context_.clear();
    }

    /** @brief Set the parent Environment */
    void setParent(Environment *parent) { 
      parent_= parent; 
      if (parent_ && name_!="") parent_->add(name_,this);
    }

    /** @brief Set the context name */
    void setName(const std::string &name) { 
      name_= name; 
      if (parent_ && name_!="") parent_->add(name_,this);
    }

    /** @brief Set an environment variable */
    void set(const std::string &name,const std::string &value) {
      variable_[name]= value;
    }

    /** @brief set a named context - ensures consistency */
    void add(const std::string &name,Environment *value) {
      if (context_[name]== value) return;
      context_[name]= value;
      value->setParent(this);
      value->setName(name);
      // cerr << "registered "<<name<<" within "<<name_<<"\n";
    }

    /** @brief do we really need this? */
    Environment *use() { usage_++; return this; }

    /** @brief do we really need this? */
    int unuse() { return !(--usage_); }

    /** @brief Expands a string containing variables
     *
     * Searches for syntactically correct variables in the provided string
     * and substitutes them using substitute(context,variable,fallback). 
     *
     * Valid variable syntax is either:
     * - @c $variable or @c ${variable} within the current context
     * - @c ${::variable} or @c ${std::variable} for the topmost context
     * - @c ${context::variable} for any context - if the @c context is not 
     *   known to the current context it is passed to the parent context,
     *   and so on. The @c 'context' can also be written as 
     *   @c 'context1::context2::context3[..]' to address nested contexts
     * - In case a variable cannot be expanded according to the above methods 
     *   substitution will not take place and the (unresolvable) variable 
     *   specification will be included in the destination string. This kind 
     *   of 'fallback' can be changed by appending @c ':fallback' to 
     *   @c 'variable' (note this only works when the ${} format is used), i.e.
     *   @c ${context::variable:fallback} or @c ${variable:fallback}
     * - If a variable cannot be resolved in the current context it is always
     *   passed to the parent context. This behaviour might be configurable in
     *   the (near or distant) future.
     *
     * Nested variables are allowed: @c ${variable_${number}} will be expanded
     * the following way: first, @c ${number} is expanded, say to @c 001. 
     * So, we get @c ${variable_001} as the result. 
     * Now, if @e nests is larger than 0, this method will recurse into 
     * itself (up to @e nests times) and expand @c ${variable_001} to
     * whatever value @c $variable_001 yields.
     */
    std::string expand(const std::string &, int nests=0);

    /** @brief Substitutes a variable honouring its context
     *
     * This method also uses parent->substitute(context,variable,fallback) or
     * context_[context]->substitute(context2,variable,fallback)
     * for expansion, if this should be necessary.
     */
    std::string substitute(const std::string &context,
			   const std::string &variable,
			   const std::string &fallback);
  };

  /** @brief Base class for any class that should use Environment 
   *
   * This class provides transparent access to the Environment hierarchy.
   */
  class EnvironmentUser {

  protected:
    /** @brief Pointer to the Environment */
    Environment *env_;

  public:
    /** @brief Constructor */
    EnvironmentUser() { env_= new Environment(); } 

    /** @brief Destructor */
    ~EnvironmentUser() { if (env_ && env_->unuse()) delete env_; }

    /** @brief Returns the pointer to the Environment */
    Environment *env() { return env_; }

    /** @brief set a new Environment for this class
     *
     * Used internally to allow setting an Environment for a new Environment
     * user.
     */
    void setenv(Environment *env) 
    {
      if (!env) return;
      if (env_ && env_->unuse()) delete env_;
      env_= env->use();
    }

    /** @brief Set a name/value pair - existing entries will be replaced */
    void setenv(const std::string &name,const std::string &value) 
    {
      if (!env_) return;
      env_->set(name,value);
    }

    /** @brief Set a name/value pair - existing entries will be replaced */
    void setenv(const std::string &name,double value) 
    { 
      setenv(name,stringNum(value)); 
    }

    /** @brief A wrapper function for Environment::expand()
     */
    std::string expand(const std::string &str, int nests=0) 
    {
      if (!env_) return "";
      return env_->expand(str, nests); 
    } 
  };

  /** @brief Container for x/y data pairs, optionally with dx and dy 
   *
   * Beware that a Data object is not to be used more than once. Each data 
   * object has it's own Environment and at some point of gracefile generation
   * the Data object is assigned with a certain name (e.g. "S0") - this is 
   * done for all Data objects on the page and very essential for variable 
   * substitution.
   */
  class Data : public EnvironmentUser {

    /** @brief Used to store the string used for the legend */
    std::string name_;
  
    /** @brief Number of data values (a.k. size of data fields) */
    int n_;
    
    /** @brief Dataset's Number - defaults to 0*/
    int setnum_;
    
    /** @brief Data fields used to store the values - NULL if not provided */
    double *x_,*y_,*dx_,*dy_;

    /** @brief Used for offset correction */
    double xoffs_, yoffs_;

    /** @brief Used for scaling the data */
    double scale_;

  public:

    /** @brief Initialized a dummy dataset.
     *
     * This dummy dataset can be used to represent literal data.
     */
    Data();
    
    /** @brief Initialized the dataset's name and data fields. 
     *
     * The name is used for the dataset's legend string.
     * N denotes the number of values in each x,y,dx and dy. 
     * The latter two can be NULL pointers if errors are not of interest.
     */
    Data(const std::string &name, int n,
	 const double *x, const double *y,
	 const double *dx=0, const double *dy=0);

    /** @brief Returns the dataset's name */
    std::string name() { return name_; }

    /** @brief Checks if at least one of x or y errors are present */
    int hasErrors() { return (dy_ || dx_); }

    /** @brief Set the dataset's number.
     *
     * Called by GraceTMPL::Graph::save() when the dataset order is known.
     * Default is 0.
     */
    void setNum(int i) { setnum_= i; }

    /** @brief Query dataset's number. */
    int num() { return setnum_; }

    /** @brief Set the data's x-offset */
    void setXOffset(double x) { xoffs_= x; }
    /** @brief Set the data's y-offset */
    void setYOffset(double y) { yoffs_= y; }
    /** @brief Set a factor by which the data will be scaled */
    void setScaling(double s) { scale_= s; }

    /** @brief Query autoscale information from the datset 
     * 
     * The dataset's maximum and minumum x and y values are stored in
     * xmin, xmax, ymin and ymax. If error values are present, they are
     * taken into account scaled by errorfac. Errorfac defaults to 1.0 
     * and can be set to 0.0 if errors are present and should not 
     * be taken into account. Autoscaling can be turned of by prepending 
     * and appending two spaces to the according axislabel: 
     * use "\ \ mylabel\ \ " instead of "mylabel" or "\ \ \ \ " instead of
     * "" to turn off autoscaling.
     *
     * autoscale only honours points within the x/y range 
     * [XMIN:XMAX]/[YMIN:YMAX].
     */
    void autoscale(double &xmin, double &xmax, double &ymin, double &ymax,
		   double XMIN,  double XMAX,  double YMIN,  double YMAX, 
		   double errorfac= 1.0);

    /** @brief Save information strings info file.
     *
     * DaSet contains the information strings relevant for this dataset.
     * if DaSet is empty or a NULL pointer NO information is being written
     * to the file. This is a mandatory behaviour for bare dataset saving.
     *
     * If a legend string ends with "$'" it is not replaced with the string
     * provided by the program.
     *
     * @todo Make this a stream
     */
    void saveinfo(FILE *f, const StringVec *daSet=0);

    /** @brief Save data-fields info file.
     *
     * if correctLog is specified as !=0 the data will be cleaned of
     * any points with non-positive y or y-dy values. This way the 
     * data is ready for a logarithic plot (dirty hack to avoid problem
     * in xmgrace). 
     */
    void savedata(FILE *f, int correctLog= 0);
  };

  class Save;

  /** @brief Container for a graph holding some datasets
   *
   * In the normal case of operation, this class would not be instantiated
   * by the user. The preferred way to get a new graph object is to call 
   * GraceTMPL::Save::addData().
   *
   * Also note that it's in the application's responsibility to clean up
   * Data objects. Graph::~Graph() would not touch them!
   */
  class Graph : public EnvironmentUser {
    /** @brief Used to store the graph's number */
    int graphnum_;

    /** @brief References the saver instance */
    Save* saver_;
  
    /** @brief Used to store the datasets */
    std::vector <Data *> dataVec_;
    /** @brief Used to store some parameters belonging to the graph */
    std::vector <std::string> params_;

    /** @brief Used to identify graph numbers holding literal data */
    std::map<int, int> literalData_;

    /** @brief Used for offset correction */
    double xoffs_, yoffs_;

    /** @brief Used for scaling the data */
    double scale_;

    /** @brief Is !=0 if underlying datasets should be corrected for
     * logplots
     */
    int correctLog_;

  public:
    /** @brief Initialize the graph with optional logplot preparation.
     *
     * The saver must be a GraceTMPL::Save instance. If it is omitted
     * or given as 0 some things will not work as expected!
     *
     * If logplot is specified as !=0 all datasets in the graph will
     * be corrected for logarithmic plotting.
     */
    Graph(Save *saver, int logplot=0);

    /** @brief Set the graph's number
     * 
     * This method is used internally to set the graph's number
     * corresponding to g0..gN.
     */
    void setGraph(int i) { graphnum_= i; }

    /** @brief Add a dataset to this graph
     *
     *
     */
    void addData(Data *d) {
      dataVec_.push_back(d);
      d->env()->setParent(env());
    }

    /** @brief Returns all datasets of this graph */
    std::vector <Data *> *data() { return &dataVec_; }
    
    /** @brief Set the graph's x-offset */
    void setXOffset(double x) { xoffs_= x; }

    /** @brief Set the data's y-offset */
    void setYOffset(double y) { yoffs_= y; }

    /** @brief Set a factor by which the data will be scaled */
    void setScaling(double s) { scale_= s; }

    /** @brief Query the graph's x-offset */
    double xoffset() { return xoffs_; }

    /** @brief Query the graph's y-offset */
    double yoffset() { return yoffs_; }

    /** @brief Query the factor by which the data will be scaled */
    double scale() { return scale_; }

    /** @brief Query if data will be corrected for logplots */
    int correctLog() { return correctLog_; }

    /** @brief Add a variable to the graph's local parameters */
    void addParam(const std::string &name,double value);

    /** @brief Prepare graph for saving - ensures consistency */
    void saveprep(const StringVecMap *daSets);

    /** @brief Save information strings info file.
     *
     * DaGraph contains the information strings relevant for this graph.
     *
     * DaSet contains the information strings relevant for this dataset.
     *
     * DaStrings containes the string definition templates used to format
     * the graph's parameter strings.
     *
     * If DaGraph is empty or a NULL pointer NO information is being written
     * to the file. This is a mandatory behaviour for bare dataset saving.
     *
     * @todo Make this a stream
     */
    void saveinfo(FILE *f, 
		  const StringVec *daGraph=0, 
		  const StringVecMap *daSets=0,
		  const StringVecMap *daStrings= 0);

    /** @brief Save all datasets linked to this graph 
     *
     * If dataonly is specified as !=0, no '@target ...' will be used to 
     * address the target graph number. This is a mandatory behaviour 
     * for bare dataset saving.
     *
     * @todo Make this a stream
     */
    void savedata(FILE *f, StringMap *literalData, int dataonly= 0);
  };

  /** @brief Container for a number of graphs to be saved in a xmgrace 
   *         compatible file format.
   *
   * For saving the data, an existing xmgrace file can be used as a template
   * by calling loadTemplate(templatename)
   *
   * So what happens in a typical livecycle of GraceTMPL usage? 
   *
   * - The application creates a new GraceTMPL::Save object.
   * - Using GraceTMPL::Save::loadTemplate it then loads a gracefile.
   *   Anything found in the template which cannot be associated with
   *   a certain graph or set is saved as a string and preserved for
   *   later, when the output files are produced. Anything regarding a
   *   graph or set is stored in special data structures. And by the
   *   way, all goes into GraceTMPL::Save::Template. Also, the doubly
   *   indexed vector request_ is built which can be queried using
   *   templateDataRequestInfo().
   * - At this time, GraceTMPL knows how many graphs fit on each page
   *   (or go to each file, resp.) and how many datasets each graph in
   *   the template contains. The latter information is of a certain
   *   'diffuse' significance. On one hand side, a graph can take
   *   @e any number of graphs. Literal data will @e always be
   *   contained in the graph. Data stored by the application will be
   *   formatted like the associated dataset in the template. If more
   *   data is stored than datasets where contained in the template,
   *   the data will be formatted in some nonpredictable way
   *   (i.e. however grace should decide).
   * - Now the application creates a number of graphs and stores
   *   datasets therein. How this is done, in a way depends on the
   *   purpose of the template. 
   *   - If the Template requests certain data via "$=DATA" or "$'DATA"
   *     the application will want to analyze whatever a call to
   *     templateDataRequestInfo() returns. Creating one graph after
   *     the other it will produce datasets as requested by the
   *     template and store them within the graphs. 
   *   - Another approach is to just create one graph after the other,
   *     happily storing any kind and count of datasets within the
   *     graphs and trust that the template is properly formatted to
   *     handle the data.
   *   Upon creation, a graph is already assigned with a name, namely
   *   "gX", with X being the graph number.
   * - The application can set any amount of name/value pairs within
   *   the GraceTMPL::Save object, each of the graphs and any of the
   *   datasets, as need may call for. 
   * - When all is done, the application may call GraceTMPL::Save::save()
   *   This will trigger the following actions: 
   *   - By expanding the filename template the name for the first
   *     output file is generated. If the resulting filename starts
   *     with pipe symbol ("|") and enablePipe(true) was set, it will
   *     be interpreted as a process not a file and the output data
   *     will be sent to the process. Note this is a security risk, so
   *     by default pipe handling is disabled.
   *   - To each graph in the template a graph as created by the
   *     application is associated by a call to saveprep. At this
   *     point, all sets contained in a graph get a name "sX" with X
   *     being the set number.
   *   - Now the output data is created. The common stuff is written
   *     as taken from the template. Then, for each graph and set the
   *     formatting stiff is written. Any string possibly containing a
   *     variable (e.g. legend strings, axis labels, titles, strings,
   *     etc.) is expanded properly. After that part, all data is
   *     written to the output and the pipe or file is closed.
   *   - If any graphs are yet unhandled, the saver will increase the
   *     page number and repeat this procedure.
   */
  class Save : public EnvironmentUser
  {
  protected:

    /** @brief Struct to give easy access to copy-data sources */
    struct CopySrc
    {
      int g;     ///< @brief graph and set number of source
      int s;     ///< @brief graph and set number of source
      Data *src; ///< @brief pointer to source (not available when null)

      /** Contructor */
      CopySrc() : g(-1),s(-1),src(0) {};
    };

    /** @brief convenience type definition */
    typedef std::map<int,CopySrc> CopyMap;
    
    /** @brief convenience type definition */
    typedef std::map<int,CopyMap> Copy2Map;

    /** @brief Struct to hold template data.
     *
     * Since the strings stored in this structure might be reused for multiple
     * puproses, they may not be altered in any way upon string expansion or 
     * alike - make sure to always work on copies.
     */
    struct Template 
    {
      std::string filename_;      ///< @brief where the template is from
      std::string header_;        ///< @brief All lines beginning with '#'
      StringVec common_;     ///< @brief Everything but graph, sets, strings
      String2Vec strings_;   ///< @brief All strings but param templates
      StringVecMap graphs_;  ///< @brief All information on graphs but sets
      StringVec2Map sets_;   ///< @brief All information on sets
      StringVec2Map params_; ///< @brief All param templates
      String2Vec request_;   ///< @brief All set requests
      StringMap data_;       ///< @brief The original data from the template
      Copy2Map copy_;        ///< @brief Flags a \ref dict_copy_data
      std::map<int, int> ignore_; ///< @brieg graphs hat shouldnot get data
      int gpp_;              ///< @brief graphs per page
      int ignores_;          ///< @brief graphs to ignore but process anyway
      int useG0_;            ///< @brief G0 has dataset format of all graphs
      int valid_;            ///< @brief template is valid and can be used
    };

    /** @brief Used to store template data 
     *
     * @todo More than one template for more than one output file?
     *       Probably not!
     */
    Template tmpl_;

    /** Used to store all graphs */
    std::vector<Graph*> graphsVec_;

    /** Used to store the output file's environment */
    StringStringMap docEnvs_;
    /** Used to store the environment specific to the saver */
    StringMap myEnv_;
    
    /** Used to store the filename template */
    std::string nameTmpl_;

    /** Used to set flag if opening a pipe for output is allowed */
    int allowPipe_;

  public:

    /** @brief Default constructor. Does nothing special. */
    Save();

    /** @brief Destructor. Cleans up things. */
    virtual ~Save() { ; }

    /** @brief Create and store a new Graph within this object. */
    virtual Graph *newGraph(int logplot= 0) {
      Graph *g= new Graph(this,logplot);
      if (!g) return 0;
      graphsVec_.push_back(g);
      return g;
    }

    /** @brief Used to query if a certain dataset should be copied
     *         from another.
     *
     * Used internally only by Graph::saveprep
     */
    int isCopydata(int g, int s);

    /** @brief Used to query the source set of a set copy operation.
     *
     * Used internally only by Graph::savedata()
     */
    Data *copydata(int g, int s) 
    {
      if (isCopydata(g,s)) return tmpl_.copy_[g][s].src;
      return 0;
    }

    /** @brief Used to set a reference to a dataset copy source.
     *
     * Used internally only by Graph::saveprep()
     */
    void regCopydata(int g, int s, Data* src);

    /** @brief Used to clear all references to dataset copy sources.
     *
     * Used internally only.
     */
    void clearCopydata();

    /** @brief Returns the number of stored graphs */
    virtual Graph *graph(int i) 
    {
      if (i<0 || i>=int(graphsVec_.size())) return 0;
      return graphsVec_[i];
    }

    /** @brief Returns the number of stored graphs */
    virtual int graphs() { return graphsVec_.size(); }

    /** @brief Set the template for the output filename 
     *
     * The accessible environment for the output template is
     * the same as for the whole file plus the environment of 
     * the first graph in the file. 
     *
     * @see save()
     */
    void setOutputName(const std::string &name) { nameTmpl_= name; } 

    /** @brief Enables writing output data to a pipe.
     *
     * If output to a pipe is enables and the outputname set with 
     * setOutputName() starts with a '|' symbol, the rest of the 
     * outputname will be interpreted as a process to which a pipe is
     * opened and the outputdata is written. This might not work as 
     * expected if only works if no template has been loaded. For this 
     * case, pipe support might be disabled in the future
     * @todo Decide upon pipe policy in case of missing template.
     */
    void enablePipe(int i) { allowPipe_= i; }

    /** @brief Query the state of pipe handling */
    int pipeEnabled() { return allowPipe_; }

    /** @brief Load an xmgrace file as template.
     *
     * In the template file, environment variables can be used in 
     * a way of writing '$NAME' of ${NAME} with NAME consisting of
     * alphanumerical characters and the underscore ('_'). This will be
     * replaced by the string related to NAME. An environment variable 
     * can be set by calling setenv(). Any environment variable belonging 
     * to a certain graph is exported to the sheet using a naming style 
     * of ${GRAPH::NAME} with GRAPH denoting the graph name (e.g. "g0", 
     * "g1", etc.). Note this naming style explicitly requires the brackets 
     * "{}".
     * 
     * Additionally, the might be an arbitrary number of parameters
     * related to a graph. To specify the positions of these parameters
     * (if they should be printed at all) the template can contain string
     * objects of the form "PARAMg0:1" where g0 stands for graph "g0" and
     * 1 stands for set number 1 withing that graph. If any, at least two 
     * strings per graph should be present, namely "PARAMg0:0" and 
     * "PARAMg0:1" for graph 0, "PARAMg1:0" and "PARAMg1:1" for graph 1 
     * and so on. The positions of the last two strings of this kind will
     * be used to interpolate the positions of further strings, so there 
     * should be enough space to hold the expected number of strings.
     * 
     * @see setenv(), save()
     */
    virtual int loadTemplate(const char *filename, int useS0= 0);

    /** @brief Query information about datasets the template requests.
     *
     * In each of the graphs defined within the template, the contained sets
     * can request the program using GraceTMPL to supply certain kind of data
     * for the according set. This is done by adding a file/data specifier to
     * the legendstring. A specifier starts with $' for sets which should keep
     * their name from the legend entry and with $= for sets which get their 
     * name from the application. This is followed by a string of arbitrary
     * length which is actually passed to the application when it calls this
     * function. 
     *
     * The result will be a 2-dimensional string vec with the first index
     * being the graph number and the second index the setnumber within that
     * graph. The setnumber must not necessarily represent the S* number as
     * known from XmGrace - literal data sets will be missing as they can not
     * be changed anyway.
     */
    virtual String2Vec templateDataRequestInfo();

    /** @brief Save all graphs to at least one file, optionally using a 
     *         template.
     *
     * If no template was loaded with loadTemplate(), each graph will result 
     * in one datafile wich can be imported into an xmgrace-graph as ascii 
     * data.
     *
     * In case a template was successfully loaded, it will be used to store
     * as many graphs in a file as specified in the template. The resulting
     * files can be loaded by the same xmgrace version used to generate the
     * template file. For notes on templates, see loadTemplate().
     *
     * On saving, the environment variable '$p' will be expanded to the 
     * file number currently written, while '$pz' is this same number 
     * formatted with enough leading zeros to give a fixed-length string 
     * for all written files.
     * To generate outputfilenames which can be sorted in ascending 
     * order, include "$pz" in your filename-template (see setOutputName()).
     * '$P' will expand to the number of outputfiles necessary to save all
     * graphs.
     */
    virtual void save();
  };

}


#endif

