/******************************************************************************
 *
 * Project:  KML Translator
 * Purpose:  Implements OGRLIBKMLDriver
 * Author:   Brian Case, rush at winkey dot org
 *
 ******************************************************************************
 * Copyright (c) 2010, Brian Case
 * Copyright (c) 2011-2014, 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 "libkml_headers.h"

#include <set>
#include <string>

#include "ogr_featurestyle.h"
#include "ogrlibkmlstyle.h"
#include "ogr_libkml.h"

using kmlbase::Color32;
using kmldom::BalloonStylePtr;
using kmldom::ContainerPtr;
using kmldom::DocumentPtr;
using kmldom::ElementPtr;
using kmldom::FeaturePtr;
using kmldom::HotSpotPtr;
using kmldom::IconStyleIconPtr;
using kmldom::IconStylePtr;
using kmldom::ItemIconPtr;
using kmldom::KmlFactory;
using kmldom::KmlPtr;
using kmldom::LabelStylePtr;
using kmldom::LineStylePtr;
using kmldom::ListStylePtr;
using kmldom::ObjectPtr;
using kmldom::PairPtr;
using kmldom::PolyStylePtr;
using kmldom::StyleMapPtr;
using kmldom::StylePtr;
using kmldom::StyleSelectorPtr;
using kmldom::STYLESTATE_HIGHLIGHT;
using kmldom::STYLESTATE_NORMAL;

/******************************************************************************
 Generic function to parse a stylestring and add to a kml style.

Args:
            pszStyleString  the stylestring to parse
            poKmlStyle      the kml style to add to (or NULL)
            poKmlFactory    the kml dom factory

Returns:
            the kml style

******************************************************************************/

