//------------------------------------------------------------------------------
//	Copyright (C) 2009, 2010 Humboldt-Universitaet zu Berlin
//
//	This library is free software; you can redistribute it and/or
//	modify it under the terms of the GNU Lesser General Public
//	License as published by the Free Software Foundation; either
//	version 2.1 of the License, or (at your option) any later version.
//
//	This library is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//	Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public
//	License along with this library; if not, write to the Free Software
//	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/**
 * @file XmlWriter.cpp
 * @author Ronald Kluth
 * @date created at 2009/02/18
 * @brief Implementation of class odemx::data::output::XmlWriter
 * @sa XmlWriter.h
 * @since 3.0
 */

#include <odemx/data/output/XmlWriter.h>
#include <odemx/data/output/DefaultTimeFormat.h>
#include <odemx/data/ManagedChannels.h>
#include <odemx/data/Producer.h>
#include <odemx/util/Exceptions.h>
#include <odemx/util/StringConversion.h>
#include <odemx/util/Version.h>
#include <odemx/resources/XmlLog_css.h>
#include <odemx/resources/XmlLog_js.h>

#include <Poco/File.h>
#include <Poco/XML/XMLException.h>

#include <iostream>
#include <fstream>

namespace odemx {
namespace data {
namespace output {

//------------------------------------------------------construction/destruction

std::tr1::shared_ptr< XmlWriter > XmlWriter::create(
		const std::string& fileNamePrefix, unsigned int fileRecordLimit )
{
	return std::tr1::shared_ptr< XmlWriter >(
			new XmlWriter( fileNamePrefix, fileRecordLimit ) );
}

XmlWriter::XmlWriter( const std::string& fileNamePrefix,
		unsigned int fileRecordLimit )
:	fileNamePrefix_( fileNamePrefix )
,	fileRecordLimit_( fileRecordLimit )
,	fileRecordCount_( 0 )
,	lastRecordTime_( static_cast< base::SimTime >(0) )
,	fileStream_()
,	writer_()
,	format_( new DefaultTimeFormat() )
{
	createHtml();
	startNewFile( lastRecordTime_ );
}

XmlWriter::~XmlWriter()
{
	try
	{
		// finish the last file
		if( writer_.get() )
		{
			writer_->endElement( "","","odemxlog" );
			writer_->endDocument();
		}

		// update last file info
		if( ! fileInfos_.empty() )
		{
			// store end time of previous file, if one exists
			fileInfos_.back().endTime = toString( lastRecordTime_ );
		}

		outputFileInfo();
	}
	catch( const Poco::XML::XMLException& ex )
	{
		std::cerr << "~XmlWriter(): exception during destruction: "
				  << ex.displayText();
	}
}

//-----------------------------------------------------------------record output

void XmlWriter::consume( const Log::ChannelId channelId, const SimRecord& record )
{
	// make sure that all records of one point in time are written to the same file
	base::SimTime recordTime = record.getTime();
	const Producer& recordSender = record.getSender();
	if( fileRecordCount_ >= fileRecordLimit_ && recordTime > lastRecordTime_ )
	{
		startNewFile( recordTime );
	}

	try
	{
		attrs_.clear();
		writer_->startElement( "","","record", attrs_ );
		writer_->dataElement( "","","time", format_->timeToString( recordTime ) );
		writer_->dataElement( "","","channel", channel_id::toString( channelId ) );
		writer_->dataElement( "","","text", record.getText().c_str() );
		writer_->dataElement( "","","scope",
				( record.hasScope() ? record.getScope().toString() : "" ) );
		writer_->dataElement( "","","senderlabel", recordSender.getLabel() );
		writer_->dataElement( "","","sendertype", recordSender.getType().toString() );

		if( record.hasDetails() )
		{
			const SimRecord::DetailVec& details = record.getDetails();
			for( SimRecord::DetailVec::const_iterator detailIter = details.begin();
				detailIter != details.end(); ++detailIter )
			{
				writer_->startElement( "","","detail" );
				writer_->dataElement( "","","name", detailIter->first.c_str() );
				writer_->dataElement( "","","value",
						detailIter->second.convert< std::string >() );
				writer_->endElement( "","","detail" );
			}
		}
		writer_->endElement( "","","record" );
	}
	catch( const Poco::XML::XMLException& ex )
	{
		throw DataOutputException( std::string( "XmlWriter::consume(): " )
				+ ex.displayText() );
	}

	++fileRecordCount_;
	lastRecordTime_ = recordTime;
}

//-------------------------------------------------------------------file switch

void XmlWriter::startNewFile( base::SimTime currentRecordTime )
{
	try
	{
		// finish previous xml file, if one exists
		if( writer_.get() )
		{
			writer_->endElement( "","","odemxlog" );
			writer_->endDocument();
		}

		// close previous file
		if( fileStream_.is_open() )
		{
			fileStream_.close();
		}

		// store info for previous file
		if( ! fileInfos_.empty() )
		{
			// store end time of previous file, if one exists
			fileInfos_.back().endTime = toString( lastRecordTime_ );
		}

		// create new file name and info with start time of the current record
		std::string file = fileNamePrefix_ + "_" + toString( fileInfos_.size() ) + ".xml";
		fileInfos_.push_back( FileInfo( file, toString( currentRecordTime ) ) );

		// reset stream flags and open new file
		fileStream_.clear();
		fileStream_.open( file.c_str() );
		if( ! fileStream_ )
		{
			throw  DataOutputException(	std::string(
					"XmlWriter::startNewFile(): failed to open file " ) + file );
		}

		// get a new XMLWriter, automatically destroys the old one
		writer_.reset(
				new Poco::XML::XMLWriter( fileStream_,
						Poco::XML::XMLWriter::WRITE_XML_DECLARATION |
						Poco::XML::XMLWriter::PRETTY_PRINT ) );

		writer_->startDocument();
		writer_->startElement( "","","odemxlog" );
	}
	catch( const Poco::XML::XMLException& ex )
	{
		throw DataOutputException( std::string(
				"XmlWriter::startNewFile(): " ) + ex.displayText() );
	}

	// reset current file record counter
	fileRecordCount_ = 0;
}

//--------------------------------------------------------------file info output

void XmlWriter::outputFileInfo()
{
	// build file name
	std::string file = fileNamePrefix_ + "_file_info.xml";
	std::ofstream xmlStream( file.c_str(), std::ios_base::out | std::ios_base::trunc );
	if( ! xmlStream )
	{
		throw DataOutputException( std::string( "XmlWriter::outputFileInfo():"
				" failed to open file: " ) + file );
	}

	// output is not done with a Poco XMLWriter because it crashed with
	// a pure virtual method call when used in the destructor of this class

	xmlStream << "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" << std::endl;
	xmlStream << "<odemxlogfiles>" << std::endl;

	for( FileInfoVec::const_iterator fileIter = fileInfos_.begin();
		fileIter != fileInfos_.end(); ++fileIter )
	{
		xmlStream << "<file name=\"" << fileIter->name << "\" ";
		xmlStream << "starttime=\"" << fileIter->startTime << "\" ";
		xmlStream << "endtime=\"" << fileIter->endTime << "\" />" << std::endl;
	}

	xmlStream << "</odemxlogfiles>" << std::endl;
	xmlStream.close();
}

//----------------------------------------------------------------xhtml creation

void writeFilterToggle( Poco::XML::XMLWriter& xhtml, Poco::XML::AttributesImpl& attrs,
		const std::string& id, const std::string& menuOption )
{
	attrs.addAttribute( "","","id", "CDATA", id );
	attrs.addAttribute( "","","class", "CDATA", "filterToggle" );
	xhtml.startElement( "","","div", attrs );
	xhtml.characters( menuOption );
	xhtml.dataElement( "","","div", " ", "id", id + "Display", "class", "filterChoice" );
	xhtml.endElement( "","","div" );
}

void copyCss()
{
  try {
    std::ofstream xmllogcss("XmlLog.css", std::ios_base::out | std::ios_base::trunc);
    xmllogcss << resources::XmlLog_css::val;
    //Poco::File css( getLibraryPath() + "/data/output/css/XmlLog.css" );
    //css.copyTo( "." );
  } catch ( ... ) {
    std::cerr << "Fatal error: XmlLog.css can't be found. Did you set ODEMX_HOME?" << std::endl;
    throw;
  }
}

void copyJs()
{
  try {
    std::ofstream xmllogjs("XmlLog.js", std::ios_base::out | std::ios_base::trunc);
    xmllogjs << resources::XmlLog_js::val;
    //Poco::File js( getLibraryPath() + "/data/output/javascript/XmlLog.js" );
    //js.copyTo( "." );
  } catch ( Poco::FileNotFoundException ) {
    std::cerr << "Fatal error: XmlLog.js can't be found. Did you set ODEMX_HOME?" << std::endl;
    throw;
  }
}

void XmlWriter::createHtml()
{
	copyCss();
	copyJs();

	try
	{
		std::string file = fileNamePrefix_ + ".html";
		std::ofstream fileStream( file.c_str(), 
					  std::ios_base::out | std::ios_base::trunc );
		if( ! fileStream )
		{
			throw DataOutputException( std::string( "XmlWriter::createHtml():"
					" failed to open file: " ) + file );
		}

		Poco::XML::XMLWriter xhtml( fileStream,
				Poco::XML::XMLWriter::CANONICAL |
				Poco::XML::XMLWriter::PRETTY_PRINT );
		xhtml.startDocument();
		xhtml.startDTD( "html", "-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
				"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"", "" );
		xhtml.endDTD();

		attrs_.clear();
		attrs_.addAttribute( "","","xmlns", "CDATA", "http://www.w3.org/1999/xhtml" );
		xhtml.startElement( "","","html", attrs_ );
		xhtml.startElement( "","","head" );

		xhtml.dataElement( "","","meta", "", "http-equiv", "Content-Script-Type",
				"content", "text/javascript" );
		xhtml.dataElement( "","","link", "", "rel", "stylesheet", "href", "XmlLog.css" );

		attrs_.clear();
		attrs_.addAttribute( "","","type", "CDATA", "text/javascript" );
		xhtml.startElement( "","","script", attrs_ );

		std::ostringstream javaScript;
		javaScript
		<< "var xmlListFile = '" << fileNamePrefix_ << "_file_info.xml'; "
		<< "var xmlLogScriptIncluded = false; //";

		xhtml.rawCharacters( javaScript.str() );
		xhtml.endElement( "","","script" );

		attrs_.addAttribute( "","","src", "CDATA", "XmlLog.js" );
		xhtml.startElement( "","","script", attrs_ );
		xhtml.endElement( "","","script" );

		attrs_.removeAttribute( "src" );
		xhtml.startElement( "","","script", attrs_ );

		javaScript.str( "" );
		javaScript << "if( ! xmlLogScriptIncluded ) "
				"{ alert( 'JavaScript file XmlLog.js not found!' ); } //";

		xhtml.rawCharacters( javaScript.str() );
		xhtml.endElement( "","","script" );

		xhtml.endElement( "","","head" );


		attrs_.clear();
		attrs_.addAttribute( "","","onload", "CDATA", "init();" );
		xhtml.startElement( "","","body", attrs_ );

		attrs_.clear();
		attrs_.addAttribute( "","","id", "CDATA", "caption" );
		xhtml.startElement( "","","div", attrs_ );
		xhtml.dataElement( "","","h2", "ODEMx XML Log");
		xhtml.dataElement( "","","h2", std::string( "ODEMx Version: " )
				+ Version::getString() );
		xhtml.endElement( "","","div" );

		attrs_.clear();
		attrs_.addAttribute( "","","id", "CDATA", "nav" );
		xhtml.startElement( "","","div", attrs_ );
		attrs_.clear();
		attrs_.addAttribute( "","","id", "CDATA", "filterNav" );
		xhtml.startElement( "","","div", attrs_ );

		xhtml.characters( "Filter >>" );
		attrs_.clear();
		attrs_.addAttribute( "","","id", "CDATA", "markNav" );
		attrs_.addAttribute( "","","class", "CDATA", "subNav" );
		xhtml.startElement( "","","div", attrs_ );

		writeFilterToggle( xhtml, attrs_, "mtn", "Record Text" );
		writeFilterToggle( xhtml, attrs_, "mti", "Record Channel" );
		writeFilterToggle( xhtml, attrs_, "mts", "Record Scope" );
		writeFilterToggle( xhtml, attrs_, "sl", "Sender Label" );
		writeFilterToggle( xhtml, attrs_, "st", "Sender Type" );
		xhtml.dataElement( "","","div", "Reset", "id", "filterReset" );

		xhtml.endElement( "","","div" ); // markNav
		xhtml.endElement( "","","div" ); // filterNav
		xhtml.dataElement( "","","div", "Toggle JS Log", "id", "logToggle" );
		xhtml.endElement( "","","div" ); // nav

		attrs_.clear();
		attrs_.addAttribute( "","","id", "CDATA", "timeNav" );
		xhtml.startElement( "","","div", attrs_ );
		xhtml.characters( "Simulation Period" );
		xhtml.dataElement( "","","div", " ", "id", "periodSelect" );
		xhtml.endElement( "","","div" ); // timeNav
		xhtml.dataElement( "","","div", " ", "id", "log" );
		xhtml.dataElement( "","","div", " ", "id", "info" );
		xhtml.dataElement( "","","div", " ", "id", "content" );

		xhtml.endElement( "","","body" );
		xhtml.endElement( "","","html" );
		xhtml.endDocument();

		fileStream.close();
	}
	catch( const Poco::XML::XMLException& ex )
	{
		throw DataOutputException( std::string( "XmlWriter::createHtml(): " )
				+ ex.displayText() );
	}
}

//-----------------------------------------------------------------------setters

void XmlWriter::setTimeFormat( TimeFormat* timeFormat )
{
	format_.reset( timeFormat );
}

} } } // namespace odemx::data::output
