#include "DocTypes.h"

NAMESPACE_UPP

static Color DocQTFColor[] = {
	Black, LtGray, White, Red, Green, Blue, LtRed, WhiteGray, LtCyan, Yellow
};

static int DocQTFFontHeight[] = {
	50, 67, 84, 100, 134, 167, 200, 234, 300, 400
};

static void SeparateNumber(String& s)
{
	if(*s.Last() >= '0' && *s.Last() <= '9')
		s.Cat(';');
}

//////////////////////////////////////////////////////////////////////

struct RichObjectTypeDrawingData
{
	void    Serialize(Stream& stream);

	Drawing drawing;
	Size    dot_size;
	String  comment;
};

void RichObjectTypeDrawingData::Serialize(Stream& stream)
{
	int version = 1;
	stream % version % dot_size % comment % drawing;
}

static String RichObjectTypeDrawingTypeName()
{
	return "Drawing";

}

static String FmtColor(Color c)
{
	if(IsNull(c)) return "N";
	for(int i = 0; i < 10; i++)
		if(DocQTFColor[i] == c)
			return String(i + '0', 1);
	return NFormat("(%d.%d.%d)", c.GetR(), c.GetG(), c.GetB());
}

String CharFmt(const Paragraph::Part& a, const Paragraph::Part& b)
{
	String fmt;
	if(a.font.IsBold() != b.font.IsBold()) fmt.Cat('*');
	if(a.font.IsItalic() != b.font.IsItalic()) fmt.Cat('/');
	if(a.font.IsUnderline() != b.font.IsUnderline()) fmt.Cat('_');
	if(a.font.IsStrikeout() != b.font.IsStrikeout()) fmt.Cat('-');
//	if(a.capitals != b.capitals) fmt.Cat('c');
	if(a.delta != b.delta)
		fmt.Cat(b.delta == 0 ? a.delta < 0 ? '`' : ',' :
		        b.delta < 0 ? '`' : ',');
	if(a.font.GetFace() != b.font.GetFace())
		switch(b.font.GetFace()) {
		case Font::ARIAL:     fmt.Cat('A'); break;
		case Font::ROMAN:     fmt.Cat('R'); break;
		case Font::COURIER:   fmt.Cat('C'); break;
		case Font::STDFONT:   fmt.Cat('G'); break;
		case Font::SYMBOL:    fmt.Cat('S'); break;
		default:
			fmt << "!" << a.font.GetFaceName() << "!";
		}
	if(a.value != b.value)
		fmt << '^' << DeQtf(StdFormat(b.value)) << '^';
	if(a.color != b.color)
		fmt << "@" << FmtColor(b.color);
	if(a.font.GetHeight() != b.font.GetHeight()) {
		for(int i = 0; i < 10; i++)
			if(b.font.GetHeight() == DocQTFFontHeight[i]) {
				SeparateNumber(fmt);
				fmt.Cat('0' + i);
				return fmt;
			}
		fmt.Cat(NFormat("+%d", tabs(b.font.GetHeight())));
	}
	return fmt;
}

static String sFmtNumber(char c, int former, int current)
{
	return former != current ? NFormat("%c%d", c, current) : String();
}

String QTFEncodeParaFormat(const Paragraph::Style& format, const Paragraph::Part& charformat,
                           const Paragraph::Style& style, const Paragraph::Part& charstyle,
                           bool savecharformat)
{
	String qtf;
	if(format.align != style.align)
		switch(format.align) {
		case ALIGN_LEFT:    qtf << '<'; break;
		case ALIGN_RIGHT:   qtf << '>'; break;
		case ALIGN_CENTER:  qtf << '='; break;
		case ALIGN_JUSTIFY: qtf << '#'; break;
		}
	qtf << sFmtNumber('l', style.lm, format.lm)
	    << sFmtNumber('r', style.rm, format.rm)
	    << sFmtNumber('i', style.indent, format.indent)
	    << sFmtNumber('b', style.before, format.before)
	    << sFmtNumber('a', style.after, format.after);
	if(style.bullet != format.bullet) {
		qtf << 'O';
		qtf << (format.bullet ? "0" : "_");
	}
	if(savecharformat)
		qtf << CharFmt(charstyle, charformat);
	qtf << " ";
	return qtf;
}

