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

#include "NetCdfTableLogWriter.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"

#define MAX_CODES (ElementURI::NO_CODE + 1)

/// Streamless Constructor
NetCdfTableLogWriter::NetCdfTableLogWriter( const Str& filename, bool version4, int varCount, char** varList, char** unitList,
        bool interpolate, int attCount, char** attList )
    : TableLogWriter( varCount, varList, unitList, interpolate ),
      filename_( filename ),
      attCount_( attCount ),
      attList_( attList ),
      attUriCodes_( new unsigned short[ attCount ] ),
      attCodeLookup_( new int[MAX_CODES] ),
      netCdfWriter_( NetCdfWriter::NewNetCdfWriter( filename.cStr(), version4 ) ),
      netCdfInitialized_( false ),
      finalized_( false ),
      atts_( new const NetCdf::NetCdfAtt * [ attCount ] ),
      attUnits_( new const NetCdf::NetCdfAtt * [ attCount ] ),
      vars_( new NetCdf::NetCdfVar * [ varCount ] )
{
    nc_set_log_level( 5 );
    for( int i = 0; i < attCount_; ++i )
    {
        atts_[i] = NULL;
        attUnits_[i] = NULL;
        attUriCodes_[i] = ElementURI::NO_CODE;
    }
    for( int i = 0; i < varCount_; ++i )
    {
        vars_[i] = NULL;
    }
    for( int i = 0; i < MAX_CODES; ++i )
    {
        attCodeLookup_[i] = -1;
    }
    initializeNetCdfTableLogWriter();
}

unsigned int NetCdfTableLogWriter::writeHeader()
{
    netCdfInitialize();
    if( netCdfInitialized_ )
    {
        netCdfInitialized_ = netCdfWriter_->initialize();
    }
    return 0;
}

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

    if( !initializedTableLogWriter_ && slateElementURICount_ != Slate::GetElementURICount() )
    {
        initializeTableLogWriter();
    }
    if( !initializedTableLogWriter_ )
    {
        return 0;
    }
    if( !netCdfInitialized_ )
    {
        writeHeader();
    }
    if( !netCdfInitialized_ )
    {
        return 0;
    }

    if( entry->getType() == LogEntry::DATA_LOG_ENTRY )
    {
        const DataEntry * dataEntry = static_cast<const DataEntry*>( entry );
        int column = attCodeLookup_[dataEntry->getDataAccess()->getElementCode()];
        if( column >= 0 )
        {
            setAttValue( dataEntry, column );
            return 0;
        }
    }

    return TableLogWriter::write( entry, unit );

}

void NetCdfTableLogWriter::setAttValue( const DataEntry *dataEntry, const int attIndex )
{
    const NetCdf::NetCdfAtt* att = atts_[attIndex];
    DataValue* dataValue = dataEntry->getDataValue();
    switch( att->netCdfType_ )
    {
    case NetCdf::NC_BYTE_TYPE:
    case NetCdf::NC_CHAR_TYPE:
        if( att->nelems_ > 1 )
        {
            netCdfWriter_->setAttValue( att, dataValue->toString( dataValue->getUnit() ) );
        }
        else
        {
            unsigned char value( 0 );
            dataValue->copyTo( dataValue->getUnit(), value );
            netCdfWriter_->setAttValue( att, value );
        }
        break;
    case NetCdf::NC_DOUBLE_TYPE:
    {
        double value( nan( "" ) );
        dataValue->copyTo( dataValue->getUnit(), value );
        netCdfWriter_->setAttValue( att, value );
    }
    break;
    case NetCdf::NC_FLOAT_TYPE:
    {
        float value( nanf( "" ) );
        dataValue->copyTo( dataValue->getUnit(), value );
        netCdfWriter_->setAttValue( att, value );
    }
    break;
    case NetCdf::NC_INT_TYPE:
    case NetCdf::NC_SHORT_TYPE:
    {
        int value( 0 );
        dataValue->copyTo( dataValue->getUnit(), value );
        netCdfWriter_->setAttValue( att, value );
    }
    break;
    default:
        printf( "Unable to handle NetCdf type %d for %s\n", att->netCdfType_,
                Slate::GetElementURI( dataEntry->getDataAccess()->getElementCode() )->cStr() );
        break;
    }
    if( NULL != attUnits_[attIndex] )
    {
        netCdfWriter_->setAttValue( attUnits_[attIndex], Str( dataValue->getUnit().getName() ) );
    }

}

