/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2005-01-01
 * Description : search widgets collection.
 * 
 * Copyright (C) 2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
 * Copyright (C) 2005 by Tom Albers <tomalbers@kde.nl>
 * Copyright (C) 2006-2007 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * 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, 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.
 * 
 * ============================================================ */

/** @file searchwidgets.cpp */

// TQt includes.

#include <tqhbox.h>
#include <tqvbox.h>
#include <tqlabel.h>
#include <tqcheckbox.h>
#include <tqcombobox.h>
#include <tqlineedit.h>
#include <tqgroupbox.h>
#include <tqvgroupbox.h>
#include <tqlayout.h>
#include <tqdatetime.h>

// KDE includes.

#include <tdelocale.h>
#include <kurl.h>
#include <kdialog.h>

// Local includes.

#include "ddebug.h"
#include "album.h"
#include "albuminfo.h"
#include "albummanager.h"
#include "ratingwidget.h"
#include "squeezedcombobox.h"
#include "kdateedit.h"
#include "searchwidgets.h"
#include "searchwidgets.moc"

namespace Digikam
{

static const int RuleKeyTableCount = 11;
static const int RuleOpTableCount  = 18;

static struct
{
    const char                           *keyText;
    TQString                               key;
    SearchAdvancedRule::valueWidgetTypes  cat;
}
RuleKeyTable[] =
{
    { I18N_NOOP("Album"),            "album",           SearchAdvancedRule::ALBUMS   },
    { I18N_NOOP("Album Name"),       "albumname",       SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Album Caption"),    "albumcaption",    SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Album Collection"), "albumcollection", SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Tag"),              "tag",             SearchAdvancedRule::TAGS     },
    { I18N_NOOP("Tag Name"),         "tagname",         SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Image Name"),       "imagename",       SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Image Date"),       "imagedate",       SearchAdvancedRule::DATE     },
    { I18N_NOOP("Image Caption"),    "imagecaption",    SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Keyword"),          "keyword",         SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Rating"),           "rating",          SearchAdvancedRule::RATING   },
};

static struct
{
    const char                           *keyText;
    TQString                               key;
    SearchAdvancedRule::valueWidgetTypes  cat;
}
RuleOpTable[] =
{
    { I18N_NOOP("Contains"),           "LIKE",         SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Does Not Contain"),   "NLIKE",        SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Equals"),             "EQ",           SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Does Not Equal"),     "NE",           SearchAdvancedRule::LINEEDIT },
    { I18N_NOOP("Equals"),             "EQ",           SearchAdvancedRule::ALBUMS   },
    { I18N_NOOP("Does Not Equal"),     "NE",           SearchAdvancedRule::ALBUMS   },
    { I18N_NOOP("Contains"),           "LIKE",         SearchAdvancedRule::ALBUMS   },
    { I18N_NOOP("Does Not Contain"),   "NLIKE",        SearchAdvancedRule::ALBUMS   },
    { I18N_NOOP("Equals"),             "EQ",           SearchAdvancedRule::TAGS     },
    { I18N_NOOP("Does Not Equal"),     "NE",           SearchAdvancedRule::TAGS     },
    { I18N_NOOP("Contains"),           "LIKE",         SearchAdvancedRule::TAGS     },
    { I18N_NOOP("Does Not Contain"),   "NLIKE",        SearchAdvancedRule::TAGS     },
    { I18N_NOOP("After"),              "GT",           SearchAdvancedRule::DATE     },
    { I18N_NOOP("Before"),             "LT",           SearchAdvancedRule::DATE     },
    { I18N_NOOP("Equals"),             "EQ",           SearchAdvancedRule::DATE     },
    { I18N_NOOP("At least"),           "GTE",          SearchAdvancedRule::RATING   },
    { I18N_NOOP("At most"),            "LTE",          SearchAdvancedRule::RATING   },
    { I18N_NOOP("Equals"),             "EQ",           SearchAdvancedRule::RATING   },
};

SearchRuleLabel::SearchRuleLabel(const TQString& text, TQWidget *parent,
                                 const char *name, WFlags f )
               : TQLabel(text, parent, name, f)
{
}

void SearchRuleLabel::mouseDoubleClickEvent( TQMouseEvent * e )
{
   emit signalDoubleClick( e );
}

