#ifndef __STUBMSGQ_H__
#define __STUBMSGQ_H__

#include <stdlib.h>
#include <stdio.h>
#include "Thread.h"
#include "Mutex.h"
#include "Condition.h"


using namespace std;



/****************************************************************************************
 ** * Overrides of standard implementations; these versions intercept the
 **  * system calls of the same name, in order to facilitate testing.
 **   * The linker sees these first and stops looking for the original system
 **   calls.
 **    ***************************************************************************************/


int msgsnd (int __msqid, __const void *__msgp, size_t __msgsz,
           int __msgflg);

int msgrcv (int __msqid, void *__msgp, size_t __msgsz,
           long int __msgtyp, int __msgflg);


int msgget (key_t __key, int __msgflg);

int msgctl (int __msqid, int __cmd, struct msqid_ds *__buf);

typedef vector<unsigned char> StubMsgData;
typedef deque<StubMsgData> StubMsgQueue;
typedef map<long int, StubMsgQueue> StubMsgQueueMap;
typedef map<int, StubMsgQueueMap> StubMsgQueueMgr;
struct DummyMsg {
   long   mtype;       /* Message type. */
   char   mtext[1];    /* Message text. */
};
static StubMsgQueueMgr stubMsgQueueMgr;
static pthread_mutex_t stubMsgQMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t stubMsgQCond = PTHREAD_COND_INITIALIZER;  



/****************************************************************************************
 The following gives a very rough idea what this fake msgQ is doing. If you simply 
 want to know how to use it. Then what you need to know is just call "initMsgQ()" 
 in your initialize function. That is all you need to do because "msgQ" syscall are 
 replaced below and it would be transparent to user.
      nickhuan@cisco.com
 ***** ***************************************************************************************/

/****************************************************************************************
 The idea of fake msgQ sending syscall is just use the STL map to store message. 
 The "key" of map is the msgQid return from "ftok" that is why we use the "true" ftok
 without faking it.
 Since it is targeted for "unit test", we don't have to write message down into file for 
 other processes to access. That is why we choose "STL" container to store message. 
 ***** ***************************************************************************************/

int msgsnd (int __msqid, __const void *__msgp, size_t __msgsz, int __msgflg){    
    // size cannot be smaller than long
    if (__msgsz < sizeof(long int)){
        return -1;
    }
    pthread_mutex_lock(&stubMsgQMutex);
    
    DummyMsg* dummyMsgPtr = (DummyMsg*)__msgp;
    long int msgtyp = dummyMsgPtr->mtype;
    unsigned char* ptr = (unsigned char*)dummyMsgPtr->mtext; 
    int result = -1;
    StubMsgQueueMgr::iterator it = stubMsgQueueMgr.find(__msqid);
    if (it != stubMsgQueueMgr.end()){ 
        // create new msg
        StubMsgData stubMsgData;
        // skip msgtype
        for (size_t i = 0; i < __msgsz - sizeof(long int); i ++){
            stubMsgData.push_back(*ptr);
            ptr ++;
        }
        
        StubMsgQueueMap& stubMsgQueueMap = it->second;  
        StubMsgQueueMap::iterator qit = stubMsgQueueMap.find(msgtyp);        
            
        if (qit != stubMsgQueueMap.end()){            
            StubMsgQueue& stubMsgQueue = qit->second;
            
            stubMsgQueue.push_back(stubMsgData);            
        }else{
            StubMsgQueue stubMsgQueue;
            stubMsgQueue.push_back(stubMsgData); 
            stubMsgQueueMap.insert(make_pair(msgtyp, stubMsgQueue)); 
        }        
        result = 0;
        pthread_cond_broadcast(&stubMsgQCond);
            
    } else {
        errno = EINVAL;
    }
    pthread_mutex_unlock(&stubMsgQMutex);
    return result;
}

/****************************************************************************************
 msgrcv is pretty much the opposite operation as msgsnd. We can even use a "deque"
 to make it more closer as msgQ. However, map gives you high efficiency when retrieving.
 Also msgrcv is not truely "FIFO" style because it allows you to retrieve msg by msgId. So,
 in this sense a map is more appropriate.
 ***** ***************************************************************************************/

