/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* libwps
 * 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) 2006, 2007 Andrew Ziem
 * Copyright (C) 2004 Marc Maurer (uwog@uwog.net)
 * Copyright (C) 2004-2006 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.
 */

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

#include <librevenge-stream/librevenge-stream.h>

#include "libwps_internal.h"
#include "libwps_tools_win.h"

#include "WPSContentListener.h"
#include "WPSEntry.h"
#include "WPSHeader.h"
#include "WPSOLEParser.h"
#include "WPSPageSpan.h"
#include "WPSPosition.h"
#include "WPSTextSubDocument.h"

#include "WPS8Graph.h"
#include "WPS8Struct.h"
#include "WPS8Table.h"
#include "WPS8Text.h"

#include "WPS8.h"

namespace WPS8ParserInternal
{
//! Internal: the subdocument of a WPS8Parser
class SubDocument final : public WPSTextSubDocument
{
public:
	//! type of an entry
	enum Type { Unknown, TEXT };
	//! constructor for a text entry
	SubDocument(RVNGInputStreamPtr const &input, WPS8Parser &pars, WPSEntry const &entry)
		: WPSTextSubDocument(input, &pars)
		, m_entry(entry) {}
	//! destructor
	~SubDocument() final {}

	//! operator==
	bool operator==(std::shared_ptr<WPSSubDocument> const &doc) const final
	{
		if (!doc || !WPSTextSubDocument::operator==(doc))
			return false;
		auto const *sDoc = dynamic_cast<SubDocument const *>(doc.get());
		if (!sDoc) return false;
		return m_entry == sDoc->m_entry;
	}

	//! the parser function
	void parse(std::shared_ptr<WPSContentListener> &listener, libwps::SubDocumentType subDocumentType) final;
	//! the entry
	WPSEntry m_entry;
};

void SubDocument::parse(std::shared_ptr<WPSContentListener> &listener, libwps::SubDocumentType subDocumentType)
{
	if (!listener.get())
	{
		WPS_DEBUG_MSG(("WPS8ParserInternal::SubDocument::parse: no listener\n"));
		return;
	}
	if (!dynamic_cast<WPSContentListener *>(listener.get()))
	{
		WPS_DEBUG_MSG(("WPS8ParserInternal::SubDocument::parse: bad listener\n"));
		return;
	}

	auto &listen =  static_cast<WPSContentListenerPtr &>(listener);
	if (!m_parser)
	{
		listen->insertCharacter(' ');
		WPS_DEBUG_MSG(("WPS8ParserInternal::SubDocument::parse: bad parser\n"));
		return;
	}

	if (m_entry.isParsed() && subDocumentType != libwps::DOC_HEADER_FOOTER)
	{
		listen->insertCharacter(' ');
		WPS_DEBUG_MSG(("WPS8ParserInternal::SubDocument::parse: this zone is already parsed\n"));
		return;
	}
	m_entry.setParsed(true);
	if (m_entry.type() != "Text")
	{
		listen->insertCharacter(' ');
		WPS_DEBUG_MSG(("WPS8ParserInternal::SubDocument::parse: send not Text entry is not implemented\n"));
		return;
	}

	if (!m_entry.valid())
	{
		WPS_DEBUG_MSG(("WPS8ParserInternal::SubDocument::parse: empty document found...\n"));
		listen->insertCharacter(' ');
		return;
	}


	auto *mnParser = dynamic_cast<WPS8Parser *>(m_parser);
	if (!mnParser)
	{
		WPS_DEBUG_MSG(("WPS8ParserInternal::SubDocument::parse: can not find a WPS8Parser...\n"));
		listen->insertCharacter(' ');
		return;
	}
	mnParser->send(m_entry);
}

/** Internal: a frame, a zone which can contain text, picture, ... and have some borders */
struct Frame
{
	//! constructor
	Frame() : m_parsed(false), m_type(UNKNOWN), m_pos(),
		m_idStrs(-1), m_idObject(-1), m_idTable(-1), m_idOle(-1), m_columns(1), m_idBorder(),
		m_backgroundColor(WPSColor::white()), m_error("")
	{
		m_pos.setRelativePosition(WPSPosition::Page);
		m_pos.setPage(1);
	}

	//! operator <<
	friend std::ostream &operator<<(std::ostream &o, Frame const &ft);

	/** The frame type
	 *
	 * - Header/Footer: the header and footer frame
	 * - Table : a table
	 * - Object : a picture, ...
	 * - Text : a text zone */
	enum { UNKNOWN = 0, DbField, Header, Footer, Table, Object, Text };

	//! a flag to know if the frame is already sent to the listener
	mutable bool m_parsed;
	//! the frame type
	int m_type;
	//! the frame position
	WPSPosition m_pos;
	int m_idStrs /** identifier corresponding to a text zone (STRS)*/,
	    m_idObject /** identifier corresponding to an object zone (EOBJ)*/,
	    m_idTable /** identifier corresponding to a table (MCLD)*/,
	    m_idOle /** identifier corresponding to an ole*/;
	//! the number of columns for a textbox, ...
	int m_columns;
	//! the border: an entry to some complex border (if sets)
	WPSEntry m_idBorder;
	//! the border's color
	WPSColor m_backgroundColor;
	//! a string used to store the parsing errors
	std::string m_error;
};

//! operator<< for a Frame
std::ostream &operator<<(std::ostream &o, Frame const &ft)
{
	switch (ft.m_type)
	{
	case Frame::DbField:
		o<< "db[field]";
		break;
	case Frame::Header:
		o<< "header";
		break;
	case Frame::Footer:
		o<< "footer";
		break;
	case Frame::Table:
		o<< "table";
		break;
	case Frame::Text:
		o<< "textbox";
		break;
	case Frame::Object:
		o<< "object";
		break;
	default:
		o << "###type=unknown";
		break;
	}
	o << "(";
	if (ft.m_idStrs >= 0) o << "STRS" << ft.m_idStrs << ",";
	if (ft.m_idObject >= 0) o << "EOBJ" << ft.m_idObject << ",";
	if (ft.m_idTable >= 0) o << "MCLD/Table" << ft.m_idTable << ",";
	if (ft.m_idOle >= 0) o << "oleId=" << ft.m_idOle << ",";
	o << "),";

	o << ft.m_pos << ",";
	switch (ft.m_pos.page())
	{
	case -1:
		o << "allpages,";
		break;
	case -2:
		break; // undef
	default:
		if (ft.m_pos.page() < 0) o << "###page=" << ft.m_pos.page() << ",";
	}

	if (ft.m_columns != 1) o << ft.m_columns << "columns,";
	if (ft.m_idBorder.valid())
		o << "border='" << ft.m_idBorder.name() << "':" << ft.m_idBorder.id() << ",";
	if (!ft.m_backgroundColor.isWhite())
		o << "backColor=" << ft.m_backgroundColor << ",";

	if (!ft.m_error.empty()) o << "errors=(" << ft.m_error << ")";
	return o;
}

//! the state of WPS8
struct State
{
	State()
		: m_eof(-1)
		, m_pageSpan()
		, m_localeLanguage("")
		, m_background()
		, m_frameList()
		, m_object2FrameMap()
		, m_table2FrameMap()
		, m_docPropertyTypes()
		, m_frameTypes()
		, m_numColumns(1)
		, m_actPage(0)
		, m_numPages(0)
	{
		initTypeMaps();
	}
	//! initializes the type map
	void initTypeMaps();
	//! the end of file
	long m_eof;
	//! the actual document size
	WPSPageSpan m_pageSpan;
	//! the language
	std::string m_localeLanguage;
	//! an identificator to design a background picture
	WPSEntry m_background;

	//! the frame's list
	std::vector<Frame> m_frameList;
	//! a map m_idObject -> frame
	std::map<int, int> m_object2FrameMap;
	//! a map m_idTable -> frame
	std::map<int, int> m_table2FrameMap;

