// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// vim:set sts=4 ts=8:

// Copyright (c) 2001-2011 XORP, Inc and Others
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License, Version
// 2.1, June 1999 as published by the Free Software Foundation.
// Redistribution and/or modification of this program under the terms of
// any other version of the GNU Lesser General Public License is not
// permitted.
// 
// 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. For more details,
// see the GNU Lesser General Public License, Version 2.1, a copy of
// which can be found in the XORP LICENSE.lgpl file.
// 
// XORP, Inc, 2953 Bunker Hill Lane, Suite 204, Santa Clara, CA 95054, USA;
// http://xorp.net


#include "libxorp/libxorp_module.h"
#include "libxorp/xorp.h"

#ifdef HOST_OS_WINDOWS	    // This entire file is for Windows only.

#include "libxorp/debug.h"
#include "libxorp/xlog.h"
#include "libxorp/win_io.h"
#include "libxorp/xorpfd.hh"
#include "libxorp/timeval.hh"
#include "libxorp/clock.hh"
#include "libxorp/callback.hh"
#include "libxorp/ioevents.hh"
#include "libxorp/eventloop.hh"
#include "libxorp/win_dispatcher.hh"



static inline int
_wsa2ioe(const long wsaevent)
{
    switch (wsaevent) {
    case FD_READ:
	return IOT_READ;
    case FD_WRITE:
	return IOT_WRITE;
    case FD_OOB:
	return IOT_EXCEPTION;
    case FD_ACCEPT:
	return IOT_ACCEPT;
    case FD_CONNECT:
	return IOT_CONNECT;
    case FD_CLOSE:
	return IOT_DISCONNECT;
    }
    return -1;
}

static inline long
_ioe2wsa(const IoEventType ioevent)
{
    switch (ioevent) {
    case IOT_READ:
	return FD_READ;
    case IOT_WRITE:
	return FD_WRITE;
    case IOT_EXCEPTION:
	return FD_OOB;
    case IOT_ACCEPT:
	return FD_ACCEPT;
    case IOT_CONNECT:
	return FD_CONNECT;
    case IOT_DISCONNECT:
	return FD_CLOSE;
    case IOT_ANY:
	return (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE);
    }
    return 0;
}

WinDispatcher::WinDispatcher(ClockBase* clock)
    : _clock(clock),
      _descriptor_count(0)
{
    // Avoid introducing a circular dependency upon libcomm by calling
    // WSAStartup() and WSACleanup() ourselves. It's safe for the same
    // thread within the same process to call it more than once.
    WSADATA wsadata;
    WORD version = MAKEWORD(2, 2);
    int retval = WSAStartup(version, &wsadata);
    if (retval != 0) {
	XLOG_FATAL("WSAStartup() error: %s", win_strerror(GetLastError()));
    }

    _handles.reserve(MAXIMUM_WAIT_OBJECTS);
}

WinDispatcher::~WinDispatcher()
{
    // Purge all pending socket event notifications from Winsock.
    for (SocketEventMap::iterator ii = _socket_event_map.begin();
	 ii != _socket_event_map.end(); ++ii) {
	WSAEventSelect(ii->first, NULL, 0);
    }
    WSACleanup();
}

