/******************************************************************************
 *
 * Project:  OGR/DODS Interface
 * Purpose:  Implements OGRDODSDataSource class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2004, Frank Warmerdam
 * Copyright (c) 2010, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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 "ogr_dods.h"
#include "cpl_conv.h"
#include "cpl_string.h"

CPL_CVSID("$Id: ogrdodsdatasource.cpp 103562a4b99e01eef1f5cb350b2dccaa6fe01e81 2017-12-14 19:39:15Z Even Rouault $")
/************************************************************************/
/*                         OGRDODSDataSource()                          */
/************************************************************************/

OGRDODSDataSource::OGRDODSDataSource() :
    papoLayers(nullptr),
    nLayers(0),
    pszName(nullptr),
    poConnection(nullptr),
    poBTF(new BaseTypeFactory())
{
    // TODO: This implies that the order in the class declaration is wrong.
    poDDS = new DDS( poBTF );
}

/************************************************************************/
/*                         ~OGRDODSDataSource()                         */
/************************************************************************/

OGRDODSDataSource::~OGRDODSDataSource()

{
    CPLFree( pszName );

    for( int i = 0; i < nLayers; i++ )
        delete papoLayers[i];

    CPLFree( papoLayers );

    if( poConnection != nullptr )
        delete poConnection;

    delete poDDS;
    delete poBTF;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

int OGRDODSDataSource::Open( const char * pszNewName )

{
    CPLAssert( nLayers == 0 );

    pszName = CPLStrdup( pszNewName );

/* -------------------------------------------------------------------- */
/*      Parse the URL into a base url, projection and constraint        */
/*      expression.                                                     */
/* -------------------------------------------------------------------- */
    char *pszWrkURL = CPLStrdup( pszNewName + 5 );
    char *pszFound = strstr(pszWrkURL, "&");
    if( pszFound )
    {
        oConstraints = pszFound;
        *pszFound = '\0';
    }

    pszFound = strstr(pszWrkURL,"?");
    if( pszFound )
    {
        oProjection = pszFound+1;
        *pszFound = '\0';
    }

    // Trim common requests.
    int nLen = static_cast<int>(strlen(pszWrkURL));
    if( strcmp(pszWrkURL+nLen-4,".das") == 0 )
        pszWrkURL[nLen-4] = '\0';
    else if( strcmp(pszWrkURL+nLen-4,".dds") == 0 )
        pszWrkURL[nLen-4] = '\0';
    else if( strcmp(pszWrkURL+nLen-4,".asc") == 0 )
        pszWrkURL[nLen-4] = '\0';
    else if( strcmp(pszWrkURL+nLen-5,".dods") == 0 )
        pszWrkURL[nLen-5] = '\0';
    else if( strcmp(pszWrkURL+nLen-5,".html") == 0 )
        pszWrkURL[nLen-5] = '\0';

    oBaseURL = pszWrkURL;
    CPLFree( pszWrkURL );

/* -------------------------------------------------------------------- */
/*      Do we want to override the .dodsrc file setting?  Only do       */
/*      the putenv() if there isn't already a DODS_CONF in the          */
/*      environment.                                                    */
/* -------------------------------------------------------------------- */
    if( CPLGetConfigOption( "DODS_CONF", nullptr ) != nullptr
        && getenv("DODS_CONF") == nullptr )
    {
        const int knBufSize = 1000;
        static char szDODS_CONF[knBufSize];

        snprintf( szDODS_CONF, knBufSize, "DODS_CONF=%.980s",
                  CPLGetConfigOption( "DODS_CONF", "" ) );
        putenv( szDODS_CONF );
    }

/* -------------------------------------------------------------------- */
/*      If we have a overriding AIS file location, apply it now.       */
/* -------------------------------------------------------------------- */
    if( CPLGetConfigOption( "DODS_AIS_FILE", nullptr ) != nullptr )
    {
        string oAISFile = CPLGetConfigOption( "DODS_AIS_FILE", "" );
        RCReader::instance()->set_ais_database( oAISFile );
    }

/* -------------------------------------------------------------------- */
/*      Connect to the server.                                          */
/* -------------------------------------------------------------------- */
    string version;

    try
    {
        poConnection = new AISConnect( oBaseURL );
        version = poConnection->request_version();
    }
    catch (Error &e)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "%s", e.get_error_message().c_str() );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      We presume we only work with version 3 servers.                 */
/* -------------------------------------------------------------------- */

    if (version.empty() || version.find("/3.") == string::npos)
    {
        CPLError( CE_Warning, CPLE_AppDefined,
                  "I connected to the URL but could not get a DAP 3.x version string\n"
                  "from the server.  I will continue to connect but access may fail.");
    }

/* -------------------------------------------------------------------- */
/*      Fetch the DAS and DDS info about the server.                    */
/* -------------------------------------------------------------------- */
    try
    {
        poConnection->request_das( oDAS );
        poConnection->request_dds( *poDDS, oProjection + oConstraints );
    }
    catch (Error &e)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Error fetching DAS or DDS:\n%s",
                 e.get_error_message().c_str() );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Do we have any ogr_layer_info attributes in the DAS?  If so,    */
