/* -*- 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 <stdlib.h>
#include <string.h>

#include <cmath>
#include <sstream>
#include <limits>
#include <map>
#include <stack>

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

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

#include "WKSContentListener.h"
#include "WKSSubDocument.h"

#include "WPSEntry.h"
#include "WPSFont.h"
#include "WPSGraphicShape.h"
#include "WPSGraphicStyle.h"
#include "WPSOLEObject.h"
#include "WPSParagraph.h"
#include "WPSPosition.h"
#include "WPSStream.h"

#include "Quattro.h"

#include "QuattroGraph.h"

namespace QuattroGraphInternal
{
//! a dialog header
struct Dialog
{
	//! constructor
	explicit Dialog()
		: m_cellBox()
	{
		for (auto &f : m_flags1) f=0;
		for (auto &f : m_flags2) f=0;
	}
	//! operator<<
	friend std::ostream &operator<<(std::ostream &o, Dialog const &gr);
	//! the cell's position
	WPSBox2i m_cellBox;
	//! some flags
	int m_flags1[5];
	//! final flag
	int m_flags2[9];
};

std::ostream &operator<<(std::ostream &o, Dialog const &dlg)
{
	if (dlg.m_cellBox!=WPSBox2i()) o << "cellBox=" << dlg.m_cellBox << ",";
	o << "fl1=[";
	for (auto f : dlg.m_flags1)
	{
		if (f)
			o << std::hex << f << std::dec << ",";
		else
			o << "_,";
	}
	o << "],";
	o << "fl2=[";
	for (auto f : dlg.m_flags2)
	{
		if (f)
			o << std::hex << f << std::dec << ",";
		else
			o << "_,";
	}
	o << "],";
	return o;
}

//! a graph
struct Graph
{
	//! the posible type
	enum Type { Button, Chart, Frame, OLE/* or bitmap */, Image, Unknown };
	//! constructor
	explicit Graph(std::shared_ptr<WPSStream> const &stream, Type type=Unknown)
		: m_type(type)
		, m_size()
		, m_cellBox()
		, m_cellBoxDecal()
		, m_label()
		, m_ole()
		, m_linkName()
		, m_stream(stream)
	{
		for (auto &f : m_flags1) f=0;
		for (auto &f : m_flags2) f=0;
		for (auto &v : m_values) v=0;
	}
	//! operator<<
	friend std::ostream &operator<<(std::ostream &o, Graph const &gr);
	//! the type
	Type m_type;
	//! the size
	Vec2f m_size;
	//! the cell's position
	WPSBox2i m_cellBox;
	//! the decal position(LT, RB)
	WPSBox2f m_cellBoxDecal;
	//! some flags
	int m_flags1[4];
	//! final flag
	int m_flags2[7];
	//! some values
	int m_values[5];

	//! the label(button)
	librevenge::RVNGString m_label;

	//! the OLE's data
	WPSEmbeddedObject m_ole;
	//! the OLE's link name
	librevenge::RVNGString m_linkName;

	//! the main stream
	std::shared_ptr<WPSStream> m_stream;
};

std::ostream &operator<<(std::ostream &o, Graph const &gr)
{
	if (gr.m_size!=Vec2f()) o << "size=" << gr.m_size << ",";
	if (gr.m_cellBox!=WPSBox2i()) o << "cellBox=" << gr.m_cellBox << ",";
	if (gr.m_cellBoxDecal!=WPSBox2f()) o << "cellBox[decal]=" << gr.m_cellBoxDecal << ",";
	o << "fl1=[";
	for (auto f : gr.m_flags1)
	{
		if (f)
			o << std::hex << f << std::dec << ",";
		else
			o << "_,";
	}
	o << "],";
	o << "fl2=[";
	for (auto f : gr.m_flags2)
	{
		if (f)
			o << std::hex << f << std::dec << ",";
		else
			o << "_,";
	}
	o << "],";
	for (int i=0; i<5; ++i)
	{
		if (gr.m_values[i])
			o << "f" << i << "=" << gr.m_values[i] << ",";
	}
	return o;
}

