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

#include <unistd.h>

#include "KmlLogWriter.h"
#include "data/DataValue.h"
#include "data/Slate.h"
#include "data/UniversalURI.h"
#include "io/OutStream.h"
#include "logger/EventEntry.h"
#include "logger/DataEntry.h"
#include "logger/SyslogEntry.h"
#include "units/Units.h"

const char* KmlLogWriter::KmlArgs_[3] = {"depth", "latitude", "longitude"};
const char* KmlLogWriter::KmlUnits_[3] = {Units::METER.getName(), Units::DEGREE.getName(), Units::DEGREE.getName()};
float KmlLogWriter::KmlErrors_[3] = { 0.5, D2R( 0.000005 ), D2R( 0.000005 ) };
const Location KmlLogWriter::ZERO_LOCATION( Units::DEGREE, 0.0, 0.0 );

/// Constructor
KmlLogWriter::KmlLogWriter( Str vehicleName, bool emitTimestamps, bool doDepth )
    : LogWriter(),
      vehicleName_( vehicleName ),
      syslogItems_( false ),
      emitTimestamps_( emitTimestamps ),
      doDepth_( doDepth ),
      haveFirstLocation_( false ),
      haveFirstDepth_( false ),
      haveNewLatitude_( false ),
      haveNewLongitude_( false ),
      haveNewDepth_( false ),
      started_( false ),
      lastTimestamp_(),
      currentTimestamp_(),
      lineCount_( 0 ),
      location_( Units::DEGREE, nanf( "" ), nanf( "" ) ),
      lastLatitude_( nanf( "" ) ),
      lastLongitude_( nanf( "" ) ),
      depth_( nanf( "" ) ),
      color_( "ff0055ff" )
{}

