/** \file
 *
 *  Contains the NavChartDb class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#include "NavChartDb.h"

#include "data/ConfigReader.h"
#include "data/DataWriter.h"
#include "data/Location.h"
#include "data/Mtx.h"
#include "data/Slate.h"
#include "units/Units.h"
#include "utils/Str.h"

#include <cstddef>
#include <dirent.h>
#include <ftw.h>
#include <stdint.h>
#include <sys/stat.h>
#include <unistd.h>

#define NAVCHART_PATH "Data/navchart/"
#define GEO_GRID_SIZE 5000
#define INTEREST_RADIUS 10000
#define COVERAGE_FILENAME ( NAVCHART_PATH "coverage.mtx" )
#define DEPTH_FILENAME ( NAVCHART_PATH "contour.depths.mtx" )
#define ENC_DAT_FILENAME ( NAVCHART_PATH "enc.dat" )
#define SOUNDINGS_NAME "soundings"
#define ENC_DATA_LINE_LENGTH 256
/**
 *  \ingroup utils
 */

bool debug_g = false;

NavChartDb* NavChartDb::Instance_( NULL );
const Str NavChartDb::NAME( "NavChartDb" );
// No need for a NavChartDbIF.h since no other components seem to read these variables.
// However, we still want to define const DataURI elements so that we can use the
// non-deprecated Component::newDataWriter method instead of the deprecated Slate::NewOutputWriter method.
const DataURI NavChartDb::CLOSEST_DISTANCE( "NavChartDb", "closestDistance", Units::METER );
const DataURI NavChartDb::NEXT_DISTANCE( "NavChartDb", "nextDistance", Units::METER );
const DataURI NavChartDb::CLOSEST_DEPTH( "NavChartDb", "closestDepth", Units::METER );
const DataURI NavChartDb::NEXT_DEPTH( "NavChartDb", "nextDepth", Units::METER );
const RestartConfigURI NavChartDb::CHARTS_CFG( "NavChartDb", "charts", Units::NONE, STRVALUE );
const RestartConfigURI NavChartDb::CYCLE_TIMEOUT_CFG( "NavChartDb", "cycleTimeout", Units::SECOND );
// TODO: It may actually be cleaner in the long run to make a NavChartDbIF.h instead. Is there a compelling reason why we shouldn't? Do we want these slate variables to remain inaccessible to other components?
const float NavChartDb::NUM_LATS( 2 * ceilf( M_PI_2 * Location::EARTH_RADIUS / GEO_GRID_SIZE ) );
const float NavChartDb::LAT_INCREMENT( M_PI / NUM_LATS );

const Timespan NavChartDb::PERIOD( 0.10 );

NavChartDb::NavChartDb() :
    AsyncComponent( NAME, NULL, PERIOD ),
    initialized_( false ),
    ready_( false ),
    okToUpdateFromDisk_( false ),
    changeOkToUpdateFromDiskTo_( -9999 ),
    encMap_(),
    depthMap_(),
    depthMtxNameMap_(),
    depthIdxNameMap_(),
    depthMtxMap_(),
    closeMap_(),
    currentGeoIndices_( false ),
    currentGeoIndex_( 0 ),
    closeSoundings_( new ClosePoint[4] ),
    encInfos_( false ),
    coverages_( true ),
    encReader_( NULL ),
    currentCount_( 0 ),
    insertCount_( 0 ),
    currentDepth_( nanf( "" ) ),
    currentDepthStr_( NULL ),
    soundingsStr_( SOUNDINGS_NAME ),
    indexIndex_( -1 ),
    depthIndex_( -1 ),
    nextDepthIndex_( 0 ),
    changeNextDepthIndexTo_( -9999 ),
    quadrant_( 0 ),
    latitude_( nanf( "" ) ),
    changeLatitudeTo_( nanf( "" ) ),
    longitude_( nanf( "" ) ),
    changeLongitudeTo_( nanf( "" ) ),
    shoreDone_( false ),
    mapDone_( false ),
    cycleTimeoutCfgTmp_( 0.05 ),
    cycleTimeoutCfg_( cycleTimeoutCfgTmp_ )
{
    closestDistanceWriter_ = newDataWriter( NavChartDb::CLOSEST_DISTANCE );
    nextDistanceWriter_ = newDataWriter( NavChartDb::NEXT_DISTANCE );
    closestDepthWriter_ = newDataWriter( NavChartDb::CLOSEST_DEPTH );
    nextDepthWriter_    = newDataWriter( NavChartDb::NEXT_DEPTH );

    chartsCfgReader_ = newConfigReader( CHARTS_CFG );
    cycleTimeoutCfgReader_ = newConfigReader( CYCLE_TIMEOUT_CFG );

    Instance_ = this;
}

