/**    
    @file     pollmng.c
    @date     2009/3/19
    @author   오재경 freefrug@falinux.com  FALinux.Co.,Ltd.
    @brief    poll 을 관리한다.

    @modify   2009-05-07 (오재경) 파일하나만을 폴로 돌리는 함수를 추가
              2009-05-20 (오재경) tag 로써 poll_obj 객체를 얻는 함수 추가
              2009-10-09 (오재경) poll_do_loop() 함수의 재귀호출 회수를 제한하도록 수정
              2010-01-04 (오재경) poll_obj_t 구조체에서 poll_ndx 멤버변수 제거
                                 poll_delete() 함수에서 tlist_delete() 함수대신 tlist_remove()함수로 수정
              2010-03-19 (오재경) poll_obj_t 구조체에서 on_disconnect 멤버 추가
                                 tcp 일 경우 사용됨
              2010-08-18 (장길석) mingw와 함께 사용할 수 있는 코드 추가
              2014-09-03 (김민수) tty가 아닌 stdin이 POLL_IN으로 등록될 경우 항상 readable하기 때문에
                                  CPU 점유율이 100%가 되는 문제가 있어서
                                  stdin이 tty가 아닐 경우 이벤트를 등록하지 않는다.
                                  
    @todo    
    @bug     
    @remark   
    
    
    @warning 
*/
//
//  저작권    에프에이리눅스(주)
//            외부공개 금지
//
//----------------------------------------------------------------------------
#define EMBEDDED_LINUX                                          // 이렇게 처리하지 않으면 EClipse에서 C 영역이 회색 바탕이 됨

#ifdef MS_WIN32
    #undef EMBEDDED_LINUX
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef EMBEDDED_LINUX

	#include <sys/socket.h>
	#include <sys/poll.h>

#else

	#include <windows.h>
    #include <winsock2.h>

#endif

#ifndef TRUE

    #define     TRUE        1
    #define     FALSE       0

#endif

#include <tlist.h>
#include <pollmng.h>


char desc_pollmng[] = "falinux pollmng ver 0.2.4";

/// 폴 관리 변수
static struct pollfd  poll_array[POLL_MAX_COUNT];
static tlist         *poll_list = NULL;
static int            cnt_recursive     = 0;                    // poll_do_loop()가 완료 되기 저에 poll_do_loop()가 호축되는 카운터
static int            is_need_rebuild   = TRUE;                 // poll 등록 변화에 의해 poll_array를 갱신할 필요가 있는지 여부
static int            is_loop_break     = FALSE;                // on_poll_xx 콜백함수에 의해 객체의 리스트가 변경되었다면
                                                                // 다음 폴이벤트를 수행하지 않고 나가기 위한 변수
                                                                // TCP 서버를 위해 필요하다.

#ifdef MS_WIN32

/* Type used for the number of file descriptors.  */
typedef unsigned long int nfds_t;

static int poll( struct pollfd *a_fds, nfds_t a_nfds, int a_timeout){

    struct pollfd   *p_fds;
    FD_SET           rset;

//jwjw 이후 체크    DWORD            dwMask;
    int              cnt_rs             = 0;
    int              cnt_sock           = 0;
    int              is_socket_exists   = FALSE;
    int              ndx;

    // 먼저 시리얼 부터 확인한다.
    p_fds   = a_fds;
    for ( ndx = 0; ndx < a_nfds; ndx++){

        p_fds->revents  = 0;

        if ( p_fds->is_serial){
            DWORD dwBytesRead, dwErrorFlags;
            COMSTAT comstat;

            ClearCommError( p_fds->fd, &dwErrorFlags, &comstat);
            dwBytesRead = comstat.cbInQue;    // input queue에 들어와 있는 데이터의 길이

            if ( 0 < dwBytesRead){
                p_fds->revents  = POLLIN;
                cnt_rs++;
            } else {
                p_fds->revents  = 0;
            }



//            if ( WaitCommEvent( p_fds->fd, &dwMask, NULL)){
//                p_fds->revents  = POLLIN;
//                cnt_rs++;
//            } else {
//                p_fds->revents  = 0;
//            }

        } else {
            is_socket_exists = TRUE;
        }
        p_fds++;
    }

    if ( !is_socket_exists){
        usleep( 1000);
        return cnt_rs;
    }

    // 소켓 쪽에 이벤트가 발생했는지 확인한다.

    p_fds   = a_fds;
    FD_ZERO( &rset);

    for( ndx = 0; ndx < a_nfds; ndx++ ){
        if ( !p_fds->is_serial){
            if ( -1 != *(SOCKET *)p_fds->fd ){
                FD_SET( *(SOCKET *)p_fds->fd, &rset );
            }
        }
        p_fds++;
    }

    struct timeval  sttTimeout;

    sttTimeout.tv_sec   = a_timeout / 1000;
    sttTimeout.tv_usec  = ( a_timeout % 1000 ) * 1000;

    cnt_sock = select( 0, &rset, NULL, NULL, &sttTimeout );
    if( 0 > cnt_sock ){                                         // 에러 상황이므로 반환
        return -1;
    }
    else {
        p_fds       = a_fds;
        cnt_sock    = 0;
        for ( ndx = 0; ndx < a_nfds; ndx++){
            if ( !p_fds->is_serial){
                if ( FD_ISSET( *(SOCKET *)p_fds->fd, &rset)){
                    p_fds->revents  = POLLIN;
                    cnt_sock++;
                }
            }
            p_fds++;
        }
    }
    return cnt_rs + cnt_sock;
}

