/**
 * \file insetbox.C
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Angus Leeming
 * \author Martin Vermeer
 * \author Jürgen Spitzmüller
 *
 * Full author contact details are available in file CREDITS.
 */

#include <config.h>

#include "insetbox.h"

#include "cursor.h"
#include "dispatchresult.h"
#include "debug.h"
#include "FuncStatus.h"
#include "funcrequest.h"
#include "gettext.h"
#include "LaTeXFeatures.h"
#include "LColor.h"
#include "lyxlex.h"
#include "metricsinfo.h"
#include "paragraph.h"

#include "support/translator.h"

#include <sstream>

using std::auto_ptr;
using std::string;
using std::istringstream;
using std::ostream;
using std::ostringstream;
using std::endl;


namespace {

typedef Translator<std::string, InsetBox::BoxType> BoxTranslator;

BoxTranslator const init_boxtranslator() {
	BoxTranslator translator("Boxed", InsetBox::Boxed);
	translator.addPair("Frameless", InsetBox::Frameless);
	translator.addPair("ovalbox", InsetBox::ovalbox);
	translator.addPair("Ovalbox", InsetBox::Ovalbox);
	translator.addPair("Shadowbox", InsetBox::Shadowbox);
	translator.addPair("Doublebox",InsetBox::Doublebox);
	return translator;
}


BoxTranslator const init_boxtranslator_loc() {
	BoxTranslator translator(_("Boxed"), InsetBox::Boxed);
	translator.addPair(_("Frameless"), InsetBox::Frameless);
	translator.addPair(_("ovalbox"), InsetBox::ovalbox);
	translator.addPair(_("Ovalbox"), InsetBox::Ovalbox);
	translator.addPair(_("Shadowbox"), InsetBox::Shadowbox);
	translator.addPair(_("Doublebox"), InsetBox::Doublebox);
	return translator;
}


BoxTranslator const & boxtranslator() {
	static BoxTranslator translator = init_boxtranslator();
	return translator;
}


BoxTranslator const & boxtranslator_loc() {
	static BoxTranslator translator = init_boxtranslator_loc();
	return translator;
}

} // anon


void InsetBox::init()
{
	setInsetName("Box");
	setButtonLabel();
}


InsetBox::InsetBox(BufferParams const & bp, string const & label)
	: InsetCollapsable(bp), params_(label)
{
	init();
}


InsetBox::InsetBox(InsetBox const & in)
	: InsetCollapsable(in), params_(in.params_)
{
	init();
}


InsetBox::~InsetBox()
{
	InsetBoxMailer(*this).hideDialog();
}


auto_ptr<InsetBase> InsetBox::doClone() const
{
	return auto_ptr<InsetBase>(new InsetBox(*this));
}


string const InsetBox::editMessage() const
{
	return _("Opened Box Inset");
}


void InsetBox::write(Buffer const & buf, ostream & os) const
{
	params_.write(os);
	InsetCollapsable::write(buf, os);
}


void InsetBox::read(Buffer const & buf, LyXLex & lex)
{
	params_.read(lex);
	InsetCollapsable::read(buf, lex);
	setButtonLabel();
}


void InsetBox::setButtonLabel()
{
	LyXFont font(LyXFont::ALL_SANE);
	font.decSize();
	font.decSize();

	BoxType btype = boxtranslator().find(params_.type);
	if (btype == Frameless) {
		if (params_.use_parbox)
			setLabel(_("Box") + " (" + _("Parbox") + ")");
		else
			setLabel(_("Box") + " (" + _("Minipage") + ")");
	} else
		setLabel(_("Box") + " (" + boxtranslator_loc().find(btype) + ")");

	font.setColor(LColor::foreground);
	setBackgroundColor(LColor::background);
	setLabelFont(font);
}


bool InsetBox::hasFixedWidth() const
{
	return params_.inner_box || params_.special != "width";
}


void InsetBox::metrics(MetricsInfo & m, Dimension & dim) const
{
	MetricsInfo mi = m;
	if (hasFixedWidth())
		mi.base.textwidth = params_.width.inPixels(m.base.textwidth);
	InsetCollapsable::metrics(mi, dim);
	dim_ = dim;
}


bool InsetBox::forceDefaultParagraphs(idx_type) const
{
	return !params_.inner_box;
}


bool InsetBox::showInsetDialog(BufferView * bv) const
{
	InsetBoxMailer(const_cast<InsetBox &>(*this)).showDialog(bv);
	return true;
}


void InsetBox::doDispatch(LCursor & cur, FuncRequest & cmd)
{
	switch (cmd.action) {

	case LFUN_INSET_MODIFY: {
		//lyxerr << "InsetBox::dispatch MODIFY" << endl;
		InsetBoxMailer::string2params(cmd.argument, params_);
		setButtonLabel();
		break;
	}

	case LFUN_INSET_DIALOG_UPDATE:
		InsetBoxMailer(*this).updateDialog(&cur.bv());
		break;

	case LFUN_MOUSE_RELEASE:
		if (cmd.button() == mouse_button::button3 && hitButton(cmd)) {
			InsetBoxMailer(*this).showDialog(&cur.bv());
			break;
		}
		InsetCollapsable::doDispatch(cur, cmd);
		break;

	default:
		InsetCollapsable::doDispatch(cur, cmd);
		break;
	}
}