NavChartDb::~NavChartDb()
{
    Instance_ = NULL;
    delete[] closeSoundings_;
    while( closeMap_.size() > 0 )
    {
        delete[] closeMap_.popIndexed( closeMap_.size() - 1 );
    }
    while( encMap_.size() > 0 )
    {
        delete encMap_.popIndexed( encMap_.size() - 1 );
    }
    while( depthMap_.size() > 0 )
    {
        delete depthMap_.popIndexed( depthMap_.size() - 1 );
    }
    while( depthIdxMap_.size() > 0 )
    {
        delete depthIdxMap_.popIndexed( depthIdxMap_.size() - 1 );
    }
    while( depthIdxNameMap_.size() > 0 )
    {
        delete depthIdxNameMap_.popIndexed( depthIdxNameMap_.size() - 1 );
    }
    while( depthMtxNameMap_.size() > 0 )
    {
        delete depthMtxNameMap_.popIndexed( depthMtxNameMap_.size() - 1 );
    }
    while( depthMtxMap_.size() > 0 )
    {
        FastMap< const int, Mtx*>* mtxMap = depthMtxMap_.popIndexed( depthMtxMap_.size() - 1 );
        while( mtxMap->size() > 0 )
        {
            delete mtxMap->popIndexed( mtxMap->size() - 1 );
        }
        delete mtxMap;
    }
}


/* Call unlink or rmdir on the path, as appropriate. */
int rm( const char *path, const struct stat *s, int flag, struct FTW *f )
{
    int ( *rm_func )( const char * );
    switch( flag )
    {
    default:
        rm_func = unlink;
        break;
    case FTW_DP:
        rm_func = rmdir;
        break;
    }
    return rm_func( path );
}

void NavChartDb::wipeOut()
{
    logger_.syslog( "Change detected in ENC collection. Wiping NavChart Directory", Syslog::FAULT );
    encMap_.clear( true );
    DIR* dir( opendir( NAVCHART_PATH ) );
    if( NULL != dir )
    {
        nftw( NAVCHART_PATH, rm, 4, FTW_DEPTH );
    }
}

void NavChartDb::setChartsCfg( const Str chartsCfg )
{
    chartsCfg_.setString( chartsCfg );
}

void NavChartDb::initialize()
{
    if( !initialized_ )
    {
        if( cycleTimeoutCfgReader_->read( Units::SECOND, cycleTimeoutCfgTmp_ ) && cycleTimeoutCfgTmp_ == cycleTimeoutCfgTmp_ )
        {
            cycleTimeoutCfg_ = cycleTimeoutCfgTmp_;
        }

        if( !initDataDir() )
        {
            return;
        }
        if( !loadEncs() )
        {
            wipeOut();
            return;
        }
        bool encMapEmpty = encMap_.size() == 0;

        if( chartsCfg_.asString() == Str::EMPTY_STR )
        {
            chartsCfgReader_->read( chartsCfg_ );
        }
        int numCharts = 0;
        Str* charts = chartsCfg_.asString().split( numCharts, "," );

        for( int iChart = 0; iChart < numCharts; ++iChart )
        {
            Str& chart = charts[iChart];
            Str filename = chart + ".000";
            logger_.syslog( "Looking for Electronic Nav Chart file at: Resources/ElectronicNavigationCharts/" +
                            filename, Syslog::INFO );

            bool ok( true );
            EncInfo* enc( encMap_.get( filename ) );
            if( NULL == enc )
            {
                int resLevel = chart[2] - '0';
                if( resLevel < 0 || resLevel > 9 )
                {
                    resLevel = 0;
                }
                int id = encMap_.size() + 1;
                enc = new EncInfo( id, resLevel, false, filename );
                encMap_.put( filename, enc );
                ok = saveEncs();
                if( !ok )
                {
                    logger_.syslog( "problem at ", __LINE__,
                                    Syslog::CRITICAL );
                    delete[] charts;
                    return;
                }
            }

            if( !enc->loaded_ )
            {
                if( !encMapEmpty )
                {
                    wipeOut();
                    delete[] charts;
                    return;
                }
                unsigned int insertIndex( 0 );
                for( ; insertIndex < encInfos_.size(); ++insertIndex )
                {
                    if( enc->resLevel_
                            < encInfos_.get( insertIndex )->resLevel_ )
                    {
                        break;
                    }
                }
                encInfos_.insert( insertIndex, enc );
                logger_.syslog(
                    "Will load Electronic Nav Chart data from " +
                    filename, Syslog::IMPORTANT );
            }
            else
            {
                logger_.syslog(
                    "Already Loaded Electronic Nav Chart data from " +
                    filename, Syslog::INFO );
            }
        }

        createSoundings();
        loadDepths();

        initialized_ = true;
        delete[] charts;
    }
}

