/* This file is part of the KDE project
   Copyright (C) 2004 by Alexander Dymo <cloudtemple@mksat.net>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/
#include "kdevdocumentationplugin.h"

#include <tqfile.h>
#include <tqpainter.h>
#include <tqstyle.h>
#include <tqheader.h>
#include <tqtextstream.h>

#include <kstandarddirs.h>
#include <kiconloader.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <kdirwatch.h>
#include <tdelocale.h>

//class DocumentationItem

DocumentationItem::DocumentationItem(DocumentationItem::Type type, TDEListView *parent,
    const TQString &name)
    :TDEListViewItem(parent, name), m_type(type)
{
    init();
}

DocumentationItem::DocumentationItem(DocumentationItem::Type type, TDEListViewItem *parent,
    const TQString &name)
    :TDEListViewItem(parent, name), m_type(type)
{
    init();
}

DocumentationItem::DocumentationItem(DocumentationItem::Type type, TDEListView *parent, 
    TDEListViewItem *after, const TQString &name)
    :TDEListViewItem(parent, after, name), m_type(type)
{
    init();
}

DocumentationItem::DocumentationItem(DocumentationItem::Type type, TDEListViewItem * parent,
    TDEListViewItem * after, const TQString & name )
    :TDEListViewItem(parent, after, name), m_type(type)
{
    init();
}

void DocumentationItem::init( )
{
    TQString icon;
    
    switch (m_type)
    {
        case Collection:
        case Catalog:
            icon = "folder";
            break;
        case Book:
            icon = "contents";
            break;
        default:
            icon = "text-x-generic";
    }
    
    setPixmap(0, SmallIcon(icon));
}


//----------------------------------------------------
//class DocumentationCatalogItem
  
DocumentationCatalogItem::DocumentationCatalogItem(DocumentationPlugin* plugin,
    TDEListView *parent, TDEListViewItem *after, const TQString &name)
    :DocumentationItem(DocumentationItem::Catalog, parent, after, name), m_plugin(plugin),
    isLoaded(false), isActivated(false), m_isProjectDocumentationItem(false)
{
    setExpandable(true);
    m_plugin->addCatalog(this);
}

DocumentationCatalogItem::DocumentationCatalogItem(DocumentationPlugin* plugin,
    DocumentationItem *parent, const TQString &name)
    :DocumentationItem(DocumentationItem::Catalog, parent, name), m_plugin(plugin),
    isLoaded(false), isActivated(false), m_isProjectDocumentationItem(false)
{
    setExpandable(true);
    m_plugin->addCatalog(this);
}

DocumentationCatalogItem::~DocumentationCatalogItem()
{
    m_plugin->clearCatalog(this);
}

void DocumentationCatalogItem::setOpen(bool o)
{
    if (o)
    {
        load();
    }
    DocumentationItem::setOpen(o);
}

void DocumentationCatalogItem::load()
{
if(isLoaded)
return;

        plugin()->createTOC(this);
        isLoaded = true;
}

void DocumentationCatalogItem::activate()
{
    if (!isActivated)
    {
        plugin()->setCatalogURL(this);
        isActivated = true;
    }
    DocumentationItem::activate();
}


//----------------------------------------------------
//class IndexItemProto

IndexItemProto::IndexItemProto(DocumentationPlugin *plugin, DocumentationCatalogItem *catalog, 
    IndexBox *listbox, const TQString &text, const TQString &description)
    : m_listbox(listbox), m_text(text), m_description(description)
{
    plugin->indexes[catalog].append(this);
    m_listbox->addIndexItem(this);
}

IndexItemProto::~IndexItemProto()
{
    m_listbox->removeIndexItem(this);
}


//----------------------------------------------------
//class IndexItem

IndexItem::IndexItem(IndexBox *listbox, const TQString &text)
    :TQListBoxText(listbox, text), m_listbox(listbox)
{
}

IndexItem::List IndexItem::urls() const
{
    List urlList;
    TQValueList<IndexItemProto*> itemProtos = m_listbox->items[text()];
    for (TQValueList<IndexItemProto*>::const_iterator it = itemProtos.begin();
        it != itemProtos.end(); ++it)
        urlList.append(tqMakePair((*it)->description(), (*it)->url()));
    return urlList;
}


//----------------------------------------------------
//class ConfigurationItem

ConfigurationItem::ConfigurationItem(TQListView *parent, DocumentationPlugin * plugin, const TQString &title, const TQString &url,
    bool indexPossible, bool fullTextSearchPossible)
    :TQCheckListItem(parent, "", TQCheckListItem::CheckBox), m_title(title), m_url(url),
    m_origTitle(title), m_contents(true), m_index(false), m_fullTextSearch(false),
    m_indexPossible(indexPossible), m_fullTextSearchPossible(fullTextSearchPossible),
    m_docPlugin( plugin )
{
    setText(3, m_title);
    setText(4, m_url);
}

void ConfigurationItem::paintCell(TQPainter *p, const TQColorGroup &cg, int column,
    int width, int align)
{
    if ( (column == 0) || (column == 1) || (column == 2) )
    {
        if ( !p )
            return;
    
        TQListView *lv = listView();
        if ( !lv )
            return;
    
        const BackgroundMode bgmode = lv->viewport()->backgroundMode();
        const TQColorGroup::ColorRole crole = TQPalette::backgroundRoleFromMode( bgmode );
        p->fillRect(0, 0, width, height(), cg.brush(crole));

        TQFontMetrics fm(lv->fontMetrics());
        int boxsize = lv->style().pixelMetric(TQStyle::PM_CheckListButtonSize, lv);
        int marg = lv->itemMargin();
        int styleflags = TQStyle::Style_Default;
                
        if (((column == 0) && m_contents) || ((column == 1) && m_index) || ((column == 2) && m_fullTextSearch))
            styleflags |= TQStyle::Style_On;
        else
            styleflags |= TQStyle::Style_Off;
        if ((column == 0) || ((column == 1) && m_indexPossible) || ((column == 2) && m_fullTextSearchPossible))
            styleflags |= TQStyle::Style_Enabled;

        int x = 0;
        int y = 0;
        x += 3;
        if (align & AlignVCenter)
            y = ((height() - boxsize) / 2) + marg;
        else
            y = (fm.height() + 2 + marg - boxsize) / 2;

        TQStyleOption opt(this);
        lv->style().tqdrawPrimitive(TQStyle::PE_CheckListIndicator, p,
            TQRect(x, y, boxsize, fm.height() + 2 + marg), cg, styleflags, opt);
               
        return;
    }
    TQListViewItem::paintCell(p, cg, column, width, align);
}

int ConfigurationItem::width(const TQFontMetrics &fm, const TQListView *lv, int c) const
{
    if ((c == 0) || (c == 1) || (c == 2))
        return lv->style().pixelMetric(TQStyle::PM_CheckListButtonSize, lv) + 4;
    return TQListViewItem::width(fm, lv, c);
}


//----------------------------------------------------
//class DocumentationPlugin

DocumentationPlugin::DocumentationPlugin(TDEConfig *pluginConfig, TQObject *parent, const char *name)
    :TQObject(parent, name), config(pluginConfig), m_indexCreated(false)
{
}

DocumentationPlugin::~DocumentationPlugin()
{
}

void DocumentationPlugin::autoSetup()
{
    config->setGroup("General");    
    if ( ! config->readBoolEntry("Autosetup", false) )
    {
        autoSetupPlugin();
        config->setGroup("General");
        config->writeEntry("Autosetup", true);
        config->sync();
    }
}

void DocumentationPlugin::clear()
{
    // Do not clear the project documentation catalogs, since those are handled by
    // the ProjectDocumentationPlugin class
    for (TQValueList<DocumentationCatalogItem *>::iterator it = catalogs.begin();
        it != catalogs.end(); /*none*/)
    {
      if (!(*it)->isProjectDocumentationItem())
      {  
        DocumentationCatalogItem *curr = *it;
        ++it;  // Need to advance before destroying the catalog, otherwise it could crash
        //clearCatalog(curr); -- not necessary, already invoked by ~DocumentationCatalogItem()
        delete curr;   // Destroy and removes the item from the TDEListView
      }
      else
        ++it;
    }
}

