/***************************************************************************
*   Copyright (C) 2003 by                                                 *
*   Jason Kivlighn (jkivlighn@gmail.com)                                  *
*   Unai Garro (ugarro@users.sourceforge.net)                             *
*   Cyril Bosselut (bosselut@b1project.com)                               *
*                                                                         *
*   Copyright (C) 2006 Jason Kivlighn (jkivlighn@gmail.com)               *
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
***************************************************************************/

#include "htmlexporter.h"

#include <tqptrdict.h>
#include <tqimage.h>
#include <tqfileinfo.h>
#include <tqdir.h>
#include <tqstylesheet.h> //for TQStyleSheet::escape() to escape for HTML
#include <dom/dom_element.h>
#include <tqpainter.h>
#include <tqfileinfo.h>

#include <tdeconfig.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdeglobal.h>
#include <tdehtml_part.h>
#include <tdehtmlview.h>
#include <kprogress.h>
#include <tdestandarddirs.h>
#include <kurl.h>
#include <kiconloader.h>

#include "datablocks/mixednumber.h"
#include "backends/recipedb.h"
#include "dialogs/setupdisplay.h"
#include "image.h"
#include "krepagelayout.h"

#include <cmath> //for ceil()

HTMLExporter::HTMLExporter( const TQString& filename, const TQString &format ) :
		BaseExporter( filename, format )
{
	TDEConfig *config = TDEGlobal::config();
	config->setGroup( "Page Setup" );

	//let's do everything we can to be sure at least some layout is loaded
	TQString template_filename = config->readEntry( "Template", locate( "appdata", "layouts/Default.template" ) );
	if ( template_filename.isEmpty() || !TQFile::exists( template_filename ) )
		template_filename = locate( "appdata", "layouts/Default.template" );
	kdDebug() << "Using template file: " << template_filename << endl;

	setTemplate( template_filename );

	//let's do everything we can to be sure at least some layout is loaded
	m_layoutFilename = config->readEntry( "Layout", locate( "appdata", "layouts/Default.klo" ) );
	if ( m_layoutFilename.isEmpty() || !TQFile::exists( m_layoutFilename ) )
		m_layoutFilename = locate( "appdata", "layouts/Default.klo" );
	kdDebug() << "Using layout file: " << m_layoutFilename << endl;
}

HTMLExporter::~HTMLExporter()
{
}

void HTMLExporter::setTemplate( const TQString &filename )
{
	TQFile templateFile( filename );
	if ( templateFile.open( IO_ReadOnly ) ) {
		m_templateFilename = filename;
		m_templateContent = TQString( templateFile.readAll() );
	}
	else
		kdDebug()<<"couldn't find/open template file"<<endl;
}

void HTMLExporter::setStyle( const TQString &filename )
{
	m_layoutFilename = filename;
}

int HTMLExporter::supportedItems() const
{
	int items = RecipeDB::All ^ RecipeDB::Properties;

	TQMap<TQString,bool>::const_iterator it = m_visibilityMap.find("properties");
	if ( it == m_visibilityMap.end() || it.data() == true )
		items |= RecipeDB::Properties;

	return RecipeDB::All;
}

TQString HTMLExporter::createContent( const Recipe& recipe )
{
	TQString templateCopy = m_templateContent;

	storePhoto( recipe );

	populateTemplate( recipe, templateCopy );
	return templateCopy;
}

TQString HTMLExporter::createContent( const RecipeList& recipes )
{
	TQString fileContent;

	RecipeList::const_iterator recipe_it;
	for ( recipe_it = recipes.begin(); recipe_it != recipes.end(); ++recipe_it ) {
		fileContent += createContent(*recipe_it);
	}

	return fileContent;
}