void NavChartDb::run()
{
    if( !initialized_ )
    {
        initialize();
        return;
    }
    else if( encInfos_.size() > 0 )
    {
        EncInfo* encInfo = encInfos_.get();
        Timestamp startTime( Timestamp::Now() );
        bool done = false;
        for( ; startTime.elapsed() < cycleTimeoutCfg_ && !done ; )
        {
            done = addEncData( encInfo );
            if( done )
            {
                encInfo->loaded_ = true;
                int ok = saveEncs();
                if( ok )
                {
                    logger_.syslog( "Loaded Electronic Nav Chart data from ",
                                    encInfo->filename_.cStr(), Syslog::IMPORTANT );
                }
            }
        }
        if( NULL == encReader_ )
        {
            encInfos_.pop();
            insertCount_ = 0;
            currentDepth_ = nanf( "" );
            currentDepthStr_ = NULL;
            initDataDir();
        }
        return;
    }
    else if( indexIndex_ < ( int ) depthMap_.size() )
    {
        createIndices();
        ++indexIndex_;
    }
    else if( !ready_ )
    {
        ready_ = true;
        setState( BLOCK_PERIODIC );
        setPeriod( Timespan( 0.4 ) );
    }
    else
    {
#ifndef __FASTSIM
        updateClosest();
#endif
    }
}

void NavChartDb::updateGeoIndices()
{
    currentGeoIndices_.clear();
    int latIndex = CalcLatIndex( latitude_ );
    float topLat = Location::NorthingsDelta( latitude_, INTEREST_RADIUS );
    int topLatIndex = CalcLatIndex( topLat );
    float botLat = Location::NorthingsDelta( latitude_, -INTEREST_RADIUS );
    int botLatIndex = CalcLatIndex( botLat );
    for( int iLatIndex = botLatIndex; iLatIndex <= topLatIndex; ++iLatIndex )
    {
        float latAtBand;
        if( iLatIndex < latIndex )
        {
            latAtBand = ( iLatIndex + 0.5 ) * LAT_INCREMENT;
        }
        else if( iLatIndex > latIndex )
        {
            latAtBand = ( iLatIndex - 0.5 ) * LAT_INCREMENT;
        }
        else
        {
            latAtBand = iLatIndex * LAT_INCREMENT;
        }
        float northings = Location::EARTH_RADIUS * ( latAtBand - latitude_ );
        float eastings = sqrt( northings * northings + INTEREST_RADIUS * INTEREST_RADIUS );
        float westLon = Location::EastingsDelta( latAtBand, longitude_, -eastings );
        int nLons = CalcNumLonIndices( iLatIndex );
        int westLonIndex = CalcLonIndex( westLon, nLons );
        float eastLon = 2 * longitude_ - westLon;
        int eastLonIndex = CalcLonIndex( eastLon, nLons );
        for( int iLonIndex = westLonIndex; iLonIndex != eastLonIndex + 1 ; ++iLonIndex )
        {
            if( iLonIndex > nLons / 2 )
            {
                iLonIndex = -nLons / 2;
            }
            int geoIndex = MakeGeoIndex( iLatIndex, iLonIndex );
            currentGeoIndices_.push( ( void* )( size_t )geoIndex );
        }
    }
}

/// Update the curent latitude/longitude pair (in radians)
void NavChartDb::updateCurrent( const float latitude, const float longitude,
                                bool gotFix )
{
    currentMutex_.lock();
    changeLatitudeTo_ = latitude;

    changeLongitudeTo_ = longitude;

    if( gotFix )
    {
        changeNextDepthIndexTo_ = -1;
        changeOkToUpdateFromDiskTo_ = true;
    }
    currentMutex_.unlock();
#ifdef __FASTSIM
    updateClosest();
#endif
}

