#include "RichText.h"


NAMESPACE_UPP

static int GetParaHeight(const Array<RichPara::Part>& parts)
{
	int ht = 0;
 	for(int i = 0; i < parts.GetCount(); i++) {
		int pht = 0;
		const RichPara::Part& part = parts[i];
		if(part.object)
			pht = part.object.GetSize().cy;
		else if(part.field)
			pht = GetParaHeight(part.fieldpart);
		else
			pht = tabs(part.format.GetHeight());
		if(pht > ht)
			ht = pht;
	}
	return ht;
}

class RTFEncoder {
public:
	RTFEncoder(Stream& stream, const RichText& richtext, byte charset);

	void            Run();

private:
	void            FacesAddFormat(const RichPara::CharFormat& format);
	void            GetFaces();
	void            GetTxtFaces(const RichTxt& txt);

	void            PutHeader();
	void            PutDocument();
	void            PutTxt(const RichTxt& txt, int nesting, int dot_width);
	void            PutTable(const RichTable& table, int nesting, int dot_width);
	void            PutParts(const Array<RichPara::Part>& parts,
		RichPara::CharFormat& base, int bpart, int boff, int epart, int eoff);

	void            Begin()                           { stream.Put('{'); }
	void            Begin(const char *cmd)            { Begin(); Command(cmd); }
	void            Begin(const char *cmd, int param) { Begin(); Command(cmd, param); }
	void            End()                             { stream.Put('}'); }

	void            Command(const char *cmd);
	void            Command(const char *cmd, int param);
	void            Space()                           { stream.Put(' '); }
	void            PutText(const char *text);
	void            PutObject(const RichObject& object);
	void            PutTabs(const Vector<RichPara::Tab>& tabs);

	bool            PutParaFormat(const RichPara::Format& pf, const RichPara::Format& difpf);
	bool            PutCharFormat(const RichPara::CharFormat& cf, const RichPara::CharFormat& difcf, bool pn);

	struct Group;

	friend struct Group;

	struct Group {
		Group(RTFEncoder *owner) : owner(owner) { owner->Begin(); }
		Group(RTFEncoder *owner, const char *cmd) : owner(owner) { owner->Begin(cmd); }
		Group(RTFEncoder *owner, const char *cmd, int param) : owner(owner) { owner->Begin(cmd, param); }
		~Group() { owner->End(); }
		RTFEncoder *owner;
	};

private:
	Stream&               stream;
	const RichText&       richtext;
	byte                  charset;

	RichPara::CharFormat  charfmt;
	RichPara::Format      parafmt;
	Uuid                  oldstyle;
	int                   old_ht;
	int                   para_ht;

	Index<int>            used_faces;
	enum { SYMBOL_INDEX = 1, WINGDINGS_INDEX = 2 };
	VectorMap<Color, int> used_ink, used_paper;
	Index<Color>          phys_colors;
	Index<Uuid>           styleid;
};

void EncodeRTF(Stream& stream, const RichText& richtext, byte charset)
{
	RTFEncoder(stream, richtext, charset).Run();
}

String EncodeRTF(const RichText& richtext, byte charset)
{
	StringStream out;
	EncodeRTF(out, richtext, charset);
	String s = out.GetResult();
	return s;
}

RTFEncoder::RTFEncoder(Stream& stream, const RichText& richtext, byte charset)
: stream(stream)
, richtext(richtext)
, charset(charset)
{
	for(int i = 0; i < richtext.GetStyleCount(); i++)
		styleid.Add(richtext.GetStyleId(i));
}

void RTFEncoder::Run()
{
	GetFaces();
	Group docgrp(this, "rtf");
	PutHeader();
	PutDocument();
}

void RTFEncoder::FacesAddFormat(const RichPara::CharFormat& format)
{
	used_faces.FindAdd(format.GetFace());
	if(used_ink.Find(format.ink) < 0) {
		Color i(format.ink.GetR(), format.ink.GetG(), format.ink.GetB());
		int x = used_ink.Get(i, -1);
		if(x < 0)
			x = phys_colors.FindAdd(i);
		used_ink.Add(format.ink, x);
	}
	if(used_paper.Find(format.paper) < 0) {
		Color p(format.paper.GetR(), format.paper.GetG(), format.paper.GetB());
		int x = used_paper.Get(p, -1);
		if(x < 0)
			x = phys_colors.FindAdd(p);
		used_paper.Add(format.paper, x);
	}
}