#endif


//------------------------------------------------------------------------------
/** @brief    poll 관리 객체를 생성한다.
    @remark
*///----------------------------------------------------------------------------
void  poll_init( void )
{
	poll_list           = tlist_create();

	is_need_rebuild     = TRUE;                                 // poll 등록 변화에 의해 poll_array를 갱신할 필요가 있는지 여부
	is_loop_break       = FALSE;                                // on_poll_xx 콜백함수에 의해 객체의 리스트가 변경되었다면

#ifdef MS_WIN32

	WSADATA     wsaData;

	if ( 0 != WSAStartup( MAKEWORD( 2, 2), &wsaData)){
	    perror( "WSAStartup() error!!" );
	    exit( 1);
	}

#endif

}	
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체를 해제한다.
    @remark
*///----------------------------------------------------------------------------
void  poll_exit( void )
{
	poll_obj_t *obj;
	int  ndx;

	for ( ndx = 0; ndx < poll_list->fcount ; ndx++ )
	{
		obj = tlist_get( poll_list, ndx );
		if ( 0 <= obj->fd )
		{
#ifdef EMBEDDED_LINUX

			close( obj->fd );

#else

			CloseHandle( obj->fd);

#endif
		}
		
		free( (void *)obj );
	}
	
	tlist_free( poll_list );
}	
//------------------------------------------------------------------------------
/** @brief    리스트로 관리되는 파일디스크립터들을 폴배열에 재구성한다.
    @remark   폴로 관리되는 객체의 이벤트함수를 등록한후 반드시 호출하여야 한다.
*///----------------------------------------------------------------------------
void  poll_rebuild( void ){

	poll_obj_t *obj;
	int  ndx;
	
	memset( poll_array, 0, sizeof(poll_array) );
	
	for (ndx= 0; ndx < poll_list->fcount; ndx++)
	{
		int events;
		
		obj = (poll_obj_t *)tlist_get( poll_list, ndx );
		
		if ( obj->fd == fileno(stdin) && isatty(fileno(stdin)) == 0 )
		{
			continue;
		}

		events = 0;		
		if ( obj->on_poll_in  ) events |= POLLIN;
		if ( obj->on_poll_out ) events |= POLLOUT;
		if ( obj->on_poll_err ) events |= POLLERR;
		if ( obj->on_poll_hup ) events |= POLLHUP;
		
		poll_array[ndx].fd     = obj->fd;
		poll_array[ndx].events = events;

#ifdef MS_WIN32

        poll_array[ndx].is_serial   =   obj->is_serial;

#endif

	}	
	
	is_loop_break   = TRUE;
	is_need_rebuild = FALSE;
}
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체에 열려진 파일 디스크립터를 등록한다.
    @param    fd  열린 파일 디스크립터
    @return   poll_obj_t 형식의 포인터
    @remark   이벤트함수( on_poll_in, on_poll_out )를 등록한후
              poll_rebuild() 함수를 반드시 호출한다.