unsigned int KmlLogWriter::writeHeader()
{
    unsigned int bytesWritten( 0 );

    if( wroteHeader_ )
    {
        return bytesWritten;
    }
    wroteHeader_ = true;
    wroteFooter_ = false;

    /*const size_t len( 100 );
    char hostname[len];
    *hostname = 0;
    if ( 0 != gethostname( hostname, len ) )
    {
        strncpy( hostname, "Tethys", len );
    }*/

    if( isValid() )
    {
        bytesWritten += logStream_->write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "<kml xmlns=\"http://earth.google.com/kml/2.2\"\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     xsi:schemaLocation=\"http://earth.google.com/kml/2.2 http://code.google.com/apis/kml/schema/kml22beta.xsd\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "  <Document>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <name>" ).bytesWritten();
        /*bytesWritten += logStream_->write( hostname ).bytesWritten();*/
        bytesWritten += logStream_->write( vehicleName_.cStr() ).bytesWritten();
        bytesWritten += logStream_->write( " Path</name>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"start\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>7fffffff</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <colorMode>normal</colorMode>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <Icon><href>https://maps.google.com/mapfiles/kml/paddle/grn-stars.png</href></Icon>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"end\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>7fffffff</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <colorMode>normal</colorMode>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <Icon><href>https://maps.google.com/mapfiles/kml/paddle/X.png</href></Icon>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"line\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <LineStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>" ).bytesWritten();
        bytesWritten += logStream_->write( color_ ).bytesWritten();
        bytesWritten += logStream_->write( "</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <width>2.5</width>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </LineStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   	<StyleMap id=\"linemap\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "      <Pair>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "        <key>normal</key>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "        <styleUrl>#line</styleUrl>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "      </Pair>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "      <Pair>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "        <key>highlight</key>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "        <styleUrl>#line</styleUrl>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "      </Pair>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </StyleMap>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"info\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>3fffffff</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <colorMode>normal</colorMode>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <Icon><href>https://maps.google.com/mapfiles/kml/shapes/info.png</href></Icon>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"error\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>7fffffff</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <colorMode>normal</colorMode>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <Icon><href>https://maps.google.com/mapfiles/kml/shapes/caution.png</href></Icon>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"important\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>ffffffff</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <colorMode>normal</colorMode>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <Icon><href>https://maps.google.com/mapfiles/kml/shapes/flag.png</href></Icon>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"fault\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>ffffffff</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <colorMode>normal</colorMode>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <Icon><href>https://maps.google.com/mapfiles/kml/shapes/hospitals.png</href></Icon>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   <Style id=\"critical\">\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    <IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <color>ffffffff</color>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <colorMode>normal</colorMode>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     <Icon><href>https://maps.google.com/mapfiles/kml/shapes/firedept.png</href></Icon>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </IconStyle>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "   </Style>\n" ).bytesWritten();
    }
    return bytesWritten;
}

/// Destructor
KmlLogWriter::~KmlLogWriter()
{}

unsigned int KmlLogWriter::writeFooter()
{
    unsigned int bytesWritten( 0 );

    if( wroteFooter_ )
    {
        return bytesWritten;
    }
    wroteFooter_ = true;
    wroteHeader_ = false;

    if( isValid() )
    {
        if( started_ )
        {

            bytesWritten += writeSyslogItems( NULL );

            char buffer[256];
            snprintf( buffer, 255, "%f,%f,%f\n",
                      location_.getLon( Units::DEGREE ),
                      location_.getLat( Units::DEGREE ),
                      -depth_ );
            buffer[255] = 0;
            lastTimestamp_ = currentTimestamp_;

            bytesWritten += logStream_->write( "    <Placemark>\n" ).bytesWritten();
            if( !emitTimestamps_ )
            {
                bytesWritten += logStream_->write( "      <name>end</name>\n" ).bytesWritten();
            }
            if( emitTimestamps_ )
            {
                bytesWritten += logStream_->write( "     <TimeStamp><when>" ).bytesWritten();
                char timeBuffer[32];
                currentTimestamp_.toString( timeBuffer, 31, 0 );
                bytesWritten += logStream_->write( timeBuffer ).bytesWritten();
                bytesWritten += logStream_->write( "</when></TimeStamp>\n" ).bytesWritten();
            }
            bytesWritten += logStream_->write( "      <styleUrl>#end</styleUrl>\n" ).bytesWritten();
            bytesWritten += logStream_->write( "     <Point>\n" ).bytesWritten();
            if( doDepth_ )
            {
                bytesWritten += logStream_->write( "      <altitudeMode>absolute</altitudeMode>\n" ).bytesWritten();
            }
            bytesWritten += logStream_->write( "      <coordinates>\n" ).bytesWritten();
            bytesWritten += logStream_->write( buffer ).bytesWritten();
            bytesWritten += logStream_->write( "      </coordinates>\n" ).bytesWritten();
            bytesWritten += logStream_->write( "     </Point>\n" ).bytesWritten();
            bytesWritten += logStream_->write( "    </Placemark>\n" ).bytesWritten();
        }
        bytesWritten += logStream_->write( "  </Document>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "</kml>\n" ).bytesWritten();
    }

    started_ = false;
    lastLatitude_ = nanf( "" );
    lastLongitude_ = nanf( "" );

    return bytesWritten;
}

/// Write the given Entry to the OutStream.
///
/// Pipes the toString() method of the entry to the OutStream with
/// some formatting.
unsigned int KmlLogWriter::write( const LogEntry *entry, const Unit* unit )
{
    // Should check for valid OutStream, eh?
    if( isValid() )
    {
        MutexLocker lock( writeMutex_ );

        if( entry->getType() == LogEntry::DATA_LOG_ENTRY )
        {
            const DataEntry* dataEntry = static_cast<const DataEntry*>( entry );
            if( dataEntry->isBest() )
            {
                const unsigned short universalCode = dataEntry->getDataAccess()->getUniversalCode();

                if( universalCode == UniversalURI::LATITUDE.getCode() )
                {
                    double latitude;
                    dataEntry->getDataValue() ->copyTo( Units::RADIAN, latitude );
                    location_.setLat( Units::RADIAN, latitude );
                    if( isnan( lastLatitude_ ) || fabs( lastLatitude_ - latitude ) > ( doDepth_ ? 1.8e-8 : 3.5e-6 ) )
                    {
                        haveNewLatitude_ = true;
                        lastLatitude_ = latitude;
                    }
                    currentTimestamp_ = dataEntry->getTimestamp();
                }
                else if( universalCode == UniversalURI::LONGITUDE.getCode() )
                {
                    double longitude;
                    dataEntry->getDataValue() ->copyTo( Units::RADIAN, longitude );
                    location_.setLon( Units::RADIAN, longitude );;
                    if( isnan( lastLongitude_ ) || fabs( lastLongitude_ - longitude ) > ( doDepth_ ? 1.8e-8 : 3.5e-6 ) )
                    {
                        haveNewLongitude_ = true;
                        lastLongitude_ = longitude;
                    }
                    currentTimestamp_ = dataEntry->getTimestamp();
                }
                else if( doDepth_ && universalCode == UniversalURI::DEPTH.getCode() )
                {
                    dataEntry->getDataValue() ->copyTo( Units::METER, depth_ );;
                    haveFirstDepth_ = true;
                    haveNewDepth_ = true;
                    currentTimestamp_ = dataEntry->getTimestamp();
                }
                if( !haveFirstLocation_ && !isnan( location_.getDistance( ZERO_LOCATION ) ) && location_.getDistance( ZERO_LOCATION ) > 100 )
                {
                    haveFirstLocation_ = true;
                }
            }
        }
        else if( entry->getType() == LogEntry::EVENT_LOG_ENTRY
                 || entry->getType() == LogEntry::SYSLOG_LOG_ENTRY )
        {
            const EventEntry* eventEntry = entry->getType() == LogEntry::EVENT_LOG_ENTRY ? static_cast<const EventEntry*>( entry ) : NULL;
            const SyslogEntry* syslogEntry = entry->getType() == LogEntry::SYSLOG_LOG_ENTRY ? static_cast<const SyslogEntry*>( entry ) : NULL;
            bool ok = ( haveFirstLocation_
                        && ( haveFirstDepth_ || !doDepth_ )
                        && ( NULL != syslogEntry
                             || ( NULL != eventEntry
                                  && eventEntry->getEventType() == EventEntry::START_CYCLE
                                  && ( haveNewLatitude_
                                       || haveNewLongitude_
                                       || haveNewDepth_
                                       || depth_ == 0.0 ) ) ) );
            if( ok )
            {
                haveNewLatitude_ = false;
                haveNewLongitude_ = false;
                haveNewDepth_ = false;
                unsigned int bytesWritten( 0 );

                char buffer[256];
                if( doDepth_ )
                {
                    snprintf( buffer, 255, "%f,%f,%f\n",
                              location_.getLon( Units::DEGREE ),
                              location_.getLat( Units::DEGREE ),
                              -depth_ );
                }
                else
                {
                    snprintf( buffer, 255, "%.4f,%.4f\n",
                              location_.getLon( Units::DEGREE ),
                              location_.getLat( Units::DEGREE ) );
                }
                buffer[255] = 0;
                lastTimestamp_ = currentTimestamp_;

                if( !started_ )
                {
                    bytesWritten += logStream_->write( "    <Placemark>\n" ).bytesWritten();
                    if( !emitTimestamps_ )
                    {
                        bytesWritten += logStream_->write( "      <name>start</name>\n" ).bytesWritten();
                    }
                    if( emitTimestamps_ )
                    {
                        bytesWritten += logStream_->write( "     <TimeStamp><when>" ).bytesWritten();
                        char timeBuffer[32];
                        currentTimestamp_.toString( timeBuffer, 31, 0 );
                        bytesWritten += logStream_->write( timeBuffer ).bytesWritten();
                        bytesWritten += logStream_->write( "</when></TimeStamp>\n" ).bytesWritten();
                    }
                    bytesWritten += logStream_->write( "      <styleUrl>#start</styleUrl>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "     <Point>\n" ).bytesWritten();
                    if( doDepth_ )
                    {
                        bytesWritten += logStream_->write( "      <altitudeMode>absolute</altitudeMode>\n" ).bytesWritten();
                    }
                    bytesWritten += logStream_->write( "      <coordinates>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( buffer ).bytesWritten();
                    bytesWritten += logStream_->write( "      </coordinates>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "     </Point>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "    </Placemark>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "    <Placemark>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "      <name>trajectory</name>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "      <styleUrl>#linemap</styleUrl>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "      <LineString>\n" ).bytesWritten();
                    if( doDepth_ )
                    {
                        bytesWritten += logStream_->write( "      <altitudeMode>absolute</altitudeMode>\n" ).bytesWritten();
                    }
                    bytesWritten += logStream_->write( "        <coordinates> \n" ).bytesWritten();
                }
                started_ = true;
                if( ++lineCount_ % 10000 == 0 )
                {
                    bytesWritten += logStream_->write( "        </coordinates>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "      </LineString>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "    </Placemark>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "    <Placemark>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "      <name>trajectory</name>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "      <styleUrl>#linemap</styleUrl>\n" ).bytesWritten();
                    bytesWritten += logStream_->write( "      <LineString>\n" ).bytesWritten();
                    if( doDepth_ )
                    {
                        bytesWritten += logStream_->write( "      <altitudeMode>absolute</altitudeMode>\n" ).bytesWritten();
                    }
                    bytesWritten += logStream_->write( "        <coordinates> \n" ).bytesWritten();
                }

                if( NULL == syslogEntry )
                {
                    bytesWritten += logStream_->write( buffer ).bytesWritten();
                }
                else
                {
                    syslogItems_.push( new SyslogItem( currentTimestamp_,
                                                       buffer, syslogEntry->getSeverity(),
                                                       syslogEntry->toKmlString() ) );
                    if( syslogItems_.size() > 200 )
                    {
                        bytesWritten += writeSyslogItems( buffer );
                    }
                }
                return bytesWritten;
            }
            return 0;
        }
    }

    /// Or else what?
    return 0;

}

unsigned int KmlLogWriter::writeSyslogItems( const char* latestCoordinates )
{
    unsigned int bytesWritten( 0 );
    bytesWritten += logStream_->write( "        </coordinates>\n" ).bytesWritten();
    bytesWritten += logStream_->write( "      </LineString>\n" ).bytesWritten();
    bytesWritten += logStream_->write( "    </Placemark>\n" ).bytesWritten();
    for( unsigned int i = 0; i < syslogItems_.size(); ++i )
    {
        SyslogItem* item = syslogItems_.get( i );
        bytesWritten += logStream_->write( "    <Placemark>\n" ).bytesWritten();
        Str& messageStr = item->message_;
        Str messageStrCopy;
        char* message = ( char * )messageStr.cStr();
        for( unsigned int j = 0; j < messageStr.length(); ++j )
        {
            if( message[j] < 32 )
            {
                if( messageStrCopy == Str::EMPTY_STR )
                {
                    messageStrCopy = messageStr;
                    message = ( char * )messageStr.cStr();
                }
                message[j] = '#';
            }
        }
        if( !emitTimestamps_ )
        {
            bytesWritten += logStream_->write( "     <name><![CDATA[" ).bytesWritten();
            bytesWritten += logStream_->write( message ).bytesWritten();
            bytesWritten += logStream_->write( "]]></name>\n" ).bytesWritten();
        }
        if( emitTimestamps_ )
        {
            bytesWritten += logStream_->write( "     <description><![CDATA[" ).bytesWritten();
            bytesWritten += logStream_->write( message ).bytesWritten();
            bytesWritten += logStream_->write( "     ]]></description>\n" ).bytesWritten();
            bytesWritten += logStream_->write( "     <TimeStamp><when>" ).bytesWritten();
            char timeBuffer[32];
            item->timestamp_.toString( timeBuffer, 31, 0 );
            bytesWritten += logStream_->write( timeBuffer ).bytesWritten();
            bytesWritten += logStream_->write( "</when></TimeStamp>\n" ).bytesWritten();
        }
        switch( item->severity_ )
        {
        case Syslog::NONE:
        case Syslog::DEBUG:
        case Syslog::INFO:
            bytesWritten += logStream_->write( "      <styleUrl>#info</styleUrl>\n" ).bytesWritten();
            break;
        case Syslog::ERROR:
            bytesWritten += logStream_->write( "      <styleUrl>#error</styleUrl>\n" ).bytesWritten();
            break;
        case Syslog::IMPORTANT:
            bytesWritten += logStream_->write( "      <styleUrl>#important</styleUrl>\n" ).bytesWritten();
            break;
        case Syslog::FAULT:
            bytesWritten += logStream_->write( "      <styleUrl>#fault</styleUrl>\n" ).bytesWritten();
            break;
        case Syslog::CRITICAL:
            bytesWritten += logStream_->write( "      <styleUrl>#critical</styleUrl>\n" ).bytesWritten();
            break;
        }
        bytesWritten += logStream_->write( "     <Point>\n" ).bytesWritten();
        if( doDepth_ )
        {
            bytesWritten += logStream_->write( "      <altitudeMode>absolute</altitudeMode>\n" ).bytesWritten();
        }
        bytesWritten += logStream_->write( "      <coordinates>\n" ).bytesWritten();
        bytesWritten += logStream_->write( item->coordinates_.cStr() ).bytesWritten();
        bytesWritten += logStream_->write( "      </coordinates>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "     </Point>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "    </Placemark>\n" ).bytesWritten();
        delete item;
    }
    syslogItems_.clear();
    if( NULL != latestCoordinates )
    {
        bytesWritten += logStream_->write( "    <Placemark>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "      <name>trajectory</name>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "      <styleUrl>#linemap</styleUrl>\n" ).bytesWritten();
        bytesWritten += logStream_->write( "      <LineString>\n" ).bytesWritten();
        if( doDepth_ )
        {
            bytesWritten += logStream_->write( "      <altitudeMode>absolute</altitudeMode>\n" ).bytesWritten();
        }
        bytesWritten += logStream_->write( "        <coordinates> \n" ).bytesWritten();
        bytesWritten += logStream_->write( latestCoordinates ).bytesWritten();
    }

    return bytesWritten;
}