StylePtr addstylestring2kml(const char *pszStyleString, StylePtr poKmlStyle,
                            KmlFactory *poKmlFactory, FeaturePtr poKmlFeature)
{
    /***** just bail now if stylestring is empty *****/
    if (!pszStyleString || !*pszStyleString)
    {
        return poKmlStyle;
    }

    LineStylePtr poKmlLineStyle = nullptr;
    PolyStylePtr poKmlPolyStyle = nullptr;
    IconStylePtr poKmlIconStyle = nullptr;
    LabelStylePtr poKmlLabelStyle = nullptr;

    /***** create and init a style mamager with the style string *****/
    OGRStyleMgr *const poOgrSM = new OGRStyleMgr;

    poOgrSM->InitStyleString(pszStyleString);

    /***** loop though the style parts *****/
    for (int i = 0; i < poOgrSM->GetPartCount(nullptr); i++)
    {
        OGRStyleTool *poOgrST = poOgrSM->GetPart(i, nullptr);

        if (!poOgrST)
        {
            continue;
        }

        switch (poOgrST->GetType())
        {
            case OGRSTCPen:
            {
                poKmlLineStyle = poKmlFactory->CreateLineStyle();

                OGRStylePen *poStylePen =
                    cpl::down_cast<OGRStylePen *>(poOgrST);

                /***** pen color *****/
                GBool nullcheck = FALSE;
                const char *const pszColor = poStylePen->Color(nullcheck);

                int nR = 0;
                int nG = 0;
                int nB = 0;
                int nA = 0;
                if (!nullcheck &&
                    poStylePen->GetRGBFromString(pszColor, nR, nG, nB, nA))
                {
                    poKmlLineStyle->set_color(Color32(
                        static_cast<GByte>(nA), static_cast<GByte>(nB),
                        static_cast<GByte>(nG), static_cast<GByte>(nR)));
                }
                poStylePen->SetUnit(OGRSTUPixel);
                double dfWidth = poStylePen->Width(nullcheck);

                if (nullcheck)
                    dfWidth = 1.0;

                poKmlLineStyle->set_width(dfWidth);

                break;
            }
            case OGRSTCBrush:
            {
                OGRStyleBrush *const poStyleBrush =
                    cpl::down_cast<OGRStyleBrush *>(poOgrST);

                /***** brush color *****/
                GBool nullcheck = FALSE;
                const char *pszColor = poStyleBrush->ForeColor(nullcheck);

                int nR = 0;
                int nG = 0;
                int nB = 0;
                int nA = 0;
                if (!nullcheck &&
                    poStyleBrush->GetRGBFromString(pszColor, nR, nG, nB, nA))
                {
                    poKmlPolyStyle = poKmlFactory->CreatePolyStyle();
                    poKmlPolyStyle->set_color(Color32(
                        static_cast<GByte>(nA), static_cast<GByte>(nB),
                        static_cast<GByte>(nG), static_cast<GByte>(nR)));
                }
                break;
            }
            case OGRSTCSymbol:
            {
                OGRStyleSymbol *const poStyleSymbol =
                    cpl::down_cast<OGRStyleSymbol *>(poOgrST);

                /***** id (kml icon) *****/
                GBool nullcheck = FALSE;
                const char *pszId = poStyleSymbol->Id(nullcheck);

                if (!nullcheck)
                {
                    if (!poKmlIconStyle)
                        poKmlIconStyle = poKmlFactory->CreateIconStyle();

                    /***** split it at the ,'s *****/
                    char **papszTokens = CSLTokenizeString2(
                        pszId, ",",
                        CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES |
                            CSLT_STRIPENDSPACES);

                    if (papszTokens)
                    {
                        // Just take the first one.
                        // TODO: Come up with a better idea.
                        if (papszTokens[0])
                        {
                            IconStyleIconPtr poKmlIcon =
                                poKmlFactory->CreateIconStyleIcon();
                            poKmlIcon->set_href(papszTokens[0]);
                            poKmlIconStyle->set_icon(poKmlIcon);
                        }

                        CSLDestroy(papszTokens);
                    }
                }

                /***** heading *****/
                double heading = poStyleSymbol->Angle(nullcheck);

                if (!nullcheck)
                {
                    if (!poKmlIconStyle)
                        poKmlIconStyle = poKmlFactory->CreateIconStyle();
                    poKmlIconStyle->set_heading(heading);
                }

                /***** scale *****/
                double dfScale = poStyleSymbol->Size(nullcheck);

                if (!nullcheck)
                {
                    if (!poKmlIconStyle)
                        poKmlIconStyle = poKmlFactory->CreateIconStyle();

                    poKmlIconStyle->set_scale(dfScale);
                }

                /***** color *****/
                const char *const pszcolor = poStyleSymbol->Color(nullcheck);

                int nR = 0;
                int nG = 0;
                int nB = 0;
                int nA = 0;
                if (!nullcheck &&
                    poOgrST->GetRGBFromString(pszcolor, nR, nG, nB, nA))
                {
                    poKmlIconStyle->set_color(Color32(
                        static_cast<GByte>(nA), static_cast<GByte>(nB),
                        static_cast<GByte>(nG), static_cast<GByte>(nR)));
                }

                /***** hotspot *****/

                double dfDx = poStyleSymbol->SpacingX(nullcheck);
                GBool nullcheck2 = FALSE;
                double dfDy = poStyleSymbol->SpacingY(nullcheck2);

                if (!nullcheck && !nullcheck2)
                {
                    if (!poKmlIconStyle)
                        poKmlIconStyle = poKmlFactory->CreateIconStyle();

                    HotSpotPtr poKmlHotSpot = poKmlFactory->CreateHotSpot();
                    if (poKmlHotSpot)
                    {
                        poKmlHotSpot->set_x(dfDx);
                        poKmlHotSpot->set_y(dfDy);

                        poKmlIconStyle->set_hotspot(poKmlHotSpot);
                    }
                }

                break;
            }
            case OGRSTCLabel:
            {
                GBool nullcheck;
                GBool nullcheck2;

                OGRStyleLabel *poStyleLabel =
                    cpl::down_cast<OGRStyleLabel *>(poOgrST);

                /***** color *****/
                const char *pszcolor = poStyleLabel->ForeColor(nullcheck);

                int nR = 0;
                int nG = 0;
                int nB = 0;
                int nA = 0;
                if (!nullcheck &&
                    poStyleLabel->GetRGBFromString(pszcolor, nR, nG, nB, nA))
                {
                    if (!poKmlLabelStyle)
                        poKmlLabelStyle = poKmlFactory->CreateLabelStyle();
                    poKmlLabelStyle->set_color(Color32(
                        static_cast<GByte>(nA), static_cast<GByte>(nB),
                        static_cast<GByte>(nG), static_cast<GByte>(nR)));
                }

                /***** scale *****/
                double dfScale = poStyleLabel->Stretch(nullcheck);

                if (!nullcheck)
                {
                    dfScale /= 100.0;
                    if (!poKmlLabelStyle)
                        poKmlLabelStyle = poKmlFactory->CreateLabelStyle();
                    poKmlLabelStyle->set_scale(dfScale);
                }

                /***** heading *****/
                const double heading = poStyleLabel->Angle(nullcheck);

                if (!nullcheck)
                {
                    if (!poKmlIconStyle)
                    {
                        poKmlIconStyle = poKmlFactory->CreateIconStyle();
                        const IconStyleIconPtr poKmlIcon =
                            poKmlFactory->CreateIconStyleIcon();
                        poKmlIconStyle->set_icon(poKmlIcon);
                    }

                    poKmlIconStyle->set_heading(heading);
                }

                /***** hotspot *****/
                const double dfDx = poStyleLabel->SpacingX(nullcheck);
                const double dfDy = poStyleLabel->SpacingY(nullcheck2);

                if (!nullcheck && !nullcheck2)
                {
                    if (!poKmlIconStyle)
                    {
                        poKmlIconStyle = poKmlFactory->CreateIconStyle();
                        const IconStyleIconPtr poKmlIcon =
                            poKmlFactory->CreateIconStyleIcon();
                        poKmlIconStyle->set_icon(poKmlIcon);
                    }

                    HotSpotPtr poKmlHotSpot = poKmlFactory->CreateHotSpot();
                    if (poKmlHotSpot)
                    {
                        poKmlHotSpot->set_x(dfDx);
                        poKmlHotSpot->set_y(dfDy);

                        poKmlIconStyle->set_hotspot(poKmlHotSpot);
                    }
                }

                /***** label text *****/
                const char *const pszText = poStyleLabel->TextString(nullcheck);

                if (!nullcheck && poKmlFeature)
                {
                    poKmlFeature->set_name(pszText);
                }

                break;
            }
            case OGRSTCNone:
            default:
            {
                break;
            }
        }

        delete poOgrST;
    }

    if (poKmlLineStyle || poKmlPolyStyle || poKmlIconStyle || poKmlLabelStyle)
    {
        if (!poKmlStyle)
            poKmlStyle = poKmlFactory->CreateStyle();

        if (poKmlLineStyle)
            poKmlStyle->set_linestyle(poKmlLineStyle);

        if (poKmlPolyStyle)
            poKmlStyle->set_polystyle(poKmlPolyStyle);

        if (poKmlIconStyle)
            poKmlStyle->set_iconstyle(poKmlIconStyle);

        if (poKmlLabelStyle)
            poKmlStyle->set_labelstyle(poKmlLabelStyle);
    }

    delete poOgrSM;

    return poKmlStyle;
}

