/* 
 * File:   JuliusOutputEventServer.cpp
 * Author: hara
 * 
 * Created on 2013/08/21, 19:29
 */

#include "JuliusOutputEventServer.h"

#include <string.h>
#include <errno.h>
#include <netinet/in.h>

namespace Junius {

    static void _cbNull(evutil_socket_t fd, short what, void *arg) {
        // Nothing to do, but it pumps all events!!
    }

    CJuliusOutputEventServer::CJuliusOutputEventServer(CJulius* julius)
    : m_julius(julius) {
        // Prepare event-base
        m_ev_base = event_base_new();
        if (!m_ev_base) return;

        //fprintf(stderr, ">> %p\n", m_ev_base);
        m_cbData.joutput = this;
        m_cbData.julius = julius;
        m_cbData.evbase = m_ev_base;
        m_cbData.evbuf = NULL;

        // prepare socket∫
        memset(&m_sin, 0, sizeof (m_sin));
        m_sin.sin_family = AF_INET; // TCP/IP
        m_sin.sin_addr.s_addr = htonl(0); // 0.0.0.0
        m_sin.sin_port = htons(JULIUS_OUTPUT_SERVER_PORT);
        m_ev_listener = evconnlistener_new_bind(m_ev_base,
                CJuliusOutputEventServer::CallbackEvAccept, &m_cbData,
                LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1,
                (struct sockaddr*) &m_sin, sizeof (m_sin));
        if (!m_ev_listener) {
            perror("Couldn't create listener");
            return;
        }
        evconnlistener_set_error_cb(m_ev_listener, CJuliusOutputEventServer::CallbackEvError);

        // Message pump 
        struct timeval one_sec = {0, 10};
        event_add(event_new(m_ev_base, -1, EV_PERSIST, _cbNull, NULL), &one_sec);

    }

    CJuliusOutputEventServer::CJuliusOutputEventServer(const CJuliusOutputEventServer& orig) {
    }

    CJuliusOutputEventServer::~CJuliusOutputEventServer() {
        //    thread_manager.join_all(); // wait...
        if (m_ev_listener) evconnlistener_free(m_ev_listener);
        if (m_ev_base) event_base_free(m_ev_base);
    }

    /*
     * LibEventによる待受を始める
     * 終わるまで制御を戻さない
     */
    void CJuliusOutputEventServer::run() {
        //    thread_manager.create_thread(boost::bind(&event_base_dispatch, m_ev_base));
        event_base_dispatch(m_ev_base);
    }
    void CJuliusOutputEventServer::runAsyncThread()
    {
        event_base_loop(m_ev_base, EVLOOP_NONBLOCK);
    }
    
    void CJuliusOutputEventServer::waitAsyncThread()
    {
        event_base_dispatch(m_ev_base);
    }
    void CJuliusOutputEventServer::terminateAsyncThread()
    {
        timeval tv = {5, 0};
//        event_base_loopbreak(m_ev_base);
        event_base_loopexit(m_ev_base, &tv);
    }
    
    bool CJuliusOutputEventServer::onData(char* text) {
        fprintf(stdout, "TEXT: %s\n", text);
        if (m_cbData.evbuf) {
            bufferevent_write(m_cbData.evbuf, text, strlen(text));
            return true;
        }

        return false;
    }

    void
    CJuliusOutputEventServer::CallbackEvAccept(evconnlistener* listener,
            int fd, sockaddr* address, int socklen, void* ctx) {
        /* We got a new connection! Set up a bufferevent for it. */
        struct event_base *base = evconnlistener_get_base(listener);
        struct bufferevent *bev = bufferevent_socket_new(
                base, fd, BEV_OPT_CLOSE_ON_FREE);

        JuliusOutputEventServerCBData *cbData = (JuliusOutputEventServerCBData*) ctx;
        cbData->evbuf = bev;

        bufferevent_setcb(bev,
                CJuliusOutputEventServer::CallbackEvRead,
                CJuliusOutputEventServer::CallbackEvWrite,
                CJuliusOutputEventServer::CallbackEvEvent,
                (void *) cbData);


        bufferevent_enable(bev, EV_READ|EV_WRITE);

        // Welcome message to client
        //bufferevent_write(bev, "WELCOME\n", strlen("WELCOME\n"));
        std::string response = CJulius::CallbackReceiveMessage("getVersion", NULL, cbData->julius);
        bufferevent_write(bev, response.c_str(), response.length());

    }

    void
    CJuliusOutputEventServer::CallbackEvError(evconnlistener* listener, void* ctx) {
        struct event_base *base = evconnlistener_get_base(listener);
        int err = EVUTIL_SOCKET_ERROR();
        fprintf(stderr, "Got an error %d (%s) on the listener. "
                "Shutting down.\n", err, evutil_socket_error_to_string(err));

        event_base_loopexit(base, NULL);
    }

    void
    CJuliusOutputEventServer::CallbackEvRead(struct bufferevent *bev, void *ctx) {
        JuliusOutputEventServerCBData *cbdata = (JuliusOutputEventServerCBData *) ctx;

        fprintf(stderr, "READ_CB\n");

        char tmp[1024];
        int i, n;

        while (n = bufferevent_read(bev, tmp, sizeof (tmp))) {
            tmp[n] = '\0';

            // 「.」だけが送られてきた場合はそこで終了する.
            if (tmp[0] == '.') event_base_loopbreak(cbdata->evbase);
            
            // chomp
            if (tmp[strlen(tmp)-1] == '\n') tmp[strlen(tmp)-1] = '\0';
            if (tmp[strlen(tmp)-1] == '\r') tmp[strlen(tmp)-1] = '\0';

            char* msg = tmp;
            char* param = NULL;
            for (i = 0; i < n; ++i) {
                if ((tmp[i] == (char) ':') && (i != n - 1)) {
                    param = &tmp[i + 1];
                    tmp[i] = '\0';
                }
            }

            std::string response = CJulius::CallbackReceiveMessage(msg, param, cbdata->julius);
            bufferevent_write(bev, response.c_str(), response.length());
        }

    }

    void
    CJuliusOutputEventServer::CallbackEvWrite(struct bufferevent *bev, void *ctx) {
        //JuliusOutputEventServerCBData *cbdata = (JuliusOutputEventServerCBData *)ctx;

        fprintf(stderr, "WRITE_CB\n");
    }

    void
    CJuliusOutputEventServer::CallbackEvEvent(struct bufferevent *bev, short events, void *ctx) {
        JuliusOutputEventServerCBData *cbdata = (JuliusOutputEventServerCBData *) ctx;

        fprintf(stderr, "EVENT_CB\n");
        if (events & BEV_EVENT_ERROR)
            perror("Error from bufferevent");
        if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
            bufferevent_free(bev);
        }
    }

}; // namespace