/******************************************************************************
 * $Id: dgn_pge.cpp,v 1.7 2003/05/21 03:42:01 warmerda Exp $
 *
 * Project:  DGN Tag Read/Write Bindings for Pacific Gas and Electric
 * Purpose:  DGNReadTags / DGNWriteTags implementation.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2002, Pacific Gas and Electric Co, San Franciso, CA, USA.
 *
 * All rights reserved.  Not to be used, reproduced or disclosed without
 * permission.
 ******************************************************************************
 *
 * $Log: dgn_pge.cpp,v $
 * Revision 1.7  2003/05/21 03:42:01  warmerda
 * Expanded tabs
 *
 * Revision 1.6  2002/04/11 14:22:31  warmerda
 * skip tags without tagsets
 *
 * Revision 1.5  2002/03/25 17:12:30  warmerda
 * allow wildcarded tagsets
 *
 * Revision 1.4  2002/03/15 20:13:23  warmerda
 * added error reporting.
 *
 * Revision 1.3  2002/03/15 16:08:42  warmerda
 * update to use strings lists for read/write function args
 *
 * Revision 1.2  2002/03/15 15:07:06  warmerda
 * use dgn_pge.h
 *
 * Revision 1.1  2002/03/14 21:40:37  warmerda
 * New
 *
 */

#include "dgn_pge.h"
#include "dgnlib.h"
#include "cpl_string.h"

CPL_CVSID("$Id: dgn_pge.cpp,v 1.7 2003/05/21 03:42:01 warmerda Exp $");

static int DGNUpdateTagValue( DGNHandle, DGNElemTagValue *, const char * );

/************************************************************************/
/*                            DGNReadTags()                             */
/************************************************************************/

int DGNReadTags( const char *pszFilename, int nTagScheme,
                 char ***ppapszRetTagSets, 
                 char ***ppapszRetTagNames, 
                 char ***ppapszRetTagValues )

{
    DGNHandle hDGN;
    DGNElemCore **papsTagValues = NULL, **papsTagSets = NULL;
    int  nTagValueCount = 0, nTagSetCount = 0;

    *ppapszRetTagSets = NULL;
    *ppapszRetTagNames = NULL;
    *ppapszRetTagValues = NULL;

/* -------------------------------------------------------------------- */
/*      Open the file for read access.                                  */
/* -------------------------------------------------------------------- */
    hDGN = DGNOpen( pszFilename, FALSE );

    if( hDGN == NULL )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Scan the file, keeping all tag set and tag value elements.      */
/* -------------------------------------------------------------------- */
    DGNElemCore *psElement; 

    while( (psElement = DGNReadElement( hDGN )) != NULL )
    {
        if( psElement->stype == DGNST_TAG_VALUE 
            && !psElement->deleted )
        {
            papsTagValues = (DGNElemCore **) 
                CPLRealloc(papsTagValues,sizeof(void*) * (++nTagValueCount));
            papsTagValues[nTagValueCount-1] = psElement;
        }
        else if( psElement->stype == DGNST_TAG_SET 
                 && !psElement->deleted )
        {
            papsTagSets = (DGNElemCore **) 
                CPLRealloc(papsTagSets,sizeof(void*) * (++nTagSetCount));
            papsTagSets[nTagSetCount-1] = psElement;
        }
        else
            DGNFreeElement( hDGN, psElement );
    }
    
/* -------------------------------------------------------------------- */
/*      For each Tag find the corresponding tagset definition.          */
/* -------------------------------------------------------------------- */
    DGNElemTagValue *psTag;
    DGNElemTagSet   *psSet;
    int iTagSet;
    int iTag;
    char *pszResult;

    pszResult = CPLStrdup( "TAG_LIST;" );

    for( iTag = 0; iTag < nTagValueCount; iTag++ )
    {
        DGNTagDef       *psTagDef = NULL;

        psTag = (DGNElemTagValue *) papsTagValues[iTag];
        psSet = NULL;

/* -------------------------------------------------------------------- */
/*      Find the tagset and tag definition within it associated with    */
/*      this tag.                                                       */
/* -------------------------------------------------------------------- */
        for( iTagSet = 0; iTagSet < nTagSetCount; iTagSet++ )
        {
            psSet = (DGNElemTagSet *) papsTagSets[iTagSet];

            if( psSet->tagSet == psTag->tagSet )
                break;
        }

        if( psSet != NULL && psSet->tagSet == psTag->tagSet )
        {
            int i;

            for( i = 0; i < psSet->tagCount; i++ )
            {
                if( psSet->tagList[i].id == psTag->tagIndex )
                {
                    psTagDef = psSet->tagList + i;
                    break;
                }
            }
        }

        if( psTagDef == NULL )
        {
            CPLDebug( "PGE_DGN", 
                      "Skipping tag %d:%d since no matching tagset found.",
                      psTag->tagSet, psTag->tagIndex );
            continue;
        }

/* -------------------------------------------------------------------- */
/*      Put the tagsetname and tag index in the result, or if we        */
/*      didn't find the associated tagset, just use the numeric         */
/*      values.                                                         */
/* -------------------------------------------------------------------- */
        if( psTagDef != NULL )
        {
            *ppapszRetTagSets = 
                CSLAddString( *ppapszRetTagSets, psSet->tagSetName );
            *ppapszRetTagNames = 
                CSLAddString( *ppapszRetTagNames, psTagDef->name );
        }
        else
        {
            char        szId[32];

            sprintf( szId, "%d", psTag->tagSet );
            *ppapszRetTagSets = CSLAddString( *ppapszRetTagSets, szId );

            sprintf( szId, "%d", psTag->tagIndex );
            *ppapszRetTagNames = CSLAddString( *ppapszRetTagNames, szId );
        }

/* -------------------------------------------------------------------- */
/*      Add the value.                                                  */
/* -------------------------------------------------------------------- */
        if( psTag->tagType == 1 )
        {
            *ppapszRetTagValues = 
                CSLAddString( *ppapszRetTagValues, psTag->tagValue.string );
        }
        else if( psTag->tagType == 3 )
        {
            char szValue[128];
            
            sprintf( szValue, "%d", psTag->tagValue.integer );
            *ppapszRetTagValues = 
                CSLAddString( *ppapszRetTagValues, szValue );
        }
        else if( psTag->tagType == 4 )
        {
            char szValue[128];
            
            sprintf( szValue, "%g", psTag->tagValue.real );
            *ppapszRetTagValues = 
                CSLAddString( *ppapszRetTagValues, szValue );
        }
        else
        {
            *ppapszRetTagValues = 
                CSLAddString( *ppapszRetTagValues, 
                              "<unrecognised tag type %d>;" );
        }
    }

/* -------------------------------------------------------------------- */
/*      Cleanup tag elements.                                           */
/* -------------------------------------------------------------------- */
    for( iTagSet = 0; iTagSet < nTagSetCount; iTagSet++ )
        DGNFreeElement( hDGN, papsTagSets[iTagSet] );

    CPLFree( papsTagSets );
    
    for( iTag = 0; iTag < nTagValueCount; iTag++ )
        DGNFreeElement( hDGN, papsTagValues[iTag] );

    CPLFree( papsTagValues );
    
    DGNClose( hDGN );

    return TRUE;
}

