// --------------------------------------------------------------------
// Loading and saving the Ipe document
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe 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 Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipedoc.h"
#include "ipepage.h"

#include "ipeversion.h"
#include "ipeq.h"

#include "appui.h"

#include <qdatetime.h>
#include <qfiledialog.h>
#include <qmessagebox.h>
#include <qstatusbar.h>

// --------------------------------------------------------------------
// New document, load, and save
// --------------------------------------------------------------------

//! Slot to create new document.
void AppUi::NewDoc()
{
  if (iDoc && iDoc->IsEdited()) {
    switch (QMessageBox::information
	    (this, "Ipe",
	     tr("The document has been changed since the last save."),
	     tr("Save now"), tr("Cancel"), tr("Discard old document"),
	     0, 1 )) {
    case 0:
      Save();
    case 2:
      break;
    case 1:
    default: // just for sanity
      return;
    }
  }
  delete iDoc;

  IpePreferences *prefs = IpePreferences::Static();

  // reset grid size to default
  iSnapData.iGridSize = prefs->iGridSize;

  iDoc = new IpeDocument;  // loads "standard" sheet

  if (!prefs->iStyleSheet.isNull()) {
    if (!IpeModel::AddStyleSheet(iDoc, prefs->iStyleSheet))
      ErrMsg(tr("Could not load or parse style sheet '%1', not added.")
	     .arg(prefs->iStyleSheet));
  }
  if (!prefs->iStyleSheet2.isNull()) {
    if (!IpeModel::AddStyleSheet(iDoc, prefs->iStyleSheet2))
      ErrMsg(tr("Could not load or parse style sheet '%1', not added.")
	     .arg(prefs->iStyleSheet2));
  }

  iDoc->push_back(iDoc->NewPage(iSnapData.iGridSize));
  ShowStyleInUi();

  IpeDocument::SProperties props = iDoc->Properties();
  // set media size
  props.iMedia = IpeRect(IpeVector::Zero, prefs->iMedia);
  props.iCropBox = true;
  iDoc->SetProperties(props);
  iDoc->SetEdited(false);
  iUndo.Clear();
  iFileName = QString::null;
  iPno = 0;
  PageChanged();
  iNeedLatex = false;
  DefaultZoom();
  statusBar()->message(tr("New document"));
}

static IpeDocument::TFormat FormatFromFileName(QString fn)
{
  if (fn.length() < 5)
    return IpeDocument::EUnknown;
  fn = fn.lower();
  QString s = fn.right(4);
  if (s == ".xml")
    return IpeDocument::EXml;
  else if (s == ".pdf")
    return IpeDocument::EPdf;
  else if (fn.right(3) == ".ps")
    return IpeDocument::EPs;
  else if (s == ".eps")
    return IpeDocument::EEps;
  else if (s == ".ipe" || fn.right(5) == ".mipe")
    return IpeDocument::EIpe5;
  return IpeDocument::EUnknown;
}