/******************************************************************************
 kml2pen
******************************************************************************/

static OGRStylePen *kml2pen(LineStylePtr poKmlLineStyle,
                            OGRStylePen *poOgrStylePen)
{
    if (!poOgrStylePen)
        poOgrStylePen = new OGRStylePen();

    /***** <LineStyle> should always have a width in pixels *****/
    poOgrStylePen->SetUnit(OGRSTUPixel);

    /***** width *****/
    if (poKmlLineStyle->has_width())
        poOgrStylePen->SetWidth(poKmlLineStyle->get_width());

    /***** color *****/
    if (poKmlLineStyle->has_color())
    {
        Color32 poKmlColor = poKmlLineStyle->get_color();
        char szColor[10] = {};
        snprintf(szColor, sizeof(szColor), "#%02X%02X%02X%02X",
                 poKmlColor.get_red(), poKmlColor.get_green(),
                 poKmlColor.get_blue(), poKmlColor.get_alpha());
        poOgrStylePen->SetColor(szColor);
    }

    return poOgrStylePen;
}

/******************************************************************************
 kml2brush
******************************************************************************/

static OGRStyleBrush *kml2brush(PolyStylePtr poKmlPolyStyle,
                                OGRStyleBrush *poOgrStyleBrush)
{
    if (!poOgrStyleBrush)
        poOgrStyleBrush = new OGRStyleBrush();

    /***** color *****/
    if (poKmlPolyStyle->has_color())
    {
        Color32 poKmlColor = poKmlPolyStyle->get_color();
        char szColor[10] = {};
        snprintf(szColor, sizeof(szColor), "#%02X%02X%02X%02X",
                 poKmlColor.get_red(), poKmlColor.get_green(),
                 poKmlColor.get_blue(), poKmlColor.get_alpha());
        poOgrStyleBrush->SetForeColor(szColor);
    }

    return poOgrStyleBrush;
}

/******************************************************************************
 kml2symbol
******************************************************************************/

static OGRStyleSymbol *kml2symbol(IconStylePtr poKmlIconStyle,
                                  OGRStyleSymbol *poOgrStyleSymbol)
{
    if (!poOgrStyleSymbol)
        poOgrStyleSymbol = new OGRStyleSymbol();

    /***** id (kml icon) *****/
    if (poKmlIconStyle->has_icon())
    {
        IconStyleIconPtr poKmlIcon = poKmlIconStyle->get_icon();

        if (poKmlIcon->has_href())
        {
            std::string oIcon = "\"";
            oIcon.append(poKmlIcon->get_href().c_str());
            oIcon.append("\"");
            poOgrStyleSymbol->SetId(oIcon.c_str());
        }
    }

    /***** heading *****/
    if (poKmlIconStyle->has_heading())
        poOgrStyleSymbol->SetAngle(poKmlIconStyle->get_heading());

    /***** scale *****/
    if (poKmlIconStyle->has_scale())
        poOgrStyleSymbol->SetSize(poKmlIconStyle->get_scale());

    /***** color *****/
    if (poKmlIconStyle->has_color())
    {
        Color32 poKmlColor = poKmlIconStyle->get_color();
        char szColor[10] = {};
        snprintf(szColor, sizeof(szColor), "#%02X%02X%02X%02X",
                 poKmlColor.get_red(), poKmlColor.get_green(),
                 poKmlColor.get_blue(), poKmlColor.get_alpha());
        poOgrStyleSymbol->SetColor(szColor);
    }

    /***** hotspot *****/
    if (poKmlIconStyle->has_hotspot())
    {
        const HotSpotPtr poKmlHotSpot = poKmlIconStyle->get_hotspot();

        if (poKmlHotSpot->has_x())
            poOgrStyleSymbol->SetSpacingX(poKmlHotSpot->get_x());
        if (poKmlHotSpot->has_y())
            poOgrStyleSymbol->SetSpacingY(poKmlHotSpot->get_y());
    }

    return poOgrStyleSymbol;
}

