/* This file is part of the KDE project
   Copyright (C) 2002 David Faure <david@mandrakesoft.com>
                 2002 Laurent Montel <lmontel@mandrakesoft.com>

   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., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/


#include "koBgSpellCheck.h"
#include "koBgSpellCheck.moc"
#include <qtimer.h>
#include <kdebug.h>
#include <kospell.h>
#include <ksconfig.h>
#include <kotextobject.h>
#include <klocale.h>

//#define DEBUG_BGSPELLCHECKING

KoBgSpellCheck::KoBgSpellCheck()
{
    m_pKSpellConfig=0L;
    m_bgSpell.kspell=0L;
    m_bDontCheckUpperWord=false;
    m_bSpellCheckEnabled=false;
    m_bDontCheckTitleCase=false;
    m_bSpellCheckConfigure=false;
    m_bgSpell.currentTextObj=0L;
    m_bgSpell.needsRepaint=false;
}

KoBgSpellCheck::~KoBgSpellCheck()
{
    delete m_bgSpell.kspell;
    delete m_pKSpellConfig;
}

void KoBgSpellCheck::spellCheckParagraphDeleted( KoTextParag *_parag,  KoTextObject *obj)
{
    if ( m_bgSpell.currentTextObj == obj && m_bgSpell.currentParag == _parag)
    {
        stopSpellChecking();
        startBackgroundSpellCheck();
    }
}


void KoBgSpellCheck::enableBackgroundSpellCheck( bool b )
{
    m_bSpellCheckEnabled=b;
    startBackgroundSpellCheck(); // will enable or disable
}

void KoBgSpellCheck::setIgnoreUpperWords( bool b)
{
    stopSpellChecking();
    m_bDontCheckUpperWord = b;
    startBackgroundSpellCheck();
}

void KoBgSpellCheck::setIgnoreTitleCase( bool b)
{
    stopSpellChecking();
    m_bDontCheckTitleCase = b;
    startBackgroundSpellCheck();
}

void KoBgSpellCheck::addIgnoreWordAll( const QString & word)
{
    if( m_spellListIgnoreAll.findIndex( word )==-1)
        m_spellListIgnoreAll.append( word );
    stopSpellChecking();
    spellConfig()->setIgnoreList( m_spellListIgnoreAll );
    startBackgroundSpellCheck();
}

void KoBgSpellCheck::addIgnoreWordAllList( const QStringList & list)
{
    m_spellListIgnoreAll.clear();
    stopSpellChecking();
    spellConfig()->setIgnoreList( list );
    startBackgroundSpellCheck();
}

void KoBgSpellCheck::clearIgnoreWordAll( )
{
    m_spellListIgnoreAll.clear();
    stopSpellChecking();
    spellConfig()->setIgnoreList( m_spellListIgnoreAll );
    startBackgroundSpellCheck();
}

void KoBgSpellCheck::startBackgroundSpellCheck()
{
    if ( !m_bSpellCheckEnabled )
        return;
    //re-test text obj
    if ( !m_bgSpell.currentTextObj )
    {
        m_bgSpell.currentTextObj = nextTextObject(m_bgSpell.currentTextObj );
    }
    if ( !m_bgSpell.currentTextObj )
    {
        QTimer::singleShot( 1000, this, SLOT( startBackgroundSpellCheck() ) );
        return;
    }
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "KoBgSpellCheck::startBackgroundSpellCheck" << endl;
#endif

    m_bgSpell.currentParag = m_bgSpell.currentTextObj->textDocument()->firstParag();
    nextParagraphNeedingCheck();

    //kdDebug(32500) << "fs=" << m_bgSpell.currentTextObj << " parag=" << m_bgSpell.currentParag << endl;

    if ( !m_bgSpell.currentTextObj || !m_bgSpell.currentParag ) {
        if ( m_bgSpell.currentTextObj )
        {
            if ( (m_bgSpell.currentTextObj->textDocument()->firstParag() == m_bgSpell.currentTextObj->textDocument()->lastParag()) && m_bgSpell.currentTextObj->textDocument()->firstParag()->length() <= 1)
                m_bgSpell.currentTextObj->setNeedSpellCheck(false);
        }
        // Might be better to launch again upon document modification (key, pasting, etc.) instead of right now
        //kdDebug(32500) << "KWDocument::startBackgroundSpellCheck nothing to check this time." << endl;
        QTimer::singleShot( 1000, this, SLOT( startBackgroundSpellCheck() ) );
        return;
    }

    bool needsWait = false;
    if ( !m_bgSpell.kspell ) // reuse if existing
    {
        m_bgSpell.kspell = new KoSpell(0L, this, SLOT( spellCheckerReady() ), m_pKSpellConfig );

        needsWait = true; // need to wait for ready()
        connect( m_bgSpell.kspell, SIGNAL( death() ),
                 this, SLOT( spellCheckerFinished() ) );
        connect( m_bgSpell.kspell, SIGNAL( misspelling( const QString &, int ) ),
                 this, SLOT( spellCheckerMisspelling( const QString &, int ) ) );
        connect( m_bgSpell.kspell, SIGNAL( done() ),
                 this, SLOT( spellCheckerDone() ) );
    }
    m_bgSpell.kspell->setIgnoreUpperWords( m_bDontCheckUpperWord );
    m_bgSpell.kspell->setIgnoreTitleCase( m_bDontCheckTitleCase );
    if ( !needsWait )
        spellCheckerReady();
}

void KoBgSpellCheck::spellCheckerReady()
{
    //necessary to restart to beginning otherwise we don't check
    //other parag
    if (m_bgSpell.currentTextObj)
        m_bgSpell.currentParag = m_bgSpell.currentTextObj->textDocument()->firstParag();

    //kdDebug(32500) << "KWDocument::spellCheckerReady" << endl;
    QTimer::singleShot( 10, this, SLOT( spellCheckNextParagraph() ) );
}

// Input: currentTextObj non-null, and currentParag set to the last parag checked
// Output: currentTextObj+currentParag set to next parag to check. Both 0 if end.
void KoBgSpellCheck::nextParagraphNeedingCheck()
{
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "KoBgSpellCheck::nextParagraphNeedingCheck" <<m_bgSpell.currentTextObj <<endl;
#endif
    if ( !m_bgSpell.currentTextObj ) {
        m_bgSpell.currentParag = 0L;
        return;
    }

    // repaint the textObject here if it requires it
    // (perhaps there should be a way to repaint just a paragraph.... - JJ)
    if(m_bgSpell.needsRepaint)
    {
         slotRepaintChanged( m_bgSpell.currentTextObj );
         m_bgSpell.needsRepaint=false;
    }
    
    KoTextParag* parag = m_bgSpell.currentParag;
    if ( parag && parag->string() && parag->string()->needsSpellCheck() )
    {
        return;
    }

    if ( parag && parag->next() )
        parag = parag->next();
    // Skip any unchanged parags
    while ( parag && !parag->string()->needsSpellCheck() )
        parag = parag->next();
    while ( parag && parag->length() <= 1 ) // empty parag
    {
        parag->string()->setNeedsSpellCheck( false ); // nothing to check
        while ( parag && !parag->string()->needsSpellCheck() ) // keep looking
            parag = parag->next();
    }
    if ( parag )
        m_bgSpell.currentParag = parag;
    else
        m_bgSpell.currentParag = 0L; // ###

    if( !m_bgSpell.currentParag)
    {
        KoTextObject *obj=m_bgSpell.currentTextObj;
        //kdDebug()<<" obj :"<<obj<<endl;
        m_bgSpell.currentTextObj=nextTextObject( m_bgSpell.currentTextObj );
        //kdDebug()<<" m_bgSpell.currentTextObj !"<<m_bgSpell.currentTextObj<<endl;
        if ( m_bgSpell.currentTextObj && m_bgSpell.currentTextObj!=obj)
        {
            m_bgSpell.currentParag = m_bgSpell.currentTextObj->textDocument()->firstParag();
        }
        else
        {
            if ( m_bgSpell.currentParag )
                m_bgSpell.currentParag->string()->setNeedsSpellCheck( false );
            if ( m_bgSpell.currentTextObj )
                m_bgSpell.currentTextObj->setNeedSpellCheck( false );
            m_bgSpell.currentParag = 0L;
        }
    }
    //kdDebug()<<" KoBgSpellCheck::nextParagraphNeedingCheck() : m_bgSpell.currentParag :"<<m_bgSpell.currentParag<<endl;

}

void KoBgSpellCheck::spellCheckNextParagraph()
{
    //kdDebug(32500) << "KoBgSpellCheck::spellCheckNextParagraph" << endl;

    nextParagraphNeedingCheck();
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "fs=" << m_bgSpell.currentTextObj << " parag=" << m_bgSpell.currentParag << endl;
#endif
    if ( !m_bgSpell.currentTextObj || !m_bgSpell.currentParag )
    {
#ifdef DEBUG_BGSPELLCHECKING
        kdDebug(32500) << "KoBgSpellCheck::spellCheckNextParagraph scheduling restart" << endl;
#endif
        // We arrived to the end of the paragraphs. Jump to startBackgroundSpellCheck,
        // it will check if we still have something to do.
        QTimer::singleShot( 100, this, SLOT( startBackgroundSpellCheck() ));
        return;
    }
    // First remove any misspelled format from the paragraph
    // - otherwise we'd never notice words being ok again :)
    KoTextStringChar *ch = m_bgSpell.currentParag->at( 0 );
    KoTextFormat format( *ch->format() );
    format.setMisspelled( false );
    m_bgSpell.currentParag->setFormat( 0, m_bgSpell.currentParag->length()-1, &format, true, KoTextFormat::Misspelled );
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "KoBgSpellCheck::spellCheckNextParagraph spell checking parag " << m_bgSpell.currentParag->paragId() << endl;
#endif
    // Now spell-check that paragraph
    QString text = m_bgSpell.currentParag->string()->toString();
    text.remove( text.length() - 1, 1 ); // trailing space
    m_bgSpell.kspell->check(text);
}

void KoBgSpellCheck::spellCheckerMisspelling(const QString &old, int pos )
{
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "KoBgSpellCheck::spellCheckerMisspelling old=" << old << " pos=" << pos << endl;
#endif
    KoTextObject * fs = m_bgSpell.currentTextObj;
    //Q_ASSERT( fs );
    if ( !fs ) return;
    KoTextParag* parag = m_bgSpell.currentParag;
    if ( !parag ) return;
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "KoBgSpellCheck::spellCheckerMisspelling parag=" << parag << " (id=" << parag->paragId() << ", length=" << parag->length() << ") pos=" << pos << " length=" << old.length() << endl;
#endif
    KoTextStringChar *ch = parag->at( pos );
    KoTextFormat format( *ch->format() );
    format.setMisspelled( true );
    parag->setFormat( pos, old.length(), &format, true, KoTextFormat::Misspelled );

    // set the repaint flags
    parag->setChanged( true );
    m_bgSpell.needsRepaint=true;
}

void KoBgSpellCheck::spellCheckerDone()
{
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "KoBgSpellCheck::spellCheckerDone" << endl;
#endif
    if(m_bgSpell.currentParag)
        m_bgSpell.currentParag->string()->setNeedsSpellCheck( false );
    if( m_bgSpell.currentTextObj && m_bgSpell.currentParag==m_bgSpell.currentTextObj->textDocument()->lastParag())
        m_bgSpell.currentTextObj->setNeedSpellCheck(false);
    // Done checking the current paragraph, schedule the next one
    QTimer::singleShot( 10, this, SLOT( spellCheckNextParagraph() ) );
}

void KoBgSpellCheck::spellCheckerFinished()
{
#ifdef DEBUG_BGSPELLCHECKING
    kdDebug(32500) << "--- KoBgSpellCheck::spellCheckerFinished ---" << endl;
#endif
    KoSpell::spellStatus status = m_bgSpell.kspell->status();
    delete m_bgSpell.kspell;
    m_bgSpell.kspell = 0;
    m_bgSpell.currentParag = 0;
    m_bgSpell.currentTextObj = 0;
    if (status == KoSpell::Error)
    {
        // KSpell badly configured... what to do?
        kdWarning() << "ISpell/ASpell not configured correctly." << endl;
        if ( !m_bSpellCheckConfigure )
        {
            m_bSpellCheckConfigure=true;
            configurateSpellChecker();
        }
        return;
    }
    else if (status == KoSpell::Crashed)
    {
        kdWarning() << "ISpell/ASpell seems to have crashed." << endl;
        return;
    }
    // Normal death - nothing to do
}

KSpellConfig* KoBgSpellCheck::spellConfig()
{
  if ( !m_pKSpellConfig )
    m_pKSpellConfig = new KSpellConfig();
  return m_pKSpellConfig;
}

void KoBgSpellCheck::setKSpellConfig(KSpellConfig _kspell)
{
  (void)spellConfig();
  stopSpellChecking();

  m_pKSpellConfig->setNoRootAffix(_kspell.noRootAffix ());
  m_pKSpellConfig->setRunTogether(_kspell.runTogether ());
  m_pKSpellConfig->setDictionary(_kspell.dictionary ());
  m_pKSpellConfig->setDictFromList(_kspell.dictFromList());
  m_pKSpellConfig->setEncoding(_kspell.encoding());
  m_pKSpellConfig->setClient(_kspell.client());
  m_bSpellCheckConfigure = false;
  startBackgroundSpellCheck();
}

void KoBgSpellCheck::stopSpellChecking()
{
  delete m_bgSpell.kspell;
  m_bgSpell.kspell = 0;
  m_bgSpell.currentParag = 0;
  m_bgSpell.currentTextObj = 0;
}
