/******************************************************************************
 * $Id: dgnread.cpp 10645 2007-01-18 02:22:39Z warmerdam $
 *
 * Project:  Microstation DGN Access Library
 * Purpose:  DGN Access Library element reading code.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Avenza Systems Inc, http://www.avenza.com/
 *
 * 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.
 ****************************************************************************/

#include "dgnlibp.h"

CPL_CVSID("$Id: dgnread.cpp 10645 2007-01-18 02:22:39Z warmerdam $");

static DGNElemCore *DGNParseTCB( DGNInfo * );
static DGNElemCore *DGNParseColorTable( DGNInfo * );
static DGNElemCore *DGNParseTagSet( DGNInfo * );


/************************************************************************/
/*                           DGNGotoElement()                           */
/************************************************************************/

/**
 * Seek to indicated element.
 *
 * Changes what element will be read on the next call to DGNReadElement(). 
 * Note that this function requires and index, and one will be built if
 * not already available.
 *
 * @param hDGN the file to affect.
 * @param element_id the element to seek to.  These values are sequentially
 * ordered starting at zero for the first element.
 * 
 * @return returns TRUE on success or FALSE on failure. 
 */

int DGNGotoElement( DGNHandle hDGN, int element_id )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;

    DGNBuildIndex( psDGN );

    if( element_id < 0 || element_id >= psDGN->element_count )
        return FALSE;

    if( VSIFSeek( psDGN->fp, psDGN->element_index[element_id].offset, 
                  SEEK_SET ) != 0 )
        return FALSE;

    psDGN->next_element_id = element_id;
    psDGN->in_complex_group = FALSE;

    return TRUE;
}

/************************************************************************/
/*                         DGNLoadRawElement()                          */
/************************************************************************/

int DGNLoadRawElement( DGNInfo *psDGN, int *pnType, int *pnLevel )

{
/* -------------------------------------------------------------------- */
/*      Read the first four bytes to get the level, type, and word      */
/*      count.                                                          */
/* -------------------------------------------------------------------- */
    int         nType, nWords, nLevel;

    if( VSIFRead( psDGN->abyElem, 1, 4, psDGN->fp ) != 4 )
        return FALSE;

    /* Is this an 0xFFFF endof file marker? */
    if( psDGN->abyElem[0] == 0xff && psDGN->abyElem[1] == 0xff )
        return FALSE;

    nWords = psDGN->abyElem[2] + psDGN->abyElem[3]*256;
    nType = psDGN->abyElem[1] & 0x7f;
    nLevel = psDGN->abyElem[0] & 0x3f;

/* -------------------------------------------------------------------- */
/*      Read the rest of the element data into the working buffer.      */
/* -------------------------------------------------------------------- */
    CPLAssert( nWords * 2 + 4 <= (int) sizeof(psDGN->abyElem) );

    if( (int) VSIFRead( psDGN->abyElem + 4, 2, nWords, psDGN->fp ) != nWords )
        return FALSE;

    psDGN->nElemBytes = nWords * 2 + 4;

    psDGN->next_element_id++;

/* -------------------------------------------------------------------- */
/*      Return requested info.                                          */
/* -------------------------------------------------------------------- */
    if( pnType != NULL )
        *pnType = nType;
    
    if( pnLevel != NULL )
        *pnLevel = nLevel;
    
    return TRUE;
}


/************************************************************************/
/*                          DGNGetRawExtents()                          */
/*                                                                      */
/*      Returns FALSE if the element type does not have reconisable     */
/*      element extents, other TRUE and the extents will be updated.    */
/*                                                                      */
/*      It is assumed the raw element data has been loaded into the     */
/*      working area by DGNLoadRawElement().                            */
/************************************************************************/

static int 
DGNGetRawExtents( DGNInfo *psDGN, int nType, unsigned char *pabyRawData,
                  GUInt32 *pnXMin, GUInt32 *pnYMin, GUInt32 *pnZMin, 
                  GUInt32 *pnXMax, GUInt32 *pnYMax, GUInt32 *pnZMax )

{
    if( pabyRawData == NULL )
        pabyRawData = psDGN->abyElem + 0;

    switch( nType )
    {
      case DGNT_LINE:
      case DGNT_LINE_STRING:
      case DGNT_SHAPE:
      case DGNT_CURVE:
      case DGNT_BSPLINE_POLE:
      case DGNT_BSPLINE_SURFACE_HEADER:
      case DGNT_BSPLINE_CURVE_HEADER:
      case DGNT_ELLIPSE:
      case DGNT_ARC:
      case DGNT_TEXT:
      case DGNT_TEXT_NODE:
      case DGNT_COMPLEX_CHAIN_HEADER:
      case DGNT_COMPLEX_SHAPE_HEADER:
      case DGNT_CONE:
      case DGNT_3DSURFACE_HEADER:
      case DGNT_3DSOLID_HEADER:
        *pnXMin = DGN_INT32( pabyRawData + 4 );
        *pnYMin = DGN_INT32( pabyRawData + 8 );
        if( pnZMin != NULL )
            *pnZMin = DGN_INT32( pabyRawData + 12 );

        *pnXMax = DGN_INT32( pabyRawData + 16 );
        *pnYMax = DGN_INT32( pabyRawData + 20 );
        if( pnZMax != NULL )
            *pnZMax = DGN_INT32( pabyRawData + 24 );
        return TRUE;

      default:
        return FALSE;
    }
}


/************************************************************************/
/*                        DGNGetElementExtents()                        */
/************************************************************************/

/**
 * Fetch extents of an element.
 *
 * This function will return the extents of the passed element if possible.
 * The extents are extracted from the element header if it contains them,
 * and transformed into master georeferenced format.  Some element types 
 * do not have extents at all and will fail.  
 *
 * This call will also fail if the extents raw data for the element is not
 * available.  This will occur if it was not the most recently read element,
 * and if the raw_data field is not loaded. 
 *
 * @param hDGN the handle of the file to read from.
 *
 * @param psElement the element to extract extents from.
 *
 * @param psMin structure loaded with X, Y and Z minimum values for the 
 * extent. 
 *
 * @param psMax structure loaded with X, Y and Z maximum values for the 
 * extent. 
 *
 * @return TRUE on success of FALSE if extracting extents fails. 
 */

int DGNGetElementExtents( DGNHandle hDGN, DGNElemCore *psElement, 
                          DGNPoint *psMin, DGNPoint *psMax )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;
    GUInt32 anMin[3], anMax[3];
    int bResult;

/* -------------------------------------------------------------------- */
/*      Get the extents if we have raw data in the element, or          */
/*      loaded in the file buffer.                                      */
/* -------------------------------------------------------------------- */
    if( psElement->raw_data != NULL )
        bResult = DGNGetRawExtents( psDGN, psElement->type, 
                                    psElement->raw_data, 
                                    anMin + 0, anMin + 1, anMin + 2,
                                    anMax + 0, anMax + 1, anMax + 2 );
    else if( psElement->element_id == psDGN->next_element_id - 1 )
        bResult = DGNGetRawExtents( psDGN, psElement->type, 
                                    psDGN->abyElem + 0, 
                                    anMin + 0, anMin + 1, anMin + 2,
                                    anMax + 0, anMax + 1, anMax + 2 );
    else
    {
        CPLError(CE_Warning, CPLE_AppDefined, 
                 "DGNGetElementExtents() fails because the requested element\n"
                 " does not have raw data available." );
        return FALSE;
    }

    if( !bResult )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Transform to user coordinate system and return.  The offset     */
/*      is to convert from "binary offset" form to twos complement.     */
/* -------------------------------------------------------------------- */
    psMin->x = anMin[0] - 2147483648.0;
    psMin->y = anMin[1] - 2147483648.0;
    psMin->z = anMin[2] - 2147483648.0;

    psMax->x = anMax[0] - 2147483648.0;
    psMax->y = anMax[1] - 2147483648.0;
    psMax->z = anMax[2] - 2147483648.0;

    DGNTransformPoint( psDGN, psMin );
    DGNTransformPoint( psDGN, psMax );

    return TRUE;
}

/************************************************************************/
/*                         DGNProcessElement()                          */
/*                                                                      */
/*      Assumes the raw element data has already been loaded, and       */
/*      tries to convert it into an element structure.                  */
/************************************************************************/

static DGNElemCore *DGNProcessElement( DGNInfo *psDGN, int nType, int nLevel )