/******************************************************************************
 kml2label
******************************************************************************/

static OGRStyleLabel *kml2label(LabelStylePtr poKmlLabelStyle,
                                OGRStyleLabel *poOgrStyleLabel)
{
    if (!poOgrStyleLabel)
        poOgrStyleLabel = new OGRStyleLabel();

    /***** color *****/
    if (poKmlLabelStyle->has_color())
    {
        Color32 poKmlColor = poKmlLabelStyle->get_color();
        char szColor[10] = {};
        snprintf(szColor, sizeof(szColor), "#%02X%02X%02X%02X",
                 poKmlColor.get_red(), poKmlColor.get_green(),
                 poKmlColor.get_blue(), poKmlColor.get_alpha());
        poOgrStyleLabel->SetForColor(szColor);
    }

    if (poKmlLabelStyle->has_scale())
    {
        double dfScale = poKmlLabelStyle->get_scale();
        dfScale *= 100.0;

        poOgrStyleLabel->SetStretch(dfScale);
    }

    return poOgrStyleLabel;
}

/******************************************************************************
 Function to add a kml style to a style table.
******************************************************************************/

static void kml2styletable(OGRStyleTable *poOgrStyleTable, StylePtr poKmlStyle)
{
    /***** No reason to add it if it don't have an id. *****/
    if (!poKmlStyle->has_id())
    {
        CPLError(CE_Warning, CPLE_AppDefined, "ERROR parsing kml Style: No id");
        return;
    }

    OGRStyleMgr *poOgrSM = new OGRStyleMgr(poOgrStyleTable);

    poOgrSM->InitStyleString(nullptr);

    /***** read the style *****/
    kml2stylestring(poKmlStyle, poOgrSM);

    /***** add the style to the style table *****/
    const std::string oName = poKmlStyle->get_id();

    poOgrSM->AddStyle(CPLString().Printf("%s", oName.c_str()), nullptr);

    /***** Cleanup the style manager. *****/
    delete poOgrSM;
}

/******************************************************************************
 Function to follow the kml stylemap if one exists.
******************************************************************************/

StyleSelectorPtr
StyleFromStyleSelector(const StyleSelectorPtr &poKmlStyleSelector,
                       OGRStyleTable *poStyleTable)
{
    /***** Is it a style? *****/
    if (poKmlStyleSelector->IsA(kmldom::Type_Style))
        return poKmlStyleSelector;

    /***** Is it a style map? *****/

    else if (poKmlStyleSelector->IsA(kmldom::Type_StyleMap))
        return StyleFromStyleMap(kmldom::AsStyleMap(poKmlStyleSelector),
                                 poStyleTable);

    /***** Not a style or a style map. *****/
    return nullptr;
}

/******************************************************************************
 kml2stylemgr
******************************************************************************/

void kml2stylestring(StylePtr poKmlStyle, OGRStyleMgr *poOgrSM)