SearchAdvancedRule::SearchAdvancedRule(TQWidget* parent, SearchAdvancedRule::Option option)
                  : SearchAdvancedBase(SearchAdvancedBase::RULE)
{
    m_box = new TQVBox(parent);
    m_box->layout()->setSpacing( KDialog::spacingHint() );
    m_box->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );

    m_optionsBox   = 0;
    m_option       = option;
    if (option != NONE)
    {
        m_optionsBox  = new TQHBox( m_box );
        m_label = new SearchRuleLabel( option == AND ?
                                       i18n("As well as") : i18n("Or"),
                                       m_optionsBox);
        TQFrame* hline = new TQFrame( m_optionsBox );
        hline->setFrameStyle( TQFrame::HLine|TQFrame::Sunken );
        m_label->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum );
        hline->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );

        connect( m_label, TQ_SIGNAL( signalDoubleClick( TQMouseEvent* ) ),
                 this,  TQ_SLOT( slotLabelDoubleClick() ));
    }

    m_hbox = new TQWidget( m_box );
    m_hbox->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum );

    m_key = new TQComboBox( m_hbox, "key" );
    m_key->setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Minimum );
    for (int i=0; i< RuleKeyTableCount; i++)
        m_key->insertItem( i18n(RuleKeyTable[i].keyText), i );

    m_operator = new TQComboBox( m_hbox );
    m_operator->setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Minimum );
    // prepopulate the operator widget to get optimal size
    for (int i=0; i< RuleOpTableCount; i++)
        m_operator->insertItem( i18n(RuleOpTable[i].keyText), i );
    m_operator->adjustSize();

    m_valueBox = new TQHBox( m_hbox );
    m_widgetType = NOWIDGET;

    slotKeyChanged( 0 );
    m_check = new TQCheckBox( m_hbox );

    m_hboxLayout = new TQHBoxLayout( m_hbox );
    m_hboxLayout->setSpacing( KDialog::spacingHint() );
    m_hboxLayout->addWidget( m_key );
    m_hboxLayout->addWidget( m_operator );
    m_hboxLayout->addWidget( m_valueBox );
    m_hboxLayout->addWidget( m_check, 0, TQt::AlignRight );

    m_box->show();

    connect( m_key, TQ_SIGNAL( activated(int) ),
             this, TQ_SLOT(slotKeyChanged(int)));
    connect( m_key, TQ_SIGNAL( activated(int) ),
             this, TQ_SIGNAL( signalPropertyChanged() ));
    connect( m_operator, TQ_SIGNAL( activated(int) ),
             this, TQ_SIGNAL( signalPropertyChanged() ));
    connect( m_check, TQ_SIGNAL( toggled( bool ) ),
             this, TQ_SIGNAL( signalBaseItemToggled() ));
}

void SearchAdvancedRule::setValues(const KURL& url)
{
    if (url.isEmpty())
        return;

    // set the key widget
    for (int i=0; i< RuleKeyTableCount; i++)
    {
        if (RuleKeyTable[i].key == url.queryItem("1.key"))
        {
            m_key->setCurrentText( i18n(RuleKeyTable[i].keyText) );
        }
    }

    // set the operator and the last widget
    slotKeyChanged( m_key->currentItem() );
    for (int i=0; i< RuleOpTableCount; i++)
    {
        if ( RuleOpTable[i].key == url.queryItem("1.op") &&
             RuleOpTable[i].cat == m_widgetType )
        {
            m_operator->setCurrentText( i18n(RuleOpTable[i].keyText) );
        }
    }

    // Set the value for the last widget.
    TQString value = url.queryItem("1.val");
    if (m_widgetType == LINEEDIT)
        m_lineEdit->setText( value );

    if (m_widgetType == DATE)
        m_dateEdit->setDate( TQDate::fromString( value, TQt::ISODate) );

    if (m_widgetType == RATING)
    {
        bool ok;
        int  num = value.toInt(&ok);
        if (ok)
            m_ratingWidget->setRating( num );
    }

    if (m_widgetType == TAGS || m_widgetType == ALBUMS)
    {
        bool ok;
        int  num = value.toInt(&ok);
        if (ok)
        {
            TQMapIterator<int,int> it;
            for (it = m_itemsIndexIDMap.begin() ; it != m_itemsIndexIDMap.end(); ++it)
            {
                if (it.data() == num)
                    m_valueCombo->setCurrentItem( it.key() );
            }
        }
    }
}

SearchAdvancedRule::~SearchAdvancedRule()
{
    delete m_box;
}

void SearchAdvancedRule::slotLabelDoubleClick()
{
    if (m_option == AND)
    {
        m_option=OR;
        m_label->setText( i18n("Or") );
    }
    else
    {
        m_option=AND;
        m_label->setText( i18n("As well as") );
    }
    emit signalPropertyChanged();
}

void SearchAdvancedRule::slotKeyChanged(int id)
{
    TQString currentOperator = m_operator->currentText();
    valueWidgetTypes currentType = m_widgetType;

    // we need to save the current size of the operator combobox
    // otherise clear() will shrink it
    TQSize curSize = m_operator->size();
    m_operator->clear();
    m_widgetType = RuleKeyTable[id].cat;

    for (int i=0; i< RuleOpTableCount; i++)
    {
        if ( RuleOpTable[i].cat == m_widgetType )
        {
            m_operator->insertItem( i18n(RuleOpTable[i].keyText) );
    
            if ( currentOperator == RuleOpTable[i].key )
                m_operator->setCurrentText( currentOperator );
        }
    }
    m_operator->setFixedSize(curSize);
    setValueWidget( currentType, m_widgetType );
}

