/***************************************************************************
*   Copyright (C) 2003-2005 by                                            *
*   Unai Garro (ugarro@users.sourceforge.net)                             *
*   Cyril Bosselut (bosselut@b1project.com)                               *
*   Jason Kivlighn (jkivlighn@gmail.com)                                  *
*                                                                         *
*   Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.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 of the License, or     *
*   (at your option) any later version.                                   *
***************************************************************************/

#include "ingredientinputwidget.h"

#include <tqlabel.h>
#include <tqwidgetstack.h>
#include <tqhbox.h>
#include <tqvbox.h>
#include <tqgroupbox.h>
#include <tqbuttongroup.h>
#include <tqradiobutton.h>
#include <tqcheckbox.h>

#include <kcombobox.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kdebug.h>

#include "backends/recipedb.h"
#include "datablocks/unit.h"
#include "widgets/fractioninput.h"
#include "widgets/ingredientcombobox.h"
#include "widgets/headercombobox.h"
#include "widgets/prepmethodcombobox.h"
#include "dialogs/createunitdialog.h"

#include "profiling.h"

IngredientInput::IngredientInput( RecipeDB *db, TQWidget *parent, bool allowHeader ) : TQHBox(parent), database(db)
{
	TQVBox *ingredientVBox = new TQVBox( this );
	TQHBox *typeHBox = new TQHBox( ingredientVBox );

	if ( allowHeader ) {
		typeButtonGrp = new TQButtonGroup();
		TQRadioButton *ingredientRadioButton = new TQRadioButton( i18n( "Ingredient:" ), typeHBox );
		typeButtonGrp->insert( ingredientRadioButton, 0 );

		TQRadioButton *headerRadioButton = new TQRadioButton( i18n( "Ingredient grouping name", "Header:" ), typeHBox );
		typeButtonGrp->insert( headerRadioButton, 1 );

		typeButtonGrp->setButton( 0 );
		connect( typeButtonGrp, TQ_SIGNAL( clicked( int ) ), TQ_SLOT( typeButtonClicked( int ) ) );
	}
	else {
		(void) new TQLabel( i18n( "Ingredient:" ), typeHBox );
		typeButtonGrp = 0;
	}
	
	header_ing_stack = new TQWidgetStack(ingredientVBox);
	ingredientBox = new IngredientComboBox( true, header_ing_stack, database );
	ingredientBox->setAutoCompletion( true );
	ingredientBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box
	ingredientBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) );
	header_ing_stack->addWidget( ingredientBox );
	headerBox = new HeaderComboBox( true, header_ing_stack, database );
	headerBox->setAutoCompletion( true );
	headerBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box
	headerBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) );
	header_ing_stack->addWidget( headerBox );

	TQVBox *amountVBox = new TQVBox( this );
	amountLabel = new TQLabel( i18n( "Amount:" ), amountVBox );
	amountEdit = new FractionInput( amountVBox );
	amountEdit->setAllowRange(true);
	amountEdit->setSizePolicy( TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Fixed ) );

	TQVBox *unitVBox = new TQVBox( this );
	unitLabel = new TQLabel( i18n( "Unit:" ), unitVBox );
	unitBox = new KComboBox( true, unitVBox );
	unitBox->setAutoCompletion( true );
	unitBox->lineEdit() ->disconnect( unitBox ); //so hitting enter doesn't enter the item into the box
	unitBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) );

	TQVBox *prepMethodVBox = new TQVBox( this );
	prepMethodLabel = new TQLabel( i18n( "Preparation Method:" ), prepMethodVBox );
	prepMethodBox = new PrepMethodComboBox( true, prepMethodVBox, database );
	prepMethodBox->setAutoCompletion( true );
	prepMethodBox->lineEdit() ->disconnect( prepMethodBox ); //so hitting enter doesn't enter the item into the box
	prepMethodBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) );

	orButton = new TQCheckBox( i18n( "OR" ), this );

	setStretchFactor( ingredientVBox, 5 );
	setStretchFactor( amountVBox, 1 );
	setStretchFactor( unitVBox, 2 );
	setStretchFactor( prepMethodVBox, 3 );

	connect( ingredientBox, TQ_SIGNAL( activated( int ) ), this, TQ_SLOT( loadUnitListCombo() ) );
	connect( ingredientBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotIngredientBoxLostFocus() ) );
	connect( unitBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotUnitBoxLostFocus() ) );
	connect( prepMethodBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotPrepMethodBoxLostFocus() ) );
	connect( orButton, TQ_SIGNAL( toggled(bool) ), this, TQ_SLOT( orToggled(bool) ) );

	connect( unitBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) );
	connect( ingredientBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) );
	connect( headerBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) );
	connect( prepMethodBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) );
	connect( amountEdit, TQ_SIGNAL( returnPressed( const TQString & ) ), this, TQ_SLOT( signalIngredient() ) );

	unitComboList = new UnitList;

	setFocusProxy( ingredientBox );
}