void DocumentationPlugin::clearCatalog(DocumentationCatalogItem *item)
{
    //clear named catalog map
    for (TQMap<TQString, DocumentationCatalogItem*>::iterator it = namedCatalogs.begin();
        it != namedCatalogs.end(); ++it)
    {
        if (it.data() == item)
        {
            namedCatalogs.remove(it);
            break;
        }
    }
    //clear indexes for catalog
    TQValueList<IndexItemProto *> idx = indexes[item];
    for (TQValueList<IndexItemProto *>::iterator it = idx.begin(); it != idx.end(); ++it)
    {
        delete *it;
    }
    indexes.remove(item);
    //clear the catalog
    catalogs.remove(item);  // Removes the item from the catalog list
}

void DocumentationPlugin::createIndex(IndexBox *index)
{
    if (m_indexCreated)
        return;
    
    for (TQValueList<DocumentationCatalogItem *>::iterator it = catalogs.begin();
        it != catalogs.end(); ++it)
    {
        loadIndex(index, *it);
    }
    m_indexCreated = true;
}

void DocumentationPlugin::cacheIndex(DocumentationCatalogItem *item)
{
    kdDebug() << "Creating index cache for " << item->text(0) << endl;
    
    TQString cacheName = locateLocal("data", TQString("kdevdocumentation/index/cache_") + item->text(0));
    TQFile cacheFile(cacheName);
    if (!cacheFile.open(IO_WriteOnly))
        return;
    
    TQTextStream str(&cacheFile);
    str.setEncoding(TQTextStream::Unicode);
    str << CACHE_VERSION << endl;

    TQValueList<IndexItemProto*> catalogIndexes = indexes[item];
    for (TQValueList<IndexItemProto*>::const_iterator it = catalogIndexes.constBegin();
        it != catalogIndexes.constEnd(); ++it)
    {
        str << (*it)->text() << endl;
        str << (*it)->description() << endl;
        str << (*it)->url().url() << endl;
    }
   
    cacheFile.close();
}