*///----------------------------------------------------------------------------
poll_obj_t *poll_add( fd_t fd )
{
	poll_obj_t *obj;
	int idx;

	if ( NULL == poll_list )
	{
		printf( "error need to call poll_init()\n" );
		return NULL;
	}

	obj = (poll_obj_t *)malloc( sizeof(poll_obj_t) );
	memset( (void *)obj, 0, sizeof(poll_obj_t) );
		
	obj->fd   = fd;
	obj->type = STYP_FILE;    
	obj->tag  = 0;
	obj->on_poll_in     = NULL;
	obj->on_poll_out    = NULL;
	obj->on_poll_err    = NULL;
	obj->on_poll_hup    = NULL;
	obj->on_timeout     = NULL;
	obj->on_disconnect  = NULL;
	obj->priv           = NULL;
	obj->user           = NULL;

#ifdef MS_WIN32

	obj->is_serial      = FALSE;

#endif

	idx = tlist_add( poll_list, (void *)obj );
	if ( 0 <= idx )	{
		is_need_rebuild = TRUE;
		return obj;	
	}
	else {
		free( (void *)obj );
		return NULL;
	}
}
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체에서 개개의 폴구조체 포인터를 얻는다.
    @param    idx  인덱스
    @return   poll_obj_t 형식의 포인터
*///----------------------------------------------------------------------------
poll_obj_t *poll_get_obj( int idx  )
{
	if ( ( 0 <= idx ) && ( idx < poll_list->fcount ) )
	{
		return (poll_obj_t *)tlist_get( poll_list, idx );
	}
	
	return NULL;
}
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체에서 사용자 포인터를 얻는다.
    @param    idx  인덱스
    @return   void 형식의 포인터
*///----------------------------------------------------------------------------
void *poll_get_priv( int idx  )
{
	poll_obj_t *obj;
	
	if ( ( 0 <= idx ) && ( idx < poll_list->fcount ) )
	{
		obj = (poll_obj_t *)tlist_get( poll_list, idx );
		
		return obj->priv;
	}
	
	return NULL;
}
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체가 관리하는 파일의 개수
    @return   관리하는 파일의 개수
*///----------------------------------------------------------------------------
int  poll_count( void  )
{
	return poll_list->fcount;
}


//------------------------------------------------------------------------------
/** @brief    poll 관리 객체에서 파일디스크립트로 관리하는 폴객체를 반환한다.
    @param    fd  파일 디스크립터
    @return   poll_obj_t 형식의 포인터
*///----------------------------------------------------------------------------
poll_obj_t *poll_obj_byfd( fd_t fd )
{
	poll_obj_t *obj;
	int  idx;
	
	for( idx=0; idx<poll_list->fcount; idx++ )
	{
		obj = tlist_get( poll_list, idx );
		if ( obj->fd == fd )
		{
			return obj;
		}
	}
	
	return NULL;
}
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체에서 객체의 포인터로 삭제한다.
    @param    obj  poll_obj_t 형식의 포인터
    @remark   관리하는 파일도 close 된다.
              poll_rebuild() 함수가 내부에서 호출되므로 외부에서는 호출하지 않는다.
*///----------------------------------------------------------------------------
void  poll_delete( poll_obj_t *obj )
{
	fd_t 	fd;
	
	fd  = obj->fd;
	
	free( (void *)obj );              // 사용한 메모리를 해제한다.

	// 관리하는 파일을 닫는다.
#ifdef EMBEDDED_LINUX

	close( fd );

#else

	CloseHandle( fd );

#endif

	tlist_remove( poll_list, obj ); 

	poll_rebuild();                   // 폴을 재구성한다.
}
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체에서 파일디스크립터로 삭제한다.
    @param    fd  파일 디스크립터
    @remark   관리하는 파일도 close 된다.
              poll_rebuild() 함수가 내부에서 호출되므로 외부에서는 호출하지 않는다.
