/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $Id$ */

#include "NotificationListener/UDPListener.h"

#include <memory>
#include <cstring>
#include <string>

#include <vector>

#include <platform.h>

#ifdef PLATFORM_WINDOWS
#include <ws2tcpip.h>
#else

#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#endif

#include <NotificationListener/IDataSink.h>
#include <Utils.h>


static const char c_DefDumpPath[] =
#ifdef PLATFORM_WINDOWS
"C:/windows/temp"
#else
"/tmp"
#endif
;

using namespace NS_DM_Client::NS_NotificationListener;

const char UDPListener::ANY_ADDR[] = "*ANY*";

UDPListener::UDPListener(NS_Logging::Logger& logger)
:m_libInitialized(false),
m_sock(0),
m_logger(logger),
m_dumpMessage(false)
{
    m_libInitialized = libInit();
}


UDPListener::~UDPListener()
{
    if (m_sock)
    {
        closeSocket();
    }

    if (m_libInitialized)
    {
#ifdef PLATFORM_WINDOWS
        WSACleanup();
#endif
    }
}


#ifndef PLATFORM_WINDOWS
int closesocket(UDPListener::socket_t s)
{
    return close(s);
}
#endif


bool    UDPListener::libInit()
{
#ifdef PLATFORM_WINDOWS
    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD( 2, 2 );
    int err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
        return false;
    }

    if ( LOBYTE( wsaData.wVersion ) != 2 ||
        HIBYTE( wsaData.wVersion ) != 2 ) {
            WSACleanup( );
            return false;
    }
#endif  // PLATFORM_WINDOWS

    return true;
}


bool    UDPListener::Init(const char* ipAddr, port_t port, size_t readBufferSize)
{
    in_addr addr = { 0 };
    addr.s_addr = (!ipAddr || strcmp(ipAddr, ANY_ADDR) == 0) ? INADDR_ANY : inet_addr(ipAddr);
    if (addr.s_addr == INADDR_NONE) LOG_ERROR_(m_logger, "Incorrect IP address: '%s'. ", ipAddr);
    return (addr.s_addr  != INADDR_NONE) ? Init(addr, port, readBufferSize) : false;
}


bool    UDPListener::Init(const in_addr& ipAddr, port_t port, size_t readBufferSize)
{
    if (!m_libInitialized || m_sock)
    {
        LOG_ERROR_(m_logger, "SockLibInitialized=%d, socket=%0Xh. ", m_libInitialized, m_sock);
        return false;
    }

    m_buf.resize(readBufferSize);
    if (m_buf.size() < readBufferSize)
    {
        LOG_ERROR_(m_logger, "Failed to allocate buffer of size=%u. ", readBufferSize);
        return false;
    }

    m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(m_sock < 0)
    {
        LOG_ERROR_(m_logger, "Create socket result=%0Xh. ", m_sock);
        return false;
    }
/*
	int on = 1;
	if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
	{
        LOG_ERROR_(m_logger, "set socket option SO_REUSEADDRC failed (error=%d). ", errno);
	}
*/
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = ipAddr.s_addr;
    if(bind(m_sock, (sockaddr *)&addr, sizeof(addr)) < 0)
    {
        LOG_ERROR_(m_logger, "Bind socket result=%d. ", errno);
        closeSocket();
        return false;
    }

    LOG_DEBUG_(m_logger, "Listening socket %s:%d. ", inet_ntoa(ipAddr), port);

    m_addr = ipAddr;
    m_port = port;

	m_stop = false;
    return true;
}