TQString HTMLExporter::createHeader( const RecipeList & )
{
	m_visibilityMap.clear();
	m_columnsMap.clear();

	TDEConfig *config = TDEGlobal::config();
	config->setGroup( "Page Setup" );

	m_error = false;

	if ( m_templateContent.isEmpty() ) {
		TQString errorStr = i18n("<html><body>\n"
			"<p><b>Error: </b>Unable to find a layout file, which is"
			" needed to view the recipe.</p>"
			"<p>Krecipes was probably not properly installed.</p>"
			"</body></html>");
		m_error = true;
		return errorStr;
	}

	TQFile layoutFile( m_layoutFilename );
	TQString error; int line; int column;
	TQDomDocument doc;
	if ( !doc.setContent( &layoutFile, &error, &line, &column ) ) {
		kdDebug()<<"Unable to load style information.  Will create HTML without it..."<<endl;
	}
	else
		processDocument(doc);

	//put all the recipe photos into this directory
	TQDir dir;
	TQFileInfo fi(fileName());
	dir.mkdir( fi.dirPath(true) + "/" + fi.baseName() + "_photos" );

	RecipeList::const_iterator recipe_it;

	TDELocale*loc = TDEGlobal::locale();
	TQString encoding = loc->encoding();

	TQString output = "<html>";
	output += "<head>";
	output += "<meta name=\"lang\" content=\"" + loc->language() + "\">\n";
	output += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
	output += TQString( "<title>%1</title>" ).arg( i18n( "Krecipes Recipes" ) );

	output += "<style type=\"text/css\">\n";

	TQString cssContent;
	TQFileInfo info(m_templateFilename);
	TQFile cssFile(info.dirPath(true) + "/" + info.baseName() + ".css");
	kdDebug()<<info.dirPath(true) + "/" + info.baseName() + ".css"<<endl;
	if ( cssFile.open( IO_ReadOnly ) ) {
		cssContent = TQString( cssFile.readAll() );
	}
	output += cssContent;

	output += m_cachedCSS;
	m_cachedCSS = TQString::null;
	output += "</style>";
	output += "</head>";
	output += "<body class=\"background\">";

	return output;
}

void HTMLExporter::beginObject( const TQString &object )
{
	m_cachedCSS += "."+object+" { \n";
}

void HTMLExporter::endObject()
{
	m_cachedCSS += " } \n";
}

void HTMLExporter::loadBackgroundColor( const TQString &/*object*/, const TQColor& color )
{
	m_cachedCSS += bgColorAsCSS(color);
}

void HTMLExporter::loadFont( const TQString &/*object*/, const TQFont& font )
{
	m_cachedCSS += fontAsCSS(font);
}

void HTMLExporter::loadTextColor( const TQString &/*object*/, const TQColor& color )
{
	m_cachedCSS += textColorAsCSS(color);
}

void HTMLExporter::loadVisibility( const TQString &object, bool visible )
{
	m_cachedCSS += visibilityAsCSS(visible);
	m_visibilityMap.insert(object,visible);
}

void HTMLExporter::loadAlignment( const TQString &/*object*/, int alignment )
{
	m_cachedCSS += alignmentAsCSS(alignment);
}

void HTMLExporter::loadBorder( const TQString &/*object*/, const KreBorder& border )
{
	m_cachedCSS += borderAsCSS(border);
}

void HTMLExporter::loadColumns( const TQString & object, int cols )
{
	m_columnsMap.insert(object,cols);
kdDebug()<<object<<" has "<<cols<<" columns"<<endl;
}

TQString HTMLExporter::createFooter()
{
	if ( m_error )
		return TQString::null;

	return "</body></html>";
}

void HTMLExporter::storePhoto( const Recipe &recipe )
{
	TQImage image;
	TQString photo_name;
	if ( recipe.photo.isNull() ) {
		image = TQImage( defaultPhoto );
		photo_name = "default_photo";
	}
	else {
		image = recipe.photo;
		photo_name = TQString::number(recipe.recipeID);
	}

	//image.smoothScale( phwidth, 0, TQImage::ScaleMax );

	TQFileInfo fi(fileName());
	TQString photo_path = fi.dirPath(true) + "/" + fi.baseName() + "_photos/" + photo_name + ".png";
	if ( !TQFile::exists( photo_path ) ) {
		image.save( photo_path, "PNG" );
	}
}

