/*****************************************************************************
 *
 * $Id$
 *
 * Copyright (C) 2009 - 2012  Richard Hacker <lerich@gmx.net>
 *                            Florian Pose <fp@igh-essen.com>
 *
 * This file is part of the PdCom library.
 *
 * The PdCom library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The PdCom library 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 Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the PdCom Library. If not, see <http://www.gnu.org/licenses/>.
 *
 * \file PdCom example implementation.
 *
 ****************************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h> // gethostbyname()
#include <string.h> // memset()

#include <iostream>
#include <sstream>
#include <cstring>
using namespace std;

#include "LinuxProcess.h"
#include "config.h"

#define DEBUG_DATA 0

/****************************************************************************/

/** Constructor.
 */
LinuxProcess::LinuxProcess(
        const char *address, /**< IP address or host name. */
        int port /**< TCP port number. */
        ):
    PdCom::Process(),
    finished(false),
    writeRequest(false)
{
    stringstream err;
    struct sockaddr_in serverAddress;
    struct hostent *hostent;
    int flags;

    memset(&serverAddress, 0, sizeof(serverAddress));

    // Get a TCP file descriptor
    if ((fd = ::socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        err << "Failed to open socket: " << strerror(errno);
        throw Exception(err.str());
    }

    // Connect to server
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(port);
    if (!(hostent = gethostbyname(address))) {
        err << "Failed to resolve hostname: " << strerror(errno);
        throw Exception(err.str());
    }

    cerr << "Connecting to " << address << ":" << port << "..." << endl;

    memcpy((char *) &serverAddress.sin_addr, hostent->h_addr,
            sizeof(struct in_addr));
    if (connect(fd, (const struct sockaddr *) &serverAddress,
                sizeof(serverAddress))) {
        err << "Failed to connect: " << strerror(errno);
        throw Exception(err.str());
    }

    cerr << "Connected." << endl;

    // Make socket non-blocking here. This is not required by
    // the library, but it may be necessary in the implementation
    // environment, for example in a desktop application where
    // the event loop should never be blocked.
    if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
        err << "Failed to get flags: " << strerror(errno);
        throw Exception(err.str());
    }

    flags |= O_NONBLOCK;

    if (fcntl(fd, F_SETFL, flags) < 0) {
        err << "Failed to set flags: " << strerror(errno);
        throw Exception(err.str());
    }
}

/****************************************************************************/

/** Destructor.
 *
 * Do here whatever you need to when the class has to shutdown.
 */
LinuxProcess::~LinuxProcess()
{
    // Close the TCP socket
    close(fd);
}

/****************************************************************************/

/** Execute.
 */
void LinuxProcess::exec(void)
{
    fd_set read_fds, write_fds;
    fd_set in, out, *out_p;

    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    FD_SET(fd, &read_fds);
    FD_SET(fd, &write_fds);

    while (!finished) {
        in = read_fds;
        if (writeRequest) {
            out = write_fds;
            out_p = &out;
        } else {
            out_p = NULL;
        }

        select(fd + 1, &in, out_p, NULL, NULL);

        if (FD_ISSET(fd, &in)) { // new data
            try {
                if (readSocket() <= 0)
                    break;
            } catch (PdCom::Exception &e) {
                cerr << "Caught " << e.what() << endl;
                break;
            }
        }
        if (FD_ISSET(fd, &out)) { // ready to write
            if (writeSocket()) {
                cerr << "Write error." << endl;
                break;
            }
		}
    }

    cerr << "Finished." << endl;
}

/****************************************************************************/

/** Read data from TCP socket.
 *
 * If new data have arrived, send them down to PdCom::Process by calling
 * newData().
 */
int LinuxProcess::readSocket()
{
	int len, ret;
	char buf[4096];

	len = ::read(fd, buf, sizeof(buf));
    if (len > 0) {
#if DEBUG_DATA
        cerr << "read: " << string(buf, len) << endl;
#endif
        ret = newData(buf, len);
        return ret;
    } else {
        return len;
    }
}

/*****************************************************************************/

/* Write data to the TCP socket.
 */
int LinuxProcess::writeSocket()
{
    int wrRet, retval = 0;

    wrRet = writeReady();

    if (wrRet < 0) {
        // sendData() encountered an error.
        cerr << "writeReady() returned "  << wrRet << endl;
        finished = true;
        retval = wrRet;
    } else if (!wrRet) {
        // No more data to send
        writeRequest = false;
    }

    return retval;
}

/****************************************************************************/

/** New data to send.
 *
 * This method is called within the newData() call and signals that there are
 * new data to be sent to the process.
 *
 * When the socket is ready to send the data, writeReady() must be called.
 */
void LinuxProcess::sendRequest()
{
    writeRequest = true;
}

/****************************************************************************/

/** Request to send data.
 *
 * This method is called within the context of writeReady().
 *
 * \return Number of bytes that were successfully transferred.
 */
int LinuxProcess::sendData(
        const char *buf, /**< Buffer with data to send. */
        size_t len /**< Number of bytes in \a buf. */
        )
{
#if DEBUG_DATA
    cerr << "write: " << string(buf, len) << endl;
#endif
    return ::write(fd, buf, len);
}

/****************************************************************************/

/**
 */
bool LinuxProcess::clientInteraction(
        const string &,
        const string &,
        const string &,
        list<ClientInteraction> &interactionList
        )
{
    list<ClientInteraction>::iterator it;

    cerr << __func__ << "()" << endl; 

    for (it = interactionList.begin(); it != interactionList.end(); it++) {
        if (it->prompt == "Username") {
            char *login = getlogin();
            if (login)
                it->response = login;
        } else if (it->prompt == "Hostname") {
            char hostname[256];
            if (!gethostname(hostname, sizeof(hostname))) {
                it->response = hostname;
            }
        } else if (it->prompt == "Application") {
            it->response = PACKAGE " example";
        }
        cerr << it->prompt << "=" << it->response << endl;
    }

    return true;
}

/****************************************************************************/
