/* This file is part of cardwords
   (c) 1999 Tobias Peters
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 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 General Public License for more details.

   You should have received a copy of the GNU 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

*/
// cardwords_select.cc

#include "cardwords_select.hh"

CardWords_Selectible::CardWords_Selectible(const CardWords_Selectible &)
  // copy constructor must not be used
{
  assert (0);
}

CardWords_Selector::CardWords_Selector ()
  : run_looping(false)
{
  next_selected = set.end();
}

CardWords_Selector::~CardWords_Selector()
{
}

void
CardWords_Selector::add (CardWords_Selectible *selectible,
                         int fd,
                         int mode,
                         time_t timeout_time = 0)
{
  assert (selectible != 0);
  Member m;
  m.selectible = selectible;
  m.fd = fd;
  m.mode = mode;
  m.timeout_time = timeout_time;
  set.insert(m);
}

void
CardWords_Selector::remove (CardWords_Selectible *selectible,
                            int fd,
                            int mode)
{
  ::set<Member>::iterator iter;
  Member m;
  m.selectible = selectible;
  m.fd = fd;
  m.mode = mode;
  m.timeout_time = 0;
  iter = set.lower_bound(m);
  assert (iter != set.end());
  assert ((*iter).selectible == selectible);
  assert ((*iter).fd == fd);
  assert ((*iter).mode == mode);
  if (next_selected == iter) {
    // do not select this iter as the next one
    ++next_selected;
  }
  set.erase(iter);
}

void CardWords_Selector::update_timeout (CardWords_Selectible * selectible,
                                         int fd,
                                         int mode,
                                         time_t timeout_time = 0)
{
  ::set<Member>::iterator iter;
  Member m;
  m.selectible = selectible;
  m.fd = fd;
  m.mode = mode;
  m.timeout_time = 0;
  iter = set.lower_bound(m);
  assert (iter != set.end());
  assert ((*iter).selectible == selectible);
  assert ((*iter).fd == fd);
  assert ((*iter).mode == mode);
  bool need_update_next_selected = (next_selected == iter);
  set.erase(iter);
  m.timeout_time = timeout_time;
  iter = set.insert(m).first;
  if (need_update_next_selected == true) {
    next_selected = iter;
  }
}

// Call this and the being_selected() methods of the appropriate
// CardWords_Selectible objects will be called;
int CardWords_Selector::run (bool looping = true)
{
  assert (run_looping == false);
  run_looping = looping;
  run_return_value = 0;

  fd_set read_fds;
  fd_set write_fds;
  fd_set exception_fds;

  int maxfd, fd;
  int retval;

  /* Disable Timeout. */ // FIXME: enable timeout
  struct timeval tv;
#ifndef MAX_TIMEOUT
#define MAX_TIMEOUT (2 * 365 * 24 * 60 * 60) /* 2 Years */
#endif
  
  ::set<Member>::const_iterator set_iter;
  //  ::set<Member>::const_iterator set_next; // this must be a data member
  //                                             because it may change during
  // it's name is                                the being_selected call
  // now: next_selected                          (calling again remove)

  ::set<Member>::const_iterator set_end;

  time_t actual_time;
  time_t timeout_time;

  // the main loop, it consists mainly of:
  // - packing the set Member filedescriptors in one of the three sets
  // - a select() system call
  // - testing which filedescriptors are responsible for the returning of
  //   select() and calling their being_selected method.
  // - additionaly, test for timeouts during packing.
  do {
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    FD_ZERO(&exception_fds);
    actual_time = time(0);
    tv.tv_sec = MAX_TIMEOUT;
    tv.tv_usec = 0;
  
    for ((maxfd = 0),(set_iter = set.begin()),(set_end = set.end());
         set_iter != set_end;
         ++set_iter) {
      fd = (*set_iter).fd;

      timeout_time = (*set_iter).timeout_time;

      if (timeout_time != (time_t)0 && timeout_time <= actual_time) {
        // timeout occurred
        Member m = *set_iter;
        remove (m.selectible, m.fd, m.mode);
        m.selectible->timed_out(m.fd, m.mode);
        // begin packing from scratch, because some more set Members may
        // have changed (and check run_looping by that time):
        goto END_OF_MAIN_LOOP; 
      }

      if (timeout_time != 0 && (timeout_time - actual_time) < tv.tv_sec) {
        tv.tv_sec = timeout_time - actual_time;
      }
      
      if (fd > maxfd) {
        maxfd = fd;
      }

      switch ((*set_iter).mode) {
      case READ_MODE:
        FD_SET(fd, &read_fds);
        break;
      case WRITE_MODE:
        FD_SET(fd, &write_fds);
        break;
      case EXCEPTION_MODE:
        FD_SET(fd, &exception_fds);
        break;
      default:
        assert (0);
      }
    }  
    retval = select(maxfd + 1,
                    &read_fds,
                    &write_fds,
                    &exception_fds,
                    (tv.tv_sec == MAX_TIMEOUT) ?
                    (struct timeval *) 0 : & tv);
    
    if (retval == -1) {
      perror ("select");
      assert (errno == EINTR || errno == ENOMEM);
      continue;
    }
    if (retval == 0) {
      // time_out
      // The timeouts will be checked for in the next round during packing.
      // This means, however, that they do not lead to a call to timed_out
      // if run_looping is false now. I think this is acceptable for the
      // cardwords_server.
      continue;
    }
    for ((set_iter = set.begin());
         set_iter != set.end() && run_looping == true;// set.end() may
         //                                              change during loop
         set_iter = next_selected) {
      fd = (*set_iter).fd;

      // Chances are thet *set_iter is removed from the set while calling
      // CardWords_Selectible::being_selected(), so get the next iterator
      // right now:
      next_selected = set_iter;
      ++next_selected;
      
      // FIXME: obeye priority
      switch ((*set_iter).mode) {
      case READ_MODE:
        if (FD_ISSET(fd, &read_fds)) {
          (*set_iter).selectible->being_selected(fd, READ_MODE);
        }
        break;
      case WRITE_MODE:
        if (FD_ISSET(fd, &write_fds)) {
          (*set_iter).selectible->being_selected(fd, write);
        }
        break;
      case EXCEPTION_MODE:
        if (FD_ISSET(fd, &exception_fds)) {
          (*set_iter).selectible->being_selected(fd,exception);
        }
        break;
      }
    }
  END_OF_MAIN_LOOP:;
  } while (run_looping
           && set.size() > 0); // return also when there is nothing
  //                              to select from anymore.
  return run_return_value;
}


// make run return:
void CardWords_Selector::quit(int ret)
{
  run_looping = false;
  run_return_value = ret;
}
