/***************************************************************************
                          quantacommon.cpp  -  description
                             -------------------
    begin                : Sat Jul 27 2002
    copyright            : (C) 2002, 2003 by Andras Mantia <amantia@kde.org>
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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; version 2 of the License.               *
 *                                                                         *
 ***************************************************************************/

//system includes
#include <sys/types.h>
#include <unistd.h>

//qt includes
#include <qstringlist.h>
#include <qdict.h>
#include <qdir.h>
#include <qdom.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qwidget.h>

//kde includes
#include <kapplication.h>
#include <dcopref.h>
#include <kurl.h>
#include <kdirwatch.h>
#include <kmimetype.h>
#include <kstandarddirs.h>
#include <kmessagebox.h>
#include <kio/netaccess.h>
#include <klocale.h>
#include <kprotocolinfo.h>
#include <kprogress.h>
#include <ktempdir.h>
#include <ktempfile.h>

//remove the below ones when KQPasteAction is removed
#include <dcopclient.h>
#include <kdebug.h>
#include <kpopupmenu.h>
#include <ktoolbar.h>
#include <ktoolbarbutton.h>
#include <kstringhandler.h>
#include <qwhatsthis.h>
#include <qtimer.h>
#include <qclipboard.h>
#include <qdatastream.h>

#include "qextfileinfo.h"
#include "quantacommon.h"
#include "tag.h"
#include "dtds.h"
//#include "resource.h"


QConfig qConfig; //holds the main configuration settings
QString tmpDir;

QRegExp scriptBeginRx;
QRegExp scriptEndRx;

Node *baseNode;
Parser *parser;
KDirWatch *fileWatcher;
KProgress *progressBar;

QString toolbarExtension = ".toolbar.tgz";
QRegExp newLineRx("\\n");
QStringList charList; //hold the list of the &char; chars. See the data/chars file.
QPtrList<KTempFile> tempFileList;
QPtrList<KTempDir> tempDirList;
bool typingInProgress;

QuantaCommon::QuantaCommon(){
}

QuantaCommon::~QuantaCommon(){
}

/** convert tag to upper or lower case */
QString QuantaCommon::tagCase( const QString& tag)
{
  QString sTag = tag;

  switch (qConfig.tagCase)
  {
    case 1: sTag = tag.lower();
            break;
    case 2: sTag = tag.upper();
  }
  return sTag;
}

/** convert attr of tag to upper or lower case */
QString QuantaCommon::attrCase( const QString& attr)
{
  QString sAttr = attr;

  switch (qConfig.attrCase)
  {
    case 1: sAttr = attr.lower();
            break;
    case 2: sAttr = attr.upper();
  }
  return sAttr;
}

/** returns the attribute value in quoted form, by taking care of the
quotation setting*/
QString QuantaCommon::quoteAttributeValue(const QString& value)
{
  QString quote = qConfig.attrValueQuotation;
  return quote + value + quote;
}

/** Set's up the url correctly from urlString. */
void QuantaCommon::setUrl(KURL &url, const QString& urlString)
{
  KURL oldUrl = url;
  url = urlString;
  if (!KProtocolInfo::isKnownProtocol(url))
  {
    url = oldUrl;
    url.setPath(urlString);
    if (url.protocol().isEmpty())
       url.setProtocol("file");
  }
}

/** No descriptions */
bool QuantaCommon::isSingleTag(const QString& dtdName, const QString& tag)
{
  bool single = false;

  //!doctype is a common tag to all DTDs not listed in the tagsList
  if(tag.lower() == "!doctype" || tag.lower() == "?xml")
    return true;

  const DTDStruct* dtd = DTDs::ref()->find(dtdName);
  if (dtd && !tag.isEmpty())
  {
    QString searchForTag = (dtd->caseSensitive) ? tag : tag.upper();
    QTag* qtag = dtd->tagsList->find(searchForTag);
    if (qtag)
        single = qtag->isSingle();
  }

  return single;
}

