/******************************************************************************
 * $Id: ogridrisilayer.cpp 27500 2014-07-06 15:01:51Z rouault $
 *
 * Project:  Idrisi Translator
 * Purpose:  Implements OGRIdrisiLayer class.
 * Author:   Even Rouault, <even dot rouault at mines dash paris dot org>
 *
 ******************************************************************************
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "ogr_idrisi.h"
#include "cpl_conv.h"
#include "cpl_string.h"
#include "ogr_p.h"
#include "ogr_srs_api.h"

CPL_CVSID("$Id: ogridrisilayer.cpp 27500 2014-07-06 15:01:51Z rouault $");

/************************************************************************/
/*                         OGRIdrisiLayer()                             */
/************************************************************************/

OGRIdrisiLayer::OGRIdrisiLayer( const char* pszFilename,
                                const char* pszLayerName,
                                VSILFILE* fp,
                                OGRwkbGeometryType eGeomType,
                                const char* pszWTKString )

{
    this->fp = fp;
    this->eGeomType = eGeomType;
    nNextFID = 1;
    bEOF = FALSE;
    fpAVL = NULL;

    if (pszWTKString)
    {
        poSRS = new OGRSpatialReference();
        char* pszTmp = (char*)pszWTKString;
        poSRS->importFromWkt(&pszTmp);
    }
    else
        poSRS = NULL;

    poFeatureDefn = new OGRFeatureDefn( pszLayerName );
    poFeatureDefn->Reference();
    poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
    poFeatureDefn->SetGeomType( eGeomType );

    OGRFieldDefn oFieldDefn("id", OFTReal);
    poFeatureDefn->AddFieldDefn( &oFieldDefn );

    bExtentValid = FALSE;
    dfMinX = dfMinY = dfMaxX = dfMaxY = 0.0;

    VSIFSeekL( fp, 1, SEEK_SET );
    if (VSIFReadL( &nTotalFeatures, sizeof(unsigned int), 1, fp ) != 1)
        nTotalFeatures = 0;
    CPL_LSBPTR32(&nTotalFeatures);

    if (nTotalFeatures != 0)
    {
        if (!Detect_AVL_ADC(pszFilename))
        {
            if( fpAVL != NULL )
                VSIFCloseL( fpAVL );
            fpAVL = NULL;
        }
    }

    ResetReading();
}

/************************************************************************/
/*                          ~OGRIdrisiLayer()                           */
/************************************************************************/

OGRIdrisiLayer::~OGRIdrisiLayer()

{
    if( poSRS != NULL )
        poSRS->Release();

    poFeatureDefn->Release();

    VSIFCloseL( fp );

    if( fpAVL != NULL )
        VSIFCloseL( fpAVL );
}

/************************************************************************/
/*                           Detect_AVL_ADC()                           */
/************************************************************************/

