/*!
 * \file gnuplot_i.h
 * \brief A C++ interface to gnuplot.
 * \author Carles Fernandez-Prades, 2017. cfernandez(at)cttc.es
 *
 * Original source code found at https://code.google.com/archive/p/gnuplot-cpp/
 * by Jeremy Conlin jeremit0(at)gmail.com
 *
 * Version history:
 *  0. C interface
 *     by N. Devillard (27/01/03)
 *  1. C++ interface: direct translation from the C interface
 *     by Rajarshi Guha (07/03/03)
 *  2. corrections for Win32 compatibility
 *     by V. Chyzhdzenka (20/05/03)
 *  3. some member functions added, corrections for Win32 and Linux
 *     compatibility
 *     by M. Burgis (10/03/08)
 *  4. Some fixes and improvements for Linux and macOS
 *     by C. Fernandez (22/10/17)
 *
 * -----------------------------------------------------------------------------
 *
 * GNSS-SDR is a Global Navigation Satellite System software-defined receiver.
 * This file is part of GNSS-SDR.
 *
 * Copyright (C) 2010-2020  (see AUTHORS file for a list of contributors)
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * -----------------------------------------------------------------------------
 */


#ifndef GNSS_SDR_GNUPLOT_I_H
#define GNSS_SDR_GNUPLOT_I_H

#include <cmath>
#include <cstdlib>  // for getenv()
#include <cstring>  // for strncpy
#include <fstream>
#include <iostream>
#include <list>     // for std::list
#include <sstream>  // for std::ostringstream
#include <stdexcept>
#include <string>
#include <sys/stat.h>
#include <vector>

#if USE_GLOG_AND_GFLAGS
#include <gflags/gflags.h>
DEFINE_bool(show_plots, true, "Show plots on screen. Disable for non-interactive testing.");
#else
#include <absl/flags/flag.h>
ABSL_FLAG(bool, show_plots, true, "Show plots on screen. Disable for non-interactive testing.");
#endif

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
// defined for 32 and 64-bit environments
// clang-format off
#include <io.h>              // for _access(), _mktemp()
#define GP_MAX_TMP_FILES 27  // 27 temporary files it's Microsoft restriction
// clang-format on
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
// all UNIX-like OSs (Linux, *BSD, macOS, Solaris, ...)
#include <unistd.h>  // for access(), mkstemp()
#define GP_MAX_TMP_FILES 1024
#else
#error unsupported or unknown operating system
#endif

// declare classes in global namespace

class GnuplotException : public std::runtime_error
{
public:
    explicit GnuplotException(const std::string &msg) : std::runtime_error(msg) {}
};


class Gnuplot
{
private:
    // -------------------------------------------------------------------------
    // member data
    //! pointer to the stream that can be used to write to the pipe
    FILE *gnucmd;
    //! validation of gnuplot session
    bool valid;
    //! true = 2d, false = 3d
    bool two_dim;
    //! number of plots in session
    int nplots;
    //! functions and data are displayed in a defined styles
    std::string pstyle;
    //! interpolate and approximate data in defined styles (e.g. spline)
    std::string smooth;
    //! list of created tmpfiles
    std::vector<std::string> tmpfile_list;

    // -------------------------------------------------------------------------
    // static data
    //! number of all tmpfiles (number of tmpfiles restricted)
    static int tmpfile_num;
    //! name of executed GNUPlot file
    static std::string m_sGNUPlotFileName;
    //! gnuplot path
    static std::string m_sGNUPlotPath;
    //! standard terminal, used by showonscreen
    static std::string terminal_std;

    // -------------------------------------------------------------------------
    // member functions (auxiliary functions)
    // ---------------------------------------------------
    //! get_program_path(); and popen();
    //
    // \param --> void
    //
    // \return <-- void
    // ---------------------------------------------------
    void init();

    // ---------------------------------------------------
    //! creates tmpfile and returns its name
    //
    // \param tmp --> points to the tempfile
    //
    // \return <-- the name of the tempfile
    // ---------------------------------------------------
    std::string create_tmpfile(std::ofstream &tmp);

    // ------------------------------------------------------------------------
    //! gnuplot path found?
    //
    // \param ---
    //
    // \return <-- found the gnuplot path (yes == true, no == false)
    // -------------------------------------------------------------------------
    static bool get_program_path();

    // -------------------------------------------------------------------------
    //! checks if file is available
    //
    // \param filename --> the filename
    // \param mode     --> the mode [optional,default value = 0]
    //
    // \return file exists (yes == true, no == false)
    // -------------------------------------------------------------------------
    bool file_available(const std::string &filename);

    // -------------------------------------------------------------------------
    //<! \brief checks if file exists
    //
    // \param filename --> the filename
    // \param mode     --> the mode [optional,default value = 0]
    //
    // \return file exists (yes == true, no == false)
    // -------------------------------------------------------------------------
    static bool file_exists(const std::string &filename, int mode = 0);

public:
    // -------------------------------------------------------------------------
    // \brief optional function: set Gnuplot path manual
    // attention:  for windows: path with slash '/' not backslash '\'
    //
    // \param path --> the gnuplot path
    //
    // \return true on success, false otherwise
    // -------------------------------------------------------------------------
    static bool set_GNUPlotPath(const std::string &path);

    // -------------------------------------------------------------------------
    // optional: set standard terminal, used by showonscreen
    //   defaults: Windows - win, Linux - x11, Mac - aqua
    //
    // \param type --> the terminal type
    //
    // \return ---
    // -------------------------------------------------------------------------
    static void set_terminal_std(const std::string &type);

    // -------------------------------------------------------------------------
    // constructors
    // -------------------------------------------------------------------------

    //! set a style during construction
    explicit Gnuplot(const std::string &style = "points");

    // plot a single std::vector at one go
    Gnuplot(const std::vector<double> &x,
        const std::string &title = "",
        const std::string &style = "points",
        const std::string &labelx = "x",
        const std::string &labely = "y");

    // plot pairs std::vector at one go
    Gnuplot(const std::vector<double> &x,
        const std::vector<double> &y,
        const std::string &title = "",
        const std::string &style = "points",
        const std::string &labelx = "x",
        const std::string &labely = "y");

    // plot triples std::vector at one go
    Gnuplot(const std::vector<double> &x,
        const std::vector<double> &y,
        const std::vector<double> &z,
        const std::string &title = "",
        const std::string &style = "points",
        const std::string &labelx = "x",
        const std::string &labely = "y",
        const std::string &labelz = "z");