/** No descriptions */
bool QuantaCommon::isOptionalTag(const QString& dtdName, const QString& tag)
{
  bool optional = false;

  const DTDStruct* dtd = DTDs::ref()->find(dtdName);
  if (dtd && !tag.isEmpty())
  {
    QString searchForTag = (dtd->caseSensitive) ? tag : tag.upper();
    QTag* qtag = dtd->tagsList->find(searchForTag);
    if (qtag)
        optional = qtag->isOptional();
  }

  return optional;
}
/** No descriptions */
bool QuantaCommon::isKnownTag(const QString& dtdName, const QString& tag)
{
  bool known = false;

  const DTDStruct* dtd = DTDs::ref()->find(dtdName);
  if (dtd && !tag.isEmpty())
  {
    QString searchForTag = (dtd->caseSensitive) ? tag : tag.upper();
    if (dtd->tagsList->find(searchForTag))
        known = true;
  }

  return known;
}

AttributeList* QuantaCommon::tagAttributes(const QString& dtdName, const QString& tag)
{
  AttributeList* attrs = 0L;

  const DTDStruct* dtd = DTDs::ref()->find(dtdName);
  if (dtd && !tag.isEmpty())
  {
    QString searchForTag = (dtd->caseSensitive) ? tag : tag.upper();
    QTag* qtag = dtd->tagsList->find(searchForTag);
    if (qtag)
        attrs = qtag->attributes();
  }

  return attrs;
}

/** Returns the QTag object for the tag "tag" from the DTD named "dtdname". */
QTag* QuantaCommon::tagFromDTD(const QString& dtdName, const QString& tag)
{
  const DTDStruct* dtd = DTDs::ref()->find(dtdName);
  return tagFromDTD(dtd, tag);
}

/** Returns the QTag object for the tag "tag" from the DTD. */
QTag* QuantaCommon::tagFromDTD(const DTDStruct *dtd, const QString& tag)
{
  QTag *qtag = 0;
  if (dtd && !tag.isEmpty())
  {
    QString searchForTag = (dtd->caseSensitive) ? tag : tag.upper();
    qtag = dtd->tagsList->find(searchForTag);
  }

 return qtag;
}

/** Returns the QTag object for the node "node" from node's DTD. */
QTag* QuantaCommon::tagFromDTD(Node *node)
{
  if(!node || !node->tag)
    return 0L;

  return tagFromDTD(node->tag->dtd(), node->tag->name);
}

/** Returns an XML style string containing the GUI for attributes. */
QString QuantaCommon::xmlFromAttributes(AttributeList* attributes)
{
 QString xmlStr;
 QTextStream stream( &xmlStr, IO_WriteOnly );
 stream.setEncoding(QTextStream::UnicodeUTF8);
 if (attributes)
 {
  int row = 0;
  for ( uint i = 0; i< attributes->count();i++)
  {
    Attribute *attribute = attributes->at(i);
    QString name = attribute->name.left(1).upper()+attribute->name.right(attribute->name.length()-1);
    stream << "  <attr name=\"" + attribute->name +"\" type=\""+attribute->type+"\"";
    if (!attribute->defaultValue.isEmpty())
        stream  << " defaultValue=\"" + attribute->defaultValue + "\"";
    if (!attribute->status.isEmpty())
        stream  << " status=\"" + attribute->status + "\"";
        stream << ">" << endl;
        stream << "    <text>" << name << "</text>" << endl;
        if (attribute->type != "check")
        {
          stream  << "    <textlocation col=\"0\" row=\"" << row << "\" />" << endl;
        }
        stream  << "    <location col=\"1\" row=\"" << row << "\" />" << endl;

    if (attribute->type == "list")
    {
      stream << "    <items>" << endl;
      for (uint j = 0; j < attribute->values.count(); j++)
      {
        stream << "      <item>" << attribute->values[j] << "</item>" << endl;
      }
      stream << "    </items>" << endl;
    }
    stream  << "  </attr>" << endl << endl ;
    row++;
  } //for
 } //if

 return xmlStr;
}

  /** Returns 0 if the (line,col) is inside the area specified by the other
arguments,      -1 if it is before the area and 1 if it is after. */
int QuantaCommon::isBetween(int line, int col, int bLine, int bCol, int eLine,
int eCol){
  int pos = 0;
  if (line < bLine || (line == bLine && (col < bCol) )) pos = -1; //it is before
  if (line > eLine || (line == eLine && (col > eCol) )) pos = 1;  //it is after

 return pos;
}