//! the state of QuattroGraph
struct State
{
	//! constructor
	State()
		: m_version(-1)
		, m_actualSheet(-1)
		, m_sheetIdToGraphMap()
		, m_actualGraph()
		, m_linkNameToObjectMap()
	{
	}
	//! store a graph
	void storeGraph(std::shared_ptr<Graph> graph)
	{
		if (!graph)
		{
			WPS_DEBUG_MSG(("QuattroGraphInternal::storeGraph: no graph\n"));
			return;
		}
		m_actualGraph=graph;
		if (m_actualSheet<0)
		{
			WPS_DEBUG_MSG(("QuattroGraphInternal::storeGraph: can not find the current sheet\n"));
			return;
		}
		m_sheetIdToGraphMap.insert(std::multimap<int, std::shared_ptr<Graph> >::value_type(m_actualSheet, graph));
	}
	//! the file version
	int m_version;
	//! the actual sheet id
	int m_actualSheet;
	//! a multimap sheetId to graph
	std::multimap<int, std::shared_ptr<Graph> > m_sheetIdToGraphMap;
	//! the actual graph
	std::shared_ptr<Graph> m_actualGraph;
	//! a map link name to object
	std::map<librevenge::RVNGString,WPSEmbeddedObject> m_linkNameToObjectMap;
};

//! Internal: the subdocument of a LotusGraphc
class SubDocument final : public WKSSubDocument
{
public:
	//! constructor for a text entry
	SubDocument(QuattroGraph &graphParser, librevenge::RVNGString const &text)
		: WKSSubDocument(RVNGInputStreamPtr(), &graphParser.m_mainParser)
		, m_graphParser(graphParser)
		, m_text(text) {}
	//! destructor
	~SubDocument() final {}

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

	//! the parser function
	void parse(std::shared_ptr<WKSContentListener> &listener, libwps::SubDocumentType subDocumentType) final;
	//! the graph parser
	QuattroGraph &m_graphParser;
	//! the main text
	librevenge::RVNGString m_text;
};

void SubDocument::parse(std::shared_ptr<WKSContentListener> &listener, libwps::SubDocumentType)
{
	if (!listener.get())
	{
		WPS_DEBUG_MSG(("QuattroGraphInternal::SubDocument::parse: no listener\n"));
		return;
	}
	if (!dynamic_cast<WKSContentListener *>(listener.get()))
	{
		WPS_DEBUG_MSG(("QuattroGraphInternal::SubDocument::parse: bad listener\n"));
		return;
	}
	WPSParagraph para;
	para.m_justify=libwps::JustificationCenter;
	listener->setParagraph(para);
	if (!m_text.empty())
		listener->insertUnicodeString(m_text);
}

}

// constructor, destructor
QuattroGraph::QuattroGraph(QuattroParser &parser)
	: m_listener()
	, m_mainParser(parser)
	, m_state(new QuattroGraphInternal::State())
{
}

QuattroGraph::~QuattroGraph()
{
}

void QuattroGraph::cleanState()
{
	m_state.reset(new QuattroGraphInternal::State());
}

void QuattroGraph::updateState()
{
}

int QuattroGraph::version() const
{
	if (m_state->m_version<0)
		m_state->m_version=m_mainParser.version();
	return m_state->m_version;
}

void QuattroGraph::storeObjects(std::map<librevenge::RVNGString,WPSEmbeddedObject> const &nameToObjectMap)
{
	m_state->m_linkNameToObjectMap=nameToObjectMap;
}

std::vector<Vec2i> QuattroGraph::getGraphicCellsInSheet(int sheetId) const
{
	std::vector<Vec2i> list;
	auto it=m_state->m_sheetIdToGraphMap.find(sheetId);
	while (it!=m_state->m_sheetIdToGraphMap.end() && it->first==sheetId)
	{
		auto const &graph=it++->second;
		if (graph)
			list.push_back(graph->m_cellBox[0]);
	}
	return list;
}

