/******************************************************************************
 *
 * Project:  VFK Reader - Feature definition
 * Purpose:  Implements IVFKFeature/VFKFeature class.
 * Author:   Martin Landa, landa.martin gmail.com
 *
 ******************************************************************************
 * Copyright (c) 2009-2010, 2012-2015, Martin Landa <landa.martin gmail.com>
 * Copyright (c) 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 "vfkreader.h"
#include "vfkreaderp.h"

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

/*!
  \brief IVFKFeature constructor

  \param poDataBlock pointer to VFKDataBlock instance
*/
IVFKFeature::IVFKFeature(IVFKDataBlock *poDataBlock)
    : m_poDataBlock(poDataBlock), m_nFID(-1),
      m_nGeometryType(poDataBlock->GetGeometryType()), m_bGeometry(false),
      m_bValid(false), m_paGeom(nullptr)
{
    CPLAssert(nullptr != poDataBlock);
}

/*!
  \brief IVFKFeature destructor
*/
IVFKFeature::~IVFKFeature()
{
    if (m_paGeom)
        delete m_paGeom;

    m_poDataBlock = nullptr;
}

/*!
  \brief Set feature geometry type
*/
void IVFKFeature::SetGeometryType(OGRwkbGeometryType nGeomType)
{
    m_nGeometryType = nGeomType;
}

/*!
  \brief Set feature id

  FID: 0 for next, -1 for same

  \param nFID feature id
*/
void IVFKFeature::SetFID(GIntBig nFID)
{
    if (m_nFID > 0)
    {
        m_nFID = nFID;
    }
    else
    {
        m_nFID = m_poDataBlock->GetFeatureCount() + 1;
    }
}

