/***************************************************************************
                          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 <tqstringlist.h>
#include <tqdict.h>
#include <tqdir.h>
#include <tqdom.h>
#include <tqfile.h>
#include <tqtextstream.h>
#include <tqwidget.h>

//kde includes
#include <tdeapplication.h>
#include <dcopref.h>
#include <kurl.h>
#include <kdirwatch.h>
#include <kmimetype.h>
#include <tdestandarddirs.h>
#include <tdemessagebox.h>
#include <tdeio/netaccess.h>
#include <tdelocale.h>
#include <kprotocolinfo.h>
#include <kprogress.h>
#include <ktempdir.h>
#include <tdetempfile.h>

//remove the below ones when KTQPasteAction is removed
#include <dcopclient.h>
#include <kdebug.h>
#include <tdepopupmenu.h>
#include <tdetoolbar.h>
#include <tdetoolbarbutton.h>
#include <kstringhandler.h>
#include <tqwhatsthis.h>
#include <tqtimer.h>
#include <tqclipboard.h>
#include <tqdatastream.h>

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


TQConfig qConfig; //holds the main configuration settings
TQString tmpDir;

TQRegExp scriptBeginRx;
TQRegExp scriptEndRx;

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

TQString toolbarExtension = ".toolbar.tgz";
TQRegExp newLineRx("\\n");
TQStringList charList; //hold the list of the &char; chars. See the data/chars file.
TQMap<int, TQString> replacementMap;
TQPtrList<KTempFile> tempFileList;
TQPtrList<KTempDir> tempDirList;
bool typingInProgress;

QuantaCommon::QuantaCommon(){
}

QuantaCommon::~QuantaCommon(){
}

/** convert tag to upper or lower case */
TQString QuantaCommon::tagCase( const TQString& tag)
{
  TQString 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 */
TQString QuantaCommon::attrCase( const TQString& attr)
{
  TQString 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*/
TQString QuantaCommon::quoteAttributeValue(const TQString& value)
{
  TQString quote = qConfig.attrValueQuotation;
  return quote + value + quote;
}

/** Set's up the url correctly from urlString. */
void QuantaCommon::setUrl(KURL &url, const TQString& 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 TQString& dtdName, const TQString& 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())
  {
    TQString searchForTag = (dtd->caseSensitive) ? tag : tag.upper();
    TQTag* qtag = dtd->tagsList->find(searchForTag);
    if (qtag)
        single = qtag->isSingle();
  }

  return single;
}

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

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

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

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

  return known;
}

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

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

  return attrs;
}

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

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

 return qtag;
}

/** Returns the TQTag object for the node "node" from node's DTD. */
TQTag* 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. */
TQString QuantaCommon::xmlFromAttributes(AttributeList* attributes)
{
 TQString xmlStr;
 TQTextStream stream( &xmlStr, IO_WriteOnly );
 stream.setEncoding(TQTextStream::UnicodeUTF8);
 if (attributes)
 {
  int row = 0;
  for ( uint i = 0; i< attributes->count();i++)
  {
    Attribute *attribute = attributes->at(i);
    TQString 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 TDEStandardDirs object usable for plugin searchup. type
is the plugin binary type (exe or lib). The returned pointer must be deleted by
the caller!! */
TDEStandardDirs* QuantaCommon::pluginDirs(const char *type)
{
 TDEStandardDirs *dirs = new TDEStandardDirs();
 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 TQString& group)
{
 KMimeType::List list = KMimeType::allMimeTypes();
 KMimeType::List::iterator it;
 bool status = false;
 KMimeType::Ptr mime = KMimeType::findByURL(url);
 TQString 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
   {
     TQVariant v = mime->property("X-TDE-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 TQString& type)
{
 bool status = false;
 TQString 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 TQString& type)
{
 bool status = false;
 TQString 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 */
TQString QuantaCommon::qUrl(const KURL &url)
{
  TQString result = url.path();
  if (url.path().startsWith("/")) result = url.url();

  return result;
}
/** No descriptions */
void QuantaCommon::dirCreationError(TQWidget *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.*/
TQString QuantaCommon::makeRxCompatible(const TQString& s)
{
  const uint max = 7;
  const TQRegExp rxs[max]={TQRegExp("\\?"),
                        TQRegExp("\\*"),
                        TQRegExp("\\."),
                        TQRegExp("\\^"),
                        TQRegExp("\\$"),
                        TQRegExp("\\{"),
                        TQRegExp("\\}")
                        };
  const TQString strs[max]={TQString("\\?"),
                         TQString("\\*"),
                         TQString("\\."),
                         TQString("\\^"),
                         TQString("\\$"),
                         TQString("\\{"),
                         TQString("\\}")
                         };
  TQString 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. */
TQString QuantaCommon::i18n2normal(const TQString& a_str)
{
//TODO: a TQMap lookup would be faster, but we need a pre-built TQMap<TQString,TQString>
  const int keywordNum = 15 *5;
  const TQString 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" };
  TQString 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 TQChar space(' ');

void QuantaCommon::removeCommentsAndQuotes(TQString &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;
 TQString 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
 TQRegExp 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 TQString &string, const DTDStruct *dtd)
{
 //Return true if position is inside a commented or quoted string
 TQString str = string;
 int pos = 0;
 int l;
 TQString 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 TQRegExp 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 TQString& interface, const TQString& method, const TQString& arguments)
{
  TQStringList argumentList = TQStringList::split(",", arguments, true);
  TQString app = "quanta";
  if (!tdeApp->inherits("TDEUniqueApplication"))
  {
    pid_t pid = ::getpid();
    app += TQString("-%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(TQString f,TQStringList& l)
{
 f.remove("\t");
 f.remove("\n");
 f.remove("\r");

 while(f.contains("<"))
  {
     TQString 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
    TQString tag1Name = tag1->dtd()->caseSensitive ? tag1->name : tag1->name.upper();
    TQString 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
    TQString tag1Name = tag1->dtd()->caseSensitive ? (tag1->nameSpace + tag1->name) : (tag1->nameSpace.upper() + tag1->name.upper());
    TQString 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(TQString namespaceName, TQString tagName, bool caseSensitive,
        TQString namespaceName2, TQString tagName2, bool caseSensitive2)
{
        TQString 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(TQWidget *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, TQWidget *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)), TQString(), i18n("Overwrite")) == KMessageBox::Cancel)
            result = false;
  }

  return result;
}

TQStringList QuantaCommon::readPathListEntry(TDEConfig *config, const TQString &pKey)
{
  TQStringList list = config->readPathListEntry(pKey);
  TQStringList::Iterator end = list.end();
  for (TQStringList::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;
}

TQString QuantaCommon::readPathEntry(TDEConfig *config, const TQString &pKey)
{
  TQString 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;
}

TQString QuantaCommon::encodedChar(uint code)
{
   
  if (replacementMap.contains(code))
    return TQString("%1;").arg(replacementMap[code]);
  else
    return TQString("&#%1;").arg(code);
}