{
    DGNElemCore *psElement = NULL;

/* -------------------------------------------------------------------- */
/*      Handle based on element type.                                   */
/* -------------------------------------------------------------------- */
    switch( nType )
    {
      case DGNT_CELL_HEADER:
      {
          DGNElemCellHeader *psCell;

          psCell = (DGNElemCellHeader *) 
              CPLCalloc(sizeof(DGNElemCellHeader),1);
          psElement = (DGNElemCore *) psCell;
          psElement->stype = DGNST_CELL_HEADER;
          DGNParseCore( psDGN, psElement );

          psCell->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;

          DGNRad50ToAscii( psDGN->abyElem[38] + psDGN->abyElem[39] * 256, 
                           psCell->name + 0 );
          DGNRad50ToAscii( psDGN->abyElem[40] + psDGN->abyElem[41] * 256, 
                           psCell->name + 3 );

          psCell->cclass = psDGN->abyElem[42] + psDGN->abyElem[43] * 256;
          psCell->levels[0] = psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
          psCell->levels[1] = psDGN->abyElem[46] + psDGN->abyElem[47] * 256;
          psCell->levels[2] = psDGN->abyElem[48] + psDGN->abyElem[49] * 256;
          psCell->levels[3] = psDGN->abyElem[50] + psDGN->abyElem[51] * 256;

          if( psDGN->dimension == 2 )
          {
              psCell->rnglow.x = DGN_INT32( psDGN->abyElem + 52 );
              psCell->rnglow.y = DGN_INT32( psDGN->abyElem + 56 );
              psCell->rnghigh.x = DGN_INT32( psDGN->abyElem + 60 );
              psCell->rnghigh.y = DGN_INT32( psDGN->abyElem + 64 );

              psCell->trans[0] =
                1.0 * DGN_INT32( psDGN->abyElem + 68 ) / (1<<31);
              psCell->trans[1] = 
                1.0 * DGN_INT32( psDGN->abyElem + 72 ) / (1<<31);
              psCell->trans[2] = 
                1.0 * DGN_INT32( psDGN->abyElem + 76 ) / (1<<31);
              psCell->trans[3] = 
                1.0 * DGN_INT32( psDGN->abyElem + 80 ) / (1<<31);

              psCell->origin.x = DGN_INT32( psDGN->abyElem + 84 );
              psCell->origin.y = DGN_INT32( psDGN->abyElem + 88 );

              {
              double a, b, c, d, a2, c2;
              a = DGN_INT32( psDGN->abyElem + 68 );
              b = DGN_INT32( psDGN->abyElem + 72 );
              c = DGN_INT32( psDGN->abyElem + 76 );
              d = DGN_INT32( psDGN->abyElem + 80 );
              a2 = a * a;
              c2 = c * c;
              
              psCell->xscale = sqrt(a2 + c2) / 214748;
              psCell->yscale = sqrt(b*b + d*d) / 214748;
              if( (a2 + c2) <= 0.0 )
                  psCell->rotation = 0.0;
              else
                  psCell->rotation = acos(a / sqrt(a2 + c2));

              if (b <= 0)
                  psCell->rotation = psCell->rotation * 180 / PI;
              else
                  psCell->rotation = 360 - psCell->rotation * 180 / PI;
              }
          }
          else
          {
              psCell->rnglow.x = DGN_INT32( psDGN->abyElem + 52 );
              psCell->rnglow.y = DGN_INT32( psDGN->abyElem + 56 );
              psCell->rnglow.z = DGN_INT32( psDGN->abyElem + 60 );
              psCell->rnghigh.x = DGN_INT32( psDGN->abyElem + 64 );
              psCell->rnghigh.y = DGN_INT32( psDGN->abyElem + 68 );
              psCell->rnghigh.z = DGN_INT32( psDGN->abyElem + 72 );

              psCell->trans[0] =
                1.0 * DGN_INT32( psDGN->abyElem + 76 ) / (1<<31);
              psCell->trans[1] = 
                1.0 * DGN_INT32( psDGN->abyElem + 80 ) / (1<<31);
              psCell->trans[2] = 
                1.0 * DGN_INT32( psDGN->abyElem + 84 ) / (1<<31);
              psCell->trans[3] = 
                1.0 * DGN_INT32( psDGN->abyElem + 88 ) / (1<<31);
              psCell->trans[4] = 
                1.0 * DGN_INT32( psDGN->abyElem + 92 ) / (1<<31);
              psCell->trans[5] = 
                1.0 * DGN_INT32( psDGN->abyElem + 96 ) / (1<<31);
              psCell->trans[6] = 
                1.0 * DGN_INT32( psDGN->abyElem + 100 ) / (1<<31);
              psCell->trans[7] = 
                1.0 * DGN_INT32( psDGN->abyElem + 104 ) / (1<<31);
              psCell->trans[8] = 
                1.0 * DGN_INT32( psDGN->abyElem + 108 ) / (1<<31);

              psCell->origin.x = DGN_INT32( psDGN->abyElem + 112 );
              psCell->origin.y = DGN_INT32( psDGN->abyElem + 116 );
              psCell->origin.z = DGN_INT32( psDGN->abyElem + 120 );
          }

          DGNTransformPoint( psDGN, &(psCell->rnglow) );
          DGNTransformPoint( psDGN, &(psCell->rnghigh) );
          DGNTransformPoint( psDGN, &(psCell->origin) );
      }
      break;

      case DGNT_CELL_LIBRARY:
      {
          DGNElemCellLibrary *psCell;
          int                 iWord;

          psCell = (DGNElemCellLibrary *) 
              CPLCalloc(sizeof(DGNElemCellLibrary),1);
          psElement = (DGNElemCore *) psCell;
          psElement->stype = DGNST_CELL_LIBRARY;
          DGNParseCore( psDGN, psElement );

          DGNRad50ToAscii( psDGN->abyElem[32] + psDGN->abyElem[33] * 256, 
                           psCell->name + 0 );
          DGNRad50ToAscii( psDGN->abyElem[34] + psDGN->abyElem[35] * 256, 
                           psCell->name + 3 );

          psElement->properties = psDGN->abyElem[38] 
              + psDGN->abyElem[39] * 256;

          psCell->dispsymb = psDGN->abyElem[40] + psDGN->abyElem[41] * 256;

          psCell->cclass = psDGN->abyElem[42] + psDGN->abyElem[43] * 256;
          psCell->levels[0] = psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
          psCell->levels[1] = psDGN->abyElem[46] + psDGN->abyElem[47] * 256;
          psCell->levels[2] = psDGN->abyElem[48] + psDGN->abyElem[49] * 256;
          psCell->levels[3] = psDGN->abyElem[50] + psDGN->abyElem[51] * 256;

          psCell->numwords = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;

          memset( psCell->description, 0, sizeof(psCell->description) );
          
          for( iWord = 0; iWord < 9; iWord++ )
          {
              int iOffset = 52 + iWord * 2;

              DGNRad50ToAscii( psDGN->abyElem[iOffset] 
                               + psDGN->abyElem[iOffset+1] * 256, 
                               psCell->description + iWord * 3 );
          }
      }
      break;

      case DGNT_LINE:
      {
          DGNElemMultiPoint *psLine;

          psLine = (DGNElemMultiPoint *) 
              CPLCalloc(sizeof(DGNElemMultiPoint),1);
          psElement = (DGNElemCore *) psLine;
          psElement->stype = DGNST_MULTIPOINT;
          DGNParseCore( psDGN, psElement );

          psLine->num_vertices = 2;
          if( psDGN->dimension == 2 )
          {
              psLine->vertices[0].x = DGN_INT32( psDGN->abyElem + 36 );
              psLine->vertices[0].y = DGN_INT32( psDGN->abyElem + 40 );
              psLine->vertices[1].x = DGN_INT32( psDGN->abyElem + 44 );
              psLine->vertices[1].y = DGN_INT32( psDGN->abyElem + 48 );
          }
          else
          {
              psLine->vertices[0].x = DGN_INT32( psDGN->abyElem + 36 );
              psLine->vertices[0].y = DGN_INT32( psDGN->abyElem + 40 );
              psLine->vertices[0].z = DGN_INT32( psDGN->abyElem + 44 );
              psLine->vertices[1].x = DGN_INT32( psDGN->abyElem + 48 );
              psLine->vertices[1].y = DGN_INT32( psDGN->abyElem + 52 );
              psLine->vertices[1].z = DGN_INT32( psDGN->abyElem + 56 );
          }
          DGNTransformPoint( psDGN, psLine->vertices + 0 );
          DGNTransformPoint( psDGN, psLine->vertices + 1 );
      }
      break;

      case DGNT_LINE_STRING:
      case DGNT_SHAPE:
      case DGNT_CURVE:
      case DGNT_BSPLINE_POLE:
      {
          DGNElemMultiPoint *psLine;
          int                i, count;
          int                pntsize = psDGN->dimension * 4;

          count = psDGN->abyElem[36] + psDGN->abyElem[37]*256;
          psLine = (DGNElemMultiPoint *) 
              CPLCalloc(sizeof(DGNElemMultiPoint)+(count-2)*sizeof(DGNPoint),1);
          psElement = (DGNElemCore *) psLine;
          psElement->stype = DGNST_MULTIPOINT;
          DGNParseCore( psDGN, psElement );

          if( psDGN->nElemBytes < 38 + count * pntsize )
          {
              CPLError( CE_Warning, CPLE_AppDefined, 
                        "Trimming multipoint vertices to %d from %d because\n"
                        "element is short.\n", 
                        (psDGN->nElemBytes - 38) / pntsize,
                        count );
              count = (psDGN->nElemBytes - 38) / pntsize;
          }
          psLine->num_vertices = count;
          for( i = 0; i < psLine->num_vertices; i++ )
          {
              psLine->vertices[i].x = 
                  DGN_INT32( psDGN->abyElem + 38 + i*pntsize );
              psLine->vertices[i].y = 
                  DGN_INT32( psDGN->abyElem + 42 + i*pntsize );
              if( psDGN->dimension == 3 )
                  psLine->vertices[i].z = 
                      DGN_INT32( psDGN->abyElem + 46 + i*pntsize );

              DGNTransformPoint( psDGN, psLine->vertices + i );
          }
      }
      break;

      case DGNT_TEXT_NODE:
      {
          DGNElemTextNode *psNode;

          psNode = (DGNElemTextNode *) CPLCalloc(sizeof(DGNElemTextNode),1);
          psElement = (DGNElemCore *) psNode;
          psElement->stype = DGNST_TEXT_NODE;
          DGNParseCore( psDGN, psElement );

          psNode->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
          psNode->numelems  = psDGN->abyElem[38] + psDGN->abyElem[39] * 256;

          psNode->node_number 	= psDGN->abyElem[40] + psDGN->abyElem[41] * 256;
          psNode->max_length 	= psDGN->abyElem[42];
          psNode->max_used 	= psDGN->abyElem[43];
          psNode->font_id	= psDGN->abyElem[44];
          psNode->justification	= psDGN->abyElem[45];
          psNode->length_mult = (DGN_INT32( psDGN->abyElem + 50 ))
              * psDGN->scale * 6.0 / 1000.0;
          psNode->height_mult = (DGN_INT32( psDGN->abyElem + 54 ))
              * psDGN->scale * 6.0 / 1000.0;

          if( psDGN->dimension == 2 )
	    {
	      psNode->rotation = DGN_INT32( psDGN->abyElem + 58 ) / 360000.0;

	      psNode->origin.x = DGN_INT32( psDGN->abyElem + 62 );
	      psNode->origin.y = DGN_INT32( psDGN->abyElem + 66 );
	    }
	  else
	    {
              /* leave quaternion for later */

              psNode->origin.x = DGN_INT32( psDGN->abyElem + 74 );
              psNode->origin.y = DGN_INT32( psDGN->abyElem + 78 );
              psNode->origin.z = DGN_INT32( psDGN->abyElem + 82 );
	    }
          DGNTransformPoint( psDGN, &(psNode->origin) );

      }
      break;

      case DGNT_GROUP_DATA:
        if( nLevel == DGN_GDL_COLOR_TABLE )
        {
            psElement = DGNParseColorTable( psDGN );
        }
        else
        {
            psElement = (DGNElemCore *) CPLCalloc(sizeof(DGNElemCore),1);
            psElement->stype = DGNST_CORE;
            DGNParseCore( psDGN, psElement );
        }
        break;

      case DGNT_ELLIPSE:
      {
          DGNElemArc *psEllipse;

          psEllipse = (DGNElemArc *) CPLCalloc(sizeof(DGNElemArc),1);
          psElement = (DGNElemCore *) psEllipse;
          psElement->stype = DGNST_ARC;
          DGNParseCore( psDGN, psElement );

          memcpy( &(psEllipse->primary_axis), psDGN->abyElem + 36, 8 );
          DGN2IEEEDouble( &(psEllipse->primary_axis) );
          psEllipse->primary_axis *= psDGN->scale;

          memcpy( &(psEllipse->secondary_axis), psDGN->abyElem + 44, 8 );
          DGN2IEEEDouble( &(psEllipse->secondary_axis) );
          psEllipse->secondary_axis *= psDGN->scale;

          if( psDGN->dimension == 2 )
          {
              psEllipse->rotation = DGN_INT32( psDGN->abyElem + 52 );
              psEllipse->rotation = psEllipse->rotation / 360000.0;
              
              memcpy( &(psEllipse->origin.x), psDGN->abyElem + 56, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.x) );
              
              memcpy( &(psEllipse->origin.y), psDGN->abyElem + 64, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.y) );
          }
          else
          {
              /* leave quaternion for later */

              memcpy( &(psEllipse->origin.x), psDGN->abyElem + 68, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.x) );
              
              memcpy( &(psEllipse->origin.y), psDGN->abyElem + 76, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.y) );
              
              memcpy( &(psEllipse->origin.z), psDGN->abyElem + 84, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.z) );

              psEllipse->quat[0] = DGN_INT32( psDGN->abyElem + 52 );
              psEllipse->quat[1] = DGN_INT32( psDGN->abyElem + 56 );
              psEllipse->quat[2] = DGN_INT32( psDGN->abyElem + 60 );
              psEllipse->quat[3] = DGN_INT32( psDGN->abyElem + 64 );
          }

          DGNTransformPoint( psDGN, &(psEllipse->origin) );

          psEllipse->startang = 0.0;
          psEllipse->sweepang = 360.0;
      }
      break;

      case DGNT_ARC:
      {
          DGNElemArc *psEllipse;
          GInt32     nSweepVal;

          psEllipse = (DGNElemArc *) CPLCalloc(sizeof(DGNElemArc),1);
          psElement = (DGNElemCore *) psEllipse;
          psElement->stype = DGNST_ARC;
          DGNParseCore( psDGN, psElement );

          psEllipse->startang = DGN_INT32( psDGN->abyElem + 36 );
          psEllipse->startang = psEllipse->startang / 360000.0;
          if( psDGN->abyElem[41] & 0x80 )
          {
              psDGN->abyElem[41] &= 0x7f;
              nSweepVal = -1 * DGN_INT32( psDGN->abyElem + 40 );
          }
          else
              nSweepVal = DGN_INT32( psDGN->abyElem + 40 );

          if( nSweepVal == 0 )
              psEllipse->sweepang = 360.0;
          else
              psEllipse->sweepang = nSweepVal / 360000.0;
          
          memcpy( &(psEllipse->primary_axis), psDGN->abyElem + 44, 8 );
          DGN2IEEEDouble( &(psEllipse->primary_axis) );
          psEllipse->primary_axis *= psDGN->scale;

          memcpy( &(psEllipse->secondary_axis), psDGN->abyElem + 52, 8 );
          DGN2IEEEDouble( &(psEllipse->secondary_axis) );
          psEllipse->secondary_axis *= psDGN->scale;
          
          if( psDGN->dimension == 2 )
          {
              psEllipse->rotation = DGN_INT32( psDGN->abyElem + 60 );
              psEllipse->rotation = psEllipse->rotation / 360000.0;
          
              memcpy( &(psEllipse->origin.x), psDGN->abyElem + 64, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.x) );
              
              memcpy( &(psEllipse->origin.y), psDGN->abyElem + 72, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.y) );
          }
          else 
          {
              /* for now we don't try to handle quaternion */
              psEllipse->rotation = 0;
          
              memcpy( &(psEllipse->origin.x), psDGN->abyElem + 76, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.x) );
              
              memcpy( &(psEllipse->origin.y), psDGN->abyElem + 84, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.y) );

              memcpy( &(psEllipse->origin.z), psDGN->abyElem + 92, 8 );
              DGN2IEEEDouble( &(psEllipse->origin.z) );

              psEllipse->quat[0] = DGN_INT32( psDGN->abyElem + 60 );
              psEllipse->quat[1] = DGN_INT32( psDGN->abyElem + 64 );
              psEllipse->quat[2] = DGN_INT32( psDGN->abyElem + 68 );
              psEllipse->quat[3] = DGN_INT32( psDGN->abyElem + 72 );
          }

          DGNTransformPoint( psDGN, &(psEllipse->origin) );
      }
      break;

      case DGNT_TEXT:
      {
          DGNElemText *psText;
          int         num_chars, text_off;

          if( psDGN->dimension == 2 )
              num_chars = psDGN->abyElem[58];
          else
              num_chars = psDGN->abyElem[74];

          psText = (DGNElemText *) CPLCalloc(sizeof(DGNElemText)+num_chars,1);
          psElement = (DGNElemCore *) psText;
          psElement->stype = DGNST_TEXT;
          DGNParseCore( psDGN, psElement );

          psText->font_id = psDGN->abyElem[36];
          psText->justification = psDGN->abyElem[37];
          psText->length_mult = (DGN_INT32( psDGN->abyElem + 38 ))
              * psDGN->scale * 6.0 / 1000.0;
          psText->height_mult = (DGN_INT32( psDGN->abyElem + 42 ))
              * psDGN->scale * 6.0 / 1000.0;

          if( psDGN->dimension == 2 )
          {
              psText->rotation = DGN_INT32( psDGN->abyElem + 46 );
              psText->rotation = psText->rotation / 360000.0;

              psText->origin.x = DGN_INT32( psDGN->abyElem + 50 );
              psText->origin.y = DGN_INT32( psDGN->abyElem + 54 );
              text_off = 60;
          }
          else
          {
              /* leave quaternion for later */

              psText->origin.x = DGN_INT32( psDGN->abyElem + 62 );
              psText->origin.y = DGN_INT32( psDGN->abyElem + 66 );
              psText->origin.z = DGN_INT32( psDGN->abyElem + 70 );
              text_off = 76;
          }

          DGNTransformPoint( psDGN, &(psText->origin) );

          /* experimental multibyte support from Ason Kang (hiska@netian.com)*/
          if (*(psDGN->abyElem + text_off) == 0xFF 
              && *(psDGN->abyElem + text_off + 1) == 0xFD) 
          {
              int n=0;
              for (int i=0;i<num_chars/2-1;i++) {
                  unsigned short w;
                  memcpy(&w,psDGN->abyElem + text_off + 2 + i*2 ,2);
                  w = CPL_LSBWORD16(w);
                  if (w<256) { // if alpa-numeric code area : Normal character 
                      *(psText->string + n) = (char) (w & 0xFF); 
                      n++; // skip 1 byte;
                  }
                  else { // if extend code area : 2 byte Korean character 
                      *(psText->string + n)     = (char) (w >> 8);   // hi
                      *(psText->string + n + 1) = (char) (w & 0xFF); // lo
                      n+=2; // 2 byte
                  }
              }
              psText->string[n] = '\0'; // terminate C string
          }
          else
          {
              memcpy( psText->string, psDGN->abyElem + text_off, num_chars );
              psText->string[num_chars] = '\0';
          }
      }
      break;

      case DGNT_TCB:
        psElement = DGNParseTCB( psDGN );
        break;

      case DGNT_COMPLEX_CHAIN_HEADER:
      case DGNT_COMPLEX_SHAPE_HEADER:
      {
          DGNElemComplexHeader *psHdr;

          psHdr = (DGNElemComplexHeader *) 
              CPLCalloc(sizeof(DGNElemComplexHeader),1);
          psElement = (DGNElemCore *) psHdr;
          psElement->stype = DGNST_COMPLEX_HEADER;
          DGNParseCore( psDGN, psElement );

          psHdr->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
          psHdr->numelems = psDGN->abyElem[38] + psDGN->abyElem[39] * 256;
      }
      break;

      case DGNT_TAG_VALUE:
      {
          DGNElemTagValue *psTag;

          psTag = (DGNElemTagValue *) 
              CPLCalloc(sizeof(DGNElemTagValue),1);
          psElement = (DGNElemCore *) psTag;
          psElement->stype = DGNST_TAG_VALUE;
          DGNParseCore( psDGN, psElement );

          psTag->tagType = psDGN->abyElem[74] + psDGN->abyElem[75] * 256;
          memcpy( &(psTag->tagSet), psDGN->abyElem + 68, 4 );
          psTag->tagSet = CPL_LSBWORD32(psTag->tagSet);
          psTag->tagIndex = psDGN->abyElem[72] + psDGN->abyElem[73] * 256;
          psTag->tagLength = psDGN->abyElem[150] + psDGN->abyElem[151] * 256;

          if( psTag->tagType == 1 )
          {
              psTag->tagValue.string = 
                  CPLStrdup( (char *) psDGN->abyElem + 154 );
          }
          else if( psTag->tagType == 3 )
          {
              memcpy( &(psTag->tagValue.integer), 
                      psDGN->abyElem + 154, 4 );
              psTag->tagValue.integer = 
                  CPL_LSBWORD32( psTag->tagValue.integer );
          }
          else if( psTag->tagType == 4 )
          {
              memcpy( &(psTag->tagValue.real), 
                      psDGN->abyElem + 154, 8 );
              DGN2IEEEDouble( &(psTag->tagValue.real) );
          }
      }
      break;

      case DGNT_APPLICATION_ELEM:
        if( nLevel == 24 )
        {
            psElement = DGNParseTagSet( psDGN );
        }
        else
        {
            psElement = (DGNElemCore *) CPLCalloc(sizeof(DGNElemCore),1);
            psElement->stype = DGNST_CORE;
            DGNParseCore( psDGN, psElement );
        }
        break;

      case DGNT_CONE:
        {
          DGNElemCone *psCone;

          psCone = (DGNElemCone *) CPLCalloc(sizeof(DGNElemCone),1);
          psElement = (DGNElemCore *) psCone;
          psElement->stype = DGNST_CONE;
          DGNParseCore( psDGN, psElement );

          CPLAssert( psDGN->dimension == 3 );
          psCone->unknown = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
          psCone->quat[0] = DGN_INT32( psDGN->abyElem + 38 );
          psCone->quat[1] = DGN_INT32( psDGN->abyElem + 42 );
          psCone->quat[2] = DGN_INT32( psDGN->abyElem + 46 );
          psCone->quat[3] = DGN_INT32( psDGN->abyElem + 50 );
 
          memcpy( &(psCone->center_1.x), psDGN->abyElem + 54, 8 );
          DGN2IEEEDouble( &(psCone->center_1.x) );
          memcpy( &(psCone->center_1.y), psDGN->abyElem + 62, 8 );
          DGN2IEEEDouble( &(psCone->center_1.y) );
          memcpy( &(psCone->center_1.z), psDGN->abyElem + 70, 8 );
          DGN2IEEEDouble( &(psCone->center_1.z) );
          memcpy( &(psCone->radius_1), psDGN->abyElem + 78, 8 );
          DGN2IEEEDouble( &(psCone->radius_1) );

          memcpy( &(psCone->center_2.x), psDGN->abyElem + 86, 8 );
          DGN2IEEEDouble( &(psCone->center_2.x) );
          memcpy( &(psCone->center_2.y), psDGN->abyElem + 94, 8 );
          DGN2IEEEDouble( &(psCone->center_2.y) );
          memcpy( &(psCone->center_2.z), psDGN->abyElem + 102, 8 );
          DGN2IEEEDouble( &(psCone->center_2.z) );
          memcpy( &(psCone->radius_2), psDGN->abyElem + 110, 8 );
          DGN2IEEEDouble( &(psCone->radius_2) );

          psCone->radius_1 *= psDGN->scale;
          psCone->radius_2 *= psDGN->scale;
          DGNTransformPoint( psDGN, &psCone->center_1 );
          DGNTransformPoint( psDGN, &psCone->center_2 );
        }
        break;

      case DGNT_3DSURFACE_HEADER:
      case DGNT_3DSOLID_HEADER:
        {
          DGNElemComplexHeader *psShape;

          psShape = 
            (DGNElemComplexHeader *) CPLCalloc(sizeof(DGNElemComplexHeader),1);
          psElement = (DGNElemCore *) psShape;
          psElement->stype = DGNST_COMPLEX_HEADER;
          DGNParseCore( psDGN, psElement );

          // Read complex header
          psShape->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
          psShape->numelems = psDGN->abyElem[38] + psDGN->abyElem[39] * 256;
          psShape->surftype = psDGN->abyElem[40];
          psShape->boundelms = psDGN->abyElem[41] + 1;
        }
        break;
      case DGNT_BSPLINE_SURFACE_HEADER:
        {
          DGNElemBSplineSurfaceHeader *psSpline;

          psSpline = (DGNElemBSplineSurfaceHeader *)
            CPLCalloc(sizeof(DGNElemBSplineSurfaceHeader), 1);
          psElement = (DGNElemCore *) psSpline;
          psElement->stype = DGNST_BSPLINE_SURFACE_HEADER;
          DGNParseCore( psDGN, psElement );

          // Read B-Spline surface header
          psSpline->desc_words = DGN_INT32(psDGN->abyElem + 36);
          psSpline->curve_type = psDGN->abyElem[41];

          // U
          psSpline->u_order = (psDGN->abyElem[40] & 0x0f) + 2;
          psSpline->u_properties = psDGN->abyElem[40] & 0xf0;
          psSpline->num_poles_u = psDGN->abyElem[42] + psDGN->abyElem[43]*256;
          psSpline->num_knots_u = psDGN->abyElem[44] + psDGN->abyElem[45]*256;
          psSpline->rule_lines_u = psDGN->abyElem[46] + psDGN->abyElem[47]*256;

          // V
          psSpline->v_order = (psDGN->abyElem[48] & 0x0f) + 2;
          psSpline->v_properties = psDGN->abyElem[48] & 0xf0;
          psSpline->num_poles_v = psDGN->abyElem[50] + psDGN->abyElem[51]*256;
          psSpline->num_knots_v = psDGN->abyElem[52] + psDGN->abyElem[53]*256;
          psSpline->rule_lines_v = psDGN->abyElem[54] + psDGN->abyElem[55]*256;

          psSpline->num_bounds = psDGN->abyElem[56] + psDGN->abyElem[57]*556;
        }
      break;
      case DGNT_BSPLINE_CURVE_HEADER:
        {
          DGNElemBSplineCurveHeader *psSpline;

          psSpline = (DGNElemBSplineCurveHeader *)
            CPLCalloc(sizeof(DGNElemBSplineCurveHeader), 1);
          psElement = (DGNElemCore *) psSpline;
          psElement->stype = DGNST_BSPLINE_CURVE_HEADER;
          DGNParseCore( psDGN, psElement );

          // Read B-Spline curve header
          psSpline->desc_words = DGN_INT32(psDGN->abyElem + 36);

          // flags
          psSpline->order = (psDGN->abyElem[40] & 0x0f) + 2;
          psSpline->properties = psDGN->abyElem[40] & 0xf0;
          psSpline->curve_type = psDGN->abyElem[41];

          psSpline->num_poles = psDGN->abyElem[42] + psDGN->abyElem[43]*256;
          psSpline->num_knots = psDGN->abyElem[44] + psDGN->abyElem[45]*256;
        }
      break;
      case DGNT_BSPLINE_SURFACE_BOUNDARY:
        {
          DGNElemBSplineSurfaceBoundary *psBounds;
          int numverts = psDGN->abyElem[38] + psDGN->abyElem[39]*256;

          psBounds = (DGNElemBSplineSurfaceBoundary *)
            CPLCalloc(sizeof(DGNElemBSplineSurfaceBoundary)+
                      (numverts-1)*sizeof(DGNPoint), 1);
          psElement = (DGNElemCore *) psBounds;
          psElement->stype = DGNST_BSPLINE_SURFACE_BOUNDARY;
          DGNParseCore( psDGN, psElement );

          // Read B-Spline surface boundary
          psBounds->number = psDGN->abyElem[36] + psDGN->abyElem[37]*256;
          psBounds->numverts = numverts;

          for (int i=0;i<psBounds->numverts;i++) {
            psBounds->vertices[i].x = DGN_INT32( psDGN->abyElem + 40 + i*8 );
            psBounds->vertices[i].y = DGN_INT32( psDGN->abyElem + 44 + i*8 );
            psBounds->vertices[i].z = 0;
          }
        }
      break;
      case DGNT_BSPLINE_KNOT:
      case DGNT_BSPLINE_WEIGHT_FACTOR:
        {
          DGNElemKnotWeight *psArray;
          // FIXME: Is it OK to assume that the # of elements corresponds
          // directly to the element size? kintel 20051215.
          int attr_bytes = psDGN->nElemBytes - 
            (psDGN->abyElem[30] + psDGN->abyElem[31]*256)*2 - 32;
          int numelems = (psDGN->nElemBytes - 36 - attr_bytes)/4;

          psArray = (DGNElemKnotWeight *)
            CPLCalloc(sizeof(DGNElemKnotWeight) + (numelems-1)*sizeof(float), 1);

          psElement = (DGNElemCore *) psArray;
          psElement->stype = DGNST_KNOT_WEIGHT;
          DGNParseCore( psDGN, psElement );

          // Read array
          for (int i=0;i<numelems;i++) {
            psArray->array[i] = 
              1.0f * DGN_INT32(psDGN->abyElem + 36 + i*4) / ((1L << 31) - 1);
          }
        }
      break;
      case DGNT_SHARED_CELL_DEFN:
      {
          DGNElemSharedCellDefn *psShared;

          psShared = (DGNElemSharedCellDefn *) 
              CPLCalloc(sizeof(DGNElemSharedCellDefn),1);
          psElement = (DGNElemCore *) psShared;
          psElement->stype = DGNST_SHARED_CELL_DEFN;
          DGNParseCore( psDGN, psElement );

          psShared->totlength = psDGN->abyElem[36] + psDGN->abyElem[37] * 256;
      }
      break;
      default:
      {
          psElement = (DGNElemCore *) CPLCalloc(sizeof(DGNElemCore),1);
          psElement->stype = DGNST_CORE;
          DGNParseCore( psDGN, psElement );
      }
      break;
    }