*///----------------------------------------------------------------------------
void  poll_delete_byfd( fd_t fd )
{
	poll_obj_t *obj;
	
	obj = poll_obj_byfd( fd );
	if ( obj )
	{
		poll_delete( obj );
	}
}
//------------------------------------------------------------------------------
/** @brief    poll 이벤트 메인 루프함수
    @param    time_out  msec 단위의 타임아웃
    @return   POLL_ASYNC_ERR  시그널에 의해 중지되었다.
              POLL_TIME_OUT   대기 시간이 종료되었다.
              POLL_EVENTED    이벤트가 정상 처리되었다.
    @remark   외부 루프에서 계속호출한다.
*///----------------------------------------------------------------------------
int  poll_do_loop( int time_out )
{
	int  fd_cnt, event_cnt;
	int  ndx, rtnval = POLL_EVENTED;

	// 재귀호출의 회수를 제한한다.
	if ( POLL_RECURSIVE_COUNT < cnt_recursive )
	{
		printf( "fatal error : poll_do_loop() recursive limit\n" );
		return POLL_RECURSIVE_LIMIT_ERR;	
	}
	cnt_recursive++;
	
	if ( is_need_rebuild ){                                   // poll 등록 변화에 의해 poll_array를 갱신할 필요가 있는지 여부
		poll_rebuild();	
	}

	// 이벤트가 발생할때까지 대기상태로 놓인다.
	fd_cnt    = poll_list->fcount;
	event_cnt = poll( (struct pollfd *)&poll_array, fd_cnt, time_out );

    if ( 0 > event_cnt )
	{
		rtnval = POLL_ASYNC_ERR;
		goto lable_poll_do_loop_end;
	}
	
    // timeout 이 발생하면 등록된 함수를 호출한다.
	if ( 0 == event_cnt )
	{
		for ( ndx=0; ndx < fd_cnt; ndx++ )
		{
    		poll_obj_t *obj;
    	
    		obj = tlist_get( poll_list, ndx );
    		if ( obj->on_timeout )
    		{
    			obj->on_timeout( obj );
    		}
		}
		rtnval = POLL_TIME_OUT;
		goto lable_poll_do_loop_end;
	}
	
	is_loop_break = FALSE;    // 콜백함수를 호출하기 전에 변수를 초기화한다.
	                            // 콜백함수내에서 poll_rebuild() 함수가 호출되면 값이 TRUE 로 변한다.
	for ( ndx=0; ndx<fd_cnt; ndx++ )
	{
    	poll_obj_t *obj;

    	// 입력이벤트 --------------------------------------
    	if( poll_array[ndx].revents & POLLIN )
    	{
			obj = tlist_get( poll_list, ndx );
			if ( ( obj ) && ( obj->on_poll_in ) )
			{
				obj->on_poll_in( obj );
				event_cnt --;
			}
		}
    	
    	// 출력이벤트 --------------------------------------
    	if( poll_array[ndx].revents & POLLOUT )
    	{
			obj = tlist_get( poll_list, ndx );
			if ( ( obj ) && ( obj->on_poll_out ) )
			{
				obj->on_poll_out( obj );
				event_cnt --;
			}
		}
    	
    	// HUP이벤트 --------------------------------------
    	if( poll_array[ndx].revents & POLLHUP )
    	{
			obj = tlist_get( poll_list, ndx );
			if ( ( obj ) && ( obj->on_poll_hup ) )
			{
				obj->on_poll_hup( obj );
				event_cnt --;
			}
		}
    	
    	// 에러이벤트 --------------------------------------
    	if( poll_array[ndx].revents & POLLERR )
    	{
			obj = tlist_get( poll_list, ndx );
			if ( ( obj ) && ( obj->on_poll_err ) )
			{
				obj->on_poll_err( obj );
				event_cnt --;
			}
		}
		
		if ( 0 >= event_cnt  ) break;
		if ( is_loop_break ) break;                       // 이벤트 처리 중 poll에 등록한 객체에 변화가 있다면( poll_rebuild()를 실행) 분기한다.
	}


lable_poll_do_loop_end:
	
	cnt_recursive --;
	return rtnval; 
}


//------------------------------------------------------------------------------
/** @brief    poll 이벤트를 하나의 파일핸들로만 돌린다.
    @param    fd        파일핸들
    @param    event     POLLIN, POLLOUT 값을 넣는다.
    @param    time_out  msec 단위의 타임아웃
    @return   POLL_ASYNC_ERR  시그널에 의해 중지되었다.
              POLL_TIME_OUT   대기 시간이 종료되었다.
              POLL_EVENTED    이벤트가 발생하였다.
*///----------------------------------------------------------------------------
int  poll_do_one( fd_t fd, int event, int time_out )
{
	int event_cnt;
	struct pollfd  poll_one[4];

	poll_one[0].fd      = fd;	
	poll_one[0].revents = 0;
	poll_one[0].events  = event;
	
	// 이벤트가 발생할때까지 대기상태로 놓인다.
	event_cnt = poll( (struct pollfd *)&poll_one, 1, time_out );
	
	if ( 0 > event_cnt )
	{
		return POLL_ASYNC_ERR;
	}
	
	if ( 0 == event_cnt )
	{
		return POLL_TIME_OUT;
	}
	
	return  POLL_EVENTED;
}
//------------------------------------------------------------------------------
/** @brief    poll 관리 객체에서 tag 번호로 찾아 폴객체를 반환한다.
    @param    tag  태그번호
    @return   poll_obj_t 형식의 포인터
*///----------------------------------------------------------------------------
poll_obj_t *poll_obj_bytag( int tag )
{
	poll_obj_t *obj;
	int  idx;
	
	for( idx=0; idx<poll_list->fcount; idx++ )
	{
		obj = tlist_get( poll_list, idx );
		if ( obj->tag == tag )
		{
			return obj;
		}
	}
	
	return NULL;
}