/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* librvngabw
 * Version: MPL 2.0 / LGPLv2.1+
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Major Contributor(s):
 * Copyright (C) 2002-2004 William Lachance (wrlach@gmail.com)
 * Copyright (C) 2004 Fridrich Strba (fridrich.strba@bluewin.ch)
 *
 * For minor contributions see the git repository.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU Lesser General Public License Version 2.1 or later
 * (LGPLv2.1+), in which case the provisions of the LGPLv2.1+ are
 * applicable instead of those above.
 *
 * For further information visit http://libwpd.sourceforge.net
 */

/* "This product is not manufactured, approved, or supported by
 * Corel Corporation or Corel Corporation Limited."
 */
#include "FilterInternal.hxx"
#include "TextRunStyle.hxx"
#include "DocumentElement.hxx"

#ifdef _MSC_VER
#include <minmax.h>
#endif

#include <string.h>
#include <stdio.h>

namespace librvngabw
{
////////////////////////////////////////////////////////////
// paragraph manager
////////////////////////////////////////////////////////////
ParagraphStyle::ParagraphStyle(const librevenge::RVNGPropertyList &pPropList, const librevenge::RVNGString &sName) : Style(sName),
	m_propList(pPropList)
{
}

ParagraphStyle::~ParagraphStyle()
{
}

void ParagraphStyle::appendBorderProperties(librevenge::RVNGPropertyList &propPropList) const
{
	librvngabw::appendBorderProperties(m_propList, propPropList,true);
}

void ParagraphStyle::write(ABWDocumentHandler *pHandler) const
{
	//RVNGABW_DEBUG_MSG(("ParagraphStyle: Writing a paragraph style..\n"));

	librevenge::RVNGPropertyList propList;
	propList.insert("name", getName());
	propList.insert("type", "P");
	if (m_propList["style:display-name"])
		propList.insert("display-name", m_propList["style:display-name"]->clone());
	if (m_propList["style:parent-style-name"])
		propList.insert("basedon", m_propList["style:parent-style-name"]->clone());
	librevenge::RVNGPropertyList propPropList;
	librevenge::RVNGPropertyList::Iter i(m_propList);
	for (i.rewind(); i.next();)
	{
		if (i.child() ||
		        !strcmp(i.key(), "style:display-name") ||
		        !strcmp(i.key(), "style:parent-style-name") ||
		        !strcmp(i.key(), "style:master-page-name") ||
		        !strncmp(i.key(), "librevenge:",11))
			continue;
		if (strcmp(i.key(), "fo:text-align")==0)
		{
			if (i()->getStr() == "start")
				propPropList.insert("text-align",  "left");
			else if (i()->getStr() == "end")
				propPropList.insert("text-align",  "right");
			else
				propPropList.insert("text-align",  i()->clone());
		}
		else if (strncmp(i.key(), "fo:margin-",10)==0)
		{
			if (strcmp(i.key(), "fo:margin-left")==0)
				propPropList.insert("margin-left",  i()->clone());
			else if (strcmp(i.key(), "fo:margin-right")==0)
				propPropList.insert("margin-right",  i()->clone());
			else if (strcmp(i.key(), "fo:margin-top")==0)
				propPropList.insert("margin-top",  i()->clone());
			else if (!strcmp(i.key(), "fo:margin-bottom"))
			{
				if (i()->getDouble() > 0.0)
					propPropList.insert("margin-bottom", i()->clone());
				else
					propPropList.insert("margin-bottom", 0.0);
			}
		}
		else if (strcmp(i.key(), "fo:line-height")==0)
		{
			if (i()->getUnit()==librevenge::RVNG_PERCENT)
				propPropList.insert("line-height",  i()->getDouble(), librevenge::RVNG_GENERIC);
			else
				propPropList.insert("line-height",  i()->clone());
		}
		else if (strcmp(i.key(), "fo:text-indent")==0)
			propPropList.insert("text-indent",  i()->clone());
		else if (strcmp(i.key(), "fo:keep-with-next")==0 && i()->getStr()=="always")
			propPropList.insert("keep-with-next", "yes");
		else if (strcmp(i.key(), "fo:keep-together")==0 && i()->getStr()=="always")
			propPropList.insert("keep-together", "yes");
	}
	const auto pTabStops = m_propList.child("style:tab-stops");
	if (pTabStops && pTabStops->count())
	{
		librevenge::RVNGString tabsBuffer("");
		librevenge::RVNGPropertyListVector::Iter k(*pTabStops);
		for (k.rewind(); k.next();)
		{
			if (!k()["style:position"] || k()["style:position"]->getDouble() < 0.0)
				continue;
			tabsBuffer.append(k()["style:position"]->getStr());
			if (k()["style:type"])
			{
				if (k()["style:type"]->getStr() == "right")
					tabsBuffer.append("/R");
				else if (k()["style:type"]->getStr() == "center")
					tabsBuffer.append("/C");
				else if (k()["style:type"]->getStr() == "char")
					tabsBuffer.append("/D");
				else
					tabsBuffer.append("/L");
			}
			else // Left aligned is default
				tabsBuffer.append("/L");

			if (k()["style:leader-text"])
			{
				if (k()["style:leader-text"]->getStr() == "-")
					tabsBuffer.append("2");
				else if (k()["style:leader-text"]->getStr() == "_")
					tabsBuffer.append("3");
				else // default to dot leader if the given leader is dot or is not supported by AbiWord
					tabsBuffer.append("1");
			}
			else
				tabsBuffer.append("0");
			tabsBuffer.append(",");
		}
		propPropList.insert("tabstops", tabsBuffer);
	}

	if (!propPropList.empty())
	{
		librevenge::RVNGPropertyListVector propPropListVector;
		propPropListVector.append(propPropList);
		propList.insert("props", propPropListVector);
	}
	pHandler->startElement("s", propList);
	pHandler->endElement("s");
}

SpanStyle::SpanStyle(const char *psName, const librevenge::RVNGPropertyList &xPropList) :
	Style(psName),
	m_propList(xPropList)
{
}

void SpanStyle::write(ABWDocumentHandler *pHandler) const
{
	//RVNGABW_DEBUG_MSG(("SpanStyle: Writing a span style..\n"));
	librevenge::RVNGPropertyList propList;
	propList.insert("name", getName());
	propList.insert("type", "C");
	if (m_propList["style:display-name"])
		propList.insert("isplay-name", m_propList["style:display-name"]->clone());
	if (m_propList["style:parent-style-name"])
		propList.insert("basedon", m_propList["style:parent-style-name"]->clone());
	librevenge::RVNGPropertyList propPropList;
	SpanStyleManager::addSpanProperties(m_propList, propPropList);
	if (!propPropList.empty())
	{
		librevenge::RVNGPropertyListVector propPropListVector;
		propPropListVector.append(propPropList);
		propList.insert("props", propPropListVector);
	}

	pHandler->startElement("s", propList);
	pHandler->endElement("s");
}

void ParagraphStyleManager::clean()
{
	m_hashNameMap.clear();
	m_nameStyleMap.clear();
	m_displayStyleNameMap.clear();
}

void ParagraphStyleManager::write(ABWDocumentHandler *pHandler) const
{
	for (const auto &iter : m_nameStyleMap)
	{
		if (iter.second)
			(iter.second)->write(pHandler);
	}
}

librevenge::RVNGString ParagraphStyleManager::findOrAdd(const librevenge::RVNGPropertyList &propList,
                                                        librevenge::RVNGPropertyList &propPropList)
{
	librevenge::RVNGPropertyList pList(propList);
	librevenge::RVNGPropertyList::Iter i(propList);
	bool hasBorders=false;
	// remove the border property, list properties, ...
	for (i.rewind(); i.next();)
	{
		if (strncmp("fo:border", i.key(), 9)==0)
		{
			pList.remove(i.key());
			hasBorders=true;
		}
		else if (strcmp(i.key(),"text:start-value")==0)
			pList.remove(i.key());
	}
	// look if we have already create this style
	librevenge::RVNGString hashKey = pList.getPropString();
	auto iter = m_hashNameMap.find(hashKey);
	librevenge::RVNGString sName("");
	if (iter!=m_hashNameMap.end())
		sName=iter->second;
	else
	{
		// ok create a new style
		sName.sprintf("S%i", (int)m_nameStyleMap.size());
		if (propList["style:display-name"])
		{
			pList=propList; // we will need the border here
			librevenge::RVNGString name(propList["style:display-name"]->getStr());
			if (m_displayStyleNameMap.find(name) != m_displayStyleNameMap.end())
			{
				RVNGABW_DEBUG_MSG(("ParagraphStyleManager::findOrAdd: a paragraph with name %s already exists\n", name.cstr()));
				pList.remove("style:display-name");
			}
			else
				m_displayStyleNameMap[name]=sName;
		}

		std::shared_ptr<ParagraphStyle> parag(new ParagraphStyle(pList, sName));
		m_nameStyleMap[sName] =parag;
		m_hashNameMap[hashKey] = sName;
	}
	// finally update the borders properties
	if (pList["style:parent-style-name"] && pList["style:parent-style-name"]->getStr()!="Standard")
	{
		auto parent=get(pList["style:parent-style-name"]->getStr());
		if (parent)
			parent->appendBorderProperties(propPropList);
	}
	if (hasBorders)
		appendBorderProperties(propList,propPropList,true);

	return sName;
}

std::shared_ptr<ParagraphStyle> const ParagraphStyleManager::get(const librevenge::RVNGString &name) const
{
	auto iter = m_nameStyleMap.find(name);
	if (iter == m_nameStyleMap.end()) return std::shared_ptr<ParagraphStyle>();
	return iter->second;
}

////////////////////////////////////////////////////////////
// span manager
////////////////////////////////////////////////////////////
void SpanStyleManager::clean()
{
	m_hashNameMap.clear();
	m_nameStyleMap.clear();
	m_displayStyleNameMap.clear();
}

void SpanStyleManager::addSpanProperties(librevenge::RVNGPropertyList const &style, librevenge::RVNGPropertyList &element)
{
	librevenge::RVNGPropertyList::Iter i(style);
	librevenge::RVNGString decorations(""), language(""), country("");
	for (i.rewind(); i.next();)
	{
		if (i.child()) continue;
		switch (i.key()[0])
		{
		case 'f':
			if (!strcmp(i.key(), "fo:font-size"))
			{
				if (style["fo:font-size"]->getDouble() > 0.0)
					element.insert("font-size", i()->clone());
				break;
			}
			if (!strcmp(i.key(), "fo:font-weight"))
				element.insert("font-weight", i()->clone());
			else if (!strcmp(i.key(), "fo:font-style"))
				element.insert("font-style", i()->clone());
			else if (!strcmp(i.key(), "fo:color"))
				element.insert("color", i()->clone());
			else if (!strcmp(i.key(), "fo:background-color"))
				element.insert("bgcolor", i()->clone());
			else if (!strcmp(i.key(), "fo:letter-spacing"))
			{
				/*
				  checkme: is condensed, expanded, semi-condensed, extra-condensed, ultra-condensed, ...  available here ?

				  if (i()->getDouble()<0)
				    element.insert("font-stretch","condensed");
				  else if (i()->getDouble()>0)
					element.insert("font-stretch","expanded");
				*/
			}
			else if (!strcmp(i.key(), "fo:language"))
				language=i()->getStr();
			else if (!strcmp(i.key(), "fo:country"))
				country=i()->getStr();
			// also  fo:hyphen*, fo:script, fo:font*, fo:text*
			break;
		case 's':
			if (!strcmp(i.key(), "style:font-name"))
				element.insert("font-family", i()->clone());
			else if (!strcmp(i.key(), "style:text-overline-type"))
				decorations.append("overline ");
			else if (!strcmp(i.key(), "style:text-underline-type"))
				decorations.append("underline ");
			else if (!strcmp(i.key(), "style:text-line-through-type"))
				decorations.append("line-through ");
			else if (!strcmp(i.key(), "style:text-position"))
			{
				librevenge::RVNGString field=i()->getStr();
				char firstChar=field.empty() ? ' ' : field.cstr()[0];
				if (firstChar=='s')
				{
					if (strncmp(field.cstr(),"super",5)==0)
						element.insert("text-position", "superscript");
					else if (strncmp(field.cstr(),"sub",3)==0)
						element.insert("text-position", "subscript");
				}
				else if (firstChar=='-') // negative delta
					element.insert("text-position", "subscript");
				else if (firstChar>='0' && firstChar<='9') // positif delta
					element.insert("text-position", "superscript");
			}
			else if (!strcmp(i.key(), "style:language"))
				language=i()->getStr();
			else if (!strcmp(i.key(), "style:country"))
				country=i()->getStr();
			else if (!strcmp(i.key(), "style:writing-mode"))
			{
				if (i()->getStr()=="rl" || i()->getStr()=="rl-tb" || i()->getStr()=="tb-rl")
					element.insert("dir-override","rtl");
			}
			// also style:font*, style:letter*, style:rfc-*, style:script*, style:text*
			break;
		case 't':
			if (!strcmp(i.key(), "text:display"))
				element.insert("display",i()->clone());
			break;
		default:
			break;
		}
	}
	if (!decorations.empty())
		element.insert("text-decoration", decorations);
	if (!language.empty())
	{
		if (country.empty() || country=="none")
			element.insert("lang", language);
		else
		{
			librevenge::RVNGString lang;
			lang.sprintf("%s-%s", language.cstr(), country.cstr());
			element.insert("lang", lang);
		}
	}
}

void SpanStyleManager::write(ABWDocumentHandler *pHandler) const
{
	for (const auto &iter : m_nameStyleMap)
	{
		if (iter.second)
			(iter.second)->write(pHandler);
	}
}

librevenge::RVNGString SpanStyleManager::findOrAdd(const librevenge::RVNGPropertyList &propList)
{
	librevenge::RVNGPropertyList pList(propList);

	auto hashKey = pList.getPropString();
	auto iter = m_hashNameMap.find(hashKey);
	if (iter!=m_hashNameMap.end()) return iter->second;

	// ok create a new list
	//RVNGABW_DEBUG_MSG(("SpanStyleManager::findOrAdd: Span Hash Key: %s\n", hashKey.cstr()));

	librevenge::RVNGString sName("");
	sName.sprintf("Span%i", (int)m_nameStyleMap.size());
	std::shared_ptr<SpanStyle> span(new SpanStyle(sName.cstr(), propList));
	m_nameStyleMap[sName] = span;
	m_hashNameMap[hashKey] = sName;
	if (propList["style:display-name"] && !propList["style:display-name"]->getStr().empty())
		m_displayStyleNameMap[propList["style:display-name"]->getStr()]=sName;
	return sName;
}

std::shared_ptr<SpanStyle> const SpanStyleManager::get(const librevenge::RVNGString &name) const
{
	auto iter = m_nameStyleMap.find(name);
	if (iter == m_nameStyleMap.end()) return std::shared_ptr<SpanStyle>();
	return iter->second;
}
}
/* vim:set shiftwidth=4 softtabstop=4 noexpandtab: */