/*!
  \brief Set feature geometry

  Also checks if given geometry is valid

  \param poGeom pointer to OGRGeometry
  \param ftype geometry VFK type

  \return true on valid feature or otherwise false
*/
bool IVFKFeature::SetGeometry(OGRGeometry *poGeom, const char *ftype)
{
    m_bGeometry = true;

    delete m_paGeom;
    m_paGeom = nullptr;
    m_bValid = true;

    if (!poGeom)
    {
        return m_bValid;
    }

    /* check empty geometries */
    if (m_nGeometryType == wkbNone && poGeom->IsEmpty())
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "%s: empty geometry fid = " CPL_FRMT_GIB,
                 m_poDataBlock->GetName(), m_nFID);
        m_bValid = false;
    }

    /* check coordinates */
    if (m_nGeometryType == wkbPoint)
    {
        auto poPoint = poGeom->toPoint();
        const double x = poPoint->getX();
        const double y = poPoint->getY();
        if (x > -430000 || x < -910000 || y > -930000 || y < -1230000)
        {
            CPLDebug("OGR-VFK", "%s: invalid point fid = " CPL_FRMT_GIB,
                     m_poDataBlock->GetName(), m_nFID);
            m_bValid = false;
        }
    }

    /* check degenerated polygons */
    if (m_nGeometryType == wkbPolygon)
    {
        OGRLinearRing *poRing = poGeom->toPolygon()->getExteriorRing();
        if (!poRing || poRing->getNumPoints() < 3)
        {
            CPLDebug("OGR-VFK", "%s: invalid polygon fid = " CPL_FRMT_GIB,
                     m_poDataBlock->GetName(), m_nFID);
            m_bValid = false;
        }
    }

    if (m_bValid)
    {
        if (ftype)
        {
            OGRPoint pt;
            OGRCircularString poGeomString;

            OGRGeometry *poGeomCurved = nullptr;
            if (EQUAL(ftype, "15") || EQUAL(ftype, "16"))
            { /* -> circle or arc */
                auto poLS = poGeom->toLineString();
                const int npoints = poLS->getNumPoints();
                for (int i = 0; i < npoints; i++)
                {
                    poLS->getPoint(i, &pt);
                    poGeomString.addPoint(&pt);
                }
                if (EQUAL(ftype, "15"))
                {
                    if (npoints < 3)
                    {
                        CPLError(CE_Warning, CPLE_AppDefined,
                                 "npoints is %d.  expected 3", npoints);
                    }
                    if (npoints > 3)
                    {
                        CPLError(CE_Warning, CPLE_AppDefined,
                                 "npoints is %d.  Will overflow buffers.  "
                                 "Cannot continue.",
                                 npoints);
                        m_bValid = false;
                        return false;
                    }

                    /* compute center and radius of a circle */
                    double x[3] = {0.0, 0.0, 0.0};
                    double y[3] = {0.0, 0.0, 0.0};

                    for (int i = 0; i < npoints; i++)
                    {
                        poLS->getPoint(i, &pt);
                        x[i] = pt.getX();
                        y[i] = pt.getY();
                    }

                    const double m1 = (x[0] + x[1]) / 2.0;
                    const double n1 = (y[0] + y[1]) / 2.0;

                    const double m2 = (x[0] + x[2]) / 2.0;
                    const double n2 = (y[0] + y[2]) / 2.0;

                    const double c1 = (x[1] - x[0]) * m1 + (y[1] - y[0]) * n1;
                    const double c2 = (x[2] - x[0]) * m2 + (y[2] - y[0]) * n2;

                    const double mx = (x[1] - x[0]) * (y[2] - y[0]) +
                                      (y[1] - y[0]) * (x[0] - x[2]);

                    const double c_x =
                        (c1 * (y[2] - y[0]) + c2 * (y[0] - y[1])) / mx;
                    const double c_y =
                        (c1 * (x[0] - x[2]) + c2 * (x[1] - x[0])) / mx;

                    /* compute a new intermediate point */
                    pt.setX(c_x - (x[1] - c_x));
                    pt.setY(c_y - (y[1] - c_y));
                    poGeomString.addPoint(&pt);

                    /* add last point */
                    poLS->getPoint(0, &pt);
                    poGeomString.addPoint(&pt);
                }
            }
            else if (strlen(ftype) > 2 && STARTS_WITH_CI(ftype, "15"))
            {                   /* -> circle with radius */
                char s[3] = {}; /* 15 */

                float r = 0.0f;
                if (2 != sscanf(ftype, "%2s %f", s, &r) || r < 0)
                {
                    CPLDebug("OGR-VFK",
                             "%s: invalid circle (unknown or negative radius) "
                             "fid = " CPL_FRMT_GIB,
                             m_poDataBlock->GetName(), m_nFID);
                    m_bValid = false;
                }
                else
                {
                    auto poLS = poGeom->toLineString();
                    poLS->getPoint(0, &pt);
                    const double c_x = pt.getX();
                    const double c_y = pt.getY();

                    /* define first point on a circle */
                    pt.setX(c_x + r);
                    pt.setY(c_y);
                    poGeomString.addPoint(&pt);

                    /* define second point on a circle */
                    pt.setX(c_x);
                    pt.setY(c_y + r);
                    poGeomString.addPoint(&pt);

                    /* define third point on a circle */
                    pt.setX(c_x - r);
                    pt.setY(c_y);
                    poGeomString.addPoint(&pt);

                    /* define fourth point on a circle */
                    pt.setX(c_x);
                    pt.setY(c_y - r);
                    poGeomString.addPoint(&pt);

                    /* define last point (=first) on a circle */
                    pt.setX(c_x + r);
                    pt.setY(c_y);
                    poGeomString.addPoint(&pt);
                }
            }
            else if (EQUAL(ftype, "11"))
            { /* curve */
                auto poLS = poGeom->toLineString();
                const int npoints = poLS->getNumPoints();
                if (npoints > 2)
                { /* circular otherwise line string */
                    for (int i = 0; i < npoints; i++)
                    {
                        poLS->getPoint(i, &pt);
                        poGeomString.addPoint(&pt);
                    }
                }
            }

            if (!poGeomString.IsEmpty())
                poGeomCurved = poGeomString.CurveToLine();

            if (poGeomCurved)
            {
                const int npoints =
                    poGeomCurved->toLineString()->getNumPoints();
                CPLDebug("OGR-VFK",
                         "%s: curve (type=%s) to linestring (npoints=%d) fid "
                         "= " CPL_FRMT_GIB,
                         m_poDataBlock->GetName(), ftype, npoints, m_nFID);
                if (npoints > 1)
                    m_paGeom = poGeomCurved->clone();
                delete poGeomCurved;
            }
        }

        if (!m_paGeom)
        {
            /* check degenerated linestrings */
            if (m_nGeometryType == wkbLineString)
            {
                auto poLS = poGeom->toLineString();
                const int npoints = poLS->getNumPoints();
                if (npoints < 2)
                {
                    CPLError(CE_Warning, CPLE_AppDefined,
                             "%s: invalid linestring (%d vertices) fid "
                             "= " CPL_FRMT_GIB,
                             m_poDataBlock->GetName(), npoints, m_nFID);
                    m_bValid = false;
                }
            }

            if (m_bValid)
                m_paGeom = poGeom->clone(); /* make copy */
        }
    }

    return m_bValid;
}