bool InsetBox::getStatus(LCursor & cur, FuncRequest const & cmd,
		FuncStatus & flag) const
{
	switch (cmd.action) {

	case LFUN_INSET_MODIFY:
	case LFUN_INSET_DIALOG_UPDATE:
		flag.enabled(true);
		return true;
	case LFUN_BREAKPARAGRAPH:
		if (params_.inner_box) {
			return InsetCollapsable::getStatus(cur, cmd, flag);
		} else {
			flag.enabled(false);
			return true;
		}

	default:
		return InsetCollapsable::getStatus(cur, cmd, flag);
	}
}


int InsetBox::latex(Buffer const & buf, ostream & os,
				OutputParams const & runparams) const
{
	BoxType btype = boxtranslator().find(params_.type);

	string width_string = params_.width.asLatexString();
	bool stdwidth(false);
	if (params_.inner_box &&
			(width_string.find("1.0\\columnwidth") != string::npos
			|| width_string.find("1.0\\textwidth") != string::npos)) {
		stdwidth = true;
		switch (btype) {
		case Frameless:
			break;
		case Boxed:
			width_string += " - 2\\fboxsep - 2\\fboxrule";
			break;
		case ovalbox:
			width_string += " - 2\\fboxsep - 0.8pt";
			break;
		case Ovalbox:
			width_string += " - 2\\fboxsep - 1.6pt";
			break;
		case Shadowbox:
			// Shadow falls outside right margin... opinions?
			width_string += " - 2\\fboxsep - 2\\fboxrule"/* "-\\shadowsize"*/;
			break;
		case Doublebox:
			width_string += " - 2\\fboxsep - 7.5\\fboxrule - 1.0pt";
			break;
		}
	}

	int i = 0;
	os << "%\n";
	// Adapt to column/text width correctly also if paragraphs indented:
	if (stdwidth)
		os << "\\noindent";

	switch (btype) {
	case Frameless:
		break;
	case Boxed:
		os << "\\framebox";
		if (!params_.inner_box) {
			os << "{\\makebox";
			// Special widths, see usrguide §3.5
			if (params_.special != "none") {
				os << "[" << params_.width.value()
				   << "\\" << params_.special << "]";
			} else
				os << "[" << width_string << "]";
			if (params_.hor_pos != 'c')
				os << "[" << params_.hor_pos << "]";
		}

		os << "{";
		break;
	case ovalbox:
		os << "\\ovalbox{";
		break;
	case Ovalbox:
		os << "\\Ovalbox{";
		break;
	case Shadowbox:
		os << "\\shadowbox{";
		break;
	case Doublebox:
		os << "\\doublebox{";
		break;
	}

	if (params_.inner_box) {
		if (params_.use_parbox)
			os << "\\parbox";
		else
			os << "\\begin{minipage}";

		os << "[" << params_.pos << "]";
		if (params_.height_special == "none") {
			os << "[" << params_.height.asLatexString() << "]";
		} else {
			// Special heights
			os << "[" << params_.height.value()
			   << "\\" << params_.height_special << "]";
		}
		if (params_.inner_pos != params_.pos)
			os << "[" << params_.inner_pos << "]";

		os << "{" << width_string << "}";

		if (params_.use_parbox)
			os << "{";
		os << "%\n";
		i += 1;
	}

	i += InsetText::latex(buf, os, runparams);

	if (params_.inner_box) {
		if (params_.use_parbox)
			os << "%\n}";
		else
			os << "%\n\\end{minipage}";
	}

	switch (btype) {
	case Frameless:
		break;
	case Boxed:
		if (!params_.inner_box)
			os << "}"; // for makebox
		os << "}";
		break;
	case ovalbox:
	case Ovalbox:
	case Doublebox:
	case Shadowbox:
		os << "}";
		break;
	}
	os << "%\n";

	i += 3;

	return i;
}


int InsetBox::linuxdoc(Buffer const & buf, std::ostream & os,
		       OutputParams const & runparams) const
{
	return InsetText::linuxdoc(buf, os, runparams);
}


int InsetBox::docbook(Buffer const & buf, std::ostream & os,
		      OutputParams const & runparams) const
{
	return InsetText::docbook(buf, os, runparams);
}


int InsetBox::plaintext(Buffer const & buf, std::ostream & os,
		    OutputParams const & runparams) const
{
	BoxType const btype = boxtranslator().find(params_.type);

	switch (btype) {
		case Frameless: break;
		case Boxed:     os << "[";  break;
		case ovalbox:   os << "(";  break;
		case Ovalbox:   os << "(("; break;
		case Shadowbox: os << "[";  break;
		case Doublebox: os << "[["; break;
	}

	int i = InsetText::plaintext(buf, os, runparams);

	switch (btype) {
		case Frameless: break;
		case Boxed:     os << "]";  break;
		case ovalbox:   os << ")";  break;
		case Ovalbox:   os << "))"; break;
		case Shadowbox: os << "]/"; break;
		case Doublebox: os << "]]"; break;
	}

	return i;
}


