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

#include "HDF5LogWriter.h"
#include "data/DataValue.h"
#include "data/ElementURI.h"
#include "data/Slate.h"
#include "data/UniversalURI.h"
#include "io/OutStream.h"
#include "logger/DataEntry.h"
#include "units/Units.h"

#include <stdio.h>

#define MAX_CODES (ElementURI::NO_CODE + 1)

/// Streamless Constructor
HDF5LogWriter::HDF5LogWriter( const Str& filename, bool matlab, int varCount, char** varList, char** unitList,
                              int attCount, char** attList )
    : FilterLogWriter( NULL, varCount, varList, unitList, false, attCount, attList ),
      filename_( filename ),
      hdf5File_( new HDF5File( filename.cStr(), matlab ) ),
      hdf5LogWriterInitialized_( false ),
      finalized_( false ),
      vars_( new HDF5Dataset * [ varCount + attCount] ),
      timeVars_( new HDF5Dataset * [ varCount + attCount] )
{
    for( int i = 0; i < varCount_ + attCount_; ++i )
    {
        vars_[i] = NULL;
        timeVars_[i] = NULL;
    }
}

/// Destructor
HDF5LogWriter::~HDF5LogWriter()
{
    if( !finalized_ )
    {
        writeFooter();
    }
    delete[] vars_;
    delete[] timeVars_;
    delete hdf5File_;
}

unsigned int HDF5LogWriter::writeHeader()
{
    initializeHDF5LogWriter();
    if( hdf5LogWriterInitialized_ )
    {
        hdf5LogWriterInitialized_ = hdf5File_->writeHeader();
    }
    return 0;
}

unsigned int HDF5LogWriter::write( const LogEntry *entry, const Unit* unit )
{
    // No mutex lock because super is called below...
    // MutexLocker lock( writeMutex_ );

    if( !initializedFilterLogWriter_ && slateElementURICount_ != Slate::GetElementURICount() )
    {
        initializeFilterLogWriter();
    }
    if( !initializedFilterLogWriter_ )
    {
        return 0;
    }
    if( !hdf5LogWriterInitialized_ )
    {
        writeHeader();
    }
    if( !hdf5LogWriterInitialized_ )
    {
        return 0;
    }

    return FilterLogWriter::write( entry, unit );

}

// Override this method to do something special with data entries
unsigned int HDF5LogWriter::writeData( const DataEntry *dataEntry, int index, const Unit* unit )
{
    if( NULL !=  vars_[ index ] && NULL != vars_[index]->getDataValue() )
    {
        if( NULL != dataEntry )
        {
            dataEntry->getDataValue()->copyTo( *( vars_[ index ]->getDataValue() ) );
        }
        else
        {
            const Unit& unit = vars_[ index ]->getDataValue()->getUnit();
            vars_[ index ]->getDataValue()->setFrom( unit, nan( "" ) );
        }
        timeVars_[ index ]->getDataValue()->setFrom( Units::SECOND, dataEntry->getTimestamp().asDouble() );
        vars_[ index ]->writeDataValue();
        timeVars_[ index ]->writeDataValue();
    }
    return 0;

}

// Override this method to do something special with attribute entries
unsigned int HDF5LogWriter::writeAtt( const DataEntry *dataEntry, int attIndex, const Unit* unit )
{
    return writeData( dataEntry, varCount_ + attIndex, unit );
}

unsigned int HDF5LogWriter::writeFooter()
{
    if( wroteFooter_ )
    {
        return 0;
    }
    wroteFooter_ = true;

    if( hdf5LogWriterInitialized_ )
    {
        finalized_ = hdf5File_->finalize();
    }
    else
    {
        printf( "No HDF5 file written\n" );
        for( int i = 0; i < varCount_; ++i )
        {
            if( uriCodes_[ i ] == ElementURI::NO_CODE )
            {
                printf( "Could not find variable \"%s\"\n", varList_[ i ] );
            }
        }
        if( initializedFilterLogWriter_ )
        {
            for( int i = 0; i < varCount_; ++i )
            {
                if( NULL == vars_[i] )
                {
                    printf( "Never ran writer for variable \"%s\"\n", varList_[ i ] );
                }
            }
        }
    }
    return 0;
}

