/**
 * \file GGraphics.C
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author John Spray
 *
 * Full author contact details are available in file CREDITS.
 */

#include <config.h>

// Too hard to make concept checks work with this file
#ifdef _GLIBCXX_CONCEPT_CHECKS
#undef _GLIBCXX_CONCEPT_CHECKS
#endif
#ifdef _GLIBCPP_CONCEPT_CHECKS
#undef _GLIBCPP_CONCEPT_CHECKS
#endif

#include "GGraphics.h"
#include "ControlGraphics.h"

#include "ghelpers.h"

#include "controllers/helper_funcs.h"

#include "insets/insetgraphicsParams.h"
#include "paper.h"
#include "lyxrc.h" // for lyxrc.default_papersize

#include "support/convert.h"
#include "support/lyxlib.h"  // for float_equal
#include "support/lstrings.h"

#include "debug.h"

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

namespace lyx {

using support::float_equal;
using support::token;

namespace frontend {

namespace {
string defaultUnit("cm");
} // namespace anon

GGraphics::GGraphics(Dialog & parent)
	: GViewCB<ControlGraphics, GViewGladeB>(parent, _("Graphics"), false)
{}


void GGraphics::doBuild()
{
	string const gladeName = findGladeFile("graphics");
	xml_ = Gnome::Glade::Xml::create(gladeName);

	Gtk::Button * button;
	xml_->get_widget("Close", button);
	setCancel(button);
	xml_->get_widget("Ok", button);
	setOK(button);
	xml_->get_widget("Apply", button);
	setApply(button);
	xml_->get_widget("Restore", button);
	setRestore(button);

	xml_->get_widget("Notebook", notebook_);

	// File Page
	xml_->get_widget("File", fileentry_);
	xml_->get_widget("DisplayScale", displayscalespin_);
	xml_->get_widget("OutputScale", outputscalespin_);
	xml_->get_widget("Browse", browsebutton_);
	xml_->get_widget("Edit", editbutton_);
	xml_->get_widget("Display", displaycombo_);
	xml_->get_widget_derived("Width", widthlengthentry_);
	xml_->get_widget_derived("Height", heightlengthentry_);
	xml_->get_widget("MaintainAspectRatio", aspectcheck_);

	xml_->get_widget("SetScaling", setscalingradio_);
	xml_->get_widget("SetSize", setsizeradio_);

	// Bounding Box Page
	xml_->get_widget("ClipToBoundingBox", clipcheck_);
	xml_->get_widget("RightTopX", righttopxspin_);
	xml_->get_widget("RightTopY", righttopyspin_);
	xml_->get_widget("LeftBottomX", leftbottomxspin_);
	xml_->get_widget("LeftBottomY", leftbottomyspin_);
	xml_->get_widget("BoundingUnits", bbunitscombo_);
	xml_->get_widget("GetFromFile", bbfromfilebutton_);

	// Extra Page
	xml_->get_widget("Angle", anglespin_);
	xml_->get_widget("Origin", origincombo_);
	xml_->get_widget("UseSubfigure", subfigcheck_);
	xml_->get_widget("SubfigureCaption", subfigentry_);
	xml_->get_widget("LatexOptions", latexoptsentry_);
	xml_->get_widget("DraftMode", draftcheck_);
	xml_->get_widget("UnzipOnExport", unzipcheck_);

	// Setup the columnrecord we use for combos
	cols_.add(stringcol_);

	// The file page

	// Disable for read-only documents.
	bcview().addReadOnly(browsebutton_);
	bcview().addReadOnly(aspectcheck_);

	fileentry_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	displayscalespin_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	displaycombo_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	outputscalespin_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	heightlengthentry_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	widthlengthentry_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	aspectcheck_->signal_toggled().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));

	setscalingradio_->signal_toggled().connect(
		sigc::mem_fun(*this, &GGraphics::onSizingModeChange));
	setsizeradio_->signal_toggled().connect(
		sigc::mem_fun(*this, &GGraphics::onSizingModeChange));

	browsebutton_->signal_clicked().connect(
		sigc::mem_fun(*this, &GGraphics::onBrowseClicked));

	editbutton_->signal_clicked().connect(
		sigc::mem_fun(*this, &GGraphics::onEditClicked));