{
    OGRStyleMgr *const poOgrNewSM = new OGRStyleMgr(nullptr);

    /***** linestyle / pen *****/
    if (poKmlStyle->has_linestyle())
    {
        poOgrNewSM->InitStyleString(nullptr);

        LineStylePtr poKmlLineStyle = poKmlStyle->get_linestyle();

        OGRStyleTool *poOgrTmpST = nullptr;
        for (int i = 0; i < poOgrSM->GetPartCount(nullptr); i++)
        {
            OGRStyleTool *poOgrST = poOgrSM->GetPart(i, nullptr);

            if (!poOgrST)
                continue;

            if (poOgrST->GetType() == OGRSTCPen && poOgrTmpST == nullptr)
            {
                poOgrTmpST = poOgrST;
            }
            else
            {
                poOgrNewSM->AddPart(poOgrST);
                delete poOgrST;
            }
        }

        OGRStylePen *poOgrStylePen =
            kml2pen(std::move(poKmlLineStyle), (OGRStylePen *)poOgrTmpST);

        poOgrNewSM->AddPart(poOgrStylePen);

        delete poOgrStylePen;
        poOgrSM->InitStyleString(poOgrNewSM->GetStyleString(nullptr));
    }

    /***** polystyle / brush *****/
    if (poKmlStyle->has_polystyle())
    {
        poOgrNewSM->InitStyleString(nullptr);

        PolyStylePtr poKmlPolyStyle = poKmlStyle->get_polystyle();

        OGRStyleTool *poOgrTmpST = nullptr;
        for (int i = 0; i < poOgrSM->GetPartCount(nullptr); i++)
        {
            OGRStyleTool *poOgrST = poOgrSM->GetPart(i, nullptr);

            if (!poOgrST)
                continue;

            if (poOgrST->GetType() == OGRSTCBrush && poOgrTmpST == nullptr)
            {
                poOgrTmpST = poOgrST;
            }
            else
            {
                poOgrNewSM->AddPart(poOgrST);
                delete poOgrST;
            }
        }

        OGRStyleBrush *poOgrStyleBrush =
            kml2brush(std::move(poKmlPolyStyle), (OGRStyleBrush *)poOgrTmpST);

        poOgrNewSM->AddPart(poOgrStyleBrush);

        delete poOgrStyleBrush;
        poOgrSM->InitStyleString(poOgrNewSM->GetStyleString(nullptr));
    }

    /***** iconstyle / symbol *****/
    if (poKmlStyle->has_iconstyle())
    {
        poOgrNewSM->InitStyleString(nullptr);

        IconStylePtr poKmlIconStyle = poKmlStyle->get_iconstyle();

        OGRStyleTool *poOgrTmpST = nullptr;
        for (int i = 0; i < poOgrSM->GetPartCount(nullptr); i++)
        {
            OGRStyleTool *poOgrST = poOgrSM->GetPart(i, nullptr);

            if (!poOgrST)
                continue;

            if (poOgrST->GetType() == OGRSTCSymbol && poOgrTmpST == nullptr)
            {
                poOgrTmpST = poOgrST;
            }
            else
            {
                poOgrNewSM->AddPart(poOgrST);
                delete poOgrST;
            }
        }

        OGRStyleSymbol *poOgrStyleSymbol =
            kml2symbol(std::move(poKmlIconStyle), (OGRStyleSymbol *)poOgrTmpST);

        poOgrNewSM->AddPart(poOgrStyleSymbol);

        delete poOgrStyleSymbol;
        poOgrSM->InitStyleString(poOgrNewSM->GetStyleString(nullptr));
    }

    /***** labelstyle / label *****/
    if (poKmlStyle->has_labelstyle())
    {
        poOgrNewSM->InitStyleString(nullptr);

        LabelStylePtr poKmlLabelStyle = poKmlStyle->get_labelstyle();

        OGRStyleTool *poOgrTmpST = nullptr;
        for (int i = 0; i < poOgrSM->GetPartCount(nullptr); i++)
        {
            OGRStyleTool *poOgrST = poOgrSM->GetPart(i, nullptr);

            if (!poOgrST)
                continue;

            if (poOgrST->GetType() == OGRSTCLabel && poOgrTmpST == nullptr)
            {
                poOgrTmpST = poOgrST;
            }
            else
            {
                poOgrNewSM->AddPart(poOgrST);
                delete poOgrST;
            }
        }

        OGRStyleLabel *poOgrStyleLabel =
            kml2label(std::move(poKmlLabelStyle), (OGRStyleLabel *)poOgrTmpST);

        poOgrNewSM->AddPart(poOgrStyleLabel);

        delete poOgrStyleLabel;
        poOgrSM->InitStyleString(poOgrNewSM->GetStyleString(nullptr));
    }

    delete poOgrNewSM;
}

/******************************************************************************
 Function to get the container from the kmlroot.

 Args:          poKmlRoot   the root element

 Returns:       root if its a container, if its a kml the container it
                contains, or NULL

******************************************************************************/

static ContainerPtr MyGetContainerFromRoot(KmlFactory *poKmlFactory,
                                           ElementPtr poKmlRoot)
{
    ContainerPtr poKmlContainer = nullptr;

    if (poKmlRoot)
    {
        /***** skip over the <kml> we want the container *****/
        if (poKmlRoot->IsA(kmldom::Type_kml))
        {
            KmlPtr poKmlKml = AsKml(poKmlRoot);

            if (poKmlKml && poKmlKml->has_feature())
            {
                FeaturePtr poKmlFeat = poKmlKml->get_feature();

                if (poKmlFeat->IsA(kmldom::Type_Container))
                {
                    poKmlContainer = AsContainer(poKmlFeat);
                }
                else if (poKmlFeat->IsA(kmldom::Type_Placemark))
                {
                    poKmlContainer = poKmlFactory->CreateDocument();
                    poKmlContainer->add_feature(
                        kmldom::AsFeature(kmlengine::Clone(poKmlFeat)));
                }
            }
        }
        else if (poKmlRoot->IsA(kmldom::Type_Container))
        {
            poKmlContainer = AsContainer(std::move(poKmlRoot));
        }
    }

    return poKmlContainer;
}