/** Returns a pointer to a KStandardDirs object usable for plugin searchup. type
is the plugin binary type (exe or lib). The returned pointer must be deleted by
the caller!! */
KStandardDirs* QuantaCommon::pluginDirs(const char *type)
{
 KStandardDirs *dirs = new KStandardDirs();
 dirs->addKDEDefaults();
 for (uint i = 0; i < qConfig.pluginSearchPaths.count(); i++)
 {
   dirs->addResourceDir(type, qConfig.pluginSearchPaths[i]);
 }
 return dirs;
}
/** Return true, if the url belong to the mimetype group. */
bool QuantaCommon::checkMimeGroup(const KURL& url, const QString& group)
{
 KMimeType::List list = KMimeType::allMimeTypes();
 KMimeType::List::iterator it;
 bool status = false;
 KMimeType::Ptr mime = KMimeType::findByURL(url);
 QString mimetype = mime->name();
 mimetype = mimetype.section('/',-1);
 for ( it = list.begin(); it != list.end(); ++it )
 {
    if ( ((*it)->name().contains(group)) && ((*it)->name().find(mimetype) != -1)
)    {
      status = true;
      break;
    }
 }

 if (!status && group == "text")
 {
   if (url.isLocalFile())
   {
      KMimeType::Format f = KMimeType::findFormatByFileContent(url.path());
      if (f.text && f.compression == KMimeType::Format::NoCompression)
          status = true;
   } else
   {
     QVariant v = mime->property("X-KDE-text");
     if (v.isValid())
         status = v.toBool();
   }
 }
 if (!status && group == "text" && mimetype == "x-zerosize")
     status = true;

 return status;
}

/** Return true, if the url has the mimetype type. */
bool QuantaCommon::checkMimeType(const KURL& url, const QString& type)
{
 bool status = false;
 QString mimetype = KMimeType::findByURL(url)->name();
 mimetype = mimetype.section('/',-1);
 if (mimetype == type) status = true;

 return status;
}

/** Return true, if the url has exactly the mimetype type. */
bool QuantaCommon::checkExactMimeType(const KURL& url, const QString& type)
{
 bool status = false;
 QString mimetype = KMimeType::findByURL(url)->name();
 if (mimetype == type) status = true;

 return status;
}

/** Returns the url without the filename. */
KURL QuantaCommon::convertToPath(const KURL& url)
{
 KURL result = url;
 result.setFileName("");
 result.adjustPath(1);
 return result;
}

/** Return a string to be used when an url is saved to the project file.
    Returns url.url() if it's an absolute url and
    url.path() if the url is relative */
QString QuantaCommon::qUrl(const KURL &url)
{
  QString result = url.path();
  if (url.path().startsWith("/")) result = url.url();

  return result;
}
/** No descriptions */
void QuantaCommon::dirCreationError(QWidget *widget, const KURL& url)
{
  KMessageBox::error(widget, i18n("<qt>Cannot create folder<br><b>%1</b>.<br>Check that you have write permission in the parent folder or that the connection to<br><b>%2</b><br> is valid.</qt>")
                             .arg(url.prettyURL(0, KURL::StripFileProtocol))
                             .arg(url.protocol()+"://"+url.user()+"@"+url.host()));}