	// the bounding box page
	leftbottomxspin_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onBBChange));
	leftbottomyspin_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onBBChange));
	righttopxspin_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onBBChange));
	righttopyspin_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onBBChange));
	bbunitscombo_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onBBChange));
	clipcheck_->signal_toggled().connect(
		sigc::mem_fun(*this, &GGraphics::onBBChange));

	bbfromfilebutton_->signal_clicked().connect(
		sigc::mem_fun(*this, &GGraphics::onBBFromFileClicked));

	// disable for read-only documents
	bcview().addReadOnly(bbfromfilebutton_);
	bcview().addReadOnly(clipcheck_);
	PopulateComboBox(bbunitscombo_, getBBUnits());


	// the extra section

	// disable for read-only documents
	bcview().addReadOnly(anglespin_);
	bcview().addReadOnly(origincombo_);
	bcview().addReadOnly(subfigcheck_);
	bcview().addReadOnly(latexoptsentry_);
	bcview().addReadOnly(draftcheck_);
	bcview().addReadOnly(unzipcheck_);

	anglespin_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	origincombo_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	subfigentry_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	latexoptsentry_->signal_changed().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	draftcheck_->signal_toggled().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));
	unzipcheck_->signal_toggled().connect(
		sigc::mem_fun(*this, &GGraphics::onInput));

	subfigcheck_->signal_toggled().connect(
		sigc::mem_fun(*this, &GGraphics::onSubFigCheckToggled));

	vector<RotationOriginPair> origindata = getRotationOriginData();

	// Store the identifiers for later
	origins_ = getSecond(origindata);
	PopulateComboBox(origincombo_, getFirst(origindata));
}


void GGraphics::onSizingModeChange()
{
	bool const scalingmode = setscalingradio_->get_active();

	outputscalespin_->set_sensitive(scalingmode);
	widthlengthentry_->set_sensitive(!scalingmode);
	heightlengthentry_->set_sensitive(!scalingmode);
	aspectcheck_->set_sensitive(!scalingmode);
	bc().input(ButtonPolicy::SMI_VALID);
}


void GGraphics::PopulateComboBox(Gtk::ComboBox * combo,
				  vector<string> const & strings)
{
	Glib::RefPtr<Gtk::ListStore> model = Gtk::ListStore::create(cols_);
	vector<string>::const_iterator it = strings.begin();
	vector<string>::const_iterator end = strings.end();
	for (int rowindex = 0; it != end; ++it, ++rowindex) {
		Gtk::TreeModel::iterator row = model->append();
		(*row)[stringcol_] = *it;
	}

	combo->set_model(model);
	Gtk::CellRendererText * cell = Gtk::manage(new Gtk::CellRendererText);
	combo->pack_start(*cell, true);
	combo->add_attribute(*cell, "text", 0);
}


