/**********************************************************************************************
    Copyright (C) 2007 Juan Pablo Daniel Borgna jpdborgna@e-mips.com.ar

    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 USA

  Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd.
  or one of its subsidiaries.

**********************************************************************************************/
#include "CTcp.h"
#include "IDevice.h"
#include "Platform.h"

#include <iostream>
#include <sstream>
#include <assert.h>
#include <errno.h>
#ifndef WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <unistd.h>
#endif

#include <time.h>

using namespace Garmin;
using namespace std;

#undef DBG

#define DBG_LINE_SIZE 16

#define MAX_SIZE 255
#define TIMEOUT 5

static int my_close( int fd)
{
    return( close( fd));
}


static ssize_t my_read(int fd, void *buf, size_t count)
{
    return( recv( fd, (char *) buf, count,0));
}


static ssize_t my_write(int fd, const void *buf, size_t count)
{
    return( send( fd, (char *) buf, count,0));
}


CTcp::CTcp(const std::string& port)
: sock_fd(-1)
, productId(0)
, softwareVersion(0)
, port(port)
{
}


CTcp::~CTcp()
{
    close();
}


struct in_addr *  CTcp::atoaddr(char *address)
{
    struct hostent *host;
    static struct in_addr saddr;

    /* First try it as aaa.bbb.ccc.ddd. */
    saddr.s_addr = inet_addr(address);
    if ((int )saddr.s_addr != -1) {
        return &saddr;
    }
    host = gethostbyname(address);
    if (host != NULL) {
        return (struct in_addr *) *host->h_addr_list;
    }
    return NULL;
}


void CTcp::open()
{
#if defined(HAVE_BIGENDIAN) || !defined(CAN_UNALIGNED)
    throw exce_t(errOpen, "The TCP driver still needs to be ported to your platform.");
#endif
    uint16_t lport = -1;
    struct in_addr *addr;
    struct sockaddr_in address;
    char conn[255],*service,*errpos;

#ifdef WIN32
    WSADATA wsaData;
    WORD wVersionRequested;
    int err;

                                 // 2.0 and above version of WinSock
    wVersionRequested = MAKEWORD( 2, 0 );
    err = WSAStartup( wVersionRequested, &wsaData );
    if ( err != 0 ) {
        stringstream msg;
        msg << "Couldn't not find a usable WinSock DLL, WSAStartup, error= " << err;
        throw exce_t(errOpen,msg.str());
    }
#endif

    if (sock_fd >= 0)
        return;

    strcpy(conn,port.c_str());

    service=strstr(conn,":")+1;

    lport = strtol(service,&errpos,0);
    if ( (errpos[0] != 0) || (lport == 0) ) {
        stringstream msg;
        msg << "Invalid port number " << port.c_str();
        throw exce_t(errOpen,msg.str());
        return;                  /* Invalid port address */
    }
    lport = htons(lport);

    *(service-1)=0;

    addr = atoaddr(conn);
    if (addr == NULL) {
        stringstream msg;
        msg << "Failed to resolve addres " << port.c_str();
        throw exce_t(errOpen,msg.str());
        return;
    }

    memset((char *) &address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = (lport);
    address.sin_addr.s_addr = addr->s_addr;

    sock_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (connect(sock_fd, (struct sockaddr *) &address, sizeof(address)) < 0) {
        stringstream msg;
        msg << "Couldn't connect to " << port.c_str();
        throw exce_t(errOpen,msg.str());
        return;
    }

    if (sock_fd < 0) {
        stringstream msg;
        msg << "Failed to open socket device " << port.c_str();
        throw exce_t(errOpen,msg.str());
    }
}


void CTcp::close()
{
#ifdef WIN32
    WSACleanup();
#endif

    my_close (sock_fd);
    sock_fd= -1;
}


int CTcp::read(char * data)
{
    int bytes_read;
    size_t total_count = 0;
    char *current_position;
    char last_read = 0;
    time_t starttime;
    starttime= time_now();

    current_position = data;

    while (last_read != 10 && (time_now() < starttime + TIMEOUT)) {
        bytes_read = my_read(sock_fd, &last_read, 1);
        if (bytes_read <= 0) {
            return -1;
        }
        if ( (total_count < MAX_SIZE) && (last_read != 10) && (last_read !=13) ) {
            current_position[0] = last_read;
            current_position++;
            total_count++;
        }
    }

    current_position[0] = 0;
    return total_count;
}


void CTcp::write(char* data)
{
    size_t bytes_sent = 0;
    int this_write;
    int count=strlen(data);

    if (data[count]!=10) {
        data[count]=10;
        data[count+1]=0;
        count++;
    }

    while (bytes_sent < (size_t) count) {
        do
        this_write = my_write(sock_fd, data, count - bytes_sent);
        while ( (this_write < 0) && (errno == EINTR) );
        if (this_write <= 0)
            return;              // this_write;
        bytes_sent += this_write;
        data += this_write;
    }
    return;
}


// check if data available
int CTcp::sock_chars_ready( void)
{
    fd_set fds_read;
    struct timeval time;
    FD_ZERO( &fds_read);
    FD_SET( sock_fd, &fds_read);
    time.tv_sec= 0;
    time.tv_usec= 1000;
    select( sock_fd+1, &fds_read, NULL, NULL, &time);
    if (FD_ISSET( sock_fd, &fds_read)) {
        return 1;
    }
    return 0;
}


time_t CTcp::time_now( void)
{
    time_t seconds= 0;
    time( &seconds);             // FIXME: handle error case
    return( seconds);
}