unsigned int NetCdfTableLogWriter::writeToColumn( const DataEntry *dataEntry, const int column )
{
    if( NULL !=  vars_[ column ] && NULL !=  vars_[ column ]->dataValue_ )
    {
        if( NULL != dataEntry )
        {
            dataEntry->getDataValue()->copyTo( *( vars_[ column ]->dataValue_ ) );
        }
        else
        {
            const Unit& unit = vars_[ column ]->dataValue_->getUnit();
            vars_[ column ]->dataValue_->setFrom( unit, nan( "" ) );
        }
    }
    return 0;
}

// Write a netcdf record
unsigned int NetCdfTableLogWriter::writeRow()
{
    return netCdfWriter_->writeRecord( lastTime_ );
}

/// Destructor
NetCdfTableLogWriter::~NetCdfTableLogWriter()
{
    if( !finalized_ )
    {
        writeFooter();
    }
    delete[] vars_;
    delete[] atts_;
    delete[] attUnits_;
    delete[] attUriCodes_;
    delete[] attCodeLookup_;
    delete netCdfWriter_;
}

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

    TableLogWriter::writeFooter();

    if( netCdfInitialized_ )
    {
        netCdfWriter_->writeRecord( lastTime_ );

        finalized_ = netCdfWriter_->finalize();
    }
    else
    {
        printf( "No netCDF 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( initializedTableLogWriter_ )
        {
            for( int i = 0; i < varCount_; ++i )
            {
                if( NULL == vars_[i] )
                {
                    printf( "Never ran writer for variable \"%s\"\n", varList_[ i ] );
                }
            }
        }
    }
    return 0;
}

void NetCdfTableLogWriter:: clearRow()
{
    /* THis is commented out for Rob's sake

    for ( int column = 0; column < varCount_; ++column )
    {
        if ( NULL !=  vars_[ column ]->dataValue_ )
        {
            const Unit& unit = vars_[ column ]->dataValue_->getUnit();
            vars_[ column ]->dataValue_->setFrom( unit, nan( "" ) );
        }
    }
    */
}


void NetCdfTableLogWriter::initializeNetCdfTableLogWriter()
{}

void NetCdfTableLogWriter::netCdfInitialize()
{
    netCdfInitialized_ = true;
    for( int i = 0; i < attCount_; ++i )
    {
        if( atts_[ i ] == NULL )
        {
            attUriCodes_[ i ] = Slate::GetElementURICode( attList_[i] );
            if( attUriCodes_[ i ] == ElementURI::NO_CODE )
            {
                continue;
            }
            attCodeLookup_[ attUriCodes_[ i ] ] = i;
            ElementURI* uri = Slate::GetElementURI( attUriCodes_[ i ] );
            for( unsigned short j = 0; j < Slate::GetDataAccessCount(); ++j )
            {
                DataAccess* dataAccess = Slate::GetDataAccess( j );
                if( NULL == dataAccess ) continue;
                if( dataAccess->getBinaryType() != NO_TYPE &&
                        ( dataAccess->getElementCode() == attUriCodes_[ i ] ) )
                {
                    Str fixedUri( *uri );
                    fixedUri.replaceChar( ':', '.' );
                    fixedUri.replaceChar( '/', '.' );
                    /// Deal with this as a global attribute
                    switch( dataAccess->getBinaryType() )
                    {
                    case UCHAR1:
                        atts_[i] =  netCdfWriter_->addGlobalAtt( fixedUri, ( char )0 );
                        break;
                    case SHORT2:
                        atts_[i] =  netCdfWriter_->addGlobalAtt( fixedUri, ( short ) 0 );
                        break;
                    case FLOAT2:
                    case FLOAT3:
                    case FLOAT4:
                    case DOUBLE4:
                        atts_[i] =  netCdfWriter_->addGlobalAtt( fixedUri, ( float )nan( "" ) );
                        break;
                    case USHORT2:
                    case INT4:
                        atts_[i] =  netCdfWriter_->addGlobalAtt( fixedUri, ( int ) 0 );
                        break;
                    case NO_TYPE:
                    case DOUBLE6:
                    case DOUBLE8:
                        atts_[i] =  netCdfWriter_->addGlobalAtt( fixedUri, nan( "" ) );
                        break;
                    case MULTIVALUE:
                        atts_[i] =  netCdfWriter_->addGlobalAtt( fixedUri, Str( ( char* )NULL, 64 ) );
                        break;
                    default:
                        printf( "Unable to handle Binary type %d for %s\n", dataAccess->getBinaryType(), uri->cStr() );
                        break;
                    }

                    if( dataAccess->getBinaryType() != MULTIVALUE )
                    {
                        attUnits_[i] = netCdfWriter_->addGlobalAtt( fixedUri + "_units", Str( ( char* )NULL, 64 ) );
                    }
                    break;
                }
            }
        }
        if( NULL == atts_[i] )
        {
            if( Slate::IsDirectoryRead() )
            {
                atts_[ i ] = netCdfWriter_->addGlobalAtt( attList_[ i ], ( float )nan( "" ) );
            }
            else
            {
                netCdfInitialized_ = false;
            }
        }
    }
    for( int varIndex = 0; varIndex < varCount_; ++varIndex )
    {
        if( vars_[ varIndex ] == NULL )
        {
            ElementURI* uri = Slate::GetElementURI( uriCodes_[ varIndex ] );
            bool triedToAdd = false;
            for( unsigned short accessIndex = 0; accessIndex < Slate::GetDataAccessCount(); ++accessIndex )
            {
                DataAccess* dataAccess = Slate::GetDataAccess( accessIndex );
                if( NULL == dataAccess ) continue;
                if( dataAccess->getBinaryType() != NO_TYPE &&
                        ( ( !isUniversal_[ varIndex ] && dataAccess->getElementCode() == uriCodes_[ varIndex ] )
                          || ( isUniversal_[ varIndex ] && dataAccess->getUniversalCode() == uriCodes_[ varIndex ] ) ) )
                {
                    const Unit* unit = units_[varIndex] == NULL ? dataAccess->getUnit() : units_[varIndex];
                    addVar( varIndex, uri, dataAccess->getBinaryType(), *unit, dataAccess->getUniversalCode() );
                    triedToAdd = true;
                    break;
                }
            }
            if( !triedToAdd && NULL == vars_[varIndex] )
            {
                if( 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 );
                }
                else if( Slate::IsDirectoryRead() )
                {
                    vars_[ varIndex ] = netCdfWriter_->addVar( varList_[ varIndex ], NetCdf::NC_FLOAT_TYPE );
                }
                else
                {
                    netCdfInitialized_ = false;
                }
            }
        }
    }
    if( netCdfInitialized_ )
    {
        netCdfWriter_->addGlobalAtt( "Conventions", "CF-1.4" );
        netCdfWriter_->addGlobalAtt( "CF:featureType", "trajectory" );
    }
}

