/******************************************************************************
 *
 * Project:  KML Driver
 * Purpose:  Class for building up the node structure of the kml file.
 * Author:   Jens Oberender, j.obi@troja.net
 *
 ******************************************************************************
 * Copyright (c) 2007, Jens Oberender
 * Copyright (c) 2007-2012, 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 "kmlnode.h"

#include <cstring>
#include <memory>
#include <string>
#include <vector>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "ogr_geometry.h"

CPL_CVSID("$Id$")

/************************************************************************/
/*                           Help functions                             */
/************************************************************************/

std::string Nodetype2String(Nodetype const& type)
{
    if(type == Empty)
        return "Empty";
    else if(type == Rest)
        return "Rest";
    else if(type == Mixed)
        return "Mixed";
    else if(type == Point)
        return "Point";
    else if(type == LineString)
        return "LineString";
    else if(type == Polygon)
        return "Polygon";
    else if(type == MultiGeometry)
        return "MultiGeometry";
    else if(type == MultiPoint)
        return "MultiPoint";
    else if(type == MultiLineString)
        return "MultiLineString";
    else if(type == MultiPolygon)
        return "MultiPolygon";
    else
        return "Unknown";
}

static
bool isNumberDigit(const char cIn)
{
    return ( cIn == '-' || cIn == '+' ||
            (cIn >= '0' && cIn <= '9') ||
             cIn == '.' || cIn == 'e' || cIn == 'E' );
}

static
Coordinate* ParseCoordinate(std::string const& text)
{
    int pos = 0;
    const char* pszStr = text.c_str();
    Coordinate *psTmp = new Coordinate();

    // X coordinate
    psTmp->dfLongitude = CPLAtof(pszStr);
    while(isNumberDigit(pszStr[pos++]));

    // Y coordinate
    if(pszStr[pos - 1] != ',')
    {
        delete psTmp;
        return nullptr;
    }

    psTmp->dfLatitude = CPLAtof(pszStr + pos);
    while(isNumberDigit(pszStr[pos++]));

    // Z coordinate
    if(pszStr[pos - 1] != ',')
    {
        psTmp->bHasZ = false;
        psTmp->dfAltitude = 0;
        return psTmp;
    }

    psTmp->bHasZ = true;
    psTmp->dfAltitude = CPLAtof(pszStr + pos);

    return psTmp;
}

/************************************************************************/
/*                         KMLNode methods                              */
/************************************************************************/

KMLNode::KMLNode() :
    pvpoChildren_(new std::vector<KMLNode*>),
    pvsContent_(new std::vector<std::string>),
    pvoAttributes_(new std::vector<Attribute*>),
    poParent_(nullptr),
    nLevel_(0),
    eType_(Unknown),
    b25D_(false),
    nLayerNumber_(-1),
    nNumFeatures_(-1)
{}

KMLNode::~KMLNode()
{
    CPLAssert( nullptr != pvpoChildren_ );
    CPLAssert( nullptr != pvoAttributes_ );

    kml_nodes_t::iterator itChild;
    for( itChild = pvpoChildren_->begin();
         itChild != pvpoChildren_->end(); ++itChild)
    {
        delete (*itChild);
    }
    delete pvpoChildren_;

    kml_attributes_t::iterator itAttr;
    for( itAttr = pvoAttributes_->begin();
         itAttr != pvoAttributes_->end(); ++itAttr)
    {
        delete (*itAttr);
    }
    delete pvoAttributes_;

    delete pvsContent_;
}

