/*
 * (C) Copyright 1996-2017 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */


#include <unistd.h>
#include <sys/ioctl.h>

#include "eckit/io/Select.h"
#include "eckit/net/TCPSocket.h"

//-----------------------------------------------------------------------------

namespace eckit {

//-----------------------------------------------------------------------------

Select::Select():
	last_(-1)
{
	FD_ZERO(&files_);	
}

Select::Select(TCPSocket& p):
	last_(-1)
{
	FD_ZERO(&files_);	
	add(p);
}

Select::Select(int fd):
	last_(-1)
{
	FD_ZERO(&files_);	
	add(fd);
}

Select::~Select()
{
}

void Select::add(int fd)
{
	ASSERT(fd >=0 && fd < getdtablesize());
	FD_SET(fd,&files_);
	if(fd > last_) last_ = fd;
}

void Select::add(TCPSocket& p)
{
	add(p.socket());
}

void Select::remove(int fd)
{
	ASSERT(fd >=0 && fd < getdtablesize());
	FD_CLR(fd,&files_);
}

void Select::remove(TCPSocket& p)
{
	remove(p.socket());
}

bool Select::set(int fd)
{
	ASSERT(fd >=0 && fd < getdtablesize());
	return FD_ISSET(fd,&set_);
}

bool Select::set(TCPSocket& p)
{
	return set(p.socket());
}

bool Select::ready(long sec)
{

	int size = last_ + 1;

	::timeval timeout;
	timeout.tv_sec  = sec;
	timeout.tv_usec = 0;

	for(;;)
	{

		// First check with ioctl, as select is not always trustworthy

		bool some = false;
		FD_ZERO(&set_);

		for(int i  = 0 ; i < size ; i++)
			if(FD_ISSET(i,&files_))
			{
				int nbytes = 0;

                //cout << "ioctl(i,FIONREAD,&nbytes) " << i << " .... " << ioctl(i,FIONREAD,&nbytes) << " " << nbytes << std::endl;
                nbytes = 0;


				//SYSCALL(ioctl(i,FIONREAD,&nbytes));
                //

                // On Linux, a socket in acceoct() mode will return "Invalid argument"
                // so we simply ignor ethe error here....
				if((ioctl(i,FIONREAD,&nbytes) == 0) && (nbytes > 0))
				{
					FD_SET(i,&set_);
					some = true;
				}
			}

		if(some)
			return true;

		for(;;)
		{

			set_         = files_;
			fd_set excep = files_;

			switch(::select(size,&set_,0,&excep,&timeout) )
			{
				case -1: 
					if(errno != EINTR) 
						throw FailedSystemCall("select");
					break; 

				case 0:  
					return false;
					break; 

				default:
					return true;
					break;
			}
		}
	}

}

//-----------------------------------------------------------------------------

} // namespace eckit

