#include "DocVisitor.h"


const float fontSizes[4] = { 12.0f, 30.0f, 24.0f, 18.0f };
const float lineSpacing = 1.5f;
const float pageLeftMargin = 60.0f;
const float pageRightMargin = 60.0f;
const float pageTopMargin = 120.0f;
const float pageBottomMargin = 60.0f;


/*
	HPDF_Page_SetTextRenderingMode(page, HPDF_FILL);
    HPDF_Page_SetTextRenderingMode(page, HPDF_STROKE);
*/  


DocVisitor::DocVisitor(DocOutputDevice* doc, DocStyle* style, DocTemplate* templ) :
	m_doc(doc),
	m_page(0),
	m_style(style),
	m_templ(templ)
{
	x = y = 0.0f;
	currentStyle.push(0);
	currentHeadingLevel.push(0);
	insideCode = 0;
	insideLink = 0;	
	
	for (int i = 0; i < 32; i++)
		indentLevels[i] = 30.0f * i;
	indentLevel = 0;
}


DocVisitor::~DocVisitor()
{
	delete m_page; // calls endText
}


void DocVisitor::AddPage()
{
	delete m_page; // calls endText
	m_page = m_doc->newPage();
	m_style->adornNewPage(*m_page);
	m_templ->Apply(*m_page);
	m_page->setColor(0);
	m_page->setFontSize(currentStyle.top(), fontSizes[currentHeadingLevel.top()]);
}


void DocVisitor::CheckIfEndOfPage()
{
	if (y > (m_page->height() - pageTopMargin - pageBottomMargin))
	{
		// Start a new page
		AddPage();
		y = 0.0f;
	}
}


/// Visit a document.
bool DocVisitor::VisitEnter( const TiXmlDocument& /*doc*/ )
{
	AddPage();
	return true;
}


/// Visit a document.
bool DocVisitor::VisitExit( const TiXmlDocument& /*doc*/ )
{
	delete m_page; // calls endText
	m_page = 0;
	return true;
}



/// Visit a declaration
bool DocVisitor::Visit(const TiXmlDeclaration& /*declaration*/)
{
	return true;
}


/// Visit a stylesheet reference
bool DocVisitor::Visit(const TiXmlStylesheetReference& /*stylesheet*/)
{
	return true;
}


/// Visit a comment node
bool DocVisitor::Visit( const TiXmlComment& /*comment*/ )
{
	return true;
}


/// Visit an unknown node
bool DocVisitor::Visit(const TiXmlUnknown& /*unknown*/)
{
	return true;
}


void DocVisitor::NewLine()
{
	x = 0.0f;
	y += fontSizes[currentHeadingLevel.top()] * lineSpacing;
	CheckIfEndOfPage();
}


/// Visit an element.
bool DocVisitor::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* /*firstAttribute*/ )
{
	if (strcmp(element.ValueTStr().c_str(), "p") == 0)
	{
		NewLine();
	}
	else if (strcmp(element.ValueTStr().c_str(), "ul") == 0)
	{
		NewLine();
	}
	else if (strcmp(element.ValueTStr().c_str(), "li") == 0)
	{
		indentLevel++;
		m_page->drawCircle(pageLeftMargin + x + indentLevels[indentLevel], y + pageTopMargin - fontSizes[currentHeadingLevel.top()] * lineSpacing * 0.25f, 1.2f);
		x += indentLevels[indentLevel] + 8.0f;
	}
	else if (strcmp(element.ValueTStr().c_str(), "code") == 0)
	{
		insideCode++;
		m_page->setColor(0.2f, 0.3f, 0.2f); // dark green
	}
	else if (strcmp(element.ValueTStr().c_str(), "a") == 0)
	{
		insideLink++;
		m_page->setColor(0.0f, 0.0f, 1.0f); // blue
	}
	else if (strcmp(element.ValueTStr().c_str(), "h1") == 0)
		currentHeadingLevel.push(1), NewLine();
	else if (strcmp(element.ValueTStr().c_str(), "h2") == 0)
		currentHeadingLevel.push(2), NewLine();
	else if (strcmp(element.ValueTStr().c_str(), "h3") == 0)
		currentHeadingLevel.push(3), NewLine();
	else if (strcmp(element.ValueTStr().c_str(), "strong") == 0)
		currentStyle.push(2);
	else if (strcmp(element.ValueTStr().c_str(), "em") == 0)
		currentStyle.push(1);
	else if (strcmp(element.ValueTStr().c_str(), "hr") == 0)
	{
	}
	else {
		return false;
	}
	return true;
}