	//! the document property type
	std::map<int,int> m_docPropertyTypes;
	//! the frame type
	std::map<int,int> m_frameTypes;
	int m_numColumns /** the number of columns */;
	int m_actPage /** the actual page*/, m_numPages /* the number of pages */;
};

void State::initTypeMaps()
{
	static int const docTypes[] =
	{
		0, 0x22, 1, 0x22, 2, 0x22, 3, 0x22, 4, 0x22, 5, 0x22, 6, 0x22, 7, 0x22,
		8, 0x1a, 0xa, 0x2,
		0x13, 0x2a, 0x15, 0x2a,
		0x18, 0x12, 0x19, 0x2, 0x1b, 0x12, 0x1c, 0x22, 0x1e, 0x22,
		0x26, 0x22, 0x27, 0x22,
		0x28, 0x22, 0x29, 0x22, 0x2a, 0x22, 0x2b, 0x22, 0x2c, 0x82, 0x2d, 0x82, 0x2e, 0x82
	};
	for (int i = 0; i+1 < int(WPS_N_ELEMENTS(docTypes)); i+=2)
		m_docPropertyTypes[docTypes[i]] = docTypes[i+1];
	static int const frameTypes[] =
	{
		0, 0x1a, 1, 0x12, 2, 0x12, 3, 0x2, 4, 0x22, 5, 0x22, 6, 0x22, 7, 0x22,
		8, 0x22, 9, 0x22, 0xa, 0x22,
		0x10, 0x2a, 0x11, 0x82, 0x13, 0x12, 0x14, 0x12, 0x16, 0x2a, 0x17, 0x2,
		0x18, 0x22, 0x19, 0x22, 0x1a, 0x12, 0x1b, 0x22, 0x1d, 0x22, 0x1e, 0x22, 0x1f, 0x22,
		0x20, 0x22, 0x26, 0x22,
		0x2a, 0x22, 0x2c, 0x1a, 0x2d, 0x1a, 0x2e, 0x22, 0x2f, 0x2,
		0x30, 0x22
	};
	for (int i = 0; i+1 < int(WPS_N_ELEMENTS(frameTypes)); i+=2)
		m_frameTypes[frameTypes[i]] = frameTypes[i+1];

}

class TableRecursionGuard
{
	// disable copying
	TableRecursionGuard(const TableRecursionGuard &) = delete;
	TableRecursionGuard &operator=(const TableRecursionGuard &) = delete;

public:
	TableRecursionGuard(std::deque<int> &stack, const int id)
		: m_stack(stack)
	{
		m_stack.push_front(id);
	}

	~TableRecursionGuard()
	{
		m_stack.pop_front();
	}

private:
	std::deque<int> &m_stack;
};


}

// constructor, destructor
WPS8Parser::WPS8Parser(RVNGInputStreamPtr &input, WPSHeaderPtr &header)
	: WPSParser(input, header)
	, m_listener()
	, m_graphParser()
	, m_tableParser()
	, m_textParser()
	, m_state()
	, m_sendingTables()
{
	if (version()<5) setVersion(5);
	m_state.reset(new WPS8ParserInternal::State);
	m_graphParser.reset(new WPS8Graph(*this));
	m_tableParser.reset(new WPS8Table(*this));
	m_textParser.reset(new WPS8Text(*this));
}

WPS8Parser::~WPS8Parser()
{
}

// small funtion ( dimension, ...)
float WPS8Parser::pageHeight() const
{
	return float(m_state->m_pageSpan.getFormLength()-m_state->m_pageSpan.getMarginTop()-m_state->m_pageSpan.getMarginBottom());
}

float WPS8Parser::pageWidth() const
{
	return float(m_state->m_pageSpan.getFormWidth()-m_state->m_pageSpan.getMarginLeft()-m_state->m_pageSpan.getMarginRight());
}

int WPS8Parser::numColumns() const
{
	return m_state->m_numColumns;
}

bool WPS8Parser::checkInFile(long pos)
{
	if (pos <= m_state->m_eof)
		return true;
	RVNGInputStreamPtr input = getInput();
	long actPos = input->tell();
	input->seek(pos, librevenge::RVNG_SEEK_SET);
	bool ok = input->tell() == pos;
	if (ok) m_state->m_eof = pos;
	input->seek(actPos, librevenge::RVNG_SEEK_SET);
	return ok;
}

// listener, new page
void WPS8Parser::setListener(std::shared_ptr<WPSContentListener> const &listener)
{
	m_listener = listener;
	m_graphParser->setListener(m_listener);
	m_tableParser->setListener(m_listener);
	m_textParser->setListener(m_listener);
}

std::shared_ptr<WPSContentListener> WPS8Parser::createListener(librevenge::RVNGTextInterface *interface)
{
	std::vector<WPSPageSpan> pageList;
	WPSPageSpan ps(m_state->m_pageSpan);

	int numPages = 1;
	for (auto const &frame : m_state->m_frameList)
	{
		if (frame.m_pos.page() > numPages)
			numPages = frame.m_pos.page();
	}

	int textPages = m_textParser->numPages();
	if (textPages > numPages) numPages = textPages;
	int tablePages = m_tableParser->numPages();
	if (tablePages > numPages) numPages = tablePages;
	int graphPages = m_graphParser->numPages();
	if (graphPages>=numPages) numPages = graphPages;

	WPSEntry entry = m_textParser->getHeaderEntry();
	if (entry.valid())
	{
		WPSSubDocumentPtr subdoc(new WPS8ParserInternal::SubDocument
		                         (getInput(), *this, entry));
		ps.setHeaderFooter(WPSPageSpan::HEADER, WPSPageSpan::ALL, subdoc);
	}

	entry = m_textParser->getFooterEntry();
	if (entry.valid())
	{
		WPSSubDocumentPtr subdoc(new WPS8ParserInternal::SubDocument
		                         (getInput(), *this, entry));
		ps.setHeaderFooter(WPSPageSpan::FOOTER, WPSPageSpan::ALL, subdoc);
	}
#ifdef DEBUG
	// create all the pages + an empty page, if we have some remaining data...
	numPages++;
#endif

	pageList.push_back(ps);
	for (int i = 1; i < numPages; i++) pageList.push_back(ps);
	m_state->m_numPages=numPages;
	return std::shared_ptr<WPSContentListener>
	       (new WPSContentListener(pageList, interface));
}

void WPS8Parser::newPage(int number)
{
	if (number <= m_state->m_actPage || number > m_state->m_numPages)
		return;

	while (m_state->m_actPage < number)
	{
		m_state->m_actPage++;

		if (m_listener.get() == nullptr || m_state->m_actPage == 1)
			continue;
		m_listener->insertBreak(WPS_PAGE_BREAK);
		m_graphParser->sendObjects(m_state->m_actPage);
	}
}

////////////////////////////////////////////////////////////
// interface with the graph/text parser
void WPS8Parser::send(WPSEntry const &entry)
{
	RVNGInputStreamPtr input = getInput();
	long actPos = input->tell();
	m_textParser->readText(entry);
	input->seek(actPos, librevenge::RVNG_SEEK_SET);
}

void WPS8Parser::send(int strsId)
{
	send(m_textParser->getEntry(strsId));
}

void WPS8Parser::sendTextInCell(int strsId, int cellId)
{
	RVNGInputStreamPtr input = getInput();
	long actPos = input->tell();
	m_textParser->readTextInCell(strsId,cellId);
	input->seek(actPos, librevenge::RVNG_SEEK_SET);
}

void WPS8Parser::sendTextBox(WPSPosition const &pos, int strsid, librevenge::RVNGPropertyList frameExtras)
{
	if (!m_listener)
	{
		WPS_DEBUG_MSG(("WPS8Parser::sendTextBox can not find the listener\n"));
		return;
	}
	WPSEntry entry = m_textParser->getEntry(strsid);
	WPSSubDocumentPtr subdoc(new WPS8ParserInternal::SubDocument
	                         (getInput(), *this, entry));
	m_listener->insertTextBox(pos, subdoc, frameExtras);
}

bool WPS8Parser::sendObject(Vec2f const &size, int objectId, bool ole)
{
	WPSPosition posi(Vec2f(),size);
	posi.setRelativePosition(WPSPosition::CharBaseLine);
	posi.m_wrapping = WPSPosition::WDynamic;
	m_graphParser->sendObject(posi,objectId,ole);
	return true;
}

bool WPS8Parser::sendTable(Vec2f const &size, int objectId)
{
	if (find(m_sendingTables.begin(), m_sendingTables.end(), objectId) != m_sendingTables.end())
	{
		WPS_DEBUG_MSG(("WPS8Parser::sendTable table %d is contained in itself\n", objectId));
		return false;
	}

	auto pos = m_state->m_object2FrameMap.find(objectId);
	if (pos == m_state->m_object2FrameMap.end())
	{
		WPS_DEBUG_MSG(("WPS8Parser::sendTable can not find the table %d \n", objectId));
		return false;
	}

	auto const &frame = m_state->m_frameList[(size_t)pos->second];
	if (frame.m_idStrs < 0)
	{
		WPS_DEBUG_MSG(("WPS8Parser:sendTable can not find the text zone \n"));
		return false;
	}

	if (frame.m_idTable < 0)
	{
		WPSPosition position(Vec2f(), size);
		position.m_anchorTo = WPSPosition::CharBaseLine; // CHECKME
		position.m_wrapping = WPSPosition::WDynamic;

		WPS_DEBUG_MSG(("WPS8Parser:sendTable can not find the table zone \n"));
		sendTextBox(position, frame.m_idStrs);
		return true;
	}
	frame.m_parsed = true;
	WPS8ParserInternal::TableRecursionGuard guard(m_sendingTables, objectId);
	return m_tableParser->sendTable(size, frame.m_idTable, frame.m_idStrs);
}

