/******************************************************************************/
/* LOGGING SYSTEM - BUILT-IN LOGGERS - TEXT FILE LOGGER ***********************/
/******************************************************************************/

#include <lw/lib/BuiltinLoggers.hh>
#include <lw/lib/SRDParser.hh>
using namespace lw;


namespace {


char const* const V_Name_ = "text-file";
inline T_String Name_( ) { return T_String::Pooled( V_Name_ ); }

bool TFLCPath_( T_SRDParserData const& data )
{
	auto const& ptok( (*data.input)[ 1 ] );
	T_VFSPath path( ptok.stringValue( ) );
	path = path.normalize( );
	if ( path.type( ) != E_VFSPathType::ABSOLUTE ) {
		data.errors.add( "invalid path" , ptok );
	} else {
		auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
		( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setPath( std::move( path ) );
	}
	return true;
}

bool TFLCAppend_( T_SRDParserData const& data )
{
	auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
	( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setAppend( true );
	return true;
}

bool TFLCTruncate_( T_SRDParserData const& data )
{
	auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) );
	( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setAppend( false );
	return true;
}

}


/*= T_TextFileLogWriterFactory ==================================================*/

T_TextFileLogWriterFactory::T_TextFileLogWriterFactory( T_VFS& vfs )
	: A_LogWriterFactory( Name_( ) ) , vfs_( vfs )
{ }

RP_LogWriterConfiguration T_TextFileLogWriterFactory::createConfiguration( T_String const& name ) const
{
	RP_LogWriterConfiguration p( new T_TextFileLogWriterCfg( ) );
	p->setName( name );
	return p;
}

OP_LogWriter T_TextFileLogWriterFactory::createLogWriter( OP_LogWriterConfiguration&& configuration ) const
{
	T_TextFileLogWriter* p( new T_TextFileLogWriter( std::move( configuration ) , vfs_ ) );
	return OwnRawPointer( p );
}

void T_TextFileLogWriterFactory::initializeSyntax( T_SRDParserDefs& , T_SRDContext& main ) const
{
	using namespace lw::SRD;
	main << ( Rule( ) << "file" << Text( ) << TFLCPath_ );
	main << ( Rule( ) << "append" << TFLCAppend_ );
	main << ( Rule( ) << "truncate" << TFLCTruncate_ );
}


/*= T_TextFileLogWriterCfg ======================================================*/

T_TextFileLogWriterCfg::T_TextFileLogWriterCfg( )
	: T_LogWriterConfiguration( Name_( ) )
{ }

T_TextFileLogWriterCfg::T_TextFileLogWriterCfg( T_TextFileLogWriterCfg const& source )
	: T_LogWriterConfiguration( source ) , path_( source.path_ ) ,
		append_( source.append_ )
{ }

OP_LogWriterConfiguration T_TextFileLogWriterCfg::clone( )
{
	T_TextFileLogWriterCfg* ptr( new T_TextFileLogWriterCfg( *this ) );
	return OwnRawPointer( ptr );
}

void T_TextFileLogWriterCfg::check( T_SRDErrors& errors , T_SRDList const& input )
{
	T_LogWriterConfiguration::check( errors , input );
	if ( path_.type( ) == E_VFSPathType::UNKNOWN || path_.elements( ) == 0 ) {
		errors.add( "no file selected" , input[ 0 ] );
	}
}


/*= T_TextFileLogWriter =========================================================*/

T_TextFileLogWriter::T_TextFileLogWriter( OP_LogWriterConfiguration&& configuration , T_VFS& vfs )
	: A_LogWriter( std::move( configuration ) ) , vfs_( vfs )
{ }

void T_TextFileLogWriter::log( T_LogTimestamp const& timestamp ,
		E_LogLevel level , T_LogPath const& path ,
		T_LogStringData const& data , uint32_t size )
{
	using namespace std::chrono;

	char timeBuffer[ 128 ];
	std::time_t tst( T_LogTimestamp::clock::to_time_t( timestamp ) );
	std::strftime( timeBuffer , 128 , "%Y-%m-%d %H:%M:%S" , std::gmtime( &tst ) );
	const auto ms( ( duration_cast< milliseconds >( timestamp - T_LogTimestamp( ) ) ).count( ) % 1000 );

	T_StringBuilder sb;

	sb << timeBuffer << '.';
	if ( ms < 100 ) {
		sb << '0';
		if ( ms < 10 ) {
			sb << '0';
		}
	}
	sb << ms << ' ' << path.toString( ) << " - " << level << ": ";
	sb.append( &( (*data) [ 0 ] ) , size );
	sb << '\n';

	auto const& cfg( configuration< T_TextFileLogWriterCfg >( ) );
	auto const& p( cfg.path( ) );
	if ( !file_ ) {
		vfs_.mkdir( p.parent( ) );
		file_ = vfs_.file( p , cfg.append( ) ? E_FileMode::READ_WRITE : E_FileMode::OVERWRITE );
		if ( !file_ ) {
			disable( );
			return;
		}

		try {
			file_->open( );
		} catch ( X_StreamError const& ) {
			disable( );
			file_.clear( );
			return;
		}
	}

	try {
		file_->position( 0 , true );
		file_->write( sb.data( ) , sb.size( ) );
		file_->flush( );
	} catch ( X_StreamError ) {
		disable( );
		file_.clear( );
		return;
	}
}