/// For the current latitude/longitude pair (in radians), update closest point on each contour
void NavChartDb::updateClosest()
{
    bool ok = true;
    currentMutex_.lock();
    if( !isnan( changeLatitudeTo_ ) )
    {
        latitude_ = changeLatitudeTo_;
        changeLatitudeTo_ = nanf( "" );
    }
    if( !isnan( changeLongitudeTo_ ) )
    {
        longitude_ = changeLongitudeTo_;
        changeLongitudeTo_ = nanf( "" );
    }
    if( -9999 != changeNextDepthIndexTo_ )
    {
        nextDepthIndex_ = changeNextDepthIndexTo_;
        changeNextDepthIndexTo_ = -9999;
    }
    if( -9999 != changeOkToUpdateFromDiskTo_ )
    {
        okToUpdateFromDisk_ = changeOkToUpdateFromDiskTo_;
        changeOkToUpdateFromDiskTo_ = -9999;
    }
    currentMutex_.unlock();

    ok &= !isnan( latitude_ ) && !isnan( longitude_ );
    if( !ok )
    {
        return;
    }

    ClosePoint* closePoints;
    const Str* depthStr;
    if( depthIndex_ < 0 )
    {
        depthStr = &soundingsStr_;
        closePoints = closeSoundings_;
    }
    else
    {
        depthStr = depthMap_.getIndexed( depthIndex_ );
        closePoints = closeMap_.getIndexed( depthIndex_ );
    }

    FastMap< const int, Mtx*>* mtxMap = depthMtxMap_.get( depthStr );

    if( currentGeoIndex_ == 0 )
    {
        updateGeoIndices();
        for( int quadrant = 1; quadrant <= 4; ++quadrant )
        {
            ClosePoint& quadrantClosePoint = workingClosePoints_[quadrant - 1];
            quadrantClosePoint.angleSq_ = nanf( "" );
            quadrantClosePoint.angle_ = nanf( "" );
            quadrantClosePoint.lat_ = nanf( "" );
            quadrantClosePoint.lon_ = nanf( "" );
            quadrantClosePoint.cosLat_ = nanf( "" );
            quadrantClosePoint.depth_ = nanf( "" );
        }

    }

    float lat = latitude_;
    float lon = longitude_;
    float cosLat = cos( latitude_ );

    Timestamp startTime( Timestamp::Now() );
    for( ; startTime.elapsed() < cycleTimeoutCfg_
            && currentGeoIndex_ < currentGeoIndices_.size();
            ++currentGeoIndex_ )
    {
        int geoIndex = ( int )( size_t )currentGeoIndices_.peek( currentGeoIndex_ );
        Mtx* mtx = mtxMap->get( geoIndex );
        if( NULL == mtx )
        {
            if( !okToUpdateFromDisk_ )
            {
                continue;
            }
            int startIndex = -1;
            int length = 0;
            Mtx* index = depthIdxMap_.get( depthStr );
            for( int j = 0; startIndex < 0 && j < index->getN(); ++j )
            {
                if( index->getInt( 0, j ) == geoIndex )
                {
                    startIndex = index->getInt( 1, j );
                    length = index->getInt( 2, j );
                }
            }
            if( startIndex > 0 )
            {
                Str* depthMtxName = depthMtxNameMap_.get( depthStr );
                mtx = new Mtx( depthMtxName->cStr(), logger_, startIndex, startIndex + length - 1 );
                for( int j = 0; j < mtx->getN(); ++j )
                {
                    // set cos(latitude) in the matrix
                    mtx->set( 2, j, cos( mtx->get( 0, j ) ) );
                }
            }
            else
            {
                mtx = new Mtx( logger_, 0, 0 );
            }
            //printf("*********** for %s geoIndex %08X, put mtx of N=%d, length=%d\n", depthStr->cStr(), geoIndex, mtx->getN(), length);
            mtxMap->put( geoIndex, mtx );
        }
        for( int j = 0; j < mtx->getN(); ++j )
        {
            float mtxLat = mtx->get( 0, j );
            float mtxLon = mtx->get( 1, j );
            float mtxCosLat = mtx->get( 2, j );
            float anAngleSq( AuvMath::Square( lat - mtxLat ) + cosLat
                             * mtxCosLat * AuvMath::Square( lon - mtxLon ) );
            int quadrant;
            //printf("******* lon=%g, mtxLon=%g\n",lon,mtxLon);
            if( mtxLon > lon )
            {
                if( mtxLat > lat )
                {
                    quadrant = 1;
                }
                else
                {
                    quadrant = 2;
                }
            }
            else
            {
                if( mtxLat < lat )
                {
                    quadrant = 3;
                }
                else
                {
                    quadrant = 4;
                }
            }
            ClosePoint& quadrantClosePoint = workingClosePoints_[quadrant - 1];
            if( isnan( quadrantClosePoint.angleSq_ ) || quadrantClosePoint.angleSq_ > anAngleSq )
            {
                quadrantClosePoint.angleSq_ = anAngleSq;
                quadrantClosePoint.lat_ = mtxLat;
                quadrantClosePoint.lon_ = mtxLon;
                quadrantClosePoint.cosLat_ = mtxCosLat;
                if( depthIndex_ < 0 )
                {
                    float mtxDepth = mtx->get( 3, j );
                    quadrantClosePoint.depth_ = mtxDepth;
                }
            }
        }
    }

    if( currentGeoIndex_ < currentGeoIndices_.size() )
    {
        return;
    }
    currentGeoIndex_ = 0;

    for( int i = 0; i < 4 ; ++i )
    {
        closePoints[i].angleSq_ = workingClosePoints_[i].angleSq_;
        closePoints[i].lat_ = workingClosePoints_[i].lat_;
        closePoints[i].lon_ = workingClosePoints_[i].lon_;
        closePoints[i].cosLat_ = workingClosePoints_[i].cosLat_;
        if( isnan( closePoints[i].angleSq_ ) )
        {
            closePoints[i].angle_ = nanf( "" );
        }
        else
        {
            closePoints[i].angle_ = sqrt( closePoints[i].angleSq_ );
            //printf("For depth %s, closePoints[%d].dist=%g\n", depthStr->cStr(), i, closePoints[i].angle_);
        }
        if( depthIndex_ < 0 )
        {
            closePoints[i].depth_ = workingClosePoints_[i].depth_;
        }
    }

    if( nextDepthIndex_ >= ( int ) depthMap_.size() )
    {
        mapDone_ = true;
        nextDepthIndex_ = -1;
        okToUpdateFromDisk_ = false;
    }
    depthIndex_ = nextDepthIndex_;
    shoreDone_ = shoreDone_ || depthIndex_ > 0;
    ++nextDepthIndex_;

}