    // destructor: needed to delete temporary files
    ~Gnuplot();

    // --------------------------------------------------------------------------------

    // send a command to gnuplot
    Gnuplot &cmd(const std::string &cmdstr);

    // ---------------------------------------------------------------------------------
    //! Sends a command to an active gnuplot session, identical to cmd()
    // send a command to gnuplot using the <<  operator
    //
    // \param cmdstr --> the command string
    //
    // \return <-- a reference to the gnuplot object
    // ---------------------------------------------------------------------------------
    inline Gnuplot &operator<<(const std::string &cmdstr)
    {
        cmd(cmdstr);
        return (*this);
    }

    // --------------------------------------------------------------------------------
    // show on screen or write to file

    // sets terminal type to terminal_std
    Gnuplot &showonscreen();  // window output is set by default (win/x11/aqua)

    // sets terminal type to unknown (disable the screen output)
    Gnuplot &disablescreen();

    // saves a gnuplot session to a postscript file, filename without extension
    Gnuplot &savetops(const std::string &filename = "gnuplot_output");

    // saves a gnuplot session to a pdf file, filename without extension
    Gnuplot &savetopdf(const std::string &filename = "gnuplot_output", unsigned int font_size = 12);

    // --------------------------------------------------------------------------------
    // set and unset

    // set line style (some of these styles require additional information):
    //  lines, points, linespoints, impulses, dots, steps, fsteps, histeps,
    //  boxes, histograms, filledcurves
    Gnuplot &set_style(const std::string &stylestr = "points");

    // interpolation and approximation of data, arguments:
    //  csplines, bezier, acsplines (for data values > 0), sbezier, unique, frequency
    // (works only with plot_x, plot_xy, plotfile_x, plotfile_xy
    // (if smooth is set, set_style has no effect on data plotting)
    Gnuplot &set_smooth(const std::string &stylestr = "csplines");

    // ----------------------------------------------------------------------
    // \brief unset smooth
    // attention: smooth is not set by default
    //
    // \param ---
    //
    // \return <-- a reference to a gnuplot object
    // ----------------------------------------------------------------------
    inline Gnuplot &unset_smooth()
    {
        smooth = "";
        return *this;
    };

    // scales the size of the points used in plots
    Gnuplot &set_pointsize(const double pointsize = 1.0);

    // turns grid on/off
    inline Gnuplot &set_grid()
    {
        cmd("set grid");
        return *this;
    };
    // grid is not set by default
    inline Gnuplot &unset_grid()
    {
        cmd("unset grid");
        return *this;
    };

    // -----------------------------------------------
    // set the mulitplot mode
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &set_multiplot(int rows, int cols)
    {
        cmd("set multiplot layout " + std::to_string(rows) + "," + std::to_string(cols));  //+ " rowfirst");
        return *this;
    };

    // -----------------------------------------------
    // unsets the mulitplot mode
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &unset_multiplot()
    {
        cmd("unset multiplot");
        return *this;
    };

    // set sampling rate of functions, or for interpolating data
    Gnuplot &set_samples(const int samples = 100);
    // set isoline density (grid) for plotting functions as surfaces (for 3d plots)
    Gnuplot &set_isosamples(const int isolines = 10);

    // --------------------------------------------------------------------------
    // enables/disables hidden line removal for surface plotting (for 3d plot)
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // --------------------------------------------------------------------------
    Gnuplot &set_hidden3d()
    {
        cmd("set hidden3d");
        return *this;
    };

    // ---------------------------------------------------------------------------
    // hidden3d is not set by default
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // ---------------------------------------------------------------------------
    inline Gnuplot &unset_hidden3d()
    {
        cmd("unset hidden3d");
        return *this;
    };

    // enables/disables contour drawing for surfaces (for 3d plot)
    //  base, surface, both
    Gnuplot &set_contour(const std::string &position = "base");
    // --------------------------------------------------------------------------
    // contour is not set by default, it disables contour drawing for surfaces
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // ------------------------------------------------------------------
    inline Gnuplot &unset_contour()
    {
        cmd("unset contour");
        return *this;
    };

    // ------------------------------------------------------------
    // enables/disables the display of surfaces (for 3d plot)
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // ------------------------------------------------------------------
    inline Gnuplot &set_surface()
    {
        cmd("set surface");
        return *this;
    };

    // ----------------------------------------------------------
    // surface is set by default,
    // it disables the display of surfaces (for 3d plot)
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // ------------------------------------------------------------------
    inline Gnuplot &unset_surface()
    {
        cmd("unset surface");
        return *this;
    }


    // switches legend on/off
    // position: inside/outside, left/center/right, top/center/bottom, nobox/box
    Gnuplot &set_legend(const std::string &position = "default");

    // ------------------------------------------------------------------
    // \brief  Switches legend off
    // attention:legend is set by default
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // ------------------------------------------------------------------
    inline Gnuplot &unset_legend()
    {
        cmd("unset key");
        return *this;
    }

    // -----------------------------------------------------------------------
    // \brief sets and clears the title of a gnuplot session
    //
    // \param title --> the title of the plot [optional, default == ""]
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------------------------------
    inline Gnuplot &set_title(const std::string &title = "")
    {
        std::string cmdstr;
        cmdstr = "set title \"";
        cmdstr += title;
        cmdstr += "\"";
        *this << cmdstr;
        return *this;
    }

    // --------------------------------------------------------------------------------
    //! Clears the title of a gnuplot session
    // The title is not set by default.
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // ---------------------------------------------------------------------------------
    inline Gnuplot &unset_title()
    {
        this->set_title();
        return *this;
    }

    // set x axis label
    Gnuplot &set_ylabel(const std::string &label = "x");
    // set y axis label
    Gnuplot &set_xlabel(const std::string &label = "y");
    // set z axis label
    Gnuplot &set_zlabel(const std::string &label = "z");

    // set axis - ranges
    Gnuplot &set_xrange(const double iFrom, const double iTo);
    // set y-axis - ranges
    Gnuplot &set_yrange(const double iFrom, const double iTo);
    // set z-axis - ranges
    Gnuplot &set_zrange(const double iFrom, const double iTo);

    // autoscale axis (set by default) of xaxis
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &set_xautoscale()
    {
        cmd("set xrange restore");
        cmd("set autoscale x");
        return *this;
    };

    // -----------------------------------------------
    // autoscale axis (set by default) of yaxis
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &set_yautoscale()
    {
        cmd("set yrange restore");
        cmd("set autoscale y");
        return *this;
    };