void KMLNode::print(unsigned int what)
{
    std::string indent;
    for(std::size_t l = 0; l < nLevel_; l++)
        indent += " ";

    if(nLevel_ > 0)
    {
        if(nLayerNumber_ > -1)
        {
            CPLDebug( "KML",
                      "%s%s (nLevel: %d Type: %s poParent: %s "
                      "pvpoChildren_: %d pvsContent_: %d pvoAttributes_: %d) "
                      "<--- Layer #%d",
                      indent.c_str(), sName_.c_str(),
                      static_cast<int>(nLevel_),
                      Nodetype2String(eType_).c_str(),
                      poParent_->sName_.c_str(),
                      static_cast<int>(pvpoChildren_->size()),
                      static_cast<int>(pvsContent_->size()),
                      static_cast<int>(pvoAttributes_->size()),
                      nLayerNumber_ );
        }
        else
        {
            CPLDebug( "KML",
                      "%s%s (nLevel: %d Type: %s poParent: %s "
                      "pvpoChildren_: %d pvsContent_: %d pvoAttributes_: %d)",
                      indent.c_str(), sName_.c_str(), static_cast<int>(nLevel_),
                      Nodetype2String(eType_).c_str(),
                      poParent_->sName_.c_str(),
                      static_cast<int>(pvpoChildren_->size()),
                      static_cast<int>(pvsContent_->size()),
                      static_cast<int>(pvoAttributes_->size()) );
        }
    }
    else
    {
        CPLDebug( "KML",
                  "%s%s (nLevel: %d Type: %s pvpoChildren_: %d "
                  "pvsContent_: %d pvoAttributes_: %d)",
                  indent.c_str(), sName_.c_str(), static_cast<int>(nLevel_),
                  Nodetype2String(eType_).c_str(),
                  static_cast<int>(pvpoChildren_->size()),
                  static_cast<int>(pvsContent_->size()),
                  static_cast<int>(pvoAttributes_->size()) );
    }

    if(what == 1 || what == 3)
    {
        for(kml_content_t::size_type z = 0; z < pvsContent_->size(); z++)
            CPLDebug( "KML", "%s|->pvsContent_: '%s'",
                      indent.c_str(), (*pvsContent_)[z].c_str() );
    }

    if(what == 2 || what == 3)
    {
        for(kml_attributes_t::size_type z = 0; z < pvoAttributes_->size(); z++)
            CPLDebug( "KML", "%s|->pvoAttributes_: %s = '%s'",
                      indent.c_str(), (*pvoAttributes_)[z]->sName.c_str(),
                      (*pvoAttributes_)[z]->sValue.c_str() );
    }

    for(kml_nodes_t::size_type z = 0; z < pvpoChildren_->size(); z++)
        (*pvpoChildren_)[z]->print(what);
}

int KMLNode::classify(KML* poKML, int nRecLevel)
{
    Nodetype all = Empty;

    /* Arbitrary value, but certainly large enough for reasonable usages ! */
    if( nRecLevel == 32 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Too many recursion levels (%d) while parsing KML geometry.",
                  nRecLevel );
        return FALSE;
    }

    if(sName_.compare("Point") == 0)
        eType_ = Point;
    else if(sName_.compare("LineString") == 0)
        eType_ = LineString;
    else if(sName_.compare("Polygon") == 0)
        eType_ = Polygon;
    else if(poKML->isRest(sName_))
        eType_ = Empty;
    else if(sName_.compare("coordinates") == 0)
    {
        for( unsigned int nCountP = 0;
             nCountP < pvsContent_->size();
             nCountP++ )
        {
            const char* pszCoord = (*pvsContent_)[nCountP].c_str();
            int nComma = 0;
            while( true )
            {
                pszCoord = strchr(pszCoord, ',');
                if (pszCoord)
                {
                    nComma ++;
                    pszCoord ++;
                }
                else
                    break;
            }
            if (nComma == 2)
                b25D_ = true;
        }
    }

    const kml_nodes_t::size_type size = pvpoChildren_->size();
    for(kml_nodes_t::size_type z = 0; z < size; z++)
    {
        // Classify pvpoChildren_
        if (!(*pvpoChildren_)[z]->classify(poKML, nRecLevel + 1))
            return FALSE;

        Nodetype curr = (*pvpoChildren_)[z]->eType_;
        b25D_ |= (*pvpoChildren_)[z]->b25D_;

        // Compare and return if it is mixed
        if(curr != all && all != Empty && curr != Empty)
        {
            if (sName_.compare("MultiGeometry") == 0 ||
                sName_.compare("MultiPolygon") == 0 ||
                sName_.compare("MultiLineString") == 0 ||
                sName_.compare("MultiPoint") == 0 )
                eType_ = MultiGeometry;
            else
                eType_ = Mixed;
        }
        else if(curr != Empty)
        {
            all = curr;
        }
    }

    if(eType_ == Unknown)
    {
        if (sName_.compare("MultiGeometry") == 0 ||
            sName_.compare("MultiPolygon") == 0 ||
            sName_.compare("MultiLineString") == 0 ||
            sName_.compare("MultiPoint") == 0 )
        {
            if (all == Point)
                eType_ = MultiPoint;
            else if (all == LineString)
                eType_ = MultiLineString;
            else if (all == Polygon)
                eType_ = MultiPolygon;
            else
                eType_ = MultiGeometry;
        }
        else
            eType_ = all;
    }

    return TRUE;
}