//! Save document
bool AppUi::Save(QString fn, bool warnOverwrite)
{
  IpeDocument::TFormat fm = FormatFromFileName(fn);
  if (fm == IpeDocument::EUnknown) {
    ErrMsg(tr("You must save as *.xml, *.pdf, *.eps, or *.ps\n"
	      "File not saved."));
    return false;
  }

  if (fm == IpeDocument::EEps && iDoc->TotalViews() > 1) {
    ErrMsg(tr("Only single-page documents can be saved in EPS format."));
    return false;
  }

  // make sure latex has been run if format is not XML
  if (fm != IpeDocument::EXml && !RunLatex(0)) {
    statusBar()->message(tr("Latex error - not saved"));
    return false;
  }

  if ((fm == IpeDocument::EEps || fm == IpeDocument::EPs) &&
      iDoc->HasTrueTypeFonts()) {
    ErrMsg(tr("The document contains TrueType fonts. "
	      "Ipe cannot currently save these in Postscript format. "
	      "You can save as PDF, and use pdftops to create a "
	      "Postscript file."));
    return false;
  }

  // check whether file exists
  if (warnOverwrite && QFile::exists(fn)) {
    if (QMessageBox::information
	(0, "Ipe", tr("The file '%1' exists already.\n"
		      "Do you wish to overwrite it?").arg(fn),
	 tr("Overwrite"), tr("Cancel save"), 0, 1) == 1) {
      statusBar()->message(tr("Not saved"));
      return false;
    }
  }
  IpeDocument::SProperties props = iDoc->Properties();
  QDateTime dt = QDateTime::currentDateTime();
  QString mod;
  mod.sprintf("D:%04d%02d%02d%02d%02d%02d",
	      dt.date().year(), dt.date().month(), dt.date().day(),
	      dt.time().hour(), dt.time().minute(), dt.time().second());
  props.iModified = IpeQ(mod);
  if (props.iCreated.empty())
    props.iCreated = props.iModified;
  iDoc->SetProperties(props);
  if (!IpeModel::Save(iDoc, fn, IPE_VERSION, fm)) {
    ErrMsg(tr("Error saving the document.\nFile is not saved."));
    return false;
  }
  iDoc->SetEdited(false);
  statusBar()->message(tr("Saved document '%1'").arg(fn));
  iFileName = fn;
  SetCaption();
  return true;
}

static QString CheckAlternativeSave(const QString &fn)
{
  QString other;
  QFileInfo fi(fn);
  if (fi.extension().lower() == "pdf")
    other = "eps";
  else if (fi.extension().lower() == "eps")
    other = "pdf";
  else
    return QString::null;

  QString ofn = fn.left(fn.length() - 3) + other;
  QFileInfo ofi(ofn);
  if (!ofi.exists())
    return QString::null;

  if (QMessageBox::information
      (0, "Ipe", QObject::tr("<qt>You have saved as '%1'. "
			     "Would you also like to save as '%2'?</qt>").
       arg(fn).arg(ofn),
       QObject::tr("Yes"), QObject::tr("No"), 0, 1) == 0) {
    return ofn;
  }
  return QString::null;
}

//! Slot to save current document.
bool AppUi::Save()
{
  if (iFileName.isNull()) {
    return SaveAs();
  } else {
    bool ret = Save(iFileName, false);
    if (ret) {
      QString s = CheckAlternativeSave(iFileName);
      if (!s.isNull())
	ret = Save(s, false);
    }
    return ret;
  }
}

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

class IpeSaveFileDialog : public QFileDialog {
public:
  IpeSaveFileDialog(const QString &dir, const QString &filter,
		    QWidget *parent);
};

IpeSaveFileDialog::IpeSaveFileDialog(const QString &dir,
				     const QString &filter,
				     QWidget *parent)
  : QFileDialog(dir,
		tr("PDF files (*.pdf);;EPS files (*.eps);;"
		   "Postscript files (*.ps);;XML files (*.xml)"),
		parent, "saveAsDlg", TRUE)
{
  setMode(QFileDialog::AnyFile);
  if (!filter.isNull())
    addFilter(filter);
}

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

//! Slot to save current document under a fresh name.
bool AppUi::SaveAs()
{
  IpePreferences *prefs = IpePreferences::Static();
  IpeSaveFileDialog dlg(prefs->iDialogDir, iSaveFilter, this);
  if (dlg.exec() == QDialog::Accepted) {
    QString fn = dlg.selectedFile();
    iSaveFilter = dlg.selectedFilter();
    int i = iSaveFilter.findRev(".");
    assert(i >= 0);
    QString ext = iSaveFilter.mid(i+1, iSaveFilter.length() - i - 2);
    ipeDebug("Selected name: %s Filter: %s", fn.latin1(), ext.latin1());
    QFileInfo fi(fn);
    if (fi.extension().isEmpty())
      fn += "." + ext;
    ipeDebug("Saving as: %s", fn.latin1());
    return Save(fn, true);
  }
  statusBar()->message(tr("Not saved"));
  return false;
}