static StyleSelectorPtr StyleFromStyleURL(const StyleMapPtr &stylemap,
                                          const string &styleurl,
                                          OGRStyleTable *poStyleTable)
{
    // TODO:: Parse the styleURL.
    char *pszUrl = CPLStrdup(styleurl.c_str());
    char *pszStyleMapId = CPLStrdup(stylemap->get_id().c_str());

    /***** Is it an internal style ref that starts with a #? *****/
    if (*pszUrl == '#' && poStyleTable)
    {
        /***** Search the style table for the style we *****/
        /***** want and copy it back into the table.   *****/
        const char *pszTest = poStyleTable->Find(pszUrl + 1);
        if (pszTest)
        {
            poStyleTable->AddStyle(pszStyleMapId, pszTest);
        }
    }

    /***** We have a real URL and need to go out and fetch it *****/
    /***** FIXME this could be a relative path in a kmz *****/
    else if (strchr(pszUrl, '#'))
    {
        const char *pszFetch =
            CPLGetConfigOption("LIBKML_EXTERNAL_STYLE", "no");
        if (CPLTestBool(pszFetch))
        {
            /***** Lets go out and fetch the style from the external URL *****/
            char *pszUrlTmp = CPLStrdup(pszUrl);
            char *pszPound = strchr(pszUrlTmp, '#');
            char *pszRemoteStyleName = nullptr;
            // Chop off the stuff (style id) after the URL
            if (pszPound != nullptr)
            {
                *pszPound = '\0';
                pszRemoteStyleName = pszPound + 1;
            }

            /***** try it as a url then a file *****/
            VSILFILE *fp = nullptr;
            if ((fp =
                     VSIFOpenL(CPLFormFilename("/vsicurl/", pszUrlTmp, nullptr),
                               "r")) != nullptr ||
                (fp = VSIFOpenL(pszUrlTmp, "r")) != nullptr)
            {
                char szbuf[1025] = {};
                std::string oStyle = "";

                /***** loop, read and copy to a string *****/
                do
                {
                    const size_t nRead =
                        VSIFReadL(szbuf, 1, sizeof(szbuf) - 1, fp);
                    if (nRead == 0)
                        break;

                    /***** copy buf to the string *****/
                    szbuf[nRead] = '\0';
                    oStyle.append(szbuf);
                } while (!VSIFEofL(fp));

                VSIFCloseL(fp);

                /***** parse the kml into the dom *****/
                std::string oKmlErrors;
                ElementPtr poKmlRoot = kmldom::Parse(oStyle, &oKmlErrors);

                if (!poKmlRoot)
                {
                    CPLError(CE_Warning, CPLE_OpenFailed,
                             "ERROR parsing style kml %s :%s", pszUrlTmp,
                             oKmlErrors.c_str());
                    CPLFree(pszUrlTmp);
                    CPLFree(pszUrl);
                    CPLFree(pszStyleMapId);

                    return nullptr;
                }

                /***** get the root container *****/
                kmldom::KmlFactory *poKmlFactory =
                    kmldom::KmlFactory::GetFactory();
                ContainerPtr poKmlContainer;
                if (!(poKmlContainer = MyGetContainerFromRoot(
                          poKmlFactory, std::move(poKmlRoot))))
                {
                    CPLFree(pszUrlTmp);
                    CPLFree(pszUrl);
                    CPLFree(pszStyleMapId);

                    return nullptr;
                }

                /**** parse the styles into the table *****/
                ParseStyles(AsDocument(poKmlContainer), &poStyleTable);

                /***** look for the style we need to map to in the table *****/
                const char *pszTest = poStyleTable->Find(pszRemoteStyleName);

                /***** if found copy it to the table as a new style *****/
                if (pszTest)
                    poStyleTable->AddStyle(pszStyleMapId, pszTest);
            }
            CPLFree(pszUrlTmp);
        }
    }

    /***** FIXME Add support here for relative links inside KML. *****/
    CPLFree(pszUrl);
    CPLFree(pszStyleMapId);

    return nullptr;
}

StyleSelectorPtr StyleFromStyleMap(const StyleMapPtr &poKmlStyleMap,
                                   OGRStyleTable *poStyleTable)
{
    /***** check the config option to see if the    *****/
    /***** user wants normal or highlighted mapping *****/
    const char *pszStyleMapKey =
        CPLGetConfigOption("LIBKML_STYLEMAP_KEY", "normal");
    const int nStyleMapKey = EQUAL(pszStyleMapKey, "highlight")
                                 ? STYLESTATE_HIGHLIGHT
                                 : STYLESTATE_NORMAL;

    /*****  Loop through the stylemap pairs and look for the "normal" one *****/
    for (size_t i = 0; i < poKmlStyleMap->get_pair_array_size(); ++i)
    {
        PairPtr myPair = poKmlStyleMap->get_pair_array_at(i);

        /***** is it the right one of the pair? *****/
        if (myPair->get_key() == nStyleMapKey)
        {
            if (myPair->has_styleselector())
                return StyleFromStyleSelector(myPair->get_styleselector(),
                                              poStyleTable);
            else if (myPair->has_styleurl())
                return StyleFromStyleURL(poKmlStyleMap, myPair->get_styleurl(),
                                         poStyleTable);
        }
    }

    return nullptr;
}

/******************************************************************************
 Function to parse a style table out of a document.
******************************************************************************/

