/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * SelectServer.h
 * The select server interface
 * Copyright (C) 2005-2008 Simon Newton
 */

#ifndef INCLUDE_OLA_IO_SELECTSERVER_H_
#define INCLUDE_OLA_IO_SELECTSERVER_H_

#include <ola/Callback.h>
#include <ola/Clock.h>
#include <ola/ExportMap.h>
#include <ola/io/Descriptor.h>
#include <ola/io/SelectServerInterface.h>
#include <ola/network/Socket.h>
#include <ola/thread/Thread.h>

#include <queue>
#include <set>
#include <string>
#include <vector>

class SelectServerTest;

namespace ola {
namespace io {

using ola::ExportMap;
using ola::thread::timeout_id;
using std::priority_queue;
using std::set;
using std::string;


/**
 * This is the core of the event driven system. The SelectServer is responsible
 * for invoking Callbacks when events occur. All methods except Execute() and
 * Terminate() must be called from the thread that Run() was called in.
 */
class SelectServer: public SelectServerInterface {
  public :
    enum Direction {READ, WRITE};

    SelectServer(ExportMap *export_map = NULL,
                 Clock *clock = NULL);
    ~SelectServer();

    bool IsRunning() const { return !m_terminate; }
    const TimeStamp *WakeUpTime() const { return &m_wake_up_time; }

    void Terminate();

    void SetDefaultInterval(const TimeInterval &poll_interval);
    void Run();
    void RunOnce(unsigned int delay_sec = POLL_INTERVAL_SECOND,
                 unsigned int delay_usec = POLL_INTERVAL_USECOND);

    bool AddReadDescriptor(ReadFileDescriptor *descriptor);
    bool AddReadDescriptor(ConnectedDescriptor *descriptor,
                           bool delete_on_close = false);
    bool RemoveReadDescriptor(ReadFileDescriptor *descriptor);
    bool RemoveReadDescriptor(ConnectedDescriptor *descriptor);

    bool AddWriteDescriptor(WriteFileDescriptor *descriptor);
    bool RemoveWriteDescriptor(WriteFileDescriptor *descriptor);

    timeout_id RegisterRepeatingTimeout(unsigned int ms,
                                        ola::Callback0<bool> *closure);
    timeout_id RegisterRepeatingTimeout(const ola::TimeInterval &interval,
                                        ola::Callback0<bool> *closure);

    timeout_id RegisterSingleTimeout(unsigned int ms,
                                     ola::SingleUseCallback0<void> *closure);
    timeout_id RegisterSingleTimeout(const ola::TimeInterval &interval,
                                     ola::SingleUseCallback0<void> *closure);
    void RemoveTimeout(timeout_id id);

    void RunInLoop(ola::Callback0<void> *closure);

    void Execute(ola::BaseCallback0<void> *closure);

    // these are pubic so that the tests can access them
    static const char K_READ_DESCRIPTOR_VAR[];
    static const char K_WRITE_DESCRIPTOR_VAR[];
    static const char K_CONNECTED_DESCRIPTORS_VAR[];
    static const char K_TIMER_VAR[];
    static const char K_LOOP_TIME[];
    static const char K_LOOP_COUNT[];

  private :
    /*
     * These are timer events, they are used inside the SelectServer class
     */
    class Event {
      public:
        explicit Event(const TimeInterval &interval, const Clock *clock)
            : m_interval(interval) {
          TimeStamp now;
          clock->CurrentTime(&now);
          m_next = now + m_interval;
        }
        virtual ~Event() {}
        virtual bool Trigger() = 0;

        void UpdateTime(const TimeStamp &now) {
          m_next = now + m_interval;
        }

        TimeStamp NextTime() const { return m_next; }

      private:
        TimeInterval m_interval;
        TimeStamp m_next;
    };

