/*
 * c't-Sim - Robotersimulator fuer den c't-Bot
 * 
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your
 * option) any later version. 
 * 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 General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307, USA.
 * 
 */

/*! @file 	tcp.c
 * @brief 	TCP/IP-Kommunikation
 * @author 	Benjamin Benz (bbe@heise.de)
 * @date 	26.12.05
*/

#include "ct-Bot.h"

#define USE_SEND_BUFFER

#ifdef PC

/* Linux with glibc:
 *   _REENTRANT to grab thread-safe libraries
 *   _POSIX_SOURCE to get POSIX semantics
 */
#ifndef WIN32
#  define _REENTRANT
//#  define _POSIX_SOURCE
#else
//	#define WIN32
#endif

/* Hack for LinuxThreads */
#ifdef __linux__
#  define _P __P
#endif

#include <pthread.h>


#ifdef WIN32
	#include <winsock.h>
#else
	#include <arpa/inet.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <netdb.h>		// for gethostbyname()
	#include <netinet/tcp.h> 
#endif


#include <stdio.h>      // for printf() and fprintf()
#include <stdlib.h>     // for atoi() and exit()
#include <string.h>     // for memset()
#include <unistd.h>     // for close()

#include "tcp.h"
#include "display.h"

int tcp_sock=0;			/*!< Unser TCP-Socket */
char *tcp_hostname = NULL;		/*!< Hostname, auf dem ct-Sim laeuft */

#ifdef PC
	uint8 sendBuffer[1024];	/*!< Sendepuffer fuer ausgehende Packete */
	int sendBufferPtr=0;	/*!< Index in den Sendepuffer */
#endif

/*!
 * Oeffnet eine TCP-Verbindung zum Server 
 * @param hostname Symbolischer Name des Host, auf dem ct-Sim laeuft
 * @return Der Socket
*/
int tcp_openConnection(const char *hostname){
    struct sockaddr_in servAddr;   // server address
    int sock=0;                        // Socket descriptor
    struct hostent *he = gethostbyname(hostname);

	// Ueberpruefen, ob der Hostname aufgeloest werden konnte
	if (NULL == he) {
		printf("gethostbyname() failed\n");
		exit(1);
	}

    // Create a stream socket for TCP
    if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
        printf("socket() failed");
	exit(1);
    }
    
    int flag = 1;
  	setsockopt(sock,            /* socket affected */
                          IPPROTO_TCP,     /* set option at TCP level */
                          TCP_NODELAY,     /* name of option */
                          (char *) &flag,  /* the cast is historical 
                                                  cruft */
                          sizeof(int));    /* length of option value */

    // Prepare server address structure
    memset(&servAddr, 0, sizeof(servAddr));     // Zero out structure
    servAddr.sin_family      = AF_INET;     // Internet address
    servAddr.sin_port        = htons(PORT);     // Port

    // Die erste Adresse aus der Liste uebernehmen
    memcpy(&servAddr.sin_addr, *(he->h_addr_list), sizeof(servAddr.sin_addr));

    // Open Connection to the server
    if (connect(sock, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0){
        printf("tcp_openConnection() to %s failed\n", inet_ntoa(servAddr.sin_addr));
	exit(1);
    }

    return sock;
}

/*!
 * Schliesst eine TCP-Connection
 * @param sock Der Socket
*/
void tcp_closeConnection(int sock){
    close(sock);
    #ifdef WIN32
		WSACleanup();
	#endif
}

/*!
 * Sende Kommando per TCP/IP im Little Endian
 * Diese Funktion setzt vorraus, dass die Symbole BYTE_ORDER und BIG_ENDIAN
 * bzw. LITTLE_ENDIAN definiert wurden. Damit dies auf Linux/Unix 
 * funktioniert darf _POSIX_SOURCE nicht definiert werden. Fuer Windows
 * wird dies in der Headerdatei tcp.h erledigt.
 * Getestet wurde dies bisher auf folgenden Systemen:
 *  - MacOSX (PPC, big endian)
 *  - Gentoo Linux (hppa, big endian)
 *  - OpenBSD (i386, little endian)
 *  - Windows 2000 (i386, little endian mit dem MinGW)
 * Sollten in command_t weitere Werte mit mehr bzw. weniger als 8 bit
 * aufgenommen werden muss hier eine entsprechende Anpassung erfolgen.
 * @param cmd Zeiger auf das Kommando
 * @return Anzahl der gesendete Bytes
 */
