/***************************************************************************
 *   Copyright (C) 2004-2009 by Thomas Fischer                             *
 *   fischer@unix-ag.uni-kl.de                                             *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <qregexp.h>

#include <klocale.h>

#include <encoderlatex.h>
#include <file.h>
#include <settings.h>
#include <entry.h>
#include "idsuggestions.h"

namespace KBibTeX
{
    const QRegExp IdSuggestions::unwantedChars = QRegExp( "[^-_:/=+a-zA-Z0-9]+" );

    IdSuggestions::IdSuggestions()
    {
// nothing
    }


    IdSuggestions::~IdSuggestions()
    {
// nothing
    }

    /**
    * Determine list of authors or editors for a given entry
    */
    QStringList IdSuggestions::authorsLastName( BibTeX::Entry *entry )
    {
        QStringList result;

        /** retrieve field holding authors information for entry */
        BibTeX::EntryField *field = entry->getField( BibTeX::EntryField::ftAuthor );
        if ( field == NULL )
        {
            /** no author field available, try editor
                patch by Jurgen Spitzmuller <juergen.sp@t-online.de> */
            field = entry->getField( BibTeX::EntryField::ftEditor );
            if ( field == NULL )
            {
                return result; /** neither author nor editor available */
            }
        }

        /** fetch container holding list of author names */
        BibTeX::PersonContainer *personContainer = field != NULL ? dynamic_cast<BibTeX::PersonContainer*>( field->value()->items.isEmpty() ? NULL : field->value()->items.first() ) : NULL;
        if ( personContainer == NULL || personContainer->persons.isEmpty() )
            return result; /** container not found or is empty */

        /** iterate through container and fetch each author's last name */
        for ( QValueList<BibTeX::Person*>::ConstIterator it = personContainer->persons.begin(); it != personContainer->persons.end(); ++it )
        {
            result.append( normalizeText(( *it )->lastName() ) );
        }

        return result;
    }

    /**
    * Normalize a given text by removing spaces or other unwanted characters,
    * replace some accented and special characters by plain ASCII transliterations
    */
    QString IdSuggestions::normalizeText( const QString& text )
    {
        QString result = text;

        for ( int i = text.length() - 1; i >= 0; --i )
            result[i] = QChar( BibTeX::EncoderLaTeX::unicodeToASCII( result[i].unicode() ) );

        return result.replace( unwantedChars, "" );
    }

    QString IdSuggestions::resolveConflict( BibTeX::File *file, const QString &id, BibTeX::Element *element )
    {
        QString result = id;
        BibTeX::Element *hit = file->containsKey( id ) ;
        if ( hit != NULL && hit != element )
        {
            int i = 0;
            do
            {
                result = QString( "%1-%2" ).arg( id ).arg( ++i );
                hit = file->containsKey( result );
            }
            while ( hit != NULL && hit != element );
        }
        return result;
    }

    /**
    * Determine year for a given entry
    */
    int IdSuggestions::extractYear( BibTeX::Entry *entry )
    {
        /** retrieve field holding year information for entry */
        BibTeX::EntryField *field = entry->getField( BibTeX::EntryField::ftYear );
        if ( field == NULL )
            return -1; /** no year field available */

        /** fetch value item holding year */
        BibTeX::ValueItem *valueItem = field != NULL ? ( field->value()->items.isEmpty() ? NULL : field->value()->items.first() ) : NULL;
        if ( valueItem == NULL )
            return -1; /** no value item found or is empty */

        /** parse value item's text */
        bool ok = FALSE;
        QRegExp yearRegExp( "\\b(\\d{2})?\\d{2}\\b" );
        yearRegExp.search( valueItem->text() );
        int year = QString( yearRegExp.cap( 0 ) ).toInt( &ok );
        if ( !ok ) year = -1;

        return year;
    }

    /**
    * Determine title for a given entry
    */
    QString IdSuggestions::extractTitle( BibTeX::Entry *entry )
    {
        /** retrieve field holding title information for entry */
        BibTeX::EntryField *field = entry->getField( BibTeX::EntryField::ftTitle );
        if ( field == NULL )
            return QString::null; /** no title field available */

        /** *fetch value item holding title */
        BibTeX::ValueItem *valueItem = field->value()->items.isEmpty() ? NULL : field->value()->items.first();
        if ( valueItem == NULL )
            return QString::null; /** no value item found or is empty */

        return valueItem->text();
    }

    QStringList IdSuggestions::createSuggestions( BibTeX::File *file, BibTeX::Entry *entry )
    {
        Settings * settings = Settings::self();
        const QStringList& formatStrList = settings->idSuggestions_formatStrList;
        QStringList result;
        QStringList allKeys = file != NULL ? file->allKeys() : QStringList();
        entry = new BibTeX::Entry( entry );
        if ( file != NULL )
            file->completeReferencedFields( entry );

        for ( QStringList::ConstIterator it = formatStrList.begin(); it != formatStrList.end(); ++it )
        {
            QString id = formatId( entry, *it );
            if ( id.isEmpty() || result.contains( id ) )
                continue;
            if ( !result.contains( id ) )
                result.append( id );
        }

        delete entry;

        return result;
    }

    QString IdSuggestions::createDefaultSuggestion( BibTeX::File *file, BibTeX::Entry *entry )
    {
        Settings * settings = Settings::self();
        if ( settings->idSuggestions_default < 0 || settings->idSuggestions_default >= ( int )settings->idSuggestions_formatStrList.size() )
            return QString::null;

        entry = new BibTeX::Entry( entry );
        if ( file != NULL )
            file->completeReferencedFields( entry );

        QString result = formatId( entry, settings->idSuggestions_formatStrList[settings->idSuggestions_default] );

        delete entry;
        return result;
    }

    QString IdSuggestions::formatId( BibTeX::Entry *entry, const QString& formatStr )
    {
        QString id;
        QStringList tokenList = QStringList::split( '|', formatStr );
        for ( QStringList::ConstIterator tit = tokenList.begin(); tit != tokenList.end(); ++tit )
            id.append( translateToken( entry, *tit ) );

        return id;
    }

    QString IdSuggestions::translateToken( BibTeX::Entry *entry, const QString& token )
    {
        switch ( token[0] )
        {
        case 'a': return translateAuthorsToken( entry, token.mid( 1 ), aOnlyFirst );
        case 'A': return translateAuthorsToken( entry, token.mid( 1 ), aAll );
        case 'z': return translateAuthorsToken( entry, token.mid( 1 ), aNotFirst );
        case 'y':
            {
                int year = extractYear( entry );
                if ( year > -1 )
                    return QString::number( year % 100 + 100 ).mid( 1 );
                else
                    return QString::null;
            }
        case 'Y':
            {
                int year = extractYear( entry );
                if ( year > -1 )
                    return QString::number( year % 10000 + 10000 ).mid( 1 );
                else
                    return QString::null;
            }
        case 't': return translateTitleToken( entry, token.mid( 1 ), FALSE );
        case 'T': return translateTitleToken( entry, token.mid( 1 ), TRUE );
        case '"': return token.mid( 1 );
        default: return QString::null;
        }
    }

    QString IdSuggestions::translateAuthorsToken( BibTeX::Entry *entry, const QString& token, Authors selectAuthors )
    {
        struct IdSuggestionTokenInfo ati = evalToken( token );
        QString result;
        bool first = true, firstInserted = true;
        QStringList authors = authorsLastName( entry );
        for ( QStringList::ConstIterator it = authors.begin(); it != authors.end(); ++it )
        {
            QString author =  normalizeText( *it ).left( ati.len );
            if ( selectAuthors == aAll || ( selectAuthors == aOnlyFirst && first ) || ( selectAuthors == aNotFirst && !first ) )
            {
                if ( !firstInserted )
                    result.append( ati.inBetween );
                result.append( author );
                firstInserted = false;
            }
            first = false;
        }

        if ( ati.toUpper )
            result = result.upper();
        else if ( ati.toLower )
            result = result.lower();

        return result;
    }

    struct IdSuggestionTokenInfo IdSuggestions::evalToken( const QString& token )
    {
        unsigned int pos = 0;
        struct IdSuggestionTokenInfo result;
        result.len = 0x00ffffff;
        result.toLower = FALSE;
        result.toUpper = FALSE;
        result.inBetween = QString::null;

        if ( token.length() > pos )
        {
            int dv = token[pos].digitValue();
            if ( dv > -1 )
            {
                result.len = dv;
                ++pos;
            }
        }

        if ( token.length() > pos )
        {
            result.toLower = token[pos] == 'l';
            result.toUpper = token[pos] == 'u';
            if ( result.toUpper || result.toLower )
                ++pos;
        }

        if ( token.length() > pos + 1 && token[pos] == '"' )
            result.inBetween = token.mid( pos + 1 );

        return result;
    }

    QString IdSuggestions::translateTitleToken( BibTeX::Entry *entry, const QString& token, bool removeSmallWords )
    {
        struct IdSuggestionTokenInfo tti = evalToken( token );
        Settings * settings = Settings::self();
        const QStringList smallWords = settings->idSuggestions_smallWords;

        QString result;
        bool first = TRUE;
        QStringList titleWords = QStringList::split( QRegExp( "\\s+" ), extractTitle( entry ) );
        for ( QStringList::ConstIterator it = titleWords.begin(); it != titleWords.end(); ++it )
        {
            if ( first )
                first = FALSE;
            else
                result.append( tti.inBetween );

            QString lowerText = ( *it ).lower();
            if ( !removeSmallWords || !smallWords.contains( lowerText ) )
                result.append( normalizeText( *it ).left( tti.len ) );
        }

        if ( tti.toUpper )
            result = result.upper();
        else if ( tti.toLower )
            result = result.lower();

        return result;
    }

    /** convert a formatting string into a human readable version (even translated) */
    QString IdSuggestions::formatStrToHuman( const QString& formatStr )
    {
        bool first = TRUE;
        QString text;
        QStringList elements = QStringList::split( '|', formatStr );
        for ( QStringList::iterator it = elements.begin();it != elements.end();++it )
        {
            if ( first ) first = FALSE; else text.append( "\n" );
            if (( *it )[0] == 'a' || ( *it )[0] == 'A' || ( *it )[0] == 'z' )
            {
                struct IdSuggestionTokenInfo info = evalToken(( *it ).mid( 1 ) );
                if (( *it )[0] == 'a' ) text.append( i18n( "First author only" ) );
                else if (( *it )[0] == 'z' ) text.append( i18n( "All but first author" ) );
                else text.append( i18n( "All authors" ) );

                int n = info.len;
                if ( info.len < 0x00ffffff ) text.append( i18n( ", but only first letter of each last name", ", but only first %n letters of each last name", n ) );
                if ( info.toUpper ) text.append( i18n( ", in upper case" ) );
                else if ( info.toLower ) text.append( i18n( ", in lower case" ) );
                if ( info.inBetween != QString::null ) text.append( QString( i18n( ", with '%1' in between" ) ).arg( info.inBetween ) );
            }
            else if (( *it )[0] == 'y' ) text.append( i18n( "Year (2 digits)" ) );
            else if (( *it )[0] == 'Y' ) text.append( i18n( "Year (4 digits)" ) );
            else if (( *it )[0] == 't' || ( *it )[0] == 'T' )
            {
                struct IdSuggestionTokenInfo info = evalToken(( *it ).mid( 1 ) );
                text.append( i18n( "Title" ) );
                int n = info.len;
                if ( info.len < 0x00ffffff ) text.append( i18n( ", but only first letter of each word", ", but only first %n letters of each word", n ) );
                if ( info.toUpper ) text.append( i18n( ", in upper case" ) );
                else if ( info.toLower ) text.append( i18n( ", in lower case" ) );
                if ( info.inBetween != QString::null ) text.append( QString( i18n( ", with '%1' in between" ) ).arg( info.inBetween ) );
                if (( *it )[0] == 'T' ) text.append( i18n( ", small words removed" ) );
            }
            else if (( *it )[0] == '"' ) text.append( QString( i18n( "Text: '%1'" ) ).arg(( *it ).mid( 1 ) ) );
            else text.append( "?" );
        }

        return text;
    }
}