TQString HTMLExporter::HTMLIfVisible( const TQString &name, const TQString &html )
{
	TQMap<TQString,bool>::const_iterator it = m_visibilityMap.find(name);
	if ( it == m_visibilityMap.end() || it.data() == true )
		return html;
	else
		return TQString::null;
}

void HTMLExporter::populateTemplate( const Recipe &recipe, TQString &content )
{
	TDEConfig * config = TDEGlobal::config();

	//=======================TITLE======================//
	content = content.replace("**TITLE**",HTMLIfVisible("title",recipe.title));

	//=======================INSTRUCTIONS======================//
	TQString instr_html = TQStyleSheet::escape( recipe.instructions );
	instr_html.replace( "\n", "<br />" );
	content = content.replace( "**INSTRUCTIONS**", HTMLIfVisible("instructions",instr_html) );

	//=======================SERVINGS======================//
	TQString yield_html = TQString( "<b>%1: </b>%2" ).arg( i18n( "Yield" ) ).arg( recipe.yield.toString() );
	content = content.replace( "**YIELD**", HTMLIfVisible("yield",yield_html) );

	//=======================PREP TIME======================//
	TQString preptime_html;
	if ( !recipe.prepTime.isNull() && recipe.prepTime.isValid() )
		preptime_html = TQString( "<b>%1: </b>%2" ).arg( i18n( "Preparation Time" ) ).arg( recipe.prepTime.toString( "h:mm" ) );
	content = content.replace( "**PREP_TIME**", HTMLIfVisible("prep_time",preptime_html) );

	//========================PHOTO========================//
	TQString photo_name;
	if ( recipe.photo.isNull() )
		photo_name = "default_photo";
	else
		photo_name = TQString::number(recipe.recipeID);

	TQFileInfo fi(fileName());
	TQString image_url = fi.baseName() + "_photos/" + escape( photo_name ) + ".png";
	image_url = KURL::encode_string( image_url );
	content = content.replace( "**PHOTO**", HTMLIfVisible("photo",image_url) );

	//=======================AUTHORS======================//
	TQString authors_html;

	int counter = 0;
	for ( ElementList::const_iterator author_it = recipe.authorList.begin(); author_it != recipe.authorList.end(); ++author_it ) {
		if ( counter )
			authors_html += ", ";
		authors_html += TQStyleSheet::escape( ( *author_it ).name );
		counter++;
	}
	if ( !authors_html.isEmpty() )
		authors_html.prepend( TQString( "<b>%1: </b>" ).arg( i18n( "Authors" ) ) );
	content = content.replace( "**AUTHORS**", HTMLIfVisible("authors",authors_html) );

	//=======================CATEGORIES======================//
	TQString categories_html;

	counter = 0;
	for ( ElementList::const_iterator cat_it = recipe.categoryList.begin(); cat_it != recipe.categoryList.end(); ++cat_it ) {
		if ( counter )
			categories_html += ", ";
		categories_html += TQStyleSheet::escape( ( *cat_it ).name );
		counter++;
	}
	if ( !categories_html.isEmpty() )
		categories_html.prepend( TQString( "<b>%1: </b>" ).arg( i18n( "Categories" ) ) );

	content = content.replace( "**CATEGORIES**", HTMLIfVisible("categories",categories_html) );

	//=======================INGREDIENTS======================//
	TQString ingredients_html;
	config->setGroup( "Formatting" );

	bool useAbbreviations = config->readBoolEntry("AbbreviateUnits");

	MixedNumber::Format number_format = ( config->readBoolEntry( "Fraction" ) ) ? MixedNumber::MixedNumberFormat : MixedNumber::DecimalFormat;

	TQString ingredient_format = config->readEntry( "Ingredient", "%n%p: %a %u" );

	TQMap<TQString,int>::const_iterator cols_it = m_columnsMap.find("ingredients");
	int cols = 1;
	if ( cols_it != m_columnsMap.end() )
		cols = cols_it.data();
	int per_col = recipe.ingList.count() / cols;
	if ( recipe.ingList.count() % cols != 0 ) //round up if division is not exact
		per_col++;

	int count = 0;
	IngredientList list_copy = recipe.ingList; //simple workaround until I fix iterating over the list dealing with groups
	for ( IngredientList group_list = list_copy.firstGroup(); group_list.count() != 0; group_list = list_copy.nextGroup() ) {
		TQString group = group_list[ 0 ].group; //just use the first's name... they're all the same

		bool loneHeader = false;
		if ( count != 0 && count % per_col == 0 ) {
			loneHeader = true;
			if ( !group.isEmpty() )
				ingredients_html += "</ul>";
			ingredients_html.append("</ul></td><td valign=\"top\"><ul>");
			if ( !group.isEmpty() )
				ingredients_html += "<li style=\"page-break-after: avoid\">" + group + ":</li><ul>";
		}
		else {
			if ( !group.isEmpty() )
				ingredients_html += "<li style=\"page-break-after: avoid\">" + group + ":</li><ul>";
		}

		for ( IngredientList::const_iterator ing_it = group_list.begin(); ing_it != group_list.end(); ++ing_it, ++count ) {
			if ( count != 0 && count % per_col == 0 && !loneHeader ) {
				if ( !group.isEmpty() )
					ingredients_html += "</ul>";
				ingredients_html.append("</ul></td><td valign=\"top\"><ul>");
				if ( !group.isEmpty() )
					ingredients_html += "<ul>";
			}

			TQString amount_str = MixedNumber( ( *ing_it ).amount ).toString( number_format );

			if ( (*ing_it).amount_offset > 0 )
				amount_str += "-"+MixedNumber( ( *ing_it ).amount + ( *ing_it ).amount_offset ).toString( number_format );
			else if ( ( *ing_it ).amount <= 1e-10 )
				amount_str = "";

			TQString unit = ( *ing_it ).units.determineName( ( *ing_it ).amount + ( *ing_it ).amount_offset, useAbbreviations );

			TQString tmp_format( ingredient_format );
			tmp_format.replace( TQRegExp( TQString::fromLatin1( "%n" ) ), TQStyleSheet::escape( ( *ing_it ).name ) );
			tmp_format.replace( TQRegExp( TQString::fromLatin1( "%a" ) ), amount_str );
			tmp_format.replace( TQRegExp( TQString::fromLatin1( "%u" ) ), TQStyleSheet::escape(unit) );
			tmp_format.replace( TQRegExp( TQString::fromLatin1( "%p" ) ), ( ( *ing_it ).prepMethodList.count() == 0 ) ?
			                    TQString::fromLatin1( "" ) : TQString::fromLatin1( "; " ) + TQStyleSheet::escape( ( *ing_it ).prepMethodList.join(",") ) );

			if ( (*ing_it).substitutes.count() > 0 )
				tmp_format += ", "+i18n("or");

			ingredients_html += TQString( "<li>%1</li>" ).arg( tmp_format );

			for ( TQValueList<IngredientData>::const_iterator sub_it = (*ing_it).substitutes.begin(); sub_it != (*ing_it).substitutes.end(); ) {
				TQString amount_str = MixedNumber( ( *sub_it ).amount ).toString( number_format );
	
				if ( (*ing_it).amount_offset > 0 )
					amount_str += "-"+MixedNumber( ( *sub_it ).amount + ( *sub_it ).amount_offset ).toString( number_format );
				else if ( ( *sub_it ).amount <= 1e-10 )
					amount_str = "";
	
				TQString unit = ( *sub_it ).units.determineName( ( *sub_it ).amount + ( *sub_it ).amount_offset, config->readBoolEntry("AbbreviateUnits") );

				TQString tmp_format( ingredient_format );
				tmp_format.replace( TQRegExp( TQString::fromLatin1( "%n" ) ), TQStyleSheet::escape( ( *sub_it ).name ) );
				tmp_format.replace( TQRegExp( TQString::fromLatin1( "%a" ) ), amount_str );
				tmp_format.replace( TQRegExp( TQString::fromLatin1( "%u" ) ), TQStyleSheet::escape(unit) );
				tmp_format.replace( TQRegExp( TQString::fromLatin1( "%p" ) ), ( ( *sub_it ).prepMethodList.count() == 0 ) ?
						TQString::fromLatin1( "" ) : TQString::fromLatin1( "; " ) + TQStyleSheet::escape( ( *sub_it ).prepMethodList.join(",") ) );

				++sub_it;
				if ( sub_it != (*ing_it).substitutes.end() )
					tmp_format += ", "+i18n("or");
				ingredients_html += TQString( "<li>%1</li>" ).arg( tmp_format );
			}
		}

		if ( !group.isEmpty() )
			ingredients_html += "</ul>";
	}
	if ( !ingredients_html.isEmpty() ) {
		ingredients_html.prepend( "<table><tr><td valign=\"top\"><ul>" );
		ingredients_html.append( "</ul></td></tr></table>" );
	}
	content = content.replace( "**INGREDIENTS**", HTMLIfVisible("ingredients",ingredients_html) );

	//=======================PROPERTIES======================//
	TQString properties_html;

	TQStringList hiddenList = config->readListEntry("HiddenProperties");
	IngredientPropertyList visibleProperties;
	for ( IngredientPropertyList::const_iterator prop_it = recipe.properties.begin(); prop_it != recipe.properties.end(); ++prop_it ) {
		if ( hiddenList.find((*prop_it).name) == hiddenList.end() )
			visibleProperties.append( *prop_it );
	}

	cols_it = m_columnsMap.find("properties");
	cols = 1;
	if ( cols_it != m_columnsMap.end() )
		cols = cols_it.data();
	per_col = visibleProperties.count() / cols;
	if ( visibleProperties.count() % cols != 0 ) //round up if division is not exact
		per_col++;

	count = 0;
	for ( IngredientPropertyList::const_iterator prop_it = visibleProperties.begin(); prop_it != visibleProperties.end(); ++prop_it ) {
		if ( count != 0 && count % per_col == 0 )
			properties_html.append("</ul></td><td valign=\"top\"><ul>");

		// if the amount given is <0, it means the property calculator found that the property was undefined for some ingredients, so the amount will be actually bigger

		TQString amount_str;

		double prop_amount = (*prop_it).amount;
		if ( prop_amount > 0 ) { //TODO: make the precision configuratble
			prop_amount = double( tqRound( prop_amount * 10.0 ) ) / 10.0; //not a "chemistry experiment" ;)  Let's only have one decimal place
			amount_str = beautify( TDEGlobal::locale() ->formatNumber( prop_amount, 5 ) );
		}
		else
			amount_str = "0";

		properties_html += TQString( "<li>%1: <nobr>%2 %3</nobr></li>" )
		                   .arg( TQStyleSheet::escape( (*prop_it).name ) )
		                   .arg( amount_str )
		                   .arg( TQStyleSheet::escape( (*prop_it).units ) );

		++count;
	}

	if ( !properties_html.isEmpty() ) {
		properties_html.prepend( "<table><tr><td valign=\"top\"><ul>" );
		properties_html.append( "</ul></td></tr></table>" );
	}
	content = content.replace( "**PROPERTIES**", HTMLIfVisible("properties",properties_html) );

	//=======================RATINGS======================//
	TQString ratings_html;
	if ( recipe.ratingList.count() > 0 )
		ratings_html += TQString("<b>%1:</b>").arg(i18n("Ratings"));

	int rating_total = 0;
	double rating_sum = 0;
	for ( RatingList::const_iterator rating_it = recipe.ratingList.begin(); rating_it != recipe.ratingList.end(); ++rating_it ) {
		ratings_html += "<hr />";

		if ( !( *rating_it ).rater.isEmpty() )
			ratings_html += "<p><b>"+( *rating_it ).rater+"</b></p>";

		if ( (*rating_it).ratingCriteriaList.count() > 0 )
			ratings_html += "<table>";
		for ( RatingCriteriaList::const_iterator rc_it = (*rating_it).ratingCriteriaList.begin(); rc_it != (*rating_it).ratingCriteriaList.end(); ++rc_it ) {
			TQString image_url = fi.baseName() + "_photos/" + TQString::number((*rc_it).stars) + "-stars.png";
			image_url = KURL::encode_string( image_url );
			ratings_html +=  "<tr><td>"+(*rc_it).name+":</td><td><img src=\""+image_url+"\" /></td></tr>";
			if ( !TQFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
				TQPixmap starPixmap = Rating::starsPixmap((*rc_it).stars,true);
				starPixmap.save( fi.dirPath(true) + "/" + image_url, "PNG" );
			}

			rating_total++;
			rating_sum += (*rc_it).stars;
		}
		if ( (*rating_it).ratingCriteriaList.count() > 0 )
			ratings_html += "</table>";

		if ( !( *rating_it ).comment.isEmpty() )
			ratings_html += "<p><i>"+( *rating_it ).comment+"</i></p>";
	}
	content = content.replace( "**RATINGS**", HTMLIfVisible("ratings",ratings_html) );

	TQString overall_html;
	if ( rating_total > 0 ) {
		double average = int(2*rating_sum/rating_total)/2;
		overall_html += TQString("<b>%1:</b>").arg(i18n("Overall Rating"));
		TQString image_url = fi.baseName() + "_photos/" + TQString::number(average) + "-stars.png";
		image_url = KURL::encode_string( image_url );
		overall_html +=  "<img src=\""+image_url+"\" />";
		if ( !TQFile::exists( fi.dirPath(true) + "/" + image_url ) ) {
			TQPixmap starPixmap = Rating::starsPixmap(average,true);
			starPixmap.save( fi.dirPath(true) + "/" + image_url, "PNG" );
		}
	}
	content = content.replace( "**OVERALL_RATING**", HTMLIfVisible("overall_rating",overall_html) );
}

