/*******************************************************************************
 *  Project: OGR CAD Driver
 *  Purpose: Implements driver based on libopencad
 *  Author: Alexandr Borzykh, mush3d at gmail.com
 *  Author: Dmitry Baryshnikov, polimax@mail.ru
 *  Language: C++
 *******************************************************************************
 *  The MIT License (MIT)
 *
 *  Copyright (c) 2016 Alexandr Borzykh
 *  Copyright (c) 2016-2019, NextGIS <info@nextgis.com>
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to
 *deal in the Software without restriction, including without limitation the
 *rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 *sell copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 *IN THE SOFTWARE.
 *******************************************************************************/
#include "cpl_conv.h"
#include "ogr_cad.h"

#include <algorithm>
#include <iomanip>
#include <sstream>

#define FIELD_NAME_GEOMTYPE "cadgeom_type"
#define FIELD_NAME_THICKNESS "thickness"
#define FIELD_NAME_COLOR "color"
#define FIELD_NAME_EXT_DATA "extentity_data"
#define FIELD_NAME_TEXT "text"

constexpr double DEG2RAD = M_PI / 180.0;
constexpr double RAD2DEG = 1.0 / DEG2RAD;

OGRCADLayer::OGRCADLayer(CADLayer &poCADLayer_, OGRSpatialReference *poSR,
                         int nEncoding)
    : poSpatialRef(poSR), poCADLayer(poCADLayer_), nDWGEncoding(nEncoding)
{
    nNextFID = 0;

    if (poSpatialRef)
        poSpatialRef->Reference();
    poFeatureDefn =
        new OGRFeatureDefn(CADRecode(poCADLayer_.getName(), nDWGEncoding));

    // Setting up layer geometry type
    OGRwkbGeometryType eGeomType;
    char dLineStringPresented = 0;
    char dCircularStringPresented = 0;
    char dPointPresented = 0;
    char dPolygonPresented = 0;
    std::vector<CADObject::ObjectType> aePresentedGeometryTypes =
        poCADLayer.getGeometryTypes();
    for (size_t i = 0; i < aePresentedGeometryTypes.size(); ++i)
    {
        switch (aePresentedGeometryTypes[i])
        {
            case CADObject::ATTDEF:
            case CADObject::TEXT:
            case CADObject::MTEXT:
            case CADObject::POINT:
                dPointPresented = 1;
                break;
            case CADObject::CIRCLE:
                dCircularStringPresented = 1;
                break;
            case CADObject::SPLINE:
            case CADObject::ELLIPSE:
            case CADObject::ARC:
            case CADObject::POLYLINE3D:
            case CADObject::POLYLINE2D:
            case CADObject::LWPOLYLINE:
            case CADObject::LINE:
                dLineStringPresented = 1;
                break;
            case CADObject::FACE3D:
            case CADObject::SOLID:
                dPolygonPresented = 1;
                break;
            default:
                break;
        }
    }

    if ((dLineStringPresented + dCircularStringPresented + dPointPresented +
         dPolygonPresented) > 1)
    {
        eGeomType = wkbGeometryCollection;
    }
    else
    {
        if (dLineStringPresented)
        {
            eGeomType = wkbLineString;
        }
        else if (dCircularStringPresented)
        {
            eGeomType = wkbCircularString;
        }
        else if (dPointPresented)
        {
            eGeomType = wkbPoint;
        }
        else if (dPolygonPresented)
        {
            eGeomType = wkbPolygon;
        }
        else
        {
            eGeomType = wkbUnknown;
        }
    }
    poFeatureDefn->SetGeomType(eGeomType);

    OGRFieldDefn oClassField(FIELD_NAME_GEOMTYPE, OFTString);
    poFeatureDefn->AddFieldDefn(&oClassField);

    OGRFieldDefn oLinetypeField(FIELD_NAME_THICKNESS, OFTReal);
    poFeatureDefn->AddFieldDefn(&oLinetypeField);

    OGRFieldDefn oColorField(FIELD_NAME_COLOR, OFTString);
    poFeatureDefn->AddFieldDefn(&oColorField);

    OGRFieldDefn oExtendedField(FIELD_NAME_EXT_DATA, OFTString);
    poFeatureDefn->AddFieldDefn(&oExtendedField);

    OGRFieldDefn oTextField(FIELD_NAME_TEXT, OFTString);
    poFeatureDefn->AddFieldDefn(&oTextField);

    auto oAttrTags = poCADLayer.getAttributesTags();
    for (const std::string &osTag : oAttrTags)
    {
        auto ret = asFeaturesAttributes.insert(osTag);
        if (ret.second == true)
        {
            OGRFieldDefn oAttrField(osTag.c_str(), OFTString);
            poFeatureDefn->AddFieldDefn(&oAttrField);
        }
    }

    // Applying spatial ref info
    if (poFeatureDefn->GetGeomFieldCount() != 0)
        poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSpatialRef);

    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->Reference();
}

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

    return poCADLayer.getGeometryCount();
}

