/***************************************************************************
**
**  This file is part of figue.
**
**  figue 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 3 of the License, or
**  (at your option) any later version.
**
**  figue 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2005-03-24
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <SciFigs.h>

#include "MainWindow.h"
#include "figueVersion.h"
#include "figueInstallPath.h"

PACKAGE_INFO("figue", FIGUE)

void createCurve(GraphicSheet * sheet);
void createCurveName(GraphicSheet * sheet);
void createGrid(GraphicSheet * sheet);
void createDots(GraphicSheet * sheet);
void createCircles(GraphicSheet * sheet);
void createBands(AxisType a, GraphicSheet * sheet);
void createManyCurves(GraphicSheet * sheet);
void createText(GraphicSheet * sheet);

ApplicationHelp * help();

static bool autoScaleColorMap=true;
static bool setXPlot=false;
static bool setYPlot=false;
static double xPlot, yPlot;
static int curvesPerPlot=0; // no limit
static ExportOptions exportOptions;
static int xColumn=0;
static QList<int> yColumns, yerrColumns, nameColumns;
static DateTime xTimeRef, yTimeRef;

// Global options
static double dx0=0.0, dy0=0.0, dx=0.0, dy=0.0, maxX=0.0, maxY=0.0;
static double dxPage=0.0, dyPage=0.0, maxXPage=0.0, maxYPage=0.0;

static MainWindow * mainWindow;

void openPageFiles(int argc, char ** argv)
{
  double x=dx0, y=dy0, xPage=maxXPage, yPage=maxYPage;
  QStringList fileList=CoreApplication::instance()->getFileList(argc, argv);
  for(QStringList::iterator it=fileList.begin();it!=fileList.end();it++) {
    App::log(tr("Opening file %1\n").arg(*it) );
    mainWindow->sheet()->fileOpen( *it, x, y);
    if(maxX>0.0) {
      x+=dx;
      if(x>=maxX) {
        x=0.0;
        xPage=maxXPage;
        y+=dy;
      }
    } else if(maxY>0.0) {
      y+=dy;
      if(y>=maxY) {
        y=0.0;
        yPage=maxYPage;
        x+=dx;
      }
    } else {
      x+=dx;
      y+=dy;
    }
    if(maxXPage>0.0 && x>=xPage) {
      x=xPage+dxPage;
      xPage+=dxPage+maxXPage;
    }
    if(maxYPage>0.0 && y>=yPage) {
      y=yPage+dyPage;
      yPage+=dyPage+maxYPage;
    }
  }
}

int main(int argc, char ** argv)
{
  Application a(argc, argv, help);
  a.setConsoleMessage();

  // Options
  enum StdinMode {PageFile, Curve, CurveName, ManyCurves, Grid, Dots, Circles, Text, BandsX, BandsY, ObjectCount};
  StdinMode mode=PageFile;
  QString makeupSheetFile;
  QString deleteTag;
  yColumns << 1;
  nameColumns << 2;
  // Check arguments
  if(!exportOptions.read(argc, argv)) {
    return 2;
  }
  int i, j=1;
  for(i=1; i<argc; i++) {
    QByteArray arg=argv[i];
    if(arg[0]=='-') {
      if(arg=="-curve" || arg=="-c") {
        mode=Curve;
      } else if(arg=="-curve-name" || arg=="-cn") {
        mode=CurveName;
      } else if(arg=="-many-curves" || arg=="-mc") {
        mode=ManyCurves;
      } else if(arg=="-dots" || arg=="-d") {
        mode=Dots;
      } else if(arg=="-circles") {
        mode=Circles;
      } else if(arg=="-grid" || arg=="-g") {
        mode=Grid;
      } else if(arg=="-bands-x" || arg=="-bx") {
        mode=BandsX;
      } else if(arg=="-bands-y" || arg=="-by") {
        mode=BandsY;
      } else if(arg=="-text" || arg=="-t") {
        mode=Text;
      } else if(arg=="-x-column") {
        Application::checkOptionArg(i, argc, argv);
        int xci=atoi(argv[i]);
        if(xci>=0) {
          xColumn=xci;
        }
      } else if(arg=="-y-columns") {
        Application::checkOptionArg(i, argc, argv);
        QString ycs=argv[i];
        QStringList ycList=ycs.split(QRegExp(" *, *"));
        QString yc;
        yColumns.clear();
        foreach(yc, ycList) {
          int yci=yc.toInt();
          if(yci>=0) {
            yColumns.append(yci);
          } else {
            App::log(tr("figue: bad column index '%1' for option '-y-columns'\n").arg(yc) );
            return 2;
          }
        }
        if(yColumns.isEmpty()) {
          yColumns << 1;
        }
      } else if(arg=="-yerr-columns") {
        Application::checkOptionArg(i, argc, argv);
        QString ycs=argv[i];
        QStringList ycList=ycs.split(QRegExp(" *, *"));
        QString yc;
        yerrColumns.clear();
        foreach(yc, ycList) {
          int yci=yc.toInt();
          if(yci>=0) {
            yerrColumns.append(yci);
          } else {
            App::log(tr("figue: bad column index '%1' for option '-yerr-columns'\n").arg(yc) );
            return 2;
          }
        }
        if(yerrColumns.count()!=yColumns.count()) {
          App::log(tr("figue: '-yerr-columns' and '-y-columns' must have the same number of indexes. "
                      "Make sure that '-y-columns' appears before '-yerr-columns'."));
          return 2;
        }
      } else if(arg=="-name-columns") {
        Application::checkOptionArg(i, argc, argv);
        QString ycs=argv[i];
        QStringList ycList=ycs.split(QRegExp(" *, *"));
        QString yc;
        nameColumns.clear();
        foreach(yc, ycList) {
          int yci=yc.toInt();
          if(yci>=0) {
            nameColumns.append(yci);
          } else {
            App::log(tr("figue: bad column index '%1' for option '-name-columns'\n").arg(yc) );
            return 2;
          }
        }
        if(nameColumns.count()!=yColumns.count()) {
          App::log(tr("figue: '-name-columns' and '-y-columns' must have the same number of indexes. "
                      "Make sure that '-y-columns' appears before '-name-columns'."));
          return 2;
        }
      } else if(arg=="-makeup-sheet" || arg=="-ms") {
        Application::checkOptionArg(i, argc, argv);
        makeupSheetFile=argv[i];
      } else if(arg=="-x") {
        Application::checkOptionArg(i, argc, argv);
        xPlot=atof(argv[i]);
        setXPlot=true;
      } else if(arg=="-y") {
        Application::checkOptionArg(i, argc, argv);
        yPlot=atof(argv[i]);
        setYPlot=true;
      } else if(arg=="-cpp") {
        Application::checkOptionArg(i, argc, argv);
        curvesPerPlot=atoi(argv[i] );
      } else if(arg=="-preserve-colormap-scale") {
        autoScaleColorMap=false;
      } else if(arg=="-dx") {
        Application::checkOptionArg(i, argc, argv);
        dx=atof(argv[i] );
      } else if(arg=="-dy") {
        Application::checkOptionArg(i, argc, argv);
        dy=atof(argv[i] );
      } else if(arg=="-dx0") {
        Application::checkOptionArg(i, argc, argv);
        dx0=atof(argv[i] );
      } else if(arg=="-dy0") {
        Application::checkOptionArg(i, argc, argv);
        dy0=atof(argv[i] );
      } else if(arg=="-max-x") {
        Application::checkOptionArg(i, argc, argv);
        maxX=atof(argv[i] );
      } else if(arg=="-max-y") {
        Application::checkOptionArg(i, argc, argv);
        maxY=atof(argv[i] );
      } else if(arg=="-dx-page") {
        Application::checkOptionArg(i, argc, argv);
        dxPage=atof(argv[i] );
      } else if(arg=="-dy-page") {
        Application::checkOptionArg(i, argc, argv);
        dyPage=atof(argv[i] );
      } else if(arg=="-max-x-page") {
        Application::checkOptionArg(i, argc, argv);
        maxXPage=atof(argv[i] );
      } else if(arg=="-max-y-page") {
        Application::checkOptionArg(i, argc, argv);
        maxYPage=atof(argv[i] );
      } else if(arg=="-x-abs-time-ref") {
        Application::checkOptionArg(i, argc, argv);
        if(!xTimeRef.fromString(argv[i])) {
          return 2;
        }
      } else if(arg=="-y-abs-time-ref") {
        Application::checkOptionArg(i, argc, argv);
        if(!yTimeRef.fromString(argv[i])) {
          return 2;
        }
      } else if(arg=="-delete-type") {
        Application::checkOptionArg(i, argc, argv);
        deleteTag=argv[i];
      } else if(arg=="-object-count") {
        mode=ObjectCount;
      } else if(arg=="-debug-plugins") {
        printf("-- Testing available plugins --\n");
        SciFigsPluginSettings  * reg=new SciFigsPluginSettings;
        if(reg->hasPluginList()) {
          QStringList libs=reg->pluginList();
          delete reg;
          printf("%i plugins\n",libs.count());
          SciFigsPlugin::loadPlugins(libs, true);
        } else {
          printf("No plugins, search for new plugins...\n");
          delete reg;
          SciFigsGlobal::initPlugins(true);
        }
        return 0;
      } else if(arg=="-clear-plugins") {
        SciFigsPluginSettings reg;
        reg.clear();
        return 0;
      } else if(arg=="-add-plugin-path") {
        CoreApplication::checkOptionArg(i, argc, argv);
        SciFigsPluginSettings reg;
        QStringList paths=reg.paths();
        paths.append(argv[i]);
        reg.setPaths(paths);
        return 0;
      } else if(arg=="-plugin-paths") {
        SciFigsPluginSettings reg;
        QStringList paths=reg.paths();
        for(QStringList::iterator it=paths.begin(); it!=paths.end(); it++) {
          printf("%s\n",it->toLatin1().data());
        }
        return 0;
      } else {
        App::log(tr("figue: bad option %1, see -help\n").arg(argv[i]) );
        return 2;
      }
    } else {
      argv[j++]=argv[i];
    }
  }
  if(j<argc) {
    argv[j]=nullptr;
    argc=j;
  }

  SciFigsGlobal s;
  mainWindow=new MainWindow;
  mainWindow->setObjectName("figue");
  Settings::getRect(mainWindow, "figue");

  openPageFiles(argc, argv);


  switch (mode) {
  case PageFile:
    break;
  case ObjectCount:
    QTextStream(stdout) << mainWindow->sheet()->objects().count() << " objects" << endl;
    delete mainWindow;
    return 0;
  case Curve:
    createCurve(mainWindow->sheet());
    break;
  case ManyCurves:
    createManyCurves(mainWindow->sheet());
    break;
  case CurveName:
    if(yColumns.count()!=nameColumns.count()) {
      App::log(tr("figue: '-name-columns' and '-y-columns' must have the same number of indexes."));
      return 2;
    }
    createCurveName(mainWindow->sheet());
    break;
  case Dots:
    createDots(mainWindow->sheet());
    break;
  case Circles:
    createCircles(mainWindow->sheet());
    break;
  case Grid:
    createGrid(mainWindow->sheet());
    break;
  case BandsX: // Convention is different than in command line interface
    createBands(YAxis, mainWindow->sheet());
    break;
  case BandsY:
    createBands(XAxis, mainWindow->sheet());
    break;
  case Text:
    createText(mainWindow->sheet());
    break;
  }

  if(!makeupSheetFile.isEmpty()) {
    mainWindow->sheet()->restoreMakeUp(makeupSheetFile);
  }

  if(!deleteTag.isEmpty()) {
    mainWindow->sheet()->removeObjects(deleteTag, true);
  }

  if(!exportOptions.exportFile().isEmpty()) {
    mainWindow->sheet()->exportFile(exportOptions.exportFile(),
                                    exportOptions.exportFormat(),
                                    exportOptions.dpi());
    delete mainWindow;
    return 0;
  }

  if(!exportOptions.exportFileGraph().isEmpty()) {
    delete mainWindow;
    return 0;
  }

  // Back to gui ...
  mainWindow->sheet()->objects().show();
  a.setStream(new StandardStream(stdout));
  a.setGuiMessage();

  mainWindow->show();
  int appReturn=a.exec();
  Settings::setRect(mainWindow, "figue");
  delete mainWindow;
  return appReturn;
}

ApplicationHelp * help()
{
  TRACE;
  ApplicationHelp * h=new ApplicationHelp;
  h->setOptionSummary( "[OPTIONS] [FILE]..." );
  h->setComments( "Customize your figures saved as .page and convert into usual image format (see option --format). FILE(s) are "
                 ".page files. These files are tar.gz files containing an xml (contents.xml) and eventually some other binary files." );
  h->addGroup("Figue", "figue");
  h->addOption("-c, -curve","Reads \"x y yErr\" from stdin and plot them. "
                            "A LineLayer layer is created. If many curve are plotted, prefer option -mc. "
                            "Several curves are separated by blank lines or comments. "
                            "See '-x-column', '-y-volumns' and '-yerr-columns' to parse custom columns.");
  h->addOption("-mc, -many-curve","Reads \"x y\" from stdin and plot them. A value is set to each curve if 'value=' is found in comments "
                            "preceding the definition of the curve. A XYValueLines layer is created. Optimized for high number of "
                            "curves. Several curves are separated by blank lines or comments.");
  h->addOption("-cn, -curve-name","Reads \"x y yErr name\" from stdin and plot them. "
                            "A XYNamePlot layer is created. "
                            "Several curves are separated by blank lines or comments.");
  h->addOption("-g, -grid","Reads a grid from stdin and plot it in a grid layer. Format can be either:\n"
                           "  nx=<NX>\n"
                           "  ny=<NY>\n"
                           "  x\n"
                           "  <NX VALUES, ONE BY LINE>\n"
                           "  y\n"
                           "  <NY VALUES, ONE BY LINE>\n"
                           "  values\n"
                           "  <NX VALUES FOR Y[0], ONE BY LINE>\n"
                           "  <NX VALUES FOR Y[1], ONE BY LINE>\n"
                           "  ...\n"
                           "  <NX VALUES FOR Y[NY-1], ONE BY LINE>\n"
                           "\nor:\n"
                           "  x y val\n"
                           "  <X> <Y> <VAL>\n"
                           "  ...\n"
                           "A IrregularGrid2D layer is created."
                           "Several plots are separated by blank or comment lines.");
  h->addOption("-d, -dots","Reads \"x y value\" from stdin and draw dots at (x,y) with color for value. "
                            "A XYValuePlot layer is created. "
                            "Several plots are separated by blank lines or comments.");
  h->addOption("-circles","Reads \"x y rx [ry] [rot] [red green blue]\" from stdin and draw circles/ellipses at (x,y) with color, rotated by rot. "
                            "A CircleViewer layer is created. "
                            "Several plots are separated by blank lines or comments.");
  h->addOption("-bx, -bands-x","Reads \"min max value\" from stdin and draw bands parallel to x axis with color for value. "
                              "A ParallelBand layer is created. "
                              "Several plots are separated by blank lines or comments.");
  h->addOption("-by, -bands-y","Reads \"min max value\" from stdin and draw bands parallel to y axis with color for value. "
                              "A ParallelBand layer is created. "
                              "Several plots are separated by blank lines or comments.");
  h->addOption("-t, -text", "Create text labels from stdin with format:\n"
                                   "  X Y [space for other properties in the future]\n"
                                   "  Text first line\n"
                                   "  ...\n"
                                   "  Text last line\n"
                                   "Several plots are separated by comment lines. If a line begins with '#', replace it by '\\#':\n"
                                   "  sed \"s/^#/\\\\#/g\"");
  h->addOption("-x-column <INDEX>", "For options '-c', '-mc', and '-cn', define the X column index (default=0).");
  h->addOption("-y-columns <INDEX>", "For options '-c', '-mc', and '-cn', define the Y column indexes (default=1). "
                                     "Several values separated by comas to plot several curves with the same X values");
  h->addOption("-yerr-columns <INDEX>", "For options '-c', '-mc', and '-cn', define the YErr column index (default=none). "
                                        "The number of indexes provided must be the same as in '-y-columns'.");
  h->addOption("-name-columns <INDEX>", "For options '-c', '-mc', and '-cn', define the name column index (default=none). "
                                        "The number of indexes provided must be the same as in '-y-columns'.");
  h->addGroup("Output","output");
  exportOptions.help(h);
  h->addOption("-cpp <n>","Number of curves per plot (option -c only).");
  h->addOption("-preserve-colormap-scale","Do not automatically adjust color map scales.");
  h->addOption("-ms, -makeup-sheet <FILE>", "Modifies the aspect of the objects in sheet by format specified in makeup file (.mkup). A "
                                            "graphicSheet makeup file is expected. To get a template export it once with the gui "
                                            "from the general menu. The make-up is applied once all objects are created.");
  h->addOption("-delete-type <TYPE>", "Delete all objects of type TYPE.");
  h->addOption("-x <X>","Set x anchor of created plots to X (overrides make-up properties).");
  h->addOption("-y <Y>","Set y anchor of created plots to Y (overrides make-up properties).");
  h->addOption("-x-abs-time-ref <TIME>","Set absolute time reference for X axis. TIME must be yyyyMMddhhmmssz.\n"
                                        "Seen 'gptime -h' for details about format definition.");
  h->addOption("-y-abs-time-ref <TIME>","Set absolute time reference for Y axis. TIME must be yyyyMMddhhmmssz.\n"
                                        "Seen 'gptime -h' for details about format definition.");
  h->addGroup("Merging pages","mergePage");
  h->addOption("-dx0 <DX>", "Adds DX shift when loading the first .page file");
  h->addOption("-dy0 <DY>", "Adds DY shifts when loading the first .page file");
  h->addOption("-dx <DX>", "Adds DX shifts when loading various .page files");
  h->addOption("-dy <DY>", "Adds DY shifts when loading various .page files");
  h->addOption("-max-x <MAXX>", "Maximum value for X when loading various .page files (default=0, no maximum). "
                                "If set to a positive value, Y is incremented by DY only if X>MAXX.");
  h->addOption("-max-y <MAXY>", "Maximum value for Y when loading various .page files (default=0, no maximum). "
                                "If set to a positive value, X is incremented by DX only if Y>MAXY. "
                                "-max-x has priority over -max-y.");
  h->addOption("-max-x-page <MAXX>", "If the current x position is greater than MAXX (default=0, no maximum), "
                                     "a page break is inserted. x position is set to the sum of MAXX and DX (see '-dx-page'.");
  h->addOption("-max-y-page <MAXY>", "If the current y position is greater than MAXY (default=0, no maximum), "
                                     "a page break is inserted. y position is set to the sum of MAXY and DY (see '-dy-page'.");
  h->addOption("-dx-page <DX>", "Adds DX shifts when MAXX (set with '-max-x-page') is reached. "
                                "This option lets the introduction of page breaks when loading various .page files");
  h->addOption("-dy-page <DY>", "Adds DY shifts when MAXY (set with '-max-y-page') is reached. "
                                "This option lets the introduction of page breaks when loading various .page files");
  h->addGroup("Plugin debug","pluginDebug");
  h->addOption( "-clear-plugins","Reset to default plugin list.");
  h->addOption( "-debug-plugins","Test available plugins and exit.");
  h->addOption( "-add-plugin-path <PATH>","Add PATH to the list of paths to search for plugins.");
  h->addOption( "-plugin-paths","Lists paths to search for plugins.");
  h->addExample( "figue curve*.page -dx 9 -dy 8 -max-x 18 -max-y-page 24 -dy-page 5.7",
                 "We assume that each curve*.page contains a plot of maximum 9x8 cm. They are grouped "
                 "by 6 on a A4 page (210mm x 297mmm). After each page, the current position is moved by "
                 "9 cm on x axis. When current x position reaches 18 cm, it is reset to 0 and y is increased by "
                 "8 cm. When current y position reaches 24 cm, a page break is inserted. The current y position is "
                 "moved to 24+5.7=29.7mm. This is repeated for all pages." );
  return h;
}

void createText(GraphicSheet * sheet)
{
  TRACE;
  QString text;
  double x=0.0, y=0.0;
  bool properties=true;
  QTextStream sIn(stdin);
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!sIn.atEnd()) {
    QString line=sIn.readLine();
    if(line[0]=='\n' || line[0]=='#') {
      TextEdit * t=new TextEdit;
      t->setText(text);
      t->setPrintXAnchor(x);
      t->setPrintYAnchor(y);
      sheet->addObject(t);
      foreach(QString m,  exportOptions.makeupObjectFiles()) {
        t->restoreMakeUp(m);
      }
      t->adjustSize();
      text=QString();
      properties=true;
    } else {
      if(properties) {
        LineParser lp(line);
        bool ok=true;
        x=lp.toDouble(0, ok);
        y=lp.toDouble(1, ok);
        properties=false;
      } else {
        if(!text.isEmpty()) {
          text+="\n";
        }
        if(line.startsWith("\\#")) {
          text+=line.mid(1);
        } else {
          text+=line;
        }
      }
    }
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  if(!text.isEmpty()) {
    TextEdit * t=new TextEdit;
    t->setText(text);
    t->setPrintXAnchor(x);
    t->setPrintYAnchor(y);
    sheet->addObject(t);
    foreach(QString m,  exportOptions.makeupObjectFiles()) {
      t->restoreMakeUp(m);
    }
    t->adjustSize();
  }
}

AxisWindow * createPlot()
{
  TRACE;
  AxisWindow * w=new AxisWindow();
  w->setPrintXAnchor(1.0);
  w->setPrintYAnchor(1.0);
  for(QStringList::const_iterator it=exportOptions.prependLayerFiles().begin();
      it!=exportOptions.prependLayerFiles().end(); it++) {
    w->graphContent()->prependLayers(*it);
  }
  return w;
}

void addPlot(GraphicSheet * sheet, AxisWindow * w, int iPlot)
{
  TRACE;
  Rect r;
  w->setPrintWidth(10.0);
  w->setPrintHeight(10.0);
  w->setRemovable(true);
  sheet->addObject(w);
  for(QStringList::const_iterator it=exportOptions.appendLayerFiles().begin();
      it!=exportOptions.appendLayerFiles().end(); it++) {
    w->graphContent()->appendLayers(*it);
  }
  w->setPrintXAnchor((iPlot%2)* w->printWidth()+1.0);
  w->setPrintYAnchor((iPlot/2)*w->printHeight()+1.0);
  GraphContent * gc=w->graphContent();
  r=gc->boundingRect();
  r.enlarge(0.05, gc->scaleX().sampling(), gc->scaleY().sampling());
  w->xAxis()->setRange(r.x1(), r.x2());
  w->yAxis()->setRange(r.y1(), r.y2());
  if(setXPlot) {
    w->setPrintXAnchor(xPlot);
  }
  if(setYPlot) {
    w->setPrintYAnchor(yPlot);
  }
  if(xTimeRef.isValid()) {
    w->xAxis()->setTimeReference(xTimeRef);
    w->xAxis()->setScaleType(Scale::AbsoluteTime);
  }
  if(yTimeRef.isValid()) {
    w->yAxis()->setTimeReference(xTimeRef);
    w->yAxis()->setScaleType(Scale::AbsoluteTime);
  }
  foreach(QString m,  exportOptions.makeupObjectFiles()) {
    w->restoreMakeUp(m);
  }
  sheet->moveObject(w);
  if(!exportOptions.exportFileGraph().isEmpty()) {
    w->exportFile(exportOptions.exportFileGraph(iPlot),
                  exportOptions.exportFormat(),
                  exportOptions.dpi());
  }
}

LineLayer * createCurvePlot(Legend * legend)
{
  TRACE;
  AxisWindow * w=createPlot();
  LineLayer * plot=new LineLayer(w);
  plot->setReferenceLine(new PlotLine);
  int n=yColumns.count();
  for(int i=0; i<n; i++) {
    AbstractLine * line=plot->addLine();
    if(legend) {
      line->setPen(legend->pen(i));
      line->setSymbol(legend->symbol(i));
    }
  }
  return plot;
}

void createCurve(GraphicSheet * sheet)
{
  TRACE;
  Legend * legend;
  if(exportOptions.legendFile().isEmpty()) {
    legend=nullptr;
  } else {
    legend=new Legend;
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load legend"));
    xmler.setFileName(exportOptions.legendFile());
    XMLSciFigs s;
    if(!xmler.exec(s.restoreFile(exportOptions.legendFile(), legend, XMLSciFigs::Data))) {
      delete legend;
      legend=nullptr;
    }
  }
  // Create plot and a first curve
  LineLayer * plot=createCurvePlot(legend);
  AxisWindow * w=plot->graph();
  Curve<Point> ** curve=new Curve<Point> *[yColumns.count()];
  for(int i=yColumns.count()-1; i>=0; i--) {
    curve[i]=&static_cast<PlotLine *>(plot->line(i))->curve();
  }
  int nColumns=yColumns.count();
  int iCurve=nColumns;
  int iPlot=0;

  // Read stdin for curves until encountering a blank line
  int n=256;
  char * buf=new char[n];
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!feof(stdin)) {
    File::readLine(buf, n, stdin);
    if(buf[0]=='\n' || buf[0]=='#') {
      if(!curve[0]->isEmpty()) {
        if(curvesPerPlot>0 && iCurve>=curvesPerPlot) {
          addPlot(sheet, w, iPlot);
          iPlot++;
          iCurve=nColumns;
          plot=createCurvePlot(legend);
          w=plot->graph();
          for(int i=yColumns.count()-1; i>=0; i--) {
            curve[i]=&static_cast<PlotLine *>(plot->line(i))->curve();
          }
        } else {
          for(int i=0; i<nColumns; i++) {
            AbstractLine * line=plot->addLine();
            if(legend) {
              line->setPen(legend->pen(iCurve+i));
              line->setSymbol(legend->symbol(iCurve+i));
            }
            curve[i]=&static_cast<PlotLine *>(plot->line(iCurve+i))->curve();
          }
          iCurve+=nColumns;
        }
      }
    } else {
      QString str(buf);
      if(!str.isEmpty()) {
        bool ok=true;
        LineParser p(str);
        for(int i=0; i<nColumns; i++) {
          Point cp;
          cp.setX(p.toDouble(xColumn, ok));
          cp.setY(p.toDouble(yColumns.at(i), ok));
          if(!yerrColumns.isEmpty()) {
            cp.setZ(p.toDouble(yerrColumns.at(i), ok));
          }
          curve[i]->append(cp);
        }
      }
    }
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  delete [] buf;
  delete legend;
  delete [] curve;
  addPlot(sheet, w, iPlot);
}

NameLineLayer * createCurveNamePlot(Legend * legend)
{
  TRACE;
  AxisWindow * w=createPlot();
  NameLineLayer * plot=new NameLineLayer(w);
  plot->setReferenceLine(new NameLine);
  int n=yColumns.count();
  for(int i=0; i<n; i++) {
    AbstractLine * line=plot->addLine();
    if(legend) {
      line->setPen(legend->pen(0) );
      line->setSymbol(legend->symbol(0) );
    }
  }
  return plot;
}

void createCurveName(GraphicSheet * sheet)
{
  TRACE;
  Legend * legend;
  if(exportOptions.legendFile().isEmpty()) {
    legend=nullptr;
  } else {
    legend=new Legend;
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load legend"));
    xmler.setFileName(exportOptions.legendFile());
    XMLSciFigs s;
    if(!xmler.exec(s.restoreFile(exportOptions.legendFile(), legend, XMLSciFigs::Data))) {
      delete legend;
      legend=nullptr;
    }
  }
  // Create plot and a first curve
  NameLineLayer * plot=createCurveNamePlot(legend);
  AxisWindow * w=plot->graph();
  Curve<NamedPoint> ** curve=new Curve<NamedPoint> *[yColumns.count()];
  for(int i=yColumns.count()-1; i>=0; i--) {
    curve[i]=&static_cast<NameLine *>(plot->line(i))->curve();
  }
  int nColumns=yColumns.count();
  int iCurve=1;
  int iPlot=0;

  // Read stdin for curves until encountering a blank line
  int n=256;
  char * buf=new char[n];
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!feof(stdin)) {
    File::readLine(buf, n, stdin);
    if(buf[0]=='\n' || buf[0]=='#') {
      if(!curve[0]->isEmpty()) {
        if(curvesPerPlot>0 && iCurve>=curvesPerPlot) {
          addPlot(sheet, w, iPlot);
          iPlot++;
          iCurve=nColumns;
          plot= createCurveNamePlot(legend);
          w=plot->graph();
          for(int i=yColumns.count()-1; i>=0; i--) {
            curve[i]=&static_cast<NameLine *>(plot->line(i))->curve();
          }
        } else {
          for(int i=0; i<nColumns; i++) {
            AbstractLine * line=plot->addLine();
            if(legend && iCurve<legend->count()) {
              line->setPen(legend->pen(iCurve) );
              line->setSymbol(legend->symbol(iCurve) );
            }
            curve[i]=&static_cast<NameLine *>(plot->line(iCurve+i))->curve();
          }
          iCurve+=nColumns;
        }
      }
    } else {
      QString str(buf);
      if(!str.isEmpty()) {
        bool ok=true;
        LineParser p(str);
        for(int i=0; i<nColumns; i++) {
          NamedPoint cp;
          cp.setX(p.toDouble(xColumn, ok));
          cp.setY(p.toDouble(yColumns.at(i), ok));
          if(!yerrColumns.isEmpty()) {
            cp.setZ(p.toDouble(yerrColumns.at(i), ok));
          }
          cp.setName(p.toString(nameColumns.at(i), ok));
          curve[i]->append(cp);
        }
      }
    }
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  delete [] buf;
  delete legend;
  delete [] curve;
  addPlot(sheet, w, iPlot);
}

IrregularGrid2DPlot * createGridPlot(ColorMap * pal)
{
  TRACE;
  AxisWindow * w=createPlot();
  IrregularGrid2DPlot * plot=new IrregularGrid2DPlot(w);
  if(pal) {
    plot->setColorMap(*pal);
  }
  return plot;
}

void createGrid(GraphicSheet * sheet)
{
  TRACE;
  ColorMap * pal;
  if(exportOptions.colorMapFile().isEmpty()) {
    pal=nullptr;
  } else {
    pal=new ColorMap;
    pal->generateColorScale(20, ColorPalette::Hsv);
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load palette"));
    xmler.setFileName(exportOptions.colorMapFile());
    XMLSciFigs s;
    if(!xmler.exec(s.restoreFile(exportOptions.colorMapFile(), pal, XMLSciFigs::Data))) {
      delete pal;
      pal=nullptr;
    }
  }
  IrregularGrid2DPlot * plot;
  AxisWindow * w;
  IrregularGrid2D grid;
  int iPlot=0;

  // Read stdin for curves until encountering a blank line
  QTextStream s(stdin);
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!s.atEnd()) {
    s >> grid;
    if(grid.nx()>0 && grid.ny()>0) {
      plot=createGridPlot(pal);
      w=plot->graph();
      plot->setGrid(grid);
      if(autoScaleColorMap) {
        plot->setLinearColorMap();
      }
      addPlot(sheet, w, iPlot);
      iPlot++;
    } else break;
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  delete pal;
}

XYValuePlot * createDotsPlot(ColorMap * pal)
{
  TRACE;
  AxisWindow * w=new AxisWindow;
  w->setPrintXAnchor(1.0);
  w->setPrintYAnchor(1.0);
  XYValuePlot * plot=new XYValuePlot(w);
  if(pal) {
    plot->setColorMap(*pal);
  }
  return plot;
}

// Temporary storage before sorting curve by decreasing value
class ValueDot : public Point2D
{
public:
  ValueDot() {_value=0.0;}
  ValueDot(const ValueDot& o) : Point2D(o) {_value=o._value;}

  bool operator<(const ValueDot& o) const {
    return _value>o._value;
  }
  void setValue(double v) {_value=v;}
  double value() const {return _value;}
private:
  double _value;
};

void addDotPlot(QVector<ValueDot>& dots, XYValuePlot * plot, int iPlot,
                 GraphicSheet * sheet)
{
  std::sort(dots.begin(), dots.end());
  AxisWindow * w=plot->graph();
  QVector<double> * x=new QVector<double>;
  QVector<double> * y=new QVector<double>;
  QVector<double> * val=new QVector<double>;
  for(QVector<ValueDot>::iterator it=dots.begin(); it!=dots.end(); it++) {
    ValueDot& d=*it;
    val->append(d.value());
    x->append(d.x());
    y->append(d.y());
  }
  plot->setXData(x);
  plot->setYData(y);
  plot->setValues(val);
  if(autoScaleColorMap) {
    double min=std::numeric_limits<double>::infinity(), max=-std::numeric_limits<double>::infinity();
    plot->valueRange(min, max);
    plot->setLinearColorMap(min, max);
  }
  addPlot(sheet, w, iPlot);
}

void createDots(GraphicSheet * sheet)
{
  TRACE;
  ColorMap * pal;
  if(exportOptions.colorMapFile().isEmpty()) {
    pal=nullptr;
  } else {
    pal=new ColorMap;
    pal->generateColorScale(20, ColorPalette::Hsv);
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load palette"));
    xmler.setFileName(exportOptions.colorMapFile());
    XMLSciFigs s;
    if(!xmler.exec(s.restoreFile(exportOptions.colorMapFile(), pal, XMLSciFigs::Data))) {
      delete pal;
      pal=nullptr;
    }
  }
  // Create plot
  XYValuePlot * plot=createDotsPlot(pal);
  QVector<ValueDot> dots;
  int iPlot=0;
  // Read stdin for dots until encountering a blank line
  int n=256;
  char * buf=new char[n];
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!feof(stdin)) {
    File::readLine(buf, n, stdin);
    if(buf[0]=='\n' || buf[0]=='#') {
      if(!dots.isEmpty()) {
        addDotPlot(dots, plot, iPlot, sheet);
        iPlot++;
        plot=createDotsPlot(pal);
        dots.clear();
      }
    } else {
      QString str(buf);
      StringSection content(str);
      dots.append(ValueDot());
      ValueDot& d=dots.last();
      const QChar * ptr=nullptr;
      StringSection f;
      f=content.nextField(ptr);
      if(f.isValid()) {
        d.setX(f.toDouble());
      } else {
        dots.erase(dots.end()-1);
        break;
      }
      f=content.nextField(ptr);
      if(f.isValid()) {
        d.setY(f.toDouble());
      } else {
        dots.erase(dots.end()-1);
        break;
      }
      f=content.nextField(ptr);
      if(f.isValid()) d.setValue(f.toDouble());
    }
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  delete [] buf;
  if(!dots.isEmpty()) {
    addDotPlot(dots, plot, iPlot, sheet);
  } else {
    delete plot->graph();
  }
  delete pal;
}

CircleViewer * createCirclePlot()
{
  TRACE;
  AxisWindow * w=createPlot();
  CircleViewer * plot=new CircleViewer(w);
  return plot;
}

void createCircles(GraphicSheet * sheet)
{
  TRACE;
  // Create plot
  CircleViewer * plot=createCirclePlot();
  int iPlot=0;
  // Read stdin for bands until encountering a blank line
  int n=256;
  char * buf=new char[n];
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!feof(stdin)) {
    File::readLine(buf, n, stdin);
    if(buf[0]=='\n' || buf[0]=='#') {
      if(plot->count()>0) {
        AxisWindow * w=plot->graph();
        addPlot(sheet, w, iPlot);
        iPlot++;
        plot=createCirclePlot();
      }
    } else {
      plot->add(buf);
    }
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  delete [] buf;
  if(plot->count()>0) {
    AxisWindow * w=plot->graph();
    addPlot(sheet, w, iPlot);
    sheet->moveObject(w);
  } else {
    delete plot->graph();
  }
  sheet->autoResizeContent();
}

XYValueLines * createManyCurvesPlot(ColorMap * pal)
{
  TRACE;
  AxisWindow * w=createPlot();
  XYValueLines * plot=new XYValueLines(w, true);
  if(pal) {
    plot->setColorMap(*pal);
  }
  return plot;
}

// Temporary storage before sorting curve by decreasing value
class ValueCurve : public QVector<Point2D>
{
public:
  ValueCurve() {_value=0.0;}
  ValueCurve(const ValueCurve& o) : QVector<Point2D>(o) {_value=o._value;}

  bool operator<(const ValueCurve& o) const {
    return _value>o._value;
  }
  void setValue(double v) {_value=v;}
  double value() const {return _value;}
private:
  double _value;
};

void addManyCurvePlot(QVector<ValueCurve>& curves, XYValueLines * plot, int iPlot,
                      GraphicSheet * sheet, ConsoleProgress * progress)
{
   if(progress) {
    progress->setCaption("Sorting curves... ");
  }
  std::sort(curves.begin(), curves.end());
  if(progress) {
    progress->end();
    progress->setCaption("Definitive storage... ");
    progress->setMaximum(curves.count());
  }
  QVector<int> pointCounts;
  QVector<Point2D> points;
  QVector<double> val;
  int n=curves.count();
  // Calculate capacity for points
  int totalCount=0;
  for(int i=0; i<n; i++ ) {
    const ValueCurve& c=curves.at(i);
    val.append(c.value());
    pointCounts.append(c.count());
    totalCount += c.count();
  }
  // Low level transfer for maximum speed. Using QVector::operator+= was too slow
  points.resize(totalCount);
  Point2D * destPtr=points.data();
  for(int i=0; i<n; i++) {
    const ValueCurve& c=curves.at(i);
    const Point2D * srcPtr=c.data();
    memcpy(destPtr, srcPtr, c.count()*sizeof(Point2D));
    destPtr+=c.count();
    if(progress) progress->setValue(i);
  }
  plot->setPointCount(points.count(), pointCounts);
  plot->setPoints(points);
  plot->setValues(val);
  if(progress) progress->end(n);
  if(autoScaleColorMap) {
    double min=std::numeric_limits<double>::infinity(), max=-std::numeric_limits<double>::infinity();
    plot->valueRange(min, max);
    plot->setLinearColorMap(min, max);
  }
  AxisWindow * w=plot->graph();
  addPlot(sheet, w, iPlot);
}

void createManyCurves(GraphicSheet * sheet)
{
  TRACE;
  ColorMap * pal;
  if(exportOptions.colorMapFile().isEmpty()) {
    pal=nullptr;
  } else {
    pal=new ColorMap;
    pal->generateColorScale(20, ColorPalette::Hsv);
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load palette"));
    xmler.setFileName(exportOptions.colorMapFile());
    XMLSciFigs s;
    if(!xmler.exec(s.restoreFile(exportOptions.colorMapFile(), pal, XMLSciFigs::Data))) {
      delete pal;
      pal=nullptr;
    }
  }
  // Create plot
  XYValueLines * plot=createManyCurvesPlot(pal);
  QVector<ValueCurve> curves(1);
  int iPlot=0;
  // Read stdin for points until encountering a blank line or comment
  ConsoleProgress progress;
  progress.setCaption("Loading curves... ");
  progress.setMaximum(0);
  QTextStream sIn(stdin);
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!sIn.atEnd()) {
    QString str=sIn.readLine();
    if(str[0]=='\n' || str[0]=='#') {
      if(curves.last().count()>0) {
        if(curves.count()==curvesPerPlot) {
          addManyCurvePlot(curves, plot, iPlot, sheet, &progress);
          iPlot++;
          curves.clear();
          plot=createManyCurvesPlot(pal);
        }
        curves.append(ValueCurve());
        progress.increaseValue(1);
      }
      int index=str.indexOf("value=");
      if(index>=0) {
        bool ok;
        double v=str.mid(index+6).toDouble(&ok);
        if(ok) curves.last().setValue(v);
      }
    } else {
      StringSection content(str);
      const QChar * ptr=nullptr;
      StringSection f;
      f=content.nextField(ptr);
      double x, y;
      if(f.isValid()) x=f.toDouble(); else break;
      f=content.nextField(ptr);
      if(f.isValid()) y=f.toDouble(); else break;
      curves.last().append(Point2D(x,y));
    }
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  progress.end();
  if(curves.last().count()>0) {
    addManyCurvePlot(curves, plot, iPlot, sheet, &progress);
  } else {
    delete plot->graph();
  }
  delete pal;
  sheet->autoResizeContent();
}

ParallelBands * createBandsPlot()
{
  TRACE;
  AxisWindow * w=createPlot();
  ParallelBands * plot=new ParallelBands(w);
  return plot;
}

void createBands(AxisType a, GraphicSheet * sheet)
{
  TRACE;
  ColorMap * map;
  if(exportOptions.colorMapFile().isEmpty()) {
    map=nullptr;
  } else {
    map=new ColorMap;
    map->generateColorScale(20, ColorPalette::Hsv);
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load palette"));
    xmler.setFileName(exportOptions.colorMapFile());
    XMLSciFigs s;
    if(!xmler.exec(s.restoreFile(exportOptions.colorMapFile(), map, XMLSciFigs::Data))) {
      delete map;
      map=nullptr;
    }
  }
  // Create plot
  ParallelBands * plot=createBandsPlot();
  plot->setAxisType(a);
  int iPlot=0;
  // Read stdin for bands until encountering a blank line
  int n=256;
  char * buf=new char[n];
  CoreApplication::instance()->debugUserInterrupts(false);
  while(!feof(stdin)) {
    File::readLine(buf, n, stdin);
    if(buf[0]=='\n' || buf[0]=='#') {
      if(plot->count()>0) {
        AxisWindow * w=plot->graph();
        addPlot(sheet, w, iPlot);
        iPlot++;
        plot=createBandsPlot();
      }
    } else {
      QString str(buf);
      StringSection content(str);
      const QChar * ptr=nullptr;
      StringSection f;
      f=content.nextField(ptr);
      double min, max;
      Brush b(Qt::SolidPattern);
      if(f.isValid()) {
        min=f.toDouble();
      } else {
        continue;
      }
      f=content.nextField(ptr);
      if(f.isValid()) {
        max=f.toDouble();
      } else {
        continue;
      }
      if(map) {
        f=content.nextField(ptr);
        if(f.isValid()) {
          QColor col;
          guiColor(map->color(f.toDouble()), col);
          b.setColor(col);
        }
      }
      plot->addBand(min, max, b);
    }
  }
  CoreApplication::instance()->debugUserInterrupts(true);
  delete [] buf;
  if(plot->count()>0) {
    AxisWindow * w=plot->graph();
    addPlot(sheet, w, iPlot);
    sheet->moveObject(w);
  } else {
    delete plot->graph();
  }
  delete map;
  sheet->autoResizeContent();
}