IngredientInput::~IngredientInput()
{
	delete unitComboList;
	delete typeButtonGrp;
}

void IngredientInput::clear()
{
	unitComboList->clear();

	orButton->setChecked(false);
	typeButtonGrp->setButton( 0 ); //put back to ingredient input
	typeButtonClicked( 0 );

	amountEdit->clear();
	ingredientBox->lineEdit()->setText("");
	prepMethodBox->lineEdit()->setText("");
	headerBox->lineEdit()->setText("");
	unitBox->lineEdit()->setText("");
}

void IngredientInput::orToggled(bool b)
{
	emit orToggled(b,this);
}

void IngredientInput::reloadCombos()
{
	//these only needed to be loaded once
	if ( ingredientBox->count() == 0 ) {
		START_TIMER("Loading ingredient input auto-completion");
		ingredientBox->reload();
		END_TIMER();
	}
	if ( headerBox->count() == 0 ) {
		START_TIMER("Loading ingredient header input auto-completion");
		headerBox->reload();
		END_TIMER();
	}
	if ( prepMethodBox->count() == 0 ) {
		START_TIMER("Loading prep method input auto-completion");
		prepMethodBox->reload();
		END_TIMER();
	}

	loadUnitListCombo();
}

void IngredientInput::slotIngredientBoxLostFocus( void )
{
	if ( ingredientBox->contains( ingredientBox->currentText() ) ) {
		ingredientBox->setCurrentItem( ingredientBox->currentText() );
		loadUnitListCombo();
	}
	else {
		unitBox->clear();
		unitBox->completionObject() ->clear();
		unitComboList->clear();
	}
}

void IngredientInput::slotUnitBoxLostFocus()
{
	if ( unitBox->contains( unitBox->currentText() ) )
		unitBox->setCurrentItem( unitBox->currentText() );
}

void IngredientInput::slotPrepMethodBoxLostFocus()
{
	if ( prepMethodBox->contains( prepMethodBox->currentText() ) )
		prepMethodBox->setCurrentItem( prepMethodBox->currentText() );
}

void IngredientInput::typeButtonClicked( int button_id )
{
	if ( amountEdit->isEnabled() == !bool( button_id ) )  //it is already set (the same button was clicked more than once)
		return ;

	amountEdit->setEnabled( !bool( button_id ) );
	unitBox->setEnabled( !bool( button_id ) );
	prepMethodBox->setEnabled( !bool( button_id ) );

	if ( button_id == 1 ) { //Header
		header_ing_stack->raiseWidget( headerBox );
	}
	else {
		header_ing_stack->raiseWidget( ingredientBox );
	}
}

void IngredientInput::enableHeader( bool enable )
{
	if ( !enable ) {
		typeButtonGrp->setButton( 0 ); //put back to ingredient input
		typeButtonClicked( 0 );
	}
	typeButtonGrp->find(1)->setEnabled(enable);
}

void IngredientInput::signalIngredient()
{
	//validate input; if successful, emit signal
	if ( isHeader() ) {
		if ( header().isEmpty() )
			return;
	}
	else {
		if ( !isInputValid() )
			return;
	}

	emit addIngredient();
}

bool IngredientInput::isInputValid()
{
	if ( ingredientBox->currentText().stripWhiteSpace().isEmpty() ) {
		KMessageBox::error( this, i18n( "Please enter an ingredient" ), TQString::null );
		ingredientBox->setFocus();
		return false;
	}
	return checkAmountEdit() && checkBounds();
}

bool IngredientInput::checkBounds()
{
	if ( ingredientBox->currentText().length() > uint(database->maxIngredientNameLength()) ) {
		KMessageBox::error( this, TQString( i18n( "Ingredient name cannot be longer than %1 characters." ) ).arg( database->maxIngredientNameLength() ) );
		ingredientBox->lineEdit() ->setFocus();
		ingredientBox->lineEdit() ->selectAll();
		return false;
	}

	if ( unitBox->currentText().length() > uint(database->maxUnitNameLength()) ) {
		KMessageBox::error( this, TQString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) );
		unitBox->lineEdit() ->setFocus();
		unitBox->lineEdit() ->selectAll();
		return false;
	}

	TQStringList prepMethodList = TQStringList::split(",",prepMethodBox->currentText());
	for ( TQStringList::const_iterator it = prepMethodList.begin(); it != prepMethodList.end(); ++it ) {
		if ( (*it).stripWhiteSpace().length() > uint(database->maxPrepMethodNameLength()) )
		{
			KMessageBox::error( this, TQString( i18n( "Preparation method cannot be longer than %1 characters." ) ).arg( database->maxPrepMethodNameLength() ) );
			prepMethodBox->lineEdit() ->setFocus();
			prepMethodBox->lineEdit() ->selectAll();
			return false;
		}
	}

	return true;
}