int WPS8Parser::getTableSTRSId(int tableId) const
{
	auto pos = m_state->m_table2FrameMap.find(tableId);
	// probably ok: checkme
	if (pos == m_state->m_table2FrameMap.end())
		return -1;
	auto const &frame = m_state->m_frameList[(size_t)pos->second];
	if (frame.m_idStrs < 0)
	{
		WPS_DEBUG_MSG(("WPS8Parser:getTableSTRSId can not find the text zone \n"));
	}
	return frame.m_idStrs;
}

////////////////////////////////////////////////////////////
// main function to parse the document
void WPS8Parser::parse(librevenge::RVNGTextInterface *documentInterface)
{
	RVNGInputStreamPtr input=getInput();
	if (!input)
	{
		WPS_DEBUG_MSG(("WPS8Parser::parse: does not find main ole\n"));
		throw (libwps::ParseException());
	}

	try
	{
		createOLEStructures();
	}
	catch (...)
	{
		WPS_DEBUG_MSG(("WPS8Parser::parse: exception catched when parsing secondary OLEs\n"));
	}

	ascii().setStream(input);
	ascii().open("CONTENTS");
	try
	{
		if (!createStructures()) throw(libwps::ParseException());
	}
	catch (...)
	{
		WPS_DEBUG_MSG(("WPS8Parser::parse: exception catched when parsing MN0\n"));
		throw (libwps::ParseException());
	}
	setListener(createListener(documentInterface));
	if (!m_listener)
	{
		WPS_DEBUG_MSG(("WPS8Parser::parse: can not create the listener\n"));
		throw (libwps::ParseException());
	}
	m_listener->startDocument();
	sendPageFrames();
	WPSEntry ent = m_textParser->getTextEntry();
	if (ent.valid())
		m_textParser->readText(ent);
	else
	{
		WPS_DEBUG_MSG(("WPS8Parser::parse: can not find main text entry\n"));
		throw (libwps::ParseException());
	}

#ifdef DEBUG
	m_tableParser->flushExtra();
#endif
	m_textParser->flushExtra();
#ifdef DEBUG
	m_graphParser->sendObjects(-1);
#endif

	m_listener->endDocument();
	m_listener.reset();

	ascii().reset();
}

// find and create all the zones ( normal/ole )
bool WPS8Parser::createStructures()
{
	if (!getInput()) return false;

	RVNGInputStreamPtr input = getInput();
	parseHeaderIndex();

	// initialize the text, table, ..
	if (!m_textParser->readStructures())
		return false;
	m_graphParser->readStructures(input);
	m_tableParser->readStructures(input);

	// read DOP zone (document properties)
	auto pos = getNameEntryMap().lower_bound("DOP ");
	while (getNameEntryMap().end() != pos)
	{
		WPSEntry const &entry = pos++->second;
		if (!entry.hasName("DOP ")) break;
		if (!entry.hasType("DOP ")) continue;

		WPSPageSpan page;
		if (readDocProperties(entry, page)) m_state->m_pageSpan = page;
	}

	// printer data
	pos = getNameEntryMap().lower_bound("PRNT");
	while (getNameEntryMap().end() != pos)
	{
		WPSEntry const &entry = pos++->second;
		if (!entry.hasName("PRNT")) break;
		if (!entry.hasType("WNPR")) continue;

		readWNPR(entry);
	}

	// SYID
	pos = getNameEntryMap().lower_bound("SYID");
	while (getNameEntryMap().end() != pos)
	{
		WPSEntry const &entry = pos++->second;
		if (!entry.hasName("SYID")) break;
		if (!entry.hasType("SYID")) continue;

		std::vector<int> listId;
		readSYID(entry, listId);
	}

	// document title
	pos = getNameEntryMap().find("TITL");
	librevenge::RVNGString title;
	if (getNameEntryMap().end() != pos && pos->second.hasType("TITL"))
	{
		pos->second.setParsed(true);
		input->seek(pos->second.begin(), librevenge::RVNG_SEEK_SET);
		m_textParser->readString(input, pos->second.length(), title);
		ascii().addPos(pos->second.begin());
		ascii().addNote(title.cstr());
	}

	// ok, we can now read the frame
	pos = getNameEntryMap().lower_bound("FRAM");
	while (getNameEntryMap().end() != pos)
	{
		WPSEntry const &entry = pos++->second;
		if (!entry.hasName("FRAM")) break;
		if (!entry.hasType("FRAM")) continue;

		readFRAM(entry);
	}
	// FRCD
	pos = getNameEntryMap().lower_bound("FRCD");
	while (getNameEntryMap().end() != pos)
	{
		WPSEntry const &entry = pos++->second;
		if (!entry.hasName("FRCD")) break;
		if (!entry.hasType("FRCD")) continue;
		readFRCD(entry);
	}

	// creates the correspondance between the eobj and the frame
	size_t numFrames = m_state->m_frameList.size();
	for (size_t i = 0; i < numFrames; i++)
	{
		WPS8ParserInternal::Frame const &frame = m_state->m_frameList[i];
		if (frame.m_idObject < 0) continue;
		m_state->m_object2FrameMap[frame.m_idObject] = (int) i;
		m_state->m_table2FrameMap[frame.m_idTable] = (int) i;
	}

	m_graphParser->computePositions();

	return true;
}

bool WPS8Parser::createOLEStructures()
{
	RVNGInputStreamPtr input=getFileInput();
	if (!input) return false;

	if (!input->isStructured()) return true;
	WPSOLEParser oleParser("CONTENTS", libwps_tools_win::Font::WIN3_WEUROPE);
	if (!oleParser.parse(input)) return false;

	m_graphParser->storeObjects(oleParser.getObjectsMap());
#ifdef DEBUG
	// there can remain some embedded Works subdocument ( WKS, ... )
	// with name MN0 and some unknown picture ole
	auto unparsed = oleParser.getNotParse();

	for (std::string const &name : unparsed)
	{
		if (name == "CONTENTS")
			continue;
		if (name== "SPELLING")
		{
			RVNGInputStreamPtr ole(input->getSubStreamByName(name.c_str()));
			if (ole && readSPELLING(ole, name))
				continue;
		}
		WPS_DEBUG_MSG(("WPS8Parser::createOLEStructures:: Find unparsed ole: %s\n", name.c_str()));

#ifdef DEBUG_WITH_FILES
		RVNGInputStreamPtr ole(input->getSubStreamByName(name.c_str()));
		if (!ole.get())
		{
			WPS_DEBUG_MSG(("WPS8Parser::createOLEStructures: error: can find OLE part: \"%s\"\n", name.c_str()));
			continue;
		}

		librevenge::RVNGBinaryData data;
		if (libwps::readDataToEnd(ole,data))
			libwps::Debug::dumpFile(data, name.c_str());
#endif
	}
#endif
	return true;
}