/// For a latitude/longitude pair (in radians), return the approximate depth.
float NavChartDb::getSeaFloorDepth( const float latitude, const float longitude )
{
    if( !shoreDone_ /*!mapDone_*/ )
    {
        return nanf( "" );
    }

    float sumInvAngles( 0.0f );
    float sumDepthDivAngle( 0.0f );
    float cosLat = cos( latitude );
    for( int i = -1; i < ( int ) closeMap_.size(); ++i )
    {
        ClosePoint* pts;
        if( i < 0 )
        {
            pts = closeSoundings_;
        }
        else
        {
            pts = closeMap_.getIndexed( i );
        }
        // For each quadrant, find the closest point
        for( int j = 0; j < 4; ++j )
        {
            if( !isnan( pts[j].angle_ ) && !isnan( pts[j].depth_ ) )
            {
                float angleSq( AuvMath::Square( latitude - pts[j].lat_ )
                               + cosLat * pts[j].cosLat_ * AuvMath::Square(
                                   longitude - pts[j].lon_ ) );
                if( !isnan( angleSq ) && 0 != angleSq )
                {
                    sumInvAngles += 1 / angleSq;
                    sumDepthDivAngle += pts[j].depth_ / angleSq;
                }
            }
        }
    }

    if( sumInvAngles == 0 )
    {
        return nanf( "" );
    }
    return sumDepthDivAngle / sumInvAngles;
}

/// For a given latitude and longitude (in radians),
/// estimates the distance from the shoreline at that location
float NavChartDb::getDistanceFromShore( const float latitude,
                                        const float longitude )
{
    if( !shoreDone_ )
    {
        return nanf( "" );
    }

    float minAngleSq( nanf( "" ) );
    float cosLat = cos( latitude );
    ClosePoint* shorePts = closeMap_.get( 0.0f );

    // For each quadrant, find the closest point
    for( int j = 0; j < 4; ++j )
    {
        if( !isnan( shorePts[j].angle_ ) )
        {
            float angleSq( AuvMath::Square( latitude - shorePts[j].lat_ )
                           + cosLat * shorePts[j].cosLat_ * AuvMath::Square(
                               longitude - shorePts[j].lon_ ) );
            if( !isnan( angleSq ) && 0 != angleSq
                    && ( isnan( minAngleSq ) || minAngleSq > angleSq ) )
            {
                minAngleSq = angleSq;
            }
        }
    }

    if( isnan( minAngleSq ) )
    {
        return nanf( "" );
    }

    return sqrt( minAngleSq ) * Location::EARTH_RADIUS;

}

void NavChartDb::uninitialize()
{
    initialized_ = false;
}

bool NavChartDb::initDataDir()
{
    // Create the Data/NavChart directory if it doesn't exist.
    Str navChartPath( NAVCHART_PATH );
    int error = mkdir( navChartPath.cStr(), ACCESSPERMS );
    if( error && EEXIST != errno )
    {
        logger_.syslog( "Unable to access " + navChartPath, Syslog::CRITICAL );
        return false;
    }

    return true;
}

bool NavChartDb::saveDepths()
{
    Mtx depthMtx( logger_, depthMap_.size(), 1 );
    for( unsigned int i = 0; i < depthMap_.size(); ++i )
    {
        depthMtx[i][0] = depthMap_.getIndexedEntry( i )->getKey();
    }
    return depthMtx.write( DEPTH_FILENAME, logger_ );
}

bool NavChartDb::loadDepths()
{
    Mtx depthMtx( DEPTH_FILENAME, logger_ );
    for( int i = -0; i < depthMtx.getM(); ++i )
    {
        float depth( depthMtx( i, 0 ) );
        mapDepth( depth );
    }
    return true;

}

void NavChartDb::doIndexing( const Str* depthStr )
{
    Str* mtxName = depthMtxNameMap_.get( depthStr );
    Str* idxName = new Str( NAVCHART_PATH + *depthStr + "_idx.mtx" );
    Mtx* idx;
    Mtx mtx( mtxName->cStr(), logger_ );
    if( mtx.shellSort( Mtx::Comp2 ) )
    {

        logger_.syslog( "Creating index for " + *depthStr, Syslog::INFO );

        mtx.write( mtxName->cStr(), logger_ );
        int lastGeoCode = -1;
        int numGeoCodes = 0;
        for( int i = 0; i < mtx.getN(); ++i )
        {
            int geoCode = mtx.getInt( 2, i );
            if( lastGeoCode != geoCode )
            {
                ++numGeoCodes;
                lastGeoCode = geoCode;
            }
        }

        idx = new Mtx( logger_, 3, numGeoCodes );
        numGeoCodes = 0;
        int geoCodeCount = 0;
        int geoCodeStart = 0;
        for( int i = 0; i < mtx.getN(); ++i )
        {
            ++geoCodeCount;
            int currentGeoCode = mtx.getInt( 2, i );
            int nextGeoCode;
            if( i + 1 < mtx.getN() )
            {
                nextGeoCode = mtx.getInt( 2, i + 1 );
            }
            else
            {
                nextGeoCode = -1;
            }
            if( currentGeoCode != nextGeoCode )
            {
                idx->setInt( 0, numGeoCodes, currentGeoCode );
                idx->setInt( 1, numGeoCodes, geoCodeStart );
                idx->setInt( 2, numGeoCodes, geoCodeCount );
                ++numGeoCodes;
                geoCodeCount = 0;
                geoCodeStart = i + 1;
            }
        }

        idx->write( idxName->cStr(), logger_ );
    }
    else
    {
        idx = new Mtx( idxName->cStr(), logger_ );
    }
    depthIdxNameMap_.put( depthStr, idxName );
    depthIdxMap_.put( depthStr, idx );
    FastMap<const int, Mtx*>* mtxMap = new FastMap<const int, Mtx*>();
    depthMtxMap_.put( depthStr, mtxMap );
}

