/** \file
 *
 *  Contains the DecimationLogManager class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#include "DecimationLogManager.h"

#include "io/LzmaEncoder.h"
#include "logger/AllLogWriter.h"
#include "logger/BinaryLogWriter.h"
#include "logger/DirectoryEntry.h"
#include "logger/DirectoryManagingLogWriter.h"
#include "logger/LinearApproxLogWriter.h"
#include "logger/MostRecentLogWriter.h"
#include "logger/MultiLogWriter.h"
#include "logger/SplitFileLogWriter.h"
#include "Service.h"

DecimationLogManager::DecimationLogManager( LogEngine& logEngine, Str& dataDir )
    : logEngine_( logEngine ),
      dataDir_( dataDir ),
      fileWriters_( true, 5 ),
      otherWriters_( false, 5 ),
      fileNum_( 0 )
{
    otherWriters_[Service::SERVICE_COURIER] = new DirectoryManagingLogWriter( *getFileWriter( Service::SERVICE_COURIER ) );
    otherWriters_[Service::SERVICE_EXPRESS] = new DirectoryManagingLogWriter( *getFileWriter( Service::SERVICE_EXPRESS ) );
    otherWriters_[Service::SERVICE_PRIORITY] = new DirectoryManagingLogWriter( *getFileWriter( Service::SERVICE_PRIORITY ) );
    otherWriters_[Service::SERVICE_NORMAL] = new DirectoryManagingLogWriter( *getFileWriter( Service::SERVICE_NORMAL ) );

    logEngine_.addAssociation( otherWriters_[Service::SERVICE_COURIER],
                               new OrRule( new AndRule( new SyslogSeverityRule( Syslog::CRITICAL, false ),
                                           new ServiceTypeRule( Service::SERVICE_UNSPECIFIED ) ),
                                           new ServiceTypeRule( Service::SERVICE_COURIER ) ) );
    logEngine_.addAssociation( otherWriters_[Service::SERVICE_EXPRESS],
                               new OrRule( new AndRule( new OrRule( new SyslogSeverityRule( Syslog::FAULT, false ),
                                           new SyslogSeverityRule( Syslog::IMPORTANT, false ) ),
                                           new ServiceTypeRule( Service::SERVICE_UNSPECIFIED ) ),
                                           new ServiceTypeRule( Service::SERVICE_EXPRESS ) ) );
    logEngine_.addAssociation( otherWriters_[Service::SERVICE_PRIORITY],
                               new OrRule( new AndRule( new SyslogSeverityRule( Syslog::IMPORTANT, true ),
                                           new ServiceTypeRule( Service::SERVICE_UNSPECIFIED ) ),
                                           new ServiceTypeRule( Service::SERVICE_PRIORITY ) ) );
    logEngine_.addAssociation( otherWriters_[Service::SERVICE_NORMAL],
                               new OrRule( new AndRule( new SyslogSeverityRule( Syslog::IMPORTANT, true ),
                                           new ServiceTypeRule( Service::SERVICE_UNSPECIFIED ) ),
                                           new ServiceTypeRule( Service::SERVICE_NORMAL ) ) );
}

DecimationLogManager::~DecimationLogManager()
{}

void DecimationLogManager::configLog( Service::ServiceType serviceType,
                                      DecimationLogWriter::DecimationType decimationType,
                                      const Str& name, DataValue* parameter, Logger& logger )
{
    Str key = Str( Service::ServiceType2Str( serviceType ) ) + " " + name;
    LogAssociation* association = logAssocationMap_.get( key );

    if( NULL == association )
    {
        association = logEngine_.addAssociation( new MultiLogWriter( *getFileWriter( serviceType ), name ), new DataNameRule( name ) );
        logAssocationMap_.put( key, association );
    }

    LogWriter* logWriter = association->getWriter();
    MultiLogWriter* mLogWriter = dynamic_cast<MultiLogWriter*>( logWriter );

    if( NULL == mLogWriter && NULL != logWriter )
    {
        logger.syslog( "Writer associated with " + key + " is not a multi-log writer!" );
        return;
    }

    DecimationLogWriter* dLogWriter = mLogWriter->getCurrent();

    if( NULL != dLogWriter && dLogWriter->getDecimationType() != decimationType )
    {
        dLogWriter = NULL;
    }

    if( NULL == dLogWriter )
    {
        LogWriter* fileWriter = getFileWriter( serviceType );
        switch( decimationType )
        {
        case DecimationLogWriter::UNDEFINED_DECIMATION:
            break;
        case DecimationLogWriter::NO_DECIMATION:
            break;
        case DecimationLogWriter::ALL_DATA:
            dLogWriter = new AllLogWriter( *fileWriter );
            break;
        case DecimationLogWriter::LINEAR_APPROXIMATION:
            dLogWriter = new LinearApproxLogWriter( *fileWriter, name, parameter, true, logger );
            break;
        case DecimationLogWriter::MOST_RECENT:
            dLogWriter = new MostRecentLogWriter( *fileWriter );
            break;
        case DecimationLogWriter::DECIMATION_COUNT:
            break;
        case DecimationLogWriter::DECIMATION_MULTI:
            break;
        }
        mLogWriter->setCurrent( dLogWriter );
    }
    else
    {
        dLogWriter->setParameter( parameter, logger );
    }
    mLogWriter->setCurrent( dLogWriter );
}

void DecimationLogManager::lockWrites()
{
    for( unsigned int i = 0; i < fileWriters_.size(); ++i )
    {
        SplitFileLogWriter* logWriter = fileWriters_.get( i );
        if( NULL != logWriter )
        {
            logWriter->lockWrites();
        }
    }
}

void DecimationLogManager::unlockWrites()
{
    for( unsigned int i = 0; i < fileWriters_.size(); ++i )
    {
        SplitFileLogWriter* logWriter = fileWriters_.get( i );
        if( NULL != logWriter )
        {
            logWriter->unlockWrites();
        }
    }
}


void DecimationLogManager::splits()
{
    for( unsigned int i = 0; i < fileWriters_.size(); ++i )
    {
        SplitFileLogWriter* logWriter = fileWriters_.get( i );
        if( NULL != logWriter )
        {
            logWriter->split( true, fileNum_++ );
        }
    }
}

void DecimationLogManager::writeHeaders()
{
    for( unsigned int i = 0; i < logAssocationMap_.size(); ++i )
    {
        LogAssociation* association = logAssocationMap_.getIndexed( i );
        MultiLogWriter* logWriter = dynamic_cast<MultiLogWriter*>( association->getWriter() );
        if( NULL != logWriter )
        {
            logWriter->writeHeader();
        }
    }
}

void DecimationLogManager::writeFooters()
{
    for( unsigned int i = 0; i < logAssocationMap_.size(); ++i )
    {
        LogAssociation* association = logAssocationMap_.getIndexed( i );
        MultiLogWriter* logWriter = dynamic_cast<MultiLogWriter*>( association->getWriter() );
        if( NULL != logWriter )
        {
            logWriter->writeFooter();
        }
    }
}

void DecimationLogManager::resetFileBases()
{
    fileNum_ = 0;
    for( unsigned int i = 0; i < fileWriters_.size(); ++i )
    {
        SplitFileLogWriter* logWriter = fileWriters_.get( i );
        if( NULL != logWriter )
        {
            Str name = Service::ServiceType2Str( ( Service::ServiceType )i );
            logWriter->resetFileBase( dataDir_ + "/" + name, fileNum_++ );
        }
    }
}

void DecimationLogManager::resetDirectoryEntries()
{
    for( unsigned int i = 0; i < logAssocationMap_.size(); ++i )
    {
        LogAssociation* association = logAssocationMap_.getIndexed( i );
        MultiLogWriter* mWriter = dynamic_cast<MultiLogWriter*>( association->getWriter() );
        if( NULL != mWriter )
        {
            mWriter->resetDirectoryEntries();
        }
    }
    for( unsigned int i = 0; i < otherWriters_.size(); ++i )
    {
        DirectoryManagingLogWriter* logWriter = otherWriters_.get( i );
        if( NULL != logWriter )
        {
            Str name = Service::ServiceType2Str( ( Service::ServiceType )i );
            logWriter->resetDirectoryEntries();
        }
    }
}

void DecimationLogManager::flush()
{
    for( unsigned int i = 0; i < logAssocationMap_.size(); ++i )
    {
        LogAssociation* association = logAssocationMap_.getIndexed( i );
        MultiLogWriter* mWriter = dynamic_cast<MultiLogWriter*>( association->getWriter() );
        if( NULL != mWriter )
        {
            mWriter->flush();
        }
    }
}


SplitFileLogWriter* DecimationLogManager::getFileWriter( Service::ServiceType serviceType )
{
    SplitFileLogWriter* writer = fileWriters_.get( serviceType );
    if( NULL == writer )
    {
        Str name = Service::ServiceType2Str( serviceType );
        // TODO: Make the option of an LZMA (or gzip, bzip2, etc.) encoder into a config setting.
        if( serviceType <= Service::SERVICE_EXPRESS )
        {
            //logger.syslog( "Writer for level " + name + " will be LZMA encoded.", Syslog::DEBUG );
            //printf( "Writer for level " + name + " will be LZMA encoded." );
            printf( "Writer for level %s will be LZMA encoded.\n", name.cStr() );
            writer = new SplitFileLogWriter( dataDir_ + "/" + name, 10 * 1000 * 1000, new BinaryLogWriter( true ), new LzmaEncoder(), fileNum_++ );
        }
        else // Don't compress (larger) files for lower service levels.
        {
            //logger.syslog( "Writer for level " + name + " will not be LZMA encoded.", Syslog::DEBUG );
            //printf( "Writer for level " + name + " will not be LZMA encoded." );
            printf( "Writer for level %s will not be LZMA encoded.\n", name.cStr() );
            writer = new SplitFileLogWriter( dataDir_ + "/" + name, 10 * 1000 * 1000, new BinaryLogWriter( true ), NULL, fileNum_++ );
        }
        // writer = new SplitFileLogWriter( dataDir_ + "/" + name, 10 * 1000 * 1000, new BinaryLogWriter( true ), serviceType <= Service::SERVICE_EXPRESS ? new LzmaEncoder() : NULL, fileNum_++ );
        fileWriters_[serviceType] = writer;
    }
    return writer;
}


unsigned int DecimationLogManager::forceWrite( DataEntry* dataEntry, Service::ServiceType serviceType )
{
    DirectoryManagingLogWriter* logWriter = otherWriters_.get( serviceType );
    if( NULL != logWriter )
    {
        return logWriter->writeData( dataEntry, NULL );
    }
    return 0;
}

unsigned int DecimationLogManager::writeSyslog( const SyslogEntry* syslogEntry, Service::ServiceType serviceType )
{
    DirectoryManagingLogWriter* logWriter = otherWriters_.get( serviceType );
    if( NULL != logWriter )
    {
        return logWriter->writeSyslog( syslogEntry );
    }
    return 0;
}
