/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbCore module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

#include "hbtypefaceinfo_p.h"
#include "hbtypefacexmlparser_p.h"
#include <QMap>
#include <QFontMetrics>
#include <QFontInfo>
#include <QRegExp>


// use this only if you want to measure and output metrics
//#define HBTYPEFACEINFO_MEASURE_METRICS
//#define HBTYPEFACEINFO_OUTPUT_METRICS

#undef HBTYPEFACEINFO_DEBUG_ENABLE
//#define HBTYPEFACEINFO_DEBUG_ENABLE

#ifdef  HBTYPEFACEINFO_OUTPUT_METRICS
#include <QXmlStreamWriter>
#endif

#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
#include <QtDebug>
#endif



// Recognized texts for values of role strings
#define PRIMARY_STRING "primary"
#define SECONDARY_STRING "secondary"
#define PRIMARYSMALL_STRING "primarysmall"
#define TITLE_STRING "title"
#define DIGITAL_STRING "digital"
#define UNDEFINED_STRING "undefined"

#define TYPEFACE_METRICS_FILE_STEM ":typeface_metrics_"
#define LARGEST_SIZE 100
// Following must be greater than 0
#define SMALLEST_SIZE 1

class HbTypefaceVariant
{
public:
	HbTypefaceVariant(const QString& typeface, bool usedBold)
	:
	mTypefaceFamilyName(typeface),
	mUsedBold(usedBold)
	{
	};
	HbTypefaceVariant()
	:
	mTypefaceFamilyName(""),
	mUsedBold(false)
	{
	};
public:
	QString mTypefaceFamilyName;
	bool mUsedBold;
};

class HbDownsizeInfo
{
public:
    class HbTypefaceMeasureInfo 
    {
    public:
        HbTypefaceMeasureInfo();
        HbTypefaceMeasureInfo(const HbTypefaceMeasureInfo &);
        ~HbTypefaceMeasureInfo();
    public:
        int pixelSize;
        int measuredAscent;
    };

public:
	HbDownsizeInfo(QString typefaceFamily);
    ~HbDownsizeInfo();

	bool readTypefaceMetricsFile();
	void autoGenerateMetrics();

private:
    void outputMetrics() const;

public: // data
	QString mTypefaceFamily;
	QMap<int, HbTypefaceMeasureInfo> mDownSizeTable;
	int mLowestExtent;
	int mHighestExtent;
};


HbDownsizeInfo::HbTypefaceMeasureInfo::HbTypefaceMeasureInfo()
:
pixelSize(0),
measuredAscent(0)
{
}

HbDownsizeInfo::HbTypefaceMeasureInfo::HbTypefaceMeasureInfo(const HbTypefaceMeasureInfo &other)
:
pixelSize(other.pixelSize),
measuredAscent(other.measuredAscent)
{
}

HbDownsizeInfo::HbTypefaceMeasureInfo::~HbTypefaceMeasureInfo()
{
}


HbDownsizeInfo::HbDownsizeInfo(QString typefaceFamily)
	:
	mTypefaceFamily(typefaceFamily),
	mLowestExtent(LARGEST_SIZE+1),
	mHighestExtent(SMALLEST_SIZE-1)
{
#ifndef HBTYPEFACEINFO_MEASURE_METRICS
	if (!readTypefaceMetricsFile()) {
		autoGenerateMetrics();
    }
#else
	autoGenerateMetrics();
#endif // HBTYPEFACEINFO_MEASURE_METRICS

#ifdef HBTYPEFACEINFO_OUTPUT_METRICS
    outputMetrics();
#endif // HBTYPEFACEINFO_OUTPUT_METRICS
}

HbDownsizeInfo::~HbDownsizeInfo()
{
}

