/* -*- tab-width: 4; c-basic-offset: 3; -*-
 *
 * Copyright (c) 2000-2003, Roland Jesse
 * All rights reserved.
 *
 * This file is part of the Mango recipe management distribution.
 * For license and copyright information see the file COPYRIGHT or 
 * <http://www.opensource.org/licenses/bsd-license.html>.
 *
 * $Id: CreateRecipeWindow.cpp,v 1.28 2003/10/27 14:14:43 rjesse Exp $
 */

#include "CreateRecipeWindow.h"
#include "Ingredient.h"
#include "Recipe.h"
#include "Repository.h"

#include <qcombobox.h>
#include <qfiledialog.h>
#include <qimage.h>
#include <qinputdialog.h>
#include <qlineedit.h>
#include <qlistbox.h>
#include <qmessagebox.h>
#include <qmultilineedit.h>
#include <qspinbox.h>
#include <qtable.h>

// Column indices for the ingredient table.
#define ING_COL_AMOUNT 0
#define ING_COL_MEASURE 1
#define ING_COL_DESCR 2

CreateRecipeWindow::CreateRecipeWindow( QWidget* parent)
   : CreateRecipeForm( parent)
{
   initWidgets();
}

CreateRecipeWindow::CreateRecipeWindow( const Recipe& recipe, QWidget* parent)
   : CreateRecipeForm( parent)
{
   m_recipe = recipe;
   m_cache = m_recipe;
   initWidgets();
   update();
}

CreateRecipeWindow::~CreateRecipeWindow()
{
}

void
CreateRecipeWindow::initWidgets() // private
{
   setMDICaption( caption());

   spinYieldsQty->setMaxValue( INT_MAX);
   spinYieldsRange->setMaxValue( INT_MAX);
   
   /* Initialise cookbook combo box. */
   QStringList cbNames = Repository::getInstance()->getCookbookNames();
   comboAddCookbook->insertStringList( cbNames);

   /* Initialise ingredients table. */
   tableIngredients->setNumCols( 3);
   tableIngredients->setShowGrid( true);
   QHeader* ingredientsHeader = tableIngredients->horizontalHeader();
     ingredientsHeader->setLabel( ING_COL_AMOUNT, tr( "Amount"));
	 ingredientsHeader->setLabel( ING_COL_MEASURE, tr( "Measurement"));
	 ingredientsHeader->setLabel( ING_COL_DESCR, tr( "Description"));
   addIngredientRow();
	 
   /* The user may select mulitple categories, e.g. for deletion. */
   listboxCategories->setSelectionMode( QListBox::Extended);
   /*
	* Initialise categories list box with all currently known categories. 
	*/
   Recipe::Categories cat = 
	  Repository::getInstance()->getKnownCategories();
   Recipe::Categories::iterator it;
   for (it = cat.begin(); it != cat.end(); it++) {
	  m_addCategoryCombo->insertItem( *it);
   }

   // Initialise picture and picture selection combo box.
   labelPicture->pixmap()->fill( backgroundColor());
   comboSelectPicture->clear();

   connect( comboIngredientGroup, SIGNAL( activated(const QString&)),
			this, SLOT( slotShowIngredients(const QString&)));

   // Remarks
   connect( m_comboUserName, SIGNAL( activated( const QString&)), SLOT( slotRemarksUserChanged(const QString&)));
   connect( m_editRemarksNew, SIGNAL( returnPressed()), SLOT( slotAddRemark()));
   connect( m_btnRemarksAdd, SIGNAL( clicked()), SLOT( slotAddRemark()));
   connect( m_btnRemarksRemove, SIGNAL( clicked()), SLOT( slotRemoveSelectedRemarks()));
} // initWidgets()