    // An event that only happens once
    class SingleEvent: public Event {
      public:
        SingleEvent(const TimeInterval &interval,
                    const Clock *clock,
                    ola::BaseCallback0<void> *closure):
          Event(interval, clock),
          m_closure(closure) {
        }

        virtual ~SingleEvent() {
          if (m_closure)
            delete m_closure;
        }

        bool Trigger() {
          if (m_closure) {
            m_closure->Run();
            // it's deleted itself at this point
            m_closure = NULL;
          }
          return false;
        }

      private:
        ola::BaseCallback0<void> *m_closure;
    };


    /*
     * An event that occurs more than once. The closure can return false to
     * indicate that it should not be called again.
     */
    class RepeatingEvent: public Event {
      public:
        RepeatingEvent(const TimeInterval &interval,
                       const Clock *clock,
                       ola::BaseCallback0<bool> *closure):
          Event(interval, clock),
          m_closure(closure) {
        }
        ~RepeatingEvent() {
          delete m_closure;
        }
        bool Trigger() {
          if (!m_closure)
            return false;
          return m_closure->Run();
        }

      private:
        ola::BaseCallback0<bool> *m_closure;
    };

    typedef struct {
      ConnectedDescriptor *descriptor;
      bool delete_on_close;
    } connected_descriptor_t;

    struct connected_descriptor_t_lt {
      bool operator()(const connected_descriptor_t &c1,
                      const connected_descriptor_t &c2) const {
        return c1.descriptor->ReadDescriptor() <
            c2.descriptor->ReadDescriptor();
      }
    };

    struct ltevent {
      bool operator()(Event *e1, Event *e2) const {
        return e1->NextTime() > e2->NextTime();
      }
    };

    typedef set<ReadFileDescriptor*> ReadDescriptorSet;
    typedef set<WriteFileDescriptor*> WriteDescriptorSet;
    typedef set<connected_descriptor_t, connected_descriptor_t_lt>
      ConnectedDescriptorSet;
    typedef set<ola::Callback0<void>*> LoopClosureSet;

    bool m_terminate, m_is_running;
    TimeInterval m_poll_interval;
    unsigned int m_next_id;
    ReadDescriptorSet m_read_descriptors;
    ConnectedDescriptorSet m_connected_read_descriptors;
    WriteDescriptorSet m_write_descriptors;
    set<timeout_id> m_removed_timeouts;
    ExportMap *m_export_map;

    typedef priority_queue<Event*, vector<Event*>, ltevent> event_queue_t;
    event_queue_t m_events;
    CounterVariable *m_loop_iterations;
    CounterVariable *m_loop_time;
    TimeStamp m_wake_up_time;
    Clock *m_clock;
    bool m_free_clock;
    LoopClosureSet m_loop_closures;
    std::queue<ola::BaseCallback0<void>*> m_incoming_queue;
    ola::thread::Mutex m_incoming_mutex;
    LoopbackDescriptor m_incoming_descriptor;

    SelectServer(const SelectServer&);
    SelectServer operator=(const SelectServer&);
    bool CheckForEvents(const TimeInterval &poll_interval);
    void CheckDescriptors(fd_set *r_set, fd_set *w_set);
    bool AddDescriptorsToSet(fd_set *r_set, fd_set *w_set, int *max_sd);
    TimeStamp CheckTimeouts(const TimeStamp &now);
    void UnregisterAll();
    void DrainAndExecute();
    void SetTerminate() { m_terminate = true; }
    void SafeIncrement(const string &var_name);
    void SafeDecrement(const string &var_name);

    static const int K_MS_IN_SECOND = 1000;
    static const int K_US_IN_SECOND = 1000000;
    // the maximum time we'll wait in the select call
    static const unsigned int POLL_INTERVAL_SECOND = 10;
    static const unsigned int POLL_INTERVAL_USECOND = 0;

    friend class ::SelectServerTest;
};
}  // namespace io
}  // namespace ola
#endif  // INCLUDE_OLA_IO_SELECTSERVER_H_