void RTFEncoder::GetFaces()
{
	used_faces.Add(Font::ARIAL); // default font

	used_faces.Add(Font::SYMBOL); // used for bullets

#ifdef PLATFORM_WIN32

	used_faces.Add(Font::WINGDINGS); // used for bullets

#endif

	phys_colors.Add(Null);
	used_ink.Add(Null, 0);
	used_paper.Add(Null, 0);
	used_ink.Add(Black(), 0);
	used_paper.Add(White(), 0);
	GetTxtFaces(richtext);
	for(int i = 0; i < richtext.GetStyleCount(); i++)
		FacesAddFormat(richtext.GetStyle(i).format);
}

void RTFEncoder::GetTxtFaces(const RichTxt& rt)
{
	for(int i = 0; i < rt.GetPartCount(); i++) {
		if(rt.IsTable(i)) {
			const RichTable& table = rt.GetTable(i);
			const RichTable::Format& tfmt = table.GetFormat();
			phys_colors.FindAdd(tfmt.framecolor);
			phys_colors.FindAdd(tfmt.gridcolor);
			for(int r = 0; r < table.GetRows(); r++)
				for(int c = 0; c < table.GetColumns(); c++) {
					const RichCell& cell = table.cell[r][c];
					phys_colors.FindAdd(cell.format.color);
					phys_colors.FindAdd(cell.format.bordercolor);
					GetTxtFaces(cell.text);
				}
		}
		else {
			const RichPara& para = rt.Get(i, richtext.GetStyles());
			FacesAddFormat(para.format);
			for(int p = 0; p < para.part.GetCount(); p++) {
				const RichPara::Part& part = para.part[p];
				if(part.IsText())
					FacesAddFormat(part.format);
			}
		}
	}
}

void RTFEncoder::Command(const char *cmd)
{
	stream.Put('\\');
	stream.Put(cmd);
}

void RTFEncoder::Command(const char *cmd, int param)
{
	stream.Put('\\');
	stream.Put(cmd);
	stream.Put(IntStr(param));
}

void RTFEncoder::PutText(const char *text)
{
	while(*text)
	{
		if(*text == '{' || *text == '}' || *text == '\\')
			stream.Put('\\');
		stream.Put(*text++);
	}
}

bool RTFEncoder::PutCharFormat(const RichPara::CharFormat& cf, const RichPara::CharFormat& difcf, bool pn)
{
	int64 pos = stream.GetPos();
	int pn2 = (pn ? 0 : 2);
	bool f;
	int t;
	if(cf.GetFace() != difcf.GetFace())
		Command("pnf" + pn2, used_faces.Find(cf.GetFace()));
	if((t = DotPoints(2 * tabs(cf.GetHeight()))) != DotPoints(2 * tabs(difcf.GetHeight())))
		Command("pnfs" + pn2, t);
	if(!pn && (t = cf.sscript) != difcf.sscript)
		Command(t == 0 ? "nosupersub" : t == 1 ? "super" : "sub");
	if((f = cf.IsBold())          != difcf.IsBold())          Command((f ? "pnb" : "pnb0") + pn2);
	if((f = cf.IsItalic())        != difcf.IsItalic())        Command((f ? "pni" : "pni0") + pn2);
	if((f = cf.IsUnderline())     != difcf.IsUnderline())     Command((f ? "pnul" : "pnul0") + pn2);
	if((f = cf.IsStrikeout())     != difcf.IsStrikeout())     Command((f ? "pnstrike" : "pnstrike0") + pn2);
	if((f = cf.capitals)          != difcf.capitals)          Command((f ? "pncaps" : "pncaps0") + pn2);
	if((t = used_ink.Get(cf.ink)) != used_ink.Get(difcf.ink)) Command("pncf" + pn2, t);
	if((t = used_paper.Get(cf.paper)) != used_paper.Get(difcf.paper)) Command("pncb" + pn2, t);
#ifdef PLATFORM_WIN32 //zapoznamkoval Fidler kdyz chtel zkompilovat pod Linuxem...

	if(!pn && cf.language         != difcf.language)          Command("lang", GetLanguageLCID(cf.language));
#endif

	// todo: link

	return stream.GetPos() != pos;
}