int OGRCADLayer::TestCapability(const char *pszCap)
{
    if (EQUAL(pszCap, OLCMeasuredGeometries))
        return true;
    if (EQUAL(pszCap, OLCZGeometries))
        return true;
    if (EQUAL(pszCap, OLCCurveGeometries))
        return true;

    return FALSE;
}

OGRCADLayer::~OGRCADLayer()
{
    if (poSpatialRef)
        poSpatialRef->Release();
    poFeatureDefn->Release();
}

void OGRCADLayer::ResetReading()
{
    nNextFID = 0;
}

OGRFeature *OGRCADLayer::GetNextFeature()
{
    OGRFeature *poFeature = GetFeature(nNextFID);
    ++nNextFID;

    if (poFeature == nullptr)
        return nullptr;

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

    return nullptr;
}

OGRFeature *OGRCADLayer::GetFeature(GIntBig nFID)
{
    if (poCADLayer.getGeometryCount() <= static_cast<size_t>(nFID) || nFID < 0)
    {
        return nullptr;
    }

    OGRFeature *poFeature = nullptr;
    CADGeometry *poCADGeometry =
        poCADLayer.getGeometry(static_cast<size_t>(nFID));

    if (nullptr == poCADGeometry ||
        GetLastErrorCode() != CADErrorCodes::SUCCESS)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Failed to get geometry with ID = " CPL_FRMT_GIB
                 " from layer \"%s\". Libopencad errorcode: %d",
                 nFID, poCADLayer.getName().c_str(), GetLastErrorCode());
        return nullptr;
    }

    poFeature = new OGRFeature(poFeatureDefn);
    poFeature->SetFID(nFID);
    poFeature->SetField(FIELD_NAME_THICKNESS, poCADGeometry->getThickness());

    if (!poCADGeometry->getEED().empty())
    {
        std::vector<std::string> asGeometryEED = poCADGeometry->getEED();
        std::string sEEDAsOneString = "";
        for (std::vector<std::string>::const_iterator iter =
                 asGeometryEED.cbegin();
             iter != asGeometryEED.cend(); ++iter)
        {
            sEEDAsOneString += *iter;
            sEEDAsOneString += ' ';
        }

        poFeature->SetField(FIELD_NAME_EXT_DATA, sEEDAsOneString.c_str());
    }

    RGBColor stRGB = poCADGeometry->getColor();
    CPLString sHexColor;
    sHexColor.Printf("#%02X%02X%02X%02X", stRGB.R, stRGB.G, stRGB.B, 255);
    poFeature->SetField(FIELD_NAME_COLOR, sHexColor);

    CPLString sStyle;
    sStyle.Printf("PEN(c:%s,w:5px)", sHexColor.c_str());
    poFeature->SetStyleString(sStyle);

    std::vector<CADAttrib> oBlockAttrs = poCADGeometry->getBlockAttributes();
    for (const CADAttrib &oAttrib : oBlockAttrs)
    {
        CPLString osTag = oAttrib.getTag();
        auto featureAttrIt = asFeaturesAttributes.find(osTag);
        if (featureAttrIt != asFeaturesAttributes.end())
        {
            poFeature->SetField(*featureAttrIt, oAttrib.getTextValue().c_str());
        }
    }

    switch (poCADGeometry->getType())
    {
        case CADGeometry::POINT:
        {
            CADPoint3D *const poCADPoint = (CADPoint3D *)poCADGeometry;
            CADVector stPositionVector = poCADPoint->getPosition();

            poFeature->SetGeometryDirectly(
                new OGRPoint(stPositionVector.getX(), stPositionVector.getY(),
                             stPositionVector.getZ()));
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADPoint");
            break;
        }

        case CADGeometry::LINE:
        {
            CADLine *const poCADLine = (CADLine *)poCADGeometry;
            OGRLineString *poLS = new OGRLineString();
            poLS->addPoint(poCADLine->getStart().getPosition().getX(),
                           poCADLine->getStart().getPosition().getY(),
                           poCADLine->getStart().getPosition().getZ());
            poLS->addPoint(poCADLine->getEnd().getPosition().getX(),
                           poCADLine->getEnd().getPosition().getY(),
                           poCADLine->getEnd().getPosition().getZ());

            poFeature->SetGeometryDirectly(poLS);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADLine");
            break;
        }

        case CADGeometry::SOLID:
        {
            CADSolid *const poCADSolid = (CADSolid *)poCADGeometry;
            OGRPolygon *poPoly = new OGRPolygon();
            OGRLinearRing *poLR = new OGRLinearRing();

            std::vector<CADVector> astSolidCorners = poCADSolid->getCorners();
            for (size_t i = 0; i < astSolidCorners.size(); ++i)
            {
                poLR->addPoint(astSolidCorners[i].getX(),
                               astSolidCorners[i].getY(),
                               astSolidCorners[i].getZ());
            }
            poPoly->addRingDirectly(poLR);
            poPoly->closeRings();
            poFeature->SetGeometryDirectly(poPoly);

            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADSolid");
            break;
        }

        case CADGeometry::CIRCLE:
        {
            CADCircle *poCADCircle = static_cast<CADCircle *>(poCADGeometry);
            OGRCircularString *poCircle = new OGRCircularString();

            CADVector stCircleCenter = poCADCircle->getPosition();
            OGRPoint oCirclePoint1;
            oCirclePoint1.setX(stCircleCenter.getX() -
                               poCADCircle->getRadius());
            oCirclePoint1.setY(stCircleCenter.getY());
            oCirclePoint1.setZ(stCircleCenter.getZ());
            poCircle->addPoint(&oCirclePoint1);

            OGRPoint oCirclePoint2;
            oCirclePoint2.setX(stCircleCenter.getX());
            oCirclePoint2.setY(stCircleCenter.getY() +
                               poCADCircle->getRadius());
            oCirclePoint2.setZ(stCircleCenter.getZ());
            poCircle->addPoint(&oCirclePoint2);

            OGRPoint oCirclePoint3;
            oCirclePoint3.setX(stCircleCenter.getX() +
                               poCADCircle->getRadius());
            oCirclePoint3.setY(stCircleCenter.getY());
            oCirclePoint3.setZ(stCircleCenter.getZ());
            poCircle->addPoint(&oCirclePoint3);

            OGRPoint oCirclePoint4;
            oCirclePoint4.setX(stCircleCenter.getX());
            oCirclePoint4.setY(stCircleCenter.getY() -
                               poCADCircle->getRadius());
            oCirclePoint4.setZ(stCircleCenter.getZ());
            poCircle->addPoint(&oCirclePoint4);

            // Close the circle
            poCircle->addPoint(&oCirclePoint1);

            /*NOTE: The alternative way:
                    OGRGeometry *poCircle =
               OGRGeometryFactory::approximateArcAngles(
                    poCADCircle->getPosition().getX(),
                    poCADCircle->getPosition().getY(),
                    poCADCircle->getPosition().getZ(),
                    poCADCircle->getRadius(), poCADCircle->getRadius(), 0.0,
                    0.0, 360.0,
                    0.0 );
            */
            poFeature->SetGeometryDirectly(poCircle);

            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADCircle");
            break;
        }

        case CADGeometry::ARC:
        {
            CADArc *poCADArc = static_cast<CADArc *>(poCADGeometry);
            OGRCircularString *poCircle = new OGRCircularString();

            // Need at least 3 points in arc
            double dfStartAngle = poCADArc->getStartingAngle() * RAD2DEG;
            double dfEndAngle = poCADArc->getEndingAngle() * RAD2DEG;
            double dfMidAngle = (dfEndAngle + dfStartAngle) / 2;
            CADVector stCircleCenter = poCADArc->getPosition();

            OGRPoint oCirclePoint;
            oCirclePoint.setX(stCircleCenter.getX() +
                              poCADArc->getRadius() * cos(dfStartAngle));
            oCirclePoint.setY(stCircleCenter.getY() +
                              poCADArc->getRadius() * sin(dfStartAngle));
            oCirclePoint.setZ(stCircleCenter.getZ());
            poCircle->addPoint(&oCirclePoint);

            oCirclePoint.setX(stCircleCenter.getX() +
                              poCADArc->getRadius() * cos(dfMidAngle));
            oCirclePoint.setY(stCircleCenter.getY() +
                              poCADArc->getRadius() * sin(dfMidAngle));
            oCirclePoint.setZ(stCircleCenter.getZ());
            poCircle->addPoint(&oCirclePoint);

            oCirclePoint.setX(stCircleCenter.getX() +
                              poCADArc->getRadius() * cos(dfEndAngle));
            oCirclePoint.setY(stCircleCenter.getY() +
                              poCADArc->getRadius() * sin(dfEndAngle));
            oCirclePoint.setZ(stCircleCenter.getZ());
            poCircle->addPoint(&oCirclePoint);

            /*NOTE: alternative way:
                OGRGeometry * poArc = OGRGeometryFactory::approximateArcAngles(
                poCADArc->getPosition().getX(),
                poCADArc->getPosition().getY(),
                poCADArc->getPosition().getZ(),
                poCADArc->getRadius(), poCADArc->getRadius(), 0.0,
                dfStartAngle,
                dfStartAngle > dfEndAngle ?
                    ( dfEndAngle + 360.0f ) :
                    dfEndAngle,
                0.0 );
            */

            poFeature->SetGeometryDirectly(poCircle);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADArc");

            break;
        }

        case CADGeometry::FACE3D:
        {
            CADFace3D *const poCADFace = (CADFace3D *)poCADGeometry;
            OGRPolygon *poPoly = new OGRPolygon();
            OGRLinearRing *poLR = new OGRLinearRing();

            for (size_t i = 0; i < 3; ++i)
            {
                poLR->addPoint(poCADFace->getCorner(i).getX(),
                               poCADFace->getCorner(i).getY(),
                               poCADFace->getCorner(i).getZ());
            }
            if (!(poCADFace->getCorner(2) == poCADFace->getCorner(3)))
            {
                poLR->addPoint(poCADFace->getCorner(3).getX(),
                               poCADFace->getCorner(3).getY(),
                               poCADFace->getCorner(3).getZ());
            }
            poPoly->addRingDirectly(poLR);
            poPoly->closeRings();
            poFeature->SetGeometryDirectly(poPoly);

            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADFace3D");
            break;
        }

        case CADGeometry::LWPOLYLINE:
        {
            CADLWPolyline *const poCADLWPolyline =
                (CADLWPolyline *)poCADGeometry;

            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADLWPolyline");

            /*
             * Excessive check, like in DXF driver.
             * I tried to make a single-point polyline, but couldn't make it.
             * Probably this check should be removed.
             */
            if (poCADLWPolyline->getVertexCount() == 1)
            {
                poFeature->SetGeometryDirectly(
                    new OGRPoint(poCADLWPolyline->getVertex(0).getX(),
                                 poCADLWPolyline->getVertex(0).getY(),
                                 poCADLWPolyline->getVertex(0).getZ()));

                break;
            }

            /*
             * If polyline has no arcs, handle it in easy way.
             */
            OGRLineString *poLS = new OGRLineString();

            if (poCADLWPolyline->getBulges().empty())
            {
                for (size_t i = 0; i < poCADLWPolyline->getVertexCount(); ++i)
                {
                    CADVector stVertex = poCADLWPolyline->getVertex(i);
                    poLS->addPoint(stVertex.getX(), stVertex.getY(),
                                   stVertex.getZ());
                }

                poFeature->SetGeometryDirectly(poLS);
                break;
            }

            /*
             * Last case - if polyline has mixed arcs and lines.
             */
            bool bLineStringStarted = false;
            std::vector<double> adfBulges = poCADLWPolyline->getBulges();
            const size_t nCount =
                std::min(adfBulges.size(), poCADLWPolyline->getVertexCount());

            for (size_t iCurrentVertex = 0; iCurrentVertex + 1 < nCount;
                 iCurrentVertex++)
            {
                CADVector stCurrentVertex =
                    poCADLWPolyline->getVertex(iCurrentVertex);
                CADVector stNextVertex =
                    poCADLWPolyline->getVertex(iCurrentVertex + 1);

                double dfLength =
                    sqrt(pow(stNextVertex.getX() - stCurrentVertex.getX(), 2) +
                         pow(stNextVertex.getY() - stCurrentVertex.getY(), 2));

                /*
                 * Handling straight polyline segment.
                 */
                if ((dfLength == 0) || (adfBulges[iCurrentVertex] == 0))
                {
                    if (!bLineStringStarted)
                    {
                        poLS->addPoint(stCurrentVertex.getX(),
                                       stCurrentVertex.getY(),
                                       stCurrentVertex.getZ());
                        bLineStringStarted = true;
                    }

                    poLS->addPoint(stNextVertex.getX(), stNextVertex.getY(),
                                   stNextVertex.getZ());
                }
                else
                {
                    double dfSegmentBulge = adfBulges[iCurrentVertex];
                    double dfH = (dfSegmentBulge * dfLength) / 2;
                    if (dfH == 0.0)
                        dfH = 1.0;  // just to avoid a division by zero
                    double dfRadius =
                        (dfH / 2) + (dfLength * dfLength / (8 * dfH));
                    double dfOgrArcRotation = 0,
                           dfOgrArcRadius = fabs(dfRadius);

                    /*
                     * Set arc's direction and keep bulge positive.
                     */
                    bool bClockwise = (dfSegmentBulge < 0);
                    if (bClockwise)
                        dfSegmentBulge *= -1;

                    /*
                     * Get arc's center point.
                     */
                    double dfSaggita = fabs(dfSegmentBulge * (dfLength / 2.0));
                    double dfApo = bClockwise ? -(dfOgrArcRadius - dfSaggita)
                                              : -(dfSaggita - dfOgrArcRadius);

                    CADVector stVertex;
                    stVertex.setX(stCurrentVertex.getX() - stNextVertex.getX());
                    stVertex.setY(stCurrentVertex.getY() - stNextVertex.getY());
                    stVertex.setZ(stCurrentVertex.getZ());

                    CADVector stMidPoint;
                    stMidPoint.setX(stNextVertex.getX() +
                                    0.5 * stVertex.getX());
                    stMidPoint.setY(stNextVertex.getY() +
                                    0.5 * stVertex.getY());
                    stMidPoint.setZ(stVertex.getZ());

                    CADVector stPperp;
                    stPperp.setX(stVertex.getY());
                    stPperp.setY(-stVertex.getX());
                    double dfStPperpLength =
                        sqrt(stPperp.getX() * stPperp.getX() +
                             stPperp.getY() * stPperp.getY());
                    // TODO: Check that length isnot 0
                    stPperp.setX(stPperp.getX() / dfStPperpLength);
                    stPperp.setY(stPperp.getY() / dfStPperpLength);

                    CADVector stOgrArcCenter;
                    stOgrArcCenter.setX(stMidPoint.getX() +
                                        (stPperp.getX() * dfApo));
                    stOgrArcCenter.setY(stMidPoint.getY() +
                                        (stPperp.getY() * dfApo));

                    /*
                     * Get the line's general vertical direction ( -1 = down, +1
                     * = up ).
                     */
                    double dfLineDir =
                        stNextVertex.getY() > stCurrentVertex.getY() ? 1.0f
                                                                     : -1.0f;

                    /*
                     * Get arc's starting angle.
                     */
                    double dfA =
                        atan2(
                            (stOgrArcCenter.getY() - stCurrentVertex.getY()),
                            (stOgrArcCenter.getX() - stCurrentVertex.getX())) *
                        RAD2DEG;
                    if (bClockwise && (dfLineDir == 1.0))
                        dfA += (dfLineDir * 180.0);

                    double dfOgrArcStartAngle =
                        dfA > 0.0 ? -(dfA - 180.0) : -(dfA + 180.0);

                    /*
                     * Get arc's ending angle.
                     */
                    dfA = atan2((stOgrArcCenter.getY() - stNextVertex.getY()),
                                (stOgrArcCenter.getX() - stNextVertex.getX())) *
                          RAD2DEG;
                    if (bClockwise && (dfLineDir == 1.0))
                        dfA += (dfLineDir * 180.0);

                    double dfOgrArcEndAngle =
                        dfA > 0.0 ? -(dfA - 180.0) : -(dfA + 180.0);

                    if (!bClockwise && (dfOgrArcStartAngle < dfOgrArcEndAngle))
                        dfOgrArcEndAngle = -180.0 + (dfLineDir * dfA);

                    if (bClockwise && (dfOgrArcStartAngle > dfOgrArcEndAngle))
                        dfOgrArcEndAngle += 360.0;

                    /*
                     * Flip arc's rotation if necessary.
                     */
                    if (bClockwise && (dfLineDir == 1.0))
                        dfOgrArcRotation = dfLineDir * 180.0;

                    /*
                     * Tessellate the arc segment and append to the linestring.
                     */
                    OGRLineString *poArcpoLS = (OGRLineString *)
                        OGRGeometryFactory::approximateArcAngles(
                            stOgrArcCenter.getX(), stOgrArcCenter.getY(),
                            stOgrArcCenter.getZ(), dfOgrArcRadius,
                            dfOgrArcRadius, dfOgrArcRotation,
                            dfOgrArcStartAngle, dfOgrArcEndAngle, 0.0);

                    poLS->addSubLineString(poArcpoLS);

                    delete (poArcpoLS);
                }
            }

            if (poCADLWPolyline->isClosed())
            {
                poLS->addPoint(poCADLWPolyline->getVertex(0).getX(),
                               poCADLWPolyline->getVertex(0).getY(),
                               poCADLWPolyline->getVertex(0).getZ());
            }

            poFeature->SetGeometryDirectly(poLS);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADLWPolyline");
            break;
        }

        // TODO: Unsupported smooth lines
        case CADGeometry::POLYLINE3D:
        {
            CADPolyline3D *const poCADPolyline3D =
                (CADPolyline3D *)poCADGeometry;
            OGRLineString *poLS = new OGRLineString();

            for (size_t i = 0; i < poCADPolyline3D->getVertexCount(); ++i)
            {
                CADVector stVertex = poCADPolyline3D->getVertex(i);

                poLS->addPoint(stVertex.getX(), stVertex.getY(),
                               stVertex.getZ());
            }

            poFeature->SetGeometryDirectly(poLS);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADPolyline3D");
            break;
        }

        case CADGeometry::TEXT:
        {
            CADText *const poCADText = (CADText *)poCADGeometry;
            OGRPoint *poPoint = new OGRPoint(poCADText->getPosition().getX(),
                                             poCADText->getPosition().getY(),
                                             poCADText->getPosition().getZ());
            CPLString sTextValue =
                CADRecode(poCADText->getTextValue(), nDWGEncoding);

            poFeature->SetField(FIELD_NAME_TEXT, sTextValue);
            poFeature->SetGeometryDirectly(poPoint);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADText");

            sStyle.Printf("LABEL(f:\"Arial\",t:\"%s\",c:%s)",
                          sTextValue.c_str(), sHexColor.c_str());
            poFeature->SetStyleString(sStyle);
            break;
        }

        case CADGeometry::MTEXT:
        {
            CADMText *const poCADMText = (CADMText *)poCADGeometry;
            OGRPoint *poPoint = new OGRPoint(poCADMText->getPosition().getX(),
                                             poCADMText->getPosition().getY(),
                                             poCADMText->getPosition().getZ());
            CPLString sTextValue =
                CADRecode(poCADMText->getTextValue(), nDWGEncoding);

            poFeature->SetField(FIELD_NAME_TEXT, sTextValue);
            poFeature->SetGeometryDirectly(poPoint);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADMText");

            sStyle.Printf("LABEL(f:\"Arial\",t:\"%s\",c:%s)",
                          sTextValue.c_str(), sHexColor.c_str());
            poFeature->SetStyleString(sStyle);
            break;
        }

        case CADGeometry::SPLINE:
        {
            CADSpline *const poCADSpline = (CADSpline *)poCADGeometry;
            OGRLineString *poLS = new OGRLineString();

            // TODO: Interpolate spline as points or curves
            for (size_t i = 0; i < poCADSpline->getControlPoints().size(); ++i)
            {
                poLS->addPoint(poCADSpline->getControlPoints()[i].getX(),
                               poCADSpline->getControlPoints()[i].getY(),
                               poCADSpline->getControlPoints()[i].getZ());
            }

            poFeature->SetGeometryDirectly(poLS);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADSpline");
            break;
        }

        case CADGeometry::ELLIPSE:
        {
            CADEllipse *poCADEllipse = static_cast<CADEllipse *>(poCADGeometry);

            // FIXME: Start/end angles should be swapped to work exactly as DXF
            // driver. is it correct?
            double dfStartAngle = poCADEllipse->getStartingAngle() * RAD2DEG;
            double dfEndAngle = poCADEllipse->getEndingAngle() * RAD2DEG;
            if (dfStartAngle > dfEndAngle)
            {
                dfEndAngle += 360.0;
            }
            double dfAxisRatio = poCADEllipse->getAxisRatio();

            CADVector stEllipseCenter = poCADEllipse->getPosition();
            CADVector vectSMAxis = poCADEllipse->getSMAxis();
            double dfPrimaryRadius, dfSecondaryRadius;
            double dfRotation;
            dfPrimaryRadius = sqrt(vectSMAxis.getX() * vectSMAxis.getX() +
                                   vectSMAxis.getY() * vectSMAxis.getY() +
                                   vectSMAxis.getZ() * vectSMAxis.getZ());

            dfSecondaryRadius = dfAxisRatio * dfPrimaryRadius;

            dfRotation =
                -1 * atan2(vectSMAxis.getY(), vectSMAxis.getX()) * RAD2DEG;
            /* NOTE: alternative way:
            OGRCircularString * poEllipse = new OGRCircularString();
            OGRPoint  oEllipsePoint1;
            oEllipsePoint1.setX( stEllipseCenter.getX() - dfPrimaryRadius *
                                                            cos( dfRotation ) );
            oEllipsePoint1.setY( stEllipseCenter.getY() + dfPrimaryRadius *
                                                            sin( dfRotation ) );
            oEllipsePoint1.setZ( stEllipseCenter.getZ() );
            poEllipse->addPoint( &oEllipsePoint1 );

            OGRPoint  oEllipsePoint2;
            oEllipsePoint2.setX( stEllipseCenter.getX() + dfSecondaryRadius *
                                                            cos( dfRotation ) );
            oEllipsePoint2.setY( stEllipseCenter.getY() + dfSecondaryRadius *
                                                            sin( dfRotation ) );
            oEllipsePoint2.setZ( stEllipseCenter.getZ() );
            poEllipse->addPoint( &oEllipsePoint2 );

            OGRPoint  oEllipsePoint3;
            oEllipsePoint3.setX( stEllipseCenter.getX() + dfPrimaryRadius *
                                                            cos( dfRotation ) );
            oEllipsePoint3.setY( stEllipseCenter.getY() - dfPrimaryRadius *
                                                            sin( dfRotation ) );
            oEllipsePoint3.setZ( stEllipseCenter.getZ() );
            poEllipse->addPoint( &oEllipsePoint3 );

            OGRPoint  oEllipsePoint4;
            oEllipsePoint4.setX( stEllipseCenter.getX() - dfSecondaryRadius *
                                                            cos( dfRotation ) );
            oEllipsePoint4.setY( stEllipseCenter.getY() - dfSecondaryRadius *
                                                            sin( dfRotation ) );
            oEllipsePoint4.setZ( stEllipseCenter.getZ() );
            poEllipse->addPoint( &oEllipsePoint4 );

            // Close the ellipse
            poEllipse->addPoint( &oEllipsePoint1 );
            */

            CPLDebug("CAD",
                     "Position: %f, %f, %f, radius %f/%f, start angle: %f, end "
                     "angle: %f, rotation: %f",
                     stEllipseCenter.getX(), stEllipseCenter.getY(),
                     stEllipseCenter.getZ(), dfPrimaryRadius, dfSecondaryRadius,
                     dfStartAngle, dfEndAngle, dfRotation);
            OGRGeometry *poEllipse = OGRGeometryFactory::approximateArcAngles(
                stEllipseCenter.getX(), stEllipseCenter.getY(),
                stEllipseCenter.getZ(), dfPrimaryRadius, dfSecondaryRadius,
                dfRotation, dfStartAngle, dfEndAngle, 0.0);

            poFeature->SetGeometryDirectly(poEllipse);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADEllipse");
            break;
        }

        case CADGeometry::ATTDEF:
        {
            CADAttdef *const poCADAttdef = (CADAttdef *)poCADGeometry;
            OGRPoint *poPoint = new OGRPoint(poCADAttdef->getPosition().getX(),
                                             poCADAttdef->getPosition().getY(),
                                             poCADAttdef->getPosition().getZ());
            CPLString sTextValue =
                CADRecode(poCADAttdef->getTag(), nDWGEncoding);

            poFeature->SetField(FIELD_NAME_TEXT, sTextValue);
            poFeature->SetGeometryDirectly(poPoint);
            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADAttdef");

            sStyle.Printf("LABEL(f:\"Arial\",t:\"%s\",c:%s)",
                          sTextValue.c_str(), sHexColor.c_str());
            poFeature->SetStyleString(sStyle);
            break;
        }

        default:
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "Unhandled feature. Skipping it.");

            poFeature->SetField(FIELD_NAME_GEOMTYPE, "CADUnknown");
            delete poCADGeometry;
            return poFeature;
        }
    }

    delete poCADGeometry;
    poFeature->GetGeometryRef()->assignSpatialReference(poSpatialRef);
    return poFeature;
}