/*!
  \brief Get feature geometry

  \return pointer to OGRGeometry or NULL on error
*/
OGRGeometry *IVFKFeature::GetGeometry()
{
    if (m_nGeometryType != wkbNone && !m_bGeometry)
        LoadGeometry();

    return m_paGeom;
}

/*!
  \brief Load geometry

  \return true on success or false on failure
*/
bool IVFKFeature::LoadGeometry()
{
    if (m_bGeometry)
        return true;

    const char *pszName = m_poDataBlock->GetName();

    if (EQUAL(pszName, "SOBR") || EQUAL(pszName, "OBBP") ||
        EQUAL(pszName, "SPOL") || EQUAL(pszName, "OB") ||
        EQUAL(pszName, "OP") || EQUAL(pszName, "OBPEJ"))
    {
        /* -> wkbPoint */

        return LoadGeometryPoint();
    }
    else if (EQUAL(pszName, "SBP") || EQUAL(pszName, "SBPG"))
    {
        /* -> wkbLineString */
        return LoadGeometryLineStringSBP();
    }
    else if (EQUAL(pszName, "HP") || EQUAL(pszName, "DPM") ||
             EQUAL(pszName, "ZVB"))
    {
        /* -> wkbLineString */
        return LoadGeometryLineStringHP();
    }
    else if (EQUAL(pszName, "PAR") || EQUAL(pszName, "BUD"))
    {
        /* -> wkbPolygon */
        return LoadGeometryPolygon();
    }

    return false;
}

/*!
  \brief VFKFeature constructor

  \param poDataBlock pointer to VFKDataBlock instance
*/
VFKFeature::VFKFeature(IVFKDataBlock *poDataBlock, GIntBig iFID)
    : IVFKFeature(poDataBlock)
{
    m_nFID = iFID;
    m_propertyList.assign(poDataBlock->GetPropertyCount(), VFKProperty());
    CPLAssert(size_t(poDataBlock->GetPropertyCount()) == m_propertyList.size());
}