void SearchAdvancedRule::setValueWidget(valueWidgetTypes oldType, valueWidgetTypes newType)
{
    // this map is used to sort album and tag list combobox
    typedef TQMap<TQString, int> SortedList;
    
    if (oldType == newType)
        return;

    if (m_lineEdit && oldType == LINEEDIT)
        delete m_lineEdit;

    if (m_dateEdit && oldType == DATE)
        delete m_dateEdit;

    if (m_ratingWidget && oldType == RATING)
	delete m_ratingWidget;

    if (m_valueCombo && (oldType == ALBUMS || oldType == TAGS))
        delete m_valueCombo;

    if (newType == DATE)
    {
        m_dateEdit = new KDateEdit( m_valueBox,"datepicker");
        m_dateEdit->setSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Minimum );
        m_dateEdit->show();

        connect( m_dateEdit, TQ_SIGNAL( dateChanged(const TQDate& ) ),
                 this, TQ_SIGNAL(signalPropertyChanged()));
    }
    else if (newType == LINEEDIT)
    {
        m_lineEdit = new TQLineEdit( m_valueBox, "lineedit" );
        m_lineEdit->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );
        m_lineEdit->show();

        connect( m_lineEdit, TQ_SIGNAL ( textChanged(const TQString&) ),
                 this, TQ_SIGNAL(signalPropertyChanged()));

    }
    else if (newType == ALBUMS)
    {
        m_valueCombo = new SqueezedComboBox( m_valueBox, "albumscombo" );
        m_valueCombo->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );

        AlbumManager* aManager = AlbumManager::instance();
        AlbumList aList = aManager->allPAlbums();

        m_itemsIndexIDMap.clear();
        
        // First we need to sort the album list.
        // We create a map with the album url as key, so that it is
        // automatically sorted.
        SortedList sAList;
        
        for ( AlbumList::Iterator it = aList.begin();
              it != aList.end(); ++it )
        {
            PAlbum *album = (PAlbum*)(*it);
            if ( !album->isRoot() )
            {
                sAList.insert(album->url().remove(0,1), album->id());
            }
        }
        
        // Now we can iterate over the sorted list and fill the combobox
        int index = 0;
        for ( SortedList::Iterator it = sAList.begin();
              it != sAList.end(); ++it )
        {
            m_valueCombo->insertSqueezedItem( it.key(), index );
            m_itemsIndexIDMap.insert(index, it.data());
            index++;
            }

        m_valueCombo->show();

        connect( m_valueCombo, TQ_SIGNAL( activated(int) ),
                 this, TQ_SIGNAL( signalPropertyChanged() ));
    }
    else if (newType == TAGS)
    {
        m_valueCombo = new SqueezedComboBox( m_valueBox, "tagscombo" );
        m_valueCombo->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum );

        AlbumManager* aManager = AlbumManager::instance();
        AlbumList tList = aManager->allTAlbums();

        m_itemsIndexIDMap.clear();
        
        // First we need to sort the tags.
        // We create a map with the album tagPath as key, so that it is
        // automatically sorted.
        SortedList sTList;
        
        for ( AlbumList::Iterator it = tList.begin();
              it != tList.end(); ++it )
        {
            TAlbum *album = (TAlbum*)(*it);
            if ( !album->isRoot() )
            {
                sTList.insert(album->tagPath(false), album->id());
            }
        }
        
        // Now we can iterate over the sorted list and fill the combobox
        int index = 0;
        for (SortedList::Iterator it = sTList.begin();
             it != sTList.end(); ++it)
        {
            m_valueCombo->insertSqueezedItem( it.key(), index );
            m_itemsIndexIDMap.insert( index, it.data() );
            ++index;
        }
        
        m_valueCombo->show();

        connect( m_valueCombo, TQ_SIGNAL( activated(int) ),
                 this, TQ_SIGNAL( signalPropertyChanged() ));
    }
    else if (newType == RATING)
    {
        m_ratingWidget = new RatingWidget( m_valueBox );
        m_ratingWidget->show();

        connect( m_ratingWidget, TQ_SIGNAL( signalRatingChanged(int) ),
                 this, TQ_SIGNAL( signalPropertyChanged() ));
    }
}

TQString SearchAdvancedRule::urlKey() const
{
    return RuleKeyTable[m_key->currentItem()].key;
}

