/* -*- C++ -*-
   This file implements the HTML exporting method of kabīs toplevel
   widget.

   the KDE addressbook

   $ Author: Mirko Boehm $
   $ Copyright: (C) 1996-2000, Mirko Boehm $
   $ Contact: mirko@kde.org
         http://www.kde.org $
   $ License: GPL with the following explicit clarification:
         This code may be linked against any version of the Qt toolkit
         from Troll Tech, Norway. $

   $Revision: 1.10 $
*/

#include "kab_topwidget.h"
#include "kab_htmlexport.h"

#include <iostream.h>
#include <qlineedit.h>
#include <qstringlist.h>
#include <qfile.h>
#include <qtextstream.h>

#include <knotifyclient.h>
#include <kfiledialog.h>
#include <kdialogbase.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <klocale.h>
#include <kstddirs.h>
#include <map>
#include <list>

// ----- some forward declarations:

class Parser;

bool createIndex(const QString& path, Parser*, const BunchOfKeys&);
/* The following code implements the parser class. It is used to
   replace "tags" in HTML file by predefined text patterns (that might
   in turn be HTML tags). By this, it is possible to write HTML files
   as templates and fill them with data provided by the
   application. */

/** Parser reads in a string (usually read from a text file) and
    searches for ASCII tags found in the map "tags". Every tag is
    replaced by the text value assigned to it.
    Tags syntax is like singular HTML statements, as <hr noshade>, for
    example. Every tag should thus get a real identifying name!
    The advantage of this style is that regular web browsers simply
    ignore the tags they do not know about.
    The resulting ASCII stream is a simple HTML-text, viewable by
    every working browser.
    Note that Parser assumes that HTML tags are not nested in the
    following way: <tag_a <tag_b arg=value> >. This allows for parsing
    in one single run.
*/

class Parser
{
public:
  enum TokenType {
    PlainText=1,
    KnownTag=2
  };
  /** The class Token is used to construct a list of the tokens of the
      input stream that hold either a - format free! - amount of plain
      text or a placeholder for a tag known to the parser.
   */
  class Token
  {
  public:
    Token(TokenType t, QString te)
      : type(t),
	content(te)
    {
    }
    QString text() { return content; }
    TokenType tokenType() { return type; }
  protected:
    TokenType type;
    QString content;
  };
  Parser();
  ~Parser();
  /** Add a tag to the tags map.
      If replace is true, an existing tags content is replaced by
      content. If replace is false, false is returned if the tag
      already exists, true otherwise.
      Also, false is returned on any error.
   */
  bool addTag(QString key, QString content, bool replace=true);
  /** Clear the tags map.
   */
  void clearTags();
  /** Print the tags and its values to stdout.
      This method might be removed or moved to kdDebug as it is mainly
      implemented for debugging purposes.
  */
  void printTags();
  /** Set the text containing the tags. Note that the text is parsed
      when it is set, changing the tags contents does not require
      reparsing, just call getText().
      Be aware that, since this does not matter to HTML, whitespaces
      are simplified during parsing.
  */
  bool setText(QString);
  /** getText() returnes the text assembled by the original HTML code
      and the content of the tags in the only argument.
      @return false if the stream could not be partened into tokens
  */
  void getText(QString&);
protected:
  typedef std::map<QString, QString, QStringLess> StringStringMap;
  StringStringMap tags;
  std::list<Token> tokens;
};

Parser::Parser()
{
}

Parser::~Parser()
{
}

bool Parser::addTag(QString key, QString content, bool replace)
{
  if(key.isEmpty())
    {
      return false;
    }
  if(replace) // entry will be replaced
    {
      tags.erase(key);
    }
  if(tags.insert(StringStringMap::value_type(key, content)).second)
    {
      return true;
    } else {
      return false;
    }
}

void Parser::printTags()
{
  StringStringMap::iterator pos;
  // -----
  for(pos=tags.begin(); pos!=tags.end(); ++pos)
    {
      cout << (*pos).first.utf8()
	   << " = "
	   << (*pos).second.utf8() << endl;
    }
}

void Parser::clearTags()
{
  if(!tags.empty()) // erase fails on empty containers!
    {
      tags.erase(tags.begin(), tags.end());
    }
}