/************************************************************************/
/*                            DGNWriteTags()                            */
/************************************************************************/

int DGNWriteTags( const char *pszFilename, int nTagScheme, 
                  char **papszTagSets, 
                  char **papszTagNames, 
                  char **papszTagValues )
    
{
    int  iTag, i, bResult = TRUE;

/* -------------------------------------------------------------------- */
/*      open the file in update mode, and index it.                     */
/* -------------------------------------------------------------------- */
    DGNHandle hDGN;
    int       nElementCount; 

    hDGN = DGNOpen( pszFilename, TRUE );
    if( hDGN == NULL )
        return FALSE;

    const DGNElementInfo *pasIndex = DGNGetElementIndex(hDGN,&nElementCount);

    DGNSetOptions( hDGN, DGNO_CAPTURE_RAW_DATA );

/* -------------------------------------------------------------------- */
/*      Load a list of all the tag sets, so we can translate tag set    */
/*      names and tag names into numeric values.                        */
/* -------------------------------------------------------------------- */
    int iElem;
    DGNElemTagSet **papsTagSets = NULL;
    int nTagSetCount = 0;

    for( iElem = 0; iElem < nElementCount; iElem++ )
    {
        if( pasIndex[iElem].stype != DGNST_TAG_SET
            || (pasIndex[iElem].flags & DGNEIF_DELETED) )
            continue;

        papsTagSets = (DGNElemTagSet **)
            CPLRealloc(papsTagSets,sizeof(void*) * (nTagSetCount+1));

        if( !DGNGotoElement( hDGN, iElem ) )
            continue;

        papsTagSets[nTagSetCount++] = (DGNElemTagSet *) DGNReadElement( hDGN );
    }

/* -------------------------------------------------------------------- */
/*      Load all tag value elements.                                    */
/* -------------------------------------------------------------------- */
    DGNElemTagValue **papsTagValues = NULL;
    int nTagValueCount = 0;

    for( iElem = 0; iElem < nElementCount; iElem++ )
    {
        if( pasIndex[iElem].stype != DGNST_TAG_VALUE 
            || (pasIndex[iElem].flags & DGNEIF_DELETED) )
            continue;

        papsTagValues = (DGNElemTagValue **)
            CPLRealloc(papsTagValues,sizeof(void*) * (nTagValueCount+1));

        if( !DGNGotoElement( hDGN, iElem ) )
        {
            CPLError( CE_Warning, CPLE_AppDefined, 
                      "DGNGotoElement(%d) failed - skipping", iElem );
            bResult = FALSE;
            continue;
        }

        papsTagValues[nTagValueCount++] = 
            (DGNElemTagValue *) DGNReadElement( hDGN );
    }

/* -------------------------------------------------------------------- */
/*      Process each tag passed by the application.                     */
/* -------------------------------------------------------------------- */
    for( iTag = 0; iTag < CSLCount(papszTagSets); iTag++ )
    {
        int  iTagSet, nTagSetId=-1, nTagId=-1, bFoundTag = FALSE;
        DGNElemTagSet *psTagSet = NULL;

        // Find matching tagset.
        for( iTagSet = 0; iTagSet < nTagSetCount; iTagSet++ )
        {
            if( !EQUAL(papszTagSets[iTag],"*")
               && !EQUAL(papszTagSets[iTag],papsTagSets[iTagSet]->tagSetName) )
                continue;

            psTagSet = papsTagSets[iTagSet];

            // Find matching tag.
            for( i = 0; psTagSet != NULL && i < psTagSet->tagCount; i++ )
            {
                if( EQUAL(papszTagNames[iTag],psTagSet->tagList[i].name) )
                {
                    nTagSetId = psTagSet->tagSet;
                    nTagId = psTagSet->tagList[i].id;
                    break;
                }
            }

            if( nTagId == -1 )
            {
                if( !EQUAL(papszTagSets[iTag],"*") )
                {
                    CPLError( CE_Failure, CPLE_AppDefined, 
                              "Failed to find tag %s:%s", 
                              papszTagSets[iTag], papszTagNames[iTag] );
                    bResult = FALSE;
                }
                continue;
            }

            bFoundTag = TRUE;

            // Now we find the associated tag value element.
            for( i = 0; i < nTagValueCount; i++ )
            {
                if( papsTagValues[i]->tagSet == nTagSetId
                    && papsTagValues[i]->tagIndex == nTagId )
                    break;
            }
            
            if( i == nTagValueCount )
            {
                CPLError( CE_Failure, CPLE_AppDefined, 
                          "Failed to find tag for %s:%s",
                          papszTagSets[iTag], papszTagNames[iTag] );
                bResult = FALSE;
                continue;
            }
            
            // Update tag value with new value.
            if( !DGNUpdateTagValue( hDGN, papsTagValues[i], 
                                    papszTagValues[iTag] ) )
            {
                bResult = FALSE;
            }
        }

        if( psTagSet == NULL )
        {
            CPLError( CE_Failure, CPLE_AppDefined, 
                      "Failed to find tagset %s", 
                      papszTagSets[iTag] );
            bResult = FALSE;
        }

        if( !bFoundTag && EQUAL(papszTagSets[iTag],"*") )
        {
            CPLError( CE_Failure, CPLE_AppDefined, 
                      "Found no tags matching *:%s", 
                      papszTagNames[iTag] );
            bResult = FALSE;
        }
    }

    DGNClose( hDGN );

    return bResult;
}