void RTFEncoder::PutObject(const RichObject& object)
{
#ifdef PLATFORM_WIN32

#ifndef PLATFORM_WINCE

	Size log_size = object.GetPixelSize(), out_size = object.GetSize();
	if(log_size.cx <= 0 || log_size.cy <= 0) log_size = out_size;
	Size scale = out_size * 100 / log_size;
	Group pict_grp(this, "pict");
	Command("wmetafile", 8);
	Command("picw", 2540 * DotTwips(out_size.cx) / 1440);
	Command("pich", 2540 * DotTwips(out_size.cy) / 1440);
	Command("picwgoal", DotTwips(out_size.cx));
	Command("pichgoal", DotTwips(out_size.cy));
//	Command("picscalex", scale.cx);

//	Command("picscaley", scale.cy);

	WinMetaFileDraw wmd(log_size.cx, log_size.cy);
	object.Paint(wmd, log_size);
	WinMetaFile wmf = wmd.Close();
	HENHMETAFILE hemf = wmf.GetHEMF();
	int size = GetWinMetaFileBits(hemf, 0, 0, MM_ANISOTROPIC, ScreenHDC());
	if(size > 0)
	{
		Buffer<byte> buffer(size);
		GetWinMetaFileBits(hemf, size, buffer, MM_ANISOTROPIC, ScreenHDC());
		enum { BLOCK = 32 };
		const byte *b = buffer;
		for(int l = size; l > 0; l -= BLOCK)
		{
			stream.PutCrLf();
			for(const byte *e = b + min<int>(l, BLOCK); b < e; b++)
			{
				static const char binhex[] = "0123456789abcdef";
				stream.Put(binhex[*b >> 4]);
				stream.Put(binhex[*b & 15]);
			}
		}
	}
#endif

#endif

}

bool RTFEncoder::PutParaFormat(const RichPara::Format& pf, const RichPara::Format& difpf)
{
	int64 pos = stream.GetPos();
	if(pf.align != difpf.align)
		switch(pf.align)
		{
		case ALIGN_NULL:
		case ALIGN_LEFT:    break;
		case ALIGN_CENTER:  Command("qc"); break;
		case ALIGN_RIGHT:   Command("qr"); break;
		case ALIGN_JUSTIFY: Command("qj"); break;
		default:            NEVER();
		}
	int oind = difpf.indent, olm = difpf.lm;
	if(difpf.bullet != RichPara::BULLET_NONE)
	{
		olm += oind;
		oind = -oind;
	}
	int nind = pf.indent, nlm = pf.lm;
	if(pf.bullet != RichPara::BULLET_NONE)
	{
		nlm += nind;
		nind = -nind;
	}
	if(nind      != oind)         Command("fi", DotTwips(nind));
	if(nlm       != olm)          Command("li", DotTwips(nlm));
	if(pf.rm     != difpf.rm)     Command("ri", DotTwips(pf.rm));
	if(pf.before != difpf.before) Command("sb", DotTwips(pf.before));
	if(pf.after  != difpf.after)  Command("sa", DotTwips(pf.after));
	if(pf.tab    != difpf.tab)    PutTabs(pf.tab);
	return stream.GetPos() != pos;
}

void RTFEncoder::PutTabs(const Vector<RichPara::Tab>& tabs)
{
	for(int i = 0; i < tabs.GetCount(); i++)
	{
		RichPara::Tab t = tabs[i];
		switch(t.align)
		{
		case ALIGN_NULL:
		case ALIGN_LEFT:   break;
		case ALIGN_CENTER: Command("tqc"); break;
		case ALIGN_RIGHT:  Command("tqr"); break;
		default: NEVER();
		}
		switch(t.fillchar)
		{
		case 0: break;
		case 1: Command("tldot"); break;
		case 2: Command("tlhyph"); break;
		case 3: Command("tlul"); break;
		default: NEVER();
		}
		Command("tx", DotTwips(t.pos));
	}
}