////////////////////////////////////////////////////////////
// send the frames which correspond to a page
////////////////////////////////////////////////////////////
void WPS8Parser::sendPageFrames()
{
	if (!m_listener) return;

	RVNGInputStreamPtr input = getInput();
	long actPos = input->tell();

	for (auto const &frame : m_state->m_frameList)
	{
		if (frame.m_parsed) continue;
		if (frame.m_pos.page() < 0 || frame.m_idObject != -1) continue;
		if (frame.m_pos.size()[0] <= 0 || frame.m_pos.size()[1] <= 0) continue;

		WPSPosition pos(frame.m_pos);
		if (pos.origin().y() < 0 || pos.origin().x() < 0)
		{
			if (frame.m_type == WPS8ParserInternal::Frame::DbField) continue;
			// FIXME: the
			WPS_DEBUG_MSG(("WPS8Parser::insertPageFrames: origin is too small\n"));
			Vec2f orig = pos.origin();
			orig += Vec2f(float(m_state->m_pageSpan.getMarginLeft()),
			              float(m_state->m_pageSpan.getMarginTop()));
			pos.setOrigin(orig);
		}
		pos.m_anchorTo = WPSPosition::Page;
		pos.m_wrapping = WPSPosition::WDynamic;
		frame.m_parsed = true;
		if (frame.m_type == WPS8ParserInternal::Frame::Object)
			m_graphParser->sendObject(pos, frame.m_idOle, true);
		else if (frame.m_type == WPS8ParserInternal::Frame::Text)
		{
			librevenge::RVNGPropertyList frameExtras;
			if (!frame.m_backgroundColor.isWhite())
				frameExtras.insert("fo:background-color", frame.m_backgroundColor.str().c_str());
			sendTextBox(pos, frame.m_idStrs, frameExtras);
		}
		else if (frame.m_type != WPS8ParserInternal::Frame::Header && frame.m_type != WPS8ParserInternal::Frame::Footer)
		{
			WPS_DEBUG_MSG(("WPS8Parser::insertPageFrames: not implemented type%d\n", int(frame.m_type)));
		}
	}

	input->seek(actPos,librevenge::RVNG_SEEK_SET);
}

////////////////////////////////////////////////////////////
// read the index
bool WPS8Parser::parseHeaderIndexEntry()
{
	RVNGInputStreamPtr input = getInput();
	long pos = input->tell();
	ascii().addPos(pos);

	libwps::DebugStream f;

	uint16_t cch = libwps::readU16(input);

	// check if the entry can be read
	input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
	if (input->tell() != pos+cch)
	{
		WPS_DEBUG_MSG(("WPS8Parser::parseHeaderIndexEntry: error: incomplete entry\n"));
		ascii().addNote("###IndexEntry incomplete (ignored)");
		return false;
	}
	input->seek(pos + 2, librevenge::RVNG_SEEK_SET);

	if (0x18 != cch)
	{
		if (cch < 0x18)
		{
			input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
			ascii().addNote("###IndexEntry too short(ignored)");
			if (cch < 10) throw libwps::ParseException();
			return true;
		}
	}

	std::string name;

	// sanity check
	int i;
	for (i =0; i < 4; i++)
	{
		uint8_t c = libwps::readU8(input);
		name.append(1, char(c));

		if (c != 0 && c != 0x20 && (41 > c || c > 90))
		{
			WPS_DEBUG_MSG(("WPS8Parser::parseHeaderIndexEntry: error: bad character=%u (0x%02x) in name in header index\n", (unsigned)c, (unsigned)c));
			ascii().addNote("###IndexEntry bad name(ignored)");

			input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
			return true;
		}
	}

	f << "Entries("<<name<<")";
	if (cch != 24) f << ", #size=" << int(cch);
	auto id = (int) libwps::readU16(input);
	f << ", id=" << id << ", (";
	for (i = 0; i < 2; i ++)
	{
		auto val = (int) libwps::read16(input);
		f << val << ",";
	}

	std::string name2;
	for (i =0; i < 4; i++)
		name2.append(1, (char) libwps::readU8(input));
	f << "), " << name2;

	WPSEntry hie;
	hie.setName(name);
	hie.setType(name2);
	hie.setId(id);
	hie.setBegin((long) libwps::readU32(input));
	hie.setLength((long) libwps::readU32(input));

	f << ", offset=" << std::hex << hie.begin() << ", length=" << hie.length();

	std::string mess;
	if (cch != 0x18 && parseHeaderIndexEntryEnd(pos+cch, hie, mess))
		f << "," << mess;

	input->seek(hie.end(), librevenge::RVNG_SEEK_SET);
	if (input->tell() != hie.end())
	{
		f << ", ###ignored";
		ascii().addNote(f.str().c_str());
		input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
		return true;
	}

	getNameEntryMap().insert(NameMultiMap::value_type(name, hie));

	ascii().addPos(pos);
	ascii().addNote(f.str().c_str());

	ascii().addPos(hie.begin());
	f.str("");
	f << name;
	if (name != name2) f << "/" << name2;
	f << ":" << std::dec << id;
	ascii().addNote(f.str().c_str());

	ascii().addPos(hie.end());
	ascii().addNote("_");

	input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
	return true;
}

//
// read the end the entry index: normally a string name
//
bool WPS8Parser::parseHeaderIndexEntryEnd(long endPos, WPSEntry &hie, std::string &mess)
{
	RVNGInputStreamPtr input = getInput();

	long pos = input->tell();
	long len = endPos-pos;
	libwps::DebugStream f;

	auto size = (int) libwps::read16(input);
	librevenge::RVNGString str;
	if (2*(size+1) != len || !m_textParser->readString(input, 2*size, str))
	{
		WPS_DEBUG_MSG(("WPS8Parser::parseHeaderIndexEntryEnd: unknown data\n"));
		ascii().addPos(pos);
		ascii().addNote("Entry(end): ###ignored");
	}
	else
	{
		hie.setExtra(str.cstr());
		f << "'" << str.cstr() << "'";
		mess = f.str();
	}
	return true;
}

/**
 * In the header, parse the index to the different sections of
 * the CONTENTS stream.
 *
 */
bool WPS8Parser::parseHeaderIndex()
{
	RVNGInputStreamPtr input = getInput();
	getNameEntryMap().clear();
	input->seek(0x08, librevenge::RVNG_SEEK_SET);

	long pos = input->tell();
	auto i0 = (int) libwps::read16(input);
	auto i1 = (int) libwps::read16(input);
	uint16_t n_entries = libwps::readU16(input);
	// fixme: sanity check n_entries

	libwps::DebugStream f;
	f << "Entries(HeaderZ): N=" << n_entries << ", " << i0 << ", " << i1 << "(";

	for (int i = 0; i < 4; i++)
		f << std::hex << libwps::read16(input) << std::dec << ",";
	f << "), ";
	f << "unk=" << std::hex <<libwps::read16(input) << std::dec << ",";

	ascii().addPos(pos);
	ascii().addNote(f.str().c_str());

	input->seek(0x18, librevenge::RVNG_SEEK_SET);
	bool readSome = false;
	do
	{
		if (input->isEnd()) return readSome;

		pos = input->tell();
		f.str("");
		uint16_t unknown1 = libwps::readU16(input); // function of the size of the entries ?

		uint16_t n_entries_local = libwps::readU16(input);
		f << "HeaderZ("<<std::hex << unknown1<<"): N=" << std::dec << n_entries_local;

		if (n_entries_local > 0x20)
		{
			WPS_DEBUG_MSG(("WPS8Parser::parseHeaderIndex: error: n_entries_local=%i\n", n_entries_local));
			return readSome;
		}

		uint32_t next_index_table = libwps::readU32(input);
		f << std::hex << ", nextHeader=" << next_index_table;
		if (next_index_table != 0xFFFFFFFF && long(next_index_table) < pos)
		{
			WPS_DEBUG_MSG(("WPS8Parser::parseHeaderIndex: error: next_index_table=%x decreasing !!!\n", next_index_table));
			return readSome;
		}

		ascii().addPos(pos);
		ascii().addNote(f.str().c_str());

		do
		{
			if (!parseHeaderIndexEntry()) return readSome;

			readSome=true;
			n_entries--;
			n_entries_local--;
		}
		while (n_entries > 0 && n_entries_local);

		if (0xFFFFFFFF == next_index_table && n_entries > 0)
		{
			WPS_DEBUG_MSG(("WPS8Parser::parseHeaderIndex: error: expected more header index entries\n"));
			return n_entries > 0;
		}

		if (0xFFFFFFFF == next_index_table)	break;

		if (input->seek((long) next_index_table, librevenge::RVNG_SEEK_SET) != 0) return readSome;
	}
	while (n_entries > 0);

	return true;
}

