/*
 * Copyright (c) 2015, 2016 Oracle and/or its affiliates. All rights reserved.
 *
 * 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; version 2 of the
 * License.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#ifndef _NGS_SERVER_H_
#define _NGS_SERVER_H_

#include <stdint.h>

#include <list>
#include <vector>
#include <memory>

#include <boost/bind.hpp>
#include <boost/atomic.hpp>
#include <boost/smart_ptr.hpp>

#include "my_global.h"

#include "ngs_common/connection_vio.h"
#include "ngs/ngs_types.h"
#include "ngs/protocol/protocol_config.h"
#include "ngs/protocol_encoder.h"
#include "ngs/protocol_authentication.h"

#include "ngs/thread.h"

// Surpressing numerous warnings generated by libevent on Windows.
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable: 4005)
#endif // WIN32
#include <event.h> // libevent
#ifdef WIN32
#pragma warning(pop)
#endif // WIN32

namespace ngs
{
  class Server;
  class Session;
  class Client;
  class Incoming_queue;

  typedef boost::shared_ptr<Client> Client_ptr;

  struct Client_list
  {
  public:
    Client_list();
    ~Client_list();

    size_t size();

    void add(Client_ptr client);
    void remove(Client_ptr client);

    void remove(uint64_t client_id);
    Client_ptr find(const uint64_t client_id);

    template<typename Functor>
    void enumerate(Functor &matcher)
    {
      RWLock_readlock guard(m_clients_lock);

      std::find_if(m_clients.begin(), m_clients.end(), matcher);
    }

    void get_all_clients(std::vector<Client_ptr> &result);
  private:
    struct Match_client
    {
      Match_client(uint64_t client_id);

      bool operator () (Client_ptr client);

      uint64_t m_id;
    };

    Client_list(const Client_list&);
    Client_list& operator=(const Client_list&);

    RWLock m_clients_lock;
    std::list<Client_ptr> m_clients;
  };


  class Server_delegate
  {
  public:
    enum Reject_reason
    {
      AcceptError,
      TooManyConnections
    };
    virtual ~Server_delegate() {}

    virtual bool will_accept_client(boost::shared_ptr<Client> client) = 0;
    virtual void did_accept_client(boost::shared_ptr<Client> client) = 0;
    virtual void did_reject_client(Reject_reason reason) = 0;
    virtual boost::shared_ptr<Client> create_client(ngs::Connection_ptr sock) = 0;
    virtual boost::shared_ptr<Session> create_session(boost::shared_ptr<Client> client,
                                                      Protocol_encoder *proto,
                                                      int session_id) = 0;
    virtual void on_client_closed(boost::shared_ptr<Client> client) = 0;
    virtual bool is_terminating() const = 0;
  };

  class Scheduler_dynamic;

  class IServer
  {
  public:
    virtual ~IServer() {}

    virtual boost::shared_ptr<Scheduler_dynamic>           worker_scheduler() const = 0;

    virtual void get_authentication_mechanisms(std::vector<std::string> &auth_mech, Client &client) = 0;
    virtual Authentication_handler_ptr get_auth_handler(const std::string &name, Session *session) = 0;
    virtual boost::shared_ptr<Protocol_config> config() const = 0;

    virtual Ssl_context *ssl_context() const = 0;

    virtual boost::shared_ptr<Session> create_session(boost::shared_ptr<Client> client,
                                                      Protocol_encoder *proto,
                                                      int session_id) = 0;
    virtual bool is_running() = 0;

    virtual void on_client_closed(boost::shared_ptr<Client> client) = 0;
    virtual void restart_client_supervision_timer() = 0;

    virtual Mutex &get_client_exit_mutex() = 0;
  };

  class Server: public IServer
  {
  public:
    Server(my_socket tcp_socket,
           boost::shared_ptr<Scheduler_dynamic> work_scheduler,
           Server_delegate *delegate,
           boost::shared_ptr<Protocol_config> config);

    ~Server();

    void set_ssl_context(Ssl_context_unique_ptr ssl_context);
    virtual Ssl_context *ssl_context() const { return m_ssl_context.get(); }

    bool prepare(const bool skip_networking, const bool skip_name_resolve);
    bool run();

    void start_failed();
    void stop();

    void close_all_clients();

    bool is_terminating();
    bool is_running();

    boost::shared_ptr<Scheduler_dynamic> worker_scheduler() const
    {
      return m_worker_scheduler;
    }

    boost::shared_ptr<Protocol_config> config() const { return m_config; }


    boost::shared_ptr<Session> create_session(boost::shared_ptr<Client> client,
                                              Protocol_encoder *proto,
                                              int session_id);

    void on_client_closed(boost::shared_ptr<Client> client);

    void add_authentication_mechanism(const std::string &name,
                                      Authentication_handler::create initiator,
                                      const bool allowed_only_with_tls);

    Authentication_handler_ptr get_auth_handler(const std::string &name, Session *session);
    void get_authentication_mechanisms(std::vector<std::string> &auth_mech, Client &client);

    Client_list &get_client_list() { return m_client_list; }

    enum State {State_initializing, State_running, State_failure, State_terminating};
    enum State_acceptor {State_acceptor_initializing, State_acceptor_running, State_acceptor_stopped};

    void add_timer(std::size_t delay_ms, boost::function<bool ()> callback);

    Mutex &get_client_exit_mutex()
    {
      return m_client_exit_mutex;
    }

  private:
    Server(const Server&);
    Server& operator=(const Server&);

    bool setup_accept();

    void stop_accepting_connections();
    void wait_for_clients_closure();
    void go_through_all_clients(boost::function<void (Client_ptr)> callback);
    bool timeout_for_clients_validation();
    void validate_client_state(ptime &oldest_object_time_of_life, const ptime& time_of_release, Client_ptr);
    void wait_for_next_client();

    // accept one connection, create a connection object for the client and tell it to
    // start reading input
    static void on_accept(int sock, short what, void *ctx);
    void start_client_supervision_timer(const time_duration &oldest_object_time_ms);
    void restart_client_supervision_timer();

    bool on_check_terminated_workers();

    static void timeout_call(int sock, short which, void *arg);
  private:
    class Auth_key
    {
    public:
      Auth_key(const std::string &key_name, const bool key_should_be_tls_active)
      : name(key_name), should_be_tls_active(key_should_be_tls_active)
      {
      }

      bool operator< (const Auth_key &key) const
      {
        int result = name.compare(key.name);

        if (0 != result)
        {
          return result < 0;
        }

        return should_be_tls_active < key.should_be_tls_active;
      }

      const std::string name;
      const bool should_be_tls_active;
    };

    typedef std::map<Auth_key, Authentication_handler::create> Auth_handler_map;

    my_socket m_tcp_socket;

    // scheduler for worker pool (execute requests)
    boost::shared_ptr<Scheduler_dynamic> m_worker_scheduler;

    struct event_base *m_evbase;
    event m_tcp_event;

    Ssl_context_unique_ptr m_ssl_context;

    struct Timer_data
    {
      boost::function<bool ()> callback;
      event ev;
      timeval tv;
      Server *self;
    };
    Mutex m_timers_mutex;
    std::vector<Timer_data*> m_timers;

    bool m_timer_running;
    bool m_skip_name_resolve;
    Sync_variable<State_acceptor> m_acceptor_state;
    Sync_variable<State> m_state;

    Auth_handler_map m_auth_handlers;

    Client_list m_client_list;

    Server_delegate *m_delegate;

    boost::shared_ptr<Protocol_config> m_config;
    Mutex m_client_exit_mutex;
  };
};

#endif