bool DocumentationPlugin::loadCachedIndex(IndexBox *index, DocumentationCatalogItem *item)
{
    TQString cacheName = locateLocal("data", TQString("kdevdocumentation/index/cache_") + item->cacheVersion() + item->text(0));
    TQFile cacheFile(cacheName);
    if (!cacheFile.open(IO_ReadOnly))
        return false;

    kdDebug() << "Using cached index for item: " << item->text(0) << endl;
    
    TQTextStream str(&cacheFile);
    str.setEncoding(TQTextStream::Unicode);
    TQString cache = str.read();
    TQStringList cacheList = TQStringList::split("\n", cache, true);
    TQString ver = cacheList.first();
    if (ver != CACHE_VERSION)
    {
        kdDebug() << "Wrong cache version: " << ver << endl;
        return false;
    }
    TQStringList::const_iterator it = cacheList.begin();
    it++;
    TQString s[3]; int c = 0;
    for (; it != cacheList.end(); ++it)
    {
        s[c] = *it;
        if (c == 2)
        {
            IndexItemProto *ii = new IndexItemProto(this, item, index, s[0], s[1]);
            ii->addURL(KURL(s[2]));
            c = 0;
        }
        else c++;
    }
    cacheFile.close();
    
    return true;
}

void DocumentationPlugin::addCatalog(DocumentationCatalogItem *item)
{
    catalogs.append(item);
    namedCatalogs[item->text(0)] = item;
//    indexes[item] = TQValueList<IndexItem*>();
}