bool NavChartDb::createIndices()
{
    if( indexIndex_ < 0 )
    {
        doIndexing( &soundingsStr_ );

    }
    else if( indexIndex_ < ( int ) depthMap_.size() )
    {
        Str* depthStr = depthMap_.getIndexed( indexIndex_ );
        doIndexing( depthStr );
    }

    return true;
}

bool NavChartDb::createSoundings()
{
    Str* soundingMtxName = new Str( NAVCHART_PATH + soundingsStr_ + ".mtx" );
    const Str* soundingsStrPtr = &soundingsStr_;
    depthMtxNameMap_.put( soundingsStrPtr, soundingMtxName );

    /// Yes, pragmatically detecting an existing file is frowned upon.
    /// A purist would use a try... catch, as some jerk could delete the
    /// file after the fopen call is made.
    /// We'll assume no jerks are logged in.
    FILE* fid = fopen( soundingMtxName->cStr(), "r" );
    if( NULL == fid )
    {
        Mtx soundingsMtx( logger_, 4, 0, 0.0 );
        soundingsMtx.write( soundingMtxName->cStr(), logger_ );
    }
    else
    {
        fclose( fid );
    }
    return true;
}

bool NavChartDb::createContour( const Str* depthStr )
{
    Mtx contourMtx( logger_, 3, 0, 0.0 );
    contourMtx.write( depthMtxNameMap_.get( depthStr )->cStr(), logger_ );

    return true;
}

bool NavChartDb::appendToContour( const Str* depthStr, const int encId,
                                  const int resLevel, const float latitude, const float longitude )
{
    for( int i = resLevel + 1; i <= 5; ++i )
    {
        EncArea* coverage = getCoverage( i );
        if( NULL != coverage && coverage->segments_ > 0
                && AuvMath::AreaContains( longitude, latitude,
                                          coverage->segments_, coverage->x0_, coverage->y0_,
                                          coverage->x1_, coverage->y1_ ) )
        {
            return true;
        }
    }

    Mtx mtx( logger_, 3, 1 );
    mtx[0][0] = latitude;
    mtx[1][0] = longitude;
    mtx.setInt( 2, 0, CalcGeoIndex( latitude, longitude ) );
    mtx.append( depthMtxNameMap_.get( depthStr )->cStr(), logger_ );

    return true;
}

bool NavChartDb::appendToSoundings( const float depth, const int encId,
                                    const int resLevel, const float latitude, const float longitude )
{
    for( int i = resLevel + 1; i <= 5; ++i )
    {
        EncArea* coverage = getCoverage( i );
        if( NULL != coverage && coverage->segments_ > 0
                && AuvMath::AreaContains( longitude, latitude,
                                          coverage->segments_, coverage->x0_, coverage->y0_,
                                          coverage->x1_, coverage->y1_ ) )
        {
            return true;
        }
    }

    Mtx mtx( logger_, 4, 1 );
    mtx[0][0] = latitude;
    mtx[1][0] = longitude;
    mtx.setInt( 2, 0, CalcGeoIndex( latitude, longitude ) );
    mtx[3][0] = depth;
    Str* soundingsName = depthMtxNameMap_.get( &soundingsStr_ );
    mtx.append( soundingsName->cStr(), logger_ );

    return true;
}

bool NavChartDb::appendToCoverage( const int encId, const int resLevel,
                                   const float x0, const float y0, const float x1, const float y1 )
{
    Mtx oldCoverageMtx( COVERAGE_FILENAME, logger_ );
    int index = oldCoverageMtx.getN();
    Mtx coverageMtx( logger_, 6, index + 1, 0.0 );
    coverageMtx += oldCoverageMtx;
    coverageMtx[0][index] = encId;
    coverageMtx[1][index] = resLevel;
    coverageMtx[2][index] = x0;
    coverageMtx[3][index] = y0;
    coverageMtx[4][index] = x1;
    coverageMtx[5][index] = y1;
    return coverageMtx.write( COVERAGE_FILENAME, logger_ );
}