void KMLNode::unregisterLayerIfMatchingThisNode(KML* poKML)
{
    for(std::size_t z = 0; z < countChildren(); z++)
    {
        getChild(z)->unregisterLayerIfMatchingThisNode(poKML);
    }
    poKML->unregisterLayerIfMatchingThisNode(this);
}

void KMLNode::eliminateEmpty(KML* poKML)
{
    for(kml_nodes_t::size_type z = 0; z < pvpoChildren_->size();)
    {
        if((*pvpoChildren_)[z]->eType_ == Empty
           && (poKML->isContainer((*pvpoChildren_)[z]->sName_)
               || poKML->isFeatureContainer((*pvpoChildren_)[z]->sName_)))
        {
            (*pvpoChildren_)[z]->unregisterLayerIfMatchingThisNode(poKML);
            delete (*pvpoChildren_)[z];
            pvpoChildren_->erase(pvpoChildren_->begin() + z);
        }
        else
        {
            (*pvpoChildren_)[z]->eliminateEmpty(poKML);
            ++z;
        }
    }
}

bool KMLNode::hasOnlyEmpty() const
{
    for(kml_nodes_t::size_type z = 0; z < pvpoChildren_->size(); z++)
    {
        if((*pvpoChildren_)[z]->eType_ != Empty)
        {
            return false;
        }
        else
        {
            if (!(*pvpoChildren_)[z]->hasOnlyEmpty())
                return false;
        }
    }

    return true;
}

void KMLNode::setType(Nodetype oNotet)
{
    eType_ = oNotet;
}

Nodetype KMLNode::getType() const
{
    return eType_;
}

void KMLNode::setName(std::string const& sIn)
{
    sName_ = sIn;
}

const std::string& KMLNode::getName() const
{
    return sName_;
}

void KMLNode::setLevel(std::size_t nLev)
{
    nLevel_ = nLev;
}

std::size_t KMLNode::getLevel() const
{
    return nLevel_;
}

void KMLNode::addAttribute(Attribute *poAttr)
{
    pvoAttributes_->push_back(poAttr);
}

void KMLNode::setParent(KMLNode* poPar)
{
    poParent_ = poPar;
}

KMLNode* KMLNode::getParent() const
{
    return poParent_;
}

void KMLNode::addChildren(KMLNode *poChil)
{
    pvpoChildren_->push_back(poChil);
}

std::size_t KMLNode::countChildren() const
{
    return pvpoChildren_->size();
}

KMLNode* KMLNode::getChild(std::size_t index) const
{
    return (*pvpoChildren_)[index];
}

void KMLNode::addContent(std::string const& text)
{
    pvsContent_->push_back(text);
}

void KMLNode::appendContent(std::string const& text)
{
    pvsContent_->back() += text;
}

std::string KMLNode::getContent(std::size_t index) const
{
    return (*pvsContent_)[index];
}

