/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  The OGRMultiPoint class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2008-2013, 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_port.h"
#include "ogr_geometry.h"

#include <cstddef>
#include <cstdio>
#include <cstring>
#include <new>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_vsi.h"
#include "ogr_core.h"
#include "ogr_p.h"


/************************************************************************/
/*                           OGRMultiPoint()                            */
/************************************************************************/

/**
 * \brief Create an empty multi point collection.
 */

OGRMultiPoint::OGRMultiPoint() = default;

/************************************************************************/
/*                OGRMultiPoint( const OGRMultiPoint& )                 */
/************************************************************************/

/**
 * \brief Copy constructor.
 *
 * Note: before GDAL 2.1, only the default implementation of the constructor
 * existed, which could be unsafe to use.
 *
 * @since GDAL 2.1
 */

OGRMultiPoint::OGRMultiPoint( const OGRMultiPoint& ) = default;

/************************************************************************/
/*                          ~OGRMultiPoint()                            */
/************************************************************************/

OGRMultiPoint::~OGRMultiPoint() = default;

/************************************************************************/
/*                  operator=( const OGRMultiPoint&)                    */
/************************************************************************/

/**
 * \brief Assignment operator.
 *
 * Note: before GDAL 2.1, only the default implementation of the operator
 * existed, which could be unsafe to use.
 *
 * @since GDAL 2.1
 */

OGRMultiPoint& OGRMultiPoint::operator=( const OGRMultiPoint& other )
{
    if( this != &other)
    {
        OGRGeometryCollection::operator=( other );
    }
    return *this;
}

/************************************************************************/
/*                               clone()                                */
/************************************************************************/

OGRMultiPoint *OGRMultiPoint::clone() const

{
    return new (std::nothrow) OGRMultiPoint(*this);
}

/************************************************************************/
/*                          getGeometryType()                           */
/************************************************************************/

OGRwkbGeometryType OGRMultiPoint::getGeometryType() const

{
    if( (flags & OGR_G_3D) && (flags & OGR_G_MEASURED) )
        return wkbMultiPointZM;
    else if( flags & OGR_G_MEASURED )
        return wkbMultiPointM;
    else if( flags & OGR_G_3D )
        return wkbMultiPoint25D;
    else
        return wkbMultiPoint;
}

/************************************************************************/
/*                            getDimension()                            */
/************************************************************************/

int OGRMultiPoint::getDimension() const

{
    return 0;
}

/************************************************************************/
/*                          getGeometryName()                           */
/************************************************************************/

const char * OGRMultiPoint::getGeometryName() const

{
    return "MULTIPOINT";
}

/************************************************************************/
/*                          isCompatibleSubType()                       */
/************************************************************************/

OGRBoolean
OGRMultiPoint::isCompatibleSubType( OGRwkbGeometryType eGeomType ) const
{
    return wkbFlatten(eGeomType) == wkbPoint;
}

/************************************************************************/
/*                            exportToWkt()                             */
/*                                                                      */
/*      Translate this structure into its well known text format        */
/*      equivalent.                                                     */
/************************************************************************/

std::string OGRMultiPoint::exportToWkt(const OGRWktOptions& opts, OGRErr *err) const
{
    try
    {
        std::string wkt = getGeometryName();
        wkt += wktTypeString(opts.variant);

        bool first(true);
        // OGRMultiPoint has a begin()/end().
        for(const OGRPoint *poPoint: this)
        {
            if( poPoint->IsEmpty() )
                continue;

            if( first )
                wkt += '(';
            else
                wkt += ',';
            first = false;

            if( opts.variant == wkbVariantIso )
                wkt += '(';

            wkt += OGRMakeWktCoordinateM(poPoint->getX(), poPoint->getY(),
                    poPoint->getZ(), poPoint->getM(), poPoint->Is3D(),
                    poPoint->IsMeasured() && (opts.variant == wkbVariantIso),
                    opts);

            if( opts.variant == wkbVariantIso )
                wkt += ')';
        }

        if (err)
            *err = OGRERR_NONE;
        if (first)
            wkt += "EMPTY";
        else
            wkt += ')';
        return wkt;
    }
    catch( const std::bad_alloc& e )
    {
        CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
        if (err)
            *err = OGRERR_FAILURE;
        return std::string();
    }
}

