/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements OGRGeoconceptLayer class.
 * Author:   Didier Richard, didier.richard@ign.fr
 * Language: C++
 *
 ******************************************************************************
 * Copyright (c) 2007,  Geoconcept and IGN
 * Copyright (c) 2008, Even Rouault <even dot rouault at spatialys.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 "cpl_conv.h"
#include "cpl_string.h"
#include "ogrgeoconceptlayer.h"


/************************************************************************/
/*                         OGRGeoconceptLayer()                         */
/************************************************************************/

OGRGeoconceptLayer::OGRGeoconceptLayer() :
    _poFeatureDefn(nullptr),
    _gcFeature(nullptr)
{}

/************************************************************************/
/*                          ~OGRGeoconceptLayer()                      */
/************************************************************************/

OGRGeoconceptLayer::~OGRGeoconceptLayer()

{
  if( _poFeatureDefn )
  {
    CPLDebug( "GEOCONCEPT",
              "%ld features on layer %s.",
              GetSubTypeNbFeatures_GCIO(_gcFeature),
              _poFeatureDefn->GetName() );

    _poFeatureDefn->Release();
  }

  _gcFeature= nullptr; /* deleted when OGCGeoconceptDatasource destroyed */
}

/************************************************************************/
/*                              Open()                                  */
/************************************************************************/

OGRErr OGRGeoconceptLayer::Open( GCSubType* Subclass )

