#pragma once
#include <map>
#include <vector>
#include <atomic>
#include "ServerConnection.h"
#include "panda/protocol/websocket/ConnectRequest.h"
#include "panda/unievent/forward.h"
#include "panda/unievent/http/ServerConnection.h"
#include "panda/unievent/http/ServerRequest.h"
#include <panda/unievent/http/Server.h>

namespace panda { namespace unievent { namespace websocket {

using ConnectRequest   = protocol::websocket::ConnectRequest;
using ConnectRequestSP = protocol::websocket::ConnectRequestSP;

struct Server;
using ServerSP = iptr<Server>;

struct Server : virtual Refcntd {
    using Connections        = std::map<uint64_t, ServerConnectionSP>;
    using handshake_fn       = function<void(const ServerSP&, const ServerConnectionSP&, const ConnectRequestSP&)>;
    using connection_fptr    = void(const ServerSP&, const ServerConnectionSP&, const ConnectRequestSP&);
    using connection_fn      = function<connection_fptr>;
    using disconnection_fptr = void(const ServerSP&, const ServerConnectionSP&, uint16_t code, const string& payload);
    using disconnection_fn   = function<disconnection_fptr>;
    
    using Location = http::Server::Location;

    struct Config : http::Server::Config, virtual ServerConnection::Config {};
    
    handshake_fn                           handshake_callback;
    CallbackDispatcher<connection_fptr>    connection_event;
    CallbackDispatcher<disconnection_fptr> disconnection_event;

    Server (const LoopSP& loop = Loop::default_loop());

    virtual void configure(const Config&);
    
    const LoopSP& loop() const { return _loop; }
    
    virtual void run ();
    virtual void stop();
    virtual void stop(uint16_t code);

    void start_listening();
    void stop_listening ();
    
    void close_connection(const ServerConnectionSP& conn, uint16_t code)  { conn->close(code); }
    void close_connection(const ServerConnectionSP& conn, int code)       { conn->close(code); }

    template <class Conn = ServerConnection>
    iptr<Conn> get_connection(uint64_t id) {
        auto iter = _connections.find(id);
        if (iter == _connections.end()) return {};
        else return dynamic_pointer_cast<Conn>(iter->second);
    }

    const http::Server::Listeners& listeners() const;
    const Connections&             connections() { return _connections; }
    
    excepted<net::SockAddr, ErrorCode> sockaddr() const;
    
    excepted<void, ErrorCode> upgrade_connection(const unievent::http::ServerRequestSP&);
    
    const http::ServerSP& http() const { return _http; }

protected:
    bool        running;
    Connections _connections;
    ServerConnection::Config conn_conf;

    virtual ServerConnectionSP new_connection(const ServerConnection::ConnectionData&);

    virtual void on_handshake    (const ServerConnectionSP& conn, const ConnectRequestSP&);
    virtual void on_connection   (const ServerConnectionSP& conn, const ConnectRequestSP&);
    virtual void on_disconnection(const ServerConnectionSP& conn, uint16_t = uint16_t(CloseCode::ABNORMALLY), const string& = {});

    void on_delete() noexcept override;

private:
    friend ServerConnection;

    static std::atomic<uint64_t> lastid;

    LoopSP         _loop;
    http::ServerSP _http;
    bool           _listening = false;
    
    void auto_upgrade_http_requests(const http::ServerRequestSP&);

    void remove_connection(const ServerConnectionSP&, Connection::State, uint16_t code, const string& payload);
};

inline std::ostream& operator<<(std::ostream& stream, const Server::Config& conf) {
    stream << "Server::Config{ locations:[";
    for (auto loc : conf.locations) stream << loc << ",";
    stream << "]};";
    return stream;
}

}}}