bool
WinDispatcher::add_socket_cb(XorpFd& fd, IoEventType type, const IoEventCb& cb,
			     int priority)
{
    UNUSED(priority);		// TODO: XXX: we should really use this

    // XXX: Currently we only support 1 callback per socket/event tuple.
    // Check that the socket does not already have a callback
    // registered for it.
    IoEventMap::iterator ii = _callback_map.find(IoEventTuple(fd, type));
    if (ii != _callback_map.end()) {
	debug_msg("callback already registered.\n");
	return false;
    }

    long oldmask;
    long newmask = _ioe2wsa(type);

    if (newmask == 0 || (newmask & ~WSAEVENT_SUPPORTED) != 0) {
	debug_msg("IoEventType does not map to a valid Winsock async "
		  "event mask.\n");
	return false;
    }

    HANDLE hevent = WSA_INVALID_EVENT;

    // Check if there are notifications already requested for
    // this socket. Listening sockets must only have IOT_ACCEPT pending.
    SocketEventMap::iterator jj = _socket_event_map.find(fd);
    if (jj != _socket_event_map.end()) {
	hevent = jj->second.first;
	oldmask = jj->second.second;
	if (((oldmask & ~FD_ACCEPT) && (newmask &  FD_ACCEPT)) ||
	    ((oldmask &  FD_ACCEPT) && (newmask & ~FD_ACCEPT))) {
	    debug_msg("attempt to register a socket for other events "
		     "in addition to IOT_ACCEPT.\n");
	    return false;
	}
    } else {
	//
	// This is a new map entry. Create an event object and
	// wire it up to the main dispatch.
	//
	hevent = WSACreateEvent();
	oldmask = 0;

#if 1
	// Paranoid check to see if new event handle is already
	// in the vector we pass off to WFMO.
	vector<HANDLE>::iterator qq;
	for (qq = _handles.begin(); qq != _handles.end(); qq++) {
	    if (*qq == hevent) {
		XLOG_FATAL("New event handle already exists in _handles");
	    }
	}
#endif

	// Insert the new entry into the socket event map.
	pair<SocketEventMap::iterator, bool> p =
	    _socket_event_map.insert(std::make_pair(fd,
					std::make_pair(hevent, oldmask)));
	XLOG_ASSERT(p.second == true);
	jj = p.first;

	// Insert the new event object into the WFMO handle vector,
	// and the event->socket map used for WFMO dispatch.
	_handles.push_back(hevent);
	XLOG_ASSERT(_handles.size() < MAXIMUM_WAIT_OBJECTS);

	pair <EventSocketMap::iterator, bool> q =
	    _event_socket_map.insert(std::make_pair(hevent, fd));
	XLOG_ASSERT(q.second == true);

	// Increment the descriptor count only if this is a new entry
	_descriptor_count++;
    }

    XLOG_ASSERT(hevent != WSA_INVALID_EVENT);
    XLOG_ASSERT(jj != _socket_event_map.end());

    // An existing map entry merely needs to have its WSA event mask
    // updated, and a call to WSAEventSelect() to update the mask.
    newmask |= oldmask;
    int retval = WSAEventSelect(fd, hevent, newmask);
    if (retval != 0) {
	// XXX: Need more paranoid error checking here.
	debug_msg("WSAEventSelect() error: %s",
		  win_strerror(WSAGetLastError()));
    }
    jj->second.second = newmask;

    // Wire up the XORP I/O callback.
    _callback_map.insert(std::make_pair(IoEventTuple(fd, type), cb));

    return true;
}

bool
WinDispatcher::add_handle_cb(XorpFd& fd, IoEventType type, const IoEventCb& cb,
			     int priority)
{
    UNUSED(priority);		// TODO: XXX: we should really use this

    // You cannot currently register for anything other
    // than an IOT_EXCEPTION event on a Windows object handle because
    // there is no way of telling why an object was signalled --
    // they are 'special'.
    if (fd.is_pipe() || fd.is_console()) {
	XLOG_ASSERT(type == IOT_READ || type == IOT_DISCONNECT);
    } else {
	XLOG_ASSERT(type == IOT_EXCEPTION);
    }

    // Check that we haven't exceeded the handle limit.
    if (_handles.size() == MAXIMUM_WAIT_OBJECTS) {
	XLOG_WARNING("exceeded Windows object handle count\n");
	return false;
    }

    // XXX: Currently we only support 1 callback per Windows object/event.
    // Check that the object does not already have a callback
    // registered for it.
    IoEventMap::iterator ii = _callback_map.find(IoEventTuple(fd, type));
    if (ii != _callback_map.end()) {
	XLOG_WARNING("callback already registered for object\n");
	return false;
    }

    // Register the callback. Add fd to the object handle array.
    _callback_map.insert(std::make_pair(IoEventTuple(fd, type), cb));

    if (fd.is_pipe()) {
	_polled_pipes.push_back(fd);
    } else {
	_handles.push_back(fd);
    }

    _descriptor_count++;

    return true;
}

