#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <err.h>
#include <fcntl.h>
#include <signal.h>

#include <event.h>

#define SET_PERSIST     // 設定時にイベント継続を指定する場合、定義します。
#define ADD_TIME_OUT    // 追加時に timeval を指定する場合、定義します。

// タイマー
struct timeval *g_ptimeout = NULL;

const int PORT_NUM = 2345; // 2345番ポート

//---------------------------------------------------------------------------
// シグナル終了合図
//---------------------------------------------------------------------------
void sigTerm( int pid )
{
        delete g_ptimeout;

        exit( 1 );
}

//---------------------------------------------------------------------------
// イベント削除
//---------------------------------------------------------------------------
void deleteEvent( int fd, struct event *pev )
{
        // ソケットディスクリプタを閉じる
        close( fd );

        // イベント削除
        event_del( pev );

        // メモリ解放
        delete pev;
}

//---------------------------------------------------------------------------
// 受信処理
// @param       fd              ファイルディスクリプタ
// @param       event   イベントフラグ
// @param       *arg    なんでも領域
//---------------------------------------------------------------------------
void connection_read( int fd, short event, void *arg )
{
        // デバッグ表示
        printf( "%s(): fd:%d, event:%d.\n", __func__, fd, event);
        struct event *pev = (struct event *)arg;

        if( event & EV_READ )
        {
                //---------------------------------------------
                // 受信イベント
                //---------------------------------------------
                char                            chatLog[255];
                char                            str[255];
                memset( chatLog, 0, sizeof( chatLog ) );
                memset( str, 0, sizeof( str ) );

                // データ受信
                int ret = recv( fd, chatLog, 255, 0 );

                if( ret == 0 )
                {
                        // 終了
                        puts( "ret == 0. shutdown." );
                        deleteEvent( fd, pev );
                }
                else if( ret < 0 )
                {
                        // エラー
                        if( errno != EWOULDBLOCK )
                        {
                                printf( "ret:%d. error.\n", ret );
                                deleteEvent( fd, pev );
                        }
                        else
                        {
                                // 読み込み未完了
                                printf( "ret:%d. would_block\n", ret );
                        }
                }
                else
                {
                        // 受信データをそのまま表示
                        printf( "ret:%d. chat_log:%s\n", ret, chatLog );

                        // クライアントにエコー
                        char echoStr[1024];
                        sprintf( echoStr, "echo -> %s", chatLog );
                        if( send( fd, echoStr, strlen( echoStr ) + 1, 0 ) < 0 )
                        {
                                printf( "send error!!\n" );
                                deleteEvent( fd, pev );
                        }

#ifndef SET_PERSIST
                        //---------------------------------------------
                        // EV_PERSIST フラグが設定されていない場合、
                        // 再度 event_add を行う必要があります。
                        //---------------------------------------------
                        event_add( pev, g_ptimeout );
#else
#ifdef ADD_TIME_OUT
                        //---------------------------------------------
                        // timeval が設定されている場合、
                        // 再度 event_add を行う必要があります。
                        //---------------------------------------------
                        event_add( pev, g_ptimeout );
#endif
#endif
                }
        }
        else if( event & EV_TIMEOUT )
        {
                //---------------------------------------------
                // タイムアウトイベント
                // このときイベントはすでに削除されています。
                //---------------------------------------------
                printf( "Time Out. fd:%d\n", fd );
                close( fd );
                delete pev;
        }
}