////////////////////////////////////////////////////////////
// low level
////////////////////////////////////////////////////////////
// read DOP zone: the document properties
bool WPS8Parser::readDocProperties(WPSEntry const &entry, WPSPageSpan &page)
{
	if (!entry.hasType(entry.name()))
	{
		WPS_DEBUG_MSG(("WPS8Parser::readDocProperties: warning: DOP name=%s, type=%s\n",
		               entry.name().c_str(), entry.type().c_str()));
		return false;
	}

	RVNGInputStreamPtr input = getInput();
	page=WPSPageSpan();

	long page_offset = entry.begin();
	long length = entry.length();
	long endPage = entry.end();

	if (length < 2)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readDocProperties: warning: DOP length=0x%lx\n", (unsigned long) length));
		return false;
	}

	entry.setParsed();
	input->seek(page_offset, librevenge::RVNG_SEEK_SET);

	libwps::DebugStream f, f2;
	if (libwps::read16(input) != length)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readDocProperties: invalid length=%ld\n", length));
		return false;
	}

	WPS8Struct::FileData mainData;
	std::string error;
	bool readOk = readBlockData(input, endPage,mainData, error);

	double dim[8] = {0, 0, 0, 0, 0, 0, 0, 0};
	bool setVal[8] = { false, false, false, false, false, false, false, false};
	for (auto const &dt : mainData.m_recursData)
	{
		if (dt.isBad()) continue;
		if (m_state->m_docPropertyTypes.find(dt.id())==m_state->m_docPropertyTypes.end())
		{
			f << "##" << dt << ",";
			continue;
		}
		if (m_state->m_docPropertyTypes.find(dt.id())->second != dt.type())
		{
			WPS_DEBUG_MSG(("WPS8Parser::readDocProperties: unexpected type for %d=%d\n", dt.id(), dt.type()));
			f << "###" << dt << ",";
			continue;
		}
		// is dt.id()==6 another dimension ?
		if (dt.id() >= 0 && (dt.id() < 6 || dt.id()==7))
		{
			dim[dt.id()] = float(dt.m_value)/914400.f;
			setVal[dt.id()] = true;
			continue;
		}
		bool ok = true;
		switch (dt.id())
		{
		case 0x8:
			if (!dt.m_value) break;
			if (dt.m_value >= 1 && dt.m_value <= 13)
				m_state->m_numColumns = int(1+dt.m_value);
			else
				f2 << "#";
			f2 << "numCols=" << dt.m_value+1 << ",";
			break;
		case 0x18:
			switch (dt.m_value)
			{
			case 1:
				break; // portrait
			case 2:
				page.setFormOrientation(WPSPageSpan::LANDSCAPE);
				f2 << "landscape,";
				break;
			default:
				f2 << "#pageOrientation=" << dt.m_value << ",";
				break;
			}
			break;
		case 0x1b:   // [-1,0,1,3,4]
			f2 << "f" << dt.id() << "=" << (int) int8_t(dt.m_value) << ",";
			break;
		case 0x13:   // an entries index, always an IBGF ?
			m_state->m_background.setName(dt.m_text);
			m_state->m_background.setId(int(dt.m_value));
			f2 << "background(entries)='" << dt.m_text << "':" << dt.m_value << ",";
			break;
		case 0x15:   // another entries index, always an 'BDR '?
			WPS_DEBUG_MSG(("WPS8Parser::readDocProperties: find a BDR entry, not implemented\n"));
			f2 << "pageBorder(entries)='" << dt.m_text << "':" << dt.m_value << ",";
			break;
		case 0xa:
			if (dt.isTrue()) f2 << "colSep(line),";
			break;
		case 0x19: // almost always set
			if (dt.isFalse())
				f2 <<  "f" << dt.id() << "=false,";
			else
				f2 <<  "f" << dt.id() << ",";
			break;
		case 0x1c:   // 0.1
			f2 <<  "colSep=" << float(dt.m_value)/914400.f << "(inch),";
			break;
		case 0x28:   // main language ?
			f2 << "lang?=" << libwps_tools_win::Language::name(dt.m_value) << ",";
			m_state->m_localeLanguage = libwps_tools_win::Language::localeName(dt.m_value);
			break;
		case 0x1e: // -3, 47 ou 85
		case 0x29: // 1 ou 2
			f2 <<  "f" << dt.id() << "=" << dt.m_value << ",";
			break;
		// 0x2a: an integer = 4|6|b
		// 0x2b: an integer = 0|8|c
		case 0x2c:
		{
			if (dt.isRead() || !dt.isArray())
			{
				ok = false;
				break;
			}
			auto size = int(dt.end()-dt.begin());
			if (size < 2 || size%2 != 0)
			{
				ok = false;
				break;
			}
			long actPos = input->tell();
			int numElt = (size-2)/2;

			input->seek(dt.begin()+2, librevenge::RVNG_SEEK_SET);
			std::string str;
			int elt=0;
			while (elt < numElt)
			{
				long val = libwps::read16(input);
				if (val < 30 && val != '\t' && val != '\n')
				{
					input->seek(-2, librevenge::RVNG_SEEK_CUR);
					break;
				}
				elt++;
				str += (char) val;
			}
			if (!str.length())
			{
				ok = false;
				input->seek(actPos, librevenge::RVNG_SEEK_SET);
				break;
			}
			f2 << "filename?=["; // wkthmLET.fmt and wkthmSCH.fmt
			if (str.length()) f2 << "\"" << str << "\",";
			numElt -= elt;
			elt=0;
			while (elt++ < numElt)
			{
				auto val = (long) libwps::readU16(input);
				if (val) f2 << "f" << elt << "=" << std::hex << val << std::dec << ",";
			}
			f2 << "],";
			input->seek(actPos, librevenge::RVNG_SEEK_SET);
			break;
		}
		case 0x2d:
		case 0x2e:
		{
			// In one file with unk2d=[f1=20,], unk2e=[f2=20,]
			if (dt.isRead() || !dt.isArray())
			{
				ok = false;
				break;
			}
			auto size = int(dt.end()-dt.begin());
			if (size != 0x100)
			{
				ok = false;
				break;
			}
			long actPos = input->tell();
			input->seek(dt.begin(), librevenge::RVNG_SEEK_SET);
			f2 << "unk" << std::hex << dt.id() << "=" << "[";
			for (int i = 0; i < 128; i++)
			{
				auto val = (long) libwps::readU16(input);
				if (val) f2 << "f" << i << "=" << std::hex << val << std::dec << ",";
			}
			f2 << "]" << std::dec << ",";
			input->seek(actPos, librevenge::RVNG_SEEK_SET);
			break;
		}

		default:
			ok = false;
			break;
		}
		if (ok) continue;
		f2 << dt << ",";
	}
	if (setVal[0])
	{
		if (dim[0] < 0.5 || dim[0] > 40) f << "###";
		else page.setFormWidth(dim[0]);
		f << "width=" << dim[0] << ",";
	}
	if (setVal[1])
	{
		if (dim[1] < 0.5 || dim[1] > 40) f << "###";
		else page.setFormLength(dim[1]);
		f << "height=" << dim[1] << ",";
	}
	f << "margin=[";
	for (int i = 2; i < 6; i++)
	{
		if (!setVal[i])
		{
			f << "_,";
			continue;
		}
		int w = i%2;
		double dd = w ? page.getFormWidth() : page.getFormLength();
		bool ok = (dim[i] >= 0) && (2*dim[i] < dd);
		switch (i)
		{
		case 2:
			if (ok) page.setMarginTop(dim[i]);
			break;
		case 3:
			if (ok) page.setMarginLeft(dim[i]);
			break;
		case 4:
			if (ok) page.setMarginBottom(dim[i]);
			break;
		case 5:
			if (ok) page.setMarginRight(dim[i]);
			break;
		default:
			break;
		}
		if (!ok) f << "###";
		f << dim[i] << ",";
	}
	f << "],";
	if (setVal[7]) f << "bordDim?=" << dim[7] << ",";
	f << f2.str();

	if (!readOk) f << "###or [" << mainData << "]";

	ascii().addPos(page_offset);
	ascii().addNote(f.str().c_str());

	return true;
}

/** Frame zone: ie a zone which can contains text, picture, ...
    and have some border */