void RTFEncoder::PutParts(const Array<RichPara::Part>& parts,
	RichPara::CharFormat& base, int bpart, int boff, int epart, int eoff)
{
	if(epart >= parts.GetCount()) {
		epart = parts.GetCount() - 1;
		eoff = INT_MAX;
	}
	ASSERT(boff >= 0 && bpart >= 0);
	for(int p = bpart; p <= epart; p++) {
		const RichPara::Part& part = parts[p];
		if(part.IsText()) {
			WString px = part.text;
			if(p == epart && px.GetLength() > eoff)
				px.Trim(eoff);
			if(p == bpart)
				px.Remove(0, boff);
			if(!px.IsEmpty()) {
				if(PutCharFormat(part.format, base, false) || p == 0)
					Space();
				base = part.format;
				PutText(FromUnicode(px, charset));
			}
		}
		else if(part.object)
			PutObject(part.object);
	}

}

void RTFEncoder::PutHeader()
{
	static const short ansicpg[][2] = {
		{ CHARSET_WIN1250, 1250 },
		{ CHARSET_WIN1251, 1251 },
		{ CHARSET_WIN1252, 1252 },
		{ CHARSET_WIN1253, 1253 },
		{ CHARSET_WIN1254, 1254 },
		{ CHARSET_WIN1255, 1255 },
		{ CHARSET_WIN1256, 1256 },
		{ CHARSET_WIN1257, 1257 },
		{ CHARSET_WIN1258, 1258 },
	};
	int i;
	for(i = __countof(ansicpg); --i >= 0;)
		if(charset == ansicpg[i][0]) {
			Command("ansicpg", ansicpg[i][1]);
			break;
		}

	Command("deff", 0);

	{
		Group ftbl(this, "fonttbl");
		for(int i = 0; i < used_faces.GetCount(); i++) {
			Group fnt(this, "f", i);
			int fn = used_faces[i];
			dword info = Font::GetFaceInfo(fn);
			if(info & Font::SYMBOLTYPE)
				Command("ftech");
			else if(info & Font::FIXEDPITCH)
				Command("fmodern");
			else if(fn == Font::ROMAN)
				Command("froman");
			else if(fn == Font::ARIAL
#ifdef PLATFORM_WIN32

			|| fn == Font::TAHOMA
#endif

			)
				Command("fswiss");
			Space();
			stream.Put(Font::GetFaceName(fn));
			stream.Put(';');
		}
	}

	if(phys_colors.GetCount() > 1) {
		Group ctbl(this, "colortbl");
		stream.Put(';');
		for(int i = 1; i < phys_colors.GetCount(); i++) {
			Color rgb = phys_colors[i];
			Command("red",   rgb.GetR());
			Command("green", rgb.GetG());
			Command("blue",  rgb.GetB());
			stream.Put(';');
		}
	}

	Begin("stylesheet");
		RichPara::CharFormat empcfmt;
		RichPara::Format emppfmt;

		for(i = 0; i < richtext.GetStyleCount(); i++) {
			const RichStyle& style = richtext.GetStyle(i);
			Begin("s", i);
				PutParaFormat(style.format, emppfmt);
				PutCharFormat(style.format, empcfmt, false);
				Command("sbasedon", 222);
				if(style.next != richtext.GetStyleId(i))
					Command("snext", styleid.Find(style.next));
				Space();
				for(const char *n = style.name; *n; n++)
					stream.Put(*n == ';' ? '_' : *n);
				stream.Put(';');
			End();
		}
	End();
}