bool IngredientInput::checkAmountEdit()
{
	if ( amountEdit->isInputValid() )
		return true;
	else {
		KMessageBox::error( this, i18n( "Amount field contains invalid input." ),
		                    i18n( "Invalid input" ) );
		amountEdit->setFocus();
		amountEdit->selectAll();
		return false;
	}
}

void IngredientInput::loadUnitListCombo()
{
	TQString store_unit = unitBox->currentText();
	unitBox->clear(); // Empty the combo first
	unitBox->completionObject() ->clear();

	int comboIndex = ingredientBox->currentItem();
	int comboCount = ingredientBox->count();

	if ( comboCount > 0 ) { // If not, the list may be empty (no ingredient list defined) and crashes while reading
		int selectedIngredient = ingredientBox->id( comboIndex );
		database->loadPossibleUnits( selectedIngredient, unitComboList );

		//Populate this data into the ComboBox
		for ( UnitList::const_iterator unit_it = unitComboList->begin(); unit_it != unitComboList->end(); ++unit_it ) {
			unitBox->insertItem( ( *unit_it ).name );
			unitBox->completionObject() ->addItem( ( *unit_it ).name );
			if ( ( *unit_it ).name != (*unit_it ).plural ) {
				unitBox->insertItem( ( *unit_it ).plural );
				unitBox->completionObject() ->addItem( ( *unit_it ).plural );
			}

			if ( !( *unit_it ).name_abbrev.isEmpty() ) {
				unitBox->insertItem( ( *unit_it ).name_abbrev );
				unitBox->completionObject() ->addItem( ( *unit_it ).name_abbrev );
			}
			if ( !(*unit_it ).plural_abbrev.isEmpty() && 
			   ( *unit_it ).name_abbrev != (*unit_it ).plural_abbrev ) {
				unitBox->insertItem( ( *unit_it ).plural_abbrev );
				unitBox->completionObject() ->addItem( ( *unit_it ).plural_abbrev );
			}

		}
	}
	unitBox->lineEdit() ->setText( store_unit );
}

bool IngredientInput::isHeader() const
{
	return typeButtonGrp && (typeButtonGrp->id( typeButtonGrp->selected() ) == 1);
}

Ingredient IngredientInput::ingredient() const
{
	Ingredient ing;

	ing.prepMethodList = ElementList::split(",",prepMethodBox->currentText());
	ing.name = ingredientBox->currentText();
	amountEdit->value(ing.amount,ing.amount_offset);
	ing.units = Unit(unitBox->currentText().stripWhiteSpace(),ing.amount+ing.amount_offset);
	ing.ingredientID = ingredientBox->id( ingredientBox->currentItem() );

	return ing;
}

TQString IngredientInput::header() const
{
	return headerBox->currentText().stripWhiteSpace();
}

void IngredientInput::updateTabOrder()
{
	TQWidget::setTabOrder( ingredientBox, amountEdit );
	TQWidget::setTabOrder( amountEdit, unitBox );
	TQWidget::setTabOrder( unitBox, prepMethodBox );
	TQWidget::setTabOrder( prepMethodBox, orButton );
}


IngredientInputWidget::IngredientInputWidget( RecipeDB *db, TQWidget *parent ) : TQVBox(parent), database(db)
{
	setSizePolicy( TQSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Fixed ) );

	m_ingInputs.append(new IngredientInput(database,this));

	// Connect signals & Slots
	connect( m_ingInputs[0], TQ_SIGNAL(addIngredient()), this, TQ_SLOT(addIngredient()) );
	connect( m_ingInputs[0], TQ_SIGNAL(orToggled(bool,IngredientInput*)), this, TQ_SLOT(updateInputs(bool,IngredientInput*)) );

	reloadCombos();
}

IngredientInputWidget::~IngredientInputWidget()
{
}

void IngredientInputWidget::clear()
{
	//clearing the first input deletes all substitute inputs
	m_ingInputs[0]->clear();
}

