/***************************************************************************
                          dtdparser.cpp  -  description
                             -------------------
    begin                : Sun Oct 19 16:47:20 EEST 2003
    copyright            : (C) 2003 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.               *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

//qt includes
#include <tqcheckbox.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqlabel.h>
#include <tqlineedit.h>
#include <tqregexp.h>
#include <tqstring.h>

//kde includes
#include <tdeconfig.h>
#include <kdebug.h>
#include <kdialogbase.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kurl.h>
#include <tdeio/netaccess.h>

//other includes
#include <libxml/parser.h>
#include <libxml/valid.h>

//own includes
#include "dtepeditdlg.h"
#include "dtdparser.h"
#include "qtag.h"
#include "dtepcreationdlg.h"
#include "quantacommon.h"
#include "qextfileinfo.h"

#define MAX_CHILD_ELEMENTS 100

namespace DTD
{
  TQString dirName;
  xmlDtdPtr dtd_ptr; /* Pointer to the parsed DTD */
  TQTextStream entityStream;
}

void saveElement(xmlElementPtr elem, xmlBufferPtr buf);
void saveEntity(xmlEntityPtr entity, xmlBufferPtr buf);

DTDParser::DTDParser(const KURL& dtdURL, const TQString &dtepDir)
{
  m_dtdURL = dtdURL;
  m_dtepDir = dtepDir;
}

DTDParser::~DTDParser()
{
}

bool DTDParser::parse(const TQString &targetDir, bool entitiesOnly)
{
  bool fineTune = false;
  TQString fileName = TQString();
  if (!TDEIO::NetAccess::download(m_dtdURL, fileName, 0))
  {
    KMessageBox::error(0, i18n("<qt>Cannot download the DTD from <b>%1</b>.</qt>").arg( m_dtdURL.prettyURL(0, KURL::StripFileProtocol)));
    return false;
  }
  DTD::dtd_ptr = xmlParseDTD(NULL, xmlCharStrndup(fileName.utf8(), fileName.utf8().length()));
  if( DTD::dtd_ptr == NULL )
  {
    TQString errorStr = i18n("Unknown");
    xmlErrorPtr errorPtr = xmlGetLastError();
    if (errorPtr != NULL)
    {
      TQString s = TQString::fromLatin1(errorPtr->message);
      if (!s.isEmpty())
        errorStr = s;
      s = TQString::fromLatin1(errorPtr->str1);
      if (!s.isEmpty())
        errorStr += "<br>" + s;
      s = TQString::fromLatin1(errorPtr->str2);
      if (!s.isEmpty())
        errorStr += "<br>" + s;
      s = TQString::fromLatin1(errorPtr->str2);
      if (!s.isEmpty())
        errorStr += "<br>" + s;
      errorStr += TQString("(%1, %2)").arg(errorPtr->line).arg(errorPtr->int2);
      xmlResetError(errorPtr);
    }
    KMessageBox::error(0, i18n("<qt>Error while parsing the DTD.<br>The error message is:<br><i>%1</i></qt>").arg(errorStr));
    return false;
  }
  if (targetDir.isEmpty())
  {
    KDialogBase dlg(0L, 0L, true, i18n("DTD - > DTEP Conversion"), KDialogBase::Ok | KDialogBase::Cancel);
    DTEPCreationDlg w(&dlg);
    dlg.setMainWidget(&w);
    TQString name = TQString((const char*)DTD::dtd_ptr->name);
    if (name == "none")
      name = TQFileInfo(m_dtdURL.fileName()).baseName();
    w.dtdName->setText(name);
    w.nickName->setText(name);
    w.directory->setText(TQFileInfo(m_dtdURL.fileName()).baseName());
    w.doctype->setText(TQString((const char*)DTD::dtd_ptr->ExternalID));
    w.dtdURL->setText(TQString((const char*)DTD::dtd_ptr->SystemID));
    if (!dlg.exec())
        return false;
    m_name = w.dtdName->text();
    m_nickName = w.nickName->text();
    m_doctype = w.doctype->text();
    m_doctype.replace(TQRegExp("<!doctype", false), "");
    m_doctype = m_doctype.left(m_doctype.findRev(">"));
    m_dtdURLLine = w.dtdURL->text();
    m_defaultExtension = w.defaultExtension->text();
    m_caseSensitive = w.caseSensitive->isChecked();
    DTD::dirName = m_dtepDir + "/" + w.directory->text();
    fineTune = w.fineTune->isChecked();
  } else
    DTD::dirName = targetDir;
  KURL u;
  u.setPath(DTD::dirName);
  if (!QExtFileInfo::createDir(u, 0L)) {
    QuantaCommon::dirCreationError(0L, u);
    return false;
  }
  DTD::dirName.append("/");
  if (DTD::dtd_ptr->entities)
  {
    TQFile file( DTD::dirName + "entities.tag" );
    if ( file.open( IO_WriteOnly ) )
    {
      DTD::entityStream.setDevice(TQT_TQIODEVICE(&file));
      DTD::entityStream.setEncoding(TQTextStream::UnicodeUTF8);
      DTD::entityStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
      DTD::entityStream << "<!DOCTYPE TAGS>" << endl
          << "<TAGS>" << endl;
      xmlHashScan((xmlEntitiesTablePtr)DTD::dtd_ptr->entities, (xmlHashScanner)saveEntity, 0);
      DTD::entityStream << "</TAGS>" << endl;
      file.close();
    } else
    {
      KMessageBox::error(0L, i18n("<qt>Cannot create the <br><b>%1</b> file.<br>Check that you have write permission in the parent folder.</qt>")
                             .arg(file.name()));
      return false;
    }
  }
  if (!entitiesOnly)
  {
    if (DTD::dtd_ptr->elements)
    {
      xmlHashScan((xmlElementTablePtr)DTD::dtd_ptr->elements, (xmlHashScanner)saveElement, 0);
    } else
    {
      KMessageBox::error(0, i18n("No elements were found in the DTD."));
      return false;
    }
  }
  xmlFreeDtd(DTD::dtd_ptr);
  if (!entitiesOnly)
  {
    writeDescriptionRC();
    if (fineTune)
    {
      KDialogBase editDlg(0L, "edit_dtep", true, i18n("Configure DTEP"), KDialogBase::Ok | KDialogBase::Cancel);
      DTEPEditDlg dtepDlg(DTD::dirName + "description.rc", &editDlg);
      editDlg.setMainWidget(&dtepDlg);
      if (editDlg.exec())
      {
        dtepDlg.saveResult();
      }
    }
  }
  return true;
}