/* -------------------------------------------------------------------- */
/*      If the element structure type is "core" or if we are running    */
/*      in "capture all" mode, record the complete binary image of      */
/*      the element.                                                    */
/* -------------------------------------------------------------------- */
    if( psElement->stype == DGNST_CORE 
        || (psDGN->options & DGNO_CAPTURE_RAW_DATA) )
    {
        psElement->raw_bytes = psDGN->nElemBytes;
        psElement->raw_data = (unsigned char *)CPLMalloc(psElement->raw_bytes);

        memcpy( psElement->raw_data, psDGN->abyElem, psElement->raw_bytes );
    }

/* -------------------------------------------------------------------- */
/*      Collect some additional generic information.                    */
/* -------------------------------------------------------------------- */
    psElement->element_id = psDGN->next_element_id - 1;

    psElement->offset = VSIFTell( psDGN->fp ) - psDGN->nElemBytes;
    psElement->size = psDGN->nElemBytes;

    return psElement;
}

/************************************************************************/
/*                           DGNReadElement()                           */
/************************************************************************/

/**
 * Read a DGN element.
 *
 * This function will return the next element in the file, starting with the
 * first.  It is affected by DGNGotoElement() calls. 
 *
 * The element is read into a structure which includes the DGNElemCore 
 * structure.  It is expected that applications will inspect the stype
 * field of the returned DGNElemCore and use it to cast the pointer to the
 * appropriate element structure type such as DGNElemMultiPoint. 
 *
 * @param hDGN the handle of the file to read from.
 *
 * @return pointer to element structure, or NULL on EOF or processing error.
 * The structure should be freed with DGNFreeElement() when no longer needed.
 */

