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

#include "utils/NetCdfReader.h"

#include <climits>
#include <math.h>

/// Returns an newNetCdfReader of a NetCdfReader for a file
NetCdfReader* NetCdfReader::NewNetCdfReader( const char* fileName )
{
    NetCdfReader * netCdfReader = new NetCdfReader( fileName );
    if( !netCdfReader->isOk() )
    {
        delete netCdfReader;
        netCdfReader = NULL;
    }
    return netCdfReader;
}

/// Destructor
NetCdfReader::~NetCdfReader()
{
    if( ncId_ != -1 )
    {
        nc_close( ncId_ );
    }
}

/// Reads the value of the variable at the indices as the indicated type
/// Returns true if no error.
bool NetCdfReader::read( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                         const size_t indices[] )
{
    switch( assignType )
    {
    case NC_BYTE_TYPE:
        status_ = nc_get_var1_uchar( ncId_, var.varId_, indices, ( unsigned char* )assignTo );
        break;
    case NC_CHAR_TYPE:
        status_ = nc_get_var1_text( ncId_, var.varId_, indices, ( char* )assignTo );
        break;
    case NC_DOUBLE_TYPE:
        status_ = nc_get_var1_double( ncId_, var.varId_, indices, ( double* )assignTo );
        break;
    case NC_FLOAT_TYPE:
        status_ = nc_get_var1_float( ncId_, var.varId_, indices, ( float* )assignTo );
        break;
    case NC_INT_TYPE:
        status_ = nc_get_var1_int( ncId_, var.varId_, indices, ( int* )assignTo );
        break;
    case NC_SHORT_TYPE:
        status_ = nc_get_var1_short( ncId_, var.varId_, indices, ( short* )assignTo );
        break;
    case NC_UBYTE_TYPE:
        status_ = nc_get_var1_ubyte( ncId_, var.varId_, indices, ( unsigned char* )assignTo );
        break;
    case NC_USHORT_TYPE:
        status_ = nc_get_var1_ushort( ncId_, var.varId_, indices, ( unsigned short* )assignTo );
        break;
    case NC_UINT_TYPE:
        status_ = nc_get_var1_uint( ncId_, var.varId_, indices, ( unsigned int* )assignTo );
        break;
    case NC_INT64_TYPE:
        status_ = nc_get_var1_longlong( ncId_, var.varId_, indices, ( long long* )assignTo );
        break;
    case NC_UINT64_TYPE:
        status_ = nc_get_var1_ulonglong( ncId_, var.varId_, indices, ( unsigned long long* )assignTo );
        break;
    case NC_STRING_TYPE:
        status_ = nc_get_var1_string( ncId_, var.varId_, indices, ( char** )assignTo );
        break;
    default:
        return false;
    }
    return status_ == NC_NOERR;
}

/// Reads the value of the variable at the 1D index as the indicated type
/// Returns true if no error.
bool NetCdfReader::read1D( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                           const unsigned long index1 )
{
    const size_t indices[] = {index1};
    return read( assignTo, assignType, var, indices );
}

/// Reads the value of the variable at the 2D index as the indicated type
/// Returns true if no error.
bool NetCdfReader::read2D( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                           const unsigned long index1, const unsigned long index2 )
{
    if( var.nDims_ < 2 )
    {
        return read1D( assignTo, assignType, var, index1 );
    }
    const size_t indices[] = {index1, index2};
    return read( assignTo, assignType, var, indices );
}

/// Reads the value of the variable at the 3D index as the indicated type
/// Returns true if no error.
bool NetCdfReader::read3D( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                           const unsigned long index1, const unsigned long index2,
                           const unsigned long index3 )
{
    if( var.nDims_ < 3 )
    {
        return read2D( assignTo, assignType, var, index1, index2 );
    }
    const size_t indices[] = {index1, index2, index3};
    return read( assignTo, assignType, var, indices );
}

/// Reads the value of the variable at the 4D index as the indicated type
/// Returns true if no error.
bool NetCdfReader::read4D( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                           const unsigned long index1, const unsigned long index2,
                           const unsigned long index3, const unsigned long index4 )
{
    if( var.nDims_ < 4 )
    {
        return read3D( assignTo, assignType, var, index1, index2, index3 );
    }
    const size_t indices[] = {index1, index2, index3, index4};
    return read( assignTo, assignType, var, indices );
}