void KMLNode::deleteContent(std::size_t index)
{
    if( index < pvsContent_->size() )
    {
        pvsContent_->erase(pvsContent_->begin() + index);
    }
}

std::size_t KMLNode::numContent() const
{
    return pvsContent_->size();
}

void KMLNode::setLayerNumber(int nNum)
{
    nLayerNumber_ = nNum;
}

int KMLNode::getLayerNumber() const
{
    return nLayerNumber_;
}

std::string KMLNode::getNameElement() const
{
    const kml_nodes_t::size_type size = pvpoChildren_->size();

    for( kml_nodes_t::size_type i = 0; i < size; ++i )
    {
        if( (*pvpoChildren_)[i]->sName_.compare("name") == 0 )
        {
            const auto subsize = (*pvpoChildren_)[i]->pvsContent_->size();
            if( subsize > 0 )
            {
                return (*(*pvpoChildren_)[i]->pvsContent_)[0];
            }
            break;
        }
    }
    return "";
}

std::string KMLNode::getDescriptionElement() const
{
    const kml_nodes_t::size_type size = pvpoChildren_->size();
    for( kml_nodes_t::size_type i = 0; i < size; ++i )
    {
        if( (*pvpoChildren_)[i]->sName_.compare("description") == 0 )
        {
            const auto subsize = (*pvpoChildren_)[i]->pvsContent_->size();
            if ( subsize > 0 )
            {
                return (*(*pvpoChildren_)[i]->pvsContent_)[0];
            }
            break;
        }
    }
    return "";
}

std::size_t KMLNode::getNumFeatures()
{
    if (nNumFeatures_ < 0)
    {
        std::size_t nNum = 0;
        kml_nodes_t::size_type size = pvpoChildren_->size();

        for( kml_nodes_t::size_type i = 0; i < size; ++i )
        {
            if( (*pvpoChildren_)[i]->sName_ == "Placemark" )
                nNum++;
        }
        nNumFeatures_ = (int)nNum;
    }
    return nNumFeatures_;
}

