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

#include <config.h>

#include "InsetIPAMacro.h"

#include "Dimension.h"
#include "FontInfo.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "InsetLayout.h"
#include "LaTeXFeatures.h"
#include "MetricsInfo.h"
#include "xml.h"
#include "texstream.h"

#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"

#include "support/debug.h"
#include "support/docstream.h"
#include "support/gettext.h"
#include "support/Lexer.h"
#include "support/Translator.h"

using namespace std;

namespace lyx {

class Buffer;

using support::Lexer;

namespace {

typedef Translator<string, InsetIPADecoParams::Type> IPADecoTranslator;
typedef Translator<docstring, InsetIPADecoParams::Type> IPADecoTranslatorLoc;

IPADecoTranslator const init_ipadecotranslator()
{
	IPADecoTranslator translator("toptiebar", InsetIPADecoParams::Toptiebar);
	translator.addPair("bottomtiebar", InsetIPADecoParams::Bottomtiebar);
	return translator;
}


IPADecoTranslatorLoc const init_ipadecotranslator_loc()
{
	IPADecoTranslatorLoc translator(_("Top tie bar"), InsetIPADecoParams::Toptiebar);
	translator.addPair(_("Bottom tie bar"), InsetIPADecoParams::Bottomtiebar);
	return translator;
}


IPADecoTranslator const & ipadecotranslator()
{
	static IPADecoTranslator const decotranslator =
			init_ipadecotranslator();
	return decotranslator;
}


IPADecoTranslatorLoc const & ipadecotranslator_loc()
{
	static IPADecoTranslatorLoc const translator =
	    init_ipadecotranslator_loc();
	return translator;
}


typedef Translator<string, InsetIPAChar::Kind> IPACharTranslator;

IPACharTranslator const init_ipachartranslator()
{
	IPACharTranslator translator("\\tone{51}", InsetIPAChar::TONE_FALLING);
	translator.addPair("\\tone{15}", InsetIPAChar::TONE_RISING);
	translator.addPair("\\tone{45}", InsetIPAChar::TONE_HIGH_RISING);
	translator.addPair("\\tone{12}", InsetIPAChar::TONE_LOW_RISING);
	translator.addPair("\\tone{454}", InsetIPAChar::TONE_HIGH_RISING_FALLING);
	return translator;
}


IPACharTranslator const & ipachartranslator()
{
	static IPACharTranslator const chartranslator =
	    init_ipachartranslator();
	return chartranslator;
}

} // namespace


InsetIPADecoParams::InsetIPADecoParams()
	: type(Bottomtiebar)
{}


void InsetIPADecoParams::write(ostream & os) const
{
	string const label = ipadecotranslator().find(type);
	os << "IPADeco " << label << "\n";
}


void InsetIPADecoParams::read(Lexer & lex)
{
	string label;
	lex >> label;
	if (lex)
		type = ipadecotranslator().find(label);
}


/////////////////////////////////////////////////////////////////////
//
// InsetIPADeco
//
/////////////////////////////////////////////////////////////////////

InsetIPADeco::InsetIPADeco(Buffer * buf, string const & label)
	: InsetCollapsible(buf)
{
	setDrawFrame(true);
	setFrameColor(Color_insetframe);
	params_.type = ipadecotranslator().find(label);
}


docstring InsetIPADeco::layoutName() const
{
	return from_ascii("IPADeco:" + ipadecotranslator().find(params_.type));
}


void InsetIPADeco::metrics(MetricsInfo & mi, Dimension & dim) const
{
	InsetText::metrics(mi, dim);

	if (params_.type == InsetIPADecoParams::Toptiebar) {
		// consider width of the inset label
		FontInfo font(getLayout().labelfont());
		font.realize(sane_font);
		font.decSize();
		font.decSize();
		int w = 0;
		int a = 0;
		int d = 0;
		docstring const label(1, char_type(0x2040));
		theFontMetrics(font).rectText(label, w, a, d);
		dim.asc += int(a * 0.5);
	}
	if (params_.type == InsetIPADecoParams::Bottomtiebar) {
		// consider width of the inset label
		FontInfo font(getLayout().labelfont());
		font.realize(sane_font);
		font.decSize();
		font.decSize();
		int w = 0;
		int a = 0;
		int d = 0;
		docstring const label(1, char_type(0x203f));
		theFontMetrics(font).rectText(label, w, a, d);
		dim.des += int(d * 1.5);
	}
}


void InsetIPADeco::draw(PainterInfo & pi, int x, int y) const
{
	// draw the text
	InsetCollapsible::draw(pi, x, y);

	// draw the inset marker
	drawMarkers(pi, x, y);

	Dimension const dim = dimension(*pi.base.bv);

	if (params_.type == InsetIPADecoParams::Toptiebar) {
		FontInfo font(getLayout().labelfont());
		font.realize(sane_font);
		font.decSize();
		font.decSize();
		int w = 0;
		int a = 0;
		int d = 0;
		int asc = dim.ascent();
		docstring const label(1, char_type(0x2040));
		theFontMetrics(font).rectText(label, w, a, d);
		int const ww = max(dim.wid, w);
		pi.pain.rectText(x + (ww - w) / 2, y - int(asc / 2.5),
			label, font, Color_none, Color_none);
	}

	if (params_.type == InsetIPADecoParams::Bottomtiebar) {
		FontInfo font(getLayout().labelfont());
		font.realize(sane_font);
		font.decSize();
		font.decSize();
		int w = 0;
		int a = 0;
		int d = 0;
		int desc = dim.descent();
		docstring const label(1, char_type(0x203f));
		theFontMetrics(font).rectText(label, w, a, d);
		int const ww = max(dim.wid, w);
		pi.pain.rectText(x + (ww - w) / 2, y + int(desc / 1.5),
			label, font, Color_none, Color_none);
	}
}


void InsetIPADeco::write(ostream & os) const
{
	params_.write(os);
	InsetCollapsible::write(os);
}


void InsetIPADeco::read(Lexer & lex)
{
	params_.read(lex);
	InsetCollapsible::read(lex);
}


void InsetIPADeco::doDispatch(Cursor & cur, FuncRequest & cmd)
{
	switch (cmd.action()) {
	case LFUN_QUOTE_INSERT: {
		FuncRequest fr(LFUN_SELF_INSERT, "\"");
		InsetText::doDispatch(cur, fr);
		break;
	}
	default:
		InsetText::doDispatch(cur, cmd);
		break;
	}
}


bool InsetIPADeco::getStatus(Cursor & cur, FuncRequest const & cmd,
		FuncStatus & flag) const
{
	switch (cmd.action()) {
	case LFUN_SCRIPT_INSERT: {
		if (cmd.argument() == "subscript") {
			flag.setEnabled(false);
			return true;
		}
		break;
	}
	case LFUN_IN_IPA:
		flag.setEnabled(true);
		return true;
		break;
	default:
		break;
	}
	return InsetText::getStatus(cur, cmd, flag);
}


void InsetIPADeco::latex(otexstream & os, OutputParams const & runparams) const
{
	if (params_.type == InsetIPADecoParams::Toptiebar)
		os << "\\texttoptiebar{";
	else if (params_.type == InsetIPADecoParams::Bottomtiebar)
		os << "\\textbottomtiebar{";
	InsetCollapsible::latex(os, runparams);
	os << "}";
}


namespace {
std::pair<docstring, docstring> splitPlainTextInHalves(
		const InsetIPADeco * inset, OutputParams const & runparams,
		size_t max_length = INT_MAX)
{
	odocstringstream ods;
	int h = inset->InsetCollapsible::plaintext(ods, runparams, max_length) / 2;
	docstring result = ods.str();
	docstring const before = result.substr(0, h);
	docstring const after = result.substr(h, result.size());
	return {before, after};
}
}


int InsetIPADeco::plaintext(odocstringstream & os,
			    OutputParams const & runparams, size_t max_length) const
{
	docstring before;
	docstring after;
	tie(before, after) = splitPlainTextInHalves(this, runparams, max_length);

	if (params_.type == InsetIPADecoParams::Toptiebar) {
		os << before;
		os.put(0x0361);
		os << after;
	}
	else if (params_.type == InsetIPADecoParams::Bottomtiebar) {
		os << before;
		os.put(0x035c);
		os << after;
	}
	return before.size() + after.size();
}


void InsetIPADeco::docbook(XMLStream & xs, OutputParams const & runparams) const
{
	// The special combining character must be put in the middle, between the two other characters.
	// It will not work if there is anything else than two pure characters, so going back to plaintext.
	docstring before;
	docstring after;
	tie(before, after) = splitPlainTextInHalves(this, runparams);

	xs << XMLStream::ESCAPE_NONE << before;
	if (params_.type == InsetIPADecoParams::Toptiebar)
		xs << XMLStream::ESCAPE_NONE << "&#x0361;";
	else if (params_.type == InsetIPADecoParams::Bottomtiebar)
		xs << XMLStream::ESCAPE_NONE << "&#x035c;";
	xs << XMLStream::ESCAPE_NONE << after;
}


docstring InsetIPADeco::xhtml(XMLStream & xs, OutputParams const & runparams) const
{
	// The DocBook encoding for this inset has no DocBook tag, but sheer XML (relying on a plaintext
	// transformation of the inset).
	docbook(xs, runparams);
	return docstring();
}


docstring InsetIPADeco::toolTip(BufferView const &, int, int) const
{
	return ipadecotranslator_loc().find(params_.type);
}


string InsetIPADeco::params2string(InsetIPADecoParams const & params)
{
	ostringstream data;
	data << "IPADeco" << ' ';
	params.write(data);
	return data.str();
}


void InsetIPADeco::string2params(string const & in, InsetIPADecoParams & params)
{
	params = InsetIPADecoParams();

	if (in.empty())
		return;

	istringstream data(in);
	Lexer lex;
	lex.setStream(data);
	lex.setContext("InsetIPADeco::string2params");
	lex >> "IPADeco" >> "toptiebar";

	params.read(lex);
}


void InsetIPADeco::validate(LaTeXFeatures & features) const
{
	features.require("tipa");
	InsetText::validate(features);
}


bool InsetIPADeco::insetAllowed(InsetCode code) const
{
	switch (code) {
	// code that is allowed
	case ERT_CODE:
	case IPACHAR_CODE:
	case SCRIPT_CODE:
		return true;
	default:
		return false;
	}
}

/////////////////////////////////////////////////////////////////////////
//
// InsetIPAChar
//
/////////////////////////////////////////////////////////////////////////


InsetIPAChar::InsetIPAChar(Kind k)
	: Inset(nullptr), kind_(k)
{}


InsetIPAChar::Kind InsetIPAChar::kind() const
{
	return kind_;
}


void InsetIPAChar::metrics(MetricsInfo & mi, Dimension & dim) const
{
	frontend::FontMetrics const & fm =
		theFontMetrics(mi.base.font);
	dim.asc = fm.maxAscent();
	dim.des = fm.maxDescent();

	string s;
	switch (kind_) {
		case TONE_FALLING:
		case TONE_RISING:
		case TONE_HIGH_RISING:
		case TONE_LOW_RISING:
		case TONE_HIGH_RISING_FALLING:
			s = "_";
			break;
	}
	docstring ds(s.begin(), s.end());
	dim.wid = fm.width(ds);
}


void InsetIPAChar::draw(PainterInfo & pi, int x, int y) const
{
	FontInfo font = pi.base.font;
	frontend::FontMetrics const & fm =
			theFontMetrics(font);

	switch (kind_) {
	case TONE_FALLING:
	{
		int w = fm.width(char_type('-'));
		int h = fm.ascent(char_type('M'));
		int x2 = x + w;
		int y2 = y - h;

		pi.pain.line(x2, y2, x2, y, Color_foreground);
		pi.pain.line(x2, y, x, y2, Color_foreground);
		break;
	}
	case TONE_RISING:
	{
		int w = fm.width(char_type('-'));
		int h = fm.ascent(char_type('M'));
		int x2 = x + w;
		int y2 = y - h;

		pi.pain.line(x2, y, x2, y2, Color_foreground);
		pi.pain.line(x2, y2, x, y, Color_foreground);
		break;
	}
	case TONE_HIGH_RISING:
	{
		int w = fm.width(char_type('-'));
		int h = fm.ascent(char_type('M'));
		int x2 = x + w;
		int y2 = y - h;
		int y3 = y - int(h * 0.75);

		pi.pain.line(x2, y, x2, y2, Color_foreground);
		pi.pain.line(x2, y2, x, y3, Color_foreground);
		break;
	}
	case TONE_LOW_RISING:
	{
		int w = fm.width(char_type('-'));
		int h = fm.ascent(char_type('M'));
		int x2 = x + w;
		int y2 = y - h;
		int y3 = y - int(h * 0.25);

		pi.pain.line(x2, y, x2, y2, Color_foreground);
		pi.pain.line(x2, y3, x, y, Color_foreground);
		break;
	}
	case TONE_HIGH_RISING_FALLING:
	{
		int w = fm.width(char_type('-'));
		int h = fm.ascent(char_type('M'));
		int x2 = x + w;
		int y2 = y - h;
		int x3 = x + int(w * 0.5);
		int y3 = y - int(h * 0.75);

		pi.pain.line(x2, y, x2, y2, Color_foreground);
		pi.pain.line(x2, y3, x3, y2, Color_foreground);
		pi.pain.line(x3, y2, x, y3, Color_foreground);
		break;
	}
	}
}


void InsetIPAChar::write(ostream & os) const
{
	string const command = ipachartranslator().find(kind_);
	if (command.empty()) {
		LYXERR0("InsetIPAChar::write: Unknown type");
		return;
	}
	os << "\\IPAChar " << command << "\n";
}


void InsetIPAChar::read(Lexer & lex)
{
	lex.next();
	string const command = lex.getString();
	kind_ = ipachartranslator().find(command);
}


void InsetIPAChar::latex(otexstream & os,
			 OutputParams const &) const
{
	string const command = ipachartranslator().find(kind_);
	os << command;
}


int InsetIPAChar::plaintext(odocstringstream & os, OutputParams const &, size_t) const
{
	switch (kind_) {
	case TONE_FALLING:
		os.put(0x02e5);
		os.put(0x02e9);
		return 2;
	case TONE_RISING:
		os.put(0x02e9);
		os.put(0x02e5);
		return 2;
	case TONE_HIGH_RISING:
		os.put(0x02e7);
		os.put(0x02e5);
		return 2;
	case TONE_LOW_RISING:
		os.put(0x02e9);
		os.put(0x02e7);
		return 2;
	case TONE_HIGH_RISING_FALLING:
		os.put(0x02e8);
		os.put(0x02e5);
		os.put(0x02e8);
		return 3;
	}
	return 0;
}


namespace {
std::string ipaCharToXMLEntity(InsetIPAChar::Kind kind) {
	switch (kind) {
	case InsetIPAChar::Kind::TONE_FALLING:
		return "&#x2e5;&#x2e9;";
	case InsetIPAChar::Kind::TONE_RISING:
		return "&#x2e9;&#x2e5;";
	case InsetIPAChar::Kind::TONE_HIGH_RISING:
		return "&#x2e7;&#x2e5;";
	case InsetIPAChar::Kind::TONE_LOW_RISING:
		return "&#x2e9;&#x2e7;";
	case InsetIPAChar::Kind::TONE_HIGH_RISING_FALLING:
		return "&#x2e8;&#x2e5;&#x2e8;";
	}
	return "";
}
}


void InsetIPAChar::docbook(XMLStream & xs, OutputParams const &) const
{
	xs << XMLStream::ESCAPE_NONE << from_ascii(ipaCharToXMLEntity(kind()));
}


docstring InsetIPAChar::xhtml(XMLStream & xs, OutputParams const &) const
{
	xs << XMLStream::ESCAPE_NONE << from_ascii(ipaCharToXMLEntity(kind()));
	return docstring();
}


void InsetIPAChar::toString(odocstream & os) const
{
	odocstringstream ods;
	plaintext(ods, OutputParams(0));
	os << ods.str();
}


void InsetIPAChar::forOutliner(docstring & os, size_t const, bool const) const
{
	odocstringstream ods;
	plaintext(ods, OutputParams(0));
	os += ods.str();
}


void InsetIPAChar::validate(LaTeXFeatures & features) const
{
	switch (kind_) {
	case TONE_FALLING:
	case TONE_RISING:
	case TONE_HIGH_RISING:
	case TONE_LOW_RISING:
	case TONE_HIGH_RISING_FALLING:
		features.require("tone");
		break;
	default:
		break;
	}
}


} // namespace lyx
