/******************************************************************************
 * $Id: pdsdataset.cpp 12658 2007-11-07 23:14:33Z warmerdam $
 *
 * Project:  PDS Driver; Planetary Data System Format
 * Purpose:  Implementation of NASAKeywordHandler - a class to read 
 *           keyword data from PDS, ISIS2 and ISIS3 data products.
 * Author:   Frank Warmerdam <warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2006, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2008-2010, Even Rouault <even dot rouault at mines-paris dot org>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************
 * Object Description Language (ODL) is used to encode data labels for PDS
 * and other NASA data systems. Refer to Chapter 12 of "PDS Standards
 * Reference" at http://pds.jpl.nasa.gov/tools/standards-reference.shtml for
 * further details about ODL.
 * 
 * This is also known as PVL (Parameter Value Language) which is written
 * about at http://www.orrery.us/node/44 where it notes:
 * 
 * The PVL syntax that the PDS uses is specified by the Consultative Committee 
 * for Space Data Systems in their Blue Book publication: "Parameter Value 
 * Language Specification (CCSD0006 and CCSD0008)", June 2000 
 * [CCSDS 641.0-B-2], and Green Book publication: "Parameter Value Language - 
 * A Tutorial", June 2000 [CCSDS 641.0-G-2]. PVL has also been accepted by the
 * International Standards Organization (ISO), as a Final Draft International 
 * Standard (ISO 14961:2002) keyword value type language for naming and 
 * expressing data values.
 * --
 * also of interest, on PDS ODL:
 *  http://pds.jpl.nasa.gov/documents/sr/Chapter12.pdf
 *  
 ****************************************************************************/

#include "cpl_string.h" 
#include "nasakeywordhandler.h"

/************************************************************************/
/* ==================================================================== */
/*                          NASAKeywordHandler                          */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                         NASAKeywordHandler()                         */
/************************************************************************/

NASAKeywordHandler::NASAKeywordHandler()

{
    papszKeywordList = NULL;
}

/************************************************************************/
/*                        ~NASAKeywordHandler()                         */
/************************************************************************/

NASAKeywordHandler::~NASAKeywordHandler()

{
    CSLDestroy( papszKeywordList );
    papszKeywordList = NULL;
}

/************************************************************************/
/*                               Ingest()                               */
/************************************************************************/

int NASAKeywordHandler::Ingest( VSILFILE *fp, int nOffset )

{
/* -------------------------------------------------------------------- */
/*      Read in buffer till we find END all on it's own line.           */
/* -------------------------------------------------------------------- */
    if( VSIFSeekL( fp, nOffset, SEEK_SET ) != 0 )
        return FALSE;

    for( ; TRUE; ) 
    {
        const char *pszCheck;
        char szChunk[513];

        int nBytesRead = VSIFReadL( szChunk, 1, 512, fp );

        szChunk[nBytesRead] = '\0';
        osHeaderText += szChunk;

        if( nBytesRead < 512 )
            break;

        if( osHeaderText.size() > 520 )
            pszCheck = osHeaderText.c_str() + (osHeaderText.size() - 520);
        else
            pszCheck = szChunk;

        if( strstr(pszCheck,"\r\nEND\r\n") != NULL 
            || strstr(pszCheck,"\nEND\n") != NULL 
            || strstr(pszCheck,"\r\nEnd\r\n") != NULL 
            || strstr(pszCheck,"\nEnd\n") != NULL )
            break;
    }

    pszHeaderNext = osHeaderText.c_str();

/* -------------------------------------------------------------------- */
/*      Process name/value pairs, keeping track of a "path stack".      */
/* -------------------------------------------------------------------- */
    return ReadGroup( "" );
}

/************************************************************************/
/*                             ReadGroup()                              */
/************************************************************************/

int NASAKeywordHandler::ReadGroup( const char *pszPathPrefix )

{
    CPLString osName, osValue;

    for( ; TRUE; )
    {
        if( !ReadPair( osName, osValue ) )
            return FALSE;

        if( EQUAL(osName,"OBJECT") || EQUAL(osName,"GROUP") )
        {
            if( !ReadGroup( (CPLString(pszPathPrefix) + osValue + ".").c_str() ) )
                return FALSE;
        }
        else if( EQUAL(osName,"END") 
                 || EQUAL(osName,"END_GROUP" )
                 || EQUAL(osName,"END_OBJECT" ) )
        {
            return TRUE;
        }
        else
        {
            osName = pszPathPrefix + osName;
            papszKeywordList = CSLSetNameValue( papszKeywordList, 
                                                osName, osValue );
        }
    }
}

/************************************************************************/
/*                              ReadPair()                              */
/*                                                                      */
/*      Read a name/value pair from the input stream.  Strip off        */
/*      white space, ignore comments, split on '='.                     */
/*      Returns TRUE on success.                                        */
/************************************************************************/

int NASAKeywordHandler::ReadPair( CPLString &osName, CPLString &osValue )