void NetCdfTableLogWriter::addVar( const int i, const ElementURI* uri, BinaryDataType binaryType, const Unit& unit, short universalCode )
{
    Str fixedUri( *uri );
    fixedUri.replaceChar( ':', '.' );
    fixedUri.replaceChar( '/', '.' );
    NetCdf::NetCdfType ncType = NetCdf::NC_ABSENT_TYPE;
    switch( binaryType )
    {
    case UCHAR1:
    case SHORT2:
        ncType = NetCdf::NC_SHORT_TYPE;
        break;
    case FLOAT2:
    case FLOAT3:
    case FLOAT4:
    case DOUBLE4:
        ncType = NetCdf::NC_FLOAT_TYPE;
        break;
    case USHORT2:
    case INT4:
        ncType = NetCdf::NC_INT_TYPE;
        break;
    case NO_TYPE:
    case DOUBLE6:
    case DOUBLE8:
        ncType = NetCdf::NC_DOUBLE_TYPE;
        break;
    case MULTIVALUE:
        break;
    default:
        printf( "Unable to handle Binary type %d for %s\n", binaryType, uri->cStr() );
        break;
    }

    if( ncType == NetCdf::NC_ABSENT_TYPE )
    {
        return;
    }

    vars_[ i ] = netCdfWriter_->findVar( fixedUri );
    if( NULL != vars_[ i ] )
    {
        return;
    }
    vars_[ i ] = netCdfWriter_->addVar( fixedUri, ncType );
    vars_[ i ]->dataValue_ = unit( binaryType );

    netCdfWriter_->addVarAtt( vars_[ i ], "units", unit.getAbbreviation() );
    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;
        netCdfWriter_->addVarAtt( vars_[ i ], "standard_name", standardName );
    }
    if( standardName != Str::EMPTY_STR
            && standardName != UniversalURI::DEPTH
            && standardName != UniversalURI::LATITUDE
            && standardName != UniversalURI::LONGITUDE )
    {
        netCdfWriter_->addVarAtt( vars_[ i ], "coordinates", "Time depth latitude longitude" );
    }
    else if( standardName != Str::EMPTY_STR )
    {
        netCdfWriter_->addVarAtt( vars_[ i ], "long_name", varList_[i] );
    }
}