bool Parser::setText(QString input)
{
  QString tag, key, text;
  StringStringMap::iterator it;
  int pos, start, begin, end;
  int count;
  // ----- simplify white spaces to simplify parsing:
  input=input.simplifyWhiteSpace();
  // ----- now scan input for known tags:
  pos=0;
  while(pos<input.length())
    {
      // ----- remember where to start to be able to handle plain text
      begin=pos;
      // ----- find start of a tag:
      while(input[pos]!=QChar('<') && pos<input.length())
	{
	  ++pos;
	}
      // ----- break if end of string reached:
      if(pos>input.length())
	{
	  break;
	}
      start=pos++;
      // ----- else search for tag end:
      while(input[pos]!=QChar('>') && pos<input.length())
	{
	  ++pos;
	}
      // ----- break if end of string reached:
      if(pos>input.length())
	{
	  qWarning("Parser::setText: beginning of tag found at "
		   "pos %i,\n"
		   "                 tag end missing.\n"
		   "                 Mismatching number of < and"
		   " > characters.", start);
	  break;
	}
      end=pos++;
      // ----- now copy the whole tag (or plain text) including its
      // opening and closing brackets:
      tag=input.mid(start, end-start+1);
      tag=tag.stripWhiteSpace();
      if(tag.isEmpty())
	{ // empty brackets are ignored
	  qDebug("Parser::setText: empty tag found at %i.", start);
	  break;
	}
      qDebug("Parser::setText: found tag \"%s\".",
	     (const char*)tag.utf8());
      // ----- now add a token to the list depending whether the tag is
      // found in the tags map or not:
      key=tag.mid(1, tag.length()-2);
      key=key.stripWhiteSpace();
      it=tags.find(key);
      if(it==tags.end())
	{ // not found
	  qDebug("Parser::setText: tag \"%s\" is unknown.",
		 (const char*)key.utf8());
	  // ----- add the whole part from begin to pos as plain text:
	  // WORK_TO_DO
	  text=input.mid(begin, pos-begin);
	  qDebug("Parser::setText: adding \"%s\" as plain text.",
		 (const char*)text.utf8());
	  tokens.push_back(Token(Parser::PlainText, text));
	} else { // found
	  qDebug("Parser::setText: found known tag \"%s\".",
		 (const char*)key.utf8());
	  // ----- add the text in front of the tag as plain text
	  // (from begin to start-1):
	  text=input.mid(begin, start-1-begin);
	  qDebug("Parser::setText: adding \"%s\" as plain text.",
		 (const char*)text.utf8());
	  tokens.push_back(Token(Parser::PlainText, text));
	  // ----- add the tag as known tag:
	  qDebug("Parser::setText: adding \"%s\" as known tag.",
		 (const char*)key.utf8());
	  tokens.push_back(Token(Parser::KnownTag, key));
	}
    }
  qDebug("Parser::setText: %i tokens.", tokens.size());
  return true;
}

void Parser::getText(QString& result)
{
  QString value;
  std::list<Token>::iterator pos;
  StringStringMap::iterator tag;
  // -----
  for(pos=tokens.begin(); pos!=tokens.end(); ++pos)
    {
      if((*pos).tokenType()==Parser::PlainText)
	{ // ----- this is plain text
	  result.append((*pos).text());
	} else {
	  // ----- this is a tag, find its value (the tag contains the
	  // key):
	  tag=tags.find((*pos).text());
	  if(tag==tags.end())
	    {
	      qDebug("Parser::getText: warning - known tag %s "
		     "not in tags map.",
		     (const char*)(*pos).text().utf8());
	    } else {
	      value=" ";
	      value+=(*tag).second;
	      value+=" ";
	      result.append(value);
	    }
	}
    }
}

/* End of class Perser. Continue with general HTML exporting code. */

HTMLExportSetup::HTMLExportSetup(QWidget *parent)
  : HTMLExportSetupBase(parent)
{
  QString templates;
  QStringList dirlist;
  // -----
  // WORK_TO_DO: set previous selections here
  // templates=locate("appdata", "htmlexport/templates");
  dirlist=KGlobal::instance()->dirs()
    ->findDirs("appdata", "htmlexport/templates");
  if(!dirlist.isEmpty())
    {
      templates=*(dirlist.at(0));
      kdDebug() << "HTMLExportSetup ctor: templates folder is "
		<< templates << "." << endl;
      leTemplate->setText(templates);
    } else {
      kdDebug() << "HTMLExportSetup ctor: no template folders found."
		<< endl;
    }
}


