//
// Document.cpp
//
// Library: PDF
// Package: PDFCore
// Module:  Document
//
// Copyright (c) 2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/PDF/Document.h"
#include "Poco/PDF/PDFException.h"
#include "Poco/File.h"
#include "Poco/Path.h"
#include "Poco/LocalDateTime.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/StringTokenizer.h"
#include "Poco/NumberParser.h"
#include <hpdf.h>
#include <utility>


namespace Poco {
namespace PDF {

static_assert(static_cast<int>(Document::Info::INFO_CREATION_DATE) == HPDF_INFO_CREATION_DATE, "Document info value mismatch");
static_assert(static_cast<int>(Document::Info::INFO_KEYWORDS) == HPDF_INFO_KEYWORDS, "Document info value mismatch");

static_assert(static_cast<int>(Document::PageLayout::PAGE_LAYOUT_SINGLE) == HPDF_PAGE_LAYOUT_SINGLE, "Page layout value mismatch");
static_assert(static_cast<int>(Document::PageLayout::PAGE_LAYOUT_TWO_COLUMN_RIGHT) == HPDF_PAGE_LAYOUT_TWO_COLUMN_RIGHT, "Page layout value mismatch");

static_assert(static_cast<int>(Document::PageMode::PAGE_MODE_USE_NONE) == HPDF_PAGE_MODE_USE_NONE, "Page mode value mismatch");
static_assert(static_cast<int>(Document::PageMode::PAGE_MODE_FULL_SCREEN) == HPDF_PAGE_MODE_FULL_SCREEN, "Page mode value mismatch");

static_assert(static_cast<int>(Document::Encryption::ENCRYPT_R2) == HPDF_ENCRYPT_R2, "Encryption value mismatch");
static_assert(static_cast<int>(Document::Encryption::ENCRYPT_R3) == HPDF_ENCRYPT_R3, "Encryption value mismatch");

static_assert(static_cast<int>(Document::PageNumberStyle::PAGE_NUM_STYLE_DECIMAL) == HPDF_PAGE_NUM_STYLE_DECIMAL, "Page number value mismatch");
static_assert(static_cast<int>(Document::PageNumberStyle::PAGE_NUM_STYLE_LOWER_LETTERS) == HPDF_PAGE_NUM_STYLE_LOWER_LETTERS, "Page number value mismatch");

Document::Document(const std::string fileName,
				   Poco::UInt32 pageCount,
				   Page::Size pageSize,
				   Page::Orientation orientation) :
	_pdf(HPDF_New(HPDF_Error_Handler, nullptr)),
	_fileName(fileName),
	_pRawData(nullptr),
	_size(0)
{
	init(pageCount, pageSize, orientation);
}

Document::Document(Poco::UInt32 pageCount, Page::Size pageSize, Page::Orientation orientation) :
	_pdf(HPDF_New(HPDF_Error_Handler, nullptr)),
	_pRawData(nullptr),
	_size(0)
{
	init(pageCount, pageSize, orientation);
}


Document::~Document()
{
	HPDF_Free(_pdf);
	delete _pRawData;
}

void Document::setPages(std::size_t pagePerPages)
{
	HPDF_SetPagesConfiguration(_pdf, static_cast<HPDF_UINT>(pagePerPages));
}


void Document::setPageLayout(PageLayout pageLayout)
{
	HPDF_SetPageLayout(_pdf, static_cast<HPDF_PageLayout>(pageLayout));
}


Document::PageLayout Document::getPageLayout() const
{
	return static_cast<PageLayout>(HPDF_GetPageLayout(_pdf));
}


void Document::setPageMode(PageMode pageMode)
{
	HPDF_SetPageMode(_pdf, static_cast<HPDF_PageMode>(pageMode));
}


Document::PageMode Document::getPageMode() const
{
	return static_cast<PageMode>(HPDF_GetPageMode(_pdf));
}

/*
void openAction()
{
	HPDF_SetOpenAction(_pdf, HPDF_Destination open_action);
}
*/


const Page& Document::getPage(int index)
{
	return _pages.at(index);
}


const Page& Document::operator [] (int index)
{
	return _pages[index];
}


void Document::compression(Compression mode)
{
	HPDF_SetCompressionMode(_pdf, mode);
}


void Document::addPageLabel(int pageNum, PageNumberStyle style, int firstPage, const std::string& prefix)
{
	HPDF_AddPageLabel(_pdf,
					  static_cast<HPDF_UINT>(pageNum),
					  static_cast<HPDF_PageNumStyle>(style),
					  static_cast<HPDF_UINT>(firstPage),
					  prefix.c_str());
}


void Document::useUTF8Encoding()
{
	HPDF_UseUTFEncodings(_pdf);
}


void Document::useJapaneseFonts()
{
	HPDF_UseJPFonts(_pdf);
}


void Document::useKoreanFonts()
{
	HPDF_UseKRFonts(_pdf);
}


void Document::useChineseFonts()
{
	HPDF_UseCNSFonts(_pdf);
}


void Document::useChineseTraditionalFonts()
{
	HPDF_UseCNTFonts(_pdf);
}


void Document::useJapaneseEncodings()
{
	HPDF_UseJPEncodings(_pdf);
}


void Document::useKoreanEncodings()
{
	HPDF_UseKREncodings(_pdf);
}


void Document::useChineseEncodings()
{
	HPDF_UseCNSEncodings(_pdf);
}


void Document::useChineseTraditionalEncodings()
{
	HPDF_UseCNTEncodings(_pdf);
}


void Document::extendedGraphicState()
{
	HPDF_CreateExtGState(_pdf);
}


const Image& Document::loadPNGImage(const std::string& fileName)
{
	return loadPNGImageImpl(fileName, true);
}


const Image& Document::loadPNGImageInfo(const std::string& fileName)
{
	return loadPNGImageImpl(fileName, false);
}


std::string Document::getInfo(Info info)
{
	return HPDF_GetInfoAttr(_pdf, static_cast<HPDF_InfoType>(info));
}


void Document::setPermission(Permission perm)
{
	HPDF_SetPermission(_pdf, static_cast<HPDF_UINT>(perm));
}


std::size_t Document::pageCount() const
{
	return _pages.size();
}


HPDF_Doc& Document::handle()
{
	return _pdf;
}


void Document::init(Poco::UInt32 pageCount,
	Page::Size pageSize, Page::Orientation orientation)
{
	useUTF8Encoding();
	compression(COMPRESSION_ALL);
	for (Poco::UInt32 i = 0; i < pageCount; ++i)
		addPage(pageSize, orientation);
}


void Document::createNew(bool resetAll)
{
	reset(resetAll);
	HPDF_NewDoc(_pdf);
}


void Document::save(const std::string fileName)
{
	std::string fn = fileName.empty() ? _fileName : fileName;
	if (fn.empty())
		HPDF_SaveToStream (_pdf);
	else
		HPDF_SaveToFile(_pdf, fn.c_str());
}


Document::DataPtr Document::data(SizeType& sz)
{
	sz = size();
	delete _pRawData;
	_pRawData = new HPDF_BYTE[sz];
	HPDF_ReadFromStream(_pdf, _pRawData, &sz);
	return _pRawData;
}


Document::SizeType Document::size()
{
	if (HPDF_Stream_Validate(_pdf->stream)) HPDF_ResetStream(_pdf);
	HPDF_SaveToStream(_pdf);
	return _size = HPDF_GetStreamSize(_pdf);
}


const Page& Document::addPage(Page::Size pageSize, Page::Orientation orientation)
{
	Page page(this, HPDF_AddPage(_pdf), pageSize);
	page.setSizeAndOrientation(pageSize, orientation);
	_pages.push_back(page);
	return _pages.back();
}


const Page& Document::insertPage(int index,
	Page::Size pageSize,
	Page::Orientation orientation)
{
	poco_assert (index > 0);
	poco_assert (index < _pages.size());
	HPDF_Page target = *((HPDF_Page*) HPDF_List_ItemAt(_pdf->page_list, static_cast<HPDF_UINT>(index)));
	return *_pages.insert(_pages.begin() + index,
		Page(this,
			HPDF_InsertPage(_pdf, target),
			pageSize,
			orientation));
}


const Page& Document::getCurrentPage()
{
	Page p(this, HPDF_GetCurrentPage(_pdf));
	auto it = _pages.begin();
	auto end = _pages.end();
	for (;it != end; ++it)
		if (*it == p) return *it;

	throw NotFoundException("Current page not found.");
}


const Font& Document::loadFont(const std::string& name, const std::string& encoding)
{
	Font font(&_pdf,
			  HPDF_GetFont(_pdf, name.c_str(), encoding.empty() ? nullptr : encoding.c_str()));
	std::pair<FontContainer::iterator, bool> ret =
		_fonts.insert(FontContainer::value_type(name, font));

	if (ret.second) return ret.first->second;

	throw IllegalStateException("Could not create font.");
}


const Font& Document::font(const std::string& name, const std::string& encoding)
{
	auto it = _fonts.find(name);
	if (_fonts.end() != it) return it->second;

	return loadFont(name, encoding);
}


std::string Document::loadType1Font(const std::string& afmFileName, const std::string& pfmFileName)
{
	return HPDF_LoadType1FontFromFile(_pdf, afmFileName.c_str(), pfmFileName.c_str());
}


std::string Document::loadTTFont(const std::string& fileName, bool embed, int index)
{
	if (-1 == index)
	{
		return HPDF_LoadTTFontFromFile(_pdf,
			fileName.c_str(),
			embed ? HPDF_TRUE : HPDF_FALSE);
	}
	else if (index >= 0)
	{
		return HPDF_LoadTTFontFromFile2(_pdf,
			fileName.c_str(),
			static_cast<HPDF_UINT>(index),
			embed ? HPDF_TRUE : HPDF_FALSE);
	}
	else
		throw InvalidArgumentException("Invalid font index.");
}


const Image& Document::loadPNGImageImpl(const std::string& fileName, bool doLoad)
{
	Path path(fileName);

	if (File(path).exists())
	{
		std::pair<ImageContainer::iterator, bool> it;
		if (doLoad)
		{
			Image image(&_pdf, HPDF_LoadPngImageFromFile(_pdf, fileName.c_str()));
			it = _images.insert(ImageContainer::value_type(path.getBaseName(), image));
		}
		else
		{
			Image image(&_pdf, HPDF_LoadPngImageFromFile2(_pdf, fileName.c_str()));
			it = _images.insert(ImageContainer::value_type(path.getBaseName(), image));
		}
		if (it.second) return it.first->second;
		else throw IllegalStateException("Could not insert image.");
	}
	else
		throw NotFoundException("File not found: " + fileName);
}


const Image& Document::loadJPEGImage(const std::string& fileName)
{
	Path path(fileName);

	if (File(path).exists())
	{
		Image image(&_pdf, HPDF_LoadJpegImageFromFile(_pdf, fileName.c_str()));
		std::pair<ImageContainer::iterator, bool> it =
			_images.insert(ImageContainer::value_type(path.getBaseName(), image));
		if (it.second) return it.first->second;
		else throw IllegalStateException("Could not insert image.");
	}
	else
		throw NotFoundException("File not found: " + fileName);
}


void Document::encryption(Encryption mode, Poco::UInt32 keyLength)
{
	if (ENCRYPT_R3 == mode && (keyLength < 5 || keyLength > 128))
		throw InvalidArgumentException("Invalid key length.");

	HPDF_SetEncryptionMode(_pdf,
		static_cast<HPDF_EncryptMode>(mode),
		static_cast<HPDF_UINT>(keyLength));
}


const Encoder& Document::loadEncoder(const std::string& name)
{
	auto it = _encoders.find(name);
	if (_encoders.end() == it) return it->second;

	Encoder enc(&_pdf, HPDF_GetEncoder(_pdf, name.c_str()), name);
	std::pair<EncoderContainer::iterator, bool> ret =
		_encoders.insert(EncoderContainer::value_type(name, enc));

	if (ret.second) return ret.first->second;

	throw IllegalStateException("Could not create encoder.");
}


const Encoder& Document::getCurrentEncoder()
{
	HPDF_Encoder enc = HPDF_GetCurrentEncoder(_pdf);
	std::string name = enc->name;
	auto it = _encoders.find(name);
	if (_encoders.end() == it)
	{
		std::pair<EncoderContainer::iterator, bool> ret =
			_encoders.insert(EncoderContainer::value_type(name, Encoder(&_pdf, enc)));

		if (ret.second) return ret.first->second;
	}

	return it->second;
}


const Encoder& Document::setCurrentEncoder(const std::string& name)
{
	loadEncoder(name);
	HPDF_SetCurrentEncoder(_pdf, name.c_str());
	return getCurrentEncoder();
}


const Outline& Document::createOutline(const std::string& title, const Outline& parent, const Encoder& encoder)
{
	_outlines.emplace_back(&_pdf, HPDF_CreateOutline(_pdf, parent, title.c_str(), encoder));
	return _outlines.back();
}


void Document::setInfo(Info info, const LocalDateTime& dt)
{
	if (INFO_CREATION_DATE == info || INFO_MOD_DATE == info)
	{
		HPDF_Date hpdfDate;
		hpdfDate.year = dt.year();
		hpdfDate.month = dt.month();
		hpdfDate.day = dt.day();
		hpdfDate.hour = dt.hour();
		hpdfDate.minutes = dt.minute();
		hpdfDate.seconds = dt.second();
		std::string tzd = DateTimeFormatter::tzdISO(dt.tzd());
		StringTokenizer st(tzd, "+-:");
		if (st.count() >= 2)
		{
			hpdfDate.ind = tzd[0];
			hpdfDate.off_hour = NumberParser::parse(st[0]);
			hpdfDate.off_minutes = NumberParser::parse(st[1]);
		}
		else hpdfDate.ind = ' ';

		HPDF_SetInfoDateAttr(_pdf, static_cast<HPDF_InfoType>(info), hpdfDate);
	}
	else
		throw InvalidArgumentException("Can not set document info.");
}


void Document::setInfo(Info info, const std::string& value)
{
	if (INFO_CREATION_DATE == info || INFO_MOD_DATE == info)
		throw InvalidArgumentException("Can not set document date.");

	HPDF_SetInfoAttr(_pdf, static_cast<HPDF_InfoType>(info), value.c_str());
}


void Document::setPassword(const std::string& ownerPassword, const std::string& userPassword)
{
	if (ownerPassword.empty())
		throw InvalidArgumentException("Owner password empty.");

	HPDF_SetPassword(_pdf, ownerPassword.c_str(), userPassword.c_str());
}


void Document::reset(bool all)
{
	if (!all) HPDF_FreeDoc(_pdf);
	else
	{
		_pages.clear();
		_fonts.clear();
		_encoders.clear();
		_outlines.clear();
		_images.clear();
		HPDF_FreeDocAll(_pdf);
	}
}


} } // namespace Poco::PDF