    // -----------------------------------------------
    // autoscale axis (set by default) of zaxis
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &set_zautoscale()
    {
        cmd("set zrange restore");
        cmd("set autoscale z");
        return *this;
    };

    // turns on/off log scaling for the specified xaxis (logscale is not set by default)
    Gnuplot &set_xlogscale(const double base = 10);
    // turns on/off log scaling for the specified yaxis (logscale is not set by default)
    Gnuplot &set_ylogscale(const double base = 10);
    // turns on/off log scaling for the specified zaxis (logscale is not set by default)
    Gnuplot &set_zlogscale(const double base = 10);

    // -----------------------------------------------
    // turns off log scaling for the x axis
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &unset_xlogscale()
    {
        cmd("unset logscale x");
        return *this;
    };

    // -----------------------------------------------
    // turns off log scaling for the y axis
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &unset_ylogscale()
    {
        cmd("unset logscale y");
        return *this;
    };

    // -----------------------------------------------
    // turns off log scaling for the z axis
    //
    // \param ---
    //
    // \return <-- reference to the gnuplot object
    // -----------------------------------------------
    inline Gnuplot &unset_zlogscale()
    {
        cmd("unset logscale z");
        return *this;
    };

    // set palette range (autoscale by default)
    Gnuplot &set_cbrange(const double iFrom, const double iTo);

    // --------------------------------------------------------------------------------
    // plot

    // plot a single std::vector: x
    //   from file
    Gnuplot &plotfile_x(const std::string &filename,
        const unsigned int column = 1,
        const std::string &title = "");

    //   from std::vector
    template <typename X>
    Gnuplot &plot_x(const X &x, const std::string &title = "");

    // plot x,y pairs: x y
    //   from file
    Gnuplot &plotfile_xy(const std::string &filename,
        const unsigned int column_x = 1,
        const unsigned int column_y = 2,
        const std::string &title = "",
        const unsigned int decimate = 1);
    //   from data
    template <typename X, typename Y>
    Gnuplot &plot_xy(const X &x, const Y &y,
        const std::string &title = "",
        const unsigned int decimate = 1);

    // plot x,y pairs with dy errorbars: x y dy
    //   from file
    Gnuplot &plotfile_xy_err(const std::string &filename,
        const unsigned int column_x = 1,
        const unsigned int column_y = 2,
        const unsigned int column_dy = 3,
        const std::string &title = "");
    //   from data
    template <typename X, typename Y, typename E>
    Gnuplot &plot_xy_err(const X &x, const Y &y, const E &dy,
        const std::string &title = "");

    template <typename X, typename Y, typename E>
    Gnuplot &plot_grid3d(const X &x, const Y &y, const E &mag,
        const std::string &title = "");

    // plot x,y,z triples: x y z
    //   from file
    Gnuplot &plotfile_xyz(const std::string &filename,
        const unsigned int column_x = 1,
        const unsigned int column_y = 2,
        const unsigned int column_z = 3,
        const std::string &title = "");
    //   from std::vector
    template <typename X, typename Y, typename Z>
    Gnuplot &plot_xyz(const X &x,
        const Y &y,
        const Z &z,
        const std::string &title = "");

    // plot an equation of the form: y = ax + b, you supply a and b
    Gnuplot &plot_slope(const double a,
        const double b,
        const std::string &title = "");

    // plot an equation supplied as a std::string y=f(x), write only the function f(x) not y=
    // the independent variable has to be x
    // binary operators: ** exponentiation, * multiply, / divide, + add, - subtract, % modulo
    // unary operators: - minus, ! factorial
    // elementary functions: rand(x), abs(x), sgn(x), ceil(x), floor(x), int(x), imag(x), real(x), arg(x),
    //   sqrt(x), exp(x), log(x), log10(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x),
    //   sinh(x), cosh(x), tanh(x), asinh(x), acosh(x), atanh(x)
    // special functions: erf(x), erfc(x), inverf(x), gamma(x), igamma(a,x), lgamma(x), ibeta(p,q,x),
    //   besj0(x), besj1(x), besy0(x), besy1(x), lambertw(x)
    // statistical functions: norm(x), invnorm(x)
    Gnuplot &plot_equation(const std::string &equation, const std::string &title = "");

    // plot an equation supplied as a std::string z=f(x,y), write only the function f(x,y) not z=
    // the independent variables have to be x and y
    Gnuplot &plot_equation3d(const std::string &equation, const std::string &title = "");

    // plot image
    Gnuplot &plot_image(const unsigned char *ucPicBuf,
        const unsigned int iWidth,
        const unsigned int iHeight,
        const std::string &title = "");

    // plot circle
    Gnuplot &plot_circle(double east, double north, double radius, const std::string &label = "");

    // --------------------------------------------------------------------------------
    //! replot repeats the last plot or splot command.
    //  this can be useful for viewing a plot with different set options,
    //  or when generating the same plot for several devices (showonscreen, savetops)
    //
    // \param ---
    //
    // \return ---
    // --------------------------------------------------------------------------------
    inline Gnuplot &replot(void)
    {
        if (nplots > 0)
            {
                cmd("replot");
            }
        return *this;
    };

    // resets a gnuplot session (next plot will erase previous ones)
    Gnuplot &reset_plot();

    // resets a gnuplot session and sets all variables to default
    Gnuplot &reset_all();

    // deletes temporary files
    void remove_tmpfiles();

    // -------------------------------------------------------------------
    // \brief Is the gnuplot session valid ??
    //
    //
    // \param ---
    //
    // \return true if valid, false if not
    // -------------------------------------------------------------------
    inline bool is_valid() { return (valid); };
};


// ----------------------------------------------------------------------------
//
// initialize static data
//
int Gnuplot::tmpfile_num = 0;

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
std::string Gnuplot::m_sGNUPlotFileName = "pgnuplot.exe";
std::string Gnuplot::m_sGNUPlotPath = "C:/program files/gnuplot/bin/";
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
std::string Gnuplot::m_sGNUPlotFileName = "gnuplot";
std::string Gnuplot::m_sGNUPlotPath = "/usr/local/bin/";
#endif

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
std::string Gnuplot::terminal_std = "windows";
#elif (defined(unix) || defined(__unix) || defined(__unix__)) && !defined(__APPLE__)
std::string Gnuplot::terminal_std = "x11";
#elif defined(__APPLE__)
std::string Gnuplot::terminal_std = "aqua";
#endif