void GGraphics::apply()
{
	// Create the parameters structure and fill the data from the dialog.
	InsetGraphicsParams & igp = controller().params();

	// the file section
	igp.filename.set(fileentry_->get_text(),
			 kernel().bufferFilepath());

	igp.lyxscale =
		static_cast<int>(displayscalespin_->get_adjustment()->get_value());
	if (igp.lyxscale == 0) {
		igp.lyxscale = 100;
	}

	switch (displaycombo_->get_active_row_number()) {
	case 4:
		igp.display = graphics::NoDisplay;
		break;
	case 3:
		igp.display = graphics::ColorDisplay;
		break;
	case 2:
		igp.display = graphics::GrayscaleDisplay;
		break;
	case 1:
		igp.display = graphics::MonochromeDisplay;
		break;
	case 0:
		igp.display = graphics::DefaultDisplay;
	}

	if (setscalingradio_->get_active()) {
		float scaleValue = outputscalespin_->get_adjustment()->get_value();
		igp.scale = convert<string>(scaleValue);
		if (float_equal(scaleValue, 0.0, 0.05))
			igp.scale = string();
		igp.width = LyXLength();
	} else {
		igp.scale = string();
		igp.width = widthlengthentry_->get_length();
	}

	igp.height = heightlengthentry_->get_length();

	igp.keepAspectRatio = aspectcheck_->get_active();
	igp.draft = draftcheck_->get_active();
	igp.noUnzip = !unzipcheck_->get_active();

	// the bb section

	if (!controller().bbChanged) {
		// don't write anything
		igp.bb.erase();
	} else {
		Glib::ustring const bbunit = (*bbunitscombo_->get_active())[stringcol_];
		string bb;

		if (leftbottomxspin_->get_text().empty())
			bb = "0";
		else
			bb = leftbottomxspin_->get_text() + bbunit;

		bb += ' ';

		if (leftbottomyspin_->get_text().empty())
			bb += "0";
		else
			bb += leftbottomyspin_->get_text() + bbunit;

		bb += ' ';

		if (righttopxspin_->get_text().empty())
			bb += "0";
		else
			bb += righttopxspin_->get_text() + bbunit;

		bb += ' ';

		if (righttopyspin_->get_text().empty())
			bb += "0";
		else
			bb += righttopyspin_->get_text() + bbunit;

		/// The bounding box with "xLB yLB yRT yRT ", divided by a space!
		igp.bb = bb;
	}
	igp.clip = clipcheck_->get_active();

	// the extra section
	igp.rotateAngle = convert<string>(anglespin_->get_adjustment()->get_value());

	int const origin_pos = origincombo_->get_active_row_number();
	igp.rotateOrigin = origins_[origin_pos];

	igp.subcaption = subfigcheck_->get_active();
	igp.subcaptionText = subfigentry_->get_text();

	igp.special = latexoptsentry_->get_text();
}


void GGraphics::update() {
	// set the right default unit
	defaultUnit = getDefaultUnit();

	// Update dialog with details from inset
	InsetGraphicsParams & igp = controller().params();

	// the file section
	string const name =
		igp.filename.outputFilename(kernel().bufferFilepath());
	fileentry_->set_text(name);
	displayscalespin_->get_adjustment()->set_value(igp.lyxscale);


	switch (igp.display) {
	case graphics::NoDisplay:
		displaycombo_->set_active(4);
		break;
	case graphics::ColorDisplay:
		displaycombo_->set_active(3);
		break;
	case graphics::GrayscaleDisplay:
		displaycombo_->set_active(2);
		break;
	case graphics::MonochromeDisplay:
		displaycombo_->set_active(1);
		break;
	case graphics::DefaultDisplay:
		displaycombo_->set_active(0);
	}

	outputscalespin_->get_adjustment()->set_value(convert<double>(igp.scale));

	widthlengthentry_->set_length(igp.width);
	heightlengthentry_->set_length(igp.height);

	if (!igp.scale.empty()
		&& !float_equal(convert<double>(igp.scale), 0.0, 0.05)) {
		// scaling sizing mode
		setscalingradio_->set_active(true);
	} else if (!igp.width.empty() && !igp.height.empty()) {
		setsizeradio_->set_active(true);
	} else {
		outputscalespin_->get_adjustment()->set_value(100.0);
		setscalingradio_->set_active(true);
	}
	onSizingModeChange();

	aspectcheck_->set_active(igp.keepAspectRatio);
	draftcheck_->set_active(igp.draft);
	unzipcheck_->set_active(!igp.noUnzip);

	// the bb section
	// set the bounding box values, if exists. First we need the whole
	// path, because the controller knows nothing about the doc-dir
	updateBB(igp.filename.absFilename(), igp.bb);
	clipcheck_->set_active(igp.clip);

	// the extra section
	anglespin_->get_adjustment()->set_value(convert<double>(igp.rotateAngle));

	int origin_pos;
	if (igp.rotateOrigin.empty()) {
		origin_pos = 0;
	} else {
		origin_pos = findPos(origins_, igp.rotateOrigin);
	}
	origincombo_->set_active(origin_pos);


	subfigcheck_->set_active(igp.subcaption);
	subfigentry_->set_text(igp.subcaptionText);
	subfigentry_->set_sensitive(subfigcheck_->get_active());
	latexoptsentry_->set_text(igp.special);

	// open dialog in the file-tab, whenever filename is empty
	if (igp.filename.empty()) {
		notebook_->set_current_page(0);
	}


	bc().input(ButtonPolicy::SMI_INVALID);
}