//! Set window caption to reflect filename and page.
void AppUi::SetCaption()
{
  QString s = "Ipe ";
  if (!iFileName.isNull())
    s += "\"" + iFileName + "\" ";
  if (iDoc->IsEdited())
    s += "[Unsaved] ";
  if (Page()->CountViews() > 1) {
    s += tr("(View %1 of %2 on page %3 of %4) ")
      .arg(iVno + 1).arg(Page()->Views().size())
      .arg(iPno + 1).arg(iDoc->size());
  } else if (iDoc->size() > 1)
    s += tr("(Page %1 of %2) ").arg(iPno + 1).arg(iDoc->size());
  setCaption(s);
}

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

static void CheckAlternative(QString &fn)
{
  QString other;
  QFileInfo fi(fn);
  if (fi.extension().lower() == "pdf")
    other = "eps";
  else if (fi.extension().lower() == "eps")
    other = "pdf";
  else
    return;

  QString ofn = fn.left(fn.length() - 3) + other;
  QFileInfo ofi(ofn);
  if (!ofi.exists() || fi.lastModified() > ofi.lastModified())
    return;

  if (QMessageBox::information
      (0, "Ipe", QObject::tr("<qt>You are trying to open the file '%1', "
			     "while '%2' appears to be more recent."
			     "<br/>Open '%3' instead?</qt>").
       arg(fn).arg(ofn).arg(ofn),
       QObject::tr("Yes"), QObject::tr("No"), 0, 1) == 0) {
    fn = ofn;
  }
}


//! Load Ipe document from \c fileName.
/*! Will discard the current document, even if IpeDocument::IsEdited
  is true! */