////////////////////////////////////////////////////////////
// low level

////////////////////////////////////////////////////////////
// zones
////////////////////////////////////////////////////////////
bool QuattroGraph::readHeader(QuattroGraphInternal::Graph &header, std::shared_ptr<WPSStream> stream, long endPos)
{
	auto input=stream->m_input;
	long pos = input->tell();
	if (endPos-pos<49)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readHeader: the zone is too short\n"));
		return false;
	}
	for (auto &fl : header.m_flags1) fl=int(libwps::readU16(input));
	int dim[4];
	for (auto &d: dim) d=libwps::readU16(input);
	header.m_cellBox=WPSBox2i(Vec2i(dim[0],dim[1]),Vec2i(dim[2],dim[3]));
	float fDim[4];
	for (auto &d: fDim) d=float(libwps::read16(input))/20.f;
	header.m_cellBoxDecal=WPSBox2f(Vec2f(fDim[0],fDim[1]),Vec2f(fDim[2],fDim[3]));
	for (int i=0; i<2; ++i) fDim[i]=float(libwps::read32(input))/20.f;
	header.m_size=Vec2f(fDim[0],fDim[1]);
	for (auto &fl : header.m_flags2) fl=int(libwps::readU8(input));
	for (auto &v : header.m_values) v=int(libwps::read16(input));
	return true;
}

bool QuattroGraph::readBeginEnd(std::shared_ptr<WPSStream> stream, int sheetId)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x321 && type != 0x322)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readBeginEnd: not a begin/end zone\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	int const expectedSize=(type==0x321 ? 0 : 2);
	m_state->m_actualGraph.reset();
	m_state->m_actualSheet=type==0x321 ? sheetId : -1;
	if (type==0x321)
		f << "Entries(GraphBegEnd)[begin]:";
	else
		f << "Entries(GraphBegEnd)[end]:";
	if (sz!=expectedSize)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readBeginEnd: size seems very bad\n"));
		f << "###";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	if (type==0x322)   // always 0
	{
		auto val=int(libwps::read16(input));
		if (val) f << "f0=" << val << ",";
	}
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	return true;
}