{
    _gcFeature= Subclass;
    if( GetSubTypeFeatureDefn_GCIO(_gcFeature) )
    {
      _poFeatureDefn = reinterpret_cast<OGRFeatureDefn *>(
          GetSubTypeFeatureDefn_GCIO(_gcFeature) );
      SetDescription( _poFeatureDefn->GetName() );
      _poFeatureDefn->Reference();
    }
    else
    {
      char pszln[512];
      snprintf(pszln, 511, "%s.%s", GetSubTypeName_GCIO(_gcFeature),
                                    GetTypeName_GCIO(GetSubTypeType_GCIO(_gcFeature)));
      pszln[511]='\0';

      _poFeatureDefn = new OGRFeatureDefn(pszln);
      SetDescription( _poFeatureDefn->GetName() );
      _poFeatureDefn->Reference();
      _poFeatureDefn->SetGeomType(wkbUnknown);

      const int n = CountSubTypeFields_GCIO(_gcFeature);
      if( n>0 )
      {
        OGRFieldType oft;
        for( int i= 0; i<n; i++ )
        {
          GCField* aField = GetSubTypeField_GCIO(_gcFeature,i);
          if( aField )
          {
            if( IsPrivateField_GCIO(aField) ) continue;
            switch(GetFieldKind_GCIO(aField)) {
            case vIntFld_GCIO      :
            case vPositionFld_GCIO :
              oft= OFTInteger;
              break;
            case vRealFld_GCIO     :
            case vLengthFld_GCIO   :
            case vAreaFld_GCIO     :
              oft= OFTReal;
              break;
            case vDateFld_GCIO     :
              oft= OFTDate;
              break;
            case vTimeFld_GCIO     :
              oft= OFTTime;
              break;
            case vMemoFld_GCIO     :
            case vChoiceFld_GCIO   :
            case vInterFld_GCIO    :
            default                :
              oft= OFTString;
              break;
            }
            OGRFieldDefn ofd(GetFieldName_GCIO(aField), oft);
            _poFeatureDefn->AddFieldDefn(&ofd);
          }
        }
      }
      SetSubTypeFeatureDefn_GCIO(_gcFeature, (OGRFeatureDefnH) _poFeatureDefn);
      _poFeatureDefn->Reference();
    }

    if( _poFeatureDefn->GetGeomFieldCount() > 0 )
        _poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(GetSpatialRef());

    return OGRERR_NONE;
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRGeoconceptLayer::ResetReading()

{
    Rewind_GCIO(GetSubTypeGCHandle_GCIO(_gcFeature),_gcFeature);
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRGeoconceptLayer::GetNextFeature()

{
    OGRFeature* poFeature = nullptr;

    for( ;; )
    {
      if( !(poFeature= (OGRFeature*)ReadNextFeature_GCIO(_gcFeature)) )
      {
        /*
         * As several features are embed in the Geoconcept file,
         * when reaching the end of the feature type, resetting
         * the reader would allow reading other features :
         * ogrinfo -ro export.gxt FT1 FT2 ...
         * will be all features for all features types !
         */
        Rewind_GCIO(GetSubTypeGCHandle_GCIO(_gcFeature),nullptr);
        break;
      }
      if( (m_poFilterGeom == nullptr || FilterGeometry( poFeature->GetGeometryRef() ) )
          &&
          (m_poAttrQuery == nullptr  || m_poAttrQuery->Evaluate( poFeature )) )
      {
        break;
      }
      delete poFeature;
    }

    CPLDebug( "GEOCONCEPT",
              "FID : " CPL_FRMT_GIB "\n"
              "%s  : %s",
              poFeature? poFeature->GetFID():-1L,
              poFeature && poFeature->GetFieldCount()>0? poFeature->GetFieldDefnRef(0)->GetNameRef():"-",
              poFeature && poFeature->GetFieldCount()>0? poFeature->GetFieldAsString(0):"");

    return poFeature;
}

/************************************************************************/
/*            OGRGeoconceptLayer_GetCompatibleFieldName()               */
/************************************************************************/

static char* OGRGeoconceptLayer_GetCompatibleFieldName(const char* pszName)
{
    char* pszCompatibleName = CPLStrdup(pszName);
    for( int i=0; pszCompatibleName[i] != 0; i++ )
    {
        if (pszCompatibleName[i] == ' ')
            pszCompatibleName[i] = '_';
    }
    return pszCompatibleName;
}

/************************************************************************/
/*                           ICreateFeature()                            */
/************************************************************************/

OGRErr OGRGeoconceptLayer::ICreateFeature( OGRFeature* poFeature )

{
    OGRGeometry* poGeom = poFeature->GetGeometryRef();

    if (poGeom == nullptr)
    {
        CPLError( CE_Warning, CPLE_NotSupported,
                  "NULL geometry not supported in Geoconcept, feature skipped.\n");
        return OGRERR_NONE;
    }

    OGRwkbGeometryType eGt = poGeom->getGeometryType();
    switch( wkbFlatten(eGt) ) {
    case wkbPoint                 :
    case wkbMultiPoint            :
      if( GetSubTypeKind_GCIO(_gcFeature)==vUnknownItemType_GCIO )
      {
        SetSubTypeKind_GCIO(_gcFeature,vPoint_GCIO);
      }
      else if( GetSubTypeKind_GCIO(_gcFeature)!=vPoint_GCIO )
      {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Can't write non ponctual feature in a ponctual Geoconcept layer %s.\n",
                  _poFeatureDefn->GetName());
        return OGRERR_FAILURE;
      }
      break;
    case wkbLineString            :
    case wkbMultiLineString       :
      if( GetSubTypeKind_GCIO(_gcFeature)==vUnknownItemType_GCIO )
      {
        SetSubTypeKind_GCIO(_gcFeature,vLine_GCIO);
      }
      else if( GetSubTypeKind_GCIO(_gcFeature)!=vLine_GCIO )
      {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Can't write non linear feature in a linear Geoconcept "
                  "layer %s.\n",
                  _poFeatureDefn->GetName());
        return OGRERR_FAILURE;
      }
      break;
    case wkbPolygon               :
    case wkbMultiPolygon          :
      if( GetSubTypeKind_GCIO(_gcFeature)==vUnknownItemType_GCIO )
      {
        SetSubTypeKind_GCIO(_gcFeature,vPoly_GCIO);
      }
      else if( GetSubTypeKind_GCIO(_gcFeature)!=vPoly_GCIO )
      {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Can't write non polygonal feature in a polygonal "
                  "Geoconcept layer %s.\n",
                  _poFeatureDefn->GetName());
        return OGRERR_FAILURE;
      }
      break;
    default                       :
      CPLError( CE_Warning, CPLE_AppDefined,
                "Geometry type %s not supported in Geoconcept, "
                "feature skipped.\n",
                OGRGeometryTypeToName(eGt) );
      return OGRERR_NONE;
    }
    if( GetSubTypeDim_GCIO(_gcFeature)==vUnknown3D_GCIO )
    {
      if( poGeom->getCoordinateDimension()==3 )
      {
        SetSubTypeDim_GCIO(_gcFeature,v3D_GCIO);
      }
      else
      {
        SetSubTypeDim_GCIO(_gcFeature,v2D_GCIO);
      }
    }

    int nbGeom = 0;
    bool isSingle = false;

    switch( wkbFlatten(eGt) ) {
    case wkbPoint                 :
    case wkbLineString            :
    case wkbPolygon               :
      nbGeom = 1;
      isSingle = true;
      break;
    case wkbMultiPoint            :
    case wkbMultiLineString       :
    case wkbMultiPolygon          :
      nbGeom = poGeom->toGeometryCollection()->getNumGeometries();
      isSingle = false;
      break;
    default                       :
      nbGeom = 0;
      isSingle = false;
      break;
    }

    /* 1st feature, let's write header : */
    if( GetGCMode_GCIO(GetSubTypeGCHandle_GCIO(_gcFeature)) == vWriteAccess_GCIO &&
        GetFeatureCount(TRUE) == 0 )
      if( WriteHeader_GCIO(GetSubTypeGCHandle_GCIO(_gcFeature))==nullptr )
      {
        return OGRERR_FAILURE;
      }

    if( nbGeom>0 )
    {
      for( int iGeom = 0; iGeom<nbGeom; iGeom++ )
      {
        int nextField
            = StartWritingFeature_GCIO(
                _gcFeature,
                isSingle ? static_cast<int>(poFeature->GetFID()) : OGRNullFID );
        while( nextField != WRITECOMPLETED_GCIO )
        {
          if( nextField==WRITEERROR_GCIO )
          {
            return OGRERR_FAILURE;
          }
          if( nextField==GEOMETRYEXPECTED_GCIO )
          {
            OGRGeometry* poGeomPart=
                isSingle? poGeom: poGeom->toGeometryCollection()->getGeometryRef(iGeom);
            nextField= WriteFeatureGeometry_GCIO(_gcFeature,
                                                 (OGRGeometryH)poGeomPart);
          }
          else
          {
            GCField* theField= GetSubTypeField_GCIO(_gcFeature,nextField);
            /* for each field, find out its mapping ... */
            int nF = poFeature->GetFieldCount();
            if( nF > 0 )
            {
              int iF = 0;
              for( ; iF<nF; iF++ )
              {
                OGRFieldDefn *poField = poFeature->GetFieldDefnRef(iF);
                char* pszName = OGRGeoconceptLayer_GetCompatibleFieldName(
                    poField->GetNameRef());
                if( EQUAL(pszName, GetFieldName_GCIO(theField)) )
                {
                  CPLFree(pszName);
                  nextField = WriteFeatureFieldAsString_GCIO(
                      _gcFeature,
                      nextField,
                      poFeature->IsFieldSetAndNotNull(iF)?
                      poFeature->GetFieldAsString(iF) : nullptr);
                  break;
                }
                CPLFree(pszName);
              }
              if( iF == nF )
              {
                CPLError( CE_Failure, CPLE_AppDefined,
                          "Can't find a field attached to %s on "
                          "Geoconcept layer %s.\n",
                          GetFieldName_GCIO(theField),
                          _poFeatureDefn->GetName() );
                return OGRERR_FAILURE;
              }
            }
            else
            {
              nextField= WRITECOMPLETED_GCIO;
            }
          }
        }
        StopWritingFeature_GCIO(_gcFeature);
      }
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           GetSpatialRef()                            */
/************************************************************************/

OGRSpatialReference *OGRGeoconceptLayer::GetSpatialRef()

{
    GCExportFileH* hGXT= GetSubTypeGCHandle_GCIO(_gcFeature);
    if( !hGXT ) return nullptr;
    GCExportFileMetadata* Meta= GetGCMeta_GCIO(hGXT);
    if( !Meta ) return nullptr;
    return (OGRSpatialReference*)GetMetaSRS_GCIO(Meta);
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/*                                                                      */
/*      If a spatial filter is in effect, we turn control over to       */
/*      the generic counter.  Otherwise we return the total count.      */
/************************************************************************/

GIntBig OGRGeoconceptLayer::GetFeatureCount( int bForce )

{
    if( m_poFilterGeom != nullptr || m_poAttrQuery != nullptr )
        return OGRLayer::GetFeatureCount( bForce );

    return GetSubTypeNbFeatures_GCIO(_gcFeature);
}

/************************************************************************/
/*                             GetExtent()                              */
/************************************************************************/

OGRErr OGRGeoconceptLayer::GetExtent( OGREnvelope* psExtent,
                                      CPL_UNUSED int bForce )
{
    GCExtent* theExtent = GetSubTypeExtent_GCIO( _gcFeature );
    if( !theExtent )
        return OGRERR_FAILURE;
    psExtent->MinX= GetExtentULAbscissa_GCIO(theExtent);
    psExtent->MinY= GetExtentLROrdinate_GCIO(theExtent);
    psExtent->MaxX= GetExtentLRAbscissa_GCIO(theExtent);
    psExtent->MaxY= GetExtentULOrdinate_GCIO(theExtent);

    return OGRERR_NONE;
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRGeoconceptLayer::TestCapability( const char* pszCap )

{
    if( EQUAL(pszCap,OLCRandomRead) )
        return FALSE; // the GetFeature() method does not work for this layer. TODO

    else if( EQUAL(pszCap,OLCSequentialWrite) )
        return TRUE; // the CreateFeature() method works for this layer.

    else if( EQUAL(pszCap,OLCRandomWrite) )
        return FALSE; // the SetFeature() method is not operational on this layer.

    else if( EQUAL(pszCap,OLCFastSpatialFilter) )
        return FALSE; // this layer does not implement spatial filtering efficiently.

    else if( EQUAL(pszCap,OLCFastFeatureCount) )
        return FALSE; // this layer can not return a feature count efficiently. FIXME

    else if( EQUAL(pszCap,OLCFastGetExtent) )
        return FALSE; // this layer can not return its data extent efficiently. FIXME

    else if( EQUAL(pszCap,OLCFastSetNextByIndex) )
        return FALSE; // this layer can not perform the SetNextByIndex() call efficiently.

    else if( EQUAL(pszCap,OLCDeleteFeature) )
        return FALSE;

    else if( EQUAL(pszCap,OLCCreateField) )
        return TRUE;

    else if( EQUAL(pszCap,OLCZGeometries) )
        return TRUE;

    return FALSE;
}

/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr OGRGeoconceptLayer::CreateField( OGRFieldDefn *poField,
                                        CPL_UNUSED int bApproxOK )
{
    if( GetGCMode_GCIO(GetSubTypeGCHandle_GCIO(_gcFeature))==vReadAccess_GCIO )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Can't create fields on a read-only Geoconcept layer.\n");
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Add field to layer                                              */
/* -------------------------------------------------------------------- */

    {
      /* check whether field exists ... */
      char* pszName = OGRGeoconceptLayer_GetCompatibleFieldName(poField->GetNameRef());

      GCField* theField = FindFeatureField_GCIO(_gcFeature,pszName);
      if( !theField )
      {
        if( GetFeatureCount(TRUE) > 0 )
        {
          CPLError( CE_Failure, CPLE_NotSupported,
                    "Can't create field '%s' on existing Geoconcept layer '%s.%s'.\n",
                    pszName,
                    GetSubTypeName_GCIO(_gcFeature),
                    GetTypeName_GCIO(GetSubTypeType_GCIO(_gcFeature)) );
          CPLFree(pszName);
          return OGRERR_FAILURE;
        }
        if( GetSubTypeNbFields_GCIO(_gcFeature)==-1)
          SetSubTypeNbFields_GCIO(_gcFeature, 0L);
        if( !(theField= AddSubTypeField_GCIO(GetSubTypeGCHandle_GCIO(_gcFeature),
                                             GetTypeName_GCIO(GetSubTypeType_GCIO(_gcFeature)),
                                             GetSubTypeName_GCIO(_gcFeature),
                                             FindFeatureFieldIndex_GCIO(_gcFeature,kNbFields_GCIO)
                                            +GetSubTypeNbFields_GCIO(_gcFeature)+1,
                                             pszName,
                                             GetSubTypeNbFields_GCIO(_gcFeature)-999L,
                                             vUnknownItemType_GCIO, nullptr, nullptr)) )
        {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Field '%s' could not be created for Feature %s.%s.\n",
                    pszName,
                    GetSubTypeName_GCIO(_gcFeature),
                    GetTypeName_GCIO(GetSubTypeType_GCIO(_gcFeature))
                  );
          CPLFree(pszName);
          return OGRERR_FAILURE;
        }
        SetSubTypeNbFields_GCIO(_gcFeature, GetSubTypeNbFields_GCIO(_gcFeature)+1);
        _poFeatureDefn->AddFieldDefn(poField);
      }
      else
      {
        if( _poFeatureDefn->GetFieldIndex(GetFieldName_GCIO(theField))==-1 )
        {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Field %s not found for Feature %s.%s.\n",
                    GetFieldName_GCIO(theField),
                    GetSubTypeName_GCIO(_gcFeature),
                    GetTypeName_GCIO(GetSubTypeType_GCIO(_gcFeature))
                  );
          CPLFree(pszName);
          return OGRERR_FAILURE;
        }
      }

      CPLFree(pszName);
      pszName = nullptr;

      /* check/update type ? */
      if( GetFieldKind_GCIO(theField)==vUnknownItemType_GCIO )
      {
        switch(poField->GetType()) {
        case OFTInteger        :
          SetFieldKind_GCIO(theField,vIntFld_GCIO);
          break;
        case OFTReal           :
          SetFieldKind_GCIO(theField,vRealFld_GCIO);
          break;
        case OFTDate           :
          SetFieldKind_GCIO(theField,vDateFld_GCIO);
          break;
        case OFTTime           :
        case OFTDateTime       :
          SetFieldKind_GCIO(theField,vTimeFld_GCIO);
          break;
        case OFTString         :
          SetFieldKind_GCIO(theField,vMemoFld_GCIO);
          break;
        case OFTIntegerList    :
        case OFTRealList       :
        case OFTStringList     :
        case OFTBinary         :
        default                :
          CPLError( CE_Failure, CPLE_NotSupported,
                    "Can't create fields of type %s on Geoconcept feature %s.\n",
                    OGRFieldDefn::GetFieldTypeName(poField->GetType()),
                    _poFeatureDefn->GetName() );
          return OGRERR_FAILURE;
        }
      }
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                             SyncToDisk()                             */
/************************************************************************/

OGRErr OGRGeoconceptLayer::SyncToDisk()

{
    FFlush_GCIO(GetSubTypeGCHandle_GCIO(_gcFeature));
    return OGRERR_NONE;
}

/************************************************************************/
/*                             SetSpatialRef()                          */
/************************************************************************/

void OGRGeoconceptLayer::SetSpatialRef( OGRSpatialReference *poSpatialRef )

{
    OGRSpatialReference* poSRS= GetSpatialRef();
    /*-----------------------------------------------------------------
     * Keep a copy of the OGRSpatialReference...
     * Note: we have to take the reference count into account...
     *----------------------------------------------------------------*/
    if( poSRS && poSRS->Dereference() == 0) delete poSRS;

    if( !poSpatialRef ) return;

    poSRS = poSpatialRef->Clone();
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    GCExportFileH* hGXT = GetSubTypeGCHandle_GCIO(_gcFeature);
    if( !hGXT )
    {
        delete poSRS;
        return;
    }
    GCExportFileMetadata* Meta = GetGCMeta_GCIO(hGXT);
    if( !Meta )
    {
        delete poSRS;
        return;
    }
    GCSysCoord* os = GetMetaSysCoord_GCIO(Meta);
    GCSysCoord* ns = OGRSpatialReference2SysCoord_GCSRS(
        reinterpret_cast<OGRSpatialReferenceH>(poSRS) );

    if( os && ns &&
        GetSysCoordSystemID_GCSRS(os)!=-1 &&
        (
          GetSysCoordSystemID_GCSRS(os)!=GetSysCoordSystemID_GCSRS(ns) ||
          GetSysCoordTimeZone_GCSRS(os)!=GetSysCoordTimeZone_GCSRS(ns)
        )
      )
    {
      CPLError( CE_Warning, CPLE_AppDefined,
                "Can't change SRS on Geoconcept layers.\n" );
      DestroySysCoord_GCSRS( &ns );
      delete poSRS;
      return;
    }

    if( os ) DestroySysCoord_GCSRS(&os);
    SetMetaSysCoord_GCIO(Meta, ns);
    SetMetaSRS_GCIO(Meta, (OGRSpatialReferenceH)poSRS);
    return;
}