void HbDownsizeInfo::outputMetrics() const
{
#ifdef HBTYPEFACEINFO_OUTPUT_METRICS
    QString filePath; // start with windows only
    filePath.append("c:\\temp\\");
    filePath.append("typeface_metrics_");
    filePath.append(mTypefaceFamily.toLower().replace(QRegExp("\\s+"), QString("_")));
    filePath.append(".xml");

    QFile file(filePath);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        return;
    }

    QXmlStreamWriter xmlWriter(&file);

    xmlWriter.setAutoFormatting(true);
    xmlWriter.writeStartDocument();
    xmlWriter.writeComment("this file is automatically generated by HBTYPEFACEINFO_MEASURE_METRICS mode");
    xmlWriter.writeCharacters("\n"); // is this platform neutral?

    xmlWriter.writeStartElement("typeface_information");

    xmlWriter.writeStartElement("typeface_metrics");
    xmlWriter.writeAttribute("family", mTypefaceFamily);

    QMap<int, HbTypefaceMeasureInfo>::const_iterator iter;

    for(iter = mDownSizeTable.constBegin(); iter != mDownSizeTable.constEnd(); iter++) {
        int key = iter.key();
        HbTypefaceMeasureInfo value = iter.value();

        xmlWriter.writeStartElement("metric");
        xmlWriter.writeAttribute("textheight", QString::number(key));
        xmlWriter.writeAttribute("size", QString::number(value.pixelSize));
        xmlWriter.writeAttribute("baseline", QString::number(0));

        xmlWriter.writeEndElement();
    }

    xmlWriter.writeEndDocument();
    file.close();

#endif // HBTYPEFACEINFO_OUTPUT_METRICS
}

bool HbDownsizeInfo::readTypefaceMetricsFile()
{
	int numPoints(0);

	HbTypefaceXmlParser *parser = new HbTypefaceXmlParser();

	QString typefaceMetricsFileName(TYPEFACE_METRICS_FILE_STEM);
	// replace whitespace with underscores and append
	typefaceMetricsFileName.append(mTypefaceFamily.toLower().replace(QRegExp("\\s+"), QString("_")));
	typefaceMetricsFileName.append(".xml");

#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
    qDebug("HbDownsizeInfo::readTypefaceMetricsFile: typeface metric filename: %s", typefaceMetricsFileName.toAscii().constData());
#endif
	parser->setFilePath(typefaceMetricsFileName);

	if (parser->init()){
#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
    qDebug( "HbDownsizeInfo::readTypefaceMetricsFile: parser init() ok");
#endif
		QString family;
		HbTypefaceXmlParser::StartElement startEl(HbTypefaceXmlParser::Undefined);
		while ((startEl = parser->readToStartElement()) != HbTypefaceXmlParser::NoStartElement) {
			if (startEl == HbTypefaceXmlParser::TypefaceMetrics) {
#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
                qDebug( "HbDownsizeInfo::readTypefaceMetricsFile: start element: typeface metrics");
#endif
				if (parser->metricsTypefaceFamily().toLower()==mTypefaceFamily.toLower()) {
				    int textHeight(0);
				    int baseline(0);
                    HbTypefaceMeasureInfo readMetrics;
					while (parser->readMetric(textHeight, readMetrics.pixelSize, baseline)) {
						numPoints++;
						mDownSizeTable.insert(textHeight, readMetrics);
						mLowestExtent = qMin(mLowestExtent, textHeight); // Stored so that we can extrapolate from the lowest value
						mHighestExtent = qMax(mHighestExtent, textHeight); // Stored so that we can extrapolate from the highest value

#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
			qDebug( "HbDownsizeInfo::readTypefaceMetricsFile: metric %d: extent = %d size = %d baseline = %d", numPoints, textHeight, readMetrics.pixelSize, baseline);
#endif
					}
				}
			}
		}
	}

	parser->close();
	delete parser;

	return numPoints > 0;
}
void HbDownsizeInfo::autoGenerateMetrics()
{
#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
    qDebug( "HbDownsizeInfo::autoGenerateMetrics: WARNING: auto generating metrics, this is inefficient!");
#endif
	QFont f(mTypefaceFamily);

    HbTypefaceMeasureInfo lastMetrics;
    HbTypefaceMeasureInfo measuredMetrics;

	int lastExtentFilled = 0;
	mDownSizeTable.insert(lastExtentFilled, lastMetrics);

#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
    qDebug( "HbDownsizeInfo::autoGenerateMetrics: extent = %d size = %d measured ascent = %d", lastExtentFilled, lastMetrics.pixelSize, lastMetrics.measuredAscent);
#endif

	int size;

	for (size = SMALLEST_SIZE; size <= LARGEST_SIZE; size++) {
        measuredMetrics.pixelSize = size;
        f.setPixelSize(size);
		QFontMetrics fm(f);
		int thisExtent = fm.height();
        measuredMetrics.measuredAscent = fm.ascent(); // not actually measuring it, just taking it from the metrics for now
		for (int extent = lastExtentFilled+1; extent < thisExtent; extent++){
			mDownSizeTable.insert(extent, lastMetrics);
#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
            qDebug( "HbDownsizeInfo::autoGenerateMetrics: fill in: extent = %d size = %d measured ascent = %d", extent, lastMetrics.pixelSize, lastMetrics.measuredAscent);
#endif
		}
		// fill in this one
		mDownSizeTable.insert(thisExtent, measuredMetrics);
#ifdef HBTYPEFACEINFO_DEBUG_ENABLE
        qDebug( "HbDownsizeInfo::autoGenerateMetrics: extent = %d size = %d measured ascent = %d", thisExtent, measuredMetrics.pixelSize, measuredMetrics.measuredAscent);
#endif
		lastExtentFilled = thisExtent;
		lastMetrics.pixelSize = measuredMetrics.pixelSize;
		lastMetrics.measuredAscent = measuredMetrics.measuredAscent;
	}
	mHighestExtent = lastExtentFilled; // Stored so that we can extrapolate from the last mapped value
}