/*!
  \brief Set feature properties

  \param pszLine pointer to line containing feature definition

  \return true on success or false on failure
*/
bool VFKFeature::SetProperties(const char *pszLine)
{
    const char *poChar = pszLine;  // Used after for.
    for (; *poChar != '\0' && *poChar != ';'; poChar++)
        /* skip data block name */
        ;
    if (*poChar == '\0')
        return false; /* nothing to read */

    poChar++; /* skip ';' after data block name */

    /* remove extra quotes (otherwise due to buggy format the parsing is
     * almost impossible) */
    CPLString osLine;
    while (*poChar != '\0')
    {
        if (*poChar == '"')
        {
            /* count quotes */
            int nQuotes = 1;
            while (*(++poChar) == '"')
                nQuotes++;

            if (nQuotes % 2 != 0)
            {
                /* even number of quotes -> only last quote used */
                poChar -= 1;
            }
            else
            {
                if ((*poChar == ';' || *poChar == '\0') &&
                    *(poChar - nQuotes - 1) == ';')
                {
                    /* empty values (;""; / ;"" / ;""""; / ...)
                       -> only last two quotes used */
                    poChar -= 2;
                }
                else if (*poChar == '\0')
                    break;
                /* odd number of quotes -> none of quotes used */
            }
        }
        osLine += *(poChar++);
    }
    poChar = osLine;

    /* read properties into the list */
    const char *poProp = poChar;
    unsigned int iIndex = 0;
    unsigned int nLength = 0;
    unsigned int nQuotes = 0;
    bool inString = false;
    char *pszProp = nullptr;
    std::vector<CPLString> oPropList;
    while (*poChar != '\0')
    {
        if ((!inString && *poChar == '"') ||               /* begin of string */
            (inString && *poChar == '"' && nQuotes == 1 && /* end of string */
             (*(poChar + 1) == ';' || *(poChar + 1) == '\0')))
        {

            poChar++; /* skip '"' */
            inString = !inString;
            if (inString)
            {
                nQuotes = 1;
                poProp = poChar;
                if (*poChar == '"' &&
                    (*(poChar + 1) == ';' || *(poChar + 1) == '\0'))
                {
                    /* process empty string */
                    poChar++;
                    inString = false;
                }
                else
                {
                    /* count number of starting quotes */
                    while (*poChar == '"')
                    {
                        nQuotes++;
                        nLength++;
                        poChar++;
                    }
                }
            }
            if (*poChar == '\0')
            {
                /* end of line */
                break;
            }
        }
        if (*poChar == ';' && !inString)
        {
            /* end of property */
            pszProp = (char *)CPLRealloc(pszProp, nLength + 1);
            if (nLength > 0)
                strncpy(pszProp, poProp, nLength);
            pszProp[nLength] = '\0';
            /* add new property into the list */
            oPropList.push_back(pszProp);
            /* prepare for next property */
            iIndex++;
            poProp = ++poChar;
            nLength = 0;
            nQuotes = 0;
        }
        else
        {
            if (*poChar == '"' && nQuotes > 1)
                nQuotes--;

            /* add character to property */
            poChar++;
            nLength++;
        }
    }
    /* append last property */
    if (inString && nLength > 0)
    {
        nLength--; /* ignore '"' */
    }
    pszProp = (char *)CPLRealloc(pszProp, nLength + 1);
    if (nLength > 0)
        strncpy(pszProp, poProp, nLength);
    pszProp[nLength] = '\0';
    oPropList.push_back(pszProp);

    /* set properties from the list */
    if (oPropList.size() != (size_t)m_poDataBlock->GetPropertyCount())
    {
        /* try to read also invalid records */
        CPLError(CE_Warning, CPLE_AppDefined,
                 "%s: invalid number of properties %d should be %d\n%s",
                 m_poDataBlock->GetName(), (int)oPropList.size(),
                 m_poDataBlock->GetPropertyCount(), pszLine);
        CPLFree(pszProp);
        return false;
    }
    iIndex = 0;
    for (std::vector<CPLString>::iterator ip = oPropList.begin();
         ip != oPropList.end(); ++ip)
    {
        SetProperty(iIndex++, (*ip).c_str());
    }

    // TODO(martinl): Why was this block disabled?
    /* set fid
    if (EQUAL(m_poDataBlock->GetName(), "SBP")) {
        const VFKProperty *poVfkProperty = GetProperty("PORADOVE_CISLO_BODU");
        if (poVfkProperty)
        {
            GUIntBig id = strtoul(poVfkProperty->GetValueS(), NULL, 0);
            if (id == 1)
                SetFID(0);
            else
                SetFID(-1);
        }
    }
    else {
        SetFID(0);
    }
    */
    CPLFree(pszProp);

    return true;
}