bool    UDPListener::Listen(IDataSink* dataSink)
{
    int bytes_read = 0;

    struct sockaddr_in  from;
    memset(&from, 0, sizeof(from));
    socklen_t fromlen = sizeof(from);

    while(!m_stop)
    {
        fromlen = sizeof(from);
		LOG_DEBUG_(m_logger, "recvfrom: started. ");
        bytes_read = recvfrom(m_sock, &m_buf[0], m_buf.size(), 0, (struct sockaddr*)&from, &fromlen);
		LOG_DEBUG_(m_logger, "recvfrom: result=%d. ", bytes_read);
        if (bytes_read == -1)
        {
#if defined(PLATFORM_POSIX)
            const int error = errno;
            const int SOCKET_CLOSED = EBADF;
            if (error == EINTR)
            {
                LOG_DEBUG_(m_logger, "recvfrom: EINTR signal received. ");
                continue;
            }

#elif defined(PLATFORM_WINDOWS)
            const int error = WSAGetLastError();
            const int SOCKET_CLOSED = WSAENOTSOCK;
#endif

            if (error == SOCKET_CLOSED)
            {
				LOG_DEBUG_(m_logger, "recvfrom: socket closed. ");
                if (!m_stop)
                {
					LOG_ERROR_(m_logger, "socket unexpectedly closed. ");
					break;
                }
                continue;
            }

            LOG_ERROR_(m_logger, "recvfrom: Error=%d. ", error);
            break;
        }

        if (bytes_read < -1)
        {
            LOG_ERROR_(m_logger, "recvfrom: Unknown result=%d. ", bytes_read);
            continue;
        }

        LOG_DEBUG_(m_logger, "Message size: %d bytes, from: %s:%d", bytes_read, inet_ntoa(from.sin_addr), ntohs(from.sin_port));

        if (bytes_read == 0)
        {
            LOG_ERROR_(m_logger, "recvfrom: Zero bytes read. ");
            continue;
        }

        if (m_dumpMessage)
        {
            dump(&m_buf[0], bytes_read);
        }

        if (bytes_read == (int)STOP_MESSAGE.size()
            && std::equal( STOP_MESSAGE.begin(), STOP_MESSAGE.end(), m_buf.begin() ))
        {
            LOG_DEBUG_(m_logger, "Stop message received. ");
            m_stop = true;
            continue;
        }

        if (dataSink)
        {
            dataSink->Write(&m_buf[0], bytes_read);
            LOG_DEBUG_(m_logger, "Data written to sink. ");
        }

    }

    return m_stop == true;
}


bool    UDPListener::Stop()
{
	LOG_DEBUG_(m_logger, "stop listening initiated. ");

/*
    socket_t dest = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    sockaddr_in RecvAddr;
    RecvAddr.sin_family = AF_INET;
    RecvAddr.sin_port = htons(m_port);
    RecvAddr.sin_addr.s_addr = m_addr.s_addr;

    S_generateStopMessage(STOP_MESSAGE);

    sendto(dest, STOP_MESSAGE.c_str(), STOP_MESSAGE.length(), 0,
        (sockaddr *) &RecvAddr, sizeof(RecvAddr));

    closesocket(dest);
*/
    m_stop = true;
	closeSocket();

    return true;
}


void    UDPListener::SetSettings(bool dumpMessage, const char* dumpPath)
{
    m_dumpMessage = dumpMessage;
    if (!dumpPath)
    {
		dumpPath = c_DefDumpPath;
    }

	m_dumpPath = dumpPath;
}


void    UDPListener::S_generateStopMessage(std::string& msg)
{
    time_t ltime;
    time( &ltime );
    char nonce[32];
    __sprintf(nonce, "%08X", (unsigned)ltime);

#ifdef _DEBUG
    msg = "!!! STOP MESSAGE !!!";
#else
    msg = "dFRE\x3s53E%@2!$fw1S%\x7wefx#^2dv4dfh";
#endif
    msg.append(nonce);

}


void UDPListener::dump(const char* data, size_t size)
{
    const char* dumpPath = m_dumpPath.c_str();
    static const char fileNamePrefix[] = "UDPListener-";
    static const char fileNameExt[] = ".dmp";
    static const char filePathSeparator[] = "/";

    String fname;
    fname.reserve(1024);
    fname = dumpPath;
    fname += filePathSeparator;
	fname += fileNamePrefix;

    time_t ltime = 0;
    time( &ltime );

    char buf[32];
    __sprintf(buf, "%08X", (unsigned)ltime);

    fname += buf;
    fname += fileNameExt;

    FILE* f = fopen(fname.c_str(), "w+b");
	const size_t writeBytes = sizeof(data[0]) * size;
	if (f)
	{
		fwrite(data, 1, writeBytes, f);
		fclose(f);
	}
	else
	{
		LOG_DEBUG_(m_logger, "Failed to write %d bytes to dump (%p) '%s'. ", writeBytes, f, fname.c_str());
	}
}


bool	UDPListener::closeSocket()
{
	bool brc = false;
	if (m_sock)
	{
        brc = ::closesocket(m_sock) == 0;
		m_sock = 0;
	}
	else
	{
		LOG_DEBUG_(m_logger, "socket is not opened. ");
	}

	return brc;
}