void DTDParser::writeDescriptionRC()
{
  TDEConfig config(DTD::dirName + "description.rc");
  config.setGroup("General");
  config.writeEntry("Name", m_name);
  config.writeEntry("NickName", m_nickName);
  config.writeEntry("DoctypeString", m_doctype);
  config.writeEntry("URL", m_dtdURLLine);
  config.writeEntry("DefaultExtension", m_defaultExtension);
  config.writeEntry("Family", "1");
  config.writeEntry("CaseSensitive", m_caseSensitive);
//   config.setGroup("Parsing rules");
//   config.writeEntry("SpecialAreas","<!-- -->,<?xml ?>,<!DOCTYPE >");
//   config.writeEntry("SpecialAreaNames","comment,XML PI,DTD");

  config.sync();
}

void saveElement(xmlElementPtr elem, xmlBufferPtr buf)
{
  Q_UNUSED(buf);
  if (elem)
  {
    TQString elemName = TQString((const char*)elem->name);
    TQFile file( DTD::dirName + elemName + ".tag" );
    if ( file.open( IO_WriteOnly ) )
    {
      TQTextStream stream( &file );
      stream.setEncoding(TQTextStream::UnicodeUTF8);
      stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
      stream << "<!DOCTYPE TAGS>" << endl
             << "<TAGS>" << endl
             << "<tag name=\"" << elemName << "\">" << endl << endl;

      xmlElementPtr  el_ptr; /* Pointer to an element description */
      xmlAttributePtr at_ptr;
      el_ptr = xmlGetDtdElementDesc(DTD::dtd_ptr, elem->name);
      AttributeList attributes;
      attributes.setAutoDelete(true);
      if (el_ptr)
      {
        at_ptr = el_ptr->attributes;
        while (at_ptr) {
          Attribute *attr =  new Attribute;
          attr->name = TQString((const char*)at_ptr->name);
          switch (at_ptr->def) {
            case 1: {attr->status = "optional"; break;} //NONE
            case 2: {attr->status = "required"; break;} //REQUIRED
            case 3: {attr->status = "implied"; break;} //IMPLIED
            case 4: {attr->status = "fixed"; break;} //FIXED
          }
          attr->defaultValue = TQString((const char*)at_ptr->defaultValue);
          xmlEnumerationPtr enum_ptr;
          enum_ptr = at_ptr->tree;
          while (enum_ptr) {
            attr->values += TQString((const char*)enum_ptr->name);
            enum_ptr = enum_ptr->next;
          }
          TQString attrtype;
          switch (at_ptr->atype) {
            case 9: {attrtype = "list"; break;}
            default: {attrtype = "input"; break;} //TODO handle the rest of types
          }
          attr->type = attrtype;
          attributes.append(attr);
          at_ptr = at_ptr->nexth;
       }

       if (!attributes.isEmpty())
          stream << QuantaCommon::xmlFromAttributes(&attributes);
       const xmlChar *list_ptr[MAX_CHILD_ELEMENTS];
       int childNum = 0;
       childNum = xmlValidGetPotentialChildren(el_ptr->content, list_ptr,
                          &childNum, MAX_CHILD_ELEMENTS);

        if (childNum > 0)
        {
          stream << "<children>" << endl;
          for( int i = 0; i < childNum; i++ )
          {
            stream << "  <child name=\"" << TQString((const char*)list_ptr[i]) << "\"";
            xmlElementPtr child_ptr = xmlGetDtdElementDesc(DTD::dtd_ptr, list_ptr[i]);
            if (child_ptr && child_ptr->content && child_ptr->content->ocur)
            {
              //if (child_ptr->content->ocur == XML_ELEMENT_CONTENT_PLUS)
              //{
  //              stream << " usage=\"required\"";
    //          }
              TQString ocur;
              switch (child_ptr->content->ocur)
              {
                case 1: {ocur = "once"; break;}
                case 2: {ocur = "opt"; break;}
                case 3: {ocur = "mult"; break;}
                case 4: {ocur = "plus"; break;}
              }
              stream << " usage=\"" << ocur << "\"";
              TQString name = TQString((const char*)child_ptr->content->name);
              if (name == "#PCDATA")
                name == "#text";
              stream << " name2=\"" << name << "\"";
            }
            stream << " />" << endl;
          }

          stream << "</children>" << endl;
          stream << endl;
        }
        /*
        xmlElementContentPtr content_ptr = el_ptr->content;
        if (content_ptr)
        {
          stream << "<children>" << endl;
          while (content_ptr)
          {
            if (!TQString((const char*)content_ptr->name).isEmpty())
            {
              stream << "  <child name=\"" << TQString((const char*)content_ptr->name) << "\"";
              TQString ocur;
              switch (content_ptr->ocur)
              {
                case 1: {ocur = "once"; break;}
                case 2: {ocur = "opt"; break;}
                case 3: {ocur = "mult"; break;}
                case 4: {ocur = "plus"; break;}
              }
              stream << " usage=\"" << ocur << "\"";
              stream << " />" << endl;
            }
            if (content_ptr->c1)
                content_ptr = content_ptr->c1;
            else if (content_ptr->c2)
                content_ptr = content_ptr->c2;
            else
            {
              if (content_ptr == el_ptr->content)
                break;
              if (content_ptr->parent)
              {
                if (content_ptr == content_ptr->parent->c1)
                    content_ptr->c1 = 0L;
                else
                    content_ptr->c2 = 0L;
              }
              content_ptr = content_ptr->parent;
            }
          }
          stream << "</children>" << endl;
        } */
      }
      stream << "</tag>" << endl
             << "</TAGS>" << endl;
      file.close();
    }
 }
}

void saveEntity(xmlEntityPtr entity, xmlBufferPtr buf)
{
  Q_UNUSED(buf);
  if (entity)
  {
    TQString name = TQString((const char*)entity->name);
    DTD::entityStream << "<tag name=\"" << name << "\" type=\"entity\" />" << endl << endl;
  }
}

TQString DTDParser::dirName()
{
  return DTD::dirName;
}