bool
WinDispatcher::add_ioevent_cb(XorpFd fd, IoEventType type, const IoEventCb& cb,
			     int priority)
{
    debug_msg("Adding event %d to object %s\n", type, fd.str().c_str());

    switch (fd.type()) {
    case XorpFd::FDTYPE_SOCKET:
	return add_socket_cb(fd, type, cb, priority);
	break;
    case XorpFd::FDTYPE_PIPE:
    case XorpFd::FDTYPE_CONSOLE:
    case XorpFd::FDTYPE_PROCESS:
	return add_handle_cb(fd, type, cb, priority);
	break;
    default:
	break;
    }

    return false;
}

bool
WinDispatcher::remove_socket_cb(XorpFd& fd, IoEventType type)
{
    bool unregistered = false;
    int retval;

    SocketEventMap::iterator ii = _socket_event_map.find(fd);
    if (ii == _socket_event_map.end()) {
	debug_msg("Attempt to remove unmapped callback of type %d "
		  "from socket %s.\n", type, fd.str().c_str());
	return false;
    }

    // Compute the new event mask. Set it to zero if we were asked to
    // deregister this socket completely. 
    long delmask = _ioe2wsa(type);
    XLOG_ASSERT(delmask != -1);
    long newmask = ii->second.second & ~delmask;
    HANDLE hevent = ii->second.first;

    if (newmask != 0) {
	// WSA event mask is non-zero; just update mask.
	retval = WSAEventSelect(fd, hevent, newmask);
	if (retval != 0) {
	    debug_msg("WSAEventSelect() error: %s",
		      win_strerror(WSAGetLastError()));
	}
	ii->second.second = newmask;
    } else {
	// WSA event mask is now zero; purge completely.
	retval = WSAEventSelect(fd, NULL, 0);
	if (retval != 0) {
	    debug_msg("WSAEventSelect() error: %s",
		      win_strerror(WSAGetLastError()));
	}
	bool found = false;
	vector<HANDLE>::iterator qq;
	for (qq = _handles.begin(); qq != _handles.end(); qq++) {
	    if (*qq == hevent) {
		found = true;
		qq = _handles.erase(qq);
		break;
	    }
	}
	XLOG_ASSERT(found == true);
	WSACloseEvent(hevent);
	EventSocketMap::iterator jj = _event_socket_map.find(hevent);
	XLOG_ASSERT(jj != _event_socket_map.end());
	_socket_event_map.erase(ii);
	_event_socket_map.erase(jj);

	// Decrement the descriptor count only if the entry was purged
	_descriptor_count--;
    }

    //
    // Deal with IOT_ANY shorthand removals as per old-style
    // dispatch management code (some code notably BGP does not
    // remove its callbacks individually).
    //
    // XXX: Requests to remove all callbacks in the old model's
    // shorthand will mean the return value of this function
    // can't be relied upon as its meaning is then overloaded.
    //
    int imin, imax;
    if (type == IOT_ANY) {
	imin = IOT_READ;
	imax = IOT_DISCONNECT;
    } else {
	imin = imax = (int)type;
    }

    for (int i = imin; i <= imax; i++) {
	IoEventType itype = static_cast<IoEventType>(i);
	IoEventMap::iterator jj = _callback_map.find(IoEventTuple(fd, itype));
	if (jj != _callback_map.end()) {
	    _callback_map.erase(jj);
	    unregistered = true;
	} else {
	    debug_msg("Attempt to remove a nonexistent callback of "
		      "type %d from socket %s.\n", itype, fd.str().c_str());
	}
    }

    return unregistered;
}