DGNElemCore *DGNReadElement( DGNHandle hDGN )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;
    DGNElemCore *psElement = NULL;
    int         nType, nLevel;
    int         bInsideFilter;

/* -------------------------------------------------------------------- */
/*      Load the element data into the current buffer.  If a spatial    */
/*      filter is in effect, loop until we get something within our     */
/*      spatial constraints.                                            */
/* -------------------------------------------------------------------- */
    do { 
        bInsideFilter = TRUE;

        if( !DGNLoadRawElement( psDGN, &nType, &nLevel ) )
            return NULL;
        
        if( psDGN->has_spatial_filter )
        {
            GUInt32     nXMin, nXMax, nYMin, nYMax;
            
            if( !psDGN->sf_converted_to_uor )
                DGNSpatialFilterToUOR( psDGN );

            if( !DGNGetRawExtents( psDGN, nType, NULL,
                                   &nXMin, &nYMin, NULL,
                                   &nXMax, &nYMax, NULL ) )
            {
                /* If we don't have spatial characterists for the element
                   we will pass it through. */
                bInsideFilter = TRUE;
            }
            else if( nXMin > psDGN->sf_max_x
                     || nYMin > psDGN->sf_max_y
                     || nXMax < psDGN->sf_min_x
                     || nYMax < psDGN->sf_min_y )
                bInsideFilter = FALSE;

            /*
            ** We want to select complex elements based on the extents of
            ** the header, not the individual elements.
            */
            if( nType == DGNT_COMPLEX_CHAIN_HEADER
                || nType == DGNT_COMPLEX_SHAPE_HEADER )
            {
                psDGN->in_complex_group = TRUE;
                psDGN->select_complex_group = bInsideFilter;
            }
            else if( psDGN->abyElem[0] & 0x80 /* complex flag set */ )
            {
                if( psDGN->in_complex_group )
                    bInsideFilter = psDGN->select_complex_group;
            }
            else
                psDGN->in_complex_group = FALSE;
        }
    } while( !bInsideFilter );