/*!
  \brief Set feature property

  \param iIndex property index
  \param pszValue property value

  \return true on success, false on failure
*/
bool VFKFeature::SetProperty(int iIndex, const char *pszValue)
{
    if (iIndex < 0 || iIndex >= m_poDataBlock->GetPropertyCount() ||
        size_t(iIndex) >= m_propertyList.size())
        return false;

    if (strlen(pszValue) < 1)
    {
        m_propertyList[iIndex] = VFKProperty();
        return true;
    }

    const OGRFieldType fType = m_poDataBlock->GetProperty(iIndex)->GetType();

    switch (fType)
    {
        case OFTInteger:
        case OFTInteger64:
        {
            errno = 0;
            int pbOverflow = 0;
            char *pszLast = nullptr;
            if (fType == OFTInteger)
                m_propertyList[iIndex] = VFKProperty(
                    static_cast<int>(strtol(pszValue, &pszLast, 10)));
            else /* OFTInteger64 */
                m_propertyList[iIndex] =
                    VFKProperty(CPLAtoGIntBigEx(pszValue, true, &pbOverflow));

            if ((fType == OFTInteger &&
                 (errno == ERANGE || !pszLast || *pszLast)) ||
                CPLGetValueType(pszValue) != CPL_VALUE_INTEGER || pbOverflow)
                CPLError(
                    CE_Warning, CPLE_AppDefined,
                    "Value '%s' parsed incompletely to integer " CPL_FRMT_GIB
                    ".",
                    pszValue,
                    (fType == OFTInteger)
                        ? m_propertyList[iIndex].GetValueI()
                        : m_propertyList[iIndex].GetValueI64());
            break;
        }
        case OFTReal:
            m_propertyList[iIndex] = VFKProperty(CPLAtof(pszValue));
            break;
        default:
            const char *pszEncoding =
                m_poDataBlock->GetProperty(iIndex)->GetEncoding();
            if (pszEncoding)
            {
                char *pszValueEnc =
                    CPLRecode(pszValue, pszEncoding, CPL_ENC_UTF8);
                m_propertyList[iIndex] = VFKProperty(pszValueEnc);
                CPLFree(pszValueEnc);
            }
            else
            {
                m_propertyList[iIndex] = VFKProperty(pszValue);
            }
            break;
    }

    return true;
}

/*!
  \brief Get property value by index

  \param iIndex property index

  \return property value, NULL on error
*/
const VFKProperty *VFKFeature::GetProperty(int iIndex) const
{
    if (iIndex < 0 || iIndex >= m_poDataBlock->GetPropertyCount() ||
        size_t(iIndex) >= m_propertyList.size())
        return nullptr;

    const VFKProperty *poProperty = &m_propertyList[iIndex];
    return poProperty;
}

/*!
  \brief Get property value by name

  \param pszName property name

  \return property value, NULL on error
*/
const VFKProperty *VFKFeature::GetProperty(const char *pszName) const
{
    return GetProperty(m_poDataBlock->GetPropertyIndex(pszName));
}

/*!
  \brief Load geometry (point layers)

  \todo Really needed?

  \return true on success, false on failure
*/
bool VFKFeature::LoadGeometryPoint()
{
    const int i_idxY = m_poDataBlock->GetPropertyIndex("SOURADNICE_Y");
    const int i_idxX = m_poDataBlock->GetPropertyIndex("SOURADNICE_X");
    if (i_idxY < 0 || i_idxX < 0)
        return false;

    auto propertyY = GetProperty(i_idxY);
    auto propertyX = GetProperty(i_idxX);
    if (!propertyY || !propertyX)
        return false;
    const double x = -1.0 * propertyY->GetValueD();
    const double y = -1.0 * propertyX->GetValueD();
    OGRPoint pt(x, y);
    SetGeometry(&pt);

    return true;
}