bool
WinDispatcher::remove_handle_cb(XorpFd& fd, IoEventType type)
{
    if (fd.is_pipe()) {
	XLOG_ASSERT(type == IOT_READ || type == IOT_DISCONNECT);
	for (vector<HANDLE>::iterator ii = _polled_pipes.begin();
	    ii != _polled_pipes.end(); ++ii) {
	    if (*ii == fd) {
		ii = _polled_pipes.erase(ii);
		_callback_map.erase(IoEventTuple(fd, type));
		_descriptor_count--;
		return true;
	    }
	}
    } else {
	if (fd.is_console()) {
	    XLOG_ASSERT(type == IOT_READ);
	} else {
	    XLOG_ASSERT(type == IOT_EXCEPTION);
	}
	for (vector<HANDLE>::iterator ii = _handles.begin();
	    ii != _handles.end(); ++ii) {
	    if (*ii == fd) {
		ii = _handles.erase(ii);
		_callback_map.erase(IoEventTuple(fd, type));
		_descriptor_count--;
		return true;
	    }
	}
    }

    return false;
}

bool
WinDispatcher::remove_ioevent_cb(XorpFd fd, IoEventType type)
{
    debug_msg("Removing event %d from object %s\n", type, fd.str().c_str());

    switch (fd.type()) {
    case XorpFd::FDTYPE_SOCKET:
	return remove_socket_cb(fd, type);
    case XorpFd::FDTYPE_PIPE:
    case XorpFd::FDTYPE_CONSOLE:
    case XorpFd::FDTYPE_PROCESS:
	return remove_handle_cb(fd, type);
	break;
    default:
	break;
    }
    return false;
}

int
WinDispatcher::get_ready_priority()
{
    // TODO: XXX: THIS IS COMPLETELY BOGUS
    return XorpTask::PRIORITY_DEFAULT;
}

bool
WinDispatcher::ready()
{
    DWORD retval;

    // TODO: XXX: THIS IS PROBABLY BOGUS

    for (vector<HANDLE>::iterator ii = _polled_pipes.begin();
	ii != _polled_pipes.end(); ++ii) {
	ssize_t result = win_pipe_read(*ii, NULL, 0);
	if (result == WINIO_ERROR_HASINPUT 
	    || result == WINIO_ERROR_DISCONNECT) {
	    return true;
	}
    }
    if (_handles.empty()) {
	return false;
    }
    retval = WaitForMultipleObjects(_handles.size(), &_handles[0],
				    FALSE, 0);
    if (retval == WAIT_TIMEOUT) {
	return false;
    }
    return true;
}

void
WinDispatcher::wait_and_dispatch(int ms)
{
    bool more_to_do = true;
    bool first = true;

    //
    // Wait or sleep. Do not enter a state where APCs may be called;
    // they are not compatible with the XORP event loop.
    // If we need to fix the sleep quantum to deal with polled objects, do so.
    //
    if ((!_polled_pipes.empty()) && (ms > POLLED_INTERVAL_MS || ms < 0))
	ms = POLLED_INTERVAL_MS;

    vector<HANDLE> tmp_handles(_handles);
    while (more_to_do) {
	more_to_do = do_wait_and_dispatch(tmp_handles, ms, first);

	// After initial call, do the rest with zero timeout so we
	// can process the rest of the 'ready' handles.
	ms = 0;
	first = false;
    }
}