OGRGeometry* KMLNode::getGeometry(Nodetype eType)
{
    OGRGeometry* poGeom = nullptr;
    KMLNode* poCoor = nullptr;
    Coordinate* psCoord = nullptr;

    if (sName_.compare("Point") == 0)
    {
        // Search coordinate Element
        for( unsigned int nCount = 0; nCount < pvpoChildren_->size(); nCount++)
        {
            if((*pvpoChildren_)[nCount]->sName_.compare("coordinates") == 0)
            {
                poCoor = (*pvpoChildren_)[nCount];
                for( unsigned int nCountP = 0;
                     nCountP < poCoor->pvsContent_->size();
                     nCountP++)
                {
                    psCoord = ParseCoordinate((*poCoor->pvsContent_)[nCountP]);
                    if(psCoord != nullptr)
                    {
                        if( psCoord->bHasZ )
                            poGeom = new OGRPoint(psCoord->dfLongitude,
                                                  psCoord->dfLatitude,
                                                  psCoord->dfAltitude);
                        else
                            poGeom = new OGRPoint(psCoord->dfLongitude,
                                                  psCoord->dfLatitude);
                        delete psCoord;
                        return poGeom;
                    }
                }
            }
        }
        poGeom = new OGRPoint();
    }
    else if (sName_.compare("LineString") == 0)
    {
        // Search coordinate Element
        poGeom = new OGRLineString();
        for( unsigned int nCount = 0; nCount < pvpoChildren_->size(); nCount++)
        {
            if((*pvpoChildren_)[nCount]->sName_.compare("coordinates") == 0)
            {
                poCoor = (*pvpoChildren_)[nCount];
                for( unsigned int nCountP = 0;
                     nCountP < poCoor->pvsContent_->size();
                     nCountP++ )
                {
                    psCoord = ParseCoordinate((*poCoor->pvsContent_)[nCountP]);
                    if(psCoord != nullptr)
                    {
                        if( psCoord->bHasZ )
                            poGeom->toLineString()->addPoint(psCoord->dfLongitude,
                                                               psCoord->dfLatitude,
                                                               psCoord->dfAltitude);
                        else
                            poGeom->toLineString()->addPoint(psCoord->dfLongitude,
                                                               psCoord->dfLatitude);
                        delete psCoord;
                    }
                }
            }
        }
    }
    else if (sName_.compare("Polygon") == 0)
    {
        //*********************************
        // Search outerBoundaryIs Element
        //*********************************
        poGeom = new OGRPolygon();
        for(unsigned int nCount = 0; nCount < pvpoChildren_->size(); nCount++)
        {
            if((*pvpoChildren_)[nCount]->sName_.compare("outerBoundaryIs") == 0 &&
               !(*pvpoChildren_)[nCount]->pvpoChildren_->empty())
            {
                poCoor = (*(*pvpoChildren_)[nCount]->pvpoChildren_)[0];
            }
        }
        // No outer boundary found
        if(poCoor == nullptr)
        {
            return poGeom;
        }
        // Search coordinate Element
        OGRLinearRing* poLinearRing = nullptr;
        for( unsigned int nCount = 0;
             nCount < poCoor->pvpoChildren_->size();
             nCount++)
        {
            if((*poCoor->pvpoChildren_)[nCount]->sName_.compare("coordinates") == 0)
            {
                for( unsigned int nCountP = 0;
                     nCountP <
                       (*poCoor->pvpoChildren_)[nCount]->pvsContent_->size();
                     nCountP++)
                {
                    psCoord = ParseCoordinate((*(*poCoor->pvpoChildren_)[nCount]->pvsContent_)[nCountP]);
                    if(psCoord != nullptr)
                    {
                        if (poLinearRing == nullptr)
                        {
                            poLinearRing = new OGRLinearRing();
                        }
                        if( psCoord->bHasZ )
                            poLinearRing->addPoint(psCoord->dfLongitude,
                                                   psCoord->dfLatitude,
                                                   psCoord->dfAltitude);
                        else
                            poLinearRing->addPoint(psCoord->dfLongitude,
                                                   psCoord->dfLatitude);
                        delete psCoord;
                    }
                }
            }
        }
        // No outer boundary coordinates found
        if(poLinearRing == nullptr)
        {
            return poGeom;
        }

        poGeom->toPolygon()->addRingDirectly(poLinearRing);
        poLinearRing = nullptr;

        //*********************************
        // Search innerBoundaryIs Elements
        //*********************************

        for( unsigned int nCount2 = 0;
             nCount2 < pvpoChildren_->size();
             nCount2++ )
        {
            if((*pvpoChildren_)[nCount2]->sName_.compare("innerBoundaryIs") == 0)
            {
                if (poLinearRing)
                    poGeom->toPolygon()->addRingDirectly(poLinearRing);
                poLinearRing = nullptr;

                if ((*pvpoChildren_)[nCount2]->pvpoChildren_->empty())
                    continue;

                poLinearRing = new OGRLinearRing();

                poCoor = (*(*pvpoChildren_)[nCount2]->pvpoChildren_)[0];
                // Search coordinate Element
                for( unsigned int nCount = 0;
                     nCount < poCoor->pvpoChildren_->size();
                     nCount++ )
                {
                    if((*poCoor->pvpoChildren_)[nCount]->sName_.compare("coordinates") == 0)
                    {
                        for( unsigned int nCountP = 0;
                             nCountP < (*poCoor->pvpoChildren_)[nCount]->pvsContent_->size();
                             nCountP++)
                        {
                            psCoord = ParseCoordinate((*(*poCoor->pvpoChildren_)[nCount]->pvsContent_)[nCountP]);
                            if(psCoord != nullptr)
                            {
                                if( psCoord->bHasZ )
                                    poLinearRing->addPoint(psCoord->dfLongitude,
                                                        psCoord->dfLatitude,
                                                        psCoord->dfAltitude);
                                else
                                    poLinearRing->addPoint(psCoord->dfLongitude,
                                                        psCoord->dfLatitude);
                                delete psCoord;
                            }
                        }
                    }
                }
            }
        }

        if( poLinearRing )
            poGeom->toPolygon()->addRingDirectly(poLinearRing);
    }
    else if (sName_.compare("MultiGeometry") == 0  ||
             sName_.compare("MultiPolygon") == 0 ||
             sName_.compare("MultiLineString") == 0 ||
             sName_.compare("MultiPoint") == 0)
    {
        if (eType == MultiPoint)
            poGeom = new OGRMultiPoint();
        else if (eType == MultiLineString)
            poGeom = new OGRMultiLineString();
        else if (eType == MultiPolygon)
            poGeom = new OGRMultiPolygon();
        else
            poGeom = new OGRGeometryCollection();
        for(unsigned int nCount = 0; nCount < pvpoChildren_->size(); nCount++)
        {
            OGRGeometry* poSubGeom = (*pvpoChildren_)[nCount]->getGeometry();
            if (poSubGeom)
                poGeom->toGeometryCollection()->addGeometryDirectly(poSubGeom);
        }
    }

    return poGeom;
}