/*! (Re)display the recipe information. */
void
CreateRecipeWindow::update()	// public slot
{
   editTitle->setText( m_cache.title());
   editTypist->setText( m_cache.typist());
   editSource->setText( m_cache.source());
   spinYieldsQty->setValue( m_cache.yieldQty());
   spinYieldsRange->setValue( m_cache.yieldRange());
   editYieldsUnit->setText( m_cache.yieldUnit());

   listboxCookbooks->insertStringList( m_cache.cookbooks());

   comboIngredientGroup->clear();
   const Recipe::Ingredients ings = m_cache.ingredients();
   Recipe::Ingredients::const_iterator it;
   for (it  = ings.begin(); it != ings.end(); it++) {
	  comboIngredientGroup->insertItem( it.key());
   }
   QString group( comboIngredientGroup->text( comboIngredientGroup->currentItem()));
   slotShowIngredients( group);
   
   listboxCategories->clear();
   listboxCategories->insertStringList( m_cache.categories());
   texteditDirections->setText( m_cache.directions());

   // Pictures
   comboSelectPicture->clear();
   comboSelectPicture->insertStringList( m_cache.allPictureLabels());
   comboSelectPicture->setCurrentText( m_cache.title());
   labelPicture->setPixmap( m_cache.picture( m_cache.title()));

   // Remarks
   m_comboUserName->clear();
   m_comboUserName->insertStringList( m_cache.remarks().keys());
   // default to typist of the recipe
   for (int i = 0; i < m_comboUserName->count(); i++) {
      if (m_comboUserName->text( i) == m_cache.typist()) {
         m_comboUserName->setCurrentItem( i);
         break;
      }
   }
   if (m_cache.remarks().count() > 0)
      slotRemarksUserChanged( m_comboUserName->currentText());
   else {
      m_listBoxRemarks->setEnabled( false);
      m_btnRemarksAdd->setEnabled( false);
      m_btnRemarksRemove->setEnabled( false);
      m_listBoxRemarks->setEnabled( false);
   }
   m_editRemarksNew->clear();
} // update()

/*!
  Extract all typed information into a recipe structure and store the
  recipe in a cookbook.

  \bug The title is ignored.
  
  \todo If recipe's cookbook list empty: Popup window with all
    available cookbooks and ask user to which the recipe is to be
    added. In case there is no cookbook available yet, don't ask and
    create a new one. Add it to the Repository.
*/
void
CreateRecipeWindow::slotApply()
{
   QString oldRecipeTitle( m_recipe.title());
   bool isNewRecipe = true;
   if (m_recipe.isValid()) isNewRecipe = false;
   
   QStringList oldCBList( m_recipe.cookbooks());
   m_recipe = m_cache;

   /* Verify Directions text in order to avoid missing text just
	  because the user did not press enter at the end.
   */
   if (m_cache.directions() != texteditDirections->text())
	  m_recipe.setDirections( texteditDirections->text());


   // Remarks
   QStringList remarks;
   for (uint r = 0; r < m_listBoxRemarks->count(); r++)
      remarks.append( m_listBoxRemarks->text( r));
   m_recipe.setRemarksForUser( m_comboUserName->currentText(), remarks);

   // Mark cookbooks
   Repository* repo = Repository::getInstance();
   QStringList l( m_recipe.cookbooks());
   if (!m_recipe.cookbooks().isEmpty()) {
	  QStringList::iterator it;
	  for (it = l.begin(); it != l.end(); it++) {
		 QString cbName( *it);
         RecipeModel* rm = repo->getCookbook( cbName);
         if (rm->contains( m_recipe)) rm->update( m_recipe);
         else rm->add( m_recipe);
	  }
   }
   // Remove from cookbooks it no longer belongs to.
   QStringList::const_iterator it2;
   for (it2 = oldCBList.begin(); it2 != oldCBList.end(); it2++) {
      QString cbName( *it2);
      if (0 == l.grep( cbName).count()) {
         RecipeModel* rm = repo->getCookbook( cbName);
         if (rm->contains( m_recipe)) rm->remove( m_recipe);
      }
   }
   
   // Store Categories in repository.
   Recipe::Categories knownCats = repo->getKnownCategories();
   for (uint a = 0; a < listboxCategories->count(); a++) {
	  QString str( listboxCategories->text( a));
	  QStringList grepRes = knownCats.grep( str);
	  if (grepRes.count() == 0) {
		 repo->addCategory( str);
		 knownCats.append( str);
	  }
   }

   if (isNewRecipe) m_recipe.calculateFingerprint();
} // slotApply()

/*!
  Simple do a refresh on the original recipe's picture to make
  sure that its MimeSource is set correctly.
*/
bool
CreateRecipeWindow::close()
{
   m_recipe.setPicture( m_recipe.title(), m_recipe.picture( m_recipe.title()));
   return inherited::close();
}

/*!
 Adds a row to the end of the ingredient table.
*/
void
CreateRecipeWindow::addIngredientRow()
{
   tableIngredients->setNumRows( tableIngredients->numRows() + 1);
   QStringList measureNames;
   QStringList mmMeasures = Measurement::mmAbbrevs();
   mmMeasures.prepend( "");     // in case the user wants a measurementless ingredient
   for (QStringList::const_iterator it = mmMeasures.begin();
        it != mmMeasures.end();
        it++)
   {
      Measurement m( *it);
      QString mDescr( m.description());
      measureNames.append( mDescr);
      m_measurements.insert( mDescr, m);
   }
   QComboTableItem* meaItem = new QComboTableItem( tableIngredients, measureNames);
   meaItem->setEditable( false);
   tableIngredients->setItem( tableIngredients->numRows() -1, ING_COL_MEASURE, meaItem);
} // addIngredientRow()