int OGRIdrisiLayer::Detect_AVL_ADC(const char* pszFilename)
{
// --------------------------------------------------------------------
//      Look for .adc file
// --------------------------------------------------------------------
    const char* pszADCFilename = CPLResetExtension(pszFilename, "adc");
    VSILFILE* fpADC = VSIFOpenL(pszADCFilename, "rb");
    if (fpADC == NULL)
    {
        pszADCFilename = CPLResetExtension(pszFilename, "ADC");
        fpADC = VSIFOpenL(pszADCFilename, "rb");
    }

    char** papszADC = NULL;
    if (fpADC != NULL)
    {
        VSIFCloseL(fpADC);
        fpADC = NULL;

        CPLPushErrorHandler(CPLQuietErrorHandler);
        papszADC = CSLLoad2(pszADCFilename, 1024, 256, NULL);
        CPLPopErrorHandler();
        CPLErrorReset();
    }

    if (papszADC == NULL)
        return FALSE;

    CSLSetNameValueSeparator( papszADC, ":" );

    const char *pszVersion = CSLFetchNameValue( papszADC, "file format " );
    if( pszVersion == NULL || !EQUAL( pszVersion, "IDRISI Values A.1" ) )
    {
        CSLDestroy( papszADC );
        return FALSE;
    }

    const char *pszFileType = CSLFetchNameValue( papszADC, "file type   " );
    if( pszFileType == NULL || !EQUAL( pszFileType, "ascii" ) )
    {
        CPLDebug("IDRISI", ".adc file found, but file type != ascii");
        CSLDestroy( papszADC );
        return FALSE;
    }

    const char* pszRecords = CSLFetchNameValue( papszADC, "records     " );
    if( pszRecords == NULL || atoi(pszRecords) != (int)nTotalFeatures )
    {
        CPLDebug("IDRISI", ".adc file found, but 'records' not found or not "
                 "consistant with feature number declared in .vdc");
        CSLDestroy( papszADC );
        return FALSE;
    }

    const char* pszFields = CSLFetchNameValue( papszADC, "fields      " );
    if( pszFields == NULL || atoi(pszFields) <= 1 )
    {
        CPLDebug("IDRISI", ".adc file found, but 'fields' not found or invalid");
        CSLDestroy( papszADC );
        return FALSE;
    }

// --------------------------------------------------------------------
//      Look for .avl file
// --------------------------------------------------------------------
    const char* pszAVLFilename = CPLResetExtension(pszFilename, "avl");
    fpAVL = VSIFOpenL(pszAVLFilename, "rb");
    if (fpAVL == NULL)
    {
        pszAVLFilename = CPLResetExtension(pszFilename, "AVL");
        fpAVL = VSIFOpenL(pszAVLFilename, "rb");
    }
    if (fpAVL == NULL)
    {
        CSLDestroy( papszADC );
        return FALSE;
    }

// --------------------------------------------------------------------
//      Build layer definition
// --------------------------------------------------------------------

    int iCurField;
    char szKey[32];

    iCurField = 0;
    sprintf(szKey, "field %d ", iCurField);

    char** papszIter = papszADC;
    const char* pszLine;
    int bFieldFound = FALSE;
    CPLString osFieldName;
    while((pszLine = *papszIter) != NULL)
    {
        //CPLDebug("IDRISI", "%s", pszLine);
        if (strncmp(pszLine, szKey, strlen(szKey)) == 0)
        {
            const char* pszColon = strchr(pszLine, ':');
            if (pszColon)
            {
                osFieldName = pszColon + 1;
                bFieldFound = TRUE;
            }
        }
        else if (bFieldFound &&
                 strncmp(pszLine, "data type   :", strlen("data type   :")) == 0)
        {
            const char* pszFieldType = pszLine + strlen("data type   :");

            OGRFieldDefn oFieldDefn(osFieldName.c_str(),
                                    EQUAL(pszFieldType, "integer") ? OFTInteger :
                                    EQUAL(pszFieldType, "real") ? OFTReal : OFTString);

            if( iCurField == 0 && oFieldDefn.GetType() != OFTInteger )
            {
                CSLDestroy( papszADC );
                return FALSE;
            }

            if( iCurField != 0 )
                poFeatureDefn->AddFieldDefn( &oFieldDefn );

            iCurField ++;
            sprintf(szKey, "field %d ", iCurField);
        }

        papszIter++;
    }

    CSLDestroy(papszADC);

    return TRUE;
}

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

void OGRIdrisiLayer::ResetReading()

{
    nNextFID = 1;
    bEOF = FALSE;
    VSIFSeekL( fp, 0x105, SEEK_SET );
    if( fpAVL != NULL )
        VSIFSeekL( fpAVL, 0, SEEK_SET );
}

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

OGRFeature *OGRIdrisiLayer::GetNextFeature()
{
    OGRFeature  *poFeature;

    while(TRUE)
    {
        if (bEOF)
            return NULL;

        poFeature = GetNextRawFeature();
        if (poFeature == NULL)
        {
            bEOF = TRUE;
            return NULL;
        }

        if((m_poFilterGeom == NULL
            || FilterGeometry( poFeature->GetGeometryRef() ) )
        && (m_poAttrQuery == NULL
            || m_poAttrQuery->Evaluate( poFeature )) )
        {
            return poFeature;
        }
        else
            delete poFeature;
    }
}

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

int OGRIdrisiLayer::TestCapability( const char * pszCap )

{
    if (EQUAL(pszCap, OLCFastFeatureCount))
        return m_poFilterGeom == NULL && m_poAttrQuery == NULL;

    if (EQUAL(pszCap, OLCFastGetExtent))
        return bExtentValid;

    return FALSE;
}

/************************************************************************/
/*                         GetNextRawFeature()                          */
/************************************************************************/