/// Visit an element.
bool DocVisitor::VisitExit( const TiXmlElement& element )
{
	if (strcmp(element.ValueTStr().c_str(), "p") == 0)
	{
		NewLine();
	}
	else if (strcmp(element.ValueTStr().c_str(), "ul") == 0)
	{
		NewLine();
	}
	else if (strcmp(element.ValueTStr().c_str(), "li") == 0)
	{
		indentLevel--;
		NewLine();
	}
	else if (strcmp(element.ValueTStr().c_str(), "code") == 0)
	{
		insideCode--;
		if (insideCode == 0)
			m_page->setColor(0); // black
	}
	else if (strcmp(element.ValueTStr().c_str(), "a") == 0)
	{
		insideLink--;
		if (insideLink == 0)
			m_page->setColor(0); // black
	}
	else if (strcmp(element.ValueTStr().c_str(), "hr") == 0)
	{
		m_page->drawLine(pageLeftMargin, y + pageTopMargin, m_page->width() - pageRightMargin, y + pageTopMargin);
		NewLine();
	}
	else if (strcmp(element.ValueTStr().c_str(), "h1") == 0) {
		currentHeadingLevel.pop();
		NewLine();
	}
	else if (strcmp(element.ValueTStr().c_str(), "h2") == 0)
	{
		currentHeadingLevel.pop();
		x = 0.0;
		y += fontSizes[currentHeadingLevel.top()] * lineSpacing * 0.5f;
		m_page->drawLine(pageLeftMargin, y + pageTopMargin, m_page->width() - pageRightMargin, y + pageTopMargin);
		y += fontSizes[currentHeadingLevel.top()] * lineSpacing * 0.5f;
		CheckIfEndOfPage();
	}
	else if (strcmp(element.ValueTStr().c_str(), "h3") == 0)
		currentHeadingLevel.pop(), NewLine();
	else if (strcmp(element.ValueTStr().c_str(), "strong") == 0)
		currentStyle.pop();
	else if (strcmp(element.ValueTStr().c_str(), "em") == 0)
		currentStyle.pop();
	else {
		return false;
	}
	return true;
}


float DocVisitor::OutputTextHelper(const char* str)
{
	float w = m_page->textWidth(str);
	if (insideCode)// || insideLink)
	{
		m_page->drawRect(pageLeftMargin + x - 1, y + pageTopMargin + 2, w + 2, fontSizes[currentHeadingLevel.top()] + 2);

		if (insideCode)
			m_page->setColor(0.1f, 0.1f, 0.1f); // dark green
		else
			m_page->setColor(0.3f, 0.3f, 1.0f); // blue
	}
	m_page->drawText(pageLeftMargin + x, y + pageTopMargin, str);
	return w;
}


// outputs current word to pdf
// splits the string up by words if it has to, and outputs new words on a new line if it will over fill the current line
// but also if the word is too big for a complete line, then will split that word mid-word as needed.
void DocVisitor::OutputCurrentWord(const char* str)
{
	// Need to see if need to break mid-word if the word is too long for the line
	float wordWidthAndMargins = pageLeftMargin + pageRightMargin + m_page->textWidth(str);
	
	if ((wordWidthAndMargins + x) >= m_page->width()) // Checking if it can fit on the current line or not
	{
		if (wordWidthAndMargins >= m_page->width()) // Can it fit on a new line by itself?
		{
			// we need to break up the text, write it char by char
			int i = 0;
			while (str[i]) {
				char s[2] = { str[i], 0 };
				// TODO: adding the dash is okay if the text is English, but not if text is other language, eg Japanese or Chinese
				float chWidthAndMargins = pageLeftMargin + pageRightMargin + m_page->textWidth(s) + m_page->textWidth("-");
				if ((chWidthAndMargins + x) >= m_page->width())
				{
					OutputTextHelper("-");
					NewLine();
				}
				x += OutputTextHelper(s);
				i++;
			}
			x += m_page->textWidth(" ");
			return;
		}
		else
		{
			NewLine();
		}
	}
	x += OutputTextHelper(str) + m_page->textWidth(" ");
}


/// Visit a text node
bool DocVisitor::Visit( const TiXmlText& text )
{
	const char* str = text.ValueTStr().c_str();

	m_page->setFontSize(currentStyle.top(), fontSizes[currentHeadingLevel.top()]);
	//m_page->setColor(0);

	std::string currentWord;
	int i = 0;
	while ( str[i] != 0 ) {
		if ( str[i] == ' ' || str[i] == '\t' || str[i] == '\n' ) {
			OutputCurrentWord(currentWord.c_str());
			if (str[i] == '\n') { // for html, this is wrong
				NewLine();
			}
			currentWord = "";
			//break;
		}
		else
		{
			char s[2] = { str[i], 0 };
			currentWord += s;
		}
		i++;
	}
	if ( str[i] == 0 ) {
		OutputCurrentWord(currentWord.c_str());
	}

	return true;
}