class HbTypefaceInfoPrivate : public QObject
{
    Q_OBJECT

public:
    HbTypefaceInfoPrivate() : initialized(false) {}
    ~HbTypefaceInfoPrivate();
    void initialize();
    static bool encodeRole(const QString &roleName, HbFontSpec::Role &role);

public: // data
    bool initialized;
    QMap<int,HbTypefaceVariant> mTypefaces;
    QMap<QString, HbDownsizeInfo*> mFontTable;
};

HbTypefaceInfoPrivate::~HbTypefaceInfoPrivate()
{
    QMapIterator<QString, HbDownsizeInfo*> i(mFontTable);
    while (i.hasNext()) {
        i.next();
        delete(i.value());
    }
    mFontTable.clear();
}

bool HbTypefaceInfoPrivate::encodeRole(const QString& roleName, HbFontSpec::Role &role)
{
	bool encoded(true); // return value

	if (roleName.toLower() == PRIMARY_STRING) {
		role = HbFontSpec::Primary;
	}
	else if (roleName.toLower() == SECONDARY_STRING) {
		role = HbFontSpec::Secondary;
	}
	else if (roleName.toLower() == TITLE_STRING) {
		role = HbFontSpec::Title;
	}
	else if (roleName.toLower() == PRIMARYSMALL_STRING) {
		role = HbFontSpec::PrimarySmall;
	}
	else if (roleName.toLower() == DIGITAL_STRING) {
		role = HbFontSpec::Digital;
	}
	else if (roleName.toLower() == UNDEFINED_STRING) {
		role = HbFontSpec::Undefined; // Translated if someone passes the string
	}
	else { // Really undefined
		role = HbFontSpec::Undefined;
		encoded = false;
	}

	return encoded;
}