/**
Adds the backslash before the special chars (like ?, *, . ) so the returned
string can be used in regular expressions.*/
QString QuantaCommon::makeRxCompatible(const QString& s)
{
  const uint max = 7;
  const QRegExp rxs[max]={QRegExp("\\?"),
                        QRegExp("\\*"),
                        QRegExp("\\."),
                        QRegExp("\\^"),
                        QRegExp("\\$"),
                        QRegExp("\\{"),
                        QRegExp("\\}")
                        };
  const QString strs[max]={QString("\\?"),
                         QString("\\*"),
                         QString("\\."),
                         QString("\\^"),
                         QString("\\$"),
                         QString("\\{"),
                         QString("\\}")
                         };
  QString str = s;
  for (uint i = 0; i < max - 1; i++)
  {
    str.replace(rxs[i], strs[i]);
  }

  return str;
}

/** Returns the translated a_str in English. A "back-translation" useful e.g in case of CSS elements selected from a listbox. */
QString QuantaCommon::i18n2normal(const QString& a_str)
{
//TODO: a QMap lookup would be faster, but we need a pre-built QMap<QString,QString>
  const int keywordNum = 15 *5;
  const QString keywords[keywordNum] =
                            {"normal",  "italic",  "oblique",   "serif",      "sans-serif",
                             "cursive", "fantasy", "monospace", "small-caps", "lighter",
                             "bold",    "bolder",  "xx-small",  "x-small",    "small",
                             "medium",  "large",   "x-large",   "xx-large",   "smaller",
                             "larger",  "repeat",  "repeat-x",  "repeat-y",   "no-repeat",
                             "scroll",  "fixed",   "top",       "center",     "bottom",
                             "left",    "right",   "none",      "underline",  "overline"
                             "line-through", "blibk", "justify","baseline",   "sub",
                             "super",   "text-top","text-bottom","capitalize","uppercase",
                             "lowercase","thin",   "thick",  "[length value]","dotted",
                             "dashed",  "solid",   "double",    "groove",     "ridge",
                             "inset",   "outset",  "block",     "inline",     "list-item",
                             "none",    "pre",     "nowrap",    "disc",       "circle",
                             "square",  "decimal", "lower-roman","upper-roman","lower-alpha",
                             "upper-alpha","inside","outside",  "auto",        "both" };
  QString str = a_str;
  if (!a_str.isEmpty())
  {
    for (int i = 0; i < keywordNum; i++)
    {
      if (!keywords[i].isEmpty() && a_str == i18n(keywords[i].utf8()))
      {
        str = keywords[i];
        break;
      }
    }
  }
  return str;
}

static const QChar space(' ');

void QuantaCommon::removeCommentsAndQuotes(QString &str, const DTDStruct *dtd)
{
 //Replace all the commented strings and the escaped quotation marks (\", \')
 // with spaces so they will not mess up our parsing
 int pos = 0;
 int l;
 QString s;
 while (pos != -1)
 {
   pos = dtd->commentsStartRx.search(str, pos);
   if (pos != -1)
   {
     s = dtd->commentsStartRx.cap();
     if (s == "\\\"" || s == "\\'")
     {
        int i = pos;
        int slahNum = 0;
        while (i > 0 && str[i] == '\\')
        {
          slahNum++;
          i--;
        }
        if (slahNum % 2 == 0)
        {
          pos++;
        } else
        {
          str[pos] = space;
          str[pos+1] = space;
          pos += 2;
        }
     } else
     {
       s = dtd->comments[s];
       l = str.find(s, pos);
       l = (l == -1) ? str.length() : l;
       for (int i = pos; i < l ; i++)
       {
         str[i] = space;
       }
       pos = l + s.length();
     }
   }
 }

 //Now replace the quoted strings with spaces
 QRegExp strRx("(\"[^\"]*\"|'[^']*')");
 pos = 0;
 while (pos != -1)
 {
   pos = strRx.search(str, pos);
   if (pos != -1)
   {
    l = strRx.matchedLength();
    for (int i = pos; i < pos + l ; i++)
    {
      str[i] = space;
    }
    pos += l;
   }
 }

}

