/***************************************************************************
                          phrasebook.cpp  -  description
                             -------------------
    begin                : Don Sep 19 2002
    copyright            : (C) 2002 by Gunnar Schmi Dt
    email                : kmouth@schmi-dt.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <qprinter.h>
#include <qpainter.h>
#include <qfile.h>
#include <qxml.h>
#include <qregexp.h>
#include <qptrstack.h>

#include <klocale.h>
#include <kaction.h>
#include <kpopupmenu.h>
#include <ktoolbar.h>
#include <ktempfile.h>
#include <kio/netaccess.h>
#include <kfiledialog.h>
#include <kmessagebox.h>
#include <kglobalsettings.h>

#include "phrasebookparser.h"
#include "phrasebook.h"

Phrase::Phrase() {
   this->phrase = "";
   this->shortcut = "";
}

Phrase::Phrase (const QString &phrase) {
   this->phrase = phrase;
   this->shortcut = "";
}

Phrase::Phrase (const QString &phrase, const QString &shortcut) {
   this->phrase = phrase;
   this->shortcut = shortcut;
}

QString Phrase::getPhrase() const {
   return phrase;
}

QString Phrase::getShortcut() const {
   return shortcut;
}

void Phrase::setPhrase (const QString &phrase) {
   this->phrase = phrase;
}

void Phrase::setShortcut (const QString &shortcut) {
   this->shortcut = shortcut;
}

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

PhraseBookEntry::PhraseBookEntry () {
   phrase = Phrase();
   level = 1;
   isPhraseValue = false;
}

PhraseBookEntry::PhraseBookEntry (Phrase phrase, int level, bool isPhrase) {
   this->phrase = phrase;
   this->level = level;
   isPhraseValue = isPhrase;
}

bool PhraseBookEntry::isPhrase() const {
  return isPhraseValue;
}

Phrase PhraseBookEntry::getPhrase() const {
   return phrase;
}

int PhraseBookEntry::getLevel() const {
   return level;
}

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

void PhraseBook::print(KPrinter *pPrinter) {
   QPainter printpainter;
   printpainter.begin(pPrinter);

   QRect size = printpainter.viewport ();
   int x = size.x();
   int y = size.y();
   int w = size.width();
   printpainter.setFont (QFont (KGlobalSettings::generalFont().family(), 12));
   QFontMetrics metrics = printpainter.fontMetrics();

   PhraseBookEntryList::iterator it;
   for (it = begin(); it != end(); ++it) {
      QRect rect = metrics.boundingRect (x+16*(*it).getLevel(), y,
                                         w-16*(*it).getLevel(), 0,
                                         Qt::AlignJustify | Qt::WordBreak,
                                         (*it).getPhrase().getPhrase());

      if (y+rect.height() > size.height()) {
         pPrinter->newPage();
         y = 0;
      }
      printpainter.drawText (x+16*(*it).getLevel(),y,
                             w-16*(*it).getLevel(),rect.height(),
                             Qt::AlignJustify | Qt::WordBreak,
                             (*it).getPhrase().getPhrase());
      y += rect.height();
   }

   printpainter.end();
}

bool PhraseBook::decode (const QString &xml) {
   QXmlInputSource source;
   source.setData (xml);
   return decode (source);
}

bool PhraseBook::decode (QXmlInputSource &source) {
   PhraseBookParser parser;
   QXmlSimpleReader reader;
   reader.setFeature ("http://trolltech.com/xml/features/report-start-end-entity", true);
   reader.setContentHandler (&parser);

   if (reader.parse(source)) {
      PhraseBookEntryList::clear();
      *(PhraseBookEntryList *)this += parser.getPhraseList();
      return true;
   }
   else
      return false;
}

QCString encodeString (const QString str) {
   QCString res = "";
   for (int i = 0; i < (int)str.length(); i++) {
      QChar ch = str.at(i);
      ushort uc = ch.unicode();
      QCString number; number.setNum(uc);
      if ((uc>127) || (uc<32) || (ch=='<') || (ch=='>') || (ch=='&') || (ch==';'))
         res = res + "&#" + number + ";";
      else
         res = res + (char)uc;
   }
   return res;
}

QString PhraseBook::encode () {
   QString result;
   result  = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
   result += "<!DOCTYPE phrasebook>\n";
   result += "<phrasebook>\n";

   PhraseBookEntryList::iterator it;
   int level = 0;
   for (it = begin(); it != end(); ++it) {
      int newLevel = (*it).getLevel();
      while (level < newLevel) {
         result += "<phrasebook>\n";
         level++;
      }
      while (level > newLevel) {
         result += "</phrasebook>\n";
         level--;
      }

      if ((*it).isPhrase()) {
         Phrase phrase = (*it).getPhrase();
         result += "<phrase shortcut=\"" + encodeString(phrase.getShortcut());
         result += "\">" + encodeString(phrase.getPhrase()) + "</phrase>\n";
      }
      else {
         Phrase phrase = (*it).getPhrase();
         result += "<phrasebook name=\"" + encodeString(phrase.getPhrase()) + "\">\n";
         level++;
      }
   }
   while (level > 0) {
      result += "</phrasebook>\n";
      level--;
   }
   result += "</phrasebook>";
   return result;
}

QStringList PhraseBook::toStringList () {
   QStringList result;

   PhraseBook::iterator it;
   for (it = begin(); it != end(); ++it) {
      if ((*it).isPhrase())
         result += (*it).getPhrase().getPhrase();
   }
   return result;
}

bool PhraseBook::save (const KURL &url) {
   QRegExp pattern("*.phrasebook",true,true);
   return save (url, pattern.exactMatch(url.filename()));
}


void PhraseBook::save (QTextStream &stream, bool asPhrasebook) {
   if (asPhrasebook)
      stream << encode();
   else
      stream << toStringList().join("\n");
}

bool PhraseBook::save (const KURL &url, bool asPhrasebook) {
   if (url.isLocalFile()) {
      QFile file(url.path());
      if(!file.open(IO_WriteOnly))
         return false;

      QTextStream stream(&file);
      save (stream, asPhrasebook);
      file.close();

      if (file.status() != IO_Ok)
         return false;
      else
         return true;
   }
   else {
      KTempFile tempFile;
      tempFile.setAutoDelete(true);

      save (*tempFile.textStream(), asPhrasebook);
      tempFile.close();

      return KIO::NetAccess::upload(tempFile.name(), url);
   }
}

int PhraseBook::save (QWidget *parent, const QString &title, KURL &url, bool phrasebookFirst) {
   // KFileDialog::getSaveURL(...) is not useful here as we need
   // to know the requested file type.

   QString filters;
   if (phrasebookFirst)
      filters = i18n("*.phrasebook|Phrase Books (*.phrasebook)\n*.txt|Plain Text Files (*.txt)\n*|All Files");
   else
      filters = i18n("*.txt|Plain Text Files (*.txt)\n*.phrasebook|Phrase Books (*.phrasebook)\n*|All Files");

   KFileDialog fdlg(QString::null,filters, parent, "filedialog", true);
   fdlg.setCaption(title);
   fdlg.setOperationMode( KFileDialog::Saving );

   if (fdlg.exec() != QDialog::Accepted) {
     return 0;
   }

   url = fdlg.selectedURL();

   if (url.isEmpty() || !url.isValid()) {
      return -1;
   }

   if (KIO::NetAccess::exists(url)) {
      if (KMessageBox::warningContinueCancel(0,QString("<qt>%1</qt>").arg(i18n("The file %1 already exists. "
                                                       "Do you want to overwrite it?").arg(url.url())),i18n("File Exists"),i18n("&Overwrite"))==KMessageBox::Cancel) {
         return 0;
      }
   }

   bool result;
   if (fdlg.currentFilter() == "*.phrasebook") {
      if (url.fileName (false).contains('.') == 0) {
         url.setFileName (url.fileName(false) + ".phrasebook");
      }
      else if (url.fileName (false).right (11).contains (".phrasebook", false) == 0) {
         int filetype = KMessageBox::questionYesNoCancel (0,QString("<qt>%1</qt>").arg(i18n("Your chosen filename <i>%1</i> has a different extension than <i>.phrasebook</i>. "
                                                           "Do you wish to add <i>.phrasebook</i> to the filename?").arg(url.filename())),i18n("File Extension"),i18n("Add"),i18n("Do Not Add"));
         if (filetype == KMessageBox::Cancel) {
            return 0;
         }
         if (filetype == KMessageBox::Yes) {
            url.setFileName (url.fileName(false) + ".phrasebook");
         }
      }
      result = save (url, true);
   }
   else if (fdlg.currentFilter() == "*.txt") {
      if (url.fileName (false).right (11).contains (".phrasebook", false) == 0) {
         result = save (url, false);
      }
      else {
         int filetype = KMessageBox::questionYesNoCancel (0,QString("<qt>%1</qt>").arg(i18n("Your chosen filename <i>%1</i> has the extension <i>.phrasebook</i>. "
                                                           "Do you wish to save in phrasebook format?").arg(url.filename())),i18n("File Extension"),i18n("As Phrasebook"),i18n("As Plain Text"));
         if (filetype == KMessageBox::Cancel) {
            return 0;
         }
         if (filetype == KMessageBox::Yes) {
            result = save (url, true);
         }
         else {
            result = save (url, false);
         }
      }
   }
   else // file format "All files" requested, so decide by extension
      result = save (url);

   if (result)
      return 1;
   else
      return -1;
}

bool PhraseBook::open (const KURL &url) {
   QString tempFile;
   KURL fileUrl = url;

   QString protocol = fileUrl.protocol();
   if (protocol.isEmpty() || protocol.isNull()) {
      fileUrl.setProtocol ("file");
      fileUrl.setPath (url.url());
   }

   if (KIO::NetAccess::download (fileUrl, tempFile)) {
      QStringList list = QStringList();

      // First: try to load it as a normal phrase book
      QFile file(tempFile);
      QXmlInputSource source (&file);
      bool error = !decode (source);

      // Second: if the file does not contain a phrase book, load it as
      // a plain text file
      if (error) {
         // Load each line of the plain text file as a new phrase

         QFile file(tempFile);
         if (file.open(IO_ReadOnly)) {
            QTextStream stream(&file);

            while (!stream.atEnd()) {
               QString s = stream.readLine();
               if (!(s.isNull() || s.isEmpty()))
                  *this += PhraseBookEntry(Phrase(s, ""), 0, true);
            }
            file.close();
            error = false;
         }
         else
            error = true;
      }
      KIO::NetAccess::removeTempFile (tempFile);

      return !error;
   }
   else
      return false;
}

void PhraseBook::addToGUI (QPopupMenu *popup, KToolBar *toolbar, KActionCollection *phrases,
                  QObject *receiver, const char *slot) const {
   if ((popup != 0) || (toolbar != 0)) {
      QPtrStack<QWidget> stack;
      QWidget *parent = popup;
      int level = 0;

      QValueListConstIterator<PhraseBookEntry> it;
      for (it = begin(); it != end(); ++it) {
         int newLevel = (*it).getLevel();
         while (newLevel > level) {
            KActionMenu *menu = new KActionMenu("", "phrasebook");
            menu->setDelayed(false);
            phrases->insert(menu);
            menu->plug (parent);
            if (parent == popup)
               menu->plug (toolbar);
            if (parent != 0)
               stack.push (parent);
            parent = menu->popupMenu();
            level++;
         }
         while (newLevel < level && (parent != popup)) {
            parent = stack.pop();
            level--;
         }
         if ((*it).isPhrase()) {
            Phrase phrase = (*it).getPhrase();
            KAction *action = new PhraseAction (phrase.getPhrase(),
                     phrase.getShortcut(), receiver, slot, phrases);
            if (parent == popup)
               action->plug (toolbar);
            if (parent != 0)
               action->plug(parent);
         }
         else {
            Phrase phrase = (*it).getPhrase();
            KActionMenu *menu = new KActionMenu(phrase.getPhrase(), "phrasebook");
            menu->setDelayed(false);
            phrases->insert(menu);
            if (parent == popup)
               menu->plug (toolbar);
            if (parent != 0)
               menu->plug (parent);
            stack.push (parent);
            parent = menu->popupMenu();
            level++;
         }
      }
   }
}

void PhraseBook::insert (const QString &name, const PhraseBook &book) {
   *this += PhraseBookEntry(Phrase(name), 0, false);

   QValueListConstIterator<PhraseBookEntry> it;
   for (it = book.begin(); it != book.end(); ++it) {
      *this += PhraseBookEntry ((*it).getPhrase(), (*it).getLevel()+1, (*it).isPhrase());
   }
}

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

PhraseBookDrag::PhraseBookDrag (PhraseBook *book, QWidget *dragSource, const char *name)
    : QDragObject (dragSource, name)
{
   setBook (book);
}

PhraseBookDrag::PhraseBookDrag (QWidget *dragSource, const char *name)
    : QDragObject (dragSource, name)
{
   setBook (0);
}

PhraseBookDrag::~PhraseBookDrag () {
}

void PhraseBookDrag::setBook (PhraseBook *book) {
   if (book == 0) {
      isEmpty = true;
      xmlphrasebook.setText(QString::null);
      xml.setText(QString::null);
      plain.setText(QString::null);
   }
   else {
      isEmpty = false;
      xmlphrasebook.setText(book->encode());
      xml.setText(book->encode());
      plain.setText(book->toStringList().join("\n"));
   }
   xmlphrasebook.setSubtype("x-xml-phrasebook");
   xml.setSubtype("xml");
   plain.setSubtype("plain");
}

const char *PhraseBookDrag::format (int i) const {
   if (isEmpty)
      return plain.format(i);
   else if (i%3 == 0)
      return plain.format(i/3);
   else if (i%3 == 1)
      return xml.format(i/3);
   else
      return xmlphrasebook.format(i/3);
}

QByteArray PhraseBookDrag::encodedData (const char* mime) const {
   QCString m(mime);
   m = m.lower();
   if (m.contains("xml-phrasebook"))
      return xmlphrasebook.encodedData(mime);
   else if (m.contains("xml"))
      return xml.encodedData(mime);
   else
      return plain.encodedData(mime);
}

bool PhraseBookDrag::canDecode (const QMimeSource* e) {
   return QTextDrag::canDecode(e);
}

bool PhraseBookDrag::decode (const QMimeSource *e, PhraseBook *book) {
   QString string;
   QCString subtype1 = "x-xml-phrasebook";
   QCString subtype2 = "xml";

   if (!QTextDrag::decode(e, string, subtype1))
      if (!QTextDrag::decode(e, string, subtype2)) {
         if (QTextDrag::decode(e, string)) {
            *book += PhraseBookEntry(Phrase(string, ""), 0, true);
            return true;
         }
         else return false;
      }

   return book->decode(string);
}

#include "phrasebook.moc"
