#ifndef PROTON_CPP_PROACTOR_CONTAINERIMPL_H
#define PROTON_CPP_PROACTOR_CONTAINERIMPL_H

/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

#include "proton/fwd.hpp"
#include "proton/container.hpp"
#include "proton/connection.hpp"
#include "proton/connection_options.hpp"
#include "proton/duration.hpp"
#include "proton/error_condition.hpp"
#include "proton/messaging_handler.hpp"
#include "proton/receiver.hpp"
#include "proton/receiver_options.hpp"
#include "proton/sender.hpp"
#include "proton/sender_options.hpp"
#include "proton/work_queue.hpp"

#include "proton_bits.hpp"

#include <list>
#include <map>
#include <set>
#include <string>
#include <vector>

#if PN_CPP_SUPPORTS_THREADS
#include <mutex>
# define MUTEX(x) std::mutex x;
# define GUARD(x) std::lock_guard<std::mutex> g(x)
# define ONCE_FLAG(x) std::once_flag x;
# define CALL_ONCE(x, ...) std::call_once(x, __VA_ARGS__)
#else
# define MUTEX(x)
# define GUARD(x)
# define ONCE_FLAG(x)
# define CALL_ONCE(x, f, o) ((o)->*(f))()
#endif

struct pn_proactor_t;
struct pn_listener_t;
struct pn_event_t;

namespace proton {

namespace internal {
class connector;
}

class container::impl {
  public:
    impl(container& c, const std::string& id, messaging_handler* = 0);
    ~impl();
    std::string id() const { return id_; }
    returned<connection> connect();
    returned<connection> connect(const std::string&, const connection_options&);
    returned<sender> open_sender(
        const std::string&, const proton::sender_options &, const connection_options &);
    returned<receiver> open_receiver(
        const std::string&, const proton::receiver_options &, const connection_options &);
    listener listen(const std::string&);
    listener listen(const std::string&, const connection_options& lh);
    listener listen(const std::string&, listen_handler& lh);
    void client_connection_options(const connection_options &);
    connection_options client_connection_options() const { return client_connection_options_; }
    void server_connection_options(const connection_options &);
    connection_options server_connection_options() const { return server_connection_options_; }
    void sender_options(const proton::sender_options&);
    class sender_options sender_options() const { return sender_options_; }
    void receiver_options(const proton::receiver_options&);
    class receiver_options receiver_options() const { return receiver_options_; }
    void run(int threads);
    void stop(const error_condition& err);
    void auto_stop(bool set);
    void schedule(duration, work);
    template <class T> static void set_handler(T s, messaging_handler* h);
    template <class T> static messaging_handler* get_handler(T s);
    static work_queue::impl* make_work_queue(container&);

  private:
    class common_work_queue;
    class connection_work_queue;
    class container_work_queue;
    pn_listener_t* listen_common_lh(const std::string&);
    pn_connection_t* make_connection_lh(const url& url, const connection_options&);
    void setup_connection_lh(const url& url, pn_connection_t *pnc);
    void start_connection(const url& url, pn_connection_t* c);
    void reconnect(pn_connection_t* pnc);
    duration next_delay(reconnect_context& rc);
    bool setup_reconnect(pn_connection_t* pnc);
    void reset_reconnect(pn_connection_t* pnc);

    // Event loop to run in each container thread
    void thread();
    enum dispatch_result {ContinueLoop, EndBatch, EndLoop};
    dispatch_result dispatch(pn_event_t*);
    void run_timer_jobs();

    int threads_;
    container& container_;
    MUTEX(lock_)

    ONCE_FLAG(start_once_)
    ONCE_FLAG(stop_once_)
    void start_event();
    void stop_event();

    typedef std::set<container_work_queue*> work_queues;
    work_queues work_queues_;
    MUTEX(work_queues_lock_)
    container_work_queue* add_work_queue();
    void remove_work_queue(container_work_queue*);

    struct scheduled {
        timestamp time; // duration from epoch for task
        work task;

        // We want to get to get the *earliest* first so test is "reversed"
        bool operator < (const scheduled& r) const { return  r.time < time; }
    };
    std::vector<scheduled> deferred_; // This vector is kept as a heap
    MUTEX(deferred_lock_)

    pn_proactor_t* proactor_;
    messaging_handler* handler_;
    std::string id_;
    connection_options client_connection_options_;
    connection_options server_connection_options_;
    proton::sender_options sender_options_;
    proton::receiver_options receiver_options_;
    error_condition disconnect_error_;

    unsigned reconnecting_;
    bool auto_stop_;
    bool stopping_;
    friend class connector;
};

template <class T>
void container::impl::set_handler(T s, messaging_handler* mh) {
    internal::set_messaging_handler(s, mh);
}

template <class T>
messaging_handler* container::impl::get_handler(T s) {
    return internal::get_messaging_handler(s);
}


}

#endif  /*!PROTON_CPP_PROACTOR_CONTAINERIMPL_H*/