bool WPS8Parser::readFRAM(WPSEntry const &entry)
{
	typedef WPS8ParserInternal::Frame Frame;
	libwps::DebugStream f;
	if (!entry.hasType(entry.name()))
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRAM warning: name=%s, type=%s\n",
		               entry.name().c_str(), entry.type().c_str()));
		return false;
	}
	RVNGInputStreamPtr input = getInput();

	long page_offset = entry.begin();
	long length = entry.length();
	long endPage = entry.end();

	if (length < 2)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRAM warning: length=0x%lx\n", (unsigned long) length));
		return false;
	}

	entry.setParsed();
	input->seek(page_offset, librevenge::RVNG_SEEK_SET);

	int numFram = libwps::read16(input);
	if (numFram < 0 || numFram*2 > length)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRAM warning: length=0x%lx, num=%d\n", (unsigned long) length, numFram));
		return false;
	}
	f << "N=" << numFram;

	ascii().addPos(page_offset);
	ascii().addNote(f.str().c_str());


	bool parsedAll = true, color = false;
	long lastPos=input->tell(); // uneeded initialization but makes clang analyser happier
	for (int i = 0; i < numFram; i++)
	{
		lastPos = input->tell();
		if (lastPos+2 > endPage)
		{
			parsedAll = false;
			break;
		}
		int sz = libwps::read16(input);
		if (sz < 2 || lastPos + sz > endPage)
		{
			parsedAll = false;
			break;
		}

		WPS8Struct::FileData mainData;
		std::string error;
		bool readOk = readBlockData(input, lastPos + sz, mainData, error);

		double dim[3] = {0, 0, 0};
		bool setVal[3] = { false, false, false};

		double bDim[4] = {0, 0, 0, 0};
		bool bset = false, bsetVal[4] = { false, false, false, false};
		libwps::DebugStream f2;

		Frame frame;
		int lastPage = 0;
		Vec2f minP, sizeP;
		for (auto const &dt : mainData.m_recursData)
		{
			if (dt.isBad()) continue;
			if (m_state->m_frameTypes.find(dt.id())==m_state->m_frameTypes.end())
			{
				WPS_DEBUG_MSG(("WPS8Parser::readFRAM: unexpected id for %d\n", dt.id()));
				f2 << "##" << dt << ",";
				continue;
			}
			if (m_state->m_frameTypes.find(dt.id())->second != dt.type())
			{
				WPS_DEBUG_MSG(("WPS8Parser::readFRAM: unexpected type for %d=%d\n", dt.id(), dt.type()));
				f2 << "###" << dt << ",";
				continue;
			}
			if (dt.id() >= 4 && dt.id() < 11)
			{
				switch (dt.id())
				{
				case 4:
					minP.setX(float(dt.m_value)/914400.f);
					break;
				case 5:
					minP.setY(float(dt.m_value)/914400.f);
					break;
				case 6:
					sizeP.setX(float(dt.m_value)/914400.f);
					break;
				case 7:
					sizeP.setY(float(dt.m_value)/914400.f);
					break;
				default:
					dim[dt.id()-8] = float(dt.m_value)/914400.f;
					setVal[dt.id()-8] = true;
					break;
				}
				continue;
			}

			bool ok = true;
			switch (dt.id())
			{
			case 0:
			{
				int page = (int16_t) dt.m_value;
				// try to avoid creating to many page
				if (page > lastPage+100)
				{
					ok = false;
					break;
				}
				frame.m_pos.setPage(page > 0 ? page+1 : page);
				if (page > lastPage) lastPage = page;
				break;
			}
			case 1:
				switch (dt.m_value)
				{
				// associated with mailing database with 0x16
				case 4:
					frame.m_type = Frame::DbField;
					break;
				// associated with 0x13=22:[0|96], 0x18,1b=0x41,0x2e, rarely with 0x1a
				case 6:
					frame.m_type = Frame::Header;
					break;
				// associated with 0x13=22:[0|96],0x18,1b=0x41,0x2e, rarely with 0x1a
				case 7:
					frame.m_type = Frame::Footer;
					break;

				// does the following correspond to page, column, char position ?

				// associated with 0x11=(m_idOle,0,1), 0x2e=?
				case 8:
					frame.m_type = Frame::Object;
					break;
				// associated with 0x18=?, 0x46=? and often with a BDR entry
				// CHECKME: seems also associated to a MCLD, how ?
				case 9:
					frame.m_type = Frame::Text;
					break;
				// associated with f3 and 0x11=(table_id,0,0) and 0x2a=?
				case 12:
					frame.m_type = Frame::Table;
					break;

				default:
					f2 << "###type=" << dt.m_value << ",";
					break;
				}
				break;
			case 0x2:
				if (dt.m_value&1) f2 << "noText[right],";
				if (dt.m_value&2) f2 << "noText[left],";
				if (dt.m_value&0xFC) f2 << "#f2=" << std::hex << (dt.m_value&0xFC) << ",";
				break;
			case 0x3: // seem to exist iff type=12
				if ((frame.m_type == Frame::Table) != dt.isTrue())
					f2 <<  "isTable?["
					   << (dt.isTrue() ? "true" : "false") << "],";
				break;
			case 0x10:   // an entry index always a BDR ?
				frame.m_idBorder.setName(dt.m_text);
				frame.m_idBorder.setId(int(dt.m_value));
				break;
			case 0x11:   // a list of 3 int32 ? Only the first is signifiant ?
			{
				if (dt.isRead() || !dt.isArray())
				{
					ok = false;
					break;
				}
				auto size = int(dt.end()-dt.begin());
				if (size < 2 || (size+2)%4 != 0)
				{
					ok = false;
					break;
				}
				int numElt = (size-2)/4;
				long actPos = input->tell();

				input->seek(dt.begin()+2, librevenge::RVNG_SEEK_SET);
				std::vector<long> values;
				for (int elt = 0; elt < numElt; elt++)
				{
					long val = libwps::read32(input);
					values.push_back(val);
				}

				bool canHaveId = frame.m_type==Frame::Table || frame.m_type==Frame::Object;
				if (canHaveId && numElt == 3 && values[1] == 0 && values[2] ==0)
					frame.m_idObject = int(values[0]);
				else if (canHaveId && numElt == 3 && values[1] == 0 && values[2] == 1)
					frame.m_idOle = int(values[0]);
				else
				{
					WPS_DEBUG_MSG(("WPS8Parser::readFRAM unknown field 0x11\n"));
					f2 << "###f17=(";
					for (int j = 0; j < numElt; j++) f2 << values[size_t(j)] << ",";
					f2 << "),";
				}

				input->seek(actPos, librevenge::RVNG_SEEK_SET);
				break;
			}
			case 0x16: // associated with field and database
				f2 << "database=" << dt.m_text << ":" << dt.m_value << ",";
				break;
			// associated with a tex box and field 0x19
			case 0x17:
				color = dt.isTrue();
				break;
			case 0x18:
			{
				if (dt.m_value < 0 || dt.m_value >= m_textParser->getNumTextZones())
				{
					ok = false;
					break;
				}
				int type = m_textParser->getTextZoneType((int) dt.m_value);
				if ((type == 6 && frame.m_type == Frame::Header) ||
				        (type == 7 && frame.m_type == Frame::Footer) ||
				        (type == 5))
					frame.m_idStrs = (int) dt.m_value;
				else
				{
					WPS_DEBUG_MSG(("WPS8Parser::readFRAM odd id for field 0x18\n"));
					ok = false;
				}
				break;
			}
			case 0x19: // CHECKME: can we also have a front color and a pattern ?
				if (!color)
					f2 << "#f23=false,";
				frame.m_backgroundColor = dt.getRGBColor();
				break;
			case 0x1b: // 41
				if (frame.m_type!=Frame::Footer || dt.m_value != 0x41)
					f2 << "##f" << dt.id() << "=" << (int16_t) dt.m_value << ",";
				break;
			case 0x26: // rotation in degree from center ( CCW)
				f2 << "rot=" << float(dt.m_value)/10. << "deg,";
				break;
			//
			// Table specific field ?
			//
			case 0x2a:   // link to the m_idTable(ids) number for a table
			{
				int id = (int16_t) dt.m_value;
				if (frame.m_type==Frame::Table)
					frame.m_idTable = id;
				else
					f2 << "f" << dt.id() << "=" << id << ",";
				break;
			}
			case 0x2c: // CHEKME
				if (frame.m_type==Frame::Table)
					frame.m_columns = int(dt.m_value);
				else ok = false;
				break;
			case 0x14: // in relation with 0x30 ? find a small number between 1 and 18
				if (frame.m_type==Frame::Table)
					f2 << "grpId=" << (int16_t) dt.m_value << ",";
				else ok = false;
				break;
			case 0x30: // seems to link the table together
				if (frame.m_type==Frame::Table)
					f2 << "tableGrp=" << (int16_t) dt.m_value << ",";
				else ok = false;
				break;
			case 0x2d: // appear 2 time in a table with value 2
				f2 << "f" << dt.id() << "=" << (int16_t) dt.m_value << ",";
				break;

			// UNKNOWN
			case 0x1a: // appear 2 time (in a header and in a footer) with value 16
				f2 << "f" << dt.id() << "=" << (int)(int8_t) dt.m_value << ",";
				break;

			case 0x2e: // in a table: objectId+1, in textbox: m_idTable ?, other = i ?
				f2 << "id" << dt.id() << "=" << (int16_t)(dt.m_value-1) << ",";
				break;
			case 0x13:
				// table 0x6-??,
				// header/footer 0x16-??,
				// textbox 0x10(means automatic resize)
				// object : no set
				f2 << "f" << dt.id();
				if (dt.m_value)
					f2 << "=" << std::hex << dt.m_value << std::dec;
				f2 << ",";
				break;
			case 0x2f:
				if (dt.isTrue())
					f2 <<  "f" << dt.id() << ",";
				else
					f2 <<  "f" << dt.id() << "=false,";
				break;
			case 0x1D:
			case 0x1E:
			case 0x1F:
			case 0x20: // in inches small value 0/0.05/0.1 -> a border size ?
				bset = true;
				bsetVal[dt.id()-0x1D] = true;
				bDim[dt.id()-0x1D] =  float(dt.m_value)/914400.f;
				break;

			default:
				ok = false;
			}

			if (ok) continue;
			f2 << "#" << dt << ",";
		}
		frame.m_pos.setOrigin(minP);
		frame.m_pos.setSize(sizeP);
		m_state->m_frameList.push_back(frame);

		f.str("");
		f << entry.name() << "(" << i << "):" << frame << ",";
		if (setVal[0] || setVal[1])
		{
			f << "f8/9=[";
			for (int j = 0; j < 2; j++)
			{
				if (!setVal[j])
				{
					f << "_,";
					continue;
				}
				double diff = 25/18.-dim[j];
				if (diff >= -1e-3 && diff <= 1e-3) f << "*,";
				else f << dim[j] << ",";
			}
			f << "],";
		}
		if (setVal[2]) f << "border[w]=" << dim[2] << ",";

		if (bset)
		{
			f << "borderMod[w]=["; // L, T, R, B
			for (int j = 0; j < 4; j++)
			{
				if (!bsetVal[j])
				{
					f << "_,";
					continue;
				}
				f << bDim[j] << ",";
			}
			f << "],";
		}
		f << f2.str();
		if (!readOk) f << ", ###or" << mainData;

		ascii().addPos(lastPos);
		ascii().addNote(f.str().c_str());
	}
	if (parsedAll) return true;

	ascii().addPos(lastPos);
	f.str("");
	f << "###" << entry.name();
	ascii().addNote(f.str().c_str());
	return false;
}