/* -------------------------------------------------------------------- */
/*      Convert into an element structure.                              */
/* -------------------------------------------------------------------- */
    psElement = DGNProcessElement( psDGN, nType, nLevel );

    return psElement;
}

/************************************************************************/
/*                       DGNElemTypeHasDispHdr()                        */
/************************************************************************/

/**
 * Does element type have display header.
 *
 * @param nElemType element type (0-63) to test. 
 *
 * @return TRUE if elements of passed in type have a display header after the
 * core element header, or FALSE otherwise. 
 */

int DGNElemTypeHasDispHdr( int nElemType )

{
    switch( nElemType )
    {
      case 0:
      case DGNT_TCB:
      case DGNT_CELL_LIBRARY:
      case DGNT_LEVEL_SYMBOLOGY:
      case 32:
      case 44:
      case 48:
      case 49:
      case 50:
      case 51:
      case 57:
      case 60:
      case 61:
      case 62:
      case 63:
        return FALSE;
        
      default:
        return TRUE;
    }
}


/************************************************************************/
/*                            DGNParseCore()                            */
/************************************************************************/

int DGNParseCore( DGNInfo *psDGN, DGNElemCore *psElement )

{
    GByte       *psData = psDGN->abyElem+0;

    psElement->level = psData[0] & 0x3f;
    psElement->complex = psData[0] & 0x80;
    psElement->deleted = psData[1] & 0x80;
    psElement->type = psData[1] & 0x7f;

    if( psDGN->nElemBytes >= 36 && DGNElemTypeHasDispHdr( psElement->type ) )
    {
        psElement->graphic_group = psData[28] + psData[29] * 256;
        psElement->properties = psData[32] + psData[33] * 256;
        psElement->style = psData[34] & 0x7;
        psElement->weight = (psData[34] & 0xf8) >> 3;
        psElement->color = psData[35];
    }
    else
    {
        psElement->graphic_group = 0;
        psElement->properties = 0;
        psElement->style = 0;
        psElement->weight = 0;
        psElement->color = 0;
    }

    if( psElement->properties & DGNPF_ATTRIBUTES )
    {
        int   nAttIndex;
        
        nAttIndex = psData[30] + psData[31] * 256;

        psElement->attr_bytes = psDGN->nElemBytes - nAttIndex*2 - 32;
        if( psElement->attr_bytes > 0 )
        {
            psElement->attr_data = (unsigned char *) 
                CPLMalloc(psElement->attr_bytes);
            memcpy( psElement->attr_data, psData + nAttIndex * 2 + 32,
                    psElement->attr_bytes );
        }
        else
        {
            CPLError(
                CE_Warning, CPLE_AppDefined, 
                "Computed %d bytes for attribute info on element,\n"
                "perhaps this element type doesn't really have a disphdr?",
                psElement->attr_bytes );
            psElement->attr_bytes = 0;
        }
    }
    
    return TRUE;
}