// ----------------------------------------------------------------------------
//
// constructor: set a style during construction
//
inline Gnuplot::Gnuplot(const std::string &style)
    : gnucmd(nullptr), valid(false), two_dim(false), nplots(0)

{
    init();
    set_style(style);
}


// ----------------------------------------------------------------------------
//
// constructor: open a new session, plot a signal (x)
//
inline Gnuplot::Gnuplot(const std::vector<double> &x,
    const std::string &title,
    const std::string &style,
    const std::string &labelx,
    const std::string &labely)
    : gnucmd(nullptr), valid(false), two_dim(false), nplots(0)
{
    init();

    set_style(style);
    set_xlabel(labelx);
    set_ylabel(labely);

    plot_x(x, title);
}


// ----------------------------------------------------------------------------
//
// constructor: open a new session, plot a signal (x,y)
//
inline Gnuplot::Gnuplot(const std::vector<double> &x,
    const std::vector<double> &y,
    const std::string &title,
    const std::string &style,
    const std::string &labelx,
    const std::string &labely)
    : gnucmd(nullptr), valid(false), two_dim(false), nplots(0)
{
    init();

    set_style(style);
    set_xlabel(labelx);
    set_ylabel(labely);

    plot_xy(x, y, title);
}


// ----------------------------------------------------------------------------
//
// constructor: open a new session, plot a signal (x,y,z)
//
inline Gnuplot::Gnuplot(const std::vector<double> &x,
    const std::vector<double> &y,
    const std::vector<double> &z,
    const std::string &title,
    const std::string &style,
    const std::string &labelx,
    const std::string &labely,
    const std::string &labelz)
    : gnucmd(nullptr), valid(false), two_dim(false), nplots(0)
{
    init();

    set_style(style);
    set_xlabel(labelx);
    set_ylabel(labely);
    set_zlabel(labelz);

    plot_xyz(x, y, z, title);
}


