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

// Copyright (c) 2001-2009 XORP, Inc.
//
// 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

// $XORP: xorp/libxorp/selector.hh,v 1.36 2008/11/07 21:52:58 abittau Exp $

#ifndef __LIBXORP_SELECTOR_HH__
#define __LIBXORP_SELECTOR_HH__

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <vector>

#include <boost/noncopyable.hpp>

#include "callback.hh"
#include "ioevents.hh"
#include "task.hh"

class ClockBase;
class SelectorList;
class TimeVal;

/**
 * Selector event type masks.
 */
enum SelectorMask {
    SEL_NONE	= 0x0,				// No events
    SEL_RD	= 0x01,				// Read events
    SEL_WR	= 0x02,				// Write events
    SEL_EX	= 0x04,				// Exception events
    SEL_ALL	= SEL_RD | SEL_WR | SEL_EX	// All events
};

class SelectorTag;

typedef ref_ptr<SelectorTag> Selector;

/**
 * @short Abstract class used to receive SelectorList notifications
 *
 * A SelectorListObserverBase abstract class can be subtyped to create classes
 * that will receive @ref SelectorList events.
 * All methods in this class are private, since they must only be invoked by
 * the friend class SelectorList
 */
class SelectorListObserverBase {
public:
    virtual ~SelectorListObserverBase();

private:
    /**
     * This function will get called when a new file descriptor is
     * added to the SelectorList.
     */
    virtual void notify_added(XorpFd fd, const SelectorMask& mask) = 0;

    /**
     * This function will get called when a new file descriptor is
     * removed from the SelectorList.
     */
    virtual void notify_removed(XorpFd fd, const SelectorMask& mask) = 0;

    SelectorList * _observed;

    friend class SelectorList;
};

/**
 * @short A class to provide an interface to I/O multiplexing.
 *
 * A SelectorList provides an entity where callbacks for pending I/O
 * operations on file descriptors may be registered.  The callbacks
 * are invoked when one of the @ref wait_and_dispatch methods is called
 * and I/O is pending on the particular descriptors.
 */
class SelectorList :
    public boost::noncopyable
{
public:

    /**
     * Default constructor.
     */
    SelectorList(ClockBase* clock);

    /**
     * Destructor.
     */
    virtual ~SelectorList();

    void set_debug(bool v) { _is_debug = v;}
    bool is_debug() const { return (_is_debug); }

    /**
     * Add a hook for pending I/O operations on a callback.
     *
     * Only one callback may be registered for each possible I/O event
     * type (read, write, exception).
     *
     * Multiple event types may share the same callback. If multiple
     * event types share the same callback and multiple types of event
     * are pending, the callback is invoked just once and the mask
     * argument to the callback shows which events are pending.
     *
     * @param file descriptor.
     *
     * @param mask mask of I/O event types
     * that should invoke callback.  An OR'ed combination of the
     * available @ref SelectorMask values.
     *
     * @param scb callback object that will be invoked when the
     * descriptor.
     *
     * @return true if function succeeds, false otherwise.
     */
     bool add_ioevent_cb(XorpFd		fd, 
			 IoEventType	type, 
			 const IoEventCb& cb,
			 int		priority = XorpTask::PRIORITY_DEFAULT);

    /**
     * Remove hooks for pending I/O operations.
     *
     * @param fd the file descriptor.
     *
     * @param mask of event types to be removed, e.g. an OR'ed
     * combination of the available @ref SelectorMask values.
     */
    void remove_ioevent_cb(XorpFd fd, IoEventType type = IOT_ANY);

    /**
     * Find out if any of the selectors are ready.
     *
     * @return true if any selector is ready.
     */
    bool ready();

    /**
     * Find out the highest priority from the ready file descriptors.
     *
     * @return the priority of the highest priority ready file descriptor.
     */
    int get_ready_priority(bool force);

    /**
     * Wait for a pending I/O events and invoke callbacks when they
     * become ready.
     *
     * @param timeout the maximum period to wait for.
     *
     * @return the number of callbacks that were made.
     */
    int	wait_and_dispatch(TimeVal& timeout);

    /**
     * Wait for a pending I/O events and invoke callbacks when they
     * become ready.
     *
     * @param millisecs the maximum period in milliseconds to wait for.
     *
     * @return the number of callbacks that were made.
     */
    int	wait_and_dispatch(int millisecs);

    /**
     * Get the count of the descriptors that have been added.
     *
     * @return the count of the descriptors that have been added.
     */
    size_t descriptor_count() const { return _descriptor_count; }

    /**
     * Get a copy of the current list of monitored file descriptors in
     * Unix fd_set format
     *
     * @param the selected mask as @ref SelectorMask (SEL_RD, SEL_WR, or SEL_EX)
     *
     * @return the selected fd_set.
     */
     void get_fd_set(SelectorMask selected_mask, fd_set& fds) const;

     /**
      * Get a the value of the largest monitored file descriptor
      *
      * @return the maximum fd.
      */
     int get_max_fd() const;

     /**
     * Set the SelectorObserver object that will receive notifications
     *
     * @return void
     */
     void set_observer(SelectorListObserverBase& obs);

     /**
     * Remove the SelectorObserver object that receives notifications
     *
     * @return void
     */
     void remove_observer();


protected:
    void callback_bad_descriptors();

private:
    int do_select(struct timeval* to, bool force);

private:
    enum {
	// correspond to SelectorMask; correspondence checked with
	// static_assert
	SEL_RD_IDX	= 0,
	SEL_WR_IDX	= 1,
	SEL_EX_IDX	= 2,
	SEL_MAX_IDX	= 3
    };
    struct Node {
	int		_mask[SEL_MAX_IDX];
	IoEventCb	_cb[SEL_MAX_IDX];
	// Reverse mapping of legacy UNIX event to IoEvent
	IoEventType	_iot[SEL_MAX_IDX];
	int		_priority[SEL_MAX_IDX];

	Node();
	bool		add_okay(SelectorMask m, IoEventType type,
				 const IoEventCb& cb, int priority);
	int		run_hooks(SelectorMask m, XorpFd fd);
	void		clear(SelectorMask m);
	bool		is_empty();
    };

    ClockBase*		_clock;
    SelectorListObserverBase * _observer;
    fd_set		_fds[SEL_MAX_IDX];
    fd_set		_testfds[SEL_MAX_IDX];
    int			_testfds_n;
    int			_maxpri_fd;
    int			_maxpri_sel;
    int			_last_served_fd;
    int			_last_served_sel;

    vector<Node>	_selector_entries;
    int			_maxfd;
    size_t		_descriptor_count;
    bool		_is_debug;
};

#endif // __LIBXORP_SELECTOR_HH__