void ParseStyles(DocumentPtr poKmlDocument, OGRStyleTable **poStyleTable)
{
    /***** if document is null just bail now *****/
    if (!poKmlDocument)
        return;

    /***** loop over the Styles *****/
    const size_t nKmlStyles = poKmlDocument->get_styleselector_array_size();

    /***** Lets first build the style table.    *****/
    /***** to begin this is just proper styles. *****/
    for (size_t iKmlStyle = 0; iKmlStyle < nKmlStyles; iKmlStyle++)
    {
        StyleSelectorPtr poKmlStyle =
            poKmlDocument->get_styleselector_array_at(iKmlStyle);

        /***** Everything that is not a style you skip *****/
        if (!poKmlStyle->IsA(kmldom::Type_Style))
            continue;

        /***** We need to check to see if this is the first style. if it *****/
        /***** is we will not have a style table and need to create one  *****/

        if (!*poStyleTable)
            *poStyleTable = new OGRStyleTable();

        kml2styletable(*poStyleTable, AsStyle(AsElement(poKmlStyle)));
    }

    /***** Now we have to loop back around and get the style maps. We    *****/
    /***** have to do this a second time since the stylemap might matter *****/
    /***** and we are just looping reference styles that are farther     *****/
    /***** down in the file. Order through the XML as it is parsed.      *****/

    for (size_t iKmlStyle = 0; iKmlStyle < nKmlStyles; iKmlStyle++)
    {
        StyleSelectorPtr poKmlStyle =
            poKmlDocument->get_styleselector_array_at(iKmlStyle);

        /***** Everything that is not a stylemap you skip *****/
        if (!poKmlStyle->IsA(kmldom::Type_StyleMap))
            continue;

        /***** We need to check to see if this is the first style. if it *****/
        /***** is we will not have a style table and need to create one  *****/
        if (!*poStyleTable)
            *poStyleTable = new OGRStyleTable();

        /***** copy the style the style map points to since *****/

        char *pszStyleMapId = CPLStrdup(poKmlStyle->get_id().c_str());
        poKmlStyle =
            StyleFromStyleMap(kmldom::AsStyleMap(poKmlStyle), *poStyleTable);
        if (!poKmlStyle)
        {
            CPLFree(pszStyleMapId);
            continue;
        }
        char *pszStyleId = CPLStrdup(poKmlStyle->get_id().c_str());

        kml2styletable(*poStyleTable, AsStyle(AsElement(poKmlStyle)));

        // Change the name of the new style in the style table

        const char *pszTest = (*poStyleTable)->Find(pszStyleId);
        // If we found the style we want in the style table we...
        if (pszTest)
        {
            (*poStyleTable)->AddStyle(pszStyleMapId, pszTest);
            (*poStyleTable)->RemoveStyle(pszStyleId);
        }
        CPLFree(pszStyleId);
        CPLFree(pszStyleMapId);
    }
}

/******************************************************************************
 Function to add a style table to a kml container.
******************************************************************************/

void styletable2kml(OGRStyleTable *poOgrStyleTable, KmlFactory *poKmlFactory,
                    ContainerPtr poKmlContainer, char **papszOptions)
{
    /***** just return if the styletable is null *****/
    if (!poOgrStyleTable)
        return;

    std::set<CPLString> aoSetNormalStyles;
    std::set<CPLString> aoSetHighlightStyles;
    poOgrStyleTable->ResetStyleStringReading();

    // Collect styles that end with _normal or _highlight.
    while (poOgrStyleTable->GetNextStyle() != nullptr)
    {
        const char *pszStyleName = poOgrStyleTable->GetLastStyleName();

        if (strlen(pszStyleName) > strlen("_normal") &&
            EQUAL(pszStyleName + strlen(pszStyleName) - strlen("_normal"),
                  "_normal"))
        {
            CPLString osName(pszStyleName);
            osName.resize(strlen(pszStyleName) - strlen("_normal"));
            aoSetNormalStyles.insert(osName);
        }
        else if (strlen(pszStyleName) > strlen("_highlight") &&
                 EQUAL(pszStyleName + strlen(pszStyleName) -
                           strlen("_highlight"),
                       "_highlight"))
        {
            CPLString osName(pszStyleName);
            osName.resize(strlen(pszStyleName) - strlen("_highlight"));
            aoSetHighlightStyles.insert(osName);
        }
    }

    /***** parse the style table *****/
    poOgrStyleTable->ResetStyleStringReading();

    const char *pszStyleString = nullptr;
    while ((pszStyleString = poOgrStyleTable->GetNextStyle()) != nullptr)
    {
        const char *pszStyleName = poOgrStyleTable->GetLastStyleName();

        if (aoSetNormalStyles.find(pszStyleName) != aoSetNormalStyles.end() &&
            aoSetHighlightStyles.find(pszStyleName) !=
                aoSetHighlightStyles.end())
        {
            continue;
        }

        /***** add the style header to the kml *****/
        StylePtr poKmlStyle = poKmlFactory->CreateStyle();

        poKmlStyle->set_id(pszStyleName);

        /***** parse the style string *****/
        addstylestring2kml(pszStyleString, poKmlStyle, poKmlFactory, nullptr);

        /***** add balloon style *****/
        const char *pszBalloonStyleBgColor = CSLFetchNameValue(
            papszOptions, CPLSPrintf("%s_balloonstyle_bgcolor", pszStyleName));
        const char *pszBalloonStyleText = CSLFetchNameValue(
            papszOptions, CPLSPrintf("%s_balloonstyle_text", pszStyleName));
        int nR = 0;
        int nG = 0;
        int nB = 0;
        int nA = 0;
        OGRStylePen oStyleTool;
        if ((pszBalloonStyleBgColor != nullptr &&
             oStyleTool.GetRGBFromString(pszBalloonStyleBgColor, nR, nG, nB,
                                         nA)) ||
            pszBalloonStyleText != nullptr)
        {
            const BalloonStylePtr poKmlBalloonStyle =
                poKmlFactory->CreateBalloonStyle();
            if (pszBalloonStyleBgColor != nullptr &&
                oStyleTool.GetRGBFromString(pszBalloonStyleBgColor, nR, nG, nB,
                                            nA))
                poKmlBalloonStyle->set_bgcolor(
                    Color32(static_cast<GByte>(nA), static_cast<GByte>(nB),
                            static_cast<GByte>(nG), static_cast<GByte>(nR)));
            if (pszBalloonStyleText != nullptr)
                poKmlBalloonStyle->set_text(pszBalloonStyleText);
            poKmlStyle->set_balloonstyle(poKmlBalloonStyle);
        }

        /***** add the style to the container *****/
        const DocumentPtr poKmlDocument = AsDocument(poKmlContainer);
        poKmlDocument->add_styleselector(poKmlStyle);
    }

    // Find style name that end with _normal and _highlight to create
    // a StyleMap from both.
    std::set<CPLString>::iterator aoSetNormalStylesIter =
        aoSetNormalStyles.begin();
    for (; aoSetNormalStylesIter != aoSetNormalStyles.end();
         ++aoSetNormalStylesIter)
    {
        CPLString osStyleName(*aoSetNormalStylesIter);
        if (aoSetHighlightStyles.find(osStyleName) !=
            aoSetHighlightStyles.end())
        {
            StyleMapPtr poKmlStyleMap = poKmlFactory->CreateStyleMap();
            poKmlStyleMap->set_id(osStyleName);

            PairPtr poKmlPairNormal = poKmlFactory->CreatePair();
            poKmlPairNormal->set_key(STYLESTATE_NORMAL);
            poKmlPairNormal->set_styleurl(
                CPLSPrintf("#%s_normal", osStyleName.c_str()));
            poKmlStyleMap->add_pair(poKmlPairNormal);

            PairPtr poKmlPairHighlight = poKmlFactory->CreatePair();
            poKmlPairHighlight->set_key(STYLESTATE_HIGHLIGHT);
            poKmlPairHighlight->set_styleurl(
                CPLSPrintf("#%s_highlight", osStyleName.c_str()));
            poKmlStyleMap->add_pair(poKmlPairHighlight);

            /***** add the style to the container *****/
            DocumentPtr poKmlDocument = AsDocument(poKmlContainer);
            poKmlDocument->add_styleselector(poKmlStyleMap);
        }
    }
}

