/*
  circular queue data structure for network i/o buffering
  which suitable for windows iocp / linux-epoll socket model.
  copyright(c) 1990 - 2011 krkim all rights reserved.
  http://krkim.net

 */

#ifdef _WINDOWS
#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <assert.h>
#else /*linux or unix g++*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>    /* isdegit() */
#include <assert.h>
#endif
#include "iocq.h"

int CIOCQ::m_iocq_count = 0;
CIOCQ::CIOCQ()
{
}

CQUEUE * CIOCQ::Init(int buffsize)
{
	CQUEUE *pIOCQ = new CQUEUE;
	if(!pIOCQ) return NULL;
	m_iocq_count++;

	pIOCQ->buff = new char[buffsize+1];
	memset(pIOCQ->buff, 0, buffsize+1);
	pIOCQ->front = pIOCQ->rear = 0;
	pIOCQ->buffsize = buffsize;
	pIOCQ->datasize = 0;
	return pIOCQ;
}

void CIOCQ::Free(CQUEUE *pIOCQ)
{
	if(pIOCQ->buff)
		delete pIOCQ->buff;
	pIOCQ->buff = NULL;
	delete pIOCQ;
	m_iocq_count--;
}

//----------------------------------------------------------------------------
// 큐 버퍼 초기화
//----------------------------------------------------------------------------
void CIOCQ::Clear(CQUEUE *pIOCQ)
{
	pIOCQ->front = pIOCQ->rear = 0;
	pIOCQ->datasize = 0;
}

//----------------------------------------------------------------------------
// 쓰기 포인터 오프셋 증가(쓴 후 처리) Move Rear Pointer
//----------------------------------------------------------------------------
int	CIOCQ::ShiftRear(CQUEUE *pIOCQ,int nShift)
{
	pIOCQ->rear = ( pIOCQ->rear + nShift ) % pIOCQ->buffsize;
	pIOCQ->datasize += nShift;
	if(pIOCQ->datasize > pIOCQ->buffsize)
		pIOCQ->datasize = pIOCQ->buffsize;
	return pIOCQ->rear;
}

//----------------------------------------------------------------------------
// 읽기 포인터 오프셋 증가(읽은 후 처리)
//----------------------------------------------------------------------------
int	CIOCQ::ShiftFront(CQUEUE *pIOCQ,int nShift)
{
	pIOCQ->front = (pIOCQ->front + nShift) % pIOCQ->buffsize;
	pIOCQ->datasize -= nShift;
	if(pIOCQ->datasize <= 0){
		pIOCQ->front = pIOCQ->rear = 0;
		pIOCQ->datasize = 0;
	}
	return pIOCQ->front;
}

bool CIOCQ::IsFull(CQUEUE *pIOCQ)
{
	if(pIOCQ->datasize == pIOCQ->buffsize)
		return true;
	return false;
}

//----------------------------------------------------------------------------
// 데이타 없는지 검사
//----------------------------------------------------------------------------
bool CIOCQ::IsEmpty(CQUEUE *pIOCQ)
{
	if(pIOCQ->datasize == 0)
		return true;
	return false;
}

//----------------------------------------------------------------------------
// 인큐 기본함수
//----------------------------------------------------------------------------
bool CIOCQ::enqueue(CQUEUE *pIOCQ,char ch)
{
	if(IsFull(pIOCQ))
		return false;
	*(pIOCQ->buff + pIOCQ->rear) = ch;
	ShiftRear(pIOCQ,1);
	return true;
}

//----------------------------------------------------------------------------
// 디큐 기본함수
//----------------------------------------------------------------------------
bool CIOCQ::dequeue(CQUEUE *pIOCQ,char *ch)
{
	*ch = 0;
	if(IsEmpty(pIOCQ))
		return false;
	*ch = *(pIOCQ->buff + pIOCQ->front);
	ShiftFront(pIOCQ,1);
	return true;
}

//----------------------------------------------------------------------------
// 버퍼내 offset의 데이타 포인터
//----------------------------------------------------------------------------
char * CIOCQ::getoffsetptr(CQUEUE *pIOCQ,int nOffset)
{
	nOffset = nOffset % pIOCQ->buffsize;
	return (pIOCQ->buff + nOffset);
}

//----------------------------------------------------------------------------
//nOffset으로 부터 버퍼끝까지 sequential 하게 입력가능한 크기
//----------------------------------------------------------------------------
int CIOCQ::getoffsetsize(CQUEUE *pIOCQ,int nOffset)
{
	int freesize;
	nOffset = nOffset % pIOCQ->buffsize;
	if(nOffset < pIOCQ->front)
		freesize = (pIOCQ->front - nOffset);
	else{
		freesize = pIOCQ->buffsize - nOffset;
	}
	return freesize;
}

//----------------------------------------------------------------------------
//Get Data size
//----------------------------------------------------------------------------
int	CIOCQ::GetDataSize(CQUEUE *pIOCQ)
{
	return pIOCQ->datasize;
}

//----------------------------------------------------------------------------
//Get Free Queue size
//----------------------------------------------------------------------------
int	CIOCQ::GetAvailSize(CQUEUE *pIOCQ)
{
	return pIOCQ->buffsize - pIOCQ->datasize;
}