void HTMLExportSetup::slotTemplateFolder()
{
  // WORK_TO_DO:
  // - set to installation template dir
  // - warn user before changing
  QString dir;
  // ----- warn the user:
  KMessageBox::information
    (this,
     i18n("<qt><b>Warning!</b><br>"
	  "Do not change this setting unless you installed "
	  "a customized version of the template files "
	  "delivered with your kab installation at another"
	  " point.</qt>"),
     i18n("HTML Export Templates"),
     "kab_html_export_templates_warning");
  // -----
  dir=KFileDialog::getExistingDirectory
    (QString(":HTMLTemplateDir"),
     this,
     i18n("Select Template Folder"));
  kdDebug() << "HTMLExportSetup::slotTemplateFolder: template folder is"
	    << dir << "." << endl;
  // -----
  if(!dir.isEmpty()) leTemplate->setText(dir);
}

void HTMLExportSetup::slotTargetFolder()
{
  QString dir;
  // -----
  dir=KFileDialog::getExistingDirectory
    (QString(":HTMLTargetDir"),
     this,
     i18n("Select Target Folder"));
  kdDebug() << "HTMLExportSetup::slotTemplateFolder: target folder is"
	    << dir << "." << endl;
  // -----
  if(!dir.isEmpty()) leTarget->setText(dir);
}

const QString HTMLExportSetup::targetFolder()
{
  return leTarget->text();
}

const QString HTMLExportSetup::templateFolder()
{
  return leTemplate->text();
}

void TopLevelWidget::exportHTML()
{
  BunchOfKeys bunch;
  // -----
  KabKey key;
  for(unsigned counter=0; counter<api->addressbook()->noOfEntries(); ++counter)
    {
      if(api->addressbook()->getKey(counter, key)!=AddressBook::NoError)
	{
	  kdDebug() << "TopLevelWidget::exportHTML: cannot get entry at "
		    << "index " << counter << "!" << endl;
	} else {
	  bunch.push_back(key);
	}
    }
  exportHTML(bunch);
}