/************************************************************************/
/*                         DGNParseColorTable()                         */
/************************************************************************/

static DGNElemCore *DGNParseColorTable( DGNInfo * psDGN )

{
    DGNElemCore *psElement;
    DGNElemColorTable  *psColorTable;
            
    psColorTable = (DGNElemColorTable *) 
        CPLCalloc(sizeof(DGNElemColorTable),1);
    psElement = (DGNElemCore *) psColorTable;
    psElement->stype = DGNST_COLORTABLE;

    DGNParseCore( psDGN, psElement );

    psColorTable->screen_flag = 
        psDGN->abyElem[36] + psDGN->abyElem[37] * 256;

    memcpy( psColorTable->color_info[255], psDGN->abyElem+38, 3 );
    memcpy( psColorTable->color_info, psDGN->abyElem+41, 765 ); 

    // We used to only install a color table as the default color
    // table if it was the first in the file.  But apparently we should
    // really be using the last one.  This doesn't necessarily accomplish
    // that either if the elements are being read out of order but it will
    // usually do better at least. 
    memcpy( psDGN->color_table, psColorTable->color_info, 768 );
    psDGN->got_color_table = 1;
    
    return psElement;
}

/************************************************************************/
/*                           DGNParseTagSet()                           */
/************************************************************************/

static DGNElemCore *DGNParseTagSet( DGNInfo * psDGN )

{
    DGNElemCore *psElement;
    DGNElemTagSet *psTagSet;
    int          nDataOffset, iTag;
            
    psTagSet = (DGNElemTagSet *) CPLCalloc(sizeof(DGNElemTagSet),1);
    psElement = (DGNElemCore *) psTagSet;
    psElement->stype = DGNST_TAG_SET;

    DGNParseCore( psDGN, psElement );

/* -------------------------------------------------------------------- */
/*      Parse the overall information.                                  */
/* -------------------------------------------------------------------- */
    psTagSet->tagCount = 
        psDGN->abyElem[44] + psDGN->abyElem[45] * 256;
    psTagSet->flags = 
        psDGN->abyElem[46] + psDGN->abyElem[47] * 256;
    psTagSet->tagSetName = CPLStrdup( (const char *) (psDGN->abyElem + 48) );

/* -------------------------------------------------------------------- */
/*      Get the tag set number out of the attributes, if available.     */
/* -------------------------------------------------------------------- */
    psTagSet->tagSet = -1;

    if( psElement->attr_bytes >= 8 
        && psElement->attr_data[0] == 0x03
        && psElement->attr_data[1] == 0x10
        && psElement->attr_data[2] == 0x2f
        && psElement->attr_data[3] == 0x7d )
        psTagSet->tagSet = psElement->attr_data[4]
            + psElement->attr_data[5] * 256;

/* -------------------------------------------------------------------- */
/*      Parse each of the tag definitions.                              */
/* -------------------------------------------------------------------- */
    psTagSet->tagList = (DGNTagDef *) 
        CPLMalloc(sizeof(DGNTagDef) * psTagSet->tagCount);

    nDataOffset = 48 + strlen(psTagSet->tagSetName) + 1 + 1; 

    for( iTag = 0; iTag < psTagSet->tagCount; iTag++ )
    {
        DGNTagDef *tagDef = psTagSet->tagList + iTag;

        CPLAssert( nDataOffset < psDGN->nElemBytes );

        /* collect tag name. */
        tagDef->name = CPLStrdup( (char *) psDGN->abyElem + nDataOffset );
        nDataOffset += strlen(tagDef->name)+1;

        /* Get tag id */
        tagDef->id = psDGN->abyElem[nDataOffset]
            + psDGN->abyElem[nDataOffset+1] * 256;
        nDataOffset += 2;

        /* Get User Prompt */
        tagDef->prompt = CPLStrdup( (char *) psDGN->abyElem + nDataOffset );
        nDataOffset += strlen(tagDef->prompt)+1;


        /* Get type */
        tagDef->type = psDGN->abyElem[nDataOffset]
            + psDGN->abyElem[nDataOffset+1] * 256;
        nDataOffset += 2;

        /* skip five zeros */
        nDataOffset += 5;

        /* Get the default */
        if( tagDef->type == 1 )
        {
            tagDef->defaultValue.string = 
                CPLStrdup( (char *) psDGN->abyElem + nDataOffset );
            nDataOffset += strlen(tagDef->defaultValue.string)+1;
        }
        else if( tagDef->type == 3 || tagDef->type == 5 )
        {
            memcpy( &(tagDef->defaultValue.integer), 
                    psDGN->abyElem + nDataOffset, 4 );
            tagDef->defaultValue.integer = 
                CPL_LSBWORD32( tagDef->defaultValue.integer );
            nDataOffset += 4;
        }
        else if( tagDef->type == 4 )
        {
            memcpy( &(tagDef->defaultValue.real), 
                    psDGN->abyElem + nDataOffset, 8 );
            DGN2IEEEDouble( &(tagDef->defaultValue.real) );
            nDataOffset += 8;
        }
        else
            nDataOffset += 4;
    }
    return psElement;
}

/************************************************************************/
/*                            DGNParseTCB()                             */
/************************************************************************/

static DGNElemCore *DGNParseTCB( DGNInfo * psDGN )

