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

#include "JsonLogWriter.h"
#include "data/BlobValue.h"
#include "data/DataValue.h"
#include "data/ElementURI.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalURI.h"
#include "io/FileOutStream.h"
#include "io/OutStream.h"
#include "logger/DataEntry.h"
#include "units/Units.h"

#include <stdio.h>
#include <stdlib.h>

#define MAX_CODES (ElementURI::NO_CODE + 1)

/// Streamless Constructor
JsonLogWriter::JsonLogWriter( bool formatted, int varCount, char** varList, char** unitList,
                              int attCount, char** attList )
    : FilterLogWriter( NULL, varCount, varList, unitList, false, attCount, attList ),
      json_( cJSON_CreateObject() ),
      jsonLogWriterInitialized_( false ),
      finalized_( false ),
      formatted_( formatted ),
      vars_( new cJSON * [ varCount + attCount] ),
      timeVars_( new cJSON * [ varCount + attCount] ),
      units_( new const Unit * [ varCount + attCount ] ),
      uris_( new const ElementURI * [ varCount + attCount ] )
{
    for( int i = 0; i < varCount_ + attCount_; ++i )
    {
        vars_[i] = NULL;
        timeVars_[i] = NULL;
        units_[i] = NULL;
        uris_[i] = NULL;
    }
}

/// Destructor
JsonLogWriter::~JsonLogWriter()
{
    if( !finalized_ )
    {
        writeFooter();
    }
    delete[] vars_;
    delete[] timeVars_;
    delete[] units_;
    delete[] uris_;
    cJSON_Delete( json_ );
}

unsigned int JsonLogWriter::writeHeader()
{
    return 0;
}

unsigned int JsonLogWriter::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( !jsonLogWriterInitialized_ )
    {
        initializeJsonLogWriter();
    }
    if( !jsonLogWriterInitialized_ )
    {
        return 0;
    }

    return FilterLogWriter::write( entry, unit );

}

// Override this method to do something special with data entries
unsigned int JsonLogWriter::writeData( const DataEntry *dataEntry, int index, const Unit* unit )
{
    if( NULL !=  vars_[ index ] )
    {
        double value( NAN );
        if( NULL != dataEntry )
        {
            cJSON_AddItemToArray( timeVars_[ index ], cJSON_CreateNumber( round( dataEntry->getTimestamp().asDouble() * 1000 ) ) );
            cJSON* json0 = vars_[ index ];
            const ElementURI* uri = uris_[index];
            int dimensions = uri->getDimensions();
            BlobValue* bv = NULL;
            StrValue* sv = NULL;
            if( dimensions > 0 )
            {
                bv = dynamic_cast<BlobValue*>( dataEntry->getDataValue() );
            }
            else if( dataEntry->getDataAccess()->getBinaryType() == MULTIVALUE )
            {
                sv = dynamic_cast<StrValue*>( dataEntry->getDataValue() );
            }
            if( NULL != bv )
            {
                int m = uri->getDimension1() > 0 ? uri->getDimension1() : bv->getElements();
                cJSON* json1 = cJSON_CreateArray();
                cJSON_AddItemToArray( json0, json1 );
                for( int i = 0; i < m; ++i )
                {
                    if( dimensions > 1 )
                    {
                        int n = uri->getDimension2();
                        cJSON* json2 = cJSON_CreateArray();
                        cJSON_AddItemToArray( json1, json2 );
                        for( int j = 0; j < n; ++j )
                        {
                            if( dimensions > 2 )
                            {
                                int o = uri->getDimension3();
                                cJSON* json3 = cJSON_CreateArray();
                                cJSON_AddItemToArray( json2, json3 );
                                for( int k = 0; k < uri->getDimension3(); ++k )
                                {
                                    bv->get( i * n * o + j * o + k, *units_[index], value );
                                    cJSON_AddItemToArray( json3, cJSON_CreateNumber( value ) );
                                }
                            }
                            else
                            {
                                bv->get( i * n + j, *units_[index], value );
                                cJSON_AddItemToArray( json2, cJSON_CreateNumber( value ) );
                            }
                        }
                    }
                    else
                    {
                        bv->get( i, *units_[index], value );
                        cJSON_AddItemToArray( json1, cJSON_CreateNumber( value ) );
                    }
                }
            }
            else if( NULL != sv )
            {
                Str str = sv->toString();
                const char* cStr = str.cStr();
                cJSON_AddItemToArray( json0, cJSON_CreateString( cStr ) );
            }
            else
            {
                dataEntry->getDataValue()->copyTo( *( units_[index] ), value );
                cJSON_AddItemToArray( json0, cJSON_CreateNumber( value ) );
            }
        }
    }
    return 0;

}

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

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

    if( jsonLogWriterInitialized_ )
    {
        char* json = formatted_ ? cJSON_Print( json_ ) : cJSON_PrintUnformatted( json_ );
        if( NULL != logStream_ )
        {
            ( *logStream_ ) << json << "\n";
        }
        free( json );
    }
    else
    {
        printf( "No Json 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 JsonLogWriter::initializeJsonLogWriter()
{
    jsonLogWriterInitialized_ = true;
    for( int varIndex = 0; varIndex < varCount_ + attCount_; ++varIndex )
    {
        if( vars_[ varIndex ] == NULL )
        {
            short code = varIndex < varCount_ ? uriCodes_[ varIndex ] : attUriCodes_[varIndex - varCount_];
            ElementURI* uri = Slate::GetElementURI( code );
            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() );
                    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 );
                }
                else if( Slate::IsDirectoryRead() )
                {
                }
                else
                {
                    jsonLogWriterInitialized_ = false;
                }
            }
        }
    }
}

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

    cJSON* group = json_;

    size_t dotAt;
    while( ( dotAt = fixedUri.find( '.' ) ) != Str::NO_POS )
    {
        Str groupName = fixedUri.substr( 0, dotAt );
        cJSON* foundGroup = cJSON_GetObjectItem( group, groupName.cStr() );
        if( NULL != foundGroup )
        {
            group = foundGroup;
        }
        else
        {
            cJSON* newGroup = cJSON_CreateObject();
            cJSON_AddItemToObject( group, groupName.cStr(), newGroup );
            group = newGroup;
        }
        fixedUri = fixedUri.substr( dotAt + 1 );
    }
    cJSON* newGroup = cJSON_CreateObject();
    cJSON_AddItemToObject( group, fixedUri.cStr(), newGroup );
    group = newGroup;

    if( NULL != cJSON_GetObjectItem( group, "value" ) )
    {
        return;
    }

    vars_[ i ] = cJSON_CreateArray(); // TODO: deal with mutiple dimensions
    cJSON_AddItemToObject( group, "value", vars_[i] );

    units_[ i ] = &unit;
    cJSON_AddItemToObject( group, "units", cJSON_CreateString( unit.getAbbreviation() ) );

    uris_[ i ] = uri;

    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] );
    }

    timeVars_[ i ] = cJSON_CreateArray();
    cJSON_AddItemToObject( group, "time", timeVars_[ i ] );

}
