/***************************************************************************
 *   Copyright (C) 2002 Roberto Raggi                                      *
 *   roberto@kdevelop.org                                                  *
 *   Copyright (C) 2002 by Bernd Gehrmann                                  *
 *   bernd@kdevelop.org                                                    *
 *   Copyright (C) 2003 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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "abbrevpart.h"

#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqregexp.h>
#include <tqvbox.h>
#include <kdebug.h>
#include <kdialogbase.h>
#include <tdelocale.h>
#include <tdeparts/part.h>
#include <tdestandarddirs.h>
#include <kdevgenericfactory.h>
#include <tdeaction.h>
#include <tdeconfig.h>
#include <tdeio/netaccess.h>
#include <kiconloader.h>
#include <kdevplugininfo.h>

#include <tdetexteditor/document.h>
#include <tdetexteditor/editinterface.h>
#include <tdetexteditor/viewcursorinterface.h>
#include <tdetexteditor/codecompletioninterface.h>

#include "kdevcore.h"
#include "kdevpartcontroller.h"
#include "abbrevconfigwidget.h"
#include "kdeveditorutil.h"

static const KDevPluginInfo data("kdevabbrev");

class AbbrevFactory : public KDevGenericFactory<AbbrevPart>
{
public:
    AbbrevFactory()
        : KDevGenericFactory<AbbrevPart>( data )
    { }

    virtual TDEInstance *createInstance()
    {
        TDEInstance *instance = KDevGenericFactory<AbbrevPart>::createInstance();
        TDEStandardDirs *dirs = instance->dirs();
        dirs->addResourceType( "codetemplates",
                               TDEStandardDirs::kde_default( "data" ) + "kdevabbrev/templates/" );
        dirs->addResourceType( "sources",
                               TDEStandardDirs::kde_default( "data" ) + "kdevabbrev/sources" );

        return instance;
    }
};

K_EXPORT_COMPONENT_FACTORY( libkdevabbrev, AbbrevFactory )

AbbrevPart::AbbrevPart(TQObject *parent, const char *name, const TQStringList &)
    : KDevPlugin(&data, parent, name ? name : "AbbrevPart")
{
    setInstance(AbbrevFactory::instance());
    setXMLFile("kdevabbrev.rc");

    connect(partController(), TQ_SIGNAL(activePartChanged(KParts::Part*)),
    	this, TQ_SLOT(slotActivePartChanged(KParts::Part*)) );

    connect(core(), TQ_SIGNAL(configWidget(KDialogBase*)), this, TQ_SLOT(configWidget(KDialogBase*)));

    TDEAction *action;
    action = new TDEAction( i18n("Expand Text"), CTRL + Key_J,
                          this, TQ_SLOT(slotExpandText()),
                          actionCollection(), "edit_expandtext" );
    action->setToolTip( i18n("Expand current word") );
    action->setWhatsThis( i18n("<b>Expand current word</b><p>Current word can be completed using the list of similar words in source files.") );

    action = new TDEAction( i18n("Expand Abbreviation"), CTRL + Key_L,
                          this, TQ_SLOT(slotExpandAbbrev()),
                          actionCollection(), "edit_expandabbrev" );
    action->setToolTip( i18n("Expand abbreviation") );
    action->setWhatsThis( i18n("<b>Expand abbreviation</b><p>Enable and configure abbreviations in <b>TDevelop Settings</b>, <b>Abbreviations</b> tab.") );

    load();

    m_inCompletion = false;
    docIface = 0;
    editIface = 0;
    viewCursorIface = 0;
    completionIface = 0;

    m_prevLine = -1;
    m_prevColumn = -1;
    m_sequenceLength = 0;

    TDEConfig* config = AbbrevFactory::instance()->config();
    TDEConfigGroupSaver group( config, "General" );
    m_autoWordCompletionEnabled = config->readBoolEntry( "AutoWordCompletion", false );

    updateActions();

    slotActivePartChanged( partController()->activePart() );
}


AbbrevPart::~AbbrevPart()
{
    save();
}

bool AbbrevPart::autoWordCompletionEnabled() const
{
    return m_autoWordCompletionEnabled;
}

void AbbrevPart::setAutoWordCompletionEnabled( bool enabled )
{
    if( enabled == m_autoWordCompletionEnabled )
	return;

    TDEConfig* config = AbbrevFactory::instance()->config();
    TDEConfigGroupSaver group( config, "General" );

    m_autoWordCompletionEnabled = enabled;
    config->writeEntry( "AutoWordCompletion", m_autoWordCompletionEnabled );
    config->sync();

    if( !docIface || !docIface->widget() )
	return;

    disconnect( docIface, 0, this, 0 );
    disconnect( docIface->widget(), 0, this, 0 );

    if( m_autoWordCompletionEnabled ){
	connect( docIface->widget(), TQ_SIGNAL(completionAborted()),
		 this, TQ_SLOT(slotCompletionAborted()) );
	connect( docIface->widget(), TQ_SIGNAL(completionDone()),
		 this, TQ_SLOT(slotCompletionDone()) );
	connect( docIface->widget(), TQ_SIGNAL(aboutToShowCompletionBox()),
		 this, TQ_SLOT(slotAboutToShowCompletionBox()) );

	connect( docIface, TQ_SIGNAL(textChanged()), this, TQ_SLOT(slotTextChanged()) );
    }
}
void AbbrevPart::load()
{
    TDEStandardDirs *dirs = AbbrevFactory::instance()->dirs();
    TQString localTemplatesFile = locateLocal("codetemplates", "templates", AbbrevFactory::instance());
    TQStringList files;
    if (TQFileInfo(localTemplatesFile).exists())
        files << localTemplatesFile;
    else
        files = dirs->findAllResources("codetemplates", TQString(), false, true);

    TQString localSourcesFile = locateLocal("sources", "sources", AbbrevFactory::instance());
    TQStringList sourceFiles;
    if (TQFileInfo(localSourcesFile).exists())
        sourceFiles << localSourcesFile;
    else
        sourceFiles = dirs->findAllResources("sources", TQString(), false, true);
    kdDebug(9028) << "=========> sourceFiles: " << sourceFiles.join(" ") << endl;

    this->m_completionFile = TQString();
    for( TQStringList::Iterator it=sourceFiles.begin(); it!=sourceFiles.end(); ++it ) {
        TQString fn = *it;
	kdDebug(9028) << "===> load file: " << fn << endl;
        TQFile f( fn );
        if ( f.open(IO_ReadOnly) ) {
		TQTextStream stream( &f );
		m_completionFile += ( stream.read() + TQString("\n") );
		f.close();
	}
    }

    TQStringList::ConstIterator it;
    for (it = files.begin(); it != files.end(); ++it) {
        TQString fn = *it;
        kdDebug(9028) << "fn = " << fn << endl;
        TQFile f( fn );
        if ( f.open(IO_ReadOnly) ) {
            TQDomDocument doc;
            doc.setContent( &f );
            TQDomElement root = doc.firstChild().toElement();
            TQDomElement e = root.firstChild().toElement();
            while ( !e.isNull() ){
                addTemplate( e.attribute("name"),
                             e.attribute("description"),
                             e.attribute("suffixes"),
                             e.attribute("code") );
                e = e.nextSibling().toElement();
            }
            f.close();
        }
    }
}


void AbbrevPart::save()
{
    TQString fn = AbbrevFactory::instance()->dirs()->saveLocation("codetemplates", "", true);
    kdDebug(9028) << "fn = " << fn << endl;

    TQDomDocument doc( "Templates" );
    TQDomElement root = doc.createElement( "Templates" );
    doc.appendChild( root );

    TQPtrList<CodeTemplate> templates = m_templates.allTemplates();
    CodeTemplate *templ;
    for (templ = templates.first(); templ; templ = templates.next())
    {
        TQDomElement e = doc.createElement( "Template" );
        e.setAttribute( "name", templ->name );
        e.setAttribute( "description", templ->description );
        e.setAttribute( "suffixes", templ->suffixes );
        e.setAttribute( "code", templ->code );
        root.appendChild( e );
    }

    TQFile f( fn + "templates" );
    if( f.open(IO_WriteOnly) ){
        TQTextStream stream( &f );
        stream << doc.toString();
        f.close();
    }
}


TQString AbbrevPart::currentWord() const
{
    return KDevEditorUtil::currentWord( dynamic_cast<KTextEditor::Document*>( partController()->activePart() ) );
}


void AbbrevPart::configWidget(KDialogBase *dlg)
{
	TQVBox *vbox = dlg->addVBoxPage(i18n("Abbreviations"), i18n("Abbreviations"), BarIcon( info()->icon(), TDEIcon::SizeMedium) );
    AbbrevConfigWidget *w = new AbbrevConfigWidget(this, vbox, "abbrev config widget");
    connect(dlg, TQ_SIGNAL(okClicked()), w, TQ_SLOT(accept()));
}


void AbbrevPart::slotExpandText()
{
    if( !editIface || !completionIface || !viewCursorIface )
        return;

    TQString word = currentWord();
    if (word.isEmpty())
        return;

    TQValueList<KTextEditor::CompletionEntry> entries = findAllWords(editIface->text(), word);
    if (entries.count() == 0) {
        ; // some statusbar message?
//    } else if (entries.count() == 1) {
//        uint line, col;
//        viewCursorIface->cursorPositionReal(&line, &col);
//        TQString txt = entries[0].text.mid(word.length());
//        editIface->insertText( line, col, txt );
//        viewCursorIface->setCursorPositionReal( line, col + txt.length() );
    } else {
        m_inCompletion = true;
        completionIface->showCompletionBox(entries, word.length());
    }
}


TQValueList<KTextEditor::CompletionEntry> AbbrevPart::findAllWords(const TQString &text, const TQString &prefix)
{
    TQValueList<KTextEditor::CompletionEntry> entries;

    KParts::ReadWritePart *part = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart());
    TQWidget *view = partController()->activeWidget();
    if (!part || !view) {
        kdDebug(9028) << "no rw part" << endl;
        return entries;
    }

    TQString suffix = part->url().url();
    int pos = suffix.findRev('.');
    if (pos != -1)
        suffix.remove(0, pos+1);
    kdDebug(9028) << "AbbrevPart::findAllWords with suffix " << suffix << endl;

    TQMap<TQString, bool> map;
    TQRegExp rx( TQString("\\b") + prefix + "[a-zA-Z0-9_]+\\b" );

    int idx = 0;
    pos = 0;
    int len = 0;
    while ( (pos = rx.search(text, idx)) != -1 ) {
	len = rx.matchedLength();
	TQString word = text.mid(pos, len);
        if (map.find(word) == map.end()) {
            KTextEditor::CompletionEntry e;
            e.text = word;
            entries << e;
            map[ word ] = true;
        }
        idx = pos + len + 1;
    }

    idx = 0;
    pos = 0;
    len = 0;
    while ( (pos = rx.search(m_completionFile, idx)) != -1 ) {
	len = rx.matchedLength();
	TQString word = m_completionFile.mid(pos, len);
        if (map.find(word) == map.end()) {
            KTextEditor::CompletionEntry e;
            e.text = word;
            entries << e;
            map[ word ] = true;
        }
        idx = pos + len + 1;
    }


    TQMap<TQString, CodeTemplate*> m = m_templates[suffix];
    for (TQMap<TQString, CodeTemplate*>::const_iterator it = m.begin(); it != m.end() ; ++it) {
        KTextEditor::CompletionEntry e;
        e.text = it.data()->description + " <abbrev>";
        e.userdata = it.key();
        entries << e;
    }

    return entries;
}


void AbbrevPart::slotExpandAbbrev()
{
    KParts::ReadWritePart *part = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart());
    TQWidget *view = partController()->activeWidget();
    if (!part || !view) {
        kdDebug(9028) << "no rw part" << endl;
        return;
    }

    TQString suffix = part->url().url();
    int pos = suffix.findRev('.');
    if (pos != -1)
        suffix.remove(0, pos+1);

    KTextEditor::EditInterface *editiface
        = dynamic_cast<KTextEditor::EditInterface*>(part);
    if (!editiface) {
        kdDebug(9028) << "no editiface" << endl;
        return;
    }
    KTextEditor::ViewCursorInterface *cursoriface
        = dynamic_cast<KTextEditor::ViewCursorInterface*>(view);
    if (!cursoriface) {
        kdDebug(9028) << "no viewcursoriface" << endl;
        return;
    }

    TQString word = currentWord();
    kdDebug(9028) << "Expanding word " << word << " with suffix " << suffix << "." << endl;

    TQMap<TQString, CodeTemplate*> m = m_templates[suffix];
    for (TQMap<TQString, CodeTemplate*>::const_iterator it = m.begin(); it != m.end() ; ++it) {
        if (it.key() != word)
            continue;

        uint line, col;
        cursoriface->cursorPositionReal(&line, &col);

        TQString linestr = editIface->textLine(line);
        int startPos = TQMAX( TQMIN( (int)col, (int)linestr.length()-1 ), 0 );
        int endPos = startPos;
        startPos--;
        while (startPos >= 0 && ( linestr[startPos].isLetterOrNumber() || linestr[startPos] == '_' || linestr[startPos] == '~') )
            startPos--;
        while (endPos < (int)linestr.length() && ( linestr[endPos].isLetterOrNumber() || linestr[endPos] == '_' ) )
            endPos++;

        editiface->removeText( line, startPos+1, line, endPos );
        insertChars(it.data()->code );
    }
}


void AbbrevPart::insertChars( const TQString &chars )
{
    unsigned line=0, col=0;
    viewCursorIface->cursorPositionReal( &line, &col );

    unsigned int currentLine=line, currentCol=col;

    TQString spaces;
    TQString s = editIface->textLine( currentLine );
    uint i=0;
    while( i<s.length() && s[ i ].isSpace() ){
        spaces += s[ i ];
        ++i;
    }

    bool foundPipe = false;
    TQString str;
    TQTextStream stream( &str, IO_WriteOnly );
    TQStringList lines = TQStringList::split( "\n", chars );
    TQStringList::Iterator it = lines.begin();
    line = currentLine;
    while( it != lines.end() ){
        TQString lineText = *it;
	if( it != lines.begin() ){
            stream << spaces;
	    if( !foundPipe )
		currentCol += spaces.length();
	}

        int idx = lineText.find( '|' );
        if( idx != -1 ){
            stream << lineText.left( idx ) << lineText.mid( idx+1 );
	    if( !foundPipe ){
		foundPipe = true;
		currentCol += lineText.left( idx ).length();
		kdDebug(9007) << "found pipe at " << currentLine << ", " << currentCol << endl;
	    }
        } else {
            stream << lineText;
        }

        ++it;

	if( it != lines.end() ){
            stream << "\n";
	    if( !foundPipe ){
		++currentLine;
		currentCol = 0;
	    }
	}
    }
    editIface->insertText( line, col, str );
    kdDebug(9007) << "go to " << currentLine << ", " << currentCol << endl;
    viewCursorIface->setCursorPositionReal( currentLine, currentCol );
}

void AbbrevPart::addTemplate( const TQString& templ,
                              const TQString& descr,
                              const TQString& suffixes,
                              const TQString& code)
{
    m_templates.insert(templ, descr, code, suffixes);
}


void AbbrevPart::removeTemplate( const TQString &suffixes, const TQString &name )
{
    m_templates.remove( suffixes, name );
}


void AbbrevPart::clearTemplates()
{
    m_templates.clear();
}

CodeTemplateList AbbrevPart::templates() const
{
    return m_templates;
}

void AbbrevPart::slotActivePartChanged( KParts::Part* part )
{
    kdDebug(9028) << "AbbrevPart::slotActivePartChanged()" << endl;
    KTextEditor::Document* doc = dynamic_cast<KTextEditor::Document*>( part );

    if( !doc || !part->widget() || doc == docIface  )
    {
        actionCollection()->action( "edit_expandtext" )->setEnabled( false );
        actionCollection()->action( "edit_expandabbrev" )->setEnabled( false );
        return;
    }

    docIface = doc;

    if( !docIface ){
        docIface = 0;
        editIface = 0;
        viewCursorIface = 0;
        completionIface = 0;
    }

    editIface = dynamic_cast<KTextEditor::EditInterface*>( part );
    viewCursorIface = dynamic_cast<KTextEditor::ViewCursorInterface*>( part->widget() );
    completionIface = dynamic_cast<KTextEditor::CodeCompletionInterface*>( part->widget() );

    updateActions();

    if( !editIface || !viewCursorIface || !completionIface )
    	return;

    disconnect( part->widget(), 0, this, 0 );
    disconnect( doc, 0, this, 0 );

    connect( part->widget(), TQ_SIGNAL(filterInsertString(KTextEditor::CompletionEntry*, TQString*)),
	     this, TQ_SLOT(slotFilterInsertString(KTextEditor::CompletionEntry*, TQString*)) );

    if( autoWordCompletionEnabled() ){
	connect( part->widget(), TQ_SIGNAL(completionAborted()), this, TQ_SLOT(slotCompletionAborted()) );
	connect( part->widget(), TQ_SIGNAL(completionDone()), this, TQ_SLOT(slotCompletionDone()) );
	connect( part->widget(), TQ_SIGNAL(aboutToShowCompletionBox()), this, TQ_SLOT(slotAboutToShowCompletionBox()) );
	connect( doc, TQ_SIGNAL(textChanged()), this, TQ_SLOT(slotTextChanged()) );
    }

    m_prevLine = -1;
    m_prevColumn = -1;
    m_sequenceLength = 0;
    kdDebug(9028) << "AbbrevPart::slotActivePartChanged() -- OK" << endl;
}

void AbbrevPart::slotTextChanged()
{
    if( m_inCompletion )
	return;

    unsigned int line, col;
    viewCursorIface->cursorPositionReal( &line, &col );

    if( m_prevLine != int(line) || m_prevColumn+1 != int(col) || col == 0 ){
        m_prevLine = line;
        m_prevColumn = col;
	m_sequenceLength = 1;
	return;
    }

    TQString textLine = editIface->textLine( line );
    TQChar ch = textLine[ col-1 ];
    TQChar currentChar = textLine[ col ];

    if( currentChar.isLetterOrNumber() || currentChar == TQChar('_') || !(ch.isLetterOrNumber() || ch == TQChar('_')) ){
        // reset
        m_prevLine = -1;
	return;
    }

    if( m_sequenceLength >= 3 )
	slotExpandText();

    ++m_sequenceLength;
    m_prevLine = line;
    m_prevColumn = col;
}

void AbbrevPart::slotFilterInsertString( KTextEditor::CompletionEntry* entry, TQString* text )
{
    kdDebug(9028) << "AbbrevPart::slotFilterInsertString()" << endl;
    KParts::ReadWritePart *part = dynamic_cast<KParts::ReadWritePart*>(partController()->activePart());
    TQWidget *view = partController()->activeWidget();
    if (!part || !view) {
        kdDebug(9028) << "no rw part" << endl;
        return;
    }

    TQString suffix = part->url().url();
    int pos = suffix.findRev('.');
    if (pos != -1)
        suffix.remove(0, pos+1);
    kdDebug(9028) << "AbbrevPart::slotFilterInsertString with suffix " << suffix << endl;

    if( !entry || !text || !viewCursorIface || !editIface )
	return;

    TQString expand( " <abbrev>" );
    if( !entry->userdata.isNull() && entry->text.endsWith(expand) ){
	TQString macro = entry->text.left( entry->text.length() - expand.length() );
	*text = "";
        uint line, col;
        viewCursorIface->cursorPositionReal( &line, &col );
        editIface->removeText( line, col-currentWord().length(), line, col );
	insertChars( m_templates[suffix][entry->userdata]->code );
    }
}

void AbbrevPart::updateActions()
{
    actionCollection()->action( "edit_expandtext" )->setEnabled( docIface != 0 );
    actionCollection()->action( "edit_expandabbrev" )->setEnabled( docIface != 0 );
}

void AbbrevPart::slotCompletionAborted()
{
    kdDebug(9028) << "AbbrevPart::slotCompletionAborted()" << endl;
    m_inCompletion = false;
}

void AbbrevPart::slotCompletionDone()
{
    kdDebug(9028) << "AbbrevPart::slotCompletionDone()" << endl;
    m_inCompletion = false;
}

void AbbrevPart::slotAboutToShowCompletionBox()
{
    kdDebug(9028) << "AbbrevPart::slotAboutToShowCompletionBox()" << endl;
    m_inCompletion = true;
}

CodeTemplateList::CodeTemplateList( )
{
    allCodeTemplates.setAutoDelete(true);
}

CodeTemplateList::~ CodeTemplateList( )
{
}

TQMap< TQString, CodeTemplate * > CodeTemplateList::operator [ ]( TQString suffix )
{
    kdDebug(9028) << "CodeTemplateList::operator []" << endl;
    TQMap< TQString, CodeTemplate * > selectedTemplates;
    for (TQMap<TQString, TQMap<TQString, CodeTemplate* > >::const_iterator it = templates.begin(); it != templates.end(); ++it)
    {
        kdDebug(9028) << "CodeTemplateList::operator [] - suffixes " << it.key() << endl;
        if (TQStringList::split(",", it.key()).contains(suffix))
        {
            kdDebug(9028) << "CodeTemplateList::operator [] - suffixes " << it.key() << " contains " << suffix << endl;

            TQMap<TQString, CodeTemplate* > m = it.data();
            for (TQMap<TQString, CodeTemplate* >::const_iterator itt = m.begin(); itt != m.end(); ++itt)
            {
                kdDebug(9028) << "x" << endl;
                selectedTemplates[itt.key()] = itt.data();
            }
        }
    }
    return selectedTemplates;
}

void CodeTemplateList::insert( TQString name, TQString description, TQString code, TQString suffixes )
{
    TQString origSuffixes = suffixes;
//    TQStringList suffixList;
    int pos = suffixes.find('(');
    if (pos == -1)
        return;
    suffixes.remove(0, pos+1);
    pos = suffixes.find(')');
    if (pos == -1)
        return;
    suffixes.remove(pos, suffixes.length()-pos);
//    suffixList = TQStringList::split(",", suffixes);

    CodeTemplate *t;
    if (templates.contains(suffixes) && templates[suffixes].contains(name))
    {
        kdDebug(9028) << "found template for suffixes " << suffixes << " and name " << name << endl;
        t = templates[suffixes][name];
    }
    else
    {
        kdDebug(9028) << "creating template for suffixes " << suffixes << " and name " << name << endl;
        t = new CodeTemplate();
        allCodeTemplates.append(t);
        templates[suffixes][name] = t;
    }
    t->name = name;
    t->description = description;
    t->code = code;
    t->suffixes = origSuffixes;
    if (!m_suffixes.contains(origSuffixes))
        m_suffixes.append(origSuffixes);
}

TQPtrList< CodeTemplate > CodeTemplateList::allTemplates( ) const
{
    return allCodeTemplates;
}

void CodeTemplateList::remove( const TQString & suffixes, const TQString & name )
{
    allCodeTemplates.remove(templates[suffixes][name]);
    templates[suffixes].remove(name);
}

void CodeTemplateList::clear( )
{
    templates.clear();
    allCodeTemplates.clear();
}

TQStringList CodeTemplateList::suffixes( )
{
    return m_suffixes;
}

#include "abbrevpart.moc"