/** try to read a frame XXX database  */
bool WPS8Parser::readFRCD(WPSEntry const &entry)
{
	libwps::DebugStream f;
	if (!entry.hasType(entry.name()))
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: name=%s, type=%s\n",
		               entry.name().c_str(), entry.type().c_str()));
		return false;
	}
	RVNGInputStreamPtr input = getInput();

	long page_offset = entry.begin();
	long length = entry.length();
	long endPage = entry.end();

	if (length < 4+33+5*8)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: length=0x%lx\n", (unsigned long) length));
		return false;
	}
	entry.setParsed();
	input->seek(page_offset, librevenge::RVNG_SEEK_SET);
	auto val=int(libwps::readU16(input)); // 0x30
	if (val!=0x30) f << "f0=" << val << ",";
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString str;
	if (4+2*sSz+33+5*8>length || !m_textParser->readString(input, sSz, str))
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: can not read main string\n"));
		return false;
	}
	f << str.cstr() << ",";
	for (int i=0; i<16; ++i)   // f1=10,f2=22,f4=4,f7=256,f11=-1,f12=-256,f13=255,f14=-1
	{
		val=int(libwps::read16(input));
		if (val) f << "f" << i+1 << "=" << val << ",";
	}
	val=int(libwps::read8(input));
	if (val) f << "g0=" << val << ",";
	ascii().addPos(page_offset);
	ascii().addNote(f.str().c_str());
	for (int i=0; i<5; ++i)
	{
		long pos=input->tell();
		f.str("");
		f << "FRCD-A" << i << ":";
		if (pos+8>endPage)
		{
			WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: the zone seems too short\n"));
			f << "###";
			ascii().addPos(pos);
			ascii().addNote(f.str().c_str());
			return true;
		}
		for (int s=0; s<2; ++s)
		{
			if (pos+4>endPage || libwps::read8(input)!=-1)
			{
				WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: the zone seems bad\n"));
				f << "###";
				ascii().addPos(pos);
				ascii().addNote(f.str().c_str());
				return true;
			}
			val=int(libwps::read8(input));
			if (val==-1)
			{
				val=int(libwps::read16(input));
				if (val!=0x204)
					f << "f" << s << "=" << val << ",";
			}
			else if (val==-2)
			{
				val=int(libwps::read8(input));
				if (val!=-1) f << "g" << s << "=" << val << ",";
				sSz=int(libwps::readU8(input));
				str.clear();
				if (pos+2*sSz>endPage || (sSz && !m_textParser->readString(input, 2*sSz, str)))
				{
					WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: the zone seems too short\n"));
					f << "###";
					ascii().addPos(pos);
					ascii().addNote(f.str().c_str());
					return true;
				}
				if (!str.empty())
					f << str.cstr() << ",";
			}
			else
			{
				WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: find unexpected value\n"));
				f << "###val=" << val << ",";
				ascii().addPos(pos);
				ascii().addNote(f.str().c_str());
				return true;
			}
		}
		ascii().addPos(pos);
		ascii().addNote(f.str().c_str());
	}
	long pos=input->tell();
	f.str("");
	f << "FRCD-B:";
	if (pos+4>endPage)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: can not read the second zone\n"));
		f << "###";
		ascii().addPos(pos);
		ascii().addNote(f.str().c_str());
		return true;
	}
	auto N=int(libwps::readU16(input));
	if (pos+4+10*N>endPage)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: can not read the second zone\n"));
		f << "###";
		ascii().addPos(pos);
		ascii().addNote(f.str().c_str());
		return true;
	}
	val=int(libwps::read16(input)); // 0
	if (val) f << "f0=" << val << ",";
	ascii().addPos(pos);
	ascii().addNote(f.str().c_str());
	for (int n=0; n<N; ++n)
	{
		pos=input->tell();
		f.str("");
		f << "FRCD-B" << n << ":";
		if (pos+10>endPage)
		{
			WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: the zone seems too short\n"));
			f << "###";
			ascii().addPos(pos);
			ascii().addNote(f.str().c_str());
			return true;
		}
		val=int(libwps::read16(input)); // 0
		if (val) f << "f0=" << val << ",";
		for (int s=0; s<2; ++s)
		{
			if (pos+4>endPage || libwps::read8(input)!=-1)
			{
				WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: the second zone seems bad\n"));
				f << "###";
				ascii().addPos(pos);
				ascii().addNote(f.str().c_str());
				return true;
			}
			val=int(libwps::read8(input));
			if (val==-2)
			{
				val=int(libwps::read8(input));
				if (val!=-1) f << "g" << s << "=" << val << ",";
				sSz=int(libwps::readU8(input));
				str.clear();
				if (pos+2*sSz>endPage || (sSz && !m_textParser->readString(input, 2*sSz, str)))
				{
					WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: the second zone seems too short\n"));
					f << "###";
					ascii().addPos(pos);
					ascii().addNote(f.str().c_str());
					return true;
				}
				if (!str.empty())
					f << str.cstr() << ",";
			}
			else
			{
				WPS_DEBUG_MSG(("WPS8Parser::readFRCD warning: find unexpected value\n"));
				f << "###val=" << val << ",";
				ascii().addPos(pos);
				ascii().addNote(f.str().c_str());
				return true;
			}
		}
		ascii().addPos(pos);
		ascii().addNote(f.str().c_str());
	}
	return true;
}

// reads SYID zone: an unknown zone which seems to have the
//    same number of entries than the text zone
// also related to the MCLD ?
bool WPS8Parser::readSYID(WPSEntry const &entry, std::vector<int> &listId)
{
	RVNGInputStreamPtr input = getInput();
	listId.resize(0);
	libwps::DebugStream f;
	if (!entry.hasType(entry.name()))
	{
		WPS_DEBUG_MSG(("WPS8Parser::readSYID: warning: SYID name=%s, type=%s\n",
		               entry.name().c_str(), entry.type().c_str()));
		return false;
	}

	long page_offset = entry.begin();
	long length = entry.length();

	if (length < 4)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readSYID: warning: SYID length=0x%lx\n", (unsigned long) length));
		return false;
	}

	input->seek(page_offset, librevenge::RVNG_SEEK_SET);

	int unk = libwps::read32(input);
	int numID = libwps::read32(input);
	if (numID < 0 || 4*(long(numID)+2) != length)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readSYID: invalid length=%ld, num=%d\n", length, numID));
		return false;
	}

	f << "N=" << numID << ", unkn=" << unk << ", (";
	for (int i = 0; i < numID; i++)
	{
		int val = libwps::read32(input);
		listId.push_back(val);
		f << val << ",";
	}
	f << ")";

	ascii().addPos(page_offset);
	ascii().addNote(f.str().c_str());

	entry.setParsed();
	return true;
}