/** Returns true if more work to be done. */
bool
WinDispatcher::do_wait_and_dispatch(vector<HANDLE>& handles, int ms, bool first) {

    DWORD retval;

    if (handles.empty()) {
	// We probably don't want to sleep forever with no pending waits,
	// as Win32 doesn't have the same concept of signals as UNIX does.
	if (ms > 0) {
	    Sleep(ms);
	}
	retval = WAIT_TIMEOUT;
    } else {
	retval = WaitForMultipleObjects(handles.size(), &handles[0],
					FALSE, ms);
    }
    //XLOG_WARNING("Done waiting, retval: %i", retval);
    _clock->advance_time();

    if (first) {
	// Reads need to be handled first because they may come from
	// dying processes picked up by the handle poll.
	if (!_polled_pipes.empty())
	    dispatch_pipe_reads();
    }

    // The order of the if clauses here is important.
    if (retval == WAIT_FAILED) {
	DWORD lasterr = GetLastError();
	if (lasterr == ERROR_INVALID_HANDLE && !handles.empty()) {
	    callback_bad_handle();
	} else {
	    // Programming error.
	    XLOG_FATAL("WaitForMultipleObjects(%d,%p,%d,%d) failed "
		       "with the error code %lu (%s). "
		       "Please report this error to the XORP core team.",
			handles.empty() ? 0 : handles.size(),
			handles.empty() ? NULL : &handles[0],
			FALSE, ms,
			lasterr, win_strerror(lasterr));
	}
    } else if (retval == WAIT_TIMEOUT) {
	// The timeout period elapsed. This is normal. Fall through.
    } else if (retval <= (WAIT_OBJECT_0 + handles.size() - 1)) {
	//
	// An object in handles was signalled. Dispatch its callback.
	// Check if it's an event associated with a socket first.
	//
	HANDLE eh = handles[retval - WAIT_OBJECT_0];

	EventSocketMap::iterator qq = _event_socket_map.find(eh);
	if (qq != _event_socket_map.end()) {
	    dispatch_sockevent(qq->first, qq->second);
	} else {
	    // It's not an Event, so it must be something else.
	    // Figure out what it is, and deal with it.
	    XorpFd efd(eh);
	    XLOG_ASSERT(efd.type() != XorpFd::FDTYPE_SOCKET);
	    XLOG_ASSERT(efd.type() != XorpFd::FDTYPE_PIPE);
	    IoEventType evtype = (efd.type() == XorpFd::FDTYPE_CONSOLE) ?
		IOT_READ : IOT_EXCEPTION;
	    IoEventMap::iterator jj =
		_callback_map.find(IoEventTuple(efd, evtype));
	    if (jj != _callback_map.end()) {
		jj->second->dispatch(efd, evtype);
	    } else {
		XLOG_ERROR("no callback for object %s", efd.str().c_str());
	    }
	}

	if (handles.size() > 1) {
	    for (vector<HANDLE>::iterator ii = handles.begin();
		 ii != handles.end(); ++ii) {
		if (*ii == eh) {
		    ii = handles.erase(ii);
		    break;
		}
	    }
	    return true;
	}
	return false;
    } else {
	// Programming error.
	XLOG_FATAL("WaitForMultipleObjects(%d,%p,%d,%d) returned an "
		   "unhandled return value %lu. "
		   "Please report this error to the XORP core team.",
		   handles.empty() ? 0 : handles.size(),
		   handles.empty() ? NULL : &handles[0],
		   FALSE, ms, retval);
    }
    return false;
}

void
WinDispatcher::dispatch_pipe_reads()
{
    ssize_t result;

    for (vector<HANDLE>::iterator ii = _polled_pipes.begin();
	ii != _polled_pipes.end(); ii++) {
	result = win_pipe_read(*ii, NULL, 0);
	if (result == WINIO_ERROR_HASINPUT) {
	    //
	    // Polled pipes *must* have a read handler.
	    //
	    IoEventMap::iterator jj = _callback_map.find(
		IoEventTuple(*ii, IOT_READ));
	    XLOG_ASSERT(jj != _callback_map.end());
	    jj->second->dispatch(*ii, IOT_READ);
	} else if (result == WINIO_ERROR_DISCONNECT) {
	    //
	    // Polled pipes may optionally have a disconnection handler.
	    // This is used by the FEA.
	    //
	    IoEventMap::iterator jj = _callback_map.find(
		IoEventTuple(*ii, IOT_DISCONNECT));
	    if (jj != _callback_map.end()) {
		jj->second->dispatch(*ii, IOT_DISCONNECT);
	    }
	}
    }
}