{
    DGNElemTCB *psTCB;
    DGNElemCore *psElement;
    int iView;

    psTCB = (DGNElemTCB *) CPLCalloc(sizeof(DGNElemTCB),1);
    psElement = (DGNElemCore *) psTCB;
    psElement->stype = DGNST_TCB;
    DGNParseCore( psDGN, psElement );

    if( psDGN->abyElem[1214] & 0x40 )
        psTCB->dimension = 3;
    else
        psTCB->dimension = 2;
          
    psTCB->subunits_per_master = DGN_INT32( psDGN->abyElem + 1112 );

    psTCB->master_units[0] = (char) psDGN->abyElem[1120];
    psTCB->master_units[1] = (char) psDGN->abyElem[1121];
    psTCB->master_units[2] = '\0';

    psTCB->uor_per_subunit = DGN_INT32( psDGN->abyElem + 1116 );

    psTCB->sub_units[0] = (char) psDGN->abyElem[1122];
    psTCB->sub_units[1] = (char) psDGN->abyElem[1123];
    psTCB->sub_units[2] = '\0';

    /* Get global origin */
    memcpy( &(psTCB->origin_x), psDGN->abyElem+1240, 8 );
    memcpy( &(psTCB->origin_y), psDGN->abyElem+1248, 8 );
    memcpy( &(psTCB->origin_z), psDGN->abyElem+1256, 8 );

    /* Transform to IEEE */
    DGN2IEEEDouble( &(psTCB->origin_x) );
    DGN2IEEEDouble( &(psTCB->origin_y) );
    DGN2IEEEDouble( &(psTCB->origin_z) );

    /* Convert from UORs to master units. */
    if( psTCB->uor_per_subunit != 0
        && psTCB->subunits_per_master != 0 )
    {
        psTCB->origin_x = psTCB->origin_x / 
            (psTCB->uor_per_subunit * psTCB->subunits_per_master);
        psTCB->origin_y = psTCB->origin_y / 
            (psTCB->uor_per_subunit * psTCB->subunits_per_master);
        psTCB->origin_z = psTCB->origin_z / 
            (psTCB->uor_per_subunit * psTCB->subunits_per_master);
    }

    if( !psDGN->got_tcb )
    {
        psDGN->got_tcb = TRUE;
        psDGN->dimension = psTCB->dimension;
        psDGN->origin_x = psTCB->origin_x;
        psDGN->origin_y = psTCB->origin_y;
        psDGN->origin_z = psTCB->origin_z;

        if( psTCB->uor_per_subunit != 0
            && psTCB->subunits_per_master != 0 )
            psDGN->scale = 1.0 
                / (psTCB->uor_per_subunit * psTCB->subunits_per_master);
    }

    /* Collect views */
    for( iView = 0; iView < 8; iView++ )
    {
        unsigned char *pabyRawView = psDGN->abyElem + 46 + iView*118;
        DGNViewInfo *psView = psTCB->views + iView;
        int i;

        psView->flags = pabyRawView[0] + pabyRawView[1] * 256;
        memcpy( psView->levels, pabyRawView + 2, 8 );
        
        psView->origin.x = DGN_INT32( pabyRawView + 10 );
        psView->origin.y = DGN_INT32( pabyRawView + 14 );
        psView->origin.z = DGN_INT32( pabyRawView + 18 );

        DGNTransformPoint( psDGN, &(psView->origin) );

        psView->delta.x = DGN_INT32( pabyRawView + 22 );
        psView->delta.y = DGN_INT32( pabyRawView + 26 );
        psView->delta.z = DGN_INT32( pabyRawView + 30 );

        psView->delta.x *= psDGN->scale;
        psView->delta.y *= psDGN->scale;
        psView->delta.z *= psDGN->scale;

        memcpy( psView->transmatrx, pabyRawView + 34, sizeof(double) * 9 );
        for( i = 0; i < 9; i++ )
            DGN2IEEEDouble( psView->transmatrx + i );

        memcpy( &(psView->conversion), pabyRawView + 106, sizeof(double) );
        DGN2IEEEDouble( &(psView->conversion) );

        psView->activez = DGN_INT32( pabyRawView + 114 );
    }

    return psElement;
}

/************************************************************************/
/*                           DGNFreeElement()                           */
/************************************************************************/

/**
 * Free an element structure.
 *
 * This function will deallocate all resources associated with any element
 * structure returned by DGNReadElement(). 
 *
 * @param hDGN handle to file from which the element was read.
 * @param psElement the element structure returned by DGNReadElement().
 */

void DGNFreeElement( DGNHandle hDGN, DGNElemCore *psElement )

{
    if( psElement->attr_data != NULL )
        VSIFree( psElement->attr_data );

    if( psElement->raw_data != NULL )
        VSIFree( psElement->raw_data );

    if( psElement->stype == DGNST_TAG_SET )
    {
        int             iTag;

        DGNElemTagSet *psTagSet = (DGNElemTagSet *) psElement;
        CPLFree( psTagSet->tagSetName );

        for( iTag = 0; iTag < psTagSet->tagCount; iTag++ )
        {
            CPLFree( psTagSet->tagList[iTag].name );
            CPLFree( psTagSet->tagList[iTag].prompt );

            if( psTagSet->tagList[iTag].type == 1 )
                CPLFree( psTagSet->tagList[iTag].defaultValue.string );
        }
        CPLFree( psTagSet->tagList );
    }
    else if( psElement->stype == DGNST_TAG_VALUE )
    {
        if( ((DGNElemTagValue *) psElement)->tagType == 1 )
            CPLFree( ((DGNElemTagValue *) psElement)->tagValue.string );
    }

    CPLFree( psElement );
}

/************************************************************************/
/*                             DGNRewind()                              */
/************************************************************************/

/**
 * Rewind element reading.
 *
 * Rewind the indicated DGN file, so the next element read with 
 * DGNReadElement() will be the first.  Does not require indexing like
 * the more general DGNReadElement() function.
 *
 * @param hDGN handle to file.
 */

void DGNRewind( DGNHandle hDGN )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;

    VSIRewind( psDGN->fp );

    psDGN->next_element_id = 0;
    psDGN->in_complex_group = FALSE;
}

/************************************************************************/
/*                         DGNTransformPoint()                          */
/************************************************************************/

void DGNTransformPoint( DGNInfo *psDGN, DGNPoint *psPoint )

{
    psPoint->x = psPoint->x * psDGN->scale - psDGN->origin_x;
    psPoint->y = psPoint->y * psDGN->scale - psDGN->origin_y;
    psPoint->z = psPoint->z * psDGN->scale - psDGN->origin_z;
}

/************************************************************************/
/*                      DGNInverseTransformPoint()                      */
/************************************************************************/

void DGNInverseTransformPoint( DGNInfo *psDGN, DGNPoint *psPoint )

{
    psPoint->x = (psPoint->x + psDGN->origin_x) / psDGN->scale;
    psPoint->y = (psPoint->y + psDGN->origin_y) / psDGN->scale;
    psPoint->z = (psPoint->z + psDGN->origin_z) / psDGN->scale;

    psPoint->x = MAX(-2147483647,MIN(2147483647,psPoint->x));
    psPoint->y = MAX(-2147483647,MIN(2147483647,psPoint->y));
    psPoint->z = MAX(-2147483647,MIN(2147483647,psPoint->z));
}

/************************************************************************/
/*                   DGNInverseTransformPointToInt()                    */
/************************************************************************/

void DGNInverseTransformPointToInt( DGNInfo *psDGN, DGNPoint *psPoint,
                                    unsigned char *pabyTarget )

{
    double     adfCT[3];
    int        i;

    adfCT[0] = (psPoint->x + psDGN->origin_x) / psDGN->scale;
    adfCT[1] = (psPoint->y + psDGN->origin_y) / psDGN->scale;
    adfCT[2] = (psPoint->z + psDGN->origin_z) / psDGN->scale;

    for( i = 0; i < psDGN->dimension; i++ )
    {
        GInt32 nCTI;
        unsigned char *pabyCTI = (unsigned char *) &nCTI;

        nCTI = (GInt32) MAX(-2147483647,MIN(2147483647,adfCT[i]));
        
#ifdef WORDS_BIGENDIAN 
        pabyTarget[i*4+0] = pabyCTI[1];
        pabyTarget[i*4+1] = pabyCTI[0];
        pabyTarget[i*4+2] = pabyCTI[3];
        pabyTarget[i*4+3] = pabyCTI[2];
#else
        pabyTarget[i*4+3] = pabyCTI[1];
        pabyTarget[i*4+2] = pabyCTI[0];
        pabyTarget[i*4+1] = pabyCTI[3];
        pabyTarget[i*4+0] = pabyCTI[2];
#endif        
    }
}

/************************************************************************/
/*                             DGNLoadTCB()                             */
/************************************************************************/