bool QuattroGraph::readFrame(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x385)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readFrame: not a frame zone\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	f << "Entries(GraphFrame):";
	auto frame=std::make_shared<QuattroGraphInternal::Graph>(stream,QuattroGraphInternal::Graph::Frame);
	m_state->m_actualGraph.reset();
	if (sz<57 || !readHeader(*frame,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readFrame: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << *frame;
	m_state->storeGraph(frame);
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (input->tell()+sSz+6>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readFrame: can not read string1\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << "name=" << text.cstr() << ",";
	for (int i=0; i<3; ++i)   // g0=1, g2=2001
	{
		auto val=int(libwps::readU16(input));
		if (val)
			f << "g" << i << "=" << std::hex << val << std::dec << ",";
	}
	if (input->tell()!=endPos)
	{
		ascFile.addDelimiter(input->tell(),'|');
		WPS_DEBUG_MSG(("QuattroGraph::readFrame: find extra data\n"));
		f << "##extra,";
	}
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	return true;
}

bool QuattroGraph::readFrameOLE(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x381)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readFrameOLE: not a frame zone\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	f << "Entries(GraphFram2):";
	auto frame=std::make_shared<QuattroGraphInternal::Graph>(stream,QuattroGraphInternal::Graph::OLE);
	m_state->m_actualGraph.reset();
	if (sz<59 || !readHeader(*frame,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readFrameOLE: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << *frame;
	m_state->storeGraph(frame);
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (input->tell()+sSz+4>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readFrameOLE: can not read string1\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	frame->m_linkName=text;
	f << "name=" << text.cstr() << ",";
	for (int i=0; i<4; ++i)   // g0=11, g2=d00, g4=7500
	{
		auto val=int(libwps::readU16(input));
		if (val)
			f << "g" << i << "=" << std::hex << val << std::dec << ",";
	}
	if (input->tell()!=endPos)
	{
		ascFile.addDelimiter(input->tell(),'|');
		WPS_DEBUG_MSG(("QuattroGraph::readFrameOLE: find extra data\n"));
		f << "##extra,";
	}
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	return true;
}

bool QuattroGraph::readOLEData(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x38b)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readOLEData: not a OLE zone\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=sz<0xFF00 ? pos+4+sz : stream->m_eof;
	f << "Entries(GraphOLE):";
	if (sz<38)
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readOLEData: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	auto frame=m_state->m_actualGraph;
	if (frame && frame->m_type!=frame->Frame)
		frame.reset();
	if (frame)
		frame->m_type=frame->OLE;
	else
	{
		WPS_DEBUG_MSG(("QuattroGraph::readOLEData: can not find current frame\n"));
	}
	for (int i=0; i<5; ++i)
	{
		int val=int(libwps::readU16(input));
		int const(expected[])= {0x1a,0x8068,0x2001,0,0};
		if (val!=expected[i])
			f << "f" << i << "=" << std::hex << val << std::dec << ",";
	}
	long actPos=input->tell();
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (actPos+2+sSz+12+1+12>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readOLEData: can not read the name\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << "name=" << text.cstr() << ",";
	input->seek(actPos+2+sSz, librevenge::RVNG_SEEK_SET);
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());

	pos=input->tell();
	WPSEmbeddedObject dummyObject;
	if (!WPSOLEObject::readOLE(stream, frame ? frame->m_ole : dummyObject,endPos))
		input->seek(pos, librevenge::RVNG_SEEK_SET);
	if (input->tell()!=endPos)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readOLEData: find extra data\n"));
		ascFile.addPos(input->tell());
		ascFile.addNote("GraphOLE:###extra");
	}
	return true;
}

bool QuattroGraph::readButton(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x386)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readButton: not a button zone\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	f << "Entries(GraphButton):";
	m_state->m_actualGraph.reset();
	auto button=std::make_shared<QuattroGraphInternal::Graph>(stream,QuattroGraphInternal::Graph::Button);
	if (sz<67 || !readHeader(*button,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readButton: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << *button; // fl1=[7f|81,0x8063,0x2000,0];
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());

	pos=input->tell();
	f.str("");
	f << "GraphButton-A:";
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (pos+2+sSz>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readButton: can not read string1 bad\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	input->seek(pos+2+sSz, librevenge::RVNG_SEEK_SET);
	f << "name=" << text.cstr() << ",";
	for (int i=0; i<5; ++i)
	{
		auto val=int(libwps::readU16(input));
		if (val) f << "f" << i << "=" << val << ",";
	}
	auto val=int(libwps::readU8(input));
	if (val) f << "f5=" << val << ",";
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	pos=input->tell();
	f.str("");
	f << "GraphButton-B:";
	auto dType=int(libwps::readU8(input));
	if (dType==1) f << "complex,";
	else if (dType)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readButton: find unknown type\n"));
		f << "##dType=" << dType << ",";
	}
	for (int st=0; st<2; ++st)
	{
		sSz=int(libwps::readU16(input));
		if (pos+2+sSz>endPos || !m_mainParser.readCString(stream,text,sSz))
		{
			WPS_DEBUG_MSG(("QuattroGraph::readButton: can not read string2 bad\n"));
			f << "##sSz,";
			ascFile.addPos(pos);
			ascFile.addNote(f.str().c_str());
			return true;
		}
		if (text.empty()) continue;
		f << (st==0 ? "macros" : "label") << "=" << text.cstr() << ",";
		if (st==1)
			button->m_label=text;
	}
	if (dType==0)
	{
		if (input->tell()!=endPos)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readButton: find extra data\n"));
			f << "##extra,";
			ascFile.addDelimiter(input->tell(),'|');
		}
		m_state->storeGraph(button);
	}
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());

	if (dType && input->tell()!=endPos)
	{
		ascFile.addPos(input->tell());
		ascFile.addNote("GraphButton-C:");
	}
	return true;
}

