/*****************************************************************************
 *
 * $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/>.
 *
 ****************************************************************************/

#include <stdio.h> // EOF
#include <string.h> // memcpy()

#include "pdcom/Process.h"

#include "ProcessStreambuf.h"

using namespace PdCom;

#define DEBUG 0

#if DEBUG
#include <iostream>
#endif

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

ProcessStreambuf::ProcessStreambuf(Process* p,
        unsigned int buflen, unsigned int max_buffers):
    std::streambuf(), process(p), buflen(buflen), max_buffers(max_buffers)
{
    reset();
}

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

ProcessStreambuf::~ProcessStreambuf()
{
    reset();
}

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

void ProcessStreambuf::reset()
{
    for (BufferList::iterator it = buf.begin(); it != buf.end(); it++)
        delete[] *it;
    buf.clear();
    wptr = wbuf = ibuf = 0;
    setp(0, 0);
}

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

bool ProcessStreambuf::hasData() const
{
    // either there is more than one buffer,
    // or the write pointer is different from the position pointer
    // in the single buffer
    return buf.size() > 2 || (wptr && pptr() > wptr);
}

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

// Called by RTCom::Process when the output stream is ready
int ProcessStreambuf::writeReady()
{
#if DEBUG
    std::cerr << "ProcessStreambuf::writeReady()" << std::endl;
#endif

    int sentSize, sizeToSend;
    bool sameBuffer = wbuf == ibuf;

    if (!wptr || pptr() == wptr)
        return 0;

    sizeToSend = sameBuffer ? pptr() - wptr : wbuf + buflen - wptr;

#if DEBUG
    std::cerr << "Sending " << sizeToSend
        << " bytes from buf " << (void *) wbuf << " " << sameBuffer << std::endl;
#endif
    sentSize = process->sendData(wptr, sizeToSend);
#if DEBUG
    std::cerr << "Sent " << sentSize << " bytes from " << (void*) wptr << std::endl;
    std::cerr << "<<<< Data: " << std::string(wptr, sentSize) << std::endl;
#endif

    if (sentSize < 0) // send error encountered; return immediately
        return sentSize;

    sentSize = std::min(sentSize, sizeToSend);

    if (sameBuffer) {
        /* Writing and reading on the same buffer */

        if (sentSize == sizeToSend) {
#if DEBUG
            std::cerr << "All buffers empty." << std::endl;
#endif
            // move pointers to beginning of ibuf
            wptr = ibuf;
            pbump(ibuf - pptr());
        } else {
            wptr += sentSize;
        }
    } else {
        /* Writing and reading on different buffers */

        if (sentSize == sizeToSend) {
            delete wbuf;
            buf.pop_front();
            wptr = wbuf = buf.front();
        } else {
            wptr += sentSize;
        }
    }

    return hasData();
}

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

// Called when a larger buffer has to be written
std::streamsize ProcessStreambuf::xsputn(const char *s, std::streamsize len)
{
    std::streamsize n, count = 0;

#if DEBUG
    std::cerr << "ProcessStreambuf::xsputn(" << std::string(s,len) << ")" << std::endl;
#endif

    while (count != len) {
        if (pptr() == epptr()) {
            // Current buffer is full
            if (new_page() == EOF)
                return count;
            continue;
        }

        n = std::min(epptr() - pptr(), len - count);
        memcpy(pptr(), s + count, n);
        pbump(n);
        count += n;
    }

    process->sendRequest();

    return count;
}

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

// When the output buffer is full and another char has to be put,
// this method is called
int ProcessStreambuf::overflow(int c)
{
#if DEBUG
    std::cerr << "ProcessStreambuf::overflow(" << c << ")" << std::endl;
#endif
    if (new_page() == EOF)
        return EOF;

    *pptr() = c;
    pbump(1);

    return 0;
}

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

int ProcessStreambuf::new_page()
{
#if DEBUG
    std::cerr << "ProcessStreambuf::new_page()" << std::endl;
#endif
    /* Tell the ProcessStreambuf class that the buffer is full. Maybe this is
     * successful in freeing up the buffer */
    if (wbuf) {
#if DEBUG
        std::cerr << "first calling sendRequest()" << std::endl;
#endif
        process->sendRequest();

        if (pptr() < epptr())
            return 0;
    }

    // Move data to beginning of buffer
    if (ibuf && wbuf == ibuf && wptr != wbuf) {
        // Only one buffer is active and whats more, the write pointer
        // does not point to buffer start, meaning that we can make space
        // by moving the data forward. Try this first
        std::streamsize size = pptr() - wptr;
#if DEBUG
        std::cerr << "moving " << size << " bytes to buffer start" << std::endl;
#endif
        memmove(ibuf, wptr, size);
        pbump(wbuf - wptr);
        wptr = wbuf;
    }
    else {
        // Have to allocate new data space

        // If max_buffers != 0, check for max buffers
        if (max_buffers && buf.size() == max_buffers) {
            // Buffer is really full
            return EOF;
        }

        ibuf = new char[buflen];
#if DEBUG
        std::cerr << "got new buffer " << (void*)ibuf << std::endl;
#endif
        buf.push_back(ibuf);
        setp(ibuf, ibuf + buflen);
        if (!wbuf) {
            wptr = wbuf = ibuf;
        }
    }

    return 0;
}

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

int ProcessStreambuf::sync()
{
    process->sendRequest();
    return 0;
}

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