/*!
  Add the currently entered category to the internal recipe cache.
 */
void
CreateRecipeWindow::slotAddCategory()
{
   QString str = m_addCategoryCombo->currentText();
   QStringList grepRes = m_cache.categories().grep( str);
   if (grepRes.count() == 0) {
	  m_cache.addCategory( str);
	  listboxCategories->insertItem( str);
	  listboxCategories->sort();
   }
}

/*!
 * Deletes all selected categories from the recipe.
 *
 * Actuall, we only need to delete them from the list box as they are not
 * <em>added</em> to the recipe before the user finishes to type it in.
 */
void
CreateRecipeWindow::slotDeleteCategories()
{
   for (uint idx = 0; idx < listboxCategories->count(); idx++) {
	  if (listboxCategories->isSelected( idx)) {
         m_cache.removeCategory( listboxCategories->text( idx));
		 listboxCategories->removeItem( idx);
      }
   }
}

/*!
  Add a cookbook name to the internal recipe.
 
  This is done whenever the user selects a cookbook name from the
  "cookbook" combo box.
 */
void
CreateRecipeWindow::slotAddCookbook( const QString& name)
{
   if ((m_cache.cookbooks().grep( name).count() == 0) // avoid double entries
       && (Repository::getInstance()->hasCookbook( name))) // allow only existing cookbook names
   {
      m_cache.addCookbook( name);
      listboxCookbooks->insertItem( name);
   }
}

/*!
  Creates a new cookbook.

  Ask the user for the name of the cookbook, create it, and add it to
  the recipe's list of cookbooks.
*/
void CreateRecipeWindow::
slotNewCookbook()
{
   bool retry = true;
   bool clickedOk = false;
   QString newCbName;
   Repository* repo = Repository::getInstance();
   while (retry) {
      newCbName = QInputDialog::getText(
         tr( "Create a new cookbook."),
         tr( "Please enter a name for the new cookbook.\n Note: An empty cookbook with this name will immediately be created,\n regardless of whether or not you apply any other changes made in this dialog."),
         QLineEdit::Normal, newCbName, &clickedOk, this);
      if (clickedOk && !newCbName.isEmpty()) {
         if (repo->getCookbookNames().grep( newCbName).count() > 0) {
            int response = QMessageBox::critical(
               this, tr( "Existing cookbook name entered.", "Response to the user when a name for a new cookbook was entered that is already in use."),
               tr( "A cookbook with the name %1 already exists. Do you want to change the name and try again?") .arg( newCbName),
               QMessageBox::Yes, QMessageBox::No);
            if (QMessageBox::Yes == response) {
               retry = true;
            }
            else {
               retry = false;
            }
         }
         else {
            retry = false;
            repo->createCookbook( newCbName);
            listboxCookbooks->insertItem( newCbName);
            comboAddCookbook->insertItem( newCbName);
            m_cache.addCookbook( newCbName);
         }
      }
      else {
         if (newCbName.isEmpty()) {
            QMessageBox::information(
               this, tr( "Empty cookbook name ignored."),
               tr( "You entered an empty cookbook name.\nLet's just forget about it."));
         }
         retry = false;
      }
   }   
}

/*!
  Removes all selected cookbooks from the internal list of
  cookbooks. The recipe is not removed from these cookbooks
  at this point. slotApply() takes care of that.

  \sa slotApply()
*/
void CreateRecipeWindow::
slotRemoveSelectedCookbooks()
{
   for (uint idx = 0; idx < listboxCookbooks->count(); idx++) {
      if (listboxCookbooks->isSelected( idx)) {
         m_cache.removeCookbook( listboxCookbooks->text( idx));
         listboxCookbooks->removeItem( idx);
      }
   }
}