void HbTypefaceInfoPrivate::initialize()
{
    bool success(true);
    HbTypefaceXmlParser *parser = new HbTypefaceXmlParser();
    if (!parser->init()){
    	// ignore errors
    	success = false;
    }
    else {
        QString role;
        QString family;
        bool isBold;
        while (parser->readMapping( role, family, isBold)) {
        	HbFontSpec::Role roleEnum;
        	if (encodeRole(role, roleEnum)){
        		mTypefaces[roleEnum] = HbTypefaceVariant(family, isBold);
        	}
        	else {
        		// role might be an alias.  Not required functionality. Ignore
        	}
        }
    }

    parser->close();
    delete parser;

	// Secondary chosen as default it not specified in the xml
	if (!mTypefaces.contains(HbFontSpec::Undefined) && mTypefaces.contains(HbFontSpec::Secondary)) {
		mTypefaces[HbFontSpec::Undefined] = mTypefaces[HbFontSpec::Secondary];
	}

    initialized = success;
    return;
}




HbTypefaceInfo::HbTypefaceInfo( QObject *parent )
    : QObject( parent ),
    d( new HbTypefaceInfoPrivate )
{
	d->initialize();
}


HbTypefaceInfo::~HbTypefaceInfo()
{
    delete d;
}

void HbTypefaceInfo::roleToTypeface(HbFontSpec::Role role, QString& typefaceFamily, int& weight) const
{
	bool validRole = d->mTypefaces.contains(role);
	if (!validRole) {
		role = HbFontSpec::Undefined;
	}
	HbTypefaceVariant choice = d->mTypefaces.value(role);
	typefaceFamily = choice.mTypefaceFamilyName;
	weight = choice.mUsedBold ? QFont::Bold : QFont::Normal;

	return;
}

int HbTypefaceInfo::textHeightToSizeInPixels(
	const QString& typefaceFamily,
	int weight,
	qreal textHeight) const
{
	Q_UNUSED(weight);
	HbDownsizeInfo *downsizeInfo;

	if (d->mFontTable.contains( typefaceFamily )) {
		downsizeInfo = d->mFontTable[typefaceFamily];
	} else {
		downsizeInfo = new HbDownsizeInfo(typefaceFamily);
		d->mFontTable.insert(typefaceFamily, downsizeInfo);
	}

	int size(-1);

	int flooredPaneHeight = qRound(textHeight-0.5);
	if (downsizeInfo->mDownSizeTable.contains(flooredPaneHeight)) {
		size = downsizeInfo->mDownSizeTable.value(flooredPaneHeight).pixelSize;
	} else if (textHeight<downsizeInfo->mLowestExtent) { // Assumed that downsize info goes quite low, below really usable fonts
		size = qRound(textHeight);
	} else if (textHeight<downsizeInfo->mHighestExtent) { // interpolate. May fail and leave size unset.
        QMap<int, HbDownsizeInfo::HbTypefaceMeasureInfo> &map = downsizeInfo->mDownSizeTable;
		QMap<int, HbDownsizeInfo::HbTypefaceMeasureInfo>::const_iterator lowerB = map.lowerBound(flooredPaneHeight);
		QMap<int, HbDownsizeInfo::HbTypefaceMeasureInfo>::const_iterator upperB = map.upperBound(flooredPaneHeight);
		if (lowerB != map.constEnd() && upperB != map.constEnd() ) {
			int k1 = lowerB.key();
			int v1 = lowerB.value().pixelSize;
			int k2 = upperB.key();
			int v2 = upperB.value().pixelSize;
			int diff = k2 - k1;
			if (diff > 0) {
				size = qRound(v1 + (flooredPaneHeight - k1)*(v2 - v1)/diff);
			}
		}
	}
	// Come here for text heights that are too large for the table, or for failure of interpolation
	if (size == -1) {
		size = qRound( textHeight
			* downsizeInfo->mDownSizeTable.value(downsizeInfo->mHighestExtent).pixelSize
			/ downsizeInfo->mHighestExtent);
	}

    // If the caller intends non-zero size, and zero would be returned by downsizing, then return 1. 
    // This avoids QFont's special treatment of zero size.
    if(textHeight > 0 && size <= 0) {
        size = 1; 
    }
	return size;
}

#include "hbtypefaceinfo.moc"