bool AppUi::Load(QString fileName)
{
  QFile f(fileName);
  if (!f.open(IO_ReadOnly)) {
    ErrMsg(tr("I cannot read this file."));
    return false;
  }
  XmlQSource source(&f);
  IpeDocument::TFormat fm = IpeDocument::FileFormat(source);
  f.close();

  if (fm == IpeDocument::EUnknown) {
    ErrMsg(tr("I don't know the format of this file."));
    return false;
  }

  if (fm == IpeDocument::EIpe5) {
    QFileInfo finfo(fileName);
    QString iml = finfo.dir().filePath(finfo.baseName() + ".xml");
    if (QMessageBox::information
	(0, "Ipe",
	 tr("<qt>You have selected to open the file"
	    "<blockquote><font color=\"red\">'%1'</font></blockquote>"
	    "This file was created by an ancient version of Ipe, and "
	    "I cannot open it directly.<br>"
	    "I will therefore convert it to a new file"
	    "<blockquote><font color=\"red\">'%2'</font></blockquote>"
	    "in Ipe 6.0 format.<br>"
	    "The conversion preserves all drawing information in the file. "
	    "You may only need to check the <em>Latex preamble</em> and "
	    "'strange' text objects.</qt>").arg(fileName).arg(iml),
	 tr("Convert file"), tr("Cancel load"), 0, 0, 1) == 1)
      return false;
    // check whether XML file exists
    if (QFile::exists(iml)) {
      if (QMessageBox::information
	  (0, "Ipe", tr("The file '%1' exists already.\n"
			"Do you wish to overwrite it?").arg(iml),
	   tr("Overwrite"), tr("Cancel conversion"), 0, 1) == 1) {
	statusBar()->message(tr("Conversion and loading canceled"));
	return false;
      }
    }
    QDir dir = IpePreferences::Static()->iBinDir;
#ifdef WIN32
    QString app = dir.filePath("ipe5toxml.exe");
    QCString cl =
      "call \"" + QFile::encodeName(QDir::convertSeparators(app)) + "\" \"" +
      QFile::encodeName(QDir::convertSeparators(fileName)) + "\" \"" +
      QFile::encodeName(QDir::convertSeparators(iml)) + "\"";
    qDebug("system('%s')", cl.data());
    system(cl.data());
#else
    QString app = dir.filePath("ipe5toxml");
    QCString cl = QFile::encodeName(app) + " " +
      QFile::encodeName(fileName) + " " + QFile::encodeName(iml);
    system(cl.data());
#endif
    fileName = iml;
  }

  CheckAlternative(fileName);

  IpeDocument *oldDoc = iDoc;
  int reason;
  iDoc = IpeModel::New(fileName, reason);
  if (!iDoc) {
    // need to reestablish document before showing the popup
    iDoc = oldDoc;
    if (reason >= 0)
      ErrMsg(tr("Error parsing document '%1' at character offset %2")
	     .arg(fileName).arg(reason));
    else if (reason < -60000)
      ErrMsg(tr("Document '%1' has been created by a version of Ipe "
		"more recent than the one you are running. "
		"Please upgrade Ipe at <tt>ipe.compgeom.org</tt>")
	     .arg(fileName));
    else
      ErrMsg(tr("Error loading document '%1' (reason %2)")
	     .arg(fileName).arg(reason));
    return false;
  }

  // for the moment, need to clear undo stack first because of bitmaps
  iUndo.Clear();
  delete oldDoc;

  ShowStyleInUi(); // why does this set the edited flag??
  iDoc->SetEdited(false);

  iFileName = fileName;
  iPno = 0;
  PageChanged();
  iNeedLatex = true;
  statusBar()->message(tr("Loaded document '%1'").arg(iFileName));

  ipeDebug("loaded");
  IpeAttributeSeq seq;
  if (!iDoc->CheckStyle(seq)) {
    IpeString syms;
    for (IpeAttributeSeq::iterator it = seq.begin(); it != seq.end(); ++it)
      syms += iDoc->Repository()->String(*it) + ", ";
    ErrMsg(tr("The document you loaded contains the following symbolic "
	      "attributes that are not defined in the style sheet:<br>")
	   + QIpe(syms));
  }
  ipeDebug("/Load");
  return true;
}

//! Slot to load document.
void AppUi::Load()
{
  if (iDoc && iDoc->IsEdited()) {
    switch (QMessageBox::information
	    (this, "Ipe",
	     tr("The document has been changed since the last save."),
	     tr("Save now"), tr("Cancel load"), tr("Load anyway"),
	     0, 1 )) {
    case 0:
      Save();
    case 2:
      break;
      break;
    case 1:
    default: // just for sanity
      statusBar()->message(tr("Loading canceled"));
      return;
    }
  }
  IpePreferences *prefs = IpePreferences::Static();
  QString fn = QFileDialog::getOpenFileName
    (prefs->iDialogDir,
     tr("Ipe files (*.xml *.pdf *.ipe *.mipe *.ps *.eps);;All files (*.*)"),
     this);
  if (!fn.isEmpty()) {
    prefs->iDialogDir = QFileInfo(fn).dirPath();
    prefs->Save();
    Load(fn);
    DefaultZoom();
  } else
    statusBar()->message(tr("Loading canceled"));
}

extern QPixmap toPng(const IpeDocument *doc, const IpePage *page, int view,
		     double zoom);

void AppUi::SaveAsBitmap()
{
  QString fn = QFileDialog::getSaveFileName
    (QString::null, tr("PNG files (*.png)"), this);
  if (fn.isEmpty()) {
    statusBar()->message(tr("No bitmap saved"));
    return;
  }

  double zoom = double(iResolution->value()) / 72000.0;
  QPixmap pixmap = toPng(iDoc, Page(), iVno, zoom);
  if (!pixmap.save(fn, "PNG")) {
    ErrMsg(tr("Could not save bitmap"));
  }
}

void AppUi::SaveKeys(const QString &fname)
{
  iKeyTranslator.save(fname);
}

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