/*! Add the current ingredient to the internal recipe cache. The
  values are verified and a new (empty) row is inserted to the
  ingredients table provide the user with the capability of adding
  just another ingredient.

  \todo Verify entries. Use validator for first column.
*/
void
CreateRecipeWindow::slotAddIngredient()
{
   // FIXME: verify entries

   Ingredient ingredient;
   int curRow = tableIngredients->currentRow();
   for (int c = 0; c < tableIngredients->numCols(); c++) {
	  QTableItem* current = tableIngredients->item( curRow, c);
	  if (!current) return;		// no content set
	  switch (c) {
		 case 0: ingredient.setAmountQty( current->text().toFloat()); break;
		 case 1:
            if (current->rtti() == 1) {
               QComboTableItem* cbitem = (QComboTableItem*) current;
               Measurement m( m_measurements[cbitem->currentText()]);
               ingredient.setMeasurement( m);
            }
            else {
               qDebug( "Internal error: CRW::slotAddIngredient(): column type is not a combo box!");
            }
            break;
		 case 2: ingredient.setName( current->text()); break;
	  }
   }
   QString curGroup( comboIngredientGroup->currentText());
   Recipe::IngredientList ingList = m_cache.ingredients()[curGroup];
   ingList.append( ingredient);

   /* Add an empty row so user can enter next ingredient. */
   addIngredientRow();
}

/*!
  Deletes all selected ingredients from the cached recipe.

  \todo Use QTableSelection.
*/
void
CreateRecipeWindow::slotDeleteIngredients()
{
   QString group( comboIngredientGroup->currentText());
   Recipe::IngredientList ingList = m_cache.ingredients()[group];
   for (int r = 0; r < tableIngredients->numRows(); r++) {
      if (tableIngredients->isRowSelected( r, true)) {
		 // create a dummy ingredient for comparison
		 Ingredient dummyIng;
		 for (int c = 0; c < tableIngredients->numCols(); c++) {
			QTableItem* current = tableIngredients->item( r, c);
			if (current) {
			   switch (c) {
				  case 0:		// amount
					 dummyIng.setAmountQty( current->text().toFloat());
					 break;
				  case 1:		// measurement
					 dummyIng.setMeasurement( current->text());
					 break;
				  case 2:		// name
					 dummyIng.setName( current->text());
					 break;
			   }
			}
		 } // for (c)
         if (! dummyIng.isEmpty()) {
            Recipe::IngredientList::iterator it = ingList.find( dummyIng);
            if (it != ingList.end()) {
               ingList.remove( it);
               m_cache.ingredients()[group] = ingList;
            }
         }
		 tableIngredients->removeRow( r);
	  } // if (selected)
   } // for (r)
   if (tableIngredients->numRows() == 0)
      addIngredientRow();
} // slotDeleteIngredients()

/*!
  Delete the current ingredient group.
  In case the group is not empty, pop up a message dialog telling
  so and don't delete the group.
*/
void
CreateRecipeWindow::slotDeleteIngredientGroup()
{
   bool doDelete = true;
   QString group( comboIngredientGroup->currentText());
   Recipe::IngredientList ingList = m_cache.ingredients()[group];
   if (ingList.count() != 0) {
	  doDelete = false;
	  QString msg( tr( "The current ingredient group is <em>not empty</em>. Removing the group will delete all its ingredients, too.\n Proceed anyway?"));
	  int choice = QMessageBox::warning( this, tr( "Mango - Remove ingredient group"), msg, QMessageBox::Yes, QMessageBox::No);
	  switch (choice) {
		 case QMessageBox::Yes:
			doDelete = true;
			break;
		 case QMessageBox::No:
			break;
	  }
   }

   if (doDelete) {
	  ingList.clear();
	  Recipe::Ingredients::iterator it = m_cache.ingredients().find( group);
	  m_cache.ingredients().erase( it);
   }
   update();
} // slotDeleteIngredientGroup()

/*!
  Fill the ingredient table with all ingredients belonging
  to \a group.
*/
void
CreateRecipeWindow::slotShowIngredients( const Recipe::IngredientList& ilist)
{
   // Clean table.
   for (int i = 0; i < tableIngredients->numRows(); i++) {
	  tableIngredients->removeRow( i);
   }
   tableIngredients->insertRows( 0, ilist.count());

   Recipe::IngredientList::const_iterator it;
   int row = 0;
   for (it = ilist.begin(); it != ilist.end(); it++, row++) {
	  tableIngredients->setText( row, ING_COL_AMOUNT, QString::number( (*it).amountQty()));
      QStringList measureNames;
      QStringList mmMeasures = Measurement::mmAbbrevs();
      mmMeasures.prepend( "");     // in case the user wants a measurementless ingredient
      for (QStringList::const_iterator lit = mmMeasures.begin();
           lit != mmMeasures.end();
           lit++)
      {
         Measurement m( *lit);
         QString mDescr( m.description());
         measureNames.append( mDescr);
      }
      QComboTableItem* meaItem = new QComboTableItem( tableIngredients, measureNames);
      meaItem->setCurrentItem( (*it).measurement().description());
      meaItem->setEditable( false);
      tableIngredients->setItem( row, ING_COL_MEASURE, meaItem);
	  tableIngredients->setText( row, ING_COL_DESCR, (*it).name());
   }
   addIngredientRow();
} // slotShowIngredients()