void DocumentationPlugin::addCatalogConfiguration(TDEListView *configurationView,
    const TQString &title, const TQString &url)
{
    new ConfigurationItem(configurationView, this, title, url, 
        hasCapability(Index), hasCapability(FullTextSearch));
}

void DocumentationPlugin::editCatalogConfiguration(ConfigurationItem *configurationItem,
    const TQString &title, const TQString &url)
{
    configurationItem->setTitle(title);
    configurationItem->setURL(url);
}

void DocumentationPlugin::deleteCatalogConfiguration(const ConfigurationItem *const configurationItem)
{
    deletedConfigurationItems << configurationItem->title();
}

void DocumentationPlugin::clearCatalogIndex(DocumentationCatalogItem *item)
{
    //clear indexes for catalog
    TQValueList<IndexItemProto *> idx = indexes[item];
    for (TQValueList<IndexItemProto *>::iterator it = idx.begin(); it != idx.end(); ++it)
    {
        delete *it;
    }
    indexes.remove(item);
}

void DocumentationPlugin::loadIndex(IndexBox *index, DocumentationCatalogItem *item)
{
    if (!indexEnabled(item))
        return;
    if (!needRefreshIndex(item) && loadCachedIndex(index, item))
        return;
    createIndex(index, item);
    cacheIndex(item);
}

TDEListViewItem* DocumentationPlugin::findCatalogPosition(const TQString &key, const TDEListView *contents) const
{
    TDEListViewItem *curr_item = (TDEListViewItem*)(contents->firstChild());
    if (!curr_item)
      return NULL;   // Empty listview
    
    TDEListViewItem *prev_item = NULL;
    while (curr_item && curr_item->text(0) <= key)
    {
      prev_item = curr_item;
      curr_item = (TDEListViewItem*)(curr_item->nextSibling());
    }
    return prev_item;
}

void DocumentationPlugin::init(TDEListView *contents)
{
    config->setGroup("Locations");
    TQMap<TQString, TQString> entryMap = config->entryMap("Locations");
    
    for (TQMap<TQString, TQString>::const_iterator it = entryMap.begin();
        it != entryMap.end(); ++it)
    {
        TQString cat_key = it.key();
        if (catalogEnabled(cat_key))
        {
            // Keep the list in increasing sorted order. Given that the list is
            // reasonably short, we can affort to scan through the list each time
            // a new item is created
            TDEListViewItem *afterItem = findCatalogPosition(cat_key, contents);
            createCatalog(contents, afterItem, cat_key, config->readPathEntry(cat_key));
        }
    }
}

void DocumentationPlugin::reinit(TDEListView *contents, IndexBox *index, TQStringList restrictions)
{
    config->setGroup("Locations");
    TQMap<TQString, TQString> entryMap = config->entryMap("Locations");

    //remove deleted in configuration catalogs
    for (TQStringList::const_iterator it = deletedConfigurationItems.constBegin();
        it != deletedConfigurationItems.constEnd(); ++it)
    {
        if (namedCatalogs.contains(*it))
            delete namedCatalogs[*it];
    }
    deletedConfigurationItems.clear();
    
    //update configuration
    for (TQMap<TQString, TQString>::const_iterator it = entryMap.begin();
        it != entryMap.end(); ++it)
    {
        config->setGroup("Locations");
        TQString cat_key = it.key();        
        if (restrictions.contains(cat_key) || (!catalogEnabled(cat_key)))
        {
            if (namedCatalogs.contains(cat_key))
                delete namedCatalogs[cat_key];
        }
        else
        {
            kdDebug() << "updating 1" << endl;
            if (!namedCatalogs.contains(cat_key))    //create catalog if it does not exist
            {
                TDEListViewItem *afterItem = findCatalogPosition(cat_key, contents);
                DocumentationCatalogItem *item = createCatalog(contents, afterItem, cat_key, config->readPathEntry(cat_key));
                loadIndex(index, item);
                index->setDirty(true);
//                index->refill(indexes[item]);
            }
            else if (!indexEnabled(namedCatalogs[cat_key]))    //clear index if it is disabled in configuration
            {
                kdDebug() << "    updating: clearCatalogIndex" << endl;
                clearCatalogIndex(namedCatalogs[cat_key]);
            }
            else if ( (indexEnabled(namedCatalogs[cat_key]))    //index is requested in configuration but does not yet exist
                && (!indexes.contains(namedCatalogs[cat_key])) )
            {
                kdDebug() << "    index requested " << endl;
                loadIndex(index, namedCatalogs[cat_key]);
                index->setDirty(true);
            }
            m_indexCreated = true;
        }
    }
}