OGRFeature *OGRIdrisiLayer::GetNextRawFeature()
{
    while(TRUE)
    {
        if (eGeomType == wkbPoint)
        {
            double dfId;
            double dfX, dfY;
            if (VSIFReadL(&dfId, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfX, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfY, sizeof(double), 1, fp) != 1)
            {
                return NULL;
            }
            CPL_LSBPTR64(&dfId);
            CPL_LSBPTR64(&dfX);
            CPL_LSBPTR64(&dfY);

            if (m_poFilterGeom != NULL &&
                (dfX < m_sFilterEnvelope.MinX ||
                 dfX > m_sFilterEnvelope.MaxX ||
                 dfY < m_sFilterEnvelope.MinY ||
                 dfY > m_sFilterEnvelope.MaxY))
            {
                nNextFID ++;
                continue;
            }

            OGRPoint* poGeom = new OGRPoint(dfX, dfY);
            if (poSRS)
                poGeom->assignSpatialReference(poSRS);
            OGRFeature* poFeature = new OGRFeature(poFeatureDefn);
            poFeature->SetField(0, dfId);
            poFeature->SetFID(nNextFID ++);
            poFeature->SetGeometryDirectly(poGeom);
            ReadAVLLine(poFeature);
            return poFeature;
        }
        else if (eGeomType == wkbLineString)
        {
            double dfId;
            double dfMinXShape, dfMaxXShape, dfMinYShape, dfMaxYShape;
            unsigned int nNodes;

            if (VSIFReadL(&dfId, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMinXShape, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMaxXShape, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMinYShape, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMaxYShape, sizeof(double), 1, fp) != 1)
            {
                return NULL;
            }
            CPL_LSBPTR64(&dfId);
            CPL_LSBPTR64(&dfMinXShape);
            CPL_LSBPTR64(&dfMaxXShape);
            CPL_LSBPTR64(&dfMinYShape);
            CPL_LSBPTR64(&dfMaxYShape);

            if (VSIFReadL(&nNodes, sizeof(unsigned int), 1, fp) != 1)
            {
                return NULL;
            }
            CPL_LSBPTR32(&nNodes);

            if (nNodes > 100 * 1000 * 1000)
                return NULL;

            if (m_poFilterGeom != NULL &&
                (dfMaxXShape < m_sFilterEnvelope.MinX ||
                 dfMinXShape > m_sFilterEnvelope.MaxX ||
                 dfMaxYShape < m_sFilterEnvelope.MinY ||
                 dfMinYShape > m_sFilterEnvelope.MaxY))
            {
                nNextFID ++;
                VSIFSeekL(fp, sizeof(OGRRawPoint) * nNodes, SEEK_CUR);
                continue;
            }

            OGRRawPoint* poRawPoints = (OGRRawPoint*)VSIMalloc2(sizeof(OGRRawPoint), nNodes);
            if (poRawPoints == NULL)
            {
                return NULL;
            }

            if ((unsigned int)VSIFReadL(poRawPoints, sizeof(OGRRawPoint), nNodes, fp) != nNodes)
            {
                VSIFree(poRawPoints);
                return NULL;
            }

#if defined(CPL_MSB)
            for(unsigned int iNode=0; iNode<nNodes; iNode++)
            {
                CPL_LSBPTR64(&poRawPoints[iNode].x);
                CPL_LSBPTR64(&poRawPoints[iNode].y);
            }
#endif

            OGRLineString* poGeom = new OGRLineString();
            poGeom->setPoints(nNodes, poRawPoints, NULL);

            VSIFree(poRawPoints);

            if (poSRS)
                poGeom->assignSpatialReference(poSRS);
            OGRFeature* poFeature = new OGRFeature(poFeatureDefn);
            poFeature->SetField(0, dfId);
            poFeature->SetFID(nNextFID ++);
            poFeature->SetGeometryDirectly(poGeom);
            ReadAVLLine(poFeature);
            return poFeature;
        }
        else /* if (eGeomType == wkbPolygon) */
        {
            double dfId;
            double dfMinXShape, dfMaxXShape, dfMinYShape, dfMaxYShape;
            unsigned int nParts;
            unsigned int nTotalNodes;

            if (VSIFReadL(&dfId, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMinXShape, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMaxXShape, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMinYShape, sizeof(double), 1, fp) != 1 ||
                VSIFReadL(&dfMaxYShape, sizeof(double), 1, fp) != 1)
            {
                return NULL;
            }
            CPL_LSBPTR64(&dfId);
            CPL_LSBPTR64(&dfMinXShape);
            CPL_LSBPTR64(&dfMaxXShape);
            CPL_LSBPTR64(&dfMinYShape);
            CPL_LSBPTR64(&dfMaxYShape);
            if (VSIFReadL(&nParts, sizeof(unsigned int), 1, fp) != 1 ||
                VSIFReadL(&nTotalNodes, sizeof(unsigned int), 1, fp) != 1)
            {
                return NULL;
            }
            CPL_LSBPTR32(&nParts);
            CPL_LSBPTR32(&nTotalNodes);

            if (nParts > 100000 || nTotalNodes > 100 * 1000 * 1000)
                return NULL;

            if (m_poFilterGeom != NULL &&
                (dfMaxXShape < m_sFilterEnvelope.MinX ||
                 dfMinXShape > m_sFilterEnvelope.MaxX ||
                 dfMaxYShape < m_sFilterEnvelope.MinY ||
                 dfMinYShape > m_sFilterEnvelope.MaxY))
            {
                VSIFSeekL(fp, sizeof(unsigned int) * nParts + sizeof(OGRRawPoint) * nTotalNodes, SEEK_CUR);
                nNextFID ++;
                continue;
            }

            OGRRawPoint* poRawPoints = (OGRRawPoint*)VSIMalloc2(sizeof(OGRRawPoint), nTotalNodes);
            if (poRawPoints == NULL)
            {
                return NULL;
            }
            unsigned int* panNodesCount = NULL;
            if( nParts > 1 )
            {
                panNodesCount = (unsigned int *)CPLMalloc(sizeof(unsigned int) * nParts);
                if (VSIFReadL(panNodesCount, sizeof(unsigned int) * nParts, 1, fp) != 1)
                {
                    VSIFree(poRawPoints);
                    VSIFree(panNodesCount);
                    return NULL;
                }
#if defined(CPL_MSB)
                for(unsigned int iPart=0; iPart < nParts; iPart ++)
                {
                    CPL_LSBPTR32(&panNodesCount[iPart]);
                }
#endif
            }
            else
            {
                unsigned int nNodes;
                if (VSIFReadL(&nNodes, sizeof(unsigned int) * nParts, 1, fp) != 1)
                {
                    VSIFree(poRawPoints);
                    return NULL;
                }
                CPL_LSBPTR32(&nNodes);
                if( nNodes != nTotalNodes )
                {
                    VSIFree(poRawPoints);
                    return NULL;
                }
            }

            unsigned int iPart;
            OGRPolygon* poGeom = new OGRPolygon();
            for(iPart = 0; iPart < nParts; iPart ++)
            {
                unsigned int nNodes = (nParts > 1) ? panNodesCount[iPart] : nTotalNodes;
                if (nNodes > nTotalNodes ||
                    (unsigned int)VSIFReadL(poRawPoints, sizeof(OGRRawPoint), nNodes, fp) != nNodes)
                {
                    VSIFree(poRawPoints);
                    VSIFree(panNodesCount);
                    delete poGeom;
                    return NULL;
                }

#if defined(CPL_MSB)
                for(unsigned int iNode=0; iNode<nNodes; iNode++)
                {
                    CPL_LSBPTR64(&poRawPoints[iNode].x);
                    CPL_LSBPTR64(&poRawPoints[iNode].y);
                }
#endif

                OGRLinearRing* poLR = new OGRLinearRing();
                poGeom->addRingDirectly(poLR);
                poLR->setPoints(nNodes, poRawPoints, NULL);
            }

            VSIFree(poRawPoints);
            VSIFree(panNodesCount);

            if (poSRS)
                poGeom->assignSpatialReference(poSRS);
            OGRFeature* poFeature = new OGRFeature(poFeatureDefn);
            poFeature->SetField(0, dfId);
            poFeature->SetFID(nNextFID ++);
            poFeature->SetGeometryDirectly(poGeom);
            ReadAVLLine(poFeature);
            return poFeature;
        }
    }
}