/// Reads a 1D range of the variable between a pair of indices
/// (a <= index <= b) into an existing 1D array of the indicated type
/// Returns true if no error.
bool NetCdfReader::read1DArray( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                                const unsigned long index1a, const unsigned long index1b )
{
    unsigned long size1 = index1b - index1a + 1;
    for( unsigned long i1 = 0; i1 < size1; ++i1 )
    {
        if( !read1D( ( void* )( ( char* )assignTo + i1 * NetCdfTypeSize_[ assignType ] ),
                     assignType, var, i1 + index1a ) )
        {
            return false;
        }
    }
    return true;
}

/// Reads a 2D range of the variable between 2 pairs of indices
/// (a <= index <= b) into an existing 2D array of the indicated type
/// Returns true if no error.
bool NetCdfReader::read2DArray( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                                const unsigned long index1a, const unsigned long index1b,
                                const unsigned long index2a, const unsigned long index2b )
{
    unsigned long size1 = index1b - index1a + 1;
    unsigned long size2 = index2b - index2a + 1;
    for( unsigned long i1 = 0; i1 < size1; ++i1 )
    {
        for( unsigned long i2 = 0; i2 < size2; ++i2 )
        {
            if( !read2D( ( void* )( ( char* )assignTo + ( i1 * size2 + i2 ) * NetCdfTypeSize_[ assignType ] ),
                         assignType, var, i1 + index1a, i2 + index2a ) )
            {
                return false;
            }
        }
    }
    return true;
}

/// Reads a 3D range of the variable between 3 pairs of indices
/// (a <= index <= b) into an existing 3D array of the indicated type
/// Returns true if no error.
bool NetCdfReader::read3DArray( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                                const unsigned long index1a, const unsigned long index1b,
                                const unsigned long index2a, const unsigned long index2b,
                                const unsigned long index3a, const unsigned long index3b )
{
    unsigned long size1 = index1b - index1a + 1;
    unsigned long size2 = index2b - index2a + 1;
    unsigned long size3 = index3b - index3a + 1;
    for( unsigned long i1 = 0; i1 < size1; ++i1 )
    {
        for( unsigned long i2 = 0; i2 < size2; ++i2 )
        {
            for( unsigned long i3 = 0; i3 < size3; ++i3 )
            {
                if( !read3D( ( void* )( ( char* )assignTo + ( ( i1 * size2 + i2 ) * size3 + i3 ) * NetCdfTypeSize_[ assignType ] ),
                             assignType, var, i1 + index1a, i2 + index2a, i3 + index3a ) )
                {
                    return false;
                }
            }
        }
    }
    return true;
}

/// Reads a 4D range of the variable between 4 pairs of indices
/// (a <= index <= b) into an existing 4D array of the indicated type
/// Returns true if no error.
bool NetCdfReader::read4DArray( void* assignTo, const NetCdfType assignType, const NetCdfVar& var,
                                const unsigned long index1a, const unsigned long index1b,
                                const unsigned long index2a, const unsigned long index2b,
                                const unsigned long index3a, const unsigned long index3b,
                                const unsigned long index4a, const unsigned long index4b )
{
    unsigned long size1 = index1b - index1a + 1;
    unsigned long size2 = index2b - index2a + 1;
    unsigned long size3 = index3b - index3a + 1;
    unsigned long size4 = index4b - index4a + 1;
    for( unsigned long i1 = 0; i1 < size1; ++i1 )
    {
        for( unsigned long i2 = 0; i2 < size2; ++i2 )
        {
            for( unsigned long i3 = 0; i3 < size3; ++i3 )
            {
                for( unsigned long i4 = 0; i4 < size4; ++i4 )
                {
                    if( !read4D( ( void* )( ( char* )assignTo + ( ( ( i1 * size2 + i2 ) * size3 + i3 ) * size4 + i4 ) * NetCdfTypeSize_[ assignType ] ),
                                 assignType, var, i1 + index1a, i2 + index2a, i3 + index3a, i4 + index4a ) )
                    {
                        return false;
                    }
                }
            }
        }
    }
    return true;
}

/// Protected Constructor
NetCdfReader::NetCdfReader( const char* fileName )
    : NetCdf()
{
    ok_ = initialize( fileName );
}