// reads the WNPR zone : a zone which seems to contain the printer preferences
// Warning: read data are not used
bool WPS8Parser::readWNPR(WPSEntry const &entry)
{
	if (!entry.hasType("WNPR"))
	{
		WPS_DEBUG_MSG(("WPS8Parser::readWNPR: warning: WNPR name=%s, type=%s\n",
		               entry.name().c_str(), entry.type().c_str()));
		return false;
	}
	RVNGInputStreamPtr input = getInput();
	long page_offset = entry.begin();
	long length = entry.length();
	long endPage = entry.end();

	if (length < 40)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readWNPR: warning: WNPR length=0x%lx\n", (unsigned long) length));
		return false;
	}

	entry.setParsed();
	input->seek(page_offset, librevenge::RVNG_SEEK_SET);

	libwps::DebugStream f;

	f << std::hex << libwps::readU32(input) << std::dec << ",";
	long dim[4];
	for (long &i : dim)
	{
		i = libwps::read32(input);
		if (i <= 0) return false;
	}

	if (dim[2] && dim[3])
	{
		f << "width=" << float(dim[0])/float(dim[2]) << ",";
		f << "height=" << float(dim[1])/float(dim[3]) << ",";
	}
	else
	{
		f << "###width=" << dim[0] << ":" << dim[3] << ",";
		f << "###height=" << dim[1] << ":" << dim[3] << ",";
	}

	f << "printmargins?=(";
	for (int i = 0; i < 4; i++)
	{
		auto val = (long) libwps::readU32(input);
		long sz = dim[2+(i%2)];
		if (sz)
			f << float(val)/float(sz) << ",";
		else
			f << "###" << val << ",";
	}
	f << "),";

	for (int i = 0; i < 2; i++)
		f << std::hex << libwps::readU16(input) << std::dec <<",";


	ascii().addPos(page_offset);
	ascii().addNote(f.str().c_str());

	long actPos = input->tell();
	if (actPos + 32*2+38+22 > endPage)
	{
		WPS_DEBUG_MSG(("WPS8Parser::readWNPR: length=0x%lx seems too short\n", (unsigned long) length));
		return false;
	}

	// DEVMODEA
	std::string st;
	for (int i = 0; i < 32; i++)
	{
		unsigned char c = libwps::readU8(input);
		if (!c) continue; // normally end by c, check me
		st+= char(c);
	}
	f.str("");
	f << "PRNT(DevMode):";
	f << "devName='" << st << "'," << std::hex;
	f << "specVersion=" << libwps::readU16(input) << ",";
	f << "driverVersion=" << libwps::readU16(input) << ",";
	int dmSize = libwps::readU16(input);
	if (actPos + dmSize > endPage || dmSize < 124) return false;
	f << "dmSize=" << dmSize << ",";
	f << "driverExtras=" << libwps::readU16(input) << ",";
	f << "dmFields=" << libwps::readU32(input) << ",";
	f << "orientation=" << libwps::readU16(input) << ","; // 1: portrait, 2: landscape
	f << "paperSize=" << libwps::readU16(input) << ","; // 1: letter, 2:letter small, ..

	long dim2[3];
	for (long &i : dim2)
		i = libwps::read16(input);
	f << std::dec;
	if (dim2[2] > 0)
	{
		f << "paperLength=" << std::dec << float(dim2[0])/float(dim2[2]) << ",";
		f << "paperWidth=" << float(dim2[1])/float(dim2[2]) << ",";
	}
	else
	{
		f << "paperLength=" << dim2[0] << ",";
		f << "paperWidth=" << dim2[1] << ",";
		f << "dmScale=" << dim2[2] << ",";
	}
	f << "dmCopies=" << libwps::readU16(input) << ",";
	f << "dmDefaultSource=" << libwps::readU16(input) << ",";
	f << "dmPrintQuality=" << libwps::read16(input) << ",";
	f << std::hex;
	f << "dmColor=" << libwps::readU16(input) << ",";
	f << "dmDuplex=" << libwps::readU16(input) << ",";
	f << "dmYResolution=" << libwps::readU16(input) << ",";
	f << "dmTTOption=" << libwps::readU16(input) << ",";
	f << "dmCollate=" << libwps::readU16(input) << ",";

	st="";
	for (int i = 0; i < 32; i++)
	{
		unsigned char c = libwps::readU8(input);
		if (!c) continue; // normally end by c, check me
		st+= char(c);
	}
	f << "formName='" << st << "',";
	f << "dmLogPixels=" << libwps::readU16(input) << ",";
	f << "dmBitsPerPel=" << libwps::readU32(input) << ",";
	f << "dmPelsWidth=" << libwps::readU32(input) << ",";
	f << "dmPelsHeight=" << libwps::readU32(input) << ",";
	f << "dmDisplayFlags=" << libwps::readU32(input) << ",";
	f << "dmDisplayFrequency=" << libwps::readU32(input) << ",";

	ascii().addPos(actPos);
	ascii().addNote(f.str().c_str());

	long devEndPos = actPos+dmSize;
	actPos = input->tell();
	if (actPos != devEndPos)
	{
		ascii().addPos(actPos);
		ascii().addNote("PRNT(DevMode-End)");
	}

	ascii().addPos(devEndPos);
	ascii().addNote("PRNT(Unknown)");

	return true;
}

////////////////////////////////////////////////////////////////
//
// ? Syllabes seperator to read the text
// it is really related to spell check or to speech
// Can surely be safety ignored in normal version.
//
////////////////////////////////////////////////////////////////
#ifndef DEBUG
bool WPS8Parser::readSPELLING(RVNGInputStreamPtr, std::string const &)
{
	return true;
}
#else
bool WPS8Parser::readSPELLING(RVNGInputStreamPtr input, std::string const &oleName)
{
	// SPELLING
	input->seek(0, librevenge::RVNG_SEEK_SET);
	auto vers = (int) libwps::read32(input); // always 6 ?
	if (vers < 0 || input->isEnd()) return false;

	std::string fName = libwps::Debug::flattenFileName(oleName);
	libwps::DebugFile asciiFile(input);
	asciiFile.open(fName);


	int actId = 0;
	std::map<uint32_t, int> listIds;

	libwps::DebugStream f;
	int num = 0;
	while (!input->isEnd())
	{
		long pos = input->tell();
		auto numVal = (int) libwps::read32(input);

		if (numVal < 0 || input->seek(8*numVal, librevenge::RVNG_SEEK_CUR) || input->tell() != pos+4+8*numVal)
		{
			input->seek(pos, librevenge::RVNG_SEEK_SET);
			break;
		}

		f.str("");
		f << "SPELLING" << num++ << ": "; // linked to STRSj by num = numSTRS-j?
		input->seek(pos+4, librevenge::RVNG_SEEK_SET);
		for (int j = 0; j < numVal; j++)
		{
			uint32_t ptr = libwps::readU32(input);
			int id;
			if (listIds.find(ptr) == listIds.end())
			{
				id = actId++;
				listIds[ptr] = id;
			}
			else
				id = listIds[ptr];
			auto sPos = (long) libwps::read32(input);
			f << std::hex << 2*sPos << ":SP" <<  std::dec << id << ",";
		}
		asciiFile.addPos(pos);
		asciiFile.addNote(f.str().c_str());
	}
	f.str("");
	f << "SPELLING:";
	if (vers != 6) f << "version = " << vers << ", ";
	f << "list=[";
	for (auto it : listIds)
	{
		uint32_t val = it.first;
		f << "SP" << std::dec << it.second << "(" << (val&0xFF) << ":"
		  << std::hex << (val>>8) << std::dec << "),";

		// (val&0xFF) :121-123, 129, 133, 139, 145
		// (val>>8) : 0, e6, d9a3b, 3b75f4, 474555, 548bbb, 609daa,748cce, ba32b9, c068cd, d39bea
		//          seems constant in a SPELLING Ole
	}
	f << "]";
	asciiFile.addPos(0);
	asciiFile.addNote(f.str().c_str());
	if (!input->isEnd())
	{
		asciiFile.addPos(input->tell());
		asciiFile.addNote("###SPELLING");
	}
	return true;
}
#endif

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