/*      use them to define the layers.                                  */
/* -------------------------------------------------------------------- */
    AttrTable::Attr_iter dv_i;

#ifdef LIBDAP_39
    AttrTable* poTable = oDAS.container();
    if (poTable == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot get container");
        return FALSE;
    }
#else
    AttrTable* poTable = &oDAS;
#endif

    for( dv_i = poTable->attr_begin(); dv_i != poTable->attr_end(); dv_i++ )
    {
        if( STARTS_WITH_CI(poTable->get_name(dv_i).c_str(), "ogr_layer_info")
            && poTable->is_container( dv_i ) )
        {
            AttrTable *poAttr = poTable->get_attr_table( dv_i );
            string target_container = poAttr->get_attr( "target_container" );
            BaseType *poVar = poDDS->var( target_container.c_str() );

            if( poVar == nullptr )
            {
                CPLError( CE_Warning, CPLE_AppDefined,
                          "Unable to find variable '%s' named in\n"
                          "ogr_layer_info.target_container, skipping.",
                          target_container.c_str() );
                continue;
            }

            if( poVar->type() == dods_sequence_c )
                AddLayer(
                    new OGRDODSSequenceLayer(this,
                                             target_container.c_str(),
                                             poAttr) );
            else if( poVar->type() == dods_grid_c
                     || poVar->type() == dods_array_c )
                AddLayer( new OGRDODSGridLayer(this,target_container.c_str(),
                                               poAttr) );
        }
    }

/* -------------------------------------------------------------------- */
/*      Walk through the DODS variables looking for easily targeted     */
/*      ones.  Eventually this will need to be driven by the AIS info.  */
/* -------------------------------------------------------------------- */
    if( nLayers == 0 )
    {
        DDS::Vars_iter v_i;

        for( v_i = poDDS->var_begin(); v_i != poDDS->var_end(); v_i++ )
        {
            BaseType *poVar = *v_i;

            if( poVar->type() == dods_sequence_c )
                AddLayer( new OGRDODSSequenceLayer(this,poVar->name().c_str(),
                                                   nullptr) );
            else if( poVar->type() == dods_grid_c
                     || poVar->type() == dods_array_c )
                AddLayer( new OGRDODSGridLayer(this,poVar->name().c_str(),
                                               nullptr) );
        }
    }

    return TRUE;
}

/************************************************************************/
/*                              AddLayer()                              */
/************************************************************************/

void OGRDODSDataSource::AddLayer( OGRDODSLayer *poLayer )

{
    papoLayers = (OGRDODSLayer **)
        CPLRealloc( papoLayers,  sizeof(OGRDODSLayer *) * (nLayers+1) );
    papoLayers[nLayers++] = poLayer;
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRDODSDataSource::TestCapability( const char * pszCap )

{
    return EQUAL(pszCap,ODsCCreateLayer);
}

/************************************************************************/
/*                              GetLayer()                              */
/************************************************************************/

OGRLayer *OGRDODSDataSource::GetLayer( int iLayer )

{
    if( iLayer < 0 || iLayer >= nLayers )
        return nullptr;
    else
        return papoLayers[iLayer];
}
