/*
 Copyright (C) 2004-2008 Grame

 This program 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 2.1 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 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser 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.

 */


#include "JackWinNamedPipe.h"
#include "JackError.h"
#include <assert.h>
#include <stdio.h>

#define BUFSIZE 4096

namespace Jack
{

int JackWinNamedPipeAux::ReadAux(void* data, int len)
{
    DWORD read;
    BOOL res = ReadFile(fNamedPipe, data, len, &read, NULL);
    if (res && read == (DWORD)len) {
        return 0;
    } else {
        jack_log("Cannot read named pipe name = %s err = %ld", fName, GetLastError());
        return -1;
    }
}

int JackWinNamedPipeAux::WriteAux(void* data, int len)
{
    DWORD written;
    BOOL res = WriteFile(fNamedPipe, data, len, &written, NULL);
    if (res && written == (DWORD)len) {
        return 0;
    } else {
        jack_log("Cannot write named pipe name = %s err = %ld", fName, GetLastError());
        return -1;
    }
}

/*
See :
    http://answers.google.com/answers/threadview?id=430173
    http://msdn.microsoft.com/en-us/library/windows/desktop/aa365800(v=vs.85).aspx
*/

/*
int JackWinNamedPipeClient::ConnectAux()
{
    fNamedPipe = CreateFile(fName, 			 // pipe name
                            GENERIC_READ |   // read and write access
                            GENERIC_WRITE,
                            0,               // no sharing
                            NULL,            // default security attributes
                            OPEN_EXISTING,   // opens existing pipe
                            0,               // default attributes
                            NULL);          // no template file

    if (fNamedPipe == INVALID_HANDLE_VALUE) {
        jack_error("Cannot connect to named pipe = %s err = %ld", fName, GetLastError());
        return -1;
    } else {
        return 0;
    }
}
*/

int JackWinNamedPipeClient::ConnectAux()
{
    jack_log("JackWinNamedPipeClient::ConnectAux : fName %s", fName);

    while (true) {

        fNamedPipe = CreateFile(fName, 			 // pipe name
                                GENERIC_READ |   // read and write access
                                GENERIC_WRITE,
                                0,               // no sharing
                                NULL,            // default security attributes
                                OPEN_EXISTING,   // opens existing pipe
                                0,               // default attributes
                                NULL);           // no template file

        // Break if the pipe handle is valid.
        if (fNamedPipe != INVALID_HANDLE_VALUE) {
            return 0;
        }

        // Exit if an error other than ERROR_PIPE_BUSY or ERROR_FILE_NOT_FOUND occurs.
        if ((GetLastError() != ERROR_PIPE_BUSY) && (GetLastError() != ERROR_FILE_NOT_FOUND)) {
            jack_error("Cannot connect to named pipe = %s err = %ld", fName, GetLastError());
            return -1;
        }

        // All pipe instances are busy, so wait for 2 seconds.
        if (!WaitNamedPipe(fName, 2000)) {
            jack_error("Cannot connect to named pipe after wait = %s err = %ld", fName, GetLastError());
            return -1;
        }
    }
}

int JackWinNamedPipeClient::Connect(const char* dir, int which)
{
    snprintf(fName, sizeof(fName), "\\\\.\\pipe\\%s_jack_%d", dir, which);
    return ConnectAux();
}

int JackWinNamedPipeClient::Connect(const char* dir, const char* name, int which)
{
    snprintf(fName, sizeof(fName), "\\\\.\\pipe\\%s_jack_%s_%d", dir, name, which);
    return ConnectAux();
}

int JackWinNamedPipeClient::Close()
{
    if (fNamedPipe != INVALID_HANDLE_VALUE) {
        CloseHandle(fNamedPipe);
        fNamedPipe = INVALID_HANDLE_VALUE;
        return 0;
    } else {
        return -1;
    }
}

void JackWinNamedPipeClient::SetReadTimeOut(long sec)
{
    /*
    COMMTIMEOUTS timeout;
    if (GetCommTimeouts(fNamedPipe, &timeout)) {
        jack_info("JackWinNamedPipeClient::SetReadTimeOut ReadIntervalTimeout = %d", timeout.ReadIntervalTimeout);
        jack_info("JackWinNamedPipeClient::SetReadTimeOut ReadTotalTimeoutMultiplier = %d", timeout.ReadTotalTimeoutMultiplier);
        jack_info("JackWinNamedPipeClient::SetReadTimeOut ReadTotalTimeoutConstant = %d", timeout.ReadTotalTimeoutConstant);
    } else {
        jack_error("JackWinNamedPipeClient::SetReadTimeOut err %d", GetLastError());
    }
    */
}

void JackWinNamedPipeClient::SetWriteTimeOut(long sec)
{
    /*
    COMMTIMEOUTS timeout;
    if (GetCommTimeouts(fNamedPipe, &timeout)) {
        jack_info("JackWinNamedPipeClient::SetWriteTimeOut WriteTotalTimeoutMultiplier = %d", timeout.WriteTotalTimeoutMultiplier);
        jack_info("JackWinNamedPipeClient::SetWriteTimeOut WriteTotalTimeoutConstant = %d", timeout.WriteTotalTimeoutConstant);
    }
    */
}

void JackWinNamedPipeClient::SetNonBlocking(bool onoff)
{}

JackWinAsyncNamedPipeClient::JackWinAsyncNamedPipeClient()
        : JackWinNamedPipeClient(), fPendingIO(false), fIOState(kIdle)
{
    fIOState = kIdle;
    fOverlap.hEvent = CreateEvent(NULL,     // default security attribute
                                  TRUE,     // manual-reset event
                                  TRUE,     // initial state = signaled
                                  NULL);    // unnamed event object
}

JackWinAsyncNamedPipeClient::JackWinAsyncNamedPipeClient(HANDLE pipe, const char* name, bool pending)
        : JackWinNamedPipeClient(pipe, name), fPendingIO(pending), fIOState(kIdle)
{
    fOverlap.hEvent = CreateEvent(NULL,     // default security attribute
                                  TRUE,     // manual-reset event
                                  TRUE,     // initial state = signaled
                                  NULL);	// unnamed event object

    if (!fPendingIO) {
        SetEvent(fOverlap.hEvent);
    }

    fIOState = (fPendingIO) ? kConnecting : kReading;
}

JackWinAsyncNamedPipeClient::~JackWinAsyncNamedPipeClient()
{
    CloseHandle(fOverlap.hEvent);
}

int JackWinAsyncNamedPipeClient::FinishIO()
{
    DWORD success, ret;
    success = GetOverlappedResult(fNamedPipe, 	// handle to pipe
                                  &fOverlap, 	// OVERLAPPED structure
                                  &ret,         // bytes transferred
                                  FALSE);       // do not wait

    switch (fIOState) {

        case kConnecting:
            if (!success) {
                jack_error("Connection error");
                return -1;
            } else {
                fIOState = kReading;
                // Prepare connection for new client ??
            }
            break;

        case kReading:
            if (!success || ret == 0) {
                return -1;
            }
            fIOState = kWriting;
            break;

        case kWriting:
            if (!success || ret == 0) {
                return -1;
            }
            fIOState = kReading;
            break;

        default:
            break;
    }

    return 0;
}

int JackWinAsyncNamedPipeClient::Read(void* data, int len)
{
    DWORD read;
    jack_log("JackWinNamedPipeClient::Read len = %ld", len);
    BOOL res = ReadFile(fNamedPipe, data, len, &read, &fOverlap);

    if (res && read != 0) {
        fPendingIO = false;
        fIOState = kWriting;
        return 0;
    } else if (!res && GetLastError() == ERROR_IO_PENDING) {
        fPendingIO = true;
        return 0;
    } else {
        jack_error("Cannot read named pipe err = %ld", GetLastError());
        return -1;
    }
}

int JackWinAsyncNamedPipeClient::Write(void* data, int len)
{
    DWORD written;
    jack_log("JackWinNamedPipeClient::Write len = %ld", len);
    BOOL res = WriteFile(fNamedPipe, data, len, &written, &fOverlap);

    if (res && written != 0) {
        fPendingIO = false;
        fIOState = kWriting;
        return 0;
    } else if (!res && GetLastError() == ERROR_IO_PENDING) {
        fPendingIO = true;
        return 0;
    } else {
        jack_error("Cannot write named pipe err = %ld", GetLastError());
        return -1;
    }
}

// Server side
int JackWinNamedPipeServer::BindAux()
{
    jack_log("JackWinNamedPipeServer::BindAux : fName %s", fName);

    if ((fNamedPipe = CreateNamedPipe(fName,
                                      PIPE_ACCESS_DUPLEX,       // read/write access
                                      PIPE_TYPE_MESSAGE |       // message type pipe
                                      PIPE_READMODE_MESSAGE |   // message-read mode
                                      PIPE_WAIT,                // blocking mode
                                      PIPE_UNLIMITED_INSTANCES, // max. instances
                                      BUFSIZE,  // output buffer size
                                      BUFSIZE,  // input buffer size
                                      INFINITE, // client time-out
                                      NULL)) == INVALID_HANDLE_VALUE) { // no security
        jack_error("Cannot bind server to pipe err = %ld", GetLastError());
        return -1;
    } else {
        return 0;
    }
}

int JackWinNamedPipeServer::Bind(const char* dir, int which)
{
     snprintf(fName, sizeof(fName), "\\\\.\\pipe\\%s_jack_%d", dir, which);
     return BindAux();
}

int JackWinNamedPipeServer::Bind(const char* dir, const char* name, int which)
{
    snprintf(fName, sizeof(fName), "\\\\.\\pipe\\%s_jack_%s_%d", dir, name, which);
    return BindAux();
}

bool JackWinNamedPipeServer::Accept()
{
    if (ConnectNamedPipe(fNamedPipe, NULL)) {
        return true;
    } else {
        jack_error("Cannot connect server pipe name = %s err = %ld", fName, GetLastError());
        if (GetLastError() == ERROR_PIPE_CONNECTED) {
            jack_error("Pipe already connected = %s", fName);
            return true;
        } else {
            return false;
        }
    }
}

JackWinNamedPipeClient* JackWinNamedPipeServer::AcceptClient()
{
    if (ConnectNamedPipe(fNamedPipe, NULL)) {
        JackWinNamedPipeClient* client = new JackWinNamedPipeClient(fNamedPipe, fName);
        // Init the pipe to the default value
        fNamedPipe = INVALID_HANDLE_VALUE;
        return client;
    } else {
        switch (GetLastError()) {

            case ERROR_PIPE_CONNECTED:
                return new JackWinNamedPipeClient(fNamedPipe, fName);

            default:
                jack_error("Cannot connect server pipe name = %s  err = %ld", fName, GetLastError());
                return NULL;
        }
    }
}

int JackWinNamedPipeServer::Close()
{
    jack_log("JackWinNamedPipeServer::Close");

    if (fNamedPipe != INVALID_HANDLE_VALUE) {
        DisconnectNamedPipe(fNamedPipe);
        CloseHandle(fNamedPipe);
        fNamedPipe = INVALID_HANDLE_VALUE;
        return 0;
    } else {
        return -1;
    }
}

// Server side

int JackWinAsyncNamedPipeServer::BindAux()
{
    jack_log("JackWinAsyncNamedPipeServer::BindAux : fName %s", fName);

    if ((fNamedPipe = CreateNamedPipe(fName,
                                      PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,  // read/write access
                                      PIPE_TYPE_MESSAGE |  // message type pipe
                                      PIPE_READMODE_MESSAGE |  // message-read mode
                                      PIPE_WAIT,  // blocking mode
                                      PIPE_UNLIMITED_INSTANCES,  // max. instances
                                      BUFSIZE,  // output buffer size
                                      BUFSIZE,  // input buffer size
                                      INFINITE,  // client time-out
                                      NULL)) == INVALID_HANDLE_VALUE) { // no security a
        jack_error("Cannot bind server to pipe err = %ld", GetLastError());
        return -1;
    } else {
        return 0;
    }
}

int JackWinAsyncNamedPipeServer::Bind(const char* dir, int which)
{
    snprintf(fName, sizeof(fName), "\\\\.\\pipe\\%s_jack_%d", dir, which);
    return BindAux();
}

int JackWinAsyncNamedPipeServer::Bind(const char* dir, const char* name, int which)
{
    snprintf(fName, sizeof(fName), "\\\\.\\pipe\\%s_jack_%s_%d", dir, name, which);
    return BindAux();
}

bool JackWinAsyncNamedPipeServer::Accept()
{
    return false;
}

JackWinNamedPipeClient* JackWinAsyncNamedPipeServer::AcceptClient()
{
    if (ConnectNamedPipe(fNamedPipe, NULL)) {
        return new JackWinAsyncNamedPipeClient(fNamedPipe, fName, false);
    } else {
        switch (GetLastError()) {

            case ERROR_IO_PENDING:
                return new JackWinAsyncNamedPipeClient(fNamedPipe, fName, true);

            case ERROR_PIPE_CONNECTED:
                return new JackWinAsyncNamedPipeClient(fNamedPipe, fName, false);

            default:
                jack_error("Cannot connect server pipe name = %s err = %ld", fName, GetLastError());
                return NULL;
                break;
        }
    }
}

} // end of namespace