void TopLevelWidget::exportHTML(const BunchOfKeys& bunch)
{
  KDialogBase dialog(this, "", true, i18n("Setup HTML export"),
		     KDialogBase::Ok|KDialogBase::Cancel,
		     KDialogBase::Ok, true);
  HTMLExportSetup widget(&dialog);
  bool firsttime=true;
  // -----
  dialog.setMainWidget(&widget);
  do {
    if(!firsttime)
      {
	KMessageBox::information
	  (this,
	   i18n("You have to specify both a template and a target folder!"),
	   i18n("Corrections needed"));
      }
    if(!dialog.exec())
      {
	KNotifyClient::beep();
	emit(setStatus(i18n("Rejected.")));
	return;
      }
    firsttime=false;
  } while (widget.leTarget->text().isEmpty()
	   || (widget.leTemplate->text()).isEmpty());
  kdDebug() << "TopLevelWidget::exportHTML: exporting to "
	    << widget.leTarget->text()
	    << " with template dir "
	    << widget.leTemplate->text() << "." << endl;
  // ----- now locate ressources and prepare parsers:
  QString path;
  const char *Index= "htmlexport/templates/index.html";
  const char *EntryList= "htmlexport/templates/kab_entrylist.html";
  const char *Person= "htmlexport/templates/kab_person.html";
  const char *Address= "htmlexport/templates/kab_address.html";
  Parser IndexParser;
  Parser EntryListParser;
  Parser PersonParser;
  Parser AddressParser;
  QString IndexPath;
  QString EntryListPath;
  QString PersonPath;
  QString AddressPath;
  QString IndexTemplate;
  QString EntryListTemplate;
  QString PersonTemplate;
  QString AddressTemplate;
  //       the ressources are copied to the target directory:
  const char *Ressources[]= {
    "htmlexport/templates/kab_background.gif" };
  //       the template files are used to create the HTML code:
  const char *TemplateFiles[]= {
    Index,
      EntryList, Person, Address };
  QString *Templates[]= {
    &IndexTemplate, &EntryListTemplate, &PersonTemplate,
      &AddressTemplate };
  QString *Paths[]= {
    &IndexPath, &EntryListPath, &PersonPath, &AddressPath };
  Parser *Parsers[]= {
    &IndexParser, &EntryListParser, &PersonParser, &AddressParser };
  const int NoOfRessources=sizeof(Ressources)/sizeof(Ressources[0]);
  const int NoOfTemplateFiles=sizeof(TemplateFiles)/sizeof(TemplateFiles[0]);
  const int NoOfTemplates=sizeof(Templates)/sizeof(Templates[0]);
  const int NoOfParsers=sizeof(Parsers)/sizeof(Parsers[0]);
  bool missingRessource=false;
  bool missingTemplate=false;
  QString textRessourceMissing=
    i18n("<qt>At least one <b>resource file</b> used to export your "
	 "address book to HTML is missing. Continue anyway?<br><br>"
	 "<i>(Resources are used as support files like backgrounds "
	 "or bullets in the resulting HTML files.)</i></qt>");
  QString textTemplateMissing=
    i18n("<qt>At least one <b>template file</b> used to export your"
	 " address book to HTML is missing. Continue anyway?<br><br>"
	 "<i>(Templates are used as a basis to create the HTML "
	 "files themselves. The HTML export will most probably not "
	 "work without it.)</i></qt>");
  // -----
  for(int counter=0; counter<NoOfRessources; ++counter)
    {
      path=locate("appdata", Ressources[counter]);
      if(path.isEmpty())
	{
	  kdDebug() << "TopLevelWidget::exportHTML: cannot locate ressource "
		    << Ressources[counter] << endl;
	  missingRessource=true;
	} else {
	  kdDebug() << "TopLevelWidget::exportHTML: found ressource "
		    << Ressources[counter] << " at " << path << "." << endl;
	}
    }
  for(int counter=0; counter<NoOfTemplateFiles; ++counter)
    {
      path=QString::null;
      path=locate("appdata", TemplateFiles[counter]);
      if(path.isEmpty())
	{
	  kdDebug() << "TopLevelWidget::exportHTML: cannot locate template "
		    << TemplateFiles[counter] << endl;
	  missingTemplate=true;
	} else {
	  kdDebug() << "TopLevelWidget::exportHTML: found template "
		    << TemplateFiles[counter] << " at " << path << "."
		    << endl;
	  *Paths[counter]=path;
	}
    }
  if(missingRessource)
    {
      if(KMessageBox::questionYesNo
	 (this,
	  textRessourceMissing,
	  i18n("Missing Resource"))!=KMessageBox::Yes)
	{
	  KNotifyClient::beep();
	  emit(setStatus(i18n("Cancelled.")));
	  return;
	}
    }
  if(missingTemplate)
    {
      if(KMessageBox::questionYesNo
	 (this,
	  textTemplateMissing,
	  i18n("Missing Template"))!=KMessageBox::Yes)
	{
	  KNotifyClient::beep();
	  emit(setStatus(i18n("Cancelled.")));
	  return;
	}
    }
  // ----- now preload the parsers with the different template files:
  for(int counter=0; counter<NoOfParsers; ++counter)
    {
      if(Paths[counter]->isEmpty())
	{
	} else {
	  QFile f(*Paths[counter]);
	  if(f.open(IO_ReadOnly))
	    {
	      QTextStream stream(&f);
	      *Templates[counter]=stream.read();
	      // Parsers[counter]->setText(*Templates.counter);
	      kdDebug() << "TopLevelWidget::exportHTML: opened and "
			<< "read template " << endl << "    "
			<< *Paths[counter] << endl
			<< "                            "
			<< "(" << Templates[counter]->length()
			<< " bytes)." << endl;
	    } else {
	      kdDebug() << "TopLevelWidget::exportHTML: cannot open template "
			<< endl << "    " << *Paths[counter] << endl
			<< "                            "
			<< "for reading." << endl;
	    }
	}
    }
  // ----- start by creating the index file, this will recursively
  // create the other HTML files needed:
  if(!createIndex(IndexPath, &IndexParser, bunch))
    {
      KMessageBox::sorry
	(this,
	 i18n("<qt><b>Sorry!</b><br><br>"
	      "Exporting to HTML failed. Problems might be caused by "
	      "insufficient permissions in the target directory or "
	      "failures in the setup of your KDE 2 installation."
	      "<br><br>"
	      "If you created your own template folder, mistakes in "
	      "there may also cause errors. As a last possibility, "
	      "the KDE address book may be buggy, too :-(.</qt>"),
	 i18n("Error exporting to HTML"));
    }
}

bool createIndex(const QString&, Parser*, const BunchOfKeys&)
{
  return false;
}


#include "kab_htmlexport.moc"
