//
// Copyright (c) 2009-2010 Ronald Kluth
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

/**
 * @file XmlFileWriter.h
 * @author Ronald Kluth
 * @date 2010/03/26
 * @brief Declaration and implementation of class template XmlFileWriter
 * @since 0.1
 */

#ifndef LOG_OUTPUT_XMLFILEWRITER_INCLUDED
#define LOG_OUTPUT_XMLFILEWRITER_INCLUDED

#include "../Record.h"
#include "XmlWriter.h"

#include <cstddef> // size_t
#include <fstream>
#include <stdexcept> // runtime_error
#ifdef _MSC_VER
#include <memory>
#else
#include <tr1/memory>
#endif

namespace Log {

/**
 * @brief Simple log consumer implementation that writes records to an xml stream
 * @tparam InfoListT List of info types to retrieve from log records
 *
 * This class is a simple XML-writing component. The @c consume method
 * transforms each record into XML format by calling methods
 * of the base class @c XmlWriter. Info type elements are added automatically,
 * according to the type list provided as template parameter.
 *
 * As soon as the given file limit is reached, the current file is closed
 * and a new one opened. For this purpose, the file name prefix is appended
 * with a number.
 *
 * @note Be aware that string names of info types should not contain white space
 * because these names are used  as XML tag names.
 */
template < typename InfoListT = Detail::NullList >
class XmlFileWriter : public XmlWriter
{
public:
	/// List of info types to retrieve from log records
	typedef InfoListT InfoListType;
	/// Pointer type to manage XmlFileWriter objects
	typedef std::tr1::shared_ptr< XmlFileWriter< InfoListType > > Ptr;

	/// Creation of XmlFileWriter objects wrapped in a @c shared_ptr
	static Ptr create( const std::string& fileNamePrefix, std::size_t fileRecordLimit,
			const std::string& root = "cpplog", unsigned int indent = 2 )
	{
		return Ptr( new XmlFileWriter( fileNamePrefix, fileRecordLimit, root, indent ) );
	}

	/**
	 * @brief Destruction
	 * 
	 * If the last XML file is still open, the root element is closed,
	 * as well as the file stream.
	 */
	virtual ~XmlFileWriter()
	{
		// finish the last file
		if( fileStream_.is_open() )
		{
			XmlWriter::closeElement( fileStream_, 0, rootElement_ );
			fileStream_.close();
		}
	}

	/// Override of the log consumer interface method
	virtual void consume( const Log::ChannelId channelId, const Record& record )
	{
		if( fileRecordCount_ >= fileRecordLimit_ )
		{
			startNewFile();
		}

		if( fileStream_.good() )
		{
			XmlWriter::startElement( fileStream_, indent_, "record" );
			fileStream_ << std::endl;
			if( ! record.getText().empty() )
			{
				XmlWriter::writeElement( fileStream_, indent_ + indent_, "text", record.getText() );
			}
			XmlWriter::StreamInfoElements addInfo( fileStream_, indent_ + indent_ );
			record.iterateInfo< InfoListType >( addInfo );
			XmlWriter::closeElement( fileStream_, indent_ , "record" );

			++fileRecordCount_;
		}
	}
private:
	std::string fileNamePrefix_;
	std::size_t fileRecordLimit_;
	std::size_t fileRecordCount_;
	std::size_t fileCount_;
	std::ofstream fileStream_;
	std::string rootElement_;
	unsigned int indent_;

private:

	/// Construction only via create()
	XmlFileWriter( const std::string& fileNamePrefix, unsigned int fileRecordLimit,
			const std::string& root = "cpplog", unsigned int indent = 2 )
	:	fileNamePrefix_( fileNamePrefix )
	,	fileRecordLimit_( fileRecordLimit )
	,	fileRecordCount_( 0 )
	,	fileCount_( 0 )
	,	fileStream_()
	,	rootElement_( root )
	,	indent_( indent )
	{
		startNewFile();
	}

	void startNewFile()
	{
		// close root element and previous file stream
		if( fileStream_.is_open() )
		{
			XmlWriter::closeElement( fileStream_, 0, rootElement_ );
			fileStream_.close();
		}

		// create new file name
		std::ostringstream fileName;
		fileName << fileNamePrefix_ << "_" << fileCount_ << ".xml";

		// reset the record counter and increase the file count
		fileRecordCount_ = 0;
		++fileCount_;

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

		// start file with default XML header and open root element
		XmlWriter::writeHeader( fileStream_ );
		XmlWriter::startElement( fileStream_, 0, rootElement_ );
		fileStream_ << std::endl;
	}
};

} // namespace Log

#endif /* LOG_OUTPUT_XMLFILEWRITER_INCLUDED */