/************************************************************************/
/*                         DGNUpdateTagValue()                          */
/************************************************************************/

int DGNUpdateTagValue( DGNHandle hDGN, DGNElemTagValue *psTag, 
                       const char *pszNewValue )

{
    if( psTag->core.raw_data == NULL )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "core.raw_data is NULL." );
        return FALSE;
    }

    if( psTag->tagType == 1 )
    {
        int nNewSize, nDataBytes;

        nNewSize = 154 + strlen(pszNewValue) + 1;
        nNewSize += nNewSize % 2; // round up to multiple of two

        if( !DGNResizeElement( hDGN, (DGNElemCore *) psTag, nNewSize ) )
            return FALSE;

        strcpy( (char *) psTag->core.raw_data + 154, pszNewValue );

        nDataBytes = strlen(pszNewValue)+1;
        psTag->core.raw_data[150] = nDataBytes % 256;
        psTag->core.raw_data[151] = nDataBytes / 256;
        psTag->core.raw_data[152] = 0;
        psTag->core.raw_data[153] = 0;
    }
    else if( psTag->tagType == 3 )
    {
        GInt32  nTagValue = CPL_LSBWORD32(atoi(pszNewValue));

        memcpy( psTag->core.raw_data + 154, &nTagValue, 4 );
    }
    else if( psTag->tagType == 4 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "Currently writing floating point tags not supported." );
        return FALSE;
    }

    return DGNWriteElement( hDGN, (DGNElemCore *) psTag );
}