void DocumentationPlugin::loadCatalogConfiguration(TDEListView *configurationView)
{
    config->setGroup("Locations");
    TQMap<TQString, TQString> entryMap = config->entryMap("Locations");

    for (TQMap<TQString, TQString>::const_iterator it = entryMap.begin();
        it != entryMap.end(); ++it)
    {
        if (namedCatalogs.contains(it.key())
            && namedCatalogs[it.key()]->isProjectDocumentationItem())
            continue;
        
	config->setGroup("Locations");
        ConfigurationItem *item = new ConfigurationItem(configurationView, this, it.key(),
	    config->readPathEntry(it.key()),
            hasCapability(Index), hasCapability(FullTextSearch));
        config->setGroup("TOC Settings");
        item->setContents(config->readBoolEntry(item->title(), true));
        config->setGroup("Index Settings");
        item->setIndex(config->readBoolEntry(item->title(), false));
        config->setGroup("Search Settings");
        item->setFullTextSearch(config->readBoolEntry(item->title(), false));
    }
}

void DocumentationPlugin::saveCatalogConfiguration(TDEListView *configurationView)
{
  
    for (TQStringList::const_iterator it = deletedConfigurationItems.constBegin();
        it != deletedConfigurationItems.constEnd(); ++it)
    {
        config->setGroup("Locations");
        config->deleteEntry(*it);
        config->setGroup("TOC Settings");
        config->deleteEntry(*it);
        config->setGroup("Index Settings");
        config->deleteEntry(*it);
        config->setGroup("Search Settings");
        config->deleteEntry(*it);
    }
    
    TQListViewItemIterator it(configurationView);
    while (it.current())
    {
        ConfigurationItem *confItem = dynamic_cast<ConfigurationItem*>(it.current());
        if ( confItem->docPlugin() != this ) 
        {
            ++it;
            continue;
        }

        config->setGroup("Locations");
        if (confItem->isChanged())
            config->deleteEntry(confItem->origTitle());
        config->writePathEntry(confItem->title(), confItem->url());
        
        config->setGroup("TOC Settings");
        if (confItem->isChanged())
            config->deleteEntry(confItem->origTitle());
        config->writeEntry(confItem->title(), confItem->contents());
        
        config->setGroup("Index Settings");
        if (confItem->isChanged())
            config->deleteEntry(confItem->origTitle());
        config->writeEntry(confItem->title(), confItem->index());

        config->setGroup("Search Settings");
        if (confItem->isChanged())
            config->deleteEntry(confItem->origTitle());
        config->writeEntry(confItem->title(), confItem->fullTextSearch());

        ++it;
    }
    config->sync();
}

void DocumentationPlugin::setIndexEnabled( DocumentationCatalogItem * item, bool e )
{
    TQString group = config->group();
    config->setGroup("Index Settings");
    config->writeEntry(item->text(0), e);
    config->setGroup(group);
}

bool DocumentationPlugin::indexEnabled( DocumentationCatalogItem * item ) const
{
    TQString group = config->group();
    config->setGroup("Index Settings");
    bool b = config->readBoolEntry(item->text(0), false);
    config->setGroup(group);
    return b;
}

bool DocumentationPlugin::catalogEnabled(const TQString &name) const
{
    TQString group = config->group();
    config->setGroup("TOC Settings");
    bool b = config->readBoolEntry(name, true);
    config->setGroup(group);
    return b;
}