Feature* KMLNode::getFeature(std::size_t nNum, int& nLastAsked, int &nLastCount)
{
    if( nNum >= getNumFeatures() )
        return nullptr;

    unsigned int nCount = 0;
    unsigned int nCountP = 0;
    KMLNode* poFeat = nullptr;
    KMLNode* poTemp = nullptr;

    if (nLastAsked + 1 != static_cast<int>(nNum ))
    {
        // nCount = 0;
        // nCountP = 0;
    }
    else
    {
        nCount = nLastCount + 1;
        nCountP = nLastAsked + 1;
    }

    for(; nCount < pvpoChildren_->size(); nCount++)
    {
        if((*pvpoChildren_)[nCount]->sName_.compare("Placemark") == 0)
        {
            if(nCountP == nNum)
            {
                poFeat = (*pvpoChildren_)[nCount];
                break;
            }
            nCountP++;
        }
    }

    nLastAsked = static_cast<int>(nNum);
    nLastCount = nCount;

    if(poFeat == nullptr)
        return nullptr;

    // Create a feature structure
    Feature *psReturn = new Feature;
    // Build up the name
    psReturn->sName = poFeat->getNameElement();
    // Build up the description
    psReturn->sDescription = poFeat->getDescriptionElement();
    // the type
    psReturn->eType = poFeat->eType_;

    std::string sElementName;
    if(poFeat->eType_ == Point ||
       poFeat->eType_ == LineString ||
       poFeat->eType_ == Polygon)
        sElementName = Nodetype2String(poFeat->eType_);
    else if (poFeat->eType_ == MultiGeometry ||
             poFeat->eType_ == MultiPoint ||
             poFeat->eType_ == MultiLineString ||
             poFeat->eType_ == MultiPolygon)
        sElementName = "MultiGeometry";
    else
    {
        delete psReturn;
        return nullptr;
    }

    for(nCount = 0; nCount < poFeat->pvpoChildren_->size(); nCount++)
    {
        const auto& sName = (*poFeat->pvpoChildren_)[nCount]->sName_;
        if(sName.compare(sElementName) == 0 ||
            (sElementName == "MultiGeometry" &&
             (sName == "MultiPolygon" || sName == "MultiLineString" ||
              sName == "MultiPoint")))
        {
            poTemp = (*poFeat->pvpoChildren_)[nCount];
            psReturn->poGeom = poTemp->getGeometry(poFeat->eType_);
            if(psReturn->poGeom)
                return psReturn;
            else
            {
                delete psReturn;
                return nullptr;
            }
        }
    }

    delete psReturn;
    return nullptr;
}