void HTMLExporter::removeHTMLFiles( const TQString &filename, int recipe_id )
{
	TQValueList<int> id;
	id << recipe_id;
	removeHTMLFiles( filename, id );
}

void HTMLExporter::removeHTMLFiles( const TQString &filename, const TQValueList<int> &recipe_ids )
{
	//remove HTML file
	TQFile old_file( filename + ".html" );
	if ( old_file.exists() )
		old_file.remove();

	//remove photos
	for ( TQValueList<int>::const_iterator it = recipe_ids.begin(); it != recipe_ids.end(); ++it ) {
		TQFile photo( filename + "_photos/" + TQString::number(*it) + ".png" );
		if ( photo.exists() )
			photo.remove(); //remove photos in directory before removing it 
	}

	//take care of the default photo
	TQFile photo( filename + "_photos/default_photo.png" );
	if ( photo.exists() ) photo.remove();

	//remove photo directory
	TQDir photo_dir;
	photo_dir.rmdir( filename + "_photos" );

	for ( double d = 0.5; d < 5.5; d += 0.5 ) {
		if ( TQFile::exists(filename+"_photos/"+TQString::number(d)+"-stars.png") ) photo.remove(filename+"_photos/"+TQString::number(d)+"-stars.png");
	}
}

TQString HTMLExporter::escape( const TQString & str )
{
	TQString tmp( str );
	return tmp.replace( '/', "_" );
}