void GGraphics::updateBB(string const & filename, string const & bb_inset)
{
	// Update dialog with details from inset
	// set the bounding box values, if exists. First we need the whole
	// path, because the controller knows nothing about the doc-dir

	if (bb_inset.empty()) {
		lyxerr[Debug::GRAPHICS]
			<< "GGraphics::updateBB() [no BoundingBox]\n";
		string const bb = controller().readBB(filename);
		if (!bb.empty()) {
			// get the values from the file
			// in this case we always have the point-unit
			leftbottomxspin_->set_text(token(bb, ' ', 0));
			leftbottomyspin_->set_text(token(bb, ' ', 1));
			righttopxspin_->set_text(token(bb, ' ', 2));
			righttopyspin_->set_text(token(bb, ' ', 3));
		} else {
			// no bb from file
			leftbottomxspin_->set_text("");
			leftbottomyspin_->set_text("");
			righttopxspin_->set_text("");
			righttopyspin_->set_text("");
		}
		unitsComboFromLength(bbunitscombo_, stringcol_,
		                     LyXLength("bp"), defaultUnit);
	} else {
		// get the values from the inset
		lyxerr[Debug::GRAPHICS]
			<< "FormGraphics::updateBB(): igp has BoundingBox"
			<< " ["<< bb_inset << "]\n";

		LyXLength anyLength;
		anyLength = LyXLength(token(bb_inset, ' ', 0));

		unitsComboFromLength(bbunitscombo_, stringcol_, anyLength, defaultUnit);

		leftbottomxspin_->get_adjustment()->set_value(anyLength.value());

		anyLength = LyXLength(token(bb_inset, ' ', 1));
		leftbottomyspin_->get_adjustment()->set_value(anyLength.value());

		anyLength = LyXLength(token(bb_inset, ' ', 2));
		righttopxspin_->get_adjustment()->set_value(anyLength.value());

		anyLength = LyXLength(token(bb_inset, ' ', 3));
		righttopyspin_->get_adjustment()->set_value(anyLength.value());
	}
	controller().bbChanged = false;
}


void GGraphics::onBrowseClicked()
{
	// Get the filename from the dialog
	string const in_name = fileentry_->get_text();
	string const out_name = controller().browse(in_name);
	lyxerr[Debug::GRAPHICS]
		<< "[FormGraphics]out_name: " << out_name << "\n";
	if (out_name != in_name && !out_name.empty()) {
		fileentry_->set_text(out_name);
	}
	if (controller().isFilenameValid(out_name) &&
	    !controller().bbChanged) {
		updateBB(out_name, string());
	}

	bc().input(ButtonPolicy::SMI_VALID);
}


void GGraphics::onBBChange()
{
	controller().bbChanged = true;
	bc().input(ButtonPolicy::SMI_VALID);
}


void GGraphics::onBBFromFileClicked()
{
	string const filename = fileentry_->get_text();
	if (!filename.empty()) {
		string bb = controller().readBB(filename);
		if (!bb.empty()) {
			leftbottomxspin_->set_text(token(bb, ' ', 0));
			leftbottomyspin_->set_text(token(bb, ' ', 1));
			righttopxspin_->set_text(token(bb, ' ', 2));
			righttopyspin_->set_text(token(bb, ' ', 3));
			unitsComboFromLength(bbunitscombo_, stringcol_,
			                     LyXLength("bp"), defaultUnit);
		}
		controller().bbChanged = false;
	} else {
		leftbottomxspin_->set_text("");
		leftbottomyspin_->set_text("");
		righttopxspin_->set_text("");
		righttopyspin_->set_text("");
		unitsComboFromLength(bbunitscombo_, stringcol_,
		                     LyXLength("bp"), defaultUnit);
	}
	bc().input(ButtonPolicy::SMI_VALID);
}


void GGraphics::onSubFigCheckToggled()
{
	subfigentry_->set_sensitive(subfigcheck_->get_active());
	bc().input(ButtonPolicy::SMI_VALID);
}


void GGraphics::onEditClicked()
{
	controller().editGraphics();
}


void GGraphics::onInput()
{
	bc().input(ButtonPolicy::SMI_VALID);
}

} // namespace frontend
} // namespace lyx