bool QuantaCommon::insideCommentsOrQuotes(int position, const QString &string, const DTDStruct *dtd)
{
 //Return true if position is inside a commented or quoted string
 QString str = string;
 int pos = 0;
 int l;
 QString s;
 while (pos != -1)
 {
   pos = dtd->commentsStartRx.search(str, pos);
   if (pos == position)
     return true;
   if (pos != -1)
   {
     s = dtd->commentsStartRx.cap();
     if (s == "\\\"" || s == "\\'")
     {
        int i = pos;
        int slahNum = 0;
        while (i > 0 && str[i] == '\\')
        {
          slahNum++;
          i--;
        }
        if (slahNum % 2 == 0)
        {
          pos++;
        } else
        {
          str[pos] = space;
          str[pos+1] = space;
          pos += 2;
        }
     } else
     {
       s = dtd->comments[s];
       l = str.find(s, pos);
       l = (l == -1) ? str.length() : l;
       for (int i = pos; i < l ; i++)
       {
         str[i] = space;
         if (i == position)
           return true;
       }
       pos = l + s.length();
     }
   }
 }

 //Now replace the quoted strings with spaces
 const QRegExp strRx("(\"[^\"]*\"|'[^']*')");
 pos = 0;
 while (pos != -1)
 {
   pos = strRx.search(str, pos);
   if (pos != -1)
   {
    l = strRx.matchedLength();
    for (int i = pos; i < pos + l ; i++)
    {
      str[i] = space;
      if (i == position)
        return true;
    }
    pos += l;
   }
 }

  return false;
}

DCOPReply QuantaCommon::callDCOPMethod(const QString& interface, const QString& method, const QString& arguments)
{
  QStringList argumentList = QStringList::split(",", arguments, true);
  QString app = "quanta";
  if (!kapp->inherits("KUniqueApplication"))
  {
    pid_t pid = ::getpid();
    app += QString("-%1").arg(pid);
  }
  DCOPRef quantaRef(app.utf8(), interface.utf8());
  DCOPReply reply;
  int argumentCount = argumentList.count();
  if (argumentCount == 0)
  {
    reply = quantaRef.call(method.utf8());
  }
  else if (argumentCount == 1)
  {
    reply = quantaRef.call(method.utf8(), argumentList[0]);
  }
  else if (argumentCount == 2)
    reply = quantaRef.call(method.utf8(), argumentList[0], argumentList[1]);
  else if (argumentCount == 3)
    reply = quantaRef.call(method.utf8(), argumentList[0], argumentList[1], argumentList[2]);
  else if (argumentCount == 4)
    reply = quantaRef.call(method.utf8(), argumentList[0], argumentList[1], argumentList[2], argumentList[3]);
  else if (argumentCount == 5)
    reply = quantaRef.call(method.utf8(), argumentList[0], argumentList[1], argumentList[2], argumentList[3], argumentList[4]);
  else if (argumentCount == 6)
    reply = quantaRef.call(method.utf8(), argumentList[0], argumentList[1], argumentList[2], argumentList[3], argumentList[4], argumentList[5]);
  else if (argumentCount == 7)
    reply = quantaRef.call(method.utf8(), argumentList[0], argumentList[1], argumentList[2], argumentList[3], argumentList[4], argumentList[5], argumentList[6]);
  else if (argumentCount == 8)
    reply = quantaRef.call(method.utf8(), argumentList[0], argumentList[1], argumentList[2], argumentList[3], argumentList[4], argumentList[5], argumentList[6], argumentList[7]);

  return reply;
}

void QuantaCommon::normalizeStructure(QString f,QStringList& l)
{
 f.remove("\t");
 f.remove("\n");
 f.remove("\r");

 while(f.contains("<"))
  {
     QString z(f);
     z.truncate(z.find(">")+1);
     z.remove(0,z.find("<"));
     f.remove(0,f.find(">")+1);
     l.append(z);
  }
}