void RTFEncoder::PutTable(const RichTable& table, int nesting, int dot_width)
{
	Vector<int> vspan_counts;
	const RichTable::Format& tfmt = table.GetFormat();
	Vector<int> column_pos;
	column_pos.SetCount(tfmt.column.GetCount() + 1);
	column_pos[0] = tfmt.lm;
	int sum = 0;
	for(int c = 0; c < tfmt.column.GetCount(); c++)
		sum += tfmt.column[c];
	int rem_width = dot_width - tfmt.lm - tfmt.rm;
	for(int c = 0; c < tfmt.column.GetCount(); c++) {
		int part = iscale(tfmt.column[c], rem_width, max(sum, 1));
		sum -= tfmt.column[c];
		rem_width -= part;
		column_pos[c + 1] = column_pos[c] + part;
	}
	for(int r = 0; r < table.GetRows(); r++) {
		String rowfmt;
		if(nesting)
			rowfmt << "\\*\\nesttableprops";
		rowfmt << "\\trowd"
			"\\trleft" << DotTwips(tfmt.lm)
			// trbrdr[ltrbv]

			;
		Vector<int> cellindex;
		Rect dflt_margin(600, 600, 600, 600);
		for(int c = 0; c < table.GetColumns(); c++) {
			const RichCell& cell = table.cell[r][c];
			dflt_margin.left = min(dflt_margin.left, cell.format.margin.left);
			dflt_margin.top = min(dflt_margin.top, cell.format.margin.top);
			dflt_margin.right = min(dflt_margin.right, cell.format.margin.right);
			dflt_margin.bottom = min(dflt_margin.bottom, cell.format.margin.bottom);
			c += cell.hspan;
		}
/*
		rowfmt <<
			"\\trpaddl" << DotTwips(dflt_margin.left) << "\\trpaddfl3"
			"\\trpaddt" << DotTwips(dflt_margin.top) << "\\trpaddft3"
			"\\trpaddr" << DotTwips(dflt_margin.right) << "\\trpaddfr3"
			"\\trpaddb" << DotTwips(dflt_margin.bottom) << "\\trpaddfb3";
*/
		for(int c = 0; c < table.GetColumns(); c++) {
			const RichCell& cell = table.cell[r][c];
/*
			if(cell.format.margin.left != dflt_margin.left)
				rowfmt << "\\clpadl" << DotTwips(cell.format.margin.left) << "\\clpadfl3";
			if(cell.format.margin.top != dflt_margin.top)
				rowfmt << "\\clpadt" << DotTwips(cell.format.margin.top) << "\\clpadft3";
			if(cell.format.margin.right != dflt_margin.right)
				rowfmt << "\\clpadr" << DotTwips(cell.format.margin.right) << "\\clpadfr3";
			if(cell.format.margin.bottom != dflt_margin.bottom)
				rowfmt << "\\clpadb" << DotTwips(cell.format.margin.bottom) << "\\clpadfb3";
*/
			switch(cell.format.align) {
				case ALIGN_TOP: rowfmt << "\\clvertalt"; break;
				default:
				case ALIGN_CENTER: rowfmt << "\\clvertalc"; break;
				case ALIGN_BOTTOM: rowfmt << "\\clvertalb"; break;
			}
			cellindex.Add(c);
			int cell_start = column_pos[c];
			if(cell.hspan)
				c += cell.hspan;
			int cell_end = column_pos[c + 1];
			if(cell.vspan) {
				vspan_counts.At(c, 0) = cell.vspan;
				rowfmt << "\\clvmgf";
			}
			else if(c < vspan_counts.GetCount() && vspan_counts[c] > 0)
				rowfmt << "\\clvmrg";
			if(!IsNull(cell.format.color))
				rowfmt << "\\clcbpat" << phys_colors.Find(cell.format.color);
			rowfmt << "\\cellx" << DotTwips(cell_end);
		}
		Begin();
		Command("pard");
		Command("intbl");
		parafmt = RichPara::Format();
		charfmt = RichPara::CharFormat();
		if(nesting)
			Command("itap", nesting + 1);
		if(!nesting)
			stream.Put(rowfmt);
		for(int c = 0; c < cellindex.GetCount(); c++) {
			int cx = cellindex[c];
			const RichCell& cell = table.cell[r][cx];
			int cell_wd = column_pos[cx + cell.hspan + 1] - column_pos[cx];
			PutTxt(cell.text, nesting + 1, cell_wd);
			Command(nesting ? "nestcell" : "cell");
		}
		stream.Put(rowfmt);
		Command(nesting ? "nestrow" : "row");
		End();
	}
}