// ----------------------------------------------------------------------------
//
// Plots a 2d graph from a list of doubles: x
//
template <typename X>
Gnuplot &Gnuplot::plot_x(const X &x, const std::string &title)
{
    if (x.empty())
        {
            throw GnuplotException("std::vector too small");
            return *this;
        }

    std::ofstream tmp;
    std::string name = create_tmpfile(tmp);
    if (name.empty())
        {
            return *this;
        }

    //
    // write the data to file
    //
    for (unsigned int i = 0; i < x.size(); i++)
        {
            tmp << x[i] << '\n';
        }

    tmp.flush();
    tmp.close();

    plotfile_x(name, 1, title);

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plots a 2d graph from a list of doubles: x y
//
template <typename X, typename Y>
Gnuplot &Gnuplot::plot_xy(const X &x, const Y &y, const std::string &title, const unsigned int decimate)
{
    if (x.empty() || y.empty())
        {
            throw GnuplotException("std::vectors too small");
            return *this;
        }

    if (x.size() != y.size())
        {
            throw GnuplotException("Length of the std::vectors differs");
            return *this;
        }

    std::ofstream tmp;
    std::string name = create_tmpfile(tmp);
    if (name.empty())
        {
            return *this;
        }

    //
    // write the data to file
    //
    for (unsigned int i = 0; i < x.size(); i++)
        {
            tmp << x[i] << " " << y[i] << '\n';
        }

    tmp.flush();
    tmp.close();

    plotfile_xy(name, 1, 2, title, decimate);

    return *this;
}


// ---------------------------------------------------------------------------
//
// plot x,y pairs with dy errorbars
//
template <typename X, typename Y, typename E>
Gnuplot &Gnuplot::plot_xy_err(const X &x,
    const Y &y,
    const E &dy,
    const std::string &title)
{
    if (x.empty() || y.empty() || dy.empty())
        {
            throw GnuplotException("std::vectors too small");
            return *this;
        }

    if (x.size() != y.size() || y.size() != dy.size())
        {
            throw GnuplotException("Length of the std::vectors differs");
            return *this;
        }

    std::ofstream tmp;
    std::string name = create_tmpfile(tmp);
    if (name.empty())
        {
            return *this;
        }

    //
    // write the data to file
    //
    for (unsigned int i = 0; i < x.size(); i++)
        {
            tmp << x[i] << " " << y[i] << " " << dy[i] << '\n';
        }

    tmp.flush();
    tmp.close();

    // Do the actual plot
    plotfile_xy_err(name, 1, 2, 3, title);

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plots a 3d grid
//
template <typename X, typename Y, typename E>
Gnuplot &Gnuplot::plot_grid3d(const X &x,
    const Y &y,
    const E &mag,
    const std::string &title)
{
    if (x.empty() || y.empty())
        {
            throw GnuplotException("std::vectors too small");
            return *this;
        }
    std::ofstream tmp;
    std::string name = create_tmpfile(tmp);
    if (name.empty())
        {
            return *this;
        }

    //
    // write the data to file
    //
    for (unsigned int i = 0; i < x.size(); i++)
        {
            for (unsigned int k = 0; k < y.size(); k++)
                {
                    tmp << static_cast<float>(x.at(i)) << " " << static_cast<float>(y.at(k)) << " " << mag.at(i).at(k) << '\n';
                }
            tmp.flush();
        }

    tmp.close();

    std::ostringstream cmdstr;
    cmdstr << "set ticslevel 0\n";
    cmdstr << "set hidden3d\n";
    cmdstr << "unset colorbox\n";
    cmdstr << "set border 5\n";
    cmdstr << "unset ztics\n";

    cmdstr << " splot \"" << name << "\" u 1:2:3";

    if (title.empty())
        {
            cmdstr << " notitle with " << pstyle << " palette";
        }
    else
        {
            cmdstr << " title \"" << title << "\" with " << pstyle << " palette";
        }
    cmdstr << "\n";

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}

// ----------------------------------------------------------------------------
//
// Plots a 3d graph from a list of doubles: x y z
//
template <typename X, typename Y, typename Z>
Gnuplot &Gnuplot::plot_xyz(const X &x,
    const Y &y,
    const Z &z,
    const std::string &title)
{
    if (x.empty() || y.empty() || z.empty())
        {
            throw GnuplotException("std::vectors too small");
            return *this;
        }

    if (x.size() != y.size() || x.size() != z.size())
        {
            throw GnuplotException("Length of the std::vectors differs");
            return *this;
        }

    std::ofstream tmp;
    std::string name = create_tmpfile(tmp);
    if (name.empty())
        {
            return *this;
        }

    //
    // write the data to file
    //
    for (unsigned int i = 0; i < x.size(); i++)
        {
            tmp << x[i] << " " << y[i] << " " << z[i] << '\n';
        }

    tmp.flush();
    tmp.close();

    plotfile_xyz(name, 1, 2, 3, title);

    return *this;
}


// ----------------------------------------------------------------------------
//
// define static member function: set Gnuplot path manual
//   for windows: path with slash '/' not backslash '\'
//
inline bool Gnuplot::set_GNUPlotPath(const std::string &path)
{
    std::string tmp = path + "/" + Gnuplot::m_sGNUPlotFileName;

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    if (Gnuplot::file_exists(tmp, 0))  // check existence
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    if (Gnuplot::file_exists(tmp, 1))  // check existence and execution permission
#endif
        {
            Gnuplot::m_sGNUPlotPath = path;
            return true;
        }

    Gnuplot::m_sGNUPlotPath.clear();
    return false;
}


// ----------------------------------------------------------------------------
//
// define static member function: set standard terminal, used by showonscreen
//  defaults: Windows - win, Linux - x11, Mac - aqua
//
inline void Gnuplot::set_terminal_std(const std::string &type)
{
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    if (type.find("x11") != std::string::npos && std::getenv("DISPLAY") == nullptr)
        {
            throw GnuplotException("Can't find DISPLAY variable");
        }
#endif

    Gnuplot::terminal_std = type;
    return;
}


// ----------------------------------------------------------------------------
//
// A string tokenizer taken
//
template <typename Container>
void stringtok(Container &container,
    std::string const &in,
    const char *const delimiters = " \t\n")
{
    const std::string::size_type len = in.length();
    std::string::size_type i = 0;

    while (i < len)
        {
            // eat leading whitespace
            i = in.find_first_not_of(delimiters, i);

            if (i == std::string::npos)
                {
                    return;  // nothing left but white space
                }

            // find the end of the token
            std::string::size_type j = in.find_first_of(delimiters, i);

            // push token
            if (j == std::string::npos)
                {
                    container.push_back(in.substr(i));
                    return;
                }

            container.push_back(in.substr(i, j - i));

            // set up for next loop
            i = j + 1;
        }

    return;
}


// ----------------------------------------------------------------------------
//
// Destructor: needed to delete temporary files
//
Gnuplot::~Gnuplot()
{
//  remove_tmpfiles();
// A stream opened by popen() should be closed by pclose()
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    if (_pclose(gnucmd) == -1)
        {
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    if (pclose(gnucmd) == -1)
        {
#endif
            // throw GnuplotException("Problem closing communication to gnuplot");
            std::cout << "Gnuplot window left open.\n";
        }
}

// ----------------------------------------------------------------------------
//
// Resets a gnuplot session (next plot will erase previous ones)
//
inline Gnuplot &Gnuplot::reset_plot()
{
    //  remove_tmpfiles();
    nplots = 0;

    return *this;
}

// ----------------------------------------------------------------------------
//
// resets a gnuplot session and sets all variables to default
//
inline Gnuplot &Gnuplot::reset_all()
{
    //  remove_tmpfiles();
    nplots = 0;
    cmd("reset");
    cmd("clear");
    pstyle = "points";
    smooth = "";
    showonscreen();

    return *this;
}

// ----------------------------------------------------------------------------
//
// Change the plotting style of a gnuplot session
//
inline Gnuplot &Gnuplot::set_style(const std::string &stylestr)
{
    if (stylestr.find("lines") == std::string::npos &&
        stylestr.find("points") == std::string::npos &&
        stylestr.find("linespoints") == std::string::npos &&
        stylestr.find("impulses") == std::string::npos &&
        stylestr.find("dots") == std::string::npos &&
        stylestr.find("steps") == std::string::npos &&
        stylestr.find("fsteps") == std::string::npos &&
        stylestr.find("histeps") == std::string::npos &&
        stylestr.find("boxes") == std::string::npos &&  // 1-4 columns of data are required
        stylestr.find("filledcurves") == std::string::npos &&
        stylestr.find("histograms") == std::string::npos)  // only for one data column
        //        stylestr.find("labels")         == std::string::npos  &&  // 3 columns of data are required
        //        stylestr.find("xerrorbars")     == std::string::npos  &&  // 3-4 columns of data are required
        //        stylestr.find("xerrorlines")    == std::string::npos  &&  // 3-4 columns of data are required
        //        stylestr.find("errorbars")      == std::string::npos  &&  // 3-4 columns of data are required
        //        stylestr.find("errorlines")     == std::string::npos  &&  // 3-4 columns of data are required
        //        stylestr.find("yerrorbars")     == std::string::npos  &&  // 3-4 columns of data are required
        //        stylestr.find("yerrorlines")    == std::string::npos  &&  // 3-4 columns of data are required
        //        stylestr.find("boxerrorbars")   == std::string::npos  &&  // 3-5 columns of data are required
        //        stylestr.find("xyerrorbars")    == std::string::npos  &&  // 4,6,7 columns of data are required
        //        stylestr.find("xyerrorlines")   == std::string::npos  &&  // 4,6,7 columns of data are required
        //        stylestr.find("boxxyerrorbars") == std::string::npos  &&  // 4,6,7 columns of data are required
        //        stylestr.find("financebars")    == std::string::npos  &&  // 5 columns of data are required
        //        stylestr.find("candlesticks")   == std::string::npos  &&  // 5 columns of data are required
        //        stylestr.find("vectors")        == std::string::npos  &&
        //        stylestr.find("image")          == std::string::npos  &&
        //        stylestr.find("rgbimage")       == std::string::npos  &&
        //        stylestr.find("pm3d")           == std::string::npos  )
        {
            pstyle = std::string("points");
        }
    else
        {
            pstyle = stylestr;
        }

    return *this;
}

// ----------------------------------------------------------------------------
//
// smooth: interpolation and approximation of data
//
inline Gnuplot &Gnuplot::set_smooth(const std::string &stylestr)
{
    if (stylestr.find("unique") == std::string::npos &&
        stylestr.find("frequency") == std::string::npos &&
        stylestr.find("csplines") == std::string::npos &&
        stylestr.find("acsplines") == std::string::npos &&
        stylestr.find("bezier") == std::string::npos &&
        stylestr.find("sbezier") == std::string::npos)
        {
            smooth = "";
        }
    else
        {
            smooth = stylestr;
        }

    return *this;
}


// ----------------------------------------------------------------------------
//
// Disable screen output
//
inline Gnuplot &Gnuplot::disablescreen()
{
    cmd("set output");
    cmd("set terminal unknown");
    return *this;
}

// ----------------------------------------------------------------------------
//
// sets terminal type to windows / x11
//
inline Gnuplot &Gnuplot::showonscreen()
{
    std::string persist(" persist");
#ifdef __APPLE__
    persist = "";
#endif
    cmd("set output");
    cmd("set terminal " + Gnuplot::terminal_std + persist);

    return *this;
}


// ----------------------------------------------------------------------------
//
// saves a gnuplot session to a pdf file
//
inline Gnuplot &Gnuplot::savetopdf(const std::string &filename, unsigned int font_size)
{
    std::ostringstream cmdstr;
    cmdstr << "set term pdfcairo enhanced color font \"Times-New-Roman," + std::to_string(font_size) + "\"\n";
    cmdstr << "set output \"" << filename << ".pdf\"\n";
    cmdstr << "replot";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// saves a gnuplot session to a postscript file
//
inline Gnuplot &Gnuplot::savetops(const std::string &filename)
{
    std::ostringstream cmdstr;
    cmdstr << "set term postscript landscape enhanced color dashed \"Times-Roman\" 18\n";
    cmdstr << "set output \"" << filename << ".ps\"\n";
    cmdstr << "replot";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// Switches legend on
//
inline Gnuplot &Gnuplot::set_legend(const std::string &position)
{
    std::ostringstream cmdstr;
    cmdstr << "set key " << position;

    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// turns on log scaling for the x axis
//
inline Gnuplot &Gnuplot::set_xlogscale(const double base)
{
    std::ostringstream cmdstr;

    cmdstr << "set logscale x " << base;
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// turns on log scaling for the y axis
//
inline Gnuplot &Gnuplot::set_ylogscale(const double base)
{
    std::ostringstream cmdstr;

    cmdstr << "set logscale y " << base;
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// turns on log scaling for the z axis
//
inline Gnuplot &Gnuplot::set_zlogscale(const double base)
{
    std::ostringstream cmdstr;

    cmdstr << "set logscale z " << base;
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// scales the size of the points used in plots
//
inline Gnuplot &Gnuplot::set_pointsize(const double pointsize)
{
    std::ostringstream cmdstr;
    cmdstr << "set pointsize " << pointsize;
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// set isoline density (grid) for plotting functions as surfaces
//
inline Gnuplot &Gnuplot::set_samples(const int samples)
{
    std::ostringstream cmdstr;
    cmdstr << "set samples " << samples;
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// set isoline density (grid) for plotting functions as surfaces
//
inline Gnuplot &Gnuplot::set_isosamples(const int isolines)
{
    std::ostringstream cmdstr;
    cmdstr << "set isosamples " << isolines;
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// enables contour drawing for surfaces set contour {base | surface | both}
//
inline Gnuplot &Gnuplot::set_contour(const std::string &position)
{
    if (position.find("base") == std::string::npos &&
        position.find("surface") == std::string::npos &&
        position.find("both") == std::string::npos)
        {
            cmd("set contour base");
        }
    else
        {
            cmd("set contour " + position);
        }

    return *this;
}


// ----------------------------------------------------------------------------
//
// set labels
//
// set the xlabel
inline Gnuplot &Gnuplot::set_xlabel(const std::string &label)
{
    std::ostringstream cmdstr;

    cmdstr << "set xlabel \"" << label << "\"";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
// set the ylabel
//
inline Gnuplot &Gnuplot::set_ylabel(const std::string &label)
{
    std::ostringstream cmdstr;

    cmdstr << "set ylabel \"" << label << "\"";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
// set the zlabel
//
inline Gnuplot &Gnuplot::set_zlabel(const std::string &label)
{
    std::ostringstream cmdstr;

    cmdstr << "set zlabel \"" << label << "\"";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// set range
//
// set the xrange
inline Gnuplot &Gnuplot::set_xrange(const double iFrom,
    const double iTo)
{
    std::ostringstream cmdstr;

    cmdstr << "set xrange[" << iFrom << ":" << iTo << "]";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
// set the yrange
//
inline Gnuplot &Gnuplot::set_yrange(const double iFrom,
    const double iTo)
{
    std::ostringstream cmdstr;

    cmdstr << "set yrange[" << iFrom << ":" << iTo << "]";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
// set the zrange
//
inline Gnuplot &Gnuplot::set_zrange(const double iFrom,
    const double iTo)
{
    std::ostringstream cmdstr;

    cmdstr << "set zrange[" << iFrom << ":" << iTo << "]";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// set the palette range
//
inline Gnuplot &Gnuplot::set_cbrange(const double iFrom,
    const double iTo)
{
    std::ostringstream cmdstr;

    cmdstr << "set cbrange[" << iFrom << ":" << iTo << "]";
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plots a linear equation y=ax+b (where you supply the
// slope a and intercept b)
//
inline Gnuplot &Gnuplot::plot_slope(const double a,
    const double b,
    const std::string &title)
{
    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == true)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "plot ";
        }

    cmdstr << a << " * x + " << b << " title \"";

    if (title.empty())
        {
            cmdstr << "f(x) = " << a << " * x + " << b;
        }
    else
        {
            cmdstr << title;
        }

    cmdstr << "\" with " << pstyle;

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plot an equation supplied as a std::string y=f(x) (only f(x) expected)
//
inline Gnuplot &Gnuplot::plot_equation(const std::string &equation,
    const std::string &title)
{
    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == true)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "plot ";
        }

    cmdstr << equation << " title \"";

    if (title.empty())
        {
            cmdstr << "f(x) = " << equation;
        }
    else
        {
            cmdstr << title;
        }

    cmdstr << "\" with " << pstyle;

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// plot an equation supplied as a std::string y=(x)
//
inline Gnuplot &Gnuplot::plot_equation3d(const std::string &equation,
    const std::string &title)
{
    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == false)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "splot ";
        }

    cmdstr << equation << " title \"";

    if (title.empty())
        {
            cmdstr << "f(x,y) = " << equation;
        }
    else
        {
            cmdstr << title;
        }

    cmdstr << "\" with " << pstyle;

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plots a 2d graph from a list of doubles (x) saved in a file
//
inline Gnuplot &Gnuplot::plotfile_x(const std::string &filename,
    const unsigned int column,
    const std::string &title)
{
    //
    // check if file exists
    //
    file_available(filename);

    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == true)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "plot ";
        }

    cmdstr << "\"" << filename << "\" using " << column;

    if (title.empty())
        {
            cmdstr << " notitle ";
        }
    else
        {
            cmdstr << " title \"" << title << "\" ";
        }

    if (smooth.empty())
        {
            cmdstr << "with " << pstyle;
        }
    else
        {
            cmdstr << "smooth " << smooth;
        }

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());  // nplots++; two_dim = true;  already in cmd();

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plots a 2d graph from a list of doubles (x y) saved in a file
//
inline Gnuplot &Gnuplot::plotfile_xy(const std::string &filename,
    const unsigned int column_x,
    const unsigned int column_y,
    const std::string &title,
    const unsigned int decimate)
{
    //
    // check if file exists
    //
    file_available(filename);

    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == true)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "plot ";
        }

    cmdstr << "\"" << filename << "\" using " << column_x << ":" << column_y << " every " << std::to_string(decimate);

    if (title.empty())
        {
            cmdstr << " notitle ";
        }
    else
        {
            cmdstr << " title \"" << title << "\" ";
        }

    if (smooth.empty())
        {
            cmdstr << "with " << pstyle;
        }
    else
        {
            cmdstr << "smooth " << smooth;
        }

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plots a 2d graph with errorbars from a list of doubles (x y dy) in a file
//
inline Gnuplot &Gnuplot::plotfile_xy_err(const std::string &filename,
    const unsigned int column_x,
    const unsigned int column_y,
    const unsigned int column_dy,
    const std::string &title)
{
    //
    // check if file exists
    //
    file_available(filename);

    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == true)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "plot ";
        }

    cmdstr << "\"" << filename << "\" using "
           << column_x << ":" << column_y << ":" << column_dy
           << " with errorbars ";

    if (title.empty())
        {
            cmdstr << " notitle ";
        }
    else
        {
            cmdstr << " title \"" << title << "\" ";
        }

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// Plots a 3d graph from a list of doubles (x y z) saved in a file
//
inline Gnuplot &Gnuplot::plotfile_xyz(const std::string &filename,
    const unsigned int column_x,
    const unsigned int column_y,
    const unsigned int column_z,
    const std::string &title)
{
    //
    // check if file exists
    //
    file_available(filename);

    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == false)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "splot ";
        }

    cmdstr << "\"" << filename << "\" using " << column_x << ":" << column_y
           << ":" << column_z;

    if (title.empty())
        {
            cmdstr << " notitle with " << pstyle;
        }
    else
        {
            cmdstr << " title \"" << title << "\" with " << pstyle;
        }

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// *  note that this function is not valid for versions of GNUPlot below 4.2
//
inline Gnuplot &Gnuplot::plot_image(const unsigned char *ucPicBuf,
    const unsigned int iWidth,
    const unsigned int iHeight,
    const std::string &title)
{
    std::ofstream tmp;
    std::string name = create_tmpfile(tmp);
    if (name.empty())
        {
            return *this;
        }

    //
    // write the data to file
    //
    int iIndex = 0;
    for (unsigned int iRow = 0; iRow < iHeight; iRow++)
        {
            for (unsigned int iColumn = 0; iColumn < iWidth; iColumn++)
                {
                    tmp << iColumn << " " << iRow << " "
                        << static_cast<float>(ucPicBuf[iIndex++]) << '\n';
                }
        }

    tmp.flush();
    tmp.close();

    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    if (nplots > 0 && two_dim == true)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "plot ";
        }

    if (title.empty())
        {
            cmdstr << "\"" << name << "\" with image";
        }
    else
        {
            cmdstr << "\"" << name << "\" title \"" << title << "\" with image";
        }

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


inline Gnuplot &Gnuplot::plot_circle(double east, double north, double radius, const std::string &label)
{
    std::ostringstream cmdstr;
    //
    // command to be sent to gnuplot
    //
    cmdstr << "set object circle at " + std::to_string(east) + "," + std::to_string(north) + " size " +
                  std::to_string(radius) + " back\n";

    if (!label.empty())
        {
            double east_label = (std::cos(M_PI / 3.0) * radius) * 1.1 + east;
            double north_label = (std::sin(M_PI / 3.0) * radius) * 1.1 + north;
            cmdstr << "set label \"" + label + "\" at first " + std::to_string(east_label) +
                          ", " + std::to_string(north_label) + " norotate back nopoint offset 0,0\n";
        }
    if (nplots > 0)
        {
            cmdstr << "replot ";
        }
    else
        {
            cmdstr << "plot ";
        }

    //
    // Do the actual plot
    //
    cmd(cmdstr.str());

    return *this;
}


// ----------------------------------------------------------------------------
//
// Sends a command to an active gnuplot session
//
inline Gnuplot &Gnuplot::cmd(const std::string &cmdstr)
{
    if (!(valid))
        {
            return *this;
        }

    // int fputs ( const char * str, FILE * stream );
    // writes the string str to the stream.
    // The function begins copying from the address specified (str) until it
    // reaches the terminating null character ('\0'). This final
    // null-character is not copied to the stream.
    fputs((cmdstr + "\n").c_str(), gnucmd);

    // int fflush ( FILE * stream );
    // If the given stream was open for writing and the last i/o operation was
    // an output operation, any unwritten data in the output buffer is written
    // to the file.  If the argument is a null pointer, all open files are
    // flushed.  The stream remains open after this call.
    fflush(gnucmd);

    if (cmdstr.find("replot") != std::string::npos)
        {
            return *this;
        }
    if (cmdstr.find("splot") != std::string::npos)
        {
            two_dim = false;
            nplots++;
        }
    else if (cmdstr.find("plot") != std::string::npos)
        {
            two_dim = true;
            nplots++;
        }

    return *this;
}


// ----------------------------------------------------------------------------
//
// Opens up a gnuplot session, ready to receive commands
//
inline void Gnuplot::init()
{
// char * getenv ( const char * name );  get value of environment variable
// Retrieves a C string containing the value of the environment variable
// whose name is specified as argument.  If the requested variable is not
// part of the environment list, the function returns a NULL pointer.
#if (defined(unix) || defined(__unix) || defined(__unix__)) && !defined(__APPLE__)
    if (std::getenv("DISPLAY") == nullptr)
        {
            valid = false;
            throw GnuplotException("Can't find DISPLAY variable");
        }
#endif

    // if gnuplot not available
    if (!Gnuplot::get_program_path())
        {
            valid = false;
            throw GnuplotException("Can't find gnuplot");
        }

    //
    // open pipe
    //
    std::string tmp = Gnuplot::m_sGNUPlotPath + "/" +
                      Gnuplot::m_sGNUPlotFileName;

// FILE *popen(const char *command, const char *mode);
// The popen() function shall execute the command specified by the string
// command, create a pipe between the calling program and the executed
// command, and return a pointer to a stream that can be used to either read
// from or write to the pipe.
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    gnucmd = _popen(tmp.c_str(), "w");
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    gnucmd = popen(tmp.c_str(), "w");
#endif

    // popen() shall return a pointer to an open stream that can be used to read
    // or write to the pipe.  Otherwise, it shall return a null pointer and may
    // set errno to indicate the error.
    if (!gnucmd)
        {
            valid = false;
            throw GnuplotException("Couldn't open connection to gnuplot");
        }

    nplots = 0;
    valid = true;
    smooth = "";

    // set terminal type
    showonscreen();

    return;
}


// ----------------------------------------------------------------------------
//
// Find out if a command lives in m_sGNUPlotPath or in PATH
//
inline bool Gnuplot::get_program_path()
{
    //
    // first look in m_sGNUPlotPath for Gnuplot
    //
    std::string tmp = Gnuplot::m_sGNUPlotPath + "/" +
                      Gnuplot::m_sGNUPlotFileName;

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    if (Gnuplot::file_exists(tmp, 0))  // check existence
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    if (Gnuplot::file_exists(tmp, 1))  // check existence and execution permission
#endif
        {
            return true;
        }

    //
    // second look in PATH for Gnuplot
    //
    const char *path;
    // Retrieves a C string containing the value of environment variable PATH
    path = std::getenv("PATH");
    std::stringstream s;
    if (path != nullptr)
        {
            s << path;
        }
    if (s.fail())
        {
            throw GnuplotException("PATH is not well defined");
        }
    std::string path_str;
    path_str = s.str();

    std::list<std::string> ls;

// split path (one long string) into list ls of strings
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    stringtok(ls, path_str, ";");
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    stringtok(ls, path_str, ":");
#endif

    // scan list for Gnuplot program files
    for (std::list<std::string>::const_iterator i = ls.begin();
        i != ls.end(); ++i)
        {
            tmp = (*i) + "/" + Gnuplot::m_sGNUPlotFileName;
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
            if (Gnuplot::file_exists(tmp, 0))  // check existence
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
            if (Gnuplot::file_exists(tmp, 1))  // check existence and execution permission
#endif
                {
                    Gnuplot::m_sGNUPlotPath = *i;  // set m_sGNUPlotPath
                    return true;
                }
        }

    tmp = "Can't find gnuplot neither in PATH nor in \"" +
          Gnuplot::m_sGNUPlotPath + "\"";
    Gnuplot::m_sGNUPlotPath = "";
    throw GnuplotException(tmp);
}


// ----------------------------------------------------------------------------
//
// check if file exists
//
inline bool Gnuplot::file_exists(const std::string &filename, int mode)
{
    if (mode < 0 || mode > 7)
        {
            throw std::runtime_error(
                "In function \"Gnuplot::file_exists\": mode\
                has to be an integer between 0 and 7");
            return false;
        }

// int _access(const char *path, int mode);
//  returns 0 if the file has the given mode,
//  it returns -1 if the named file does not exist or is not accessible in
//  the given mode
// mode = 0 (F_OK) (default): checks file for existence only
// mode = 1 (X_OK): execution permission
// mode = 2 (W_OK): write permission
// mode = 4 (R_OK): read permission
// mode = 6       : read and write permission
// mode = 7       : read, write and execution permission
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    if (_access(filename.c_str(), mode) == 0)
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    if (access(filename.c_str(), mode) == 0)
#endif
        {
            return true;
        }
    return false;
}


inline bool Gnuplot::file_available(const std::string &filename)
{
    std::ostringstream except;
    if (Gnuplot::file_exists(filename, 0))  // check existence
        {
            if (!(Gnuplot::file_exists(filename, 4)))
                {  // check read permission
                    except << "No read permission for File \"" << filename << "\"";
                    throw GnuplotException(except.str());
                    return false;
                }
        }
    else
        {
            except << "File \"" << filename << "\" does not exist";
            throw GnuplotException(except.str());
            return false;
        }
    return true;
}


// ----------------------------------------------------------------------------
//
// Opens a temporary file
//
inline std::string Gnuplot::create_tmpfile(std::ofstream &tmp)
{
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    char name[] = "gnuplotiXXXXXX";  // tmp file in working directory
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    char name[] = "/tmp/gnuplotiXXXXXX";  // tmp file in /tmp
#endif

    //
    // check if maximum number of temporary files reached
    //
    if (Gnuplot::tmpfile_num == GP_MAX_TMP_FILES - 1)
        {
            std::ostringstream except;
            except << "Maximum number of temporary files reached ("
                   << GP_MAX_TMP_FILES << "): cannot open more files\n";

            throw GnuplotException(except.str());
        }

        // int mkstemp(char *name);
        // shall replace the contents of the string pointed to by "name" by a unique
        // filename, and return a file descriptor for the file open for reading and
        // writing.  Otherwise, -1 shall be returned if no suitable file could be
        // created.  The string in template should look like a filename with six
        // trailing 'X' s; mkstemp() replaces each 'X' with a character from the
        // portable filename character set.  The characters are chosen such that the
        // resulting name does not duplicate the name of an existing file at the
        // time of a call to mkstemp()

        //
        // open temporary files for output
        //

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__)
    if (_mktemp(name) == NULL)
#elif defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    mode_t mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
    if (mkstemp(name) == -1)
#endif
        {
            std::ostringstream except;
            except << "Cannot create temporary file \"" << name << "\"";
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
            umask(mask);
#endif
            throw GnuplotException(except.str());
        }
#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
    umask(mask);
#endif
    tmp.open(name);
    if (tmp.bad())
        {
            std::ostringstream except;
            except << "Cannot create temporary file \"" << name << "\"";
            throw GnuplotException(except.str());
        }

    //
    // Save the temporary filename
    //
    tmpfile_list.emplace_back(name);
    Gnuplot::tmpfile_num++;

    return name;
}


inline void Gnuplot::remove_tmpfiles()
{
    if (!(tmpfile_list).empty())
        {
            for (auto &i : tmpfile_list)
                {
                    if (remove(i.c_str()) != 0)
                        {
                            std::cout << "Problem closing files\n";
                        }
                }

            Gnuplot::tmpfile_num -= tmpfile_list.size();
        }
}


#endif