bool QuattroGraph::readImage(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x382)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readImage: unknown id\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	f << "Entries(GraphImage):";
	auto zone382=std::make_shared<QuattroGraphInternal::Graph>(stream,QuattroGraphInternal::Graph::Image);
	m_state->m_actualGraph.reset();
	if (sz<53 || !readHeader(*zone382,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readImage: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << *zone382;
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (input->tell()+sSz+2>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readImage: can not read string1\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << text.cstr() << ","; // find Bitmap5, followed by
	// 20000000070001000080000000ffffff0000000100000000070064e96729a1e87328af2b0700628597334100c767040064e96729b7e87328af2b04008bce67354800c7670500673575ce0400
	// 2d002d00: a picture dimension ?
	// eb010000: the gif's size
	// the gif
	if (input->tell()!=endPos)
	{
		ascFile.addDelimiter(input->tell(),'|');
	}
	static bool first=true;
	if (first)
	{
		first=false;
		WPS_DEBUG_MSG(("QuattroGraph::readImage: this file contains a zone 382, there will not be recovered\n"));
	}
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	return true;
}

bool QuattroGraph::readBitmap(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x383)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readBitmap: unknown id\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	f << "Entries(GraphBitmap):";
	auto bitmap=std::make_shared<QuattroGraphInternal::Graph>(stream,QuattroGraphInternal::Graph::OLE);
	m_state->m_actualGraph.reset();
	if (sz<67 || !readHeader(*bitmap,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readBitmap: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << *bitmap;
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (input->tell()+sSz+16>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readBitmap: can not read string1\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << text.cstr() << ",";
	f << "unkn=[";
	for (int i=0; i<8; ++i)
	{
		auto val=int(libwps::readU16(input));
		if (val)
			f << std::hex << val << std::dec << ",";
		else
			f << "_,";
	}
	f << "],";
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());

	WPSEmbeddedObject object;
	pos=input->tell();
	if (!WPSOLEObject::readWMF(stream, bitmap->m_ole, endPos))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readBitmap: can not find the wmf file\n"));
		ascFile.addPos(pos);
		ascFile.addNote("GraphBitmap:###");
	}
	else
		m_state->storeGraph(bitmap);

	return true;
}

bool QuattroGraph::readChart(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x384)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readChart: unknown id\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	f << "Entries(GraphChart):";
	auto chart=std::make_shared<QuattroGraphInternal::Graph>(stream,QuattroGraphInternal::Graph::Chart);
	m_state->m_actualGraph.reset();
	if (sz<57 || !readHeader(*chart,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readChart: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << *chart;
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (input->tell()+sSz+6>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readChart: can not read string1\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << text.cstr() << ","; // find Inserted1-21
	for (int i=0; i<2; ++i)   // f0=1|30
	{
		auto val=int(libwps::read16(input));
		if (val) f << "f" << i << "=" << val << ",";
	}
	sSz=int(libwps::readU16(input));
	if (input->tell()+sSz>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readChart: can not read string1\n"));
		f << "##sSz2,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << "name=" << text.cstr() << ",";
	if (input->tell()!=endPos)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readChart: find extra data\n"));
		f << "##extra,";
		ascFile.addDelimiter(input->tell(),'|');
	}
	static bool first=true;
	if (first)
	{
		first=false;
		WPS_DEBUG_MSG(("QuattroGraph::readChart: this file contains some charts, there will not be recovered\n"));
	}
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	return true;
}

////////////////////////////////////////////////////////////
// dialog
////////////////////////////////////////////////////////////
bool QuattroGraph::readHeader(QuattroGraphInternal::Dialog &header, std::shared_ptr<WPSStream> stream, long endPos)
{
	auto input=stream->m_input;
	long pos = input->tell();
	if (endPos-pos<22)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readHeader: the zone is too short\n"));
		return false;
	}
	for (auto &fl : header.m_flags1) fl=int(libwps::readU16(input));
	int dim[4];
	for (auto &d: dim) d=libwps::readU16(input);
	header.m_cellBox=WPSBox2i(Vec2i(dim[0],dim[1]),Vec2i(dim[2],dim[3]));
	for (auto &fl : header.m_flags2) fl=int(libwps::readU8(input));
	return true;
}

bool QuattroGraph::readDialog(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type != 0x35e)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readDialog: unknown id\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	f << "Entries(DialogMain):";
	QuattroGraphInternal::Dialog dialog;
	if (sz<65 || !readHeader(dialog,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readDialog: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << dialog << ",";
	int val;
	for (int i=0; i<3; ++i)   // f1=0|-256, f2=-1
	{
		val=int(libwps::read16(input));
		if (val)
			f << "f" << i << "=" << val <<",";
	}
	for (int i=0; i<3; ++i)   // 0
	{
		val=int(libwps::read16(input));
		if (val)
			f << "f" << i+4 << "=" << val <<",";
	}
	val=int(libwps::readU16(input));
	if (val!=0x100)
		f << "f7=" << std::hex << val << std::dec << ",";
	auto sSz=int(libwps::readU16(input));
	librevenge::RVNGString text;
	if (input->tell()+sSz+7+15>endPos || !m_mainParser.readCString(stream,text,sSz))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readDialog: can not read string1\n"));
		f << "##sSz,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	f << text.cstr() << ","; // find Dialog1...
	val=int(libwps::readU8(input));
	if (val!=0x1)
		f << "f9=" << val << ",";
	sSz=int(libwps::readU16(input));
	if (sSz<4 || input->tell()+sSz+15>endPos || (sz>4 && !m_mainParser.readCString(stream,text,sSz-4)))
	{
		WPS_DEBUG_MSG(("QuattroGraph::readDialog: can not read string1\n"));
		f << "##sSz2,";
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	if (!text.empty()) // find Enter ...
		f << text.cstr() << ",";
	for (int i=0; i<2; ++i)   // 0
	{
		val=int(libwps::read16(input));
		if (val) f << "g" << i << "=" << val << ",";
	}
	if (input->tell()!=endPos) // then some flags
		ascFile.addDelimiter(input->tell(),'|');
	static bool first=true;
	if (first)
	{
		first=false;
		WPS_DEBUG_MSG(("QuattroGraph::readDialog: this file contains some dialogs, there will not be recovered\n"));
	}
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	return true;
}

bool QuattroGraph::readDialogUnknown(std::shared_ptr<WPSStream> stream)
{
	RVNGInputStreamPtr input = stream->m_input;
	libwps::DebugFile &ascFile=stream->m_ascii;
	libwps::DebugStream f;
	long pos = input->tell();
	auto type = int(libwps::readU16(input)&0x7fff);

	if (type<0x330 || type>0x380)
	{
		WPS_DEBUG_MSG(("QuattroGraph::readDialogUnknown: unknown id\n"));
		return false;
	}
	auto sz = (long)libwps::readU16(input);
	long endPos=pos+4+sz;
	switch (type)
	{
	case 0x335:
		f << "Entries(DialogTextbox):";
		break;
	case 0x343:
		f << "Entries(DialogButton):";
		break;
	case 0x345:
		f << "Entries(DialogButton)[bitmap]:";
		break;
	default:
		f << "Entries(DialogUnknown):type=" << std::hex << type << std::dec << ",";
		break;
	}
	QuattroGraphInternal::Dialog dialog;
	if (sz<38 || !readHeader(dialog,stream,endPos))
	{
		if (sz)
		{
			WPS_DEBUG_MSG(("QuattroGraph::readDialogUnknown: size seems very bad\n"));
			f << "###";
		}
		ascFile.addPos(pos);
		ascFile.addNote(f.str().c_str());
		return true;
	}
	if (dialog.m_flags2[8]&0x80)
	{
		f << "select,";
		dialog.m_flags2[8]&=0x7f;
	}
	f << dialog << ",";
	auto fl=int(libwps::readU8(input)); // [0128][08ac]
	if (fl&1) f << "has[frame],";
	fl &= 0xfe;
	if (fl) f << "flag=" << std::hex << fl << std::dec << ",";
	auto id=int(libwps::readU16(input));
	f << "id=" << id << ",";
	unsigned char col[3];
	for (auto &c : col) c=libwps::readU8(input);
	f << "col=" << WPSColor(col[0],col[1],col[2]) << ",";
	f << "fl3=[";
	for (int i=0; i<5; ++i)   // 0
	{
		auto val=int(libwps::readU8(input));
		if (val)
			f << std::hex << val << std::dec << ",";
		else
			f << "_,";
	}
	f << "],";
	if (input->tell()!=endPos) // then some flags
		ascFile.addDelimiter(input->tell(),'|');
	ascFile.addPos(pos);
	ascFile.addNote(f.str().c_str());
	return true;
}

////////////////////////////////////////////////////////////
// send data
////////////////////////////////////////////////////////////
bool QuattroGraph::sendGraphics(int sheetId, Vec2i const &cell) const
{
	if (!m_listener)
	{
		WPS_DEBUG_MSG(("QuattroGraph::sendGraphics: can not find the listener\n"));
		return false;
	}
	auto it=m_state->m_sheetIdToGraphMap.find(sheetId);
	bool find=false;
	while (it!=m_state->m_sheetIdToGraphMap.end() && it->first==sheetId)
	{
		auto &graph=it++->second;
		if (!graph || graph->m_cellBox[0]!=cell) continue;
		sendGraphic(*graph);
		find=true;
	}
	if (!find)
	{
		WPS_DEBUG_MSG(("QuattroGraph::sendGraphics: sorry, can not find any graph\n"));
	}
	return find;
}

bool QuattroGraph::sendGraphic(QuattroGraphInternal::Graph const &graph) const
{
	if (!m_listener)
	{
		WPS_DEBUG_MSG(("QuattroGraph::sendGraphic: can not find the listener\n"));
		return false;
	}
	WPSPosition pos(graph.m_cellBoxDecal[0], graph.m_size, librevenge::RVNG_POINT);
	pos.m_anchorTo = WPSPosition::Cell;
	pos.m_anchorCellName = libwps::getCellName(graph.m_cellBox[1]+Vec2i(1,1)).c_str();
	if (graph.m_type==graph.OLE)
	{
		if (!graph.m_linkName.empty())
		{
			auto it=m_state->m_linkNameToObjectMap.find(graph.m_linkName);
			if (it==m_state->m_linkNameToObjectMap.end() || it->second.isEmpty())
			{
				WPS_DEBUG_MSG(("QuattroGraph::sendGraphic: can not find ole %s\n", graph.m_linkName.cstr()));
			}
			else
				m_listener->insertObject(pos, it->second);
		}
		else if (graph.m_ole.isEmpty())
		{
			WPS_DEBUG_MSG(("QuattroGraph::sendGraphic: find emty ole\n"));
		}
		else
			m_listener->insertObject(pos, graph.m_ole);
		return true;
	}
	if (graph.m_type==graph.Button)
	{
		if (graph.m_label.empty())
		{
			WPS_DEBUG_MSG(("QuattroGraph::sendGraphic: find emty label\n"));
		}
		else
		{
			WPSGraphicStyle style;
			style.setBackgroundColor(WPSColor(128,128,128));
			auto doc=std::make_shared<QuattroGraphInternal::SubDocument>(const_cast<QuattroGraph &>(*this),graph.m_label);
			m_listener->insertTextBox(pos, doc, style);
		}
		return true;
	}
	static bool first=true;
	if (first)
	{
		first=false;
		WPS_DEBUG_MSG(("QuattroGraph::sendGraphic: sorry, unexpected graph type\n"));
	}
	return true;

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