bool NavChartDb::loadCoverage( const int resLevel, EncArea** coveragePtr )
{
    Mtx coverageMtx( COVERAGE_FILENAME, logger_ );
    int segments = 0;
    for( int i = 0; i < coverageMtx.getM(); ++i )
    {
        if( coverageMtx( 1, i ) == resLevel )
        {
            ++segments;
        }
    }
    *coveragePtr = new EncArea();
    if( 0 == segments )
    {
        return true;
    }
    EncArea* coverage = *coveragePtr;
    coverage->segments_ = segments;
    coverage->x0_ = new float[segments];
    coverage->y0_ = new float[segments];
    coverage->x1_ = new float[segments];
    coverage->y1_ = new float[segments];

    for( int i = 0; i < coverageMtx.getM(); ++i )
    {
        if( coverageMtx( 1, i ) == resLevel )
        {
            coverage->x0_[i] = coverageMtx( 2, i );
            coverage->y0_[i] = coverageMtx( 3, i );
            coverage->x1_[i] = coverageMtx( 4, i );
            coverage->y1_[i] = coverageMtx( 5, i );
        }
    }
    return true;
}

bool NavChartDb::saveEncs()
{
    FILE* fid = fopen( ENC_DAT_FILENAME, "w" );
    if( NULL == fid )
    {
        return false;
    }
    for( unsigned int i = 0; i < encMap_.size(); ++i )
    {
        EncInfo* enc = encMap_.getIndexed( i );
        fprintf( fid, "%d,%d,%d,%s\n", enc->encId_, enc->resLevel_, enc->loaded_, enc->filename_.cStr() );
    }
    fclose( fid );
    return true;
}

bool NavChartDb::loadEncs()
{
    FILE* fid = fopen( ENC_DAT_FILENAME, "r" );
    if( NULL == fid )
    {
        return true;
    }
    bool allLoaded( true );
    char line[ENC_DATA_LINE_LENGTH];
    while( fgets( line, ENC_DATA_LINE_LENGTH, fid ) )
    {
        char* saveptr;
        char* encIdStr = strtok_r( line, ",", &saveptr );
        if( encIdStr == NULL )
        {
            continue;
        }
        int encId = atoi( encIdStr );
        if( encId == 0 )
        {
            continue;
        }
        char* resLevelStr = strtok_r( NULL, ",", &saveptr );
        if( resLevelStr == NULL )
        {
            continue;
        }
        int resLevel = atoi( resLevelStr );
        if( resLevel == 0 )
        {
            continue;
        }
        char* loadedStr = strtok_r( NULL, ",", &saveptr );
        if( loadedStr == NULL )
        {
            continue;
        }
        int loaded = atoi( loadedStr );
        Str filename = strtok_r( NULL, ",\n", &saveptr );
        if( filename == Str::EMPTY_STR )
        {
            continue;
        }
        EncInfo* enc =  new EncInfo( encId, resLevel, loaded, filename );
        allLoaded &= enc->loaded_;
        encMap_.put( filename, enc );
    }
    fclose( fid );
    return encMap_.size() > 0 && allLoaded;
}

// Returns false until enc file is completely read
bool NavChartDb::addEncData( EncInfo* encInfo )
{
    if( NULL == encReader_ )
    {
        const int MAX_FILENAME( 64 );
        char fullFilename[MAX_FILENAME];
        snprintf( fullFilename, MAX_FILENAME, "Resources/ElectronicNavigationCharts/%s",
                  encInfo->filename_.cStr() );
        encReader_ = new EncReader( fullFilename );
    }
    else if( NULL != encReader_ && !encReader_->scanDone() && enabled_ )
    {
        EncScanPoints points;
        encReader_->scan( points, logger_ );

        if( points.length_ > 0 && NULL == points.z_ )
        {
            if( currentDepth_ != points.zz_ )
            {
                currentDepthStr_ = depthMap_.get( points.zz_ );
                if( NULL == currentDepthStr_ )
                {
                    currentDepthStr_ = mapDepth( points.zz_ );
                    if( !createContour( currentDepthStr_ ) )
                    {
                        return false;
                    }
                    else
                    {
                        if( !saveDepths() )
                        {
                            logger_.syslog( "problem at ", __LINE__,
                                            Syslog::CRITICAL );
                            delete depthMap_.get( points.zz_, true );
                            return false;
                        }
                    }
                }
            }
            for( int i = 0; i < points.length_; ++i )
            {
                if( !appendToContour( currentDepthStr_, encInfo->encId_,
                                      encInfo->resLevel_, D2R( points.y_[i] * 1e-7 ),
                                      D2R( points.x_[i] * 1e-7 ) ) )
                {
                    logger_.syslog( "problem at ", __LINE__, Syslog::CRITICAL );
                    return false;
                }
                if( ( ++insertCount_ % 5000 ) == 0 )
                {
                    logger_.syslog( "# of records loaded: ", insertCount_,
                                    Syslog::INFO );
                }
            }
            currentDepth_ = points.zz_;
        }
        else if( points.length_ > 0 && NULL != points.z_ )
        {
            for( int i = 0; i < points.length_; ++i )
            {
                if( !appendToSoundings( points.z_[i] * 1e-1,
                                        encInfo->encId_, encInfo->resLevel_,
                                        D2R( points.y_[i] * 1e-7 ), D2R( points.x_[i] * 1e-7 ) ) )
                {
                    logger_.syslog( "problem at ", __LINE__, Syslog::CRITICAL );
                    return false;
                }
                if( ( ++insertCount_ % 5000 ) == 0 )
                {
                    logger_.syslog( "# of records loaded: ", insertCount_,
                                    Syslog::INFO );
                }
            }
        }
    }
    else
    {
        EncArea* area;
        while( NULL != ( area = encReader_->popCoverage() ) )
        {
            for( int i = 0; i < area->segments_; ++i )
            {
                if( !appendToCoverage( encInfo->encId_, encInfo->resLevel_,
                                       area->x0_[i], area->y0_[i], area->x1_[i], area->y1_[i] ) )
                {
                    logger_.syslog( "problem at ", __LINE__, Syslog::CRITICAL );
                    return false;
                }
                if( ( ++insertCount_ % 5000 ) == 0 )
                {
                    logger_.syslog( "# of records loaded: ", insertCount_,
                                    Syslog::INFO );
                }
            }
            delete area;
        }
        logger_.syslog( "# of records loaded: ", insertCount_, Syslog::INFO );
        encReader_->uninitialize();
        delete encReader_;
        encReader_ = NULL;
        return true;

    }
    return false;
}