bool QuantaCommon::closesTag(Tag *tag1, Tag *tag2)
{
  if (tag1->nameSpace.isEmpty())
  {
    if (!tag2->nameSpace.isEmpty())
      return false; //namespace missmatch
    QString tag1Name = tag1->dtd()->caseSensitive ? tag1->name : tag1->name.upper();
    QString tag2Name = tag2->dtd()->caseSensitive ? tag2->name : tag2->name.upper();
    if ("/" + tag1Name != tag2Name)
      return false; //not the closing tag
  } else
  {
    if (tag2->nameSpace.isEmpty())
      return false; //namespace missmatch
    QString tag1Name = tag1->dtd()->caseSensitive ? (tag1->nameSpace + tag1->name) : (tag1->nameSpace.upper() + tag1->name.upper());
    QString tag2Name = tag2->dtd()->caseSensitive ? (tag2->nameSpace + tag2->name) : (tag2->nameSpace.upper() + tag2->name.upper());
    if ("/" + tag1Name != tag2Name)
      return false; //namespace missmatch or not the closing tag
  }
  return true;
}

bool QuantaCommon::closesTag(QString namespaceName, QString tagName, bool caseSensitive,
        QString namespaceName2, QString tagName2, bool caseSensitive2)
{
        QString tag1Name, tag2Name;
        if(namespaceName.isEmpty())
        {
                if(!namespaceName.isEmpty())
                        return false;//namespace missmatch
                tag1Name = caseSensitive ? tagName : tagName.upper();
                tag2Name = caseSensitive2 ? tagName2 : tagName2.upper();
                if("/" + tag1Name != tag2Name)
                        return false;//not the closing tag
        }
        else
        {
                if(namespaceName2.isEmpty())
                        return false;//namespace missmatch
                tag1Name = caseSensitive ? (namespaceName + tagName) : (namespaceName.upper() +
                        tagName.upper());
                tag2Name = caseSensitive2 ? (namespaceName2 + tagName2) : (namespaceName2.upper() +
                        tagName2.upper());
                if("/" + tag1Name != tag2Name)
                        return false; //namespace missmatch or not the closing tag
        }
        return true;
}

int QuantaCommon::denyBinaryInsert(QWidget *window)
{
  int result = KMessageBox::questionYesNo(window, i18n("The file type is not recognized. \
  Opening binary files may confuse Quanta.\n Are you sure you want to open this file?"),
  i18n("Unknown Type"), KStdGuiItem::open(), i18n("Do Not Open"), "Open Everything");
 return result;
}

bool QuantaCommon::checkOverwrite(const KURL& url, QWidget *window)
{
  bool result = true;

  if (QExtFileInfo::exists(url, false, window))
  {
    if (KMessageBox::warningContinueCancel(window,
            i18n( "<qt>The file <b>%1</b> already exists.<br>Do you want to overwrite it?</qt>" ).arg(url.prettyURL(0, KURL::StripFileProtocol)), QString::null, i18n("Overwrite")) == KMessageBox::Cancel)
            result = false;
  }

  return result;
}

QStringList QuantaCommon::readPathListEntry(KConfig *config, const QString &pKey)
{
  QStringList list = config->readPathListEntry(pKey);
  QStringList::Iterator end = list.end();
  for (QStringList::Iterator it = list.begin(); it != end; ++it)
  {
    KURL u = KURL::fromPathOrURL(*it);
    if (u.isValid() && u.isLocalFile())
    {
      u.setPath(QExtFileInfo::canonicalPath(u.path()));
      *it = u.url();
    }
  }
  return list;
}

QString QuantaCommon::readPathEntry(KConfig *config, const QString &pKey)
{
  QString path = config->readPathEntry(pKey);
  KURL u = KURL::fromPathOrURL(path);
  if (u.isValid() && u.isLocalFile())
  {
    u.setPath(QExtFileInfo::canonicalPath(u.path()));
    path = u.url();
  }
  return path;
}