/************************************************************************/
/*                           importFromWkt()                            */
/************************************************************************/

OGRErr OGRMultiPoint::importFromWkt( const char ** ppszInput )

{
    const char *pszInputBefore = *ppszInput;
    int bHasZ = FALSE;
    int bHasM = FALSE;
    bool bIsEmpty = false;
    OGRErr eErr = importPreambleFromWkt(ppszInput, &bHasZ, &bHasM, &bIsEmpty);
    flags = 0;
    if( eErr != OGRERR_NONE )
        return eErr;
    if( bHasZ ) flags |= OGR_G_3D;
    if( bHasM ) flags |= OGR_G_MEASURED;
    if( bIsEmpty )
        return OGRERR_NONE;

    char szToken[OGR_WKT_TOKEN_MAX] = {};
    const char *pszInput = *ppszInput;

    const char* pszPreScan = OGRWktReadToken( pszInput, szToken );
    OGRWktReadToken( pszPreScan, szToken );

    // Do we have an inner bracket?
    if( EQUAL(szToken,"(") || EQUAL(szToken, "EMPTY") )
    {
        *ppszInput = pszInputBefore;
        return importFromWkt_Bracketed( ppszInput, bHasM, bHasZ );
    }

/* -------------------------------------------------------------------- */
/*      Read the point list.                                            */
/* -------------------------------------------------------------------- */
    OGRRawPoint *paoPoints = nullptr;
    double *padfZ = nullptr;
    double *padfM = nullptr;
    int flagsFromInput = flags;
    int nMaxPoint = 0;
    int nPointCount = 0;

    pszInput = OGRWktReadPointsM( pszInput, &paoPoints, &padfZ, &padfM,
                                  &flagsFromInput,
                                  &nMaxPoint, &nPointCount );
    if( pszInput == nullptr )
    {
        CPLFree( paoPoints );
        CPLFree( padfZ );
        CPLFree( padfM );
        return OGRERR_CORRUPT_DATA;
    }
    if( (flagsFromInput & OGR_G_3D) && !(flags & OGR_G_3D) )
    {
        flags |= OGR_G_3D;
        bHasZ = TRUE;
    }
    if( (flagsFromInput & OGR_G_MEASURED) && !(flags & OGR_G_MEASURED) )
    {
        flags |= OGR_G_MEASURED;
        bHasM = TRUE;
    }

/* -------------------------------------------------------------------- */
/*      Transform raw points into point objects.                        */
/* -------------------------------------------------------------------- */
    for( int iGeom = 0; iGeom < nPointCount; iGeom++ )
    {
        OGRPoint *poPoint =
            new OGRPoint(paoPoints[iGeom].x, paoPoints[iGeom].y);
        if( bHasM )
        {
            if( padfM != nullptr )
                poPoint->setM(padfM[iGeom]);
            else
                poPoint->setM(0.0);
        }
        if( bHasZ )
        {
            if( padfZ != nullptr )
                poPoint->setZ(padfZ[iGeom]);
            else
                poPoint->setZ(0.0);
        }

        eErr = addGeometryDirectly( poPoint );
        if( eErr != OGRERR_NONE )
        {
            CPLFree( paoPoints );
            CPLFree( padfZ );
            CPLFree( padfM );
            delete poPoint;
            return eErr;
        }
    }

    CPLFree( paoPoints );
    CPLFree( padfZ );
    CPLFree( padfM );

    *ppszInput = pszInput;

    return OGRERR_NONE;
}