/// Opens a NetCdfReader for a file
bool NetCdfReader::initialize( const char* fileName )
{
    status_ = nc_open( fileName, NC_NOWRITE, &ncId_ );
    ok_ = status_ == NC_NOERR;
    if( !ok_ )
    {
        ncId_ = -1;
        return false;
    }

    char name[NC_MAX_NAME + 1];

    int nDims( 0 );
    status_ = nc_inq_ndims( ncId_, &nDims );
    ok_ = status_ == NC_NOERR;
    if( !ok_ ) return false;
    for( int dimId = 0; dimId < nDims; ++dimId )
    {
        *name = 0;
        status_ = nc_inq_dimname( ncId_, dimId, name );
        ok_ = status_ == NC_NOERR;
        if( !ok_ ) return false;
        size_t dimLen;
        status_ = nc_inq_dimlen( ncId_, dimId, &dimLen );
        ok_ = status_ == NC_NOERR;
        if( !ok_ ) return false;
        NetCdfDim* netCdfDim = new NetCdfDim();
        netCdfDim->dimId_ = dimId;
        netCdfDim->dimSize_ = dimLen;
        dimArray_.set( dimId, netCdfDim );
        dims_.put( name, netCdfDim );
    }

    int nAtts( 0 );
    status_ = nc_inq_natts( ncId_, &nAtts );
    ok_ = status_ == NC_NOERR;
    if( !ok_ ) return false;
    for( int attId = 0; attId < nAtts; ++attId )
    {
        *name = 0;
        status_ = nc_inq_attname( ncId_, NC_GLOBAL, attId, name );
        ok_ = status_ == NC_NOERR;
        if( !ok_ ) return false;
        size_t attLen;
        status_ = nc_inq_attlen( ncId_, NC_GLOBAL, name, &attLen );
        ok_ = status_ == NC_NOERR;
        if( !ok_ ) return false;
        nc_type attType;
        status_ = nc_inq_atttype( ncId_, NC_GLOBAL, name, &attType );
        ok_ = status_ == NC_NOERR;
        if( !ok_ ) return false;
        NetCdfAtt* netCdfAtt = new NetCdfAtt();
        netCdfAtt->attId_ = attId;
        netCdfAtt->varId_ = NC_GLOBAL;
        netCdfAtt->name_ = name;
        netCdfAtt->netCdfType_ = ( NetCdfType )attType;
        netCdfAtt->nelems_ = attLen;
        gatts_.put( name, netCdfAtt );
    }

    int nVars( 0 );
    status_ = nc_inq_nvars( ncId_, &nVars );
    ok_ = status_ == NC_NOERR;
    if( !ok_ ) return false;
    for( int varId = 0; varId < nVars; ++varId )
    {
        *name = 0;
        status_ = nc_inq_varname( ncId_, varId, name );
        ok_ = status_ == NC_NOERR;
        if( !ok_ ) return false;
        NetCdfVar* netCdfVar = new NetCdfVar();
        netCdfVar->varId_ = varId;
        status_ = nc_inq_varndims( ncId_, varId, &nDims );
        ok_ = status_ == NC_NOERR;
        if( !ok_ )
        {
            delete netCdfVar;
            return false;
        }
        netCdfVar->nDims_ = nDims;
        netCdfVar->dimIds_ = new int[nDims];
        status_ = nc_inq_vardimid( ncId_, varId, netCdfVar->dimIds_ );
        ok_ = status_ == NC_NOERR;
        if( !ok_ )
        {
            delete netCdfVar;
            return false;
        }
        nc_type ncType = NC_NAT;
        status_ = nc_inq_vartype( ncId_, varId, &ncType );
        ok_ = status_ == NC_NOERR;
        if( !ok_ )
        {
            delete netCdfVar;
            return false;
        }
        netCdfVar->netCdfType_ = ( NetCdfType )ncType;
        varArray_.set( varId, netCdfVar );
        vars_.put( name, netCdfVar );
        status_ = nc_inq_varnatts( ncId_, varId, &nAtts );
        ok_ = status_ == NC_NOERR;
        if( !ok_ )
        {
            delete netCdfVar;
            return false;
        }
        for( int attId = 0; attId < nAtts; ++attId )
        {
            *name = 0;
            status_ = nc_inq_attname( ncId_, varId, attId, name );
            ok_ = status_ == NC_NOERR;
            if( !ok_ )
            {
                delete netCdfVar;
                return false;
            }
            size_t attLen;
            status_ = nc_inq_attlen( ncId_, varId, name, &attLen );
            ok_ = status_ == NC_NOERR;
            if( !ok_ )
            {
                delete netCdfVar;
                return false;
            }
            nc_type attType;
            status_ = nc_inq_atttype( ncId_, varId, name, &attType );
            ok_ = status_ == NC_NOERR;
            if( !ok_ )
            {
                delete netCdfVar;
                return false;
            }
            NetCdfAtt* netCdfAtt = new NetCdfAtt();
            netCdfAtt->attId_ = attId;
            netCdfAtt->varId_ = varId;
            netCdfAtt->name_ = name;
            netCdfAtt->netCdfType_ = ( NetCdfType )attType;
            netCdfAtt->nelems_ = attLen;
            netCdfVar->atts_.put( name, netCdfAtt );
        }
    }

    return true;
}