/**
 * Load TCB if not already loaded. 
 *
 * This function will load the TCB element if it is not already loaded.
 * It is used primarily to ensure the TCB is loaded before doing any operations
 * that require TCB values (like creating new elements). 
 *
 * @return FALSE on failure or TRUE on success.
 */

int DGNLoadTCB( DGNHandle hDGN )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;

    if( psDGN->got_tcb )
        return TRUE;

    while( !psDGN->got_tcb )
    {
        DGNElemCore *psElem = DGNReadElement( hDGN );
        if( psElem == NULL )
        {
            CPLError( CE_Failure, CPLE_AppDefined, 
                      "DGNLoadTCB() - unable to find TCB in file." );
            return FALSE;
        }
        DGNFreeElement( hDGN, psElem );
    }

    return TRUE;
}

/************************************************************************/
/*                         DGNGetElementIndex()                         */
/************************************************************************/

/**
 * Fetch element index.
 *
 * This function will return an array with brief information about every
 * element in a DGN file.  It requires one pass through the entire file to
 * generate (this is not repeated on subsequent calls). 
 *
 * The returned array of DGNElementInfo structures contain the level, type, 
 * stype, and other flags for each element in the file.  This can facilitate
 * application level code representing the number of elements of various types
 * effeciently. 
 *
 * Note that while building the index requires one pass through the whole file,
 * it does not generally request much processing for each element. 
 *
 * @param hDGN the file to get an index for.
 * @param pnElementCount the integer to put the total element count into. 
 *
 * @return a pointer to an internal array of DGNElementInfo structures (there 
 * will be *pnElementCount entries in the array), or NULL on failure.  The
 * returned array should not be modified or freed, and will last only as long
 * as the DGN file remains open. 
 */

const DGNElementInfo *DGNGetElementIndex( DGNHandle hDGN, int *pnElementCount )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;

    DGNBuildIndex( psDGN );

    if( pnElementCount != NULL )
        *pnElementCount = psDGN->element_count;
    
    return psDGN->element_index;
}

/************************************************************************/
/*                           DGNGetExtents()                            */
/************************************************************************/

/**
 * Fetch overall file extents.
 *
 * The extents are collected for each element while building an index, so
 * if an index has not already been built, it will be built when 
 * DGNGetExtents() is called.  
 * 
 * The Z min/max values are generally meaningless (0 and 0xffffffff in uor
 * space). 
 * 
 * @param hDGN the file to get extents for.
 * @param padfExtents pointer to an array of six doubles into which are loaded
 * the values xmin, ymin, zmin, xmax, ymax, and zmax.
 *
 * @return TRUE on success or FALSE on failure.
 */

int DGNGetExtents( DGNHandle hDGN, double * padfExtents )

{
    DGNInfo     *psDGN = (DGNInfo *) hDGN;
    DGNPoint    sMin, sMax;

    DGNBuildIndex( psDGN );

    if( !psDGN->got_bounds )
        return FALSE;

    sMin.x = psDGN->min_x - 2147483648.0;
    sMin.y = psDGN->min_y - 2147483648.0;
    sMin.z = psDGN->min_z - 2147483648.0;
    
    DGNTransformPoint( psDGN, &sMin );

    padfExtents[0] = sMin.x;
    padfExtents[1] = sMin.y;
    padfExtents[2] = sMin.z;
    
    sMax.x = psDGN->max_x - 2147483648.0;
    sMax.y = psDGN->max_y - 2147483648.0;
    sMax.z = psDGN->max_z - 2147483648.0;

    DGNTransformPoint( psDGN, &sMax );

    padfExtents[3] = sMax.x;
    padfExtents[4] = sMax.y;
    padfExtents[5] = sMax.z;

    return TRUE;
}

/************************************************************************/
/*                           DGNBuildIndex()                            */
/************************************************************************/

void DGNBuildIndex( DGNInfo *psDGN )

{
    int nMaxElements, nType, nLevel;
    long nLastOffset;
    GUInt32 anRegion[6];

    if( psDGN->index_built ) 
        return;

    psDGN->index_built = TRUE;
    
    DGNRewind( psDGN );

    nMaxElements = 0;

    nLastOffset = VSIFTell( psDGN->fp );
    while( DGNLoadRawElement( psDGN, &nType, &nLevel ) )
    {
        DGNElementInfo  *psEI;

        if( psDGN->element_count == nMaxElements )
        {
            nMaxElements = (int) (nMaxElements * 1.5) + 500;
            
            psDGN->element_index = (DGNElementInfo *) 
                CPLRealloc( psDGN->element_index, 
                            nMaxElements * sizeof(DGNElementInfo) );
        }

        psEI = psDGN->element_index + psDGN->element_count;
        psEI->level = (unsigned char) nLevel;
        psEI->type = (unsigned char) nType;
        psEI->flags = 0;
        psEI->offset = (long) nLastOffset;

        if( psDGN->abyElem[0] & 0x80 )
            psEI->flags |= DGNEIF_COMPLEX;

        if( psDGN->abyElem[1] & 0x80 )
            psEI->flags |= DGNEIF_DELETED;

        if( nType == DGNT_LINE || nType == DGNT_LINE_STRING
            || nType == DGNT_SHAPE || nType == DGNT_CURVE
            || nType == DGNT_BSPLINE_POLE )
            psEI->stype = DGNST_MULTIPOINT;

        else if( nType == DGNT_GROUP_DATA && nLevel == DGN_GDL_COLOR_TABLE )
        {
            DGNElemCore *psCT = DGNParseColorTable( psDGN );
            DGNFreeElement( (DGNHandle) psDGN, psCT );
            psEI->stype = DGNST_COLORTABLE;
        }
        else if( nType == DGNT_ELLIPSE || nType == DGNT_ARC )
            psEI->stype = DGNST_ARC;
        
        else if( nType == DGNT_COMPLEX_SHAPE_HEADER 
                 || nType == DGNT_COMPLEX_CHAIN_HEADER
                 || nType == DGNT_3DSURFACE_HEADER
                 || nType == DGNT_3DSOLID_HEADER)
            psEI->stype = DGNST_COMPLEX_HEADER;
        
        else if( nType == DGNT_TEXT )
            psEI->stype = DGNST_TEXT;

        else if( nType == DGNT_TAG_VALUE )
            psEI->stype = DGNST_TAG_VALUE;

        else if( nType == DGNT_APPLICATION_ELEM )
        {
            if( nLevel == 24 )
                psEI->stype = DGNST_TAG_SET;
            else
                psEI->stype = DGNST_CORE;
        }
        else if( nType == DGNT_TCB )
        {
            DGNElemCore *psTCB = DGNParseTCB( psDGN );
            DGNFreeElement( (DGNHandle) psDGN, psTCB );
            psEI->stype = DGNST_TCB;
        }
        else if( nType == DGNT_CONE )
            psEI->stype = DGNST_CONE;
        else
            psEI->stype = DGNST_CORE;

        if( !(psEI->flags & DGNEIF_DELETED)
            && !(psEI->flags & DGNEIF_COMPLEX) 
            && DGNGetRawExtents( psDGN, nType, NULL,
                                 anRegion+0, anRegion+1, anRegion+2,
                                 anRegion+3, anRegion+4, anRegion+5 ) )
        {
#ifdef notdef
            printf( "panRegion[%d]=%.1f,%.1f,%.1f,%.1f,%.1f,%.1f\n", 
                    psDGN->element_count,
                    anRegion[0] - 2147483648.0,
                    anRegion[1] - 2147483648.0,
                    anRegion[2] - 2147483648.0,
                    anRegion[3] - 2147483648.0,
                    anRegion[4] - 2147483648.0,
                    anRegion[5] - 2147483648.0 );
#endif            
            if( psDGN->got_bounds )
            {
                psDGN->min_x = MIN(psDGN->min_x, anRegion[0]);
                psDGN->min_y = MIN(psDGN->min_y, anRegion[1]);
                psDGN->min_z = MIN(psDGN->min_z, anRegion[2]);
                psDGN->max_x = MAX(psDGN->max_x, anRegion[3]);
                psDGN->max_y = MAX(psDGN->max_y, anRegion[4]);
                psDGN->max_z = MAX(psDGN->max_z, anRegion[5]);
            }
            else
            {
                memcpy( &(psDGN->min_x), anRegion, sizeof(GInt32) * 6 );
                psDGN->got_bounds = TRUE;
            }
        }

        psDGN->element_count++;

        nLastOffset = VSIFTell( psDGN->fp );
    }

    DGNRewind( psDGN );

    psDGN->max_element_count = nMaxElements;
}