int msgrcv (int __msqid, void *__msgp, size_t __msgsz, long int __msgtyp, int __msgflg){
    
    int result = -1;
    bool isQueueEmpty = false;
    // size cannot be smaller than long
    if (__msgsz <= sizeof(long int) || __msgflg & MSG_EXCEPT){
        // temporarily I don't want to handle the "flag" unless I have to...
        return -1;
    }
    pthread_mutex_lock(&stubMsgQMutex);
    DummyMsg* dummyMsgPtr = (DummyMsg*)__msgp;
    
    do{   
        isQueueEmpty = false;
        unsigned char* ptr = (unsigned char*)dummyMsgPtr->mtext; 
        StubMsgQueueMgr::iterator queueIt;
        queueIt = stubMsgQueueMgr.find(__msqid);

        if (queueIt == stubMsgQueueMgr.end()){
            // no such msgQ with such msgid
            errno = EINVAL;
        }else{
            // if indeed we have the "queue of that '__msqid'"    
            StubMsgQueueMap& stubMsgQueueMap = queueIt->second;
            // now we need to search for the msg itself
            StubMsgQueueMap::iterator msgIt;
            // according to "msgrcv" the "type" has such requirement
            if (__msgtyp > 0){
                // exact match
                msgIt = stubMsgQueueMap.find(__msqid);
            }else{
                if (__msgtyp == 0){
                    // first match
                    msgIt = stubMsgQueueMap.begin();
                }else{
                    // closest match: using "absolute value of type" with no more than bigger than that
                    for (msgIt = stubMsgQueueMap.begin(); msgIt != stubMsgQueueMap.end(); msgIt ++){
                        if (msgIt->first <= (-__msgtyp)){
                            break;
                        }
                    }
                }
            }
            // two cases, 
            //1. there is not even any message,
            //2. there is an empty queue for some type        
            if (msgIt != stubMsgQueueMap.end()){
                StubMsgQueue& stubMsgQueue = msgIt->second;
                if (stubMsgQueue.size() > 0){
                    StubMsgData& stubMsgData = stubMsgQueue.front();
                    stubMsgQueue.pop_front();
                    size_t size = stubMsgData.size();;
                    if (size > __msgsz){
                        size = __msgsz;
                    }
                    for (size_t i = 0; i < size; i ++){
                        *ptr = stubMsgData[i];
                    }
                    dummyMsgPtr->mtype = __msgtyp;
                    result = size;                
                }else{
                    // second case, there is such type of queue, but it is empty
                    if (__msgflg & IPC_NOWAIT){
                        errno = ENOMSG;
                    }else{
                        isQueueEmpty = true;
                    }
                }
            }  else{
                // first case, no such type of queue
                if (__msgflg & IPC_NOWAIT){
                    errno = ENOMSG;
                }else{
                    isQueueEmpty = true;
                }
            }
            if (isQueueEmpty){
                pthread_cond_wait(&stubMsgQCond, &stubMsgQMutex);
            }
                
        }
    }while (isQueueEmpty && !(__msgflg & IPC_NOWAIT) );
    pthread_mutex_unlock(&stubMsgQMutex);    
    return result;
    
}

/****************************************************************************************
 msgget actually create one msg "bucket" by inserting itself into "manager".
 ***** ***************************************************************************************/

int msgget (key_t __key, int __msgflg){
    pthread_mutex_lock(&stubMsgQMutex);
    int key = (int)__key;
    int result = -1;
    if (!(__msgflg & IPC_CREAT)){
        if (stubMsgQueueMgr.find(key) != stubMsgQueueMgr.end()){             
            result = key;
        }else{
            result = -1;
            // acting like "GOD"
            errno = ENOENT;
        }
    }else{
        if (stubMsgQueueMgr.find(key) != stubMsgQueueMgr.end()){            
            if (__msgflg & IPC_EXCL){
                // IPC requires if queue exists when both IPC_CREAT and IPC_EXCL are used, return error
                result = -1;
                errno = EEXIST;
            }else{
                result = key;
            }
        }else{
            StubMsgQueueMap stubMsgQueueMap;
            stubMsgQueueMgr.insert(make_pair(key, stubMsgQueueMap));
            result = key;
        }
    }   
    pthread_mutex_unlock(&stubMsgQMutex);
    return result;     
}

/****************************************************************************************
 Currently msgctl is the only stub we have because our STL container cannot support
 those file characteristic controlled by "msgctl".
 ***** ***************************************************************************************/

int msgctl (int __msqid, int __cmd, struct msqid_ds *__buf){
    pthread_mutex_lock(&stubMsgQMutex);
    if (__cmd == IPC_RMID){
        //remove that particular queue-id
        stubMsgQueueMgr.erase(__msqid);
        pthread_cond_broadcast(&stubMsgQCond);
    }    
    //always successful
    pthread_mutex_unlock(&stubMsgQMutex);
    return 0;
}

#endif // __UTHelper_H__
