/* -*- 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 <math.h>
#include <time.h>

#include <iostream>
#include <locale>
#include <string>
#include <sstream>

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

#include "Table.hxx"
#ifdef _MSC_VER
#include <minmax.h>
#endif

#include <string.h>

namespace librvngabw
{
void TableCell::addTo(DocumentElementVector &tableContent, int &xId, RVNGABWVec2i const &decal) const
{
	RVNGABWVec2i pos=m_position+decal;
	librevenge::RVNGPropertyList cellList;
	cellList.insert("xid", ++xId);
	if (pos[0]>=0)
	{
		m_cellPropList.insert("left-attach", pos[0]);
		m_cellPropList.insert("right-attach", pos[0]+m_span[0]);
	}
	if (pos[1]>=0)
	{
		m_cellPropList.insert("top-attach", pos[1]);
		m_cellPropList.insert("bottom-attach", pos[1]+m_span[1]);
	}
	if (!m_cellPropList.empty())
	{
		librevenge::RVNGPropertyListVector cellPropVector;
		cellPropVector.append(m_cellPropList);
		cellList.insert("props", cellPropVector);
	}
	tableContent.push_back(std::make_shared<TagOpenElement>("cell", cellList));
	bool empty=true;
	for (const auto &i : m_content)
	{
		if (!i)
			continue;
		tableContent.push_back(i);
		empty=false;
	}
	if (empty)
	{
		librevenge::RVNGPropertyList paraList;
		paraList.insert("xid", ++xId);
		tableContent.push_back(std::make_shared<TagOpenElement>("p", paraList));
		tableContent.push_back(std::make_shared<TagCloseElement>("p"));
	}
	tableContent.push_back(std::make_shared<TagCloseElement>("cell"));
}

Table::Table(TableManager &manager, const librevenge::RVNGPropertyList &xPropList, int &xId, bool isSpreadsheet) :
	m_manager(manager), m_propList(xPropList), m_xId(xId), m_isSpreadsheet(isSpreadsheet), m_tableContent(), m_row(), m_cell(),
	m_actualRow(0), m_actualColumn(0),
	m_isRowOpened(false), m_isCellOpened(false),
	m_lastFillPosition(-1,-1), m_rowDataMap(), m_delayedCellList()
{
}

Table::~Table()
{
}

void Table::splitRow(int row)
{
	auto it=m_rowDataMap.lower_bound(row);
	if (it==m_rowDataMap.end() && !m_rowDataMap.empty())
	{
		it=m_rowDataMap.end();
		--it;
		if (it->first>row || it->first+it->second.m_repeated<=row)
			return;
	}
	if (it==m_rowDataMap.end() || it->first==row || it->first+it->second.m_repeated==row)
		return;
	TableRow newRow(it->second);
	it->second.m_repeated=row-it->first;
	newRow.m_repeated-=(row-it->first);
	m_rowDataMap[row]=newRow;
}

void Table::addCellInserted(RVNGABWVec2i const &cellPos, RVNGABWVec2i const &span)
{
	if (cellPos[1]<0)
	{
		RVNGABW_DEBUG_MSG(("Table::addCellInserted: the cellPos seens bad\n"));
		return;
	}
	if (span[1]<=0)
	{
		RVNGABW_DEBUG_MSG(("Table::addCellInserted: the span seens bad\n"));
		return;
	}
	splitRow(cellPos[1]);
	splitRow(cellPos[1]+span[1]);
	int prevRow=cellPos[1];
	RVNGABWVec2i cols(cellPos[0], cellPos[0]+span[0]-1);
	bool hasData=cellPos[0]>=0 && span[0]>0;
	while (prevRow<cellPos[1]+span[1])
	{
		auto it=m_rowDataMap.lower_bound(prevRow);
		if (it!=m_rowDataMap.end() && it->first==prevRow)
		{
			if (hasData)
				it->second.m_fillColumnSet.insert(cols);
			prevRow+=it->second.m_repeated;
			continue;
		}
		int row=cellPos[1]+span[1];
		if (it!=m_rowDataMap.end() && it->first<row)
			row=it->first;
		if (row<=prevRow)
		{
			RVNGABW_DEBUG_MSG(("Table::addCellInserted: argh something is bad\n"));
			break;
		}
		TableRow newRow(row-prevRow);
		if (hasData)
			newRow.m_fillColumnSet.insert(cols);
		m_rowDataMap[prevRow]=newRow;
		prevRow=row;
	}
}

bool Table::openRow(const librevenge::RVNGPropertyList &propList)
{
	if (m_isRowOpened)
	{
		RVNGABW_DEBUG_MSG(("Table::openRow: a row is already open\n"));
		return false;
	}
	m_isRowOpened=true;
	int numRepeated=1;
	if (propList["librevenge:row"])
		m_actualRow=propList["librevenge:row"]->getInt();
	if (propList["table:number-rows-repeated"] && propList["table:number-rows-repeated"]->getInt()>1)
		numRepeated=propList["table:number-rows-repeated"]->getInt();
	m_row.m_repeated=numRepeated;
	// force the creation of cells rows and update the maximun number of rows(if needed)
	addCellInserted(RVNGABWVec2i(0, m_actualRow), RVNGABWVec2i(0, numRepeated));
	if (!m_isSpreadsheet && m_actualRow+numRepeated-1>m_lastFillPosition[1])
		m_lastFillPosition[1]=m_actualRow+numRepeated-1;
	// retrieve the height
	double val, height=0;
	if ((propList["style:row-height"] && getInchValue(*propList["style:row-height"], val)) ||
	        (propList["style:min-row-height"] && getInchValue(*propList["style:min-row-height"], val)))
		height=val;
	// update the different height
	int prevRow=m_actualRow;
	while (prevRow<m_actualRow+numRepeated)
	{
		auto it=m_rowDataMap.lower_bound(prevRow);
		if (it==m_rowDataMap.end() || it->first!=prevRow)
		{
			RVNGABW_DEBUG_MSG(("Table::openRow: can not find a row\n"));
			break;
		}
		if (it->second.m_height>0)
		{
			RVNGABW_DEBUG_MSG(("Table::openRow: argh row %d height is set\n", prevRow));
		}
		else
			it->second.m_height=height;
		prevRow+=it->second.m_repeated;
	}
	return true;
}

bool Table::closeRow()
{
	if (!m_isRowOpened)
	{
		RVNGABW_DEBUG_MSG(("Table::closeRow: no row is open\n"));
		return false;
	}
	m_isRowOpened=false;
	m_actualRow+=m_row.m_repeated;
	return true;
}

void Table::checkTable()
{
	// add the empty delayed cell
	for (auto const &cell : m_delayedCellList)
	{
		for (int r=0; r<cell.m_repeated[1] ; ++r)
		{
			if (cell.m_position[1]+cell.m_span[1]-1+r>m_lastFillPosition[1])
				break;
			for (int c=0; c<cell.m_repeated[0]; ++c)
			{
				if (cell.m_position[0]+cell.m_span[0]-1+c>m_lastFillPosition[0])
					break;
				cell.addTo(m_tableContent, m_xId,RVNGABWVec2i(c,r));
				addCellInserted(cell.m_position+RVNGABWVec2i(c,r), cell.m_span);
			}
		}
	}

	librevenge::RVNGPropertyList cellList, cellPropList;
	// first set all border to none
	for (int i=0; i<4; ++i)
	{
		static char const *(what[])= {"left", "right", "top", "bot"};
		librevenge::RVNGString tmp;
		tmp.sprintf("%s-style", what[i]);
		cellPropList.insert(tmp.cstr(), 0);
	}
	std::map<int, TableRow>::const_iterator it;
	librevenge::RVNGPropertyList paraList;
	int actRow=0;
	for (it=m_rowDataMap.begin(); it!=m_rowDataMap.end() && actRow<=m_lastFillPosition[1]; ++it)
	{
		// first add the empty row
		for (; actRow<it->first && actRow<=m_lastFillPosition[1] ;)
		{
			cellPropList.insert("top-attach", (int) actRow);
			cellPropList.insert("bottom-attach", (int) ++actRow);
			for (int c=0; c<=m_lastFillPosition[0]; ++c)
			{
				cellList.insert("xid", ++m_xId);

				cellPropList.insert("left-attach", c);
				cellPropList.insert("right-attach", c+1);
				librevenge::RVNGPropertyListVector cellPropVector;
				cellPropVector.append(cellPropList);
				cellList.insert("props", cellPropVector);

				m_tableContent.push_back(std::make_shared<TagOpenElement>("cell", cellList));
				paraList.insert("xid", ++m_xId);
				m_tableContent.push_back(std::make_shared<TagOpenElement>("p", paraList));
				m_tableContent.push_back(std::make_shared<TagCloseElement>("p"));
				m_tableContent.push_back(std::make_shared<TagCloseElement>("cell"));
			}
		}

		auto const &row=it->second;
		for (; actRow<it->first+row.m_repeated  && actRow<=m_lastFillPosition[1];)
		{
			cellPropList.insert("top-attach", (int) actRow);
			cellPropList.insert("bottom-attach", (int) ++actRow);
			auto cIt=row.m_fillColumnSet.begin();
			for (int c=0; c<=m_lastFillPosition[0]; ++c)
			{
				if (cIt!=row.m_fillColumnSet.end() && c>=(*cIt)[0] && c<=(*cIt)[1])
					continue;

				if (cIt!=row.m_fillColumnSet.end() && c>(*cIt)[1])
				{
					++cIt;
					if (cIt!=row.m_fillColumnSet.end() && c>=(*cIt)[0] && c<=(*cIt)[1])
						continue;
				}
				cellList.insert("xid", ++m_xId);

				cellPropList.insert("left-attach", c);
				cellPropList.insert("right-attach", c+1);
				librevenge::RVNGPropertyListVector cellPropVector;
				cellPropVector.append(cellPropList);
				cellList.insert("props", cellPropVector);

				m_tableContent.push_back(std::make_shared<TagOpenElement>("cell", cellList));
				paraList.insert("xid", ++m_xId);
				m_tableContent.push_back(std::make_shared<TagOpenElement>("p", paraList));
				m_tableContent.push_back(std::make_shared<TagCloseElement>("p"));
				m_tableContent.push_back(std::make_shared<TagCloseElement>("cell"));
			}
		}
	}
}

bool Table::openCell(const librevenge::RVNGPropertyList &propList)
{
	if (!m_isRowOpened || m_isCellOpened)
	{
		RVNGABW_DEBUG_MSG(("Table::openCell: can not open a cell\n"));
		return false;
	}
	int spanCol=1, spanRow=1;
	if (propList["librevenge:column"])
		m_actualColumn=propList["librevenge:column"]->getInt();
	if (propList["librevenge:row"])
		m_actualRow=propList["librevenge:row"]->getInt();
	if (propList["table:number-columns-spanned"] && propList["table:number-columns-spanned"]->getInt()>1)
		spanCol=propList["table:number-columns-spanned"]->getInt();
	else if (propList["table:number-matrix-columns-spanned"] && propList["table:number-matrix-columns-spanned"]->getInt()>1)
		spanCol=propList["table:number-matrix-columns-spanned"]->getInt();
	if (propList["table:number-rows-spanned"] && propList["table:number-rows-spanned"]->getInt()>1)
		spanRow=propList["table:number-rows-spanned"]->getInt();
	else if (propList["table:number-matrix-rows-spanned"] && propList["table:number-matrix-rows-spanned"]->getInt()>1)
		spanRow=propList["table:number-matrix-rows-spanned"]->getInt();
	m_cell.clear();
	m_cell.m_position=RVNGABWVec2i(m_actualColumn,m_actualRow);
	m_cell.m_span=RVNGABWVec2i(spanCol,spanRow);
	if (propList["table:number-columns-repeated"] && propList["table:number-columns-repeated"]->getInt()>1)
		m_cell.m_repeated[0]=propList["table:number-columns-repeated"]->getInt();
	m_cell.m_repeated[1]=m_row.m_repeated;
	// other cell's data
	if (propList["fo:background-color"])
		m_cell.m_cellPropList.insert("background-color", propList["fo:background-color"]->getStr());
	appendBorderProperties(propList,m_cell.m_cellPropList,false);

	m_isCellOpened=true;
	if (!m_isSpreadsheet) return true;

	//
	// look if the propList contains a value, if so add it in the cell
	//
	if (!propList["librevenge:value-type"]) return true;

	// now we need to retrieve the value in proplist
	std::string valueType(propList["librevenge:value-type"]->getStr().cstr());
	if (valueType=="double" || valueType=="scientific") valueType="float";
	else if (valueType=="percent") valueType="percentage";

	std::stringstream s;
	s.imbue(std::locale("C")); // be sure that we use standart double
	if (propList["librevenge:value"] && (valueType=="float" || valueType=="percentage" || valueType=="currency"))
	{
		if (valueType=="percentage")
			s << 100. * propList["librevenge:value"]->getDouble() << "%";
		else
		{
			// if (valueType=="currency") m_impl->m_stream << "$";
			s << propList["librevenge:value"]->getDouble();
		}
	}
	else if (propList["librevenge:value"] && (valueType=="bool" || valueType=="boolean"))
	{
		if (propList["librevenge:value"]->getDouble()<0||propList["librevenge:value"]->getDouble()>0)
			s<< "true";
		else
			s<< "false";
	}
	else if (valueType=="date" || valueType=="time")
	{
		// checkme
		struct tm time;
		time.tm_sec=time.tm_min=time.tm_hour=0;
		time.tm_mday=time.tm_mon=1;
		time.tm_year=100;
		time.tm_wday=time.tm_yday=time.tm_isdst=-1;
		char buf[256];
		if (valueType=="date")
		{
			time.tm_mday=propList["librevenge:day"] ? propList["librevenge:day"]->getInt() : 1;
			time.tm_mon=propList["librevenge:month"] ? propList["librevenge:month"]->getInt()-1 : 0;
			time.tm_year=propList["librevenge:year"] ? propList["librevenge:year"]->getInt()-1900 : 100;
			// changeme: use numbering format
#pragma GCC diagnostic ignored "-Wformat-y2k"
			if (mktime(&time)!=-1 && strftime(buf, 256, "%m/%d/%y", &time))
				s << buf;
		}
		else
		{
			time.tm_hour=propList["librevenge:hours"] ? propList["librevenge:hours"]->getInt() : 0;
			time.tm_min=propList["librevenge:minutes"] ? propList["librevenge:minutes"]->getInt() : 0;
			time.tm_sec=propList["librevenge:seconds"] ? propList["librevenge:seconds"]->getInt() : 0;
			// changeme: use numbering format
			if (mktime(&time)!=-1 && strftime(buf, 256, "%H:%M:%S", &time))
				s << buf;
		}
	}
	else if (valueType!="string" && valueType!="text")
	{
		RVNGABW_DEBUG_MSG(("Table::openCell: unexpected value type: %s\n", valueType.c_str()));
		return true;
	}
	if (s.str().empty())
		return true;

	auto &content=m_cell.m_content;
	librevenge::RVNGPropertyList paraList;
	if (propList["fo:text-align"])
		paraList.insert("fo:text-align", propList["fo:text-align"]->getStr());
	auto sName= m_manager.getSpanManager().findOrAdd(paraList);
	std::shared_ptr<TagOpenElement> pParaOpenElement(new TagOpenElement("p"));
	pParaOpenElement->addAttribute("style", sName);
	pParaOpenElement->addAttribute("xid", ++m_xId);
	content.push_back(pParaOpenElement);

	sName= m_manager.getSpanManager().findOrAdd(propList);
	std::shared_ptr<TagOpenElement> pSpanOpenElement(new TagOpenElement("c"));
	pSpanOpenElement->addAttribute("style", sName);
	pSpanOpenElement->addAttribute("xid", ++m_xId);
	content.push_back(pSpanOpenElement);
	content.push_back(std::make_shared<TextElement>(s.str().c_str()));
	content.push_back(std::make_shared<TagCloseElement>("c"));

	content.push_back(std::make_shared<TagCloseElement>("p"));
	return true;
}

bool Table::closeCell()
{
	if (!m_isCellOpened)
	{
		RVNGABW_DEBUG_MSG(("Table::closeCell: no cell are opened\n"));
		return false;
	}
	m_isCellOpened=false;
	m_actualColumn+=m_cell.m_repeated[0]+(m_cell.m_span[0]-1);

	RVNGABWVec2i rbPos=m_cell.m_position+m_cell.m_span+m_cell.m_repeated-RVNGABWVec2i(2,2);
	if (m_isSpreadsheet && m_cell.isEmpty() && (rbPos[0]>m_lastFillPosition[0] || rbPos[1]>m_lastFillPosition[1]))
	{
		m_delayedCellList.push_back(m_cell);
		return true;
	}

	addCellInserted(m_cell.m_position, m_cell.m_repeated+m_cell.m_span-RVNGABWVec2i(1,1));
	if (rbPos[0]>m_lastFillPosition[0])
		m_lastFillPosition[0]=rbPos[0];
	if (rbPos[1]>m_lastFillPosition[1])
		m_lastFillPosition[1]=rbPos[1];
	for (int r=0; r<m_cell.m_repeated[1]; ++r)
	{
		for (int c=0; c<m_cell.m_repeated[0]; ++c)
		{
			m_cell.addTo(m_tableContent, m_xId,RVNGABWVec2i(c,r));
		}
	}
	return true;
}

void Table::write(DocumentElementVector &storage) const
{
	librevenge::RVNGPropertyList propList, propPropList;
	propList.insert("xid", ++m_xId);

	if (m_propList["table:align"] && m_propList["table:align"]->getStr()=="margins" && m_propList["fo:margin-left"])
		propPropList.insert("table-column-leftpos", m_propList["fo:margin-left"]->getStr());

	const auto columns = m_propList.child("librevenge:table-columns");
	if (columns && columns->count())
	{
		librevenge::RVNGString cols;
		int actCol=0;
		for (unsigned long i=0; i<columns->count(); ++i)
		{
			int numRepeated=1;
			if (propList["table:number-columns-repeated"] && propList["table:number-columns-repeated"]->getInt()>1)
				numRepeated=propList["table:number-columns-repeated"]->getInt();
			for (int c=0; c<numRepeated && actCol<=m_lastFillPosition[0]; ++c, ++actCol)
			{
				if ((*columns)[i]["style:column-width"])
					cols.append((*columns)[i]["style:column-width"]->getStr());
				cols.append("/");
			}
			if (actCol>m_lastFillPosition[0])
				break;
		}
		propPropList.insert("table-column-props", cols);
	}
	bool hasRowHeights=false;
	std::map<int, TableRow>::const_iterator rIt;
	for (rIt=m_rowDataMap.begin(); rIt!=m_rowDataMap.end(); ++rIt)
	{
		if (rIt->second.m_height<=0) continue;
		hasRowHeights=true;
		break;
	}
	if (hasRowHeights)
	{
		librevenge::RVNGString rows;
		int actRow=0;
		hasRowHeights=false;
		for (rIt=m_rowDataMap.begin(); rIt!=m_rowDataMap.end() && actRow<=m_lastFillPosition[1]; ++rIt)
		{
			for (; actRow<rIt->first  && actRow<=m_lastFillPosition[1] ; ++actRow)  // empty row
				rows.append("/");
			for (; actRow<rIt->first+rIt->second.m_repeated  && actRow<=m_lastFillPosition[1] ; ++actRow)
			{
				if (rIt->second.m_height>0)
				{
					std::stringstream s;
					s.imbue(std::locale("C")); // be sure that we use standart double
					s << rIt->second.m_height << "in";
					rows.append(s.str().c_str());
					hasRowHeights=true;
				}
				rows.append("/");
			}
		}
		if (hasRowHeights)
			propPropList.insert("table-row-heights", rows);
	}
	if (!propPropList.empty())
	{
		librevenge::RVNGPropertyListVector propPropVector;
		propPropVector.append(propPropList);
		propList.insert("props", propPropVector);
	}
	storage.push_back(std::make_shared<TagOpenElement>("table", propList));
	librvngabw::spliceOrigDest(const_cast<DocumentElementVector &>(m_tableContent), storage);
	storage.push_back(std::make_shared<TagCloseElement>("table"));
}

bool Table::insertCoveredCell(const librevenge::RVNGPropertyList &propList)
{
	if (!m_isRowOpened || m_isCellOpened)
	{
		RVNGABW_DEBUG_MSG(("Table::insertCoveredCell: can not open a cell\n"));
		return false;
	}
	if (propList["librevenge:column"])
		m_actualColumn=propList["librevenge:column"]->getInt();
	if (propList["librevenge:row"])
		m_actualRow=propList["librevenge:row"]->getInt();
	return true;
}

TableManager::TableManager(SpanStyleManager &spanManager, ParagraphStyleManager &paragraphManager) :
	m_spanManager(spanManager), m_paragraphManager(paragraphManager), m_tableOpenedList()
{
}

TableManager::~TableManager()
{
}

void TableManager::clean()
{
	m_tableOpenedList.clear();
}

std::shared_ptr<Table> TableManager::openTable(const librevenge::RVNGPropertyList &xPropList, int &xId, bool isSpreadsheet)
{
	std::shared_ptr<Table> table(new Table(*this, xPropList, xId, isSpreadsheet));
	m_tableOpenedList.push_back(table);
	return table;
}

bool TableManager::closeTable()
{
	if (m_tableOpenedList.empty())
	{
		RVNGABW_DEBUG_MSG(("TableManager::oops: no table are opened\n"));
		return false;
	}
	m_tableOpenedList.pop_back();
	return true;
}

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