/*!
  \brief Load geometry (linestring SBP/SBPG layer)

  \todo Really needed?

  \return true on success or false on failure
*/
bool VFKFeature::LoadGeometryLineStringSBP()
{
    VFKDataBlock *poDataBlockPoints =
        (VFKDataBlock *)m_poDataBlock->GetReader()->GetDataBlock("SOBR");
    if (!poDataBlockPoints)
        return false;

    const int idxId = poDataBlockPoints->GetPropertyIndex("ID");
    const int idxBp_Id = m_poDataBlock->GetPropertyIndex("BP_ID");
    const int idxPCB = m_poDataBlock->GetPropertyIndex("PORADOVE_CISLO_BODU");
    if (idxId < 0 || idxBp_Id < 0 || idxPCB < 0)
        return false;

    VFKFeature *poLine = this;
    OGRLineString OGRLine;
    while (true)
    {
        auto property_idxBp_Id = poLine->GetProperty(idxBp_Id);
        if (!property_idxBp_Id)
            break;
        auto property_idxPCB = poLine->GetProperty(idxPCB);
        if (!property_idxPCB)
            break;

        const int id = property_idxBp_Id->GetValueI();
        const int ipcb = property_idxPCB->GetValueI();
        if (OGRLine.getNumPoints() > 0 && ipcb == 1)
        {
            m_poDataBlock->GetPreviousFeature(); /* push back */
            break;
        }

        VFKFeature *poPoint = poDataBlockPoints->GetFeature(idxId, id);
        if (!poPoint)
        {
            continue;
        }
        OGRPoint *pt = poPoint->GetGeometry()->toPoint();
        OGRLine.addPoint(pt);

        poLine = (VFKFeature *)m_poDataBlock->GetNextFeature();
        if (!poLine)
            break;
    };

    OGRLine.setCoordinateDimension(2); /* force 2D */
    SetGeometry(&OGRLine);

    /* reset reading */
    poDataBlockPoints->ResetReading();

    return true;
}

/*!
  \brief Load geometry (linestring HP/DPM layer)

  \todo Really needed?

  \return true on success or false on failure
*/
bool VFKFeature::LoadGeometryLineStringHP()
{
    VFKDataBlock *poDataBlockLines =
        (VFKDataBlock *)m_poDataBlock->GetReader()->GetDataBlock("SBP");
    if (!poDataBlockLines)
        return false;

    const int idxId = m_poDataBlock->GetPropertyIndex("ID");
    const int idxHp_Id = poDataBlockLines->GetPropertyIndex("HP_ID");
    if (idxId < 0 || idxHp_Id < 0)
        return false;

    auto property = GetProperty(idxId);
    if (!property)
        return false;
    const int id = property->GetValueI();
    VFKFeature *poLine = poDataBlockLines->GetFeature(idxHp_Id, id);
    if (!poLine || !poLine->GetGeometry())
        return false;

    SetGeometry(poLine->GetGeometry());
    poDataBlockLines->ResetReading();

    return true;
}

/*!
  \brief Load geometry (polygon BUD/PAR layers)

  \todo Implement (really needed?)

  \return true on success or false on failure
*/
bool VFKFeature::LoadGeometryPolygon()
{
    return false;
}

OGRErr VFKFeature::LoadProperties(OGRFeature *poFeature)
{
    for (int iField = 0; iField < m_poDataBlock->GetPropertyCount(); iField++)
    {
        auto property = GetProperty(iField);
        if (!property || property->IsNull())
            continue;

        OGRFieldType fType =
            poFeature->GetDefnRef()->GetFieldDefn(iField)->GetType();
        if (fType == OFTInteger)
            poFeature->SetField(iField, property->GetValueI());
        else if (fType == OFTReal)
            poFeature->SetField(iField, property->GetValueD());
        else
            poFeature->SetField(iField, property->GetValueS());
    }

    return OGRERR_NONE;
}