void
WinDispatcher::dispatch_sockevent(HANDLE hevent, XorpFd fd)
{
    int err;
    WSANETWORKEVENTS netevents;

    memset(&netevents, 0, sizeof(netevents));
    err = WSAEnumNetworkEvents(fd, hevent, &netevents);
    if (err != 0) {
	if (WSAGetLastError() == WSAENOTSOCK) {
	    // The socket might have been closed or EOF'd by the time
	    // we get the event.
	    XLOG_WARNING("WSAEnumNetworkEvents() returned WSAENOTSOCK for "
			 "socket %s", fd.str().c_str());
	    callback_bad_socket(fd);
	}
	return;
    }

    // Short circuit mask check if no events occured.
    if (netevents.lNetworkEvents == 0) {
	debug_msg("event %p signalled but event mask is zero\n", hevent);
	return;
    }

    for (int evbit = 0; evbit < FD_MAX_EVENTS ; evbit++) {
	int evflag = 1 << evbit;

	if ((evflag & netevents.lNetworkEvents) == evflag) {
	    debug_msg("processing event %d\n", evflag);

	    int itype = _wsa2ioe(evflag);
	    if (itype == -1) {
		debug_msg("Can't map WSA event %d to an IoEventType", evflag);
		continue;
	    }

	    IoEventType type = static_cast<IoEventType>(itype);
	    IoEventMap::iterator jj =
		_callback_map.find(IoEventTuple(fd, type));
	    if (jj == _callback_map.end()) {
		debug_msg("IoEventType %d on socket %s does not map to a "
			  "callback.\n", itype, fd.str().c_str());
		continue;
	    }

	    TimeVal start;
	    if (eloop_trace.on()) {
		_clock->advance_time();
		_clock->current_time(start);
	    }
	    jj->second->dispatch(fd, type);
	    if (eloop_trace.on()) {
		TimeVal now;
		_clock->advance_time();
		_clock->current_time(now);
		if (now.to_ms() > start.to_ms() + 1000) {
		    XLOG_INFO("socket callback took too long to run: %lims, fd: %s  type: %i\n",
			      (long)(now.to_ms() - start.to_ms()), cstring(fd), (int)(type));
		}
	    }
	}
    }
}

//
// If a socket is detected as being bad, force all callbacks
// registered for it to be invoked, just like
// SelectorList::callback_bad_fd() on the UNIX path.
//
// Just as with the UNIX path, removal of the bad socket is
// the caller's responsibility.
//
// We can't detect this case at WFMO level because the condition
// has been detected on the socket, not the associated event object
// which XORP actually waits on.
//
void
WinDispatcher::callback_bad_socket(XorpFd& fd)
{
    for (int i = IOT_READ; i < IOT_DISCONNECT; ++i) {
	IoEventType type = static_cast<IoEventType>(i);
	IoEventMap::iterator jj = _callback_map.find(IoEventTuple(fd, type));
	if (jj == _callback_map.end())
	    continue;
	jj->second->dispatch(fd, type);
    }
}

//
// If an opaque Windows handle is detected as being bad, force
// all of its callbacks to be invoked.
//
// Just as with the UNIX path, removal of the bad handle is
// the caller's responsibility.
//
// This sweeps the entire handle vector for bad handles.
//
void
WinDispatcher::callback_bad_handle()
{
    DWORD dwFlags;

    for (vector<HANDLE>::iterator ii = _handles.begin();
	 ii != _handles.end();
	 ++ii) {
	if (GetHandleInformation(*ii, &dwFlags) == 0) {
	    EventSocketMap::iterator jj = _event_socket_map.find(*ii);
	    if (jj != _event_socket_map.end()) {
		//
		// The bad handle is an Event handle associated with a
		// socket and therefore managed within the event framework.
		//
		// Bad sockets are meant to be caught (and handled) by
		// dispatch_sockevent() and callback_bad_socket()
		// respectively.
		//
		// This means Something Is Horribly Wrong. Abort fatally.
		//
		XLOG_FATAL("Event handle associated with a socket is bad.");
	    }
	    //
	    // Force all of this handle's callbacks to be invoked.
	    //
	    XorpFd fd(*ii);
	    for (int i = IOT_READ; i < IOT_DISCONNECT; ++i) {
		IoEventType type = static_cast<IoEventType>(i);
		IoEventMap::iterator kk =
		    _callback_map.find(IoEventTuple(fd, type));
		if (kk == _callback_map.end())
		    continue;
		kk->second->dispatch(fd, type);
	    }
	}
    }
}

#endif // HOST_OS_WINDOWS