{
    osName = "";
    osValue = "";

    if( !ReadWord( osName ) )
        return FALSE;

    SkipWhite();

    if( EQUAL(osName,"END") )
        return TRUE;

    if( *pszHeaderNext != '=' )
    {
        // ISIS3 does not have anything after the end group/object keyword. 
        if( EQUAL(osName,"End_Group") || EQUAL(osName,"End_Object") )
            return TRUE;
        else
            return FALSE;
    }
    
    pszHeaderNext++;
    
    SkipWhite();
    
    osValue = "";

    // Handle value lists like:     Name   = (Red, Red)
    if( *pszHeaderNext == '(' )
    {
        CPLString osWord;

        while( ReadWord( osWord ) )
        {
            SkipWhite();

            osValue += osWord;
            if( osWord[strlen(osWord)-1] == ')' )
                break;
        }
    }

    // Handle value lists like:     Name   = {Red, Red}
    else if( *pszHeaderNext == '{' )
    {
        CPLString osWord;

        while( ReadWord( osWord ) )
        {
            SkipWhite();

            osValue += osWord;
            if( osWord[strlen(osWord)-1] == '}' )
                break;
        }
    }

    else // Handle more normal "single word" values. 
    {
        if( !ReadWord( osValue ) )
            return FALSE;

    }
        
    SkipWhite();

    // No units keyword?   
    if( *pszHeaderNext != '<' )
        return TRUE;

    // Append units keyword.  For lines that like like this:
    //  MAP_RESOLUTION               = 4.0 <PIXEL/DEGREE>
    
    CPLString osWord;
    
    osValue += " ";
    
    while( ReadWord( osWord ) )
    {
        SkipWhite();
        
        osValue += osWord;
        if( osWord[strlen(osWord)-1] == '>' )
            break;
    }
    
    return TRUE;
}

/************************************************************************/
/*                              ReadWord()                              */
/*  Returns TRUE on success                                             */
/************************************************************************/

int NASAKeywordHandler::ReadWord( CPLString &osWord )

{
    osWord = "";

    SkipWhite();

    if( !(*pszHeaderNext != '\0' 
           && *pszHeaderNext != '=' 
           && !isspace((unsigned char)*pszHeaderNext)) )
        return FALSE;

    /* Extract a text string delimited by '\"' */
    /* Convert newlines (CR or LF) within quotes. While text strings
       support them as per ODL, the keyword list doesn't want them */
    if( *pszHeaderNext == '"' )
    {
        osWord += *(pszHeaderNext++);
        while( *pszHeaderNext != '"' )
        {
            if( *pszHeaderNext == '\0' )
                return FALSE;
            if( *pszHeaderNext == '\n' )
            {
                osWord += "\\n";
                pszHeaderNext++;
                continue;
            }
            if( *pszHeaderNext == '\r' )
            {
                osWord += "\\r";
                pszHeaderNext++;
                continue;
            }
            osWord += *(pszHeaderNext++);
        }
        osWord += *(pszHeaderNext++);
        return TRUE;
    }

    /* Extract a symbol string */
    /* These are expected to not have
       '\'' (delimiters),
       format effectors (should fit on a single line) or
       control characters.  
    */
    if( *pszHeaderNext == '\'' )
    {
        osWord += *(pszHeaderNext++);
        while( *pszHeaderNext != '\'' )
        {
            if( *pszHeaderNext == '\0' )
                return FALSE;

            osWord += *(pszHeaderNext++);
        }
        osWord += *(pszHeaderNext++);
        return TRUE;
    }

    /*
     * Extract normal text.  Terminated by '=' or whitespace. 
     *
     * A special exception is that a line may terminate with a '-' 
     * which is taken as a line extender, and we suck up white space to new
     * text.
     */
    while( *pszHeaderNext != '\0' 
           && *pszHeaderNext != '=' 
           && !isspace((unsigned char)*pszHeaderNext) )
    {
        osWord += *pszHeaderNext;
        pszHeaderNext++;

        if( *pszHeaderNext == '-' 
            && (pszHeaderNext[1] == 10 || pszHeaderNext[1] == 13) )
        {
            pszHeaderNext += 2;
            SkipWhite();
        }
    }
    
    return TRUE;
}

/************************************************************************/
/*                             SkipWhite()                              */
/*  Skip white spaces and C style comments                              */
/************************************************************************/

void NASAKeywordHandler::SkipWhite()

{
    for( ; TRUE; )
    {
        // Skip C style comments 
        if( *pszHeaderNext == '/' && pszHeaderNext[1] == '*' )
        {
            pszHeaderNext += 2;
            
            while( *pszHeaderNext != '\0' 
                   && (*pszHeaderNext != '*' 
                       || pszHeaderNext[1] != '/' ) )
            {
                pszHeaderNext++;
            }

            pszHeaderNext += 2;

            // consume till end of line.
            // reduce sensibility to a label error
            while( *pszHeaderNext != '\0'
            		&& *pszHeaderNext != 10
            		&& *pszHeaderNext != 13 )
            {
            	pszHeaderNext++;
            }
            continue;
        }

        // Skip # style comments 
         if( (*pszHeaderNext == 10 || *pszHeaderNext == 13 ||
 	     *pszHeaderNext == ' ' || *pszHeaderNext == '\t' )
              && pszHeaderNext[1] == '#' )
        {
            pszHeaderNext += 2;

            // consume till end of line.
            while( *pszHeaderNext != '\0' 
                   && *pszHeaderNext != 10
                   && *pszHeaderNext != 13 )
            {
                pszHeaderNext++;
            }
            continue;
        }

        // Skip white space (newline, space, tab, etc )
        if( isspace( (unsigned char)*pszHeaderNext ) )
        {
            pszHeaderNext++; 
            continue;
        }
        
        // not white space, return. 
        return;
    }
}

/************************************************************************/
/*                             GetKeyword()                             */
/************************************************************************/

const char *NASAKeywordHandler::GetKeyword( const char *pszPath,
                                            const char *pszDefault )

{
    const char *pszResult;

    pszResult = CSLFetchNameValue( papszKeywordList, pszPath );
    if( pszResult == NULL )
        return pszDefault;
    else
        return pszResult;
}

/************************************************************************/
/*                             GetKeywordList()                         */
/************************************************************************/
 
char **NASAKeywordHandler::GetKeywordList()
{
    return papszKeywordList;
}