void HDF5LogWriter::initializeHDF5LogWriter()
{
    hdf5LogWriterInitialized_ = true;

    for( int varIndex = 0; varIndex < varCount_ + attCount_; ++varIndex )
    {
        bool useChunks = varIndex < varCount_;
        if( vars_[ varIndex ] == NULL )
        {
            short code = varIndex < varCount_ ? uriCodes_[ varIndex ] : attUriCodes_[varIndex - varCount_];
            ElementURI* uri = Slate::GetElementURI( code );
            if( uri != NULL && uri->startsWith( "#.", 2 ) ) continue;
            for( unsigned short accessIndex = 0; accessIndex < Slate::GetDataAccessCount(); ++accessIndex )
            {
                DataAccess* dataAccess = Slate::GetDataAccess( accessIndex );
                if( NULL == dataAccess ) continue;
                if( dataAccess->getBinaryType() != NO_TYPE &&
                        ( ( varIndex >= varCount_ && dataAccess->getElementCode() == code )
                          || ( varIndex < varCount_ && !isUniversal_[ varIndex ] && dataAccess->getElementCode() == code )
                          || ( varIndex < varCount_ && isUniversal_[ varIndex ] && dataAccess->getUniversalCode() == code ) ) )
                {
                    const Unit* unit = ( varIndex >= varCount_ || units_[varIndex] == NULL ) ? dataAccess->getUnit() : units_[varIndex];
                    addVar( varIndex, uri, dataAccess->getBinaryType(), *unit, dataAccess->getUniversalCode(), useChunks );
                    break;
                }
            }
            if( NULL == vars_[varIndex] )
            {
                if( varIndex < varCount_ && NULL != units_[varIndex] )
                {

                    ElementURI aUri = ElementURI( "", varList_[ varIndex ] );
                    size_t dotAt = aUri.findLastOf( '.' );
                    const char* universalChars = varList_[ varIndex ];
                    if( dotAt != Str::NO_POS )
                    {
                        universalChars += dotAt + 1;
                    }
                    unsigned short universalCode = Slate::GetElementURICode( universalChars );
                    addVar( varIndex, &aUri, units_[varIndex]->getDefaultDataType(),
                            *units_[varIndex], universalCode, useChunks );
                }
                else if( Slate::IsDirectoryRead() )
                {
                }
                else
                {
                    hdf5LogWriterInitialized_ = false;
                }
            }
        }
    }
    if( hdf5LogWriterInitialized_ )
    {
        hdf5File_->addAttribute( "Conventions", "CF-1.4" );
        hdf5File_->addAttribute( "CF:featureType", "trajectory" );
    }
}

void HDF5LogWriter::addVar( const int i, const ElementURI* uri,
                            BinaryDataType binaryType, const Unit& unit,
                            short universalCode, bool useChunks )
{
    Str fixedUri( *uri );
    fixedUri.replaceChar( ':', '.' );
    fixedUri.replaceChar( '/', '.' );

    // Replace '_' and '#' prefix with External so they can be loaded in MATLAB
    if( hdf5File_->isMatlab() && ( fixedUri.startsWith( "_." ) || fixedUri.startsWith( "#." ) ) )
    {
        fixedUri = "External" + fixedUri.substr( 1 );
    }

    HDF5Group* group = hdf5File_;

    size_t dotAt;
    while( ( dotAt = fixedUri.find( '.' ) ) != Str::NO_POS )
    {
        Str groupName = fixedUri.substr( 0, dotAt );
        group = group->addGroup( groupName.cStr(), true );
        fixedUri = fixedUri.substr( dotAt + 1 );
    }
    group = group->addGroup( fixedUri.cStr(), true );

    if( NULL != group->findDataset( "value" ) )
    {
        return;
    }
    vars_[ i ] = group->addDataset( "value", binaryType, unit, useChunks, uri->getBlobType(), uri->getDimensions() + 1, uri->getDimension1(), uri->getDimension2(), uri->getDimension3() );

    vars_[ i ]->addAttribute( "units", unit.getAbbreviation() );
    if( hdf5File_->isMatlab() )
    {
        group->addScalarDataset( "units", unit.getAbbreviation(), strlen( unit.getAbbreviation() ), Units::NONE );
    }
    Str standardName( Str::EMPTY_STR );
    if( universalCode != UniversalURI::NO_CODE )
    {
        const ElementURI* universal;
        if( isUniversal_[ i ] )
        {
            universal = uri;
        }
        else
        {
            universal = Slate::GetElementURI( universalCode );
        }
        standardName = *universal;
        // **** DEBUG group->addVarAtt( vars_[ i ], "standard_name", standardName );
    }
    if( standardName != Str::EMPTY_STR
            && standardName != UniversalURI::DEPTH
            && standardName != UniversalURI::LATITUDE
            && standardName != UniversalURI::LONGITUDE )
    {
        // **** DEBUG group->addVarAtt( vars_[ i ], "coordinates", "Time depth latitude longitude" );
    }
    else if( standardName != Str::EMPTY_STR )
    {
        // **** DEBUG group->addVarAtt( vars_[ i ], "long_name", varList_[i] );
    }

    if( hdf5File_->isMatlab() )
    {
        timeVars_[ i ] = group->addDataset( "time", DOUBLE8, Units::MATLAB_TIME, useChunks );
    }
    else
    {
        timeVars_[ i ] = group->addDataset( "time", DOUBLE8, Units::EPOCH_SECOND, useChunks );
    }
    timeVars_[ i ]->addAttribute( "units", timeVars_[ i ]->getDataValue()->getUnit().getAbbreviation() );
}
