#include <Eris/Types.h>

#include <Eris/PollDefault.h>
#include <Eris/Exceptions.h>
#include <Eris/DeleteLater.h>
#include <Eris/Log.h>
#include <Eris/TimedEventService.h>

#include <skstream/skstream.h>

bool Eris::Poll::new_timeout_ = false;

Eris::Poll& Eris::Poll::instance()
{
  if(!_inst)
    _inst = new PollDefault();

  return *_inst;
}

void Eris::Poll::setInstance(Poll* p)
{
  if(_inst)
    throw InvalidOperation("Can't set poll instance, already have one");

  _inst = p;
}

Eris::Poll* Eris::Poll::_inst = 0;

namespace Eris {

class PollDataDefault : public PollData
{
public:
	PollDataDefault(const PollDefault::MapType&, bool&, unsigned long);

	virtual bool isReady(const basic_socket_stream*);
private:
	typedef PollDefault::MapType::const_iterator _iter;
	fd_set reading, writing;
	SOCKET_TYPE maxfd;
};

} // namespace Eris

using namespace Eris;

PollDataDefault::PollDataDefault(const PollDefault::MapType& str,
    bool &got_data, unsigned long msec_timeout) : maxfd(0)
{
	FD_ZERO(&reading);
	FD_ZERO(&writing);
	got_data = false;

#ifndef _MSC_VER
	for(_iter I = str.begin(); I != str.end(); ++I) {
#else
//MSVC stupidity fix
	std::map<const basic_socket_stream*, Check> *str2 = const_cast<std::map<const basic_socket_stream*, Check>* >(&str);
	for(_iter I = str2->begin(); I != str2->end(); ++I) {
#endif
		SOCKET_TYPE fd = I->first->getSocket();
		if(fd == INVALID_SOCKET) continue;
        
		got_data = true;
		if(I->second & Poll::READ)
			FD_SET(fd, &reading);
		if(I->second & Poll::WRITE)
			FD_SET(fd, &writing);
		if(fd > maxfd)
			maxfd = fd;
	}

	if (!got_data) return;

	struct timeval timeout = {msec_timeout / 1000, (msec_timeout % 1000) * 1000};
	int retval = select(maxfd+1, &reading, &writing, NULL, &timeout);
	if (retval < 0) {
        warning() << "select() returned error: " << retval;
        got_data = false;
    }

	got_data = (retval != 0);
}

bool PollDataDefault::isReady(const basic_socket_stream* str)
{
	SOCKET_TYPE fd = str->getSocket();

	return (fd != INVALID_SOCKET) && (fd <= maxfd)
		&& (FD_ISSET(fd, &reading) || FD_ISSET(fd, &writing));
}

void PollDefault::doPoll(unsigned long timeout)
{
    if(_streams.size() == 0)
	return;

    bool got_data;
    PollDataDefault data(_streams, got_data, timeout);

    if(got_data)
	Ready.emit(data);
}

void PollDefault::poll(unsigned long timeout)
{
  // This will throw if someone is using another kind
  // of poll, and that's a good thing.
  PollDefault &inst = dynamic_cast<PollDefault&>(Poll::instance());

  // Prevent reentrancy
  static bool already_polling = false;
  assert(!already_polling);
  already_polling = true;

  try {
    unsigned long wait_time = 0;
    inst.new_timeout_ = false;

    // This will only happen for timeout != 0
    while(wait_time < timeout) {
      inst.doPoll(wait_time);
      timeout -= wait_time;
      wait_time = TimedEventService::instance()->tick();
      if(inst.new_timeout_) {
        // Added a timeout, the time until it must be called
        // may be shorter than wait_time
        wait_time = 0;
        inst.new_timeout_ = false;
      }
    }

    inst.doPoll(timeout);
    TimedEventService::instance()->tick();
    execDeleteLaters();

    // We're done, turn off the reentrancy prevention flag
    assert(already_polling);
    already_polling = false;
  }
  catch(...) {
    already_polling = false;
    throw;
  }
}

void PollDefault::addStream(const basic_socket_stream* str, Check c)
{
    assert(c && Poll::MASK);

    if(!_streams.insert(std::make_pair(str, c)).second)
	throw Eris::InvalidOperation("Duplicate streams in PollDefault"); 
}

void PollDefault::changeStream(const basic_socket_stream* str, Check c)
{
    assert(c && Poll::MASK);

    _iter i = _streams.find(str);

    if(i == _streams.end())
	throw Eris::InvalidOperation("Can't find stream in PollDefault");

    i->second = c;
}

void PollDefault::removeStream(const basic_socket_stream* str)
{
    if(_streams.erase(str) == 0)
	throw Eris::InvalidOperation("Can't find stream in PollDefault");
}


syntax highlighted by Code2HTML, v. 0.9.1