void IngredientInputWidget::updateInputs(bool on, IngredientInput* input)
{
	TQValueList<IngredientInput*>::iterator curr = m_ingInputs.find(input);
	IngredientInput *prev_input = *curr;
	++curr;

	if ( on ) {
		IngredientInput *new_input = new IngredientInput(database,this,false);
		new_input->reloadCombos();

		TQWidget::setTabOrder( prev_input, new_input );
		new_input->updateTabOrder();

		connect( new_input, TQ_SIGNAL(addIngredient()), this, TQ_SLOT(addIngredient()) );
		connect( new_input, TQ_SIGNAL(orToggled(bool,IngredientInput*)), this, TQ_SLOT(updateInputs(bool,IngredientInput*)) );
		
		new_input->show();
		m_ingInputs.insert(curr,new_input);

		m_ingInputs[0]->enableHeader(false);
		
	}
	else {
		while ( curr != m_ingInputs.end() ) {
			(*curr)->deleteLater();
			curr = m_ingInputs.remove(curr);
		}
		if ( m_ingInputs.count() == 1 )
			m_ingInputs[0]->enableHeader(true);
	}
}

void IngredientInputWidget::addIngredient()
{
	if ( m_ingInputs[0]->isHeader() ) {
		TQString header = m_ingInputs[0]->header();
		if ( header.isEmpty() )
			return;

		int group_id = createNewGroupIfNecessary( header,database );
		emit headerEntered( Element(header,group_id) );
	}
	else {
		for ( TQValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) {
			if ( !(*it)->isInputValid() )
				return;
		}

		TQValueList<IngredientData> list;
		for ( TQValueList<IngredientInput*>::const_iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) {
			Ingredient ing = (*it)->ingredient();
			ing.ingredientID = createNewIngredientIfNecessary(ing.name,database);

			bool plural = ing.amount+ing.amount_offset > 1;
			ing.units.id = createNewUnitIfNecessary( (plural)?ing.units.plural:ing.units.name, plural, ing.ingredientID, ing.units,database );
			if ( ing.units.id == -1 )  // this will happen if the dialog to create a unit was cancelled
				return ;
	
			TQValueList<int> prepIDs = createNewPrepIfNecessary( ing.prepMethodList,database );
			TQValueList<int>::const_iterator id_it = prepIDs.begin();
			for ( ElementList::iterator it = ing.prepMethodList.begin(); it != ing.prepMethodList.end(); ++it, ++id_it ) {
				(*it).id = *id_it;
			}

			list.append(ing);
		}

		Ingredient ing = list.first();
		list.pop_front();
		ing.substitutes = list;
		emit ingredientEntered( ing );
	}
	clear();

	m_ingInputs[0]->setFocus(); //put cursor back to the ingredient name so user can begin next ingredient
}

int IngredientInputWidget::createNewIngredientIfNecessary( const TQString &ing, RecipeDB *database )
{
	int id = -1;
	if ( ing.isEmpty() )
		return -1;

	id = database->findExistingIngredientByName( ing );
	if (  id == -1 ) {
		database->createNewIngredient( ing );
		id = database->lastInsertID();
	}
	return id;
}

int IngredientInputWidget::createNewUnitIfNecessary( const TQString &unit, bool plural, int ingredientID, Unit &new_unit, RecipeDB *database )
{
	int id = database->findExistingUnitByName( unit );
	if ( -1 == id )
	{
		CreateUnitDialog getUnit( 0, ( plural ) ? TQString::null : unit, ( !plural ) ? TQString::null : unit );
		if ( getUnit.exec() == TQDialog::Accepted ) {
			new_unit = getUnit.newUnit();
			database->createNewUnit( new_unit );

			id = database->lastInsertID();
		}
	}

	if ( !database->ingredientContainsUnit(
			ingredientID,
			id ) )
	{
		database->addUnitToIngredient(
			ingredientID,
			id );
	}

	new_unit = database->unitName( id );

	//loadUnitListCombo();
	return id;
}

TQValueList<int> IngredientInputWidget::createNewPrepIfNecessary( const ElementList &prepMethods, RecipeDB *database )
{
	TQValueList<int> ids;

	if ( prepMethods.isEmpty() )  //no prep methods
		return ids;
	else
	{
		for ( ElementList::const_iterator it = prepMethods.begin(); it != prepMethods.end(); ++it ) {
			int id = database->findExistingPrepByName( (*it).name.stripWhiteSpace() );
			if ( id == -1 )
			{
				database->createNewPrepMethod( (*it).name.stripWhiteSpace() );
				id = database->lastInsertID();
			}
			ids << id;
		}

		return ids;
	}
}

int IngredientInputWidget::createNewGroupIfNecessary( const TQString &group, RecipeDB *database )
{
	if ( group.stripWhiteSpace().isEmpty() )  //no group
		return -1;
	else
	{
		int id = database->findExistingIngredientGroupByName( group );
		if ( id == -1 ) //creating new
		{
			database->createNewIngGroup( group );
			id = database->lastInsertID();
		}

		return id;
	}
}

void IngredientInputWidget::reloadCombos()
{
	for ( TQValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it )
		(*it)->reloadCombos();
}

#include "ingredientinputwidget.moc"