/************************************************************************/
/*                      importFromWkt_Bracketed()                       */
/*                                                                      */
/*      This operates similar to importFromWkt(), but reads a format    */
/*      with brackets around each point.  This is the form defined      */
/*      in the BNF of the SFSQL spec.  It is called from                */
/*      importFromWkt().                                                */
/************************************************************************/

OGRErr OGRMultiPoint::importFromWkt_Bracketed( const char ** ppszInput,
                                               int bHasM, int bHasZ )

{
/* -------------------------------------------------------------------- */
/*      Skip MULTIPOINT keyword.                                        */
/* -------------------------------------------------------------------- */
    char szToken[OGR_WKT_TOKEN_MAX] = {};
    const char *pszInput = *ppszInput;
    pszInput = OGRWktReadToken( pszInput, szToken );

    if( bHasZ || bHasM )
    {
        // Skip Z, M or ZM.
        pszInput = OGRWktReadToken( pszInput, szToken );
    }

/* -------------------------------------------------------------------- */
/*      Read points till we get to the closing bracket.                 */
/* -------------------------------------------------------------------- */

    OGRRawPoint *paoPoints = nullptr;
    double *padfZ = nullptr;
    double *padfM = nullptr;

    while( (pszInput = OGRWktReadToken( pszInput, szToken )) != nullptr
           && (EQUAL(szToken, "(") || EQUAL(szToken, ",")) )
    {
        const char* pszNext = OGRWktReadToken( pszInput, szToken );
        if( EQUAL(szToken, "EMPTY") )
        {
            OGRPoint *poGeom = new OGRPoint(0.0, 0.0);
            poGeom->empty();
            const OGRErr eErr = addGeometryDirectly( poGeom );
            if( eErr != OGRERR_NONE )
            {
                CPLFree( paoPoints );
                delete poGeom;
                return eErr;
            }

            pszInput = pszNext;

            continue;
        }

        int flagsFromInput = flags;
        int nMaxPoint = 0;
        int nPointCount = 0;
        pszInput = OGRWktReadPointsM( pszInput, &paoPoints, &padfZ, &padfM,
                                      &flagsFromInput,
                                      &nMaxPoint, &nPointCount );

        if( pszInput == nullptr || nPointCount != 1 )
        {
            CPLFree( paoPoints );
            CPLFree( padfZ );
            CPLFree( padfM );
            return OGRERR_CORRUPT_DATA;
        }
        if( (flagsFromInput & OGR_G_3D) && !(flags & OGR_G_3D) )
        {
            flags |= OGR_G_3D;
            bHasZ = TRUE;
        }
        if( (flagsFromInput & OGR_G_MEASURED) && !(flags & OGR_G_MEASURED) )
        {
            flags |= OGR_G_MEASURED;
            bHasM = TRUE;
        }

        OGRPoint *poPoint = new OGRPoint(paoPoints[0].x, paoPoints[0].y);
        if( bHasM )
        {
            if( padfM != nullptr )
                poPoint->setM(padfM[0]);
            else
                poPoint->setM(0.0);
        }
        if( bHasZ )
        {
            if( padfZ != nullptr )
                poPoint->setZ(padfZ[0]);
            else
                poPoint->setZ(0.0);
        }

        const OGRErr eErr = addGeometryDirectly( poPoint );
        if( eErr != OGRERR_NONE )
        {
            CPLFree( paoPoints );
            CPLFree( padfZ );
            CPLFree( padfM );
            delete poPoint;
            return eErr;
        }
    }

/* -------------------------------------------------------------------- */
/*      Cleanup.                                                        */
/* -------------------------------------------------------------------- */
    CPLFree( paoPoints );
    CPLFree( padfZ );
    CPLFree( padfM );

    if( !EQUAL(szToken, ")") )
        return OGRERR_CORRUPT_DATA;

    *ppszInput = pszInput;

    return OGRERR_NONE;
}

/************************************************************************/
/*                         hasCurveGeometry()                           */
/************************************************************************/

OGRBoolean OGRMultiPoint::hasCurveGeometry( int /* bLookForNonLinear */ ) const
{
    return FALSE;
}