void DocumentationPlugin::setCatalogEnabled(const TQString &name, bool e)
{
    TQString group = config->group();
    config->setGroup("TOC Settings");
    config->writeEntry(name, e);
    config->setGroup(group);
}


//----------------------------------------------------
//class IndexBox

IndexBox::IndexBox(TQWidget *parent, const char *name)
    :TDEListBox(parent, name), m_dirty(false)
{
}

void IndexBox::addIndexItem(IndexItemProto *item)
{
    items[item->text()].append(item);
}

void IndexBox::removeIndexItem(IndexItemProto *item)
{
    TQString text = item->text();
    items[text].remove(item);
    if (items[text].count() == 0)
    {
        items.remove(text);
        TQListBoxItem *item = findItem(text, TQt::CaseSensitive | TQt::ExactMatch);
        if (item)
            delete item;
    }
}

void IndexBox::fill()
{
    for (TQMap<TQString, TQValueList<IndexItemProto*> >::const_iterator it = items.begin();
        it != items.end(); ++it)
    {
        new IndexItem(this, it.key());
    }
}

void IndexBox::setDirty(bool dirty)
{
    m_dirty = dirty;
}

void IndexBox::refill()
{
    if (m_dirty)
    {
        clear();
        fill();
        setDirty(false);
    }
}


ProjectDocumentationPlugin::ProjectDocumentationPlugin(DocumentationPlugin *docPlugin, DocumentationPlugin::ProjectDocType type)
    :TQObject(0, 0), m_docPlugin(docPlugin), m_catalog(0), m_type(type), m_contents(0), m_index(0)
{
    kdDebug() << "ProjectDocumentationPlugin::ProjectDocumentationPlugin for type " << type << endl;
    
    m_watch = new KDirWatch(this);
    connect(m_watch, TQT_SIGNAL(dirty(const TQString&)), this, TQT_SLOT(reinit()));
    m_watch->startScan();
}

ProjectDocumentationPlugin::~ProjectDocumentationPlugin()
{
    deinit();
}

TDEListViewItem* ProjectDocumentationPlugin::findCatalogPosition(const TQString &key, const TDEListView *contents) const
{
    TDEListViewItem *curr_item = (TDEListViewItem*)(contents->firstChild());
    if (!curr_item)
      return NULL;   // Empty listview
    
    TDEListViewItem *prev_item = NULL;
    while (curr_item && curr_item->text(0) <= key)
    {
      prev_item = curr_item;
      curr_item = (TDEListViewItem*)(curr_item->nextSibling());
    }
    return prev_item;
}

void ProjectDocumentationPlugin::init(TDEListView *contents, IndexBox *index, const TQString &url)
{
    m_contents = contents;
    m_index = index;
    m_url = url;
    
    if (m_catalog)
        deinit();
    TQString cat_key = (m_type == DocumentationPlugin::APIDocs ? i18n("Project API Documentation")
                                                               : i18n("Project User Manual"));
    TDEListViewItem *afterItem = findCatalogPosition(cat_key, contents);
    m_catalog = m_docPlugin->createCatalog(contents, afterItem, cat_key, url);
    if (m_catalog)
    {
        m_catalog->setProjectDocumentationItem(true);
        m_watch->addFile(url);
    }
}

void ProjectDocumentationPlugin::reinit()
{
    deinit();
    if (m_contents != 0 && m_index != 0 && m_url != 0)
        init(m_contents, m_index, m_url);
}

void ProjectDocumentationPlugin::deinit()
{
    m_watch->removeFile(m_url);
    if (m_catalog)
    {
        delete m_catalog;
        m_catalog = 0;
    }
}

TQString ProjectDocumentationPlugin::pluginName() const
{
    return m_docPlugin->pluginName();
}

TQString ProjectDocumentationPlugin::catalogURL() const
{
    return m_url;
}

#include "kdevdocumentationplugin.moc"
