/******************************************************************************
 *
 * Project:  GDAL/OGR Geography Network support (Geographic Network Model)
 * Purpose:  GNM db based generic driver.
 * Authors:  Mikhail Gusev (gusevmihs at gmail dot com)
 *           Dmitry Baryshnikov, polimax@mail.ru
 *
 ******************************************************************************
 * Copyright (c) 2014, Mikhail Gusev
 * Copyright (c) 2014-2015, 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 "gnmdb.h"
#include "gnm_priv.h"

CPL_CVSID("$Id$")

GNMDatabaseNetwork::GNMDatabaseNetwork() : GNMGenericNetwork()
{
    m_poDS = nullptr;
}

GNMDatabaseNetwork::~GNMDatabaseNetwork()
{
    FlushCache(true);

    GDALClose(m_poDS);
}

CPLErr GNMDatabaseNetwork::Open(GDALOpenInfo *poOpenInfo)
{
    FormName(poOpenInfo->pszFilename, poOpenInfo->papszOpenOptions);

    if(CSLFindName(poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES") == -1)
        poOpenInfo->papszOpenOptions = CSLAddNameValue(
                    poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "YES");

    m_poDS = (GDALDataset*) GDALOpenEx( m_soNetworkFullName, GDAL_OF_VECTOR |
                     GDAL_OF_UPDATE, nullptr, nullptr, poOpenInfo->papszOpenOptions );

    if( nullptr == m_poDS )
    {
    CPLError( CE_Failure, CPLE_OpenFailed, "Open '%s' failed",
              m_soNetworkFullName.c_str() );
    return CE_Failure;
    }

    // There should be only one schema so no schema name can be in table name
    if(LoadMetadataLayer(m_poDS) != CE_None)
    {
        return CE_Failure;
    }

    if(LoadGraphLayer(m_poDS) != CE_None)
    {
        return CE_Failure;
    }

    if(LoadFeaturesLayer(m_poDS) != CE_None)
    {
        return CE_Failure;
    }

    return CE_None;
}

CPLErr GNMDatabaseNetwork::Create( const char* pszFilename, char** papszOptions )
{
    FormName(pszFilename, papszOptions);

    if(m_soName.empty() || m_soNetworkFullName.empty())
    {
        CPLError( CE_Failure, CPLE_IllegalArg,
                  "The network name should be present" );
        return CE_Failure;
    }

    if(nullptr == m_poDS)
    {
        m_poDS = (GDALDataset*) GDALOpenEx( m_soNetworkFullName, GDAL_OF_VECTOR |
                                       GDAL_OF_UPDATE, nullptr, nullptr, papszOptions );
    }

    if( nullptr == m_poDS )
    {
        CPLError( CE_Failure, CPLE_OpenFailed, "Open '%s' failed",
                  m_soNetworkFullName.c_str() );
        return CE_Failure;
    }

    GDALDriver *l_poDriver = m_poDS->GetDriver();
    if(nullptr == l_poDriver)
    {
        CPLError( CE_Failure, CPLE_OpenFailed, "Get dataset driver failed");
        return CE_Failure;
    }

    if(!CheckStorageDriverSupport(l_poDriver->GetDescription()))
    {
        return CE_Failure;
    }

    // check required options

    const char* pszNetworkDescription = CSLFetchNameValue(papszOptions,
                                                         GNM_MD_DESCR);
    if(nullptr != pszNetworkDescription)
        sDescription = pszNetworkDescription;

    // check Spatial reference
    const char* pszSRS = CSLFetchNameValue(papszOptions, GNM_MD_SRS);
    if( nullptr == pszSRS )
    {
        CPLError( CE_Failure, CPLE_IllegalArg,
                  "The network spatial reference should be present" );
        return CE_Failure;
    }
    else
    {
        OGRSpatialReference spatialRef;
        if (spatialRef.SetFromUserInput(pszSRS) != OGRERR_NONE)
        {
            CPLError( CE_Failure, CPLE_IllegalArg,
                      "The network spatial reference should be present" );
            return CE_Failure;
        }

        char *wktSrs = nullptr;
        if (spatialRef.exportToWkt(&wktSrs) != OGRERR_NONE)
        {
            CPLError( CE_Failure, CPLE_IllegalArg,
                      "The network spatial reference should be present" );
            CPLFree(wktSrs);
            return CE_Failure;
        }
        m_soSRS = wktSrs;

        CPLFree(wktSrs);
    }

    int nResult = CheckNetworkExist(pszFilename, papszOptions);

    if(TRUE == nResult)
    {
        CPLError( CE_Failure, CPLE_IllegalArg, "The network already exist" );
        return CE_Failure;
    }

    // Create the necessary system layers and fields

    // Create meta layer

    CPLErr eResult = CreateMetadataLayer(m_poDS, GNM_VERSION_NUM);

    if(CE_None != eResult)
    {
        //an error message should come from function
        return CE_Failure;    }

    // Create graph layer

    eResult = CreateGraphLayer(m_poDS);

    if(CE_None != eResult)
    {
        DeleteMetadataLayer();
        return CE_Failure;
    }

    // Create features layer

    eResult = CreateFeaturesLayer(m_poDS);

    if(CE_None != eResult)
    {
        DeleteMetadataLayer();
        DeleteGraphLayer();
        return CE_Failure;
    }

    return CE_None;
}

int GNMDatabaseNetwork::CheckNetworkExist(const char *pszFilename, char **papszOptions)
{
    // check if path exist
    // if path exist check if network already present and OVERWRITE option
    // else create the path

    if(FormName(pszFilename, papszOptions) != CE_None)
    {
        return TRUE;
    }

    if(nullptr == m_poDS)
    {
        m_poDS = (GDALDataset*) GDALOpenEx( m_soNetworkFullName, GDAL_OF_VECTOR |
                                      GDAL_OF_UPDATE, nullptr, nullptr, papszOptions );
    }

    const bool bOverwrite = CPLFetchBool(papszOptions, "OVERWRITE", false);

    std::vector<int> anDeleteLayers;
    int i;
    for(i = 0; i < m_poDS->GetLayerCount(); ++i)
    {
        OGRLayer* poLayer = m_poDS->GetLayer(i);
        if(nullptr == poLayer)
            continue;

        if(EQUAL(poLayer->GetName(), GNM_SYSLAYER_META) ||
           EQUAL(poLayer->GetName(), GNM_SYSLAYER_GRAPH) ||
           EQUAL(poLayer->GetName(), GNM_SYSLAYER_FEATURES) )
        {
            anDeleteLayers.push_back(i);
        }
    }

    if(anDeleteLayers.empty())
        return FALSE;

    if( bOverwrite )
    {
        for(i = (int)anDeleteLayers.size(); i > 0; i--)
        {
            CPLDebug("GNM", "Delete layer: %d", i);
            if(m_poDS->DeleteLayer(anDeleteLayers[i - 1]) != OGRERR_NONE)
                return TRUE;
        }
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

CPLErr GNMDatabaseNetwork::DeleteMetadataLayer()
{
    return DeleteLayerByName(GNM_SYSLAYER_META);
}

CPLErr GNMDatabaseNetwork::DeleteGraphLayer()
{
    return DeleteLayerByName(GNM_SYSLAYER_GRAPH);
}

CPLErr GNMDatabaseNetwork::DeleteFeaturesLayer()
{
    return DeleteLayerByName(GNM_SYSLAYER_FEATURES);
}

CPLErr GNMDatabaseNetwork::DeleteLayerByName(const char* pszLayerName)
{
    if(nullptr == m_poDS)
        return CE_Failure;

    for(int i = 0; i < m_poDS->GetLayerCount(); ++i)
    {
        OGRLayer* poLayer = m_poDS->GetLayer(i);
        if(nullptr == poLayer)
            continue;

        if(EQUAL(poLayer->GetName(), pszLayerName))
            return m_poDS->DeleteLayer(i) == OGRERR_NONE ? CE_None : CE_Failure;
    }

    CPLError(CE_Failure, CPLE_IllegalArg, "The layer %s not exist",
             pszLayerName );
    return CE_Failure;
}

CPLErr GNMDatabaseNetwork::DeleteNetworkLayers()
{
    while(GetLayerCount() > 0)
    {
        OGRErr eErr = DeleteLayer(0);
        if(eErr != OGRERR_NONE)
            return CE_Failure;
    }
    return CE_None;
}

CPLErr GNMDatabaseNetwork::LoadNetworkLayer(const char *pszLayername)
{
    // check if not loaded
    for(size_t i = 0; i < m_apoLayers.size(); ++i)
    {
        if(EQUAL(m_apoLayers[i]->GetName(), pszLayername))
            return CE_None;
    }

    OGRLayer* poLayer = m_poDS->GetLayerByName(pszLayername);
    if(nullptr == poLayer)
    {
        CPLError( CE_Failure, CPLE_OpenFailed, "Layer '%s' is not exist",
                  pszLayername );
        return CE_Failure;
    }

    CPLDebug("GNM", "Layer '%s' loaded", poLayer->GetName());

    GNMGenericLayer* pGNMLayer = new GNMGenericLayer(poLayer, this);
    m_apoLayers.push_back(pGNMLayer);

    return CE_None;
}

bool GNMDatabaseNetwork::CheckStorageDriverSupport(const char *pszDriverName)
{
    if(EQUAL(pszDriverName, "PostgreSQL"))
        return true;
    //TODO: expand this list with supported OGR direvers
    return false;
}

CPLErr GNMDatabaseNetwork::FormName(const char *pszFilename, char **papszOptions)
{
    if(m_soNetworkFullName.empty())
        m_soNetworkFullName = pszFilename;

    if(m_soName.empty())
    {
        const char* pszNetworkName = CSLFetchNameValue(papszOptions, GNM_MD_NAME);
        if(nullptr != pszNetworkName)
        {
            m_soName = pszNetworkName;
        }

        char *pszActiveSchemaStart;
        pszActiveSchemaStart = (char *)strstr(pszFilename, "active_schema=");
        if (pszActiveSchemaStart == nullptr)
            pszActiveSchemaStart = (char *)strstr(pszFilename, "ACTIVE_SCHEMA=");
        if (pszActiveSchemaStart != nullptr)
        {
            char           *pszActiveSchema;

            pszActiveSchema = CPLStrdup( pszActiveSchemaStart + strlen("active_schema=") );

            const char* pszEnd = strchr(pszActiveSchemaStart, ' ');
            if( pszEnd == nullptr )
                pszEnd = pszFilename + strlen(pszFilename);

            pszActiveSchema[pszEnd - pszActiveSchemaStart - strlen("active_schema=")] = '\0';

            m_soName = pszActiveSchema;
            CPLFree(pszActiveSchema);
        }
        else
        {
            if(!m_soName.empty())
            {
                //add active schema
                m_soNetworkFullName += "ACTIVE_SCHEMA=" + m_soName;
            }
            else
            {
                m_soName = "public";
            }
        }

        CPLDebug( "GNM", "Network name: %s", m_soName.c_str() );
    }
    return CE_None;
}

OGRErr GNMDatabaseNetwork::DeleteLayer(int nIndex)
{
    if(nullptr == m_poDS)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Network not opened." );
        return OGRERR_FAILURE;
    }

    OGRLayer* poNetworkLayer = GetLayer(nIndex);

    CPLDebug("GNM", "Delete network layer '%s'", poNetworkLayer->GetName());

    int nDeleteIndex = -1;
    for(int i = 0; i < m_poDS->GetLayerCount(); ++i)
    {
        OGRLayer* poLayer = m_poDS->GetLayer(i);
        if(EQUAL(poNetworkLayer->GetName(), poLayer->GetName()))
        {
            nDeleteIndex = i;
            break;
        }
    }

    if(m_poDS->DeleteLayer(nDeleteIndex) != OGRERR_NONE)
    {
        return OGRERR_FAILURE;
    }

    return GNMGenericNetwork::DeleteLayer(nIndex);
}

OGRLayer *GNMDatabaseNetwork::ICreateLayer(const char *pszName,
                                OGRSpatialReference * /*poSpatialRef*/,
                                OGRwkbGeometryType eGType, char **papszOptions)
{
    //check if layer with such name exist
    for(int i = 0; i < GetLayerCount(); ++i)
    {
        OGRLayer* pLayer = GetLayer(i);
        if(nullptr == pLayer)
            continue;
        if(EQUAL(pLayer->GetName(), pszName))
        {
            CPLError( CE_Failure, CPLE_IllegalArg,
                      "The network layer '%s' already exist.", pszName );
            return nullptr;
        }
    }

    OGRSpatialReference oSpaRef(m_soSRS);

    OGRLayer *poLayer = m_poDS->CreateLayer( pszName, &oSpaRef, eGType, papszOptions );
    if( poLayer == nullptr )
    {
        CPLError( CE_Failure, CPLE_FileIO, "Layer creation failed." );
        return nullptr;
    }

    OGRFieldDefn oField( GNM_SYSFIELD_GFID, GNMGFIDInt );
    if( poLayer->CreateField( &oField ) != OGRERR_NONE )
    {
        CPLError( CE_Failure, CPLE_FileIO, "Creating global identificator field failed." );
        return nullptr;
    }

    OGRFieldDefn oFieldBlock(GNM_SYSFIELD_BLOCKED, OFTInteger);
    if( poLayer->CreateField( &oFieldBlock ) != OGRERR_NONE )
    {
        CPLError( CE_Failure, CPLE_FileIO, "Creating is blocking field failed." );
        return nullptr;
    }

    GNMGenericLayer* pGNMLayer = new GNMGenericLayer(poLayer, this);
    m_apoLayers.push_back(pGNMLayer);
    return pGNMLayer;
}
