/***************************************************************************
 *   Copyright (C) 2004 by Alexander Dymo                                  *
 *   cloudtemple@mksat.net                                                 *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/
#include "docdoxygenplugin.h"

#include <unistd.h>

#include <tqdom.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqdialog.h>
#include <tqregexp.h>
#include <tqvaluestack.h>

#include <kurl.h>
#include <tdeaboutdata.h>
#include <tdeconfig.h>
#include <tdelocale.h>
#include <kstandarddirs.h>

#include <urlutil.h>
#include <kdevgenericfactory.h>
#include <kdevplugininfo.h>

#include "../../../../config.h"

class DoxyDocumentationCatalogItem: public DocumentationCatalogItem
{
public:
    DoxyDocumentationCatalogItem(const TQString &origUrl, DocumentationPlugin* plugin,
        TDEListView *parent, TDEListViewItem *after, const TQString &name)
        :DocumentationCatalogItem(plugin, parent, after, name), m_origUrl(origUrl)
    {
    }
    DoxyDocumentationCatalogItem(const TQString &origUrl, DocumentationPlugin* plugin,
        DocumentationItem *parent, const TQString &name)
        :DocumentationCatalogItem(plugin, parent, name), m_origUrl(origUrl)
    {
    }
    TQString origUrl() const { return m_origUrl; }
    
private:
    TQString m_origUrl;
};


static const KDevPluginInfo data("docdoxygenplugin");
typedef KDevGenericFactory<DocDoxygenPlugin> DocDoxygenPluginFactory;
K_EXPORT_COMPONENT_FACTORY( libdocdoxygenplugin, DocDoxygenPluginFactory(data) )

DocDoxygenPlugin::DocDoxygenPlugin(TQObject* parent, const char* name, const TQStringList)
    :DocumentationPlugin(DocDoxygenPluginFactory::instance()->config(), parent, name)
{
    setCapabilities(Index | FullTextSearch | ProjectDocumentation);
    autoSetup();
}

DocDoxygenPlugin::~DocDoxygenPlugin()
{
}

TQPair<KFile::Mode, TQString> DocDoxygenPlugin::catalogLocatorProps()
{
    return TQPair<KFile::Mode, TQString>(KFile::File, "index.html *.tag");
}

TQString DocDoxygenPlugin::catalogTitle(const TQString& url)
{
    TQFileInfo fi(url);
    if (!fi.exists())
        return TQString();
    
    if (fi.extension(false) == "html")
    {
        TQFile f(url);
        if (!f.open(IO_ReadOnly))
            return TQString();
        
        TQTextStream ts(&f);
        TQString contents = ts.read();
        TQRegExp re(".*<title>(.*)</title>.*");
        re.setCaseSensitive(false);
        re.search(contents);
        return re.cap(1);   
    }
    else if (fi.extension(false) == "tag")
    {
        TQFile *f = 0;
        TQFile f1(fi.dirPath(true) + "/html/index.html");
        if (f1.open(IO_ReadOnly))
            f = &f1;
        TQFile f2(fi.dirPath(true) + "/index.html");
        if (f2.open(IO_ReadOnly))
            f = &f2;
        if (f != 0)
        {
            TQTextStream ts(f);
            TQString contents = ts.read();
            TQRegExp re(".*<title>(.*)</title>.*");
            re.setCaseSensitive(false);
            re.search(contents);
            return re.cap(1);   
        }
    }
    return TQString();
}

TQString DocDoxygenPlugin::pluginName() const
{
    return i18n("Doxygen Documentation Collection");
}

TQStringList DocDoxygenPlugin::fullTextSearchLocations()
{
    TQStringList locs;
        
    TQMap<TQString, TQString> entryMap = config->entryMap("Locations");

    for (TQMap<TQString, TQString>::const_iterator it = entryMap.begin();
        it != entryMap.end(); ++it)
    {
        config->setGroup("Search Settings");
        if (config->readBoolEntry(it.key(), false))
        {
            config->setGroup("Locations");
            TQFileInfo fi(config->readPathEntry(it.key()));
            locs << fi.dirPath(true);
        }
    }
    
    return locs;
}

void DocDoxygenPlugin::setCatalogURL(DocumentationCatalogItem* item)
{
    if (item->url().url().endsWith("tag"))
    {
        TQFileInfo fi(item->url().directory(false) + "html/index.html");
        if (fi.exists())
        {
            item->setURL(KURL::fromPathOrURL(fi.absFilePath()));
            return;
        }
        TQFileInfo fi2(item->url().directory(false) + "index.html");
        if (fi2.exists())
        {
            item->setURL(KURL::fromPathOrURL(fi2.absFilePath()));
            return;
        }
        item->setURL(KURL());
    }
}

bool DocDoxygenPlugin::needRefreshIndex(DocumentationCatalogItem* item)
{
    DoxyDocumentationCatalogItem *doxyItem = dynamic_cast<DoxyDocumentationCatalogItem*>(item);
    if (!doxyItem)
        return false;
    
    TQFileInfo fi(doxyItem->origUrl());
    config->setGroup("Index");
    if (fi.lastModified() > config->readDateTimeEntry(item->text(0), new TQDateTime()))
    {
        kdDebug() << "need rebuild index for " << item->text(0) << endl;
        config->writeEntry(item->text(0), fi.lastModified());
        return true;
    }
    else
        return false;
}

void DocDoxygenPlugin::autoSetupPlugin()
{
    // Clear groups, to allow for re-autosetup calls
    config->deleteGroup("Index");
    config->deleteGroup("Index Settings");
    config->deleteGroup("Locations");
    config->deleteGroup("Search Settings");
    config->deleteGroup("TOC Settings");
    config->sync();
    
    autoSetupDocs(TDELIBS_DOXYDIR, "en/tdelibs-apidocs", "The TDE API Reference");
    autoSetupDocs("", "en/tdevelop-apidocs", "The TDevelop Platform API Documentation");
    config->sync();
}

void DocDoxygenPlugin::autoSetupDocs(const TQString &defaultDir, const TQString &searchDir,
    const TQString &name)
{
    TQString doxyDocDir(defaultDir);
    doxyDocDir = URLUtil::envExpand(doxyDocDir);
    if (doxyDocDir.isEmpty())
    {
        TQStringList apiDirs = DocDoxygenPluginFactory::instance()->dirs()->findDirs("html", searchDir);
        for (TQStringList::const_iterator it = apiDirs.begin(); it != apiDirs.end(); ++it )
        {
            doxyDocDir = *it;
            TQString indexFile = doxyDocDir + "index.html";
            if (TQFile::exists(indexFile))
            {
                doxyDocDir = doxyDocDir + "/" + searchDir;
                break;
            }
            doxyDocDir = "";
        }
    }
    if (!doxyDocDir.isEmpty())
    {
        config->setGroup("Search Settings");
        config->writeEntry(name, true);
        config->setGroup("Index Settings");
        config->writeEntry(name, true);
        config->setGroup("Locations");
        config->writePathEntry(name, doxyDocDir + TQString("/index.html"));
    }
}

void DocDoxygenPlugin::createIndex(IndexBox* index, DocumentationCatalogItem* item)
{
    TQFileInfo fi(item->url().path());
    if (!fi.exists())
        return;

    DoxyDocumentationCatalogItem *doxyItem = dynamic_cast<DoxyDocumentationCatalogItem*>(item);
    if (!doxyItem)
        return;
    
    //doxygen documentation mode (if catalog points to a .tag)
    if (doxyItem->origUrl().endsWith("tag"))
    {
        TQString htmlUrl;
        TQFileInfo fi2(item->url().directory(false) + "index.html");
        if (fi2.exists())
            htmlUrl = fi2.dirPath(true) + "/";
        TQFileInfo fi(item->url().directory(false) + "html/index.html");
        if (fi.exists())
            htmlUrl = fi.dirPath(true) + "/";
        
        createBookIndex(doxyItem->origUrl(), index, item, htmlUrl);
    }
    
    //KDE doxygen documentation mode (if catalog points to a index.html)
    TQDir d;
    TQValueStack<TQString> dirStack;
    dirStack.push(fi.dirPath(true));
    do {
        d.setPath(dirStack.pop());
        if (!d.exists())
            continue;

        const TQFileInfoList *dirEntries = d.entryInfoList();
        if (!dirEntries) continue;
        TQPtrListIterator<TQFileInfo> it(*dirEntries);
        for (; it.current(); ++it)
        {
            TQString fileName = it.current()->fileName();
            if (fileName == "." || fileName == ".." || fileName == "common" || fileName == "html")
                continue;
            if (it.current()->isDir())
                dirStack.push(it.current()->absFilePath());
        }

        if (TQFile::exists(d.absPath() + "/html/index.html"))
            createBookIndex(d.absPath() + "/" + d.dirName() + ".tag", index, item);
    } while (!dirStack.isEmpty());
}

void DocDoxygenPlugin::createTOC(DocumentationCatalogItem* item)
{
    TQFileInfo fi(item->url().path());
    if (!fi.exists())
        return;
    
    DoxyDocumentationCatalogItem *doxyItem = dynamic_cast<DoxyDocumentationCatalogItem*>(item);
    if (!doxyItem)
        return;
    
    //doxygen documentation mode (if catalog points to a .tag)
    if (doxyItem->origUrl().endsWith("tag"))
    {
        TQString htmlUrl;
        TQFileInfo fi2(item->url().directory(false) + "index.html");
        if (fi2.exists())
            htmlUrl = fi2.dirPath(true) + "/";
        TQFileInfo fi(item->url().directory(false) + "html/index.html");
        if (fi.exists())
            htmlUrl = fi.dirPath(true) + "/";
        if (!htmlUrl.isEmpty())
            createBookTOC(item, doxyItem->origUrl(), htmlUrl);
    }
    
    //KDE doxygen documentation mode (if catalog points to a index.html)
    TQDir d;
    TQValueStack<TQString> dirStack;
    dirStack.push(fi.dirPath(true));
    do {
        d.setPath(dirStack.pop());
        if (!d.exists())
            continue;

        const TQFileInfoList *dirEntries = d.entryInfoList();
        if (!dirEntries) continue;
        TQPtrListIterator<TQFileInfo> it(*dirEntries);
        for (; it.current(); ++it)
        {
            TQString fileName = it.current()->fileName();
            if (fileName == "." || fileName == ".." || fileName == "common" || fileName == "html")
                continue;
            if (it.current()->isDir())
                dirStack.push(it.current()->absFilePath());
        }

        if (TQFile::exists(d.absPath() + "/html/index.html"))
        {
            DocumentationItem *docItem = new DocumentationItem(DocumentationItem::Book, item, d.dirName());
            docItem->setURL(KURL(d.absPath() + "/html/index.html"));
            docItem->setExpandable(true);
            createBookTOC(docItem);
        }
    } while (!dirStack.isEmpty());
}

DocumentationCatalogItem *DocDoxygenPlugin::createCatalog(TDEListView *contents, TDEListViewItem *after, const TQString &title, const TQString &url)
{
    kdDebug() << "DocDoxygenPlugin::createCatalog: url=" << url << endl;
    DocumentationCatalogItem *item = new DoxyDocumentationCatalogItem(url, this, contents, after, title);
    item->setURL(url);
    return item;
}

void DocDoxygenPlugin::createBookTOC(DocumentationItem *item, const TQString &tagUrl, const TQString &baseHtmlUrl)
{
    TQString tagName;
    if (tagUrl.isEmpty())
        tagName = item->url().upURL().directory(false) + item->text(0) + ".tag";
    else
        tagName = tagUrl;
    
    TQString baseUrl;
    if (baseHtmlUrl.isEmpty())
        baseUrl = item->url().directory(false);
    else
        baseUrl = baseHtmlUrl;
            
    //@todo list html files in the directory if tag was not found
    if (!TQFile::exists(tagName))
        return;

    TQStringList tagFileList;
    if (tagName.endsWith(".tag"))
        tagFileList = tagFiles(TQFileInfo(tagName).dirPath() + "/");
    else
        tagFileList += tagName;

    TQStringList::ConstIterator end = tagFileList.constEnd();
    for (TQStringList::ConstIterator it = tagFileList.constBegin(); it != end; ++it)
    {
        TQFile f(*it);
        if (!f.open(IO_ReadOnly))
        {
            kdDebug(9002) << "Could not open tag file: " << f.name() << endl;
            return;
        }
    
        TQDomDocument dom;
        if (!dom.setContent(&f) || dom.documentElement().nodeName() != "tagfile")
        {
            kdDebug(9002) << "No valid tag file" << endl;
            return;
        }
        f.close();
    
        TQDomElement docEl = dom.documentElement();
    
        TQDomElement childEl = docEl.lastChild().toElement();
        while (!childEl.isNull())
        {
            if (childEl.tagName() == "compound" && childEl.attribute("kind") == "class")
            {
                TQString classname = childEl.namedItem("name").firstChild().toText().data();
                TQString filename = childEl.namedItem("filename").firstChild().toText().data();
    
                if (TQFile::exists(baseUrl + filename))
                {
                    DocumentationItem *docItem = new DocumentationItem(DocumentationItem::Document,
                        item, classname);
                    docItem->setURL(KURL(baseUrl + filename));
                }
            }
            childEl = childEl.previousSibling().toElement();
        }
    }
}

void DocDoxygenPlugin::createBookIndex(const TQString &tagfile, IndexBox* index, DocumentationCatalogItem* item, const TQString &baseHtmlUrl)
{
    TQString tagName = tagfile;
    kdDebug() << tagfile << endl;
    if (!TQFile::exists(tagName))
        return;
    TQString prefix = baseHtmlUrl.isEmpty() ? KURL(tagfile).directory(false) + "html/" : baseHtmlUrl;
    
    TQStringList tagFileList = tagFiles(TQFileInfo(tagName).dirPath() + "/");

    TQStringList::ConstIterator end = tagFileList.constEnd();
    for (TQStringList::ConstIterator it = tagFileList.constBegin(); it != end; ++it)
    {
        TQFile f(*it);
        if (!f.open(IO_ReadOnly))
        {
            kdDebug(9002) << "Could not open tag file: " << f.name() << endl;
            return;
        }

        TQDomDocument dom;
        if (!dom.setContent(&f) || dom.documentElement().nodeName() != "tagfile")
        {
            kdDebug(9002) << "No valid tag file" << endl;
            return;
        }
        f.close();

        TQDomElement docEl = dom.documentElement();
        createIndexFromTag(dom, index, item, docEl, prefix);
  }
}

void DocDoxygenPlugin::createIndexFromTag(TQDomDocument &dom, IndexBox *index,
    DocumentationCatalogItem *item, TQDomElement &parentEl, const TQString &prefix)
{
    TQDomElement docEl = parentEl;

    TQDomElement childEl = docEl.firstChild().toElement();
    while (!childEl.isNull())
    {
        if (childEl.tagName() == "compound" && 
            ((childEl.attribute("kind") == "class")
            || (childEl.attribute("kind") == "struct")
            || (childEl.attribute("kind") == "namespace") ))
        {
            TQString classname = childEl.namedItem("name").firstChild().toText().data();
            TQString filename = childEl.namedItem("filename").firstChild().toText().data();

            IndexItemProto *indexItem = new IndexItemProto(this, item, index, classname, 
            i18n("%1 Class Reference").arg(classname));
            indexItem->addURL(KURL(prefix + filename));
            
            createIndexFromTag(dom, index, item, childEl, prefix + filename);
        }
        else if ((childEl.tagName() == "member") && 
            ((childEl.attribute("kind") == "function")
            || (childEl.attribute("kind") == "slot")
            || (childEl.attribute("kind") == "signal") ))
        {
            TQString classname = parentEl.namedItem("name").firstChild().toText().data();
            TQString membername = childEl.namedItem("name").firstChild().toText().data();
            TQString anchor = childEl.namedItem("anchor").firstChild().toText().data();
            TQString arglist = childEl.namedItem("arglist").firstChild().toText().data();
            
            if (classname != membername)
            {
                IndexItemProto *indexItem = new IndexItemProto(this, item, index, membername,i18n("%1::%2%3 Member Reference").arg(classname).arg(membername).arg(arglist));
                indexItem->addURL(KURL(prefix + "#" + anchor));
            }
        }
        childEl = childEl.nextSibling().toElement();
    }
}

ProjectDocumentationPlugin *DocDoxygenPlugin::projectDocumentationPlugin(ProjectDocType type)
{
    if (type == APIDocs)
        return new ProjectDocumentationPlugin(this, type);
    return DocumentationPlugin::projectDocumentationPlugin(type);
}

TQStringList DocDoxygenPlugin::tagFiles(const TQString& path, int level)
{
  TQStringList r;
  TQDir dir(path);
  if (level > 10) return r;
  if (!dir.isReadable()) return r;
  if (!dir.exists()) return r;

  TQStringList  dirList;
  TQStringList  fileList;
  TQStringList::Iterator it;

  dir.setFilter ( TQDir::Dirs);
  dirList = dir.entryList();

  dirList.remove(".");
  dirList.remove("..");

  dir.setFilter(TQDir::Files | TQDir::Hidden | TQDir::System);
  fileList = dir.entryList();
  TQStringList::Iterator end = dirList.end();
  for ( it = dirList.begin(); it != end; ++it )
  {
    TQString name = *it;
    if (TQFileInfo( dir, *it ).isSymLink())
      continue;
    r += tagFiles(path + name + "/", level + 1 );
  }

  TQStringList::Iterator fend = fileList.end();
  for ( it = fileList.begin(); it != fend; ++it )
  {
    TQString name = *it;
    TQFileInfo fi( dir, *it );
    if (fi.isSymLink() || !fi.isFile())
      continue;

    if (TQDir::match(TQString("*.tag"), name))
      r += (path+name);
  }

  return r;
}

#include "docdoxygenplugin.moc"