/*!
  This is an overloaded member function as convenience. See comment above
  for functionality.
*/
void
CreateRecipeWindow::slotShowIngredients( const QString& group)
{
   slotShowIngredients( m_cache.ingredients()[group]);
}

void
CreateRecipeWindow::slotSelectPicture( const QString&)
{
   qDebug( "CreateRecieWindow::slotSelectPicture( const QString&) is not implemented, yet.");
}

void
CreateRecipeWindow::slotLoadPicture()
{
   QString caption( tr( "Load picture"));
   QStrList formatlist = QImageIO::inputFormats();
   QString formats( tr( "Images"));
   for (uint i = 0; i < formatlist.count(); i++) {
	  formats.append( " *.");
	  formats.append( formatlist.at( i));
   }
   QString path = QFileDialog::getOpenFileName( QString::null, formats.lower(), 
												this, "loadPicture", caption);
   if (! path.isEmpty()) {
	  QPixmap pic( path);
	  m_cache.setPicture( m_cache.title(), pic);
   }

   labelPicture->setPixmap( m_cache.picture( m_cache.title()));
}

/*!
  Remove the currently shown picture from the internal recipe cache.
*/
void
CreateRecipeWindow::slotDeletePicture()
{
   slotDeletePicture( comboSelectPicture->currentText());
   comboSelectPicture->setCurrentText( m_cache.title());
   labelPicture->setPixmap( m_cache.picture( comboSelectPicture->currentText()));
}

/*!
  Remove the picture associated with \a name from the internal recipe cache.
*/
void
CreateRecipeWindow::slotDeletePicture( const QString& name)
{
   m_cache.removePicture( name);
   comboSelectPicture->removeItem( comboSelectPicture->currentItem());
}

/*!
  Put the new typist string in the internal recipe cache.
*/
void
CreateRecipeWindow::slotTypistChanged( const QString& s)
{
   m_cache.setTypist( s);
}

/*!
  Put the new source string into the internal recipe cache.
*/
void
CreateRecipeWindow::slotSourceChanged( const QString& s)
{
   m_cache.setSource( s);
}

/*!
  Put the new title into the internal recipe cache.
*/
void
CreateRecipeWindow::slotTitleChanged( const QString&  s)
{
   m_cache.setTitle( s);
}

/*!
  Put the new yield value in the internal recipe cache.
*/
void
CreateRecipeWindow::slotYieldQtyChanged( int val)
{
   m_cache.setYieldQty( val);
}

/*!
  Put the new yield range into the internal recipe cache.
*/
void CreateRecipeWindow::
slotYieldRangeChanged( int val)
{
   m_cache.setYieldRange( val);
}

/*!
  Put the new yield unig into the internal recipe cache.
*/
void CreateRecipeWindow::
slotYieldUnitChanged( const QString& s)
{
   m_cache.setYieldUnit( s);
}

/*!
  Put the contents of the directions edit field into the internal
  recipe cache.
*/
void
CreateRecipeWindow::slotReturnPressedInDirections()
{
   inherited::slotReturnPressedInDirections();
   m_cache.setDirections( texteditDirections->text());
}

/*!
   The current user name in the remarks tab changed to \a newUser.
   Therefore, the new user's remarks should be displayed.
*/
void CreateRecipeWindow::
slotRemarksUserChanged(const QString& newUser)
{
   m_listBoxRemarks->setEnabled( true);
   m_btnRemarksAdd->setEnabled( true);
   m_btnRemarksRemove->setEnabled( true);
   m_listBoxRemarks->setEnabled( true);

   m_listBoxRemarks->clear();
   QMap<QString,QStringList>::iterator it = m_cache.remarks().find( newUser);
   if (it != m_cache.remarks().end())
      m_listBoxRemarks->insertStringList( it.data());
}

void CreateRecipeWindow::
slotAddRemark()
{
   m_listBoxRemarks->insertItem( m_editRemarksNew->text());
   m_editRemarksNew->clear();
}

void CreateRecipeWindow::
slotRemoveSelectedRemarks()
{
   for (uint i = 0; i < m_listBoxRemarks->count(); i++) {
      if (m_listBoxRemarks->isSelected( i)) {
         m_listBoxRemarks->removeItem( i);
      }
   }
}