void InsetBox::validate(LaTeXFeatures & features) const
{
	features.require("calc");
	BoxType btype = boxtranslator().find(params_.type);
	switch (btype) {
	case Frameless:
	case Boxed:
		break;
	case ovalbox:
	case Ovalbox:
	case Shadowbox:
	case Doublebox:
		features.require("fancybox");
		break;
	}
	InsetText::validate(features);
}


InsetBoxMailer::InsetBoxMailer(InsetBox & inset)
	: inset_(inset)
{}


string const InsetBoxMailer::name_ = "box";


string const InsetBoxMailer::inset2string(Buffer const &) const
{
	return params2string(inset_.params());
}


string const InsetBoxMailer::params2string(InsetBoxParams const & params)
{
	ostringstream data;
	data << "box" << ' ';
	params.write(data);
	return data.str();
}


void InsetBoxMailer::string2params(string const & in,
				   InsetBoxParams & params)
{
	params = InsetBoxParams(string());
	if (in.empty())
		return;

	istringstream data(in);
	LyXLex lex(0,0);
	lex.setStream(data);

	string name;
	lex >> name;
	if (!lex || name != name_)
		return print_mailer_error("InsetBoxMailer", in, 1, name_);

	// This is part of the inset proper that is usually swallowed
	// by LyXText::readInset
	string id;
	lex >> id;
	if (!lex || id != "Box")
		return print_mailer_error("InsetBoxMailer", in, 2, "Box");

	params.read(lex);
}


InsetBoxParams::InsetBoxParams(string const & label)
	: type(label),
	  use_parbox(false),
	  inner_box(true),
	  width(LyXLength("100col%")),
	  special("none"),
	  pos('t'),
	  hor_pos('c'),
	  inner_pos('t'),
	  height(LyXLength("1in")),
	  height_special("totalheight") // default is 1\\totalheight
{}


void InsetBoxParams::write(ostream & os) const
{
	os << "Box " << type << "\n";
	os << "position \"" << pos << "\"\n";
	os << "hor_pos \"" << hor_pos << "\"\n";
	os << "has_inner_box " << inner_box << "\n";
	os << "inner_pos \"" << inner_pos << "\"\n";
	os << "use_parbox " << use_parbox << "\n";
	os << "width \"" << width.asString() << "\"\n";
	os << "special \"" << special << "\"\n";
	os << "height \"" << height.asString() << "\"\n";
	os << "height_special \"" << height_special << "\"\n";
}


void InsetBoxParams::read(LyXLex & lex)
{
	if (!lex.isOK())
		return;

	if (lex.isOK()) {
		lex.next();
		type = lex.getString();
	}
	if (!lex.isOK())
		return;
	lex.next();
	string token;
	token = lex.getString();
	if (token == "position") {
		lex.next();
		// The [0] is needed. We need the first and only char in
		// this string -- MV
		pos = lex.getString()[0];
	} else {
		lyxerr << "InsetBox::Read: Missing 'position'-tag!" << token << endl;
		lex.pushToken(token);
	}
	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "hor_pos") {
		lex.next();
		hor_pos = lex.getString()[0];
	} else {
		lyxerr << "InsetBox::Read: Missing 'hor_pos'-tag!" << token << endl;
		lex.pushToken(token);
	}
	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "has_inner_box") {
		lex.next();
		inner_box = lex.getInteger();
	} else {
		lyxerr << "InsetBox::Read: Missing 'has_inner_box'-tag!" << endl;
		lex.pushToken(token);
	}

	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "inner_pos") {
		lex.next();
		inner_pos = lex.getString()[0];
	} else {
		lyxerr << "InsetBox::Read: Missing 'inner_pos'-tag!"
			<< token << endl;
		lex.pushToken(token);
	}
	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "use_parbox") {
		lex.next();
		use_parbox = lex.getInteger();
	} else {
		lyxerr << "InsetBox::Read: Missing 'use_parbox'-tag!" << endl;
		lex.pushToken(token);
	}
	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "width") {
		lex.next();
		width = LyXLength(lex.getString());
	} else {
		lyxerr << "InsetBox::Read: Missing 'width'-tag!" << endl;
		lex.pushToken(token);
	}
	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "special") {
		lex.next();
		special = lex.getString();
	} else {
		lyxerr << "InsetBox::Read: Missing 'special'-tag!" << endl;
		lex.pushToken(token);
	}
	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "height") {
		lex.next();
		height = LyXLength(lex.getString());
	} else {
		lyxerr << "InsetBox::Read: Missing 'height'-tag!" << endl;
		lex.pushToken(token);
	}
	if (!lex.isOK())
		return;
	lex.next();
	token = lex.getString();
	if (token == "height_special") {
		lex.next();
		height_special = lex.getString();
	} else {
		lyxerr << "InsetBox::Read: Missing 'height_special'-tag!" << endl;
		lex.pushToken(token);
	}
}