//---------------------------------------------------------------------------
// 接続
// @param       fd              ファイルディスクリプタ
// @param       event   イベントフラグ
// @param       *arg    なんでも領域
//---------------------------------------------------------------------------
void connection_accept( int fd, short event, void *arg )
{
        // デバッグ表示
        fprintf( stderr, "%s(): fd = %d, event = %d.\n", __func__, fd, event );

        if( event & EV_READ )
        {
                //---------------------------------------------
                // 受信イベント
                //---------------------------------------------
                struct sockaddr_in sock_in;
                socklen_t len = sizeof( sock_in );

                //---------------------------------------------
                // accept
                //---------------------------------------------
                int newsock = accept( fd, (struct sockaddr *) &sock_in, &len );
                if( newsock < 0 )
                {
                        perror( "accept" );
                        return;
                }

                //---------------------------------------------
                // 接続してきたクライアント用のイベントを設定
                //---------------------------------------------
                struct event *pev = new struct event;

#ifdef SET_PERSIST
                //---------------------------------------------
                // libevent 設定
                // EV_READ に加えて EV_PERSIST を設定することで、
                // イベントが内部で削除されるのを防ぎます。
                //---------------------------------------------
                event_set( pev, newsock, EV_READ | EV_PERSIST, connection_read, pev );
#else
                //---------------------------------------------
                // libevent 設定
                // EV_READ のみを設定すると、EV_READイベントを
                // 一度受け取った際、内部でイベントが削除されます。
                //---------------------------------------------
                event_set( pev, newsock, EV_READ, connection_read, pev );
#endif

#ifdef ADD_TIME_OUT
                if( !g_ptimeout )
                {
                        g_ptimeout = new struct timeval;
                        g_ptimeout->tv_sec      = 10;   // 10秒でタイムアウトイベント発生するように設定
                        g_ptimeout->tv_usec     = 0;
                }
#endif
                //---------------------------------------------
                // 先ほど設定したイベントを追加
                // NULL 以外を追加すると、EV_PERSIST 指定は無効になります。
                //---------------------------------------------
                event_add( pev, g_ptimeout );
        }
        else if( event & EV_TIMEOUT )
        {
                //---------------------------------------------
                // タイムアウトイベント
                // acceptでは来ない予定。
                //---------------------------------------------
                printf( "Time Out. fd:%d\n", fd );
        }
}

//---------------------------------------------------------------------------
// メイン
// CFLAGS=-DUSE_DEBUG(./configure 時指定で libevent のデバッグ)
//---------------------------------------------------------------------------
int main()
{
        //---------------------------------------------
        // ソケット作成
        //---------------------------------------------
        int sock = socket( PF_INET, SOCK_STREAM, 0 );
        if( sock < 0 )
        {
                perror( "socket" );
                exit(1);
        }

        //---------------------------------------------
        // bind
        //---------------------------------------------
        struct sockaddr_in sock_in;
        bzero( &sock_in, sizeof( sock_in ) );
        sock_in.sin_family      = AF_INET;
        sock_in.sin_port        = htons( PORT_NUM );
        sock_in.sin_addr.s_addr = INADDR_ANY;
        if( bind( sock, (struct sockaddr *) &sock_in, sizeof( sock_in ) ) < 0 )
        {
                perror("bind");
                exit(1);
        }

        //---------------------------------------------
        // listen
        //---------------------------------------------
        if( listen( sock, 128 ) < 0 )
        {
                perror( "listen" );
                exit(1);
        }

        //---------------------------------------------
        // libevent を初期化
        //---------------------------------------------
        event_init();

        struct event ev;
        //---------------------------------------------
        // libevent 設定
        // EV_READ に加えて EV_PERSIST を設定することで、
        // イベントが内部で削除されるのを防ぎます。
        // accept は何度も繰り返しイベントが発生するので、EV_PRESIST 指定
        //---------------------------------------------
        event_set( &ev, sock, EV_READ | EV_PERSIST, connection_accept, &ev );

        //---------------------------------------------
        // libevent 追加
        // NULL 以外を追加すると、EV_PERSISTは無効になります。
        // accept は何度も繰り返しイベントが発生するので、NULL
        //---------------------------------------------
        event_add( &ev, g_ptimeout );

        // method check.
        printf( "event_method : %s\n", event_get_method() );

        puts( "starting..." );

        //---------------------------------------------
        // イベント開始
        //---------------------------------------------
        event_dispatch();

        puts( "finish." );

        delete g_ptimeout;

        return 0;
}