int tcp_send_cmd(command_t *cmd){
#if BYTE_ORDER == BIG_ENDIAN
	command_t le_cmd;

	/* Kopieren des Kommandos und auf Little Endian wandeln */
	memcpy(&le_cmd, cmd, sizeof(command_t));

	/* Alle 16bit Werte in Little Endian wandeln */
	le_cmd.data_l = cmd->data_l << 8;
	le_cmd.data_l |= (cmd->data_l >> 8) & 0xff;
	le_cmd.data_r = cmd->data_r << 8;
	le_cmd.data_r |= (cmd->data_r >> 8) & 0xff;
	le_cmd.seq = cmd->seq <<8;
	le_cmd.seq |= (cmd->seq >> 8) & 0xff;

	/* "Umdrehen" des Bitfields */
	le_cmd.request.subcommand = cmd->request.subcommand >> 1;
	le_cmd.request.direction = (cmd->request.subcommand & 1) << 7;

	return tcp_write(&le_cmd, sizeof(command_t));
#else
	return tcp_write(cmd, sizeof(command_t));
#endif
}

/*! Puffert daten im Sendepuffer zwischen
 * @param data zeiger auf die daten
 * @param length Anzahl der Bytes
 * @return -1 wenn kein Platz mehr im Puffer ist, 0 wenn alles ok ist
 */
int copy2Buffer(uint8* data, int length){
	int i;
	uint8 * ptr = data;
	
	if ((sendBufferPtr + length) > sizeof(sendBuffer))
		return -1;
		
//	printf("Store %d bytes",length);
	// Auf dem PC kopieren wir nur alles in den Ausgangspuffer
	for (i=0; i< length ; i++){
		sendBuffer[sendBufferPtr++]= *ptr++ ;
	}
//	printf(" %d Bytes now in buffer\n",sendBufferPtr);
	return 0;
}

/*!
 * Uebertrage Daten per TCP/IP
 * @param data Zeiger auf die Daten
 * @param length Anzahl der Bytes
 * @return 0 wenn alles ok, -1 wenn puffer voll
*/
int tcp_write(void* data, int length){
	#ifdef USE_SEND_BUFFER
		return copy2Buffer(data, length);
	#else
		if (send(tcp_sock,data,length,0) != length){
			printf("send() sent a different number of bytes than expected");
			return -1;
		}
		return length;
	#endif
}

/*!
 * Lese Daten von TCP/IP-Verbindung.
 * Achtung: blockierend!
 * @param data Zeiger auf die Daten
 * @param length Anzahl der gewuenschten Bytes
 * @return Anzahl der uebertragenen Bytes
*/
int tcp_read(void* data, int length){
	int bytesReceived=0;

	if ((bytesReceived = recv(tcp_sock, data, length, 0)) <= 0){
            printf("recv() failed or connection closed prematurely\n");
	    exit(1);
	    return -1;
	}
	
	return bytesReceived;
}

/*! 
 * Initialisiere TCP/IP Verbindung 
 */ 
void tcp_init(void){
	#ifdef WIN32
	    WSADATA wsaData;
	    WORD wVersionRequested;
	    int err;
		
	    wVersionRequested = MAKEWORD( 2, 0 ); // 2.0 and above version of WinSock
	    err = WSAStartup( wVersionRequested, &wsaData );
	    if ( err != 0 ) {
	        fprintf(stderr, "Couldn't not find a usable WinSock DLL.\n");
	        exit(1); 
	    }
	#endif
	
	
    if ((tcp_sock=tcp_openConnection(tcp_hostname)) != -1)
        printf ("connection to %s established on Port: %d\n", tcp_hostname, PORT);
    else {
		printf ("connection to %s failed on Port: %d\n", tcp_hostname, PORT);
    	exit(1);
    }

}

/*! 
 * Schreibt den Sendepuffer auf einen Schlag raus 
 * @return -1 bei Fehlern, sonst zahl der uebertragenen Bytes
 */
int flushSendBuffer(void){
	#ifdef USE_SEND_BUFFER
	//	printf("Flushing Buffer with %d bytes\n",sendBufferPtr);
	
		int length=sendBufferPtr;
		sendBufferPtr=0;	// Puffer auf jedenfall leeren
		if (send(tcp_sock,(char*)&sendBuffer,length,0) != length){
			printf("send() sent a different number of bytes than expected");
			return -1;
		}
		return length;
	#else
		return 0;
	#endif
}

#endif