String QTFEncodePara(const Paragraph& p,
                     const Paragraph::Style& style, const Paragraph::Part& charstyle)
{
	Paragraph::Part paraformat;
	paraformat.font = p.GetParaFont();
	String qtf = QTFEncodeParaFormat(p.GetStyle(), paraformat, style, charstyle,
		                             p.GetCount() == 0);
	int d = qtf.GetLength();
	for(int i = 0; i < p.GetCount(); i++) {
		const Paragraph::Part& part = p.GetParts()[i];
		if(part.pr) {
//			const RichObject& object = part.object;
			Size sz = part.sz;
			qtf << "\r\n";
			qtf << "@@" << RichObjectTypeDrawingTypeName() << ':' << sz.cx
			    << '*' << sz.cy << "\r\n";
			DrawingDraw ddraw(sz);
			part.pr.Paint(ddraw, sz, Black, White, 0);
			RichObjectTypeDrawingData dwg;
			dwg.drawing = ddraw;
			dwg.dot_size = sz;
			StringStream datastrm;
			datastrm % dwg;
			String data = datastrm;
			const char *q = data.Begin();
			const char *slim = data.End();
			int n = 0;
			qtf.Reserve(qtf.GetLength() + 8 * data.GetLength() / 7);
			while(q < slim - 7) {
				byte seven = 0;
				byte data[8];
				data[0] = ((q[0] & 0x80) >> 7) |
				          ((q[1] & 0x80) >> 6) |
				          ((q[2] & 0x80) >> 5) |
				          ((q[3] & 0x80) >> 4) |
				          ((q[4] & 0x80) >> 3) |
				          ((q[5] & 0x80) >> 2) |
				          ((q[6] & 0x80) >> 1) |
				          0x80;
				data[1] = q[0] | 0x80;
				data[2] = q[1] | 0x80;
				data[3] = q[2] | 0x80;
				data[4] = q[3] | 0x80;
				data[5] = q[4] | 0x80;
				data[6] = q[5] | 0x80;
				data[7] = q[6] | 0x80;
				qtf.Cat(data, 8);
				if(++n % 10 == 0)
					qtf << "\r\n";
				q += 7;
			}
			while(q < slim) {
				byte seven = 0;
				const char *lim = slim;
				const char *s;
				for(s = q; s < lim; s++)
					seven = (seven >> 1) | (*s & 0x80);
				seven >>= 8 - (lim - q);
				qtf.Cat(seven | 0x80);
				for(s = q; s < lim; s++)
					qtf.Cat(*s | 0x80);
				if(++n % 10 == 0)
					qtf << "\r\n";
				q += 7;
			}
		}
		else {
			String cf = CharFmt(charstyle, part);
			if(!cf.IsEmpty()) {
				qtf << '[' << cf << ' ';
				d += cf.GetLength() + 2;
			}
			for(const char *s = part.text.Begin(); s != part.text.End(); s++) {
				byte c = *s;
				if(c < 128) {
					if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
					|| c == ' ' || c == '.' || c == ',' || c == '?' || c == '(' || c == ')'
					|| c == ';')
					{
						qtf.Cat(c);
						d++;
					}
					else if(c == 9)
					{
						qtf.Cat(127);
						d++;
					}
					else if(c == 31)
					{
						qtf.Cat('_');
						d++;
					}
					else
					{
						qtf.Cat('`');
						qtf.Cat(c);
						d += 2;
					}
					if(d > 60 && c == ' ')
					{
						qtf.Cat("\r\n");
						d = 0;
					}
				}
				else
				{
					qtf.Cat(c);
					d++;
				}
				if(d > 80)
				{
					qtf.Cat("\r\n");
					d = 0;
				}
			}
			if(!cf.IsEmpty()) {
				d++;
				qtf << ']';
			}
		}
	}
	return qtf;
}

String AsQtf(const Document& text)
{
	Paragraph::Style empty_format;
	empty_format.Init();
	Paragraph::Part empty_part;
	String qtf;
//	qtf << "[";
	for(int i = 0; i < text.GetCount(); i++)
		if(text.IsParagraph(i)) {
			const Paragraph& p = text.GetParagraph(i);
			if(i) qtf << "\r\n&\r\n";
			qtf << "[";
			qtf << QTFEncodePara(p, empty_format, empty_part);
			qtf << "]";
		}
	qtf << "\r\n"; // ]";
	return qtf;
}

END_UPP_NAMESPACE