//----------------------------------------------------------------------------
//Get Data Buffer From Front Pointer
//lpRestDataLen :front로부터 버퍼끝까지의 데이타크기
// |-->F         R-->| , R - F size
// |-->R         F-->| , F--> size only(except -->R)
//----------------------------------------------------------------------------
char * CIOCQ::GetFrontPtr(CQUEUE *pIOCQ)
{
	return pIOCQ->buff + pIOCQ->front;
}

//----------------------------------------------------------------------------
//Get Rear Pointer to Write Data
//----------------------------------------------------------------------------
char * CIOCQ::GetRearPtr(CQUEUE *pIOCQ)
{
	return (pIOCQ->buff + pIOCQ->rear);
}

//----------------------------------------------------------------------------
//return the size that add to sequentially new data 
//from current rear to end of buffer.
//----------------------------------------------------------------------------
int CIOCQ::GetRearSize(CQUEUE *pIOCQ)
{
	return getoffsetsize(pIOCQ,pIOCQ->rear);
}

//----------------------------------------------------------------------------
//낭비되는 공간을 활용하여 연속으로 입력할 최대 공간을 확보한다
//즉,front(F) 가 rear(R) 보다 작을때 , front가 버퍼처음으로부터
//떨어져 있는 만큼 낭비되는 rear이후의 순차적인 입력공간을 확보한다.
//IOCP에 현재 수신요청할 버퍼에 GetRearPtr을 주기전에 수행하여,여유공간을 확보.
//왜냐하면,IOCP버퍼는 circular 가 아닌 일반적인 linear-buffer(array).
// |-->F         R-->| , 일때 F가 띄여진 만큼 R이후의 공간이 낭비된다.
// |F          R---->| 되도록 보정한다.
// rear 이 front 보다 작을때는 필요없다,입력가능 공간(rear~front앞)까지
// 순차적이어서 낭비공간이 없기 때문이다.
//리턴값:연속입력 가능한 (rear 이후의) 데이타 크기
//IOCP에 받을 버퍼와 받을 크기를 넘겨줄때 사용한다.
//ShiftData() 호출후 받을크기와 GetRearPtr()로 받을 버퍼지정
//ex)
//다음수신을 받기위해 IOCP 요청시,
//버퍼를 정리하면서 sequential 한 버퍼크기를 얻어온다.
//int nAvailBuffSize = CIOCQ::ShiftData(pIOBuff);
//char *buf = CIOCQ::GetRearPtr(pIOBuff);
//----------------------------------------------------------------------------

int CIOCQ::ShiftData(CQUEUE *pIOCQ)
{
	if(pIOCQ->rear >= pIOCQ->front && pIOCQ->front > 0){
		for(int i = 0; i < pIOCQ->datasize; i++){
			*(pIOCQ->buff + i) = *(pIOCQ->buff + pIOCQ->front + i);
		}
		pIOCQ->rear -= pIOCQ->front;
		pIOCQ->front = 0;
	}
	return GetRearSize(pIOCQ);
}

//----------------------------------------------------------------------------
//bAll : TRUE = nLen 만큼 모두를 입력하기 원할때
//       FALSE = 가능한 만큼만 입력하고 입력갯수를 리턴
//return : 입력된 데이타 갯수
//----------------------------------------------------------------------------
int CIOCQ::Enqueue(CQUEUE *pIOCQ,char *pData, int nLen,bool bAll)
{
	int nDataSize = GetDataSize(pIOCQ);
	int nAvailSize = GetAvailSize(pIOCQ);
	int nWrite = 0;
	if(bAll && nAvailSize < nLen)//모든데이타를 입력을 원하는 경우
		return 0;
	for(nWrite = 0; nWrite < nLen;nWrite++){
		if(!enqueue(pIOCQ,*(pData+nWrite)))
			break;
	}
	return nWrite;
}

//----------------------------------------------------------------------------
//Peek Data from Front Pointer (실제 shift front 하지 않는다)
//nLen: 읽을 크기
//nOffset : front 로부터의 오프셋,즉 버퍼내에서의 시작위치로 부터
//          0 = 현재 읽을 위치인 front 로부터 읽음
//리턴값: 실제 읽은 크기(0 = empty)
//----------------------------------------------------------------------------
int CIOCQ::Peek(CQUEUE *pIOCQ,char *pDest, int nLen,int nOffset)
{
	int nRead = 0;
	int nDataSize = GetDataSize(pIOCQ);
	int nSkipSize = nOffset;
	int nToLen = (nDataSize < nOffset + nLen) ? nDataSize - nOffset: nLen;
	int front = (pIOCQ->front + nOffset) % pIOCQ->buffsize;//큐오프셋으로 변환
	for(nRead = 0; nRead < nToLen;nRead++){
		char ch = *(pIOCQ->buff + front);
		*(pDest+nRead) = ch;
		front = (front + 1) % pIOCQ->buffsize;
	}
	*(pDest+nRead) = 0;
	return nRead;
}

//----------------------------------------------------------------------------
//GetData from Front Pointer and shift front
//nOffset : front 로부터의 오프셋,즉,받은 버퍼내에서의 오프셋
//nLen: 원하는 데이타 크기
//return:실제 읽은 데이타 크기
//----------------------------------------------------------------------------
int CIOCQ::Dequeue(CQUEUE *pIOCQ,char *pDest, int nLen,int nOffset)
{
	int nRead = 0;
	nRead = Peek(pIOCQ,pDest,nLen,nOffset);
	ShiftFront(pIOCQ,nOffset + nRead);
	return nRead;
}