void RTFEncoder::PutTxt(const RichTxt& txt, int nesting, int dot_width)
{
	for(int i = 0; i < txt.GetPartCount(); i++) {
		if(txt.IsTable(i))
			PutTable(txt.GetTable(i), nesting, dot_width);
		else {
			const RichPara& para = txt.Get(i, richtext.GetStyles());
			Uuid pstyle = txt.GetParaStyle(i);
			int para_ht = GetParaHeight(para.part);
			int first_part = 0, first_ind = 0;
			bool hdiff = (para.format.bullet != RichPara::BULLET_NONE
				&& para.format.bullet != RichPara::BULLET_TEXT && para_ht != para_ht);
			if(pstyle != oldstyle
			|| para.format.bullet != parafmt.bullet || para.format.bullet == RichPara::BULLET_TEXT
			|| hdiff || para.format.tab != parafmt.tab) {
				Command("s", styleid.Find(pstyle));
				oldstyle = pstyle;
				parafmt = richtext.GetStyle(pstyle).format;
	//			charfmt = richtext.GetStyle(pstyle).charformat;

	//			if(para.format.tab != parafmt.tab || hdiff)

				{
					Command("pard");
					if(nesting)
						Command("intbl");
					if(nesting > 1)
						Command("itap", nesting);
					parafmt = RichPara::Format();
					para_ht = 0;
				}
			}
			if(para.format.bullet == RichPara::BULLET_TEXT)
			{
				int epart = 0, eoff = 0;
				while(epart < para.part.GetCount() && (eoff = para.part[epart].text.Find('\t')) < 0)
					epart++;

				RichPara::CharFormat rcf = charfmt;
				rcf.Height(0);
				Begin("pntext");
					PutParts(para.part, rcf, 0, 0, epart, eoff);
					stream.Put('\t');
				End();

				Begin("*\\pn");
					Command("pnhang");
					Command("pnql");
					rcf = charfmt;
					if(epart > 0 || eoff > 0)
						PutCharFormat(para.part[0].format, rcf, true);
					Begin("pntxta");
					End();
					Begin("pntxtb");
						Space();
						PutParts(para.part, rcf, 0, 0, epart, eoff);
						stream.Put('\t');
					End();
				End();
			}
			else if(para.format.bullet != RichPara::BULLET_NONE)
			{
				int sym_face = SYMBOL_INDEX;
				byte sym_char = 0xB7;

				switch(para.format.bullet) {
				default:
					NEVER();

				case RichPara::BULLET_ROUND:      sym_face = SYMBOL_INDEX;    sym_char = 0xB7; break;
				case RichPara::BULLET_ROUNDWHITE: sym_face = WINGDINGS_INDEX; sym_char = 0xA1; break;
				case RichPara::BULLET_BOX:        sym_face = WINGDINGS_INDEX; sym_char = 'n'; break;
				case RichPara::BULLET_BOXWHITE:   sym_face = WINGDINGS_INDEX; sym_char = 'o'; break;
				}

				Begin("pntext");
	//					Command("tqr");

	//					Command("tx", DotTwips(para.format.indent));

	//					Command("qr");

					Command("f", sym_face);
					Command("fs", DotPoints(2 * para_ht));
					Space();
					stream.Put(sym_char);
					stream.Put('\t');
				End();

				Begin("*\\pn");
					Command("pnlvlblt");
					Command("pnf", sym_face);
					Command("pnfs", DotPoints(2 * para_ht));
					Command("pnql");
					Begin("pntxtb");
						Space();
						stream.Put(sym_char);
	//						stream.Put('\t');

					End();
					Begin("pntxta");
					End();
				End();
			}
			PutParaFormat(para.format, parafmt);
			para_ht  = para_ht;
			parafmt = para.format;
			PutParts(para.part, charfmt, first_part, first_ind, para.part.GetCount(), 0);
			if(i + 1 < txt.GetPartCount())
				Command("par");
		}
	}
}

void RTFEncoder::PutDocument()
{
	charfmt.Height(PointDots(0));
	charfmt.language = 0;
	old_ht = DotPoints(2 * tabs(charfmt.GetHeight()));
	para_ht = 0;
	oldstyle = Uuid::Create();
	PutTxt(richtext, 0, 3600);
}

END_UPP_NAMESPACE


syntax highlighted by Code2HTML, v. 0.9.1