Str* NavChartDb::mapDepth( const float depth )
{
    char depthChars[10] = { 0 };
    snprintf( depthChars, sizeof( depthChars ), "%.1f", depth );
    Str* depthStr = new Str( depthChars );
    depthStr->replaceChar( '-', 'm' );
    depthStr->replaceChar( '.', 'p' );
    depthMap_.put( depth, depthStr );
    ClosePoint* closePoints = new ClosePoint[4];
    closeMap_.put( depth, closePoints );
    for( int i = 0; i < 4; ++i )
    {
        closePoints[i].depth_ = depth;
    }
    Str* depthMtxName = new Str( NAVCHART_PATH + *depthStr + ".mtx" );
    const Str* constDepthStr = depthStr;
    depthMtxNameMap_.put( constDepthStr, depthMtxName );
    return depthStr;
}

EncArea* NavChartDb::getCoverage( int resLevel )
{
    EncArea* coverage = coverages_.get( resLevel );
    if( NULL == coverage )
    {
        loadCoverage( resLevel, &coverage );
        coverages_.set( resLevel, coverage );
    }
    return coverage;
}

float NavChartDb::calcDistFromLineSq( const float x, const float y,
                                      const float x0, const float y0, const float x1, const float y1 )
{
    const float vx = x0 - x, vy = y0 - y, ux = x1 - x0, uy = y1 - y0;
    const float length = ux * ux + uy * uy;

    float det = ( -vx * ux ) + ( -vy * uy ); //if this is < 0 or > length then its outside the line segment
    if( det < 0 )
        return ( x0 - x ) * ( x0 - x ) + ( y0 - y ) * ( y0 - y );
    if( det > length )
        return ( x1 - x ) * ( x1 - x ) + ( y1 - y ) * ( y1 - y );

    det = ux * vy - uy * vx;
    return ( det * det ) / length;
}

/// converts a latitude and longitude into a index that covers a small area
/// of the planet (smaller than GEO_GRID_SIZE * GEO_GRID_SIZE )
int NavChartDb::CalcGeoIndex( float latitude, float longitude )
{
    int latIndex = CalcLatIndex( latitude );
    int nLons = CalcNumLonIndices( latIndex );
    int lonIndex = CalcLonIndex( longitude, nLons );
    return MakeGeoIndex( latIndex, lonIndex );
}

/// Combines latitude index and longitude index into a single index
int NavChartDb::MakeGeoIndex( int latIndex, int lonIndex )
{
    return ( lonIndex << 16 ) | ( latIndex & 0xFFFF );
}

/// converts a latitude into a index that covers a small band
/// around the planet
int NavChartDb::CalcLatIndex( float latitude )
{
    return ( int )roundf( latitude / LAT_INCREMENT );
}

/// Returns number of small longitudinal areas in the latIndex band
/// around the planet (each smaller than GEO_GRID_SIZE * GEO_GRID_SIZE)
int NavChartDb::CalcNumLonIndices( int latIndex )
{
    return 2 * ceilf( M_PI * Location::EARTH_RADIUS * cos( latIndex * LAT_INCREMENT ) / GEO_GRID_SIZE );
}

/// Calculates longitudinal index of small area in the latIndex band
/// around the planet
int NavChartDb::CalcLonIndex( float longitude, int nLons )
{
    float lonInc = M_2PI / nLons;
    return ( int )roundf( longitude / lonInc );
}

