/*
 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 "JackWinNamedPipeServerChannel.h"
#include "JackNotification.h"
#include "JackRequest.h"
#include "JackServer.h"
#include "JackLockedEngine.h"
#include "JackGlobals.h"
#include "JackClient.h"
#include "JackNotification.h"
#include "JackException.h"
#include <assert.h>

using namespace std;

namespace Jack
{

HANDLE JackClientPipeThread::fMutex = NULL;  // Never released....

// fRefNum = -1 correspond to already removed client

JackClientPipeThread::JackClientPipeThread(JackWinNamedPipeClient* pipe)
    :fPipe(pipe), fDecoder(NULL), fServer(NULL), fThread(this), fRefNum(0)
{
    // First one allocated the static fMutex
    if (fMutex == NULL) {
        fMutex = CreateMutex(NULL, FALSE, NULL);
    }
}

JackClientPipeThread::~JackClientPipeThread()
{
    jack_log("JackClientPipeThread::~JackClientPipeThread");
    delete fPipe;
}

int JackClientPipeThread::Open(JackServer* server)      // Open the Server/Client connection
{
    // Start listening
    if (fThread.Start() != 0) {
        jack_error("Cannot start Jack server listener\n");
        return -1;
    } else {
        fDecoder = new JackRequestDecoder(server, this);
        fServer = server;
        return 0;
    }
}

void JackClientPipeThread::Close()                                      // Close the Server/Client connection
{
    jack_log("JackClientPipeThread::Close 0 %x %ld", this, fRefNum);

    //fThread.Kill();
    fPipe->Close();
    fRefNum = -1;

    delete fDecoder;
    fDecoder = NULL;
}

bool JackClientPipeThread::Execute()
{
    try {

        jack_log("JackClientPipeThread::Execute %x", this);
        JackRequest header;
        int res = header.Read(fPipe);
        bool ret = true;

        // Lock the global mutex
        if (WaitForSingleObject(fMutex, INFINITE) == WAIT_FAILED) {
            jack_error("JackClientPipeThread::Execute : mutex wait error");
        }

        // Decode header
        if (res < 0) {
            jack_log("JackClientPipeThread::Execute : cannot decode header");
            ClientKill();
            ret = false;
        // Decode request
        } else if (fDecoder->HandleRequest(fPipe, header.fType) < 0) {
            ret = false;
        }

        // Unlock the global mutex
        if (!ReleaseMutex(fMutex)) {
            jack_error("JackClientPipeThread::Execute : mutex release error");
        }
        return ret;

    } catch (JackQuitException& e) {
        jack_log("JackClientPipeThread::Execute : JackQuitException");
        return false;
    }
}

void JackClientPipeThread::ClientAdd(detail::JackChannelTransactionInterface* socket, JackClientOpenRequest* req, JackClientOpenResult *res)
{
    jack_log("JackClientPipeThread::ClientAdd %x %s", this, req->fName);
    fRefNum = -1;
    res->fResult = fServer->GetEngine()->ClientExternalOpen(req->fName, req->fPID, req->fUUID, &fRefNum, &res->fSharedEngine, &res->fSharedClient, &res->fSharedGraph);
}

void JackClientPipeThread::ClientRemove(detail::JackChannelTransactionInterface* socket_aux, int refnum)
{
    jack_log("JackClientPipeThread::ClientRemove ref = %d", refnum);
    Close();
}

void JackClientPipeThread::ClientKill()
{
    jack_log("JackClientPipeThread::ClientKill ref = %d", fRefNum);

    if (fRefNum == -1) {        // Correspond to an already removed client.
        jack_log("Kill a closed client %x", this);
    } else if (fRefNum == 0) {  // Correspond to a still not opened client.
        jack_log("Kill a not opened client %x", this);
    } else {
        fServer->GetEngine()->ClientKill(fRefNum);
    }

    Close();
}

JackWinNamedPipeServerChannel::JackWinNamedPipeServerChannel():fThread(this)
{}

JackWinNamedPipeServerChannel::~JackWinNamedPipeServerChannel()
{
    std::list<JackClientPipeThread*>::iterator it;

    for (it = fClientList.begin(); it !=  fClientList.end(); it++) {
        JackClientPipeThread* client = *it;
        client->Close();
        delete client;
    }
}

int JackWinNamedPipeServerChannel::Open(const char* server_name, JackServer* server)
{
    jack_log("JackWinNamedPipeServerChannel::Open");
    snprintf(fServerName, sizeof(fServerName), server_name);

    // Needed for internal connection from JackWinNamedPipeServerNotifyChannel object
    if (ClientListen()) {
        fServer = server;
        return 0;
    } else {
        jack_error("JackWinNamedPipeServerChannel::Open : cannot create result listen pipe");
        return -1;
    }
}

void JackWinNamedPipeServerChannel::Close()
{
    /* TODO : solve WIN32 thread Kill issue
    This would hang the server... since we are quitting it, its not really problematic,
    all resources will be deallocated at the end.

    fRequestListenPipe.Close();
    fThread.Stop();
    */

    fRequestListenPipe.Close();
}

int JackWinNamedPipeServerChannel::Start()
{
    if (fThread.Start() != 0) {
        jack_error("Cannot start Jack server listener");
        return -1;
    } else {
        return 0;
    }
}

void JackWinNamedPipeServerChannel::Stop()
{
    fThread.Kill();
}

bool JackWinNamedPipeServerChannel::Init()
{
    jack_log("JackWinNamedPipeServerChannel::Init");
    // Accept first client, that is the JackWinNamedPipeServerNotifyChannel object
    return ClientAccept();
}

bool JackWinNamedPipeServerChannel::ClientListen()
{
    if (fRequestListenPipe.Bind(jack_server_dir, fServerName, 0) < 0) {
        jack_error("JackWinNamedPipeServerChannel::ClientListen : cannot create result listen pipe");
        return false;
    } else {
        return true;
    }
}

bool JackWinNamedPipeServerChannel::ClientAccept()
{
     JackWinNamedPipeClient* pipe;

     if ((pipe = fRequestListenPipe.AcceptClient()) == NULL) {
        jack_error("JackWinNamedPipeServerChannel::ClientAccept : cannot connect pipe");
        return false;
    } else {
        ClientAdd(pipe);
        return true;
    }
}

bool JackWinNamedPipeServerChannel::Execute()
{
    if (!ClientListen()) {
       return false;
    }

    return ClientAccept();
}

void JackWinNamedPipeServerChannel::ClientAdd(JackWinNamedPipeClient* pipe)
{
    // Remove dead (= not running anymore) clients.
    std::list<JackClientPipeThread*>::iterator it = fClientList.begin();
    JackClientPipeThread* client;

    jack_log("JackWinNamedPipeServerChannel::ClientAdd size %ld", fClientList.size());

    while (it != fClientList.end()) {
        client = *it;
        if (client->IsRunning()) {
            it++;
        } else {
            it = fClientList.erase(it);
            delete client;
        }
    }

    client = new JackClientPipeThread(pipe);
    client->Open(fServer);

    // Here we are sure that the client is running (because it's thread is in "running" state).
    fClientList.push_back(client);
}

} // end of namespace


