/**
 * \file FormCitation.C
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Angus Leeming
 * \author Rob Lahaye
 *
 * Full author contact details are available in file CREDITS.
 */

#include <config.h>

#include "FormCitation.h"
#include "ControlCitation.h"
#include "forms/form_citation.h"

#include "Tooltips.h"
#include "xforms_helpers.h"
#include "xformsBC.h"

#include "bufferparams.h"

#include "support/lstrings.h"

#include "lyx_forms.h"

using std::find;
using std::max;

using std::vector;
using std::string;
using std::swap;


namespace lyx {

using support::getStringFromVector;
using support::getVectorFromString;
using support::trim;

namespace frontend {

namespace {

// shamelessly stolen from Menubar_pimpl.C
int string_width(string const & str)
{
	return fl_get_string_widthTAB(FL_NORMAL_STYLE, FL_NORMAL_SIZE,
				      str.c_str(),
				      static_cast<int>(str.length()));
}


void fillChoice(FD_citation * dialog, vector<string> vec)
{
	// Check whether the current contents of the browser will be
	// changed by loading the contents of the vec...
	vector<string> const choice_style = getVector(dialog->choice_style);

	if (vec == choice_style)
		return;

	// They will be changed. Proceed
	bool const noVec = vec.empty();
	string const str = noVec ? string() : getStringFromVector(vec, "|");

	fl_clear_choice(dialog->choice_style);
	fl_addto_choice(dialog->choice_style, str.c_str());
	setEnabled(dialog->choice_style, !noVec);
}


void updateStyle(FD_citation * dialog, string command)
{
	// Find the style of the citekeys
	vector<biblio::CiteStyle> const & styles =
		ControlCitation::getCiteStyles();
	biblio::CitationStyle const cs(command);

	vector<biblio::CiteStyle>::const_iterator cit =
		find(styles.begin(), styles.end(), cs.style);

	// Use this to initialise the GUI
	bool const noStyles = cit == styles.end();
	int const index = 1 + ( noStyles ? 0 : int(cit - styles.begin()) );
	fl_set_choice(dialog->choice_style, index);

	// Disable if there are no styles, otherwise use cs member settings.
	fl_set_button(dialog->check_full_author_list, !noStyles && cs.full);
	fl_set_button(dialog->check_force_uppercase, !noStyles && cs.forceUCase);
}

} // namespace anon


typedef FormController<ControlCitation, FormView<FD_citation> > base_class;


FormCitation::FormCitation(Dialog & parent)
	: base_class(parent, _("Citation"))
{}


void FormCitation::apply()
{
	string command = "cite";
	if (isActive(dialog_->choice_style)) {
		vector<biblio::CiteStyle> const & styles =
			ControlCitation::getCiteStyles();

		int const choice =
			std::max(0, fl_get_choice(dialog_->choice_style) - 1);
		bool const full  =
			fl_get_button(dialog_->check_full_author_list);
		bool const force =
			fl_get_button(dialog_->check_force_uppercase);

		command =
			biblio::CitationStyle(styles[choice], full, force)
			.asLatexStr();

	}

	controller().params().setCmdName(command);
	controller().params().setContents(getStringFromVector(citekeys));

	string const before  = getString(dialog_->input_before);
	controller().params().setSecOptions(before);

	string const after  = getString(dialog_->input_after);
	controller().params().setOptions(after);
}


void FormCitation::hide()
{
	citekeys.clear();
	bibkeys.clear();

	FormDialogView::hide();
}


void FormCitation::build()
{
	dialog_.reset(build_citation(this));

	// Manage the ok, apply, restore and cancel/close buttons
	bcview().setOK(dialog_->button_ok);
	bcview().setApply(dialog_->button_apply);
	bcview().setCancel(dialog_->button_close);
	bcview().setRestore(dialog_->button_restore);

	// disable for read-only documents
	bcview().addReadOnly(dialog_->button_add);
	bcview().addReadOnly(dialog_->button_del);
	bcview().addReadOnly(dialog_->button_up);
	bcview().addReadOnly(dialog_->button_down);
	bcview().addReadOnly(dialog_->choice_style);
	bcview().addReadOnly(dialog_->input_before);
	bcview().addReadOnly(dialog_->input_after);
	bcview().addReadOnly(dialog_->check_full_author_list);
	bcview().addReadOnly(dialog_->check_force_uppercase);

	// trigger an input event for cut&paste with middle mouse button.
	setPrehandler(dialog_->input_search);
	setPrehandler(dialog_->input_before);
	setPrehandler(dialog_->input_after);

	fl_set_input_return(dialog_->input_after,  FL_RETURN_CHANGED);
	fl_set_input_return(dialog_->input_before, FL_RETURN_CHANGED);
	fl_set_input_return(dialog_->input_search, FL_RETURN_END);

	//set up the tooltip mechanism
	string str = _("Add citation of the selected bibliography entry.");
	tooltips().init(dialog_->button_add, str);

	str = _("Delete citation of the selected bibliography entry.");
	tooltips().init(dialog_->button_del, str);

	str = _("Move the selected entry upwards (in the current list).");
	tooltips().init(dialog_->button_up, str);

	str = _("Move the selected entry downwards (in the current list).");
	tooltips().init(dialog_->button_down, str);

	str = _("The entries which will be cited. Select them with the arrow buttons from the right browser window.");
	tooltips().init(dialog_->browser_cite, str);
#if FL_VERSION == 0 || (FL_REVISION == 0 && FL_FIXLEVEL == 0)
	// Work-around xforms' bug; enable tooltips for browser widgets.
	setPrehandler(dialog_->browser_cite);
#endif

	str = _("All entries in the database you have loaded (via \"Insert->List / TOC->BibTeX Bibliography\"). Move the ones you want to cite with the arrow buttons into the left browser window.");
	tooltips().init(dialog_->browser_bib, str);
#if FL_VERSION == 0 || (FL_REVISION == 0 && FL_FIXLEVEL == 0)
	// Work-around xforms' bug; enable tooltips for browser widgets.
	setPrehandler(dialog_->browser_bib);
#endif

	str = _("Information about the selected bibliography entry");
	tooltips().init(dialog_->browser_info, str);
#if FL_VERSION == 0 || (FL_REVISION == 0 && FL_FIXLEVEL == 0)
	// Work-around xforms' bug; enable tooltips for browser widgets.
	setPrehandler(dialog_->browser_info);
#endif

	str = _("Here you may select how the citation label should look inside the text (Natbib).");
	tooltips().init(dialog_->choice_style, str);

	str = _("Activate if you want to print all authors in a citation with more than three authors, and not \"<First Author> et al.\" (Natbib).");
	tooltips().init(dialog_->check_full_author_list, str);

	str = _("Activate if you want to print the first character of the author name as uppercase (\"Van Gogh\", not \"van Gogh\"). Useful at the beginning of sentences (Natbib).");
	tooltips().init(dialog_->check_force_uppercase, str);

	str = _("Optional text which appears before the citation, e.g. \"see <Citation>\"");
	tooltips().init(dialog_->input_before, str);

	str = _("Optional text which appears after the citation, e.g. \"pp. 12\"");
	tooltips().init(dialog_->input_after, str);

	str = _("Search your database (all fields will be searched).");
	tooltips().init(dialog_->input_search, str);

	str = _("Activate if you want to have case sensitive search: \"bibtex\" finds \"bibtex\", but not \"BibTeX\".");
	tooltips().init(dialog_->check_search_case, str);

	str = _("Activate if you want to enter Regular Expressions.");
	tooltips().init(dialog_->check_search_type, str);
}


void FormCitation::findBiblio(biblio::Direction const dir)
{
	string const str = getString(dialog_->input_search);
	biblio::InfoMap const & theMap = controller().bibkeysInfo();
	bool const caseSensitive =
		fl_get_button(dialog_->check_search_case);
	biblio::Search const type =
		fl_get_button(dialog_->check_search_type) ?
		biblio::REGEX : biblio::SIMPLE;

	vector<string>::const_iterator start = bibkeys.begin();
	int const sel = fl_get_browser(dialog_->browser_bib);
	if (sel >= 1 && sel <= int(bibkeys.size()))
		start += sel - 1;

	// Find the NEXT instance...
	if (dir == biblio::FORWARD)
		start += 1;


	vector<string>::const_iterator const cit =
		biblio::searchKeys(theMap, bibkeys, str,
				   start, type, dir, caseSensitive);

	if (cit == bibkeys.end())
		return;

	int const found = int(cit - bibkeys.begin()) + 1;
	if (found == sel)
		return;

	// Update the display
	int const top = max(found - 5, 1);
	fl_set_browser_topline(dialog_->browser_bib, top);
	fl_select_browser_line(dialog_->browser_bib, found);
	input(dialog_->browser_bib, 0);
}


ButtonPolicy::SMInput FormCitation::input(FL_OBJECT * ob, long)
{
	ButtonPolicy::SMInput activate = ButtonPolicy::SMI_NOOP;

	biblio::InfoMap const & theMap = controller().bibkeysInfo();

	string topCitekey;
	if (!citekeys.empty()) topCitekey = citekeys[0];

	if (ob == dialog_->browser_bib) {
		fl_deselect_browser(dialog_->browser_cite);

		unsigned int const sel = fl_get_browser(dialog_->browser_bib);
		if (sel < 1 || sel > bibkeys.size())
			return ButtonPolicy::SMI_NOOP;

		// Put into browser_info the additional info associated with
		// the selected browser_bib key
		fl_clear_browser(dialog_->browser_info);

		string const tmp = formatted(biblio::getInfo(theMap,
							     bibkeys[sel - 1]),
					      dialog_->browser_info->w - 10);
		fl_add_browser_line(dialog_->browser_info, tmp.c_str());

		// Highlight the selected browser_bib key in browser_cite if
		// present
		vector<string>::const_iterator cit =
			find(citekeys.begin(), citekeys.end(), bibkeys[sel - 1]);

		if (cit != citekeys.end()) {
			int const n = int(cit - citekeys.begin());
			fl_select_browser_line(dialog_->browser_cite, n + 1);
			fl_set_browser_topline(dialog_->browser_cite, n + 1);
		}

		if (!kernel().isBufferReadonly()) {
			if (cit != citekeys.end()) {
				setBibButtons(OFF);
				setCiteButtons(ON);
			} else {
				setBibButtons(ON);
				setCiteButtons(OFF);
			}
		}

	} else if (ob == dialog_->browser_cite) {
		unsigned int const sel = fl_get_browser(dialog_->browser_cite);
		if (sel < 1 || sel > citekeys.size())
			return ButtonPolicy::SMI_NOOP;

		if (!kernel().isBufferReadonly()) {
			setBibButtons(OFF);
			setCiteButtons(ON);
		}

		// Highlight the selected browser_cite key in browser_bib
		vector<string>::const_iterator cit =
			find(bibkeys.begin(), bibkeys.end(), citekeys[sel - 1]);

		if (cit != bibkeys.end()) {
			int const n = int(cit - bibkeys.begin());
			fl_select_browser_line(dialog_->browser_bib, n + 1);
			fl_set_browser_topline(dialog_->browser_bib, n + 1);

			// Put into browser_info the additional info associated
			// with the selected browser_cite key
			fl_clear_browser(dialog_->browser_info);
			string const tmp =
				formatted(biblio::getInfo(theMap, citekeys[sel - 1]),
					  dialog_->browser_info->w - 10);
			fl_add_browser_line(dialog_->browser_info, tmp.c_str());
		}

	} else if (ob == dialog_->button_add) {
		unsigned int const sel = fl_get_browser(dialog_->browser_bib);
		if (sel < 1 || sel > bibkeys.size())
			return ButtonPolicy::SMI_NOOP;

		// Add the selected browser_bib key to browser_cite
		fl_addto_browser(dialog_->browser_cite,
				  bibkeys[sel - 1].c_str());
		citekeys.push_back(bibkeys[sel - 1]);

		int const n = int(citekeys.size());
		fl_select_browser_line(dialog_->browser_cite, n);

		setBibButtons(OFF);
		setCiteButtons(ON);
		activate = ButtonPolicy::SMI_VALID;

	} else if (ob == dialog_->button_del) {
		unsigned int const sel = fl_get_browser(dialog_->browser_cite);
		if (sel < 1 || sel > citekeys.size())
			return ButtonPolicy::SMI_NOOP;

		// Remove the selected key from browser_cite
		fl_delete_browser_line(dialog_->browser_cite, sel) ;
		citekeys.erase(citekeys.begin() + sel - 1);

		setBibButtons(ON);
		setCiteButtons(OFF);
		activate = ButtonPolicy::SMI_VALID;

	} else if (ob == dialog_->button_up) {
		unsigned int const sel = fl_get_browser(dialog_->browser_cite);
		if (sel < 2 || sel > citekeys.size())
			return ButtonPolicy::SMI_NOOP;

		// Move the selected key up one line
		string const tmp = citekeys[sel - 1];

		fl_delete_browser_line(dialog_->browser_cite, sel);
		swap(citekeys[sel - 2], citekeys[sel - 1]);

		fl_insert_browser_line(dialog_->browser_cite, sel - 1, tmp.c_str());
		fl_select_browser_line(dialog_->browser_cite, sel - 1);
		setCiteButtons(ON);
		activate = ButtonPolicy::SMI_VALID;

	} else if (ob == dialog_->button_down) {
		unsigned int const sel = fl_get_browser(dialog_->browser_cite);
		if (sel < 1 || sel > citekeys.size() - 1)
			return ButtonPolicy::SMI_NOOP;

		// Move the selected key down one line
		string const tmp = citekeys[sel - 1];

		fl_delete_browser_line(dialog_->browser_cite, sel);
		swap(citekeys[sel], citekeys[sel - 1]);

		fl_insert_browser_line(dialog_->browser_cite, sel+1, tmp.c_str());
		fl_select_browser_line(dialog_->browser_cite, sel+1);
		setCiteButtons(ON);
		activate = ButtonPolicy::SMI_VALID;

	} else if (ob == dialog_->button_previous) {
		findBiblio(biblio::BACKWARD);
	} else if (ob == dialog_->button_next) {
		findBiblio(biblio::FORWARD);
	} else if (ob == dialog_->input_search) {
		findBiblio(biblio::FORWARD);
	} else if (ob == dialog_->choice_style ||
		   ob == dialog_->check_full_author_list ||
		   ob == dialog_->check_force_uppercase ||
		   ob == dialog_->input_before ||
		   ob == dialog_->input_after) {
		activate = ButtonPolicy::SMI_VALID;
	}

	string currentCitekey;
	if (!citekeys.empty())
		currentCitekey = citekeys[0];

	if (topCitekey != currentCitekey) {
		int choice = std::max(1, fl_get_choice(dialog_->choice_style));
		fillChoice(dialog_.get(),
			   controller().getCiteStrings(currentCitekey));
		fl_set_choice(dialog_->choice_style, choice);
	}

	// Invalid if no citation has been chosen
	if (fl_get_browser_maxline(dialog_->browser_cite) < 1)
		activate = ButtonPolicy::SMI_INVALID;

	return activate;
}


void FormCitation::update()
{
	// Make the list of all available bibliography keys
	bibkeys = biblio::getKeys(controller().bibkeysInfo());
	updateBrowser(dialog_->browser_bib, bibkeys);

	// Ditto for the keys cited in this inset
	citekeys = getVectorFromString(controller().params().getContents());
	updateBrowser(dialog_->browser_cite, citekeys);

	// Use the first citekey to fill choice_style
	string key;
	if (!citekeys.empty())
		key = citekeys[0];

	fillChoice(dialog_.get(), controller().getCiteStrings(key));

	// Use the citation command to update the GUI
	updateStyle(dialog_.get(), controller().params().getCmdName());

	biblio::CiteEngine const engine = controller().getEngine();
	bool const natbib_engine =
		engine == biblio::ENGINE_NATBIB_AUTHORYEAR ||
		engine == biblio::ENGINE_NATBIB_NUMERICAL;
	bool const basic_engine = engine == biblio::ENGINE_BASIC;

	setEnabled(dialog_->check_full_author_list, natbib_engine);
	setEnabled(dialog_->check_force_uppercase, natbib_engine);
	setEnabled(dialog_->choice_style, !basic_engine);
	setEnabled(dialog_->input_before, !basic_engine);

	// No keys have been selected yet, so...
	fl_clear_browser(dialog_->browser_info);
	setBibButtons(OFF);
	setCiteButtons(OFF);

	// Natbib can have comments before and after the citation.
	fl_set_input(dialog_->input_after,
		     controller().params().getOptions().c_str());

	fl_set_input(dialog_->input_before,
		     controller().params().getSecOptions().c_str());
}


void FormCitation::updateBrowser(FL_OBJECT * browser,
				 vector<string> const & keys) const
{
	// Check whether the current contents of the browser will be
	// changed by loading the contents of the vec...
	vector<string> browser_keys = getVector(browser);

	if (browser_keys == keys) {
		fl_deselect_browser(browser);
		fl_set_browser_topline(browser, 1);
		return;
	}

	// They will be changed. Proceed.
	fl_clear_browser(browser);

	for (vector<string>::const_iterator it = keys.begin();
	     it != keys.end(); ++it) {
		string key = trim(*it);
		if (!key.empty())
			fl_add_browser_line(browser, key.c_str());
	}
}


void FormCitation::setBibButtons(State status) const
{
	setEnabled(dialog_->button_add, (status == ON));
}


void FormCitation::setCiteButtons(State status) const
{
	int const sel     = fl_get_browser(dialog_->browser_cite);
	int const maxline = fl_get_browser_maxline(dialog_->browser_cite);
	bool const activate      = (status == ON);
	bool const activate_up   = (activate && sel != 1);
	bool const activate_down = (activate && sel != maxline);

	setEnabled(dialog_->button_del,  activate);
	setEnabled(dialog_->button_up,   activate_up);
	setEnabled(dialog_->button_down, activate_down);
}

} // namespace frontend
} // namespace lyx
