/* -*- 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-2003 William Lachance (wrlach@gmail.com)
 *
 * 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 <iostream>

#include "DocumentElement.hxx"
#include "FilterInternal.hxx"
#include "ListStyle.hxx"
#include "TextRunStyle.hxx"

namespace librvngabw
{
//! internal structure used to defined correspondance between bullet and AbiWord index
struct BulletToIndexCorrespondance
{
	//! constructor
	BulletToIndexCorrespondance() : m_map()
	{
		static int const(s_unicodeToList[])=
		{
			int('.'), 5, 0x2022, 5, // bullet
			int('+'), 6, int('-'), 6, // dash
			0x25a0, 7, // black box
			0x25b6, 8, 0x25b2, 8, // black up triangle
			0x25c6, 9, 0x25c7, 9, 0x2666, 9, // diamond
			0x2605, 10, 0x2606, 10, 0x2733, 10, // star
			0x2192, 11, 0x8658, 11, 0x21D2, 11, 0x2794, 11, // imply
			0x2610, 12, 0x2713, 12, // checkbox
			0x25A1, 13, 0x2610, 13, 0x2752, 13, // white box
			0x261E, 14, 0x261b, 14, // hand
			0x2764, 15, 0x2765, 15, // heart
			0x27a2, 16, 0x27a3, 16, 0x27a4, 16 // arrow
		};
		int const numElements=RVNGABW_N_ELEMENTS(s_unicodeToList);
		for (int i=0; i<numElements; i+=2)
		{
			librevenge::RVNGString string("");
			appendUnicode(uint32_t(s_unicodeToList[i]), string);
			m_map[string]=s_unicodeToList[i+1];
		}
	}
	//! returns an index corresponding to a bullet
	int get(librevenge::RVNGString const &bullet) const
	{
		auto iter = m_map.find(bullet);
		if (iter!=m_map.end()) return iter->second;
		return 5;
	}
private:
	std::map<librevenge::RVNGString, int> m_map;
};

static BulletToIndexCorrespondance s_bulletToIndexMap;

void ListLevelStyle::addTo(librevenge::RVNGPropertyList &propList, librevenge::RVNGPropertyList &propPropList, int what, int parentId) const
{
	propList.insert("parentid", parentId>=0 ? parentId : m_parentId);
	librevenge::RVNGPropertyList &pList=what==0 ? propPropList : propList;
	if (m_ordered)
	{
		if (m_propList["style:num-format"] && !m_propList["style:num-format"]->getStr().empty())
		{
			switch (m_propList["style:num-format"]->getStr().cstr()[0])
			{
			case 'a' :
				pList.insert("type", 1);
				break;
			case 'A' :
				pList.insert("type", 2);
				break;
			case 'i' :
				pList.insert("type", 3);
				break;
			case 'I' :
				pList.insert("type", 4);
				break;
			case '1' :
			default:
				pList.insert("type", 0);
				break;
			}
		}
		else
			pList.insert("type", 0);
		if (m_propList["text:start-value"])
		{
			if (m_propList["text:start-value"]->getInt() > 0)
				pList.insert("start-value", m_propList["text:start-value"]->getStr());
			else
				pList.insert("start-value", "1");
		}
		else // we need a start-value to avoid invalid file
			pList.insert("start-value", "1");
		pList.insert("list-decimal", ".");

		librevenge::RVNGString sEscapedString("");
		if (m_propList["style:num-prefix"])
			sEscapedString.appendEscapedXML(m_propList["style:num-prefix"]->getStr());
		sEscapedString.append("%L");
		if (m_propList["style:num-suffix"])
			sEscapedString.appendEscapedXML(m_propList["style:num-suffix"]->getStr());
		pList.insert("list-delim", sEscapedString);
	}
	else // unordered list
	{
		pList.insert
		("type", m_propList["text:bullet-char"] ? s_bulletToIndexMap.get(m_propList["text:bullet-char"]->getStr()) : 5);
		pList.insert("start-value", 0);
		pList.insert("list-delim", "%L");
		pList.insert("list-decimal", ".");
	}

	if (what==0)
	{
		propList.insert("level", m_level);
		propList.insert("listid", m_id);

		static char const *(names[])=
		{
			"Numbered List", "Lower Case List", "Upper Case List", "Lower Roman List", "Upper Roman List",
			"Bullet List", "Dashed List", "Square List", "Triangle List", "Diamond List",
			"Star List", "Implies List", "Tick List", "Box List", "Hand List",
			"Heart List", "Arrowhead List"
		};
		if (pList["type"] && pList["type"]->getInt()>=0 && pList["type"]->getInt()<=16)
		{
			pList.insert("list-style", names[pList["type"]->getInt()]);
			pList.remove("type");
		}
		else
		{
			RVNGABW_DEBUG_MSG(("ListLevelStyle::addTo: unexpected style\n"));
		}
		// now try to update the text-indent field and margin (checkme)
		pList.insert("field-font", "NULL");
		double minLabelWidth=0, tmp, textIndent=0, marginLeft=0;
		if (m_propList["text:space-before"] && getInchValue(*m_propList["text:space-before"], tmp) && tmp>0)
			minLabelWidth+=tmp;
		if (m_propList["text:min-label-width"] && getInchValue(*m_propList["text:min-label-width"], tmp) && tmp>0)
			minLabelWidth+=tmp;
		if (m_propList["text:min-label-distance"] && getInchValue(*m_propList["text:min-label-distance"], tmp) && tmp>0)
			minLabelWidth+=tmp;
		if (minLabelWidth<0.1)
			minLabelWidth=0.3;
		if (m_propList["fo:text-indent"])
			getInchValue(*m_propList["fo:text-indent"], textIndent);
		pList.insert("text-indent", textIndent-minLabelWidth);
		if (m_propList["fo:margin-left"])
			getInchValue(*m_propList["fo:margin-left"], marginLeft);
		pList.insert("margin-left", 0.2*m_level+marginLeft+minLabelWidth);
	}
	else
		propList.insert("id", m_id);
}

bool ListLevelStyle::isEqualTo(const librevenge::RVNGPropertyList &xPropList, int level, bool ordered) const
{
	if (level!=m_level || ordered!=m_ordered)
		return false;
	for (int i=0; i<4; ++i)
	{
		static char const *(attributes[])=
		{
			"style:num-format", "text:start-value", "style:num-prefix", "style:num-suffix", "text:bullet-char"
		};
		if (!xPropList[attributes[i]])
		{
			if (m_propList[attributes[i]])
				return false;
			continue;
		}
		if (!m_propList[attributes[i]] || m_propList[attributes[i]]->getStr()!=xPropList[attributes[i]]->getStr())
			return false;
	}
	return true;
}

void ListLevelStyle::writeDefinition(ABWDocumentHandler *pHandler) const
{
	librevenge::RVNGPropertyList propList, propPropList;
	addTo(propList, propPropList, 1);
	TagOpenElement("l", propList).write(pHandler);
	pHandler->endElement("l");
}

List::List(const int iListID, ListManager &manager) :
	m_manager(manager),
	m_id(iListID),
	m_levelIdList()
{
}

List::~List()
{
}

void List::setListLevel(int level, std::shared_ptr<ListLevelStyle> iListLevelStyle)
{
	if (!iListLevelStyle)
		return;
	m_manager.storeLevel(iListLevelStyle);
	if (level<0)
	{
		RVNGABW_DEBUG_MSG(("List::setListLevel: the level %d seems bad\n", level));
		return;
	}
	if (level>=int(m_levelIdList.size()))
		m_levelIdList.resize(size_t(level)+1);
	m_levelIdList[size_t(level)] = iListLevelStyle->getId();
	if (level>0 && isListLevelDefined(level-1))
		iListLevelStyle->setParentId(m_levelIdList[size_t(level-1)]);
	if (isListLevelDefined(level+1))
	{
		std::shared_ptr<ListLevelStyle> child=m_manager.getLevel(m_levelIdList[size_t(level+1)]);
		if (child)
			child->setParentId(m_levelIdList[size_t(level)]);
	}
}

void List::updateListLevel(const int level, const librevenge::RVNGPropertyList &xPropList, bool ordered)
{
	if (level < 0)
	{
		RVNGABW_DEBUG_MSG(("List::updateListLevel: the level %d seems bad\n", level));
		return;
	}
	if (isListLevelDefined(level))
	{
		std::shared_ptr<ListLevelStyle> levelStyle=m_manager.getLevel(m_levelIdList[size_t(level)]);
		if (levelStyle && levelStyle->isEqualTo(xPropList, level, ordered))
			return;
	}

	std::shared_ptr<ListLevelStyle> newLevel(new ListLevelStyle(xPropList, level, ordered, m_manager.getNewLevelId()));
	setListLevel(level, newLevel);
}

void List::addTo(librevenge::RVNGPropertyList &propList, librevenge::RVNGPropertyList &propPropList, int level, int parentId) const
{
	std::shared_ptr<ListLevelStyle> levelStyle;
	if (isListLevelDefined(level))
		levelStyle=m_manager.getLevel(m_levelIdList[size_t(level)]);
	if (!levelStyle)
	{
		RVNGABW_DEBUG_MSG(("ListLevelStyle::addTo: can not find level %d\n", level));
		return;
	}
	levelStyle->addTo(propList, propPropList, 0, parentId);
}

//
// list manager
//

ListManager::State::State() :
	m_currentList(nullptr), m_currentLevel(0), m_createdLevel(0),
	m_levelIdList(),
	m_listElementOpenedStack()
{
}

ListManager::State::State(const ListManager::State &state) :
	m_currentList(state.m_currentList), m_currentLevel(state.m_currentLevel), m_createdLevel(state.m_currentLevel),
	m_levelIdList(state.m_levelIdList),
	m_listElementOpenedStack(state.m_listElementOpenedStack)
{
}

ListManager::State &ListManager::State::operator=(const ListManager::State &state)
{
	if (this == &state)
		return *this;
	m_currentList=state.m_currentList;
	m_currentLevel=state.m_currentLevel;
	m_createdLevel=state.m_currentLevel;
	m_levelIdList=state.m_levelIdList;
	m_listElementOpenedStack=state.m_listElementOpenedStack;
	return *this;
}
//

ListManager::ListManager(ParagraphStyleManager &paragraphManager) :
	m_paragraphManager(paragraphManager), m_levelId(1000), m_idToLevelMap(), m_styleList(), m_idToStyleMap(), m_stateStack()
{
	m_stateStack.push(State());
}

ListManager::~ListManager()
{
}

ListManager::State &ListManager::getState()
{
	if (!m_stateStack.empty()) return m_stateStack.top();
	RVNGABW_DEBUG_MSG(("ListManager::getState: call with no state\n"));
	static ListManager::State bad;
	return bad;
}

void ListManager::popState()
{
	if (m_stateStack.size()>1)
		m_stateStack.pop();
}

void ListManager::pushState()
{
	m_stateStack.push(State());
}

bool ListManager::openListElement(const librevenge::RVNGPropertyList &propList, DocumentElementVector &output, int &xId)
{
	auto &state=getState();

	librevenge::RVNGPropertyList finalPropList(propList);
	finalPropList.insert("style:parent-style-name", "Standard");
	librevenge::RVNGPropertyList paraList, propPropList;
	librevenge::RVNGString paragraphName= m_paragraphManager.findOrAdd(finalPropList, propPropList);

	// create a document element corresponding to the paragraph, and append it to our list of document elements
	paraList.insert("style", paragraphName);
	paraList.insert("xid", ++xId);
	if (state.m_currentList)
	{
		bool ok=true;
		for (int lev=state.m_createdLevel+1; lev<state.m_currentLevel; ++lev)
		{
			// some level are not created, we must created them
			static bool first=true;
			if (first)
			{
				first=false;
				RVNGABW_DEBUG_MSG(("ListManager::openListElement must opened some empty paragraph\n"));
			}
			if (!state.m_currentList->isListLevelDefined(lev-1))
			{
				librevenge::RVNGPropertyList emptyLevel;
				emptyLevel.insert("librevenge:list-id",state.m_currentList->getListId());
				emptyLevel.insert("librevenge:level",lev);
				defineLevel(emptyLevel, false);
			}
			librevenge::RVNGPropertyList emptyPara;
			emptyPara.insert("fo:line-height", 1, librevenge::RVNG_POINT);
			++state.m_createdLevel;
			if (openListElement(emptyPara, output, xId))
				closeListElement(output);
			if (state.m_createdLevel<lev)   // sanety check
			{
				RVNGABW_DEBUG_MSG(("ListManager::openListElement oops something is bad\n"));
				ok=false;
				break;
			}
		}
		if (ok)
		{
			int level=state.m_currentLevel-1;
			// time to update the list id state
			int levelId=state.m_currentList->getLevelId(level);
			if (level>=0 && levelId>=0)
			{
				if (level>=int(state.m_levelIdList.size()))
					state.m_levelIdList.resize(size_t(level+1),0);
				state.m_levelIdList[size_t(level)]=levelId;
			}

			state.m_currentList->addTo
			(paraList, propPropList, level,
			 (level>0&&level-1<(int)state.m_levelIdList.size()) ? state.m_levelIdList[size_t(level-1)] : 0);
			if (state.m_createdLevel<state.m_currentLevel)
				state.m_createdLevel=state.m_currentLevel;

		}
	}
	if (!propPropList.empty())
	{
		librevenge::RVNGPropertyListVector propPropVector;
		propPropVector.append(propPropList);
		paraList.insert("props", propPropVector);
	}
	output.push_back(std::make_shared<TagOpenElement>("p", paraList));

	return true;
}

void ListManager::closeListElement(DocumentElementVector &output)
{
	output.push_back(std::make_shared<TagCloseElement>("p"));
}

bool ListManager::openLevel(const librevenge::RVNGPropertyList &propList, bool ordered)
{
	auto &state=getState();
	librevenge::RVNGPropertyList pList(propList);
	if (!pList["librevenge:level"])
		pList.insert("librevenge:level", state.m_currentLevel+1);
	state.m_currentLevel=pList["librevenge:level"]->getInt();
	if (state.m_currentLevel<0)
	{
		RVNGABW_DEBUG_MSG(("ListManager: ListManager::openLevel the level seems bad, assume 1\n"));
		state.m_currentLevel=1;
		pList.insert("librevenge:level", 1);
	}
	defineLevel(pList, ordered);
	if (!state.m_listElementOpenedStack.empty() && !state.m_listElementOpenedStack.top())
		state.m_listElementOpenedStack.top() = true;

	state.m_listElementOpenedStack.push(false);
	return true;
}

void ListManager::closeLevel()
{
	auto &state=getState();
	if (state.m_listElementOpenedStack.empty())
	{
		// this implies that openListLevel was not called, so it is better to stop here
		RVNGABW_DEBUG_MSG(("ListManager: Attempting to close an unexisting level\n"));
		return;
	}
	if (state.m_currentLevel>0)
		--state.m_currentLevel;
	state.m_listElementOpenedStack.pop();
	if (!state.m_listElementOpenedStack.empty())
		state.m_listElementOpenedStack.top() = false;
}

void ListManager::write(ABWDocumentHandler *pHandler) const
{
	if (m_styleList.empty())
		return;
	pHandler->startElement("lists", librevenge::RVNGPropertyList());
	for (const auto &iterLevels : m_idToLevelMap)
	{
		if (iterLevels.second)
			iterLevels.second->writeDefinition(pHandler);
	}
	pHandler->endElement("lists");
}

void ListManager::defineLevel(const librevenge::RVNGPropertyList &propList, bool ordered)
{
	int id = -1;
	if (propList["librevenge:list-id"])
		id = propList["librevenge:list-id"]->getInt();

	List *pList = nullptr;
	auto &state=getState();
	// first check if we can reused the current list
	if (state.m_currentList && state.m_currentList->getListId() == id)
		pList = state.m_currentList;

	if (pList == nullptr && m_idToStyleMap.find(id) != m_idToStyleMap.end() && m_idToStyleMap.find(id)->second)
		pList = m_idToStyleMap.find(id)->second.get();

	if (pList == nullptr)
	{
		RVNGABW_DEBUG_MSG(("ListManager:defineLevel Attempting to create a new list style (listid: %i)\n", id));
		std::shared_ptr<List> newList(new List(id, *this));
		m_idToStyleMap[id]=newList;
		m_styleList.push_back(newList);
		state.m_currentList = pList = newList.get();
	}
	state.m_currentList = pList;

	if (!propList["librevenge:level"])
	{
		RVNGABW_DEBUG_MSG(("ListManager:defineLevel can not find the set level\n"));
		return;
	}
	int level=propList["librevenge:level"]->getInt() - 1;
	pList->updateListLevel(level, propList, ordered);
}
}

/* vim:set shiftwidth=4 softtabstop=4 noexpandtab: */
