/*! 
* 
* Copyright(c) 2009 Apogee Instruments, Inc. 
* \class UdpSocketBase 
* \brief Base class for a upd socket that finds apogee devices on a subnet 
* 
*/ 

#ifdef WIN32
// removes warnings about vectors
#pragma warning(disable: 4786)
#endif

#include "UdpSocketBase.h" 
#include <sstream>

#if defined (WIN32)
    #include "winsock2.h"
#else
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <stdexcept>
    #define SOCKET_ERROR -1
    #define INVALID_SOCKET -1
#endif


namespace
{
    const int DISCOVERY_TIMEOUT_SECS = 10;
    const int DISCOVERY_MAXBUFFER = (16 * 1024);
}

//////////////////////////// 
// CTOR 
UdpSocketBase::UdpSocketBase() : m_SocketDescriptor(INVALID_SOCKET),
                                 m_udpPacket(""),
                                 m_timeout(DISCOVERY_TIMEOUT_SECS),
                                 m_elapsedSec(0)

{ 
    
} 

//////////////////////////// 
// DTOR 
UdpSocketBase::~UdpSocketBase() 
{ 
    
} 

//////////////////////////// 
// SEARCH   4   APOGEE      DEVICES
std::vector<std::string> UdpSocketBase::Search4ApogeeDevices(const std::string & subnet,
                                           const unsigned short portNum)
{

    CreateSocket( portNum );

    SetSocketOptions();

    CreateUpdPacket();

    //blast a UPD message looking for all
    //Apogee devices on the subnet
    BroadcastMsg(subnet, portNum);

    //collect the return messages
    std::vector<std::string> msgs = GetReturnedMsgs();
    
    //use the platform specific close
    CloseSocket();

    return msgs;

}

//////////////////////////// 
//  CREATE  SOCKET
void UdpSocketBase::CreateSocket(unsigned short portNum)
{
    m_SocketDescriptor = socket( AF_INET, SOCK_DGRAM, 0 );

    if( INVALID_SOCKET == m_SocketDescriptor )
    {
        throw std::runtime_error("Failed to create a socket");
        
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
	addr.sin_family	= AF_INET;
	addr.sin_port	 = htons( portNum );
    //INADDR_ANY puts your IP address automatically 
	addr.sin_addr.s_addr = INADDR_ANY;  

    //bind the socket to the input port number
    const int result = bind(m_SocketDescriptor,reinterpret_cast<sockaddr *>(&addr),
        sizeof(struct sockaddr) );

    if(-1 == result )
    {
        throw std::runtime_error("Binding socket failed");
    }     

}

//////////////////////////// 
//  SET     SOCKET      OPTIONS
void UdpSocketBase::SetSocketOptions()
{
     //TODO: figure out why this is one
	int OptVal = 1;  
    const int OptLen = sizeof(int);

    int result = setsockopt( m_SocketDescriptor, 
        SOL_SOCKET, SO_BROADCAST, 
        reinterpret_cast<char*>(&OptVal), OptLen);

    if( result )
	{
        std::stringstream ss;
        ss << result;
		
        std::string errMsg = "setsockopt failed with error " + ss.str();
        throw std::runtime_error( errMsg );
	}
}


//////////////////////////// 
//  CREATE      UPD     PACKET
void UdpSocketBase::CreateUpdPacket()
{
    std::stringstream lineStream;

    lineStream << "Discovery::Request-Except: \"Apogee\"; ";
    lineStream << std::hex << std::showbase;
    lineStream << 0x12345678 << "; ";
    lineStream << std::dec << std::noshowbase;
    lineStream << 0 << "; ";
    lineStream << DISCOVERY_TIMEOUT_SECS / 2 << "; ";
    lineStream << 0 << "; ";
    lineStream << 0 << "\r\n\r\n";

    std::string lineStr = lineStream.str();

    std::stringstream ss;
    
    ss << "MIME-Version: 1.0\r\n";
    ss << "Content-Type: application/octet-stream\r\n";
    ss << "Content-Transfer-Encoding: binary\r\n";
    ss << std::hex << std::showbase;
    ss << "Content-Length: " << lineStr.size() << "\r\n";
    ss << "X-Project: Apogee\r\n"; 
    ss << "X-Project-Version: 0.1\r\n\r\n";
   
 
    m_udpPacket = ss.str() + lineStr;
}


//////////////////////////// 
//  BROADCAST   MSG
void UdpSocketBase::BroadcastMsg(const std::string & subnet,
            unsigned short portNum)
{

    struct hostent *he = gethostbyname( subnet.c_str() );

    if( !he )
    {
        throw std::runtime_error("Failed to create hostent structure");   
    }

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family	= AF_INET;
	servaddr.sin_port	= htons( portNum );
	servaddr.sin_addr	= *(reinterpret_cast<in_addr *>(he->h_addr) );

    int result = sendto( m_SocketDescriptor, m_udpPacket.c_str(), 
        m_udpPacket.size(), 0, 
        reinterpret_cast<sockaddr *>(&servaddr), 
        sizeof(servaddr) );

    if( SOCKET_ERROR == result ) 
    {
        std::stringstream ss;
        ss << result;

        std::string errMsg = "sendto failed with error " + ss.str();
        throw std::runtime_error( errMsg );
    }
}

//////////////////////////// 
//  RECEIVED        MSG
std::vector<std::string> UdpSocketBase::GetReturnedMsgs()
{

    struct timeval tv;
    tv.tv_sec	= 1;
	tv.tv_usec = 0;

	fd_set rset;

    std::vector<std::string> returnedStrs;
    //wait for data to come back
    //for a while
    for(m_elapsedSec = 0; m_elapsedSec < m_timeout; ++m_elapsedSec)
   {
        FD_ZERO( &rset );
		FD_SET( m_SocketDescriptor, &rset );

        int result = select( m_SocketDescriptor + 1, &rset, 0, 0, &tv );
        
    	if( SOCKET_ERROR == result ) 
		{
           std::stringstream ss;
           ss << result;
		
            std::string errMsg = "select failed with error " + ss.str();
            throw std::runtime_error( errMsg );
        }

        // return of 0 = time limit expired
        // return of > 0 total number of socket handles 
        // that are ready and contained in fd_set
        // there is data so lets go fectch it
        if( result > 0 )
        {
            returnedStrs.push_back( FetchMsgFromSocket() );
        }
    }

    return returnedStrs;
}


//////////////////////////// 
//  FETCH   MSG     FROM        SOCKET
std::string UdpSocketBase::FetchMsgFromSocket()
{
    //TODO - figure out if can just use the 
    //string I return here
    std::vector<char> returnedMsg(DISCOVERY_MAXBUFFER);

    int value = recvfrom(m_SocketDescriptor, &(*returnedMsg.begin()), 
        returnedMsg.size(), 0, 0,0);

    if( SOCKET_ERROR == value ) 
    {
        throw std::runtime_error( "recvfrom socket operation failed" );
    }

    std::string msg( &(*returnedMsg.begin()) );
    return msg;
} 