/// Reads the value of the attribute as a Str
/// Returns true if no error.
bool NetCdfReader::readAttStr( Str& assignTo, const NetCdfAtt* att )
{
    char* tmpStr = new char[ att->nelems_ + 1];
    status_ = nc_get_att_text( ncId_, att->varId_, att->name_.cStr(), tmpStr );
    if( status_ == NC_NOERR )
    {
        tmpStr[att->nelems_] = 0;
        assignTo = tmpStr;
    }
    delete[] tmpStr;
    return status_ == NC_NOERR;
}

/// Reads the value of the attribute
/// Returns true if no error.
bool NetCdfReader::readAtt( void* assignTo, const NetCdfType assignType, const NetCdfAtt* att )
{
    switch( assignType )
    {
    case NC_BYTE_TYPE:
        status_ =
            status_ = nc_get_att_uchar( ncId_, att->varId_, att->name_.cStr(), ( unsigned char* )assignTo );
        break;
    case NC_CHAR_TYPE:
        status_ = nc_get_att_text( ncId_, att->varId_, att->name_.cStr(), ( char* )assignTo );
        break;
    case NC_DOUBLE_TYPE:
        status_ = nc_get_att_double( ncId_, att->varId_, att->name_.cStr(), ( double* )assignTo );
        break;
    case NC_FLOAT_TYPE:
        status_ = nc_get_att_float( ncId_, att->varId_, att->name_.cStr(), ( float* )assignTo );
        break;
    case NC_INT_TYPE:
        status_ = nc_get_att_int( ncId_, att->varId_, att->name_.cStr(), ( int* )assignTo );
        break;
    case NC_SHORT_TYPE:
        status_ = nc_get_att_short( ncId_, att->varId_, att->name_.cStr(), ( short* )assignTo );
        break;
    case NC_UBYTE_TYPE:
        status_ = nc_get_att_ubyte( ncId_, att->varId_, att->name_.cStr(), ( unsigned char* )assignTo );
        break;
    case NC_USHORT_TYPE:
        status_ = nc_get_att_ushort( ncId_, att->varId_, att->name_.cStr(), ( unsigned short* )assignTo );
        break;
    case NC_UINT_TYPE:
        status_ = nc_get_att_uint( ncId_, att->varId_, att->name_.cStr(), ( unsigned int* )assignTo );
        break;
    case NC_INT64_TYPE:
        status_ = nc_get_att_longlong( ncId_, att->varId_, att->name_.cStr(), ( long long* )assignTo );
        break;
    case NC_UINT64_TYPE:
        status_ = nc_get_att_ulonglong( ncId_, att->varId_, att->name_.cStr(), ( unsigned long long* )assignTo );
        break;
    case NC_STRING_TYPE:
        status_ = nc_get_att_string( ncId_, att->varId_, att->name_.cStr(), ( char** )assignTo );
        break;
    default:
        return false;
    }
    return status_ == NC_NOERR;
}


NetCdf::NetCdfVar* NetCdfReader::findNetCdfVarByAttribute( const Str& name, const Str& value )
{
    const NetCdfVars& vars = getVars();
    for( unsigned int i = 0; i < vars.size(); ++i )
    {
        NetCdfVar* var = vars.getIndexed( i );
        NetCdfAtts& varAtts = var->atts_;
        NetCdfAtt* varAtt = varAtts.get( name );
        if( varAtt )
        {
            Str varAttValue;
            if( readAttStr( varAttValue, varAtt ) && value == varAttValue )
            {
                return var;
            }
        }
    }
    return NULL;
}