/******************************************************************************
 Function to add a ListStyle and select it to a container.
******************************************************************************/

void createkmlliststyle(KmlFactory *poKmlFactory, const char *pszBaseName,
                        ContainerPtr poKmlLayerContainer,
                        DocumentPtr poKmlDocument,
                        const CPLString &osListStyleType,
                        const CPLString &osListStyleIconHref)
{
    if (!osListStyleType.empty() || !osListStyleIconHref.empty())
    {
        StylePtr poKmlStyle = poKmlFactory->CreateStyle();

        const char *pszStyleName = CPLSPrintf(
            "%s_liststyle", OGRLIBKMLGetSanitizedNCName(pszBaseName).c_str());
        poKmlStyle->set_id(pszStyleName);

        ListStylePtr poKmlListStyle = poKmlFactory->CreateListStyle();
        poKmlStyle->set_liststyle(poKmlListStyle);
        if (!osListStyleType.empty())
        {
            if (EQUAL(osListStyleType, "check"))
                poKmlListStyle->set_listitemtype(kmldom::LISTITEMTYPE_CHECK);
            else if (EQUAL(osListStyleType, "radioFolder"))
                poKmlListStyle->set_listitemtype(
                    kmldom::LISTITEMTYPE_RADIOFOLDER);
            else if (EQUAL(osListStyleType, "checkOffOnly"))
                poKmlListStyle->set_listitemtype(
                    kmldom::LISTITEMTYPE_CHECKOFFONLY);
            else if (EQUAL(osListStyleType, "checkHideChildren"))
                poKmlListStyle->set_listitemtype(
                    kmldom::LISTITEMTYPE_CHECKHIDECHILDREN);
            else
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Invalid value for list style type: %s. "
                         "Defaulting to Check",
                         osListStyleType.c_str());
                poKmlListStyle->set_listitemtype(kmldom::LISTITEMTYPE_CHECK);
            }
        }

        if (!osListStyleIconHref.empty())
        {
            ItemIconPtr poItemIcon = poKmlFactory->CreateItemIcon();
            poItemIcon->set_href(osListStyleIconHref.c_str());
            poKmlListStyle->add_itemicon(poItemIcon);
        }

        poKmlDocument->add_styleselector(poKmlStyle);
        poKmlLayerContainer->set_styleurl(CPLSPrintf("#%s", pszStyleName));
    }
}