/************************************************************************/
/*                            ReadAVLLine()                             */
/************************************************************************/

void OGRIdrisiLayer::ReadAVLLine(OGRFeature* poFeature)
{
    if (fpAVL == NULL)
        return;

    const char* pszLine = CPLReadLineL(fpAVL);
    if (pszLine == NULL)
        return;

    char** papszTokens = CSLTokenizeStringComplex(pszLine, "\t", TRUE, TRUE);
    if (CSLCount(papszTokens) == poFeatureDefn->GetFieldCount())
    {
        int nID = atoi(papszTokens[0]);
        if (nID == poFeature->GetFID())
        {
            int i;
            for(i=1;i<poFeatureDefn->GetFieldCount();i++)
            {
                poFeature->SetField(i, papszTokens[i]);
            }
        }
    }
    CSLDestroy(papszTokens);
}

/************************************************************************/
/*                             SetExtent()                              */
/************************************************************************/

void OGRIdrisiLayer::SetExtent(double dfMinX, double dfMinY, double dfMaxX, double dfMaxY)
{
    bExtentValid = TRUE;
    this->dfMinX = dfMinX;
    this->dfMinY = dfMinY;
    this->dfMaxX = dfMaxX;
    this->dfMaxY = dfMaxY;
}

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

OGRErr OGRIdrisiLayer::GetExtent(OGREnvelope *psExtent, int bForce)
{
    if (!bExtentValid)
        return OGRLayer::GetExtent(psExtent, bForce);

    psExtent->MinX = dfMinX;
    psExtent->MinY = dfMinY;
    psExtent->MaxX = dfMaxX;
    psExtent->MaxY = dfMaxY;
    return OGRERR_NONE;
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/************************************************************************/

int OGRIdrisiLayer::GetFeatureCount( int bForce )
{
    if (nTotalFeatures > 0 && m_poFilterGeom == NULL && m_poAttrQuery == NULL)
        return nTotalFeatures;

    return OGRLayer::GetFeatureCount(bForce);
}