TQString SearchAdvancedRule::urlOperator() const
{
    TQString string;

    int countItems = 0;
    for (int i=0; i< RuleOpTableCount; i++)
    {
        if ( RuleOpTable[i].cat == m_widgetType )
        {
            if ( countItems == m_operator->currentItem() )
                string = RuleOpTable[i].key;
            ++countItems;
        }
    }

    return string;
}

TQString SearchAdvancedRule::urlValue() const
{
    TQString string;

    if (m_widgetType == LINEEDIT)
        string = m_lineEdit->text() ;

    else if (m_widgetType == DATE)
        string = m_dateEdit->date().toString(TQt::ISODate) ;

    else if (m_widgetType == TAGS || m_widgetType == ALBUMS)
        string = TQString::number(m_itemsIndexIDMap[ m_valueCombo->currentItem() ]);

    else if (m_widgetType == RATING)
        string = TQString::number(m_ratingWidget->rating()) ;

    return string;
}

TQWidget* SearchAdvancedRule::widget() const
{
    return m_box;
}

bool SearchAdvancedRule::isChecked() const
{
    return (m_check && m_check->isChecked());
}

void SearchAdvancedRule::addOption(Option option)
{
    if (option == NONE)
    {
        removeOption();
        return;
    }

    m_box->layout()->remove(m_hbox);

    m_optionsBox = new TQHBox(m_box);
    new TQLabel(option == AND ? i18n("As well as") : i18n("Or"), m_optionsBox);
    TQFrame* hline = new TQFrame(m_optionsBox);
    hline->setFrameStyle(TQFrame::HLine|TQFrame::Sunken);
    hline->setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Minimum);
    m_optionsBox->show();

    m_box->layout()->add(m_hbox);
    m_option =  option;
}

void SearchAdvancedRule::removeOption()
{
    m_option = NONE;
    delete m_optionsBox;
    m_optionsBox = 0;
}

void SearchAdvancedRule::addCheck()
{
    m_check = new TQCheckBox(m_hbox);
    m_check->setSizePolicy(TQSizePolicy::Minimum, TQSizePolicy::Minimum);
    m_hboxLayout->addWidget( m_check, 0, TQt::AlignRight );
    m_check->show();

    connect( m_check, TQ_SIGNAL( toggled( bool ) ),
             this, TQ_SIGNAL( signalBaseItemToggled() ));
}

void SearchAdvancedRule::removeCheck()
{
    delete m_check;
    m_check = 0;
}

SearchAdvancedGroup::SearchAdvancedGroup(TQWidget* parent)
                   : SearchAdvancedBase(SearchAdvancedBase::GROUP)
{
    m_box      = new TQHBox(parent);
    m_box->layout()->setSpacing(KDialog::spacingHint());
    m_groupbox = new TQVGroupBox(m_box);
    m_check    = new TQCheckBox(m_box);
    m_option   = SearchAdvancedRule::NONE;
    m_box->show();

    connect( m_check, TQ_SIGNAL( toggled( bool ) ),
             this, TQ_SIGNAL( signalBaseItemToggled() ));
}

SearchAdvancedGroup::~SearchAdvancedGroup()
{
    delete m_box;
}

TQWidget* SearchAdvancedGroup::widget() const
{
    return m_box;
}

bool SearchAdvancedGroup::isChecked() const
{
    return m_check->isChecked();
}

void SearchAdvancedGroup::addRule(SearchAdvancedRule* rule)
{
    if (m_childRules.isEmpty() && rule->option() != SearchAdvancedRule::NONE)
    {
        // this is the first rule being inserted in this group.
        // get its option and remove its option
        addOption(rule->option());
        rule->removeOption();
    }

    rule->removeCheck();

    m_childRules.append(rule);
    rule->widget()->reparent(m_groupbox, TQPoint(0,0));
    rule->widget()->show();
}

void SearchAdvancedGroup::removeRules()
{
    typedef TQValueList<SearchAdvancedRule*> RuleList;

    for (RuleList::iterator it = m_childRules.begin();
         it != m_childRules.end(); ++it)
    {
        SearchAdvancedRule* rule = (SearchAdvancedRule*)(*it);
        if (it == m_childRules.begin())
        {
            rule->addOption(m_option);
        }
        rule->addCheck();

        rule->widget()->reparent((TQWidget*)m_box->parent(), TQPoint(0,0));
        rule->widget()->show();
    }

    m_childRules.clear();
    removeOption();
}

TQValueList<SearchAdvancedRule*> SearchAdvancedGroup::childRules() const
{
    return m_childRules;
}

void SearchAdvancedGroup::addOption(Option option)
{
    m_option = option;
    m_groupbox->setTitle(m_option == SearchAdvancedRule::AND ? i18n("As well as") : i18n("Or"));
}

void SearchAdvancedGroup::removeOption()
{
    m_option = NONE;
    m_groupbox->setTitle("");
}

}  // namespace Digikam
