#include "IPCMsgQueue.h"

#include <pwd.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

#include "Logger.h"

#define  MSG_NOT_VALID	-1

CIPCMsgQueue::CIPCMsgQueue()
	: m_msqid(MSG_NOT_VALID), m_key(MSG_NOT_VALID)
{

}

CIPCMsgQueue::CIPCMsgQueue(key_t qkey, int msgflg /* = IPC_CREAT | 0644 */, const char* user /* = "nobody" */, uint32_t qsize /* = 0 */)
	: m_msqid(MSG_NOT_VALID), m_key(qkey), m_msgflg(msgflg), m_user(user), m_qsize(qsize)
{
}

CIPCMsgQueue::~CIPCMsgQueue()
{
}

bool CIPCMsgQueue::Create()
{
	bool r = true;

	if (m_key == MSG_NOT_VALID)
	{
		LOG(LERR, "Key message has not been set.");
		cerr << "Key message has not been set." << endl;
		return false;
	}

	m_msqid = msgget(m_key, m_msgflg);

	if (m_msqid == MSG_NOT_VALID)
	{
		LOG(LERR, "cannot create [%s]", strerror(errno));
		cerr << "cannot create [" << strerror(errno) << "]" << endl;
		r = false;
	}

	r = CtlOperations(IPC_SET);

	return r;
}

bool CIPCMsgQueue::Create(key_t qkey, int msgflg /* = IPC_CREAT | 0644 */, const char* user /* = "nobody" */, uint32_t qsize /* = 0 */)
{
	if (m_key != MSG_NOT_VALID)
	{
		LOG(LERR, "Got the message key is set, you can not use that function.");
		cerr << "Got the message key is set, you can not use that function." << endl;
		return false;
	}

	return ChangeQueue(qkey, msgflg, user, qsize);
}

bool CIPCMsgQueue::ChangeQueue(key_t qkey, int msgflg /* = IPC_CREAT | 0644 */, const char* user /* = "nobody" */, uint32_t qsize /* = 0 */)
{
	m_msqid = MSG_NOT_VALID;
	m_key = qkey;
	m_msgflg = msgflg;
	m_user = user;
	m_qsize = qsize;
	
	return Create();
}

bool CIPCMsgQueue::Remove()
{
	return CtlOperations(IPC_RMID);
}

bool CIPCMsgQueue::CtlOperations(int cmd)
{
	struct msqid_ds msginfo;
	struct passwd *pwd = NULL;

	if (m_msqid == MSG_NOT_VALID)
	{
		LOG(LERR, "A message queue could not be created.");
		cerr << "A message queue could not be created." << endl;
		return false;
	}

	if (m_user.size())
	{
		if ((pwd = getpwnam(m_user.c_str())) == NULL)
		{
			LOG(LERR, "cannot find user '%s'", m_user.c_str());
			cerr << "cannot find user '" << m_user << "'" << endl;
			return false;
		}
	}

	if (msgctl(m_msqid, IPC_STAT, &msginfo) == -1)
	{
		LOG(LERR, "cannot get status [%s]", strerror(errno));
		cerr << "cannot get status [" << strerror(errno) << "]" << endl;		
		return false;
	}

	if (m_qsize > 0)
		msginfo.msg_qbytes = m_qsize;
	
	if (pwd) {
		msginfo.msg_perm.uid = pwd->pw_uid;
		msginfo.msg_perm.gid = pwd->pw_gid;
	}

	if (msgctl(m_msqid, cmd, &msginfo) == -1)
	{
		LOG(LERR, "cannot set status [%s]", strerror(errno));
		cerr << "cannot set status [" << strerror(errno) << "]" << endl;	
		return false;
	}

	return true;
}

int CIPCMsgQueue::Pop(void * data, size_t maxmsgsz, int flags /* = IPC_NOWAIT */, long msgtype /* = 0 */)
{
	if (m_msqid == MSG_NOT_VALID)
	{
		LOG(LERR, "A message queue could not be created.");
		cerr << "A message queue could not be created." << endl;
		return -2;
	}

	ssize_t msglen = msgrcv(m_msqid, data, maxmsgsz, msgtype, flags);
	if (msglen == -1) 
	{
		int err = errno;
		if (err != ENOMSG)
		{
			LOG(LERR, "can't pop data to ipc message queue because msgrcv() fail. [%s(%d)].", strerror(err), err);
			cerr << "can't pop data to ipc message queue because msgrcv() fail. [" << strerror(err) << "(" << err << ")]" << endl;
			return -1;
		}
		else
		{
			if (flags != IPC_NOWAIT)
			{
				LOG(LWAR, "There is no message of the requested type available on the message queue.");
				cerr << "There is no message of the requested type available on the message queue." << endl;
			}
			return 0;
		}
	}

	return msglen;
}

int CIPCMsgQueue::Push(void * data, size_t maxmsgsz, int flags /* = IPC_NOWAIT */)
{
	if (m_msqid == MSG_NOT_VALID)
	{
		LOG(LERR, "A message queue could not be created.");
		cerr << "A message queue could not be created." << endl;
		return -2;
	}

	if (msgsnd(m_msqid, data, maxmsgsz, flags) == -1)
	{
		int err = errno;
		LOG(LERR, "can't push data to ipc message queue because msgsnd() fail.[%s(%d)]", strerror(err),err);
		cerr << "can't push data to ipc message queue because msgsnd() fail. [" << strerror(err) << "(" << err<< ")]" << endl;
		return -1;
	}

	return (maxmsgsz);
}
