/**---------------------------------------------------------------------------
* 版权 2004 啄木鸟基金会
* 保留一切权利。
* 
* 重新分发或者使用源代码格式和二进制格式，无论是否修改，必须在符合
* 以下许可条件的情况下进行：
* 1. 重新分发的源代码必须保留上述版权信息、本许可条件列表以及下列
*    否认声明。
* 2. 重新分发的二进制格式程序必须再次产生上述版权信息，本许可条件列
*    表以及在本分发中的文档或者其他材料中提供的否认声明。
*
* 本软件是由作者和参与者“照此”提供，并且任何显式或隐式的保证，只
* 包含在内但不被其限制，否认任何商业性和特殊目的适切性的隐式担保
* 之效力。本软件的作者或参与者不对由于本软件造成的任何直接地、
* 间接地、连带地、特殊地、惩戒性地，或者由此而产生地损害（还包括
* 以下的不被其限制的内容在内：替代品或服务地取得；使用过程、数据、
* 或由此产生的收益之损失；或商业行为的中断）发生在即使被告知可能
* 出现上述损害后，仍设法摆脱使用本软件的过程中，无论其起因和任何
* 责任地推测，其是否属于合同范畴、限定的义务、或者民事侵权行为
*（包括疏忽及其他）都否认其责任。
* 
* 
*
* Copyright (c) 2004 The Woodpecker Foundation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
**---------------------------------------------------------------------------
*/

#include <sys/time.h>
#include <fcntl.h>
#include "shmqueue.h"
//#define DEBUG

//char g_hello[1024 * 1024];
int _shmqueue_empty(shmqueue *shmq);
char *get_region_info(shmregion *shmr, char *info);
char *get_shmq_info(void *baseaddr, char *info);
int  create_file(char *fn);
int  remove_file(char *fn);
char *gen_uname(char *buff);

typedef struct namecache_t namecache;
struct namecache_t{
    char shmqname[64];
    void *shmaddr;
};

int g_cache_num = 0;
namecache g_caches[128];

void *shmqcache_find(char *shmqname) 
{
    int i;    
    for (i = 0; i < g_cache_num; i++){
        if (strcmp(shmqname, g_caches[i].shmqname) == 0) { 
            return g_caches[i].shmaddr;
        }
    }
    return NULL;
}
void shmqcache_add(char *shmqname, void *shmaddr)
{
    strcpy(g_caches[g_cache_num].shmqname, shmqname);
    g_caches[g_cache_num].shmaddr = shmaddr;
    g_cache_num ++;
}

/**
 * purpose : create it if not exist or fetch it, return the shared memory base address
 * param   : name - the pool Name (should be a path name)
 * param   : size - the max number item to be put in;
 * param   : timeout - remove a region when it live over timeout. (unit: sec.)
 * return  : the base address point to the share memory, or NULL if fail.
 */
void *shmqueue_init(char *pn, int size, int timeout)
{
    int len;
    void *shmaddr;
    shmqueue shmq;
    int isexist = 1;
    char info[128], fn[128];

    sprintf(fn, "%s%s", POOL_PREFIX, pn);

    if ( NULL == (shmaddr = shmqcache_find(fn)) ){
        if (shmgetid(fn) <= 0){
            isexist = 0;
            create_file(fn);
        }

        if ( NULL == (shmaddr = shminit(fn, sizeof(shmqueue) + sizeof(shmregion) * size)) ){
            printf("shminit error!\n");
            return NULL;
        }
        shmqcache_add(fn, shmaddr);
    }
    

    if (isexist == 0){
        strcpy(shmq.name, fn);
        strcpy(shmq.version, "1.0");
        shmq.offset = sizeof(shmqueue);
        shmq.maxsize = size;
        shmq.size = 0;
        shmq.unit_size = sizeof(shmregion);
        shmq.timeout = timeout;
        shmq.head = -1;
        shmq.tail = -1;
        memcpy(shmaddr, &shmq, sizeof(shmqueue));
    }
    return shmaddr;
}

int shmqueue_removeall(void *baseaddr)
{
    int   offset;
    void *shmaddr;
    shmqueue *shmq;
    shmregion *shmr;

    if (NULL == baseaddr)
        return -1;
    shmq = (shmqueue *)baseaddr;

    splock();    
    while ( !((shmq->head == -1) && (shmq->tail == -1)) ){
        offset = shmq->offset + (shmq->unit_size * shmq->head);
        shmr = (shmregion *)(shmaddr + offset);
        shmctl(shmr->id, IPC_RMID, 0);
        remove_file(shmr->name);

        shmq->head = (shmq->head + 1) >= shmq->maxsize ? 0 : shmq->head + 1;
        if (shmq->head == shmq->tail){
            // after fetch this item, the queue is empty
            _shmqueue_empty(shmq);
        }
    }
    spunlock();    
    return 0;
}

int shmqueue_destroy(char *pn)
{
    char fn[128];

    sprintf(fn, "%s%s", POOL_PREFIX, pn);
    shmrm(fn);
    remove_file(fn);
}
/*
 * return :  >0    push message's len
 *           -1    exception 
 *           -2    the queue is full
 */
int shmqueue_push(void *baseaddr, void *ptrval, int size, unsigned long sender, unsigned long receiver)
{

    char buff[128], temp_name[128];
    int offset, tail, temp_id, temp_size;
    shmqueue  *shmq;
    shmregion *shmr;
    void      *shmraddr;

    //TODO should add semophore

    if (NULL == baseaddr)
        return -1;
    shmq = (shmqueue *)baseaddr;

    splock();
    // get a empty region
    if ((shmq->head == -1) && (shmq->tail == -1)){
    	//the first item!
        shmq->head = 0;
        shmq->tail = 0;
        shmq->size = 1;
        offset = shmq->offset;
    }
    else {
        tail = shmq->tail + 1 >= shmq->maxsize? 0: shmq->tail + 1;

        // the queue is full?
        if (tail == shmq->head){
            spunlock();
	    return -2;
        }
        
        offset = shmq->offset + (shmq->unit_size * tail);
        shmq->size ++ ;
        shmq->tail = tail;
    }

    // register shared region in the shared queue and created the shared region
    shmr = (shmregion *)(baseaddr + offset);

    strcpy(shmr->name, gen_uname(buff));
    create_file(shmr->name);
    shmr->id = shmget(ftok(shmr->name, 0), size, SVSHM_MODE|IPC_CREAT);
    if (shmr->id < 0)
        return -1;
    shmr->sender = sender;
    shmr->receiver = receiver;
    shmr->size = size;
    shmr->durability = shmq->timeout;

    temp_id = shmr->id;
//    spunlock();
    // copy Object to shared region
    shmraddr = shmat(temp_id, NULL, 0);
    memcpy(shmraddr, ptrval, size);
    shmdt(shmraddr);

#ifdef DEBUG
    printf("[%d] push  %s [%d]\n", getpid(), get_region_info(shmr, buff), shmq->size);
#endif
    spunlock();
    return size;
}


// succ :the message's len
// fail < 0
int shmqueue_pull(void *baseaddr, void **ptrval, unsigned long *sender)
{
    int  offset, temp_id, temp_size;
    unsigned long temp_sender;
    char info[128], temp_name[128];
    void *shmaddr;
    shmqueue *shmq;
    shmregion *shmr;


    //TODO should add semophore

    *ptrval = NULL;
    if (NULL == baseaddr)
        return -1;
    shmq = (shmqueue *)baseaddr;
  
    splock(); 
    // the queue is empty?
    if ((shmq->head == -1) && (shmq->tail == -1)){
        spunlock();
        return -2;  
    }

    // get shared region
    offset = shmq->offset + (shmq->unit_size * shmq->head);
    shmr = (shmregion *)(baseaddr + offset);
    
    shmq->size --;

    // after fetch this item, the queue is empty?
    if (shmq->head == shmq->tail)
        _shmqueue_empty(shmq);
    else
        shmq->head = (shmq->head + 1) >= shmq->maxsize ? 0 : shmq->head + 1;

#ifdef DEBUG
    printf("[%d] pull %s. [%d]\n", getpid(), get_region_info(shmr, info), shmq->size);
#endif

    temp_sender = shmr->sender;
    temp_id = shmr->id;
    temp_size = shmr->size;
    strcpy(temp_name, shmr->name);
    spunlock();

    //here is a long time operation, so should be put out of lock block
    shmaddr = shmat(temp_id, NULL, 0);
    *ptrval = (void *)malloc(temp_size);
    memcpy(*ptrval, shmaddr, temp_size);
    shmdt(shmaddr);
    shmctl(temp_id, IPC_RMID, NULL);
    remove_file(temp_name);

    *sender = temp_sender;
    return temp_size;
}

// succ : the message's len
// fail < 0
int shmqueue_popup(void *baseaddr, void **ptrval, unsigned long *sender)
{
    int  offset, temp_id, temp_size;
    unsigned long temp_sender;
    char info[128], temp_name[128];
    void *shmaddr;
    shmqueue *shmq;
    shmregion *shmr;

    //TODO should add semophore

    *ptrval = NULL;
    if (NULL == baseaddr)
        return -1;
    shmq = (shmqueue *)baseaddr;
  
    splock(); 
    // the queue is empty?
    if ((shmq->head == -1) && (shmq->tail == -1)){
        spunlock();
        return -2;  //never put a item into queue, 
    }

    // get shared region
    offset = shmq->offset + (shmq->unit_size * shmq->tail);
    shmr = (shmregion *)(baseaddr + offset);

    shmq->size --;

    // after fetch this item, the queue is empty?
    if (shmq->head == shmq->tail)
        _shmqueue_empty(shmq);
    else
        shmq->tail = ((shmq->tail - 1) < 0 ) ? (shmq->maxsize - 1) : (shmq->tail - 1);

#ifdef DEBUG
    printf("[%d] popup %s.[%d]\n", getpid(), get_region_info(shmr, info), shmq->size);
#endif

    temp_sender = shmr->sender;
    temp_id = shmr->id;
    temp_size = shmr->size;
    strcpy(temp_name, shmr->name);
    spunlock();

    //here is a long time operation, so should be put out of lock block
    shmaddr = shmat(temp_id, NULL, 0);
    *ptrval = (void *)malloc(temp_size);
    memcpy(*ptrval, shmaddr, temp_size);
    shmdt(shmaddr);
    shmctl(temp_id, IPC_RMID, NULL);
    remove_file(temp_name);

    *sender = temp_sender;
    return temp_size;
}

#define CMP_BY_RECEIVER 1
#define CMP_BY_SHMID    2

int compare_something(shmregion *shmr, int compare_flag, unsigned long someid)
{
    if (compare_flag == CMP_BY_RECEIVER)
        return (shmr->receiver == someid);
    else if (compare_flag == CMP_BY_SHMID)
        return (shmr->id == someid);
}

// succ : the region's creator > 0
// fail < 0
int shmqueue_fetch(void *baseaddr, void **ptrval, unsigned long *sender,
    unsigned long someid, int remove_flag, int compare_flag)
{
    int  offset, index, temp_id, temp_size;
    unsigned long temp_sender;
    char buff[128], temp_name[128];
    void *shmraddr;
    void *toaddr;
    void *fromaddr;
    shmqueue *shmq;
    shmregion *shmr;


    *ptrval = NULL;
    if (NULL == baseaddr)
        return -1;
    shmq = (shmqueue *)baseaddr;

    splock();
    //the queue empty?
    if ( (shmq->head == -1) && (shmq->tail == -1) ){
        spunlock();
        return -2;
    }

    index = shmq->head;
    for(;;){
        offset = shmq->offset + (shmq->unit_size * index);
        shmr = (shmregion *)(baseaddr + offset);

        // match the condition?
        if (compare_something(shmr, compare_flag, someid)) {
            // save to temp var, use this var to relase shared region 
            temp_sender = shmr->sender;
            temp_id = shmr->id;
            temp_size = shmr->size;
            strcpy(temp_name, shmr->name);

            // if not removable
            if (remove_flag == ROO_FETCH_NOT_REMOVE){
                spunlock();
                *sender = temp_sender;
                return temp_size;
            }

            if (shmq->head == shmq->tail){
                _shmqueue_empty(shmq);
            }
            else {
                // move the last region to current position
                if (index != shmq->tail){
                //copy from tail
                    fromaddr = baseaddr +  shmq->offset + (shmq->unit_size * shmq->tail); 
                    toaddr = baseaddr +  shmq->offset + (shmq->unit_size * index); 
                    memcpy(toaddr,  fromaddr, sizeof(shmregion));
                }
                shmq->tail = ((shmq->tail - 1) < 0) ? shmq->maxsize - 1 : shmq->tail - 1;
                shmq->size--;
            }
#ifdef DEBUG
            printf("[%d] fetch %s [%d]\n", getpid(), get_region_info(shmr, buff), shmq->size);
#endif
            spunlock();

            // copy the shared memory
            shmraddr = shmat(temp_id, NULL, 0);
            *ptrval = (void *)malloc(temp_size);
            memcpy(*ptrval, shmraddr, temp_size);
  
            // release the share memory
            shmdt(shmraddr);
            shmctl(temp_id, IPC_RMID, NULL);
            remove_file(temp_name);
            //spunlock();

            *sender = temp_sender;
            return temp_size;
        }

        // finish seach?
        if (index == shmq->tail){
            break;
        }

        // move to next
        index = ((index + 1) >= shmq->maxsize) ? 0 : index + 1;
    }
    spunlock();
    // not found
    return -3;
}


/**
 * garbage collection : decrement shared region's durability, when durability < 0, delete it
 * 
 */
int shmqueue_gc(void *baseaddr)
{
    int  offset, index, temp_id, temp_size;
    unsigned long temp_sender;
    char buff[128], temp_name[128];
    void *shmraddr;
    void *toaddr;
    void *fromaddr;
    shmqueue *shmq;
    shmregion *shmr;


    if (NULL == baseaddr)
        return -1;
    shmq = (shmqueue *)baseaddr;

    splock();
    //the queue empty?
    if ( (shmq->head == -1) && (shmq->tail == -1) ){
        spunlock();
        return -2;
    }

    index = shmq->head;
    for(;;){
        offset = shmq->offset + (shmq->unit_size * index);
        shmr = (shmregion *)(baseaddr + offset);
         
        shmr->durability --;
        if (shmr->durability < 0){

            // remove the shared region
            shmctl(shmr->id, IPC_RMID, NULL);
            remove_file(shmr->name);

            printf("delete share region:%s\n", shmr->name);

            if (shmq->head == shmq->tail){
                _shmqueue_empty(shmq);
                break;
            }
            else {
                // move the last region to current position
                if (index != shmq->tail){
                    //copy from tail
                    fromaddr = baseaddr +  shmq->offset + (shmq->unit_size * shmq->tail); 
                    toaddr = baseaddr +  shmq->offset + (shmq->unit_size * index); 
                    memcpy(toaddr,  fromaddr, sizeof(shmregion));
                }
                shmq->tail = ((shmq->tail - 1) < 0) ? shmq->maxsize - 1 : shmq->tail - 1;
                shmq->size--;
            }
        }

        // finish seach?
        if (index == shmq->tail){
            break;
        }

        // move to next
        index = ((index + 1) >= shmq->maxsize) ? 0 : index + 1;
    }
    spunlock();
}

void shmqueue_gc_all()
{
    int i;

    for (i = 0; i < g_cache_num; i++){
        shmqueue_gc(g_caches[i].shmaddr);
    }
}

char *get_shmq_info(void *baseaddr, char *info)
{
    shmqueue *shmq = (shmqueue *)baseaddr;

#ifdef DEBUG
    sprintf(info, "name=%s, version=%s, offset=%d, maxsize=%d, size=%d, " 
        "unit_size=%d, timeout=%d, head=%d, tail=%d",
        shmq->name, shmq->version, shmq->offset, shmq->maxsize, shmq->size, 
        shmq->unit_size, shmq->timeout, shmq->head, shmq->tail);
#else
    sprintf(info, "name=%-30s, maxsize=%-4d, size=%-4d, timeout=%-4d, head=%-4d, tail=%-4d",
        shmq->name, shmq->maxsize, shmq->size, shmq->timeout, shmq->head, shmq->tail);
#endif
    return info;
}
char *get_allshmq_info(char *buff)
{
    int i;
    char s[256];

    strcpy(buff, "");
    for (i = 0; i < g_cache_num; i++){
        strcat(buff, get_shmq_info(g_caches[i].shmaddr, s));
        strcat(buff, "\n");
    }
    return buff;
}

char *get_region_info(shmregion *shmr, char *info)
{
    sprintf(info, "region(id=%d,name=%s,sender=%d,receiver=%d,size=%d,dura=%d)", 
        shmr->id, shmr->name, shmr->sender, shmr->receiver, shmr->size, shmr->durability);
    return info;
}

int _shmqueue_empty(shmqueue *shmq)
{
    shmq->head = -1;
    shmq->tail = -1;
    shmq->size = 0;
}
int  create_file(char *fn)
{
    int fd;
    fd = open(fn, O_CREAT);
    close(fd);
}
int  remove_file(char *fn)
{
    unlink(fn);
}
char *gen_uname(char *buff)
{
    struct timeval today;

    gettimeofday(&today, NULL); 
    sprintf(buff, "%s_%d_%d_%d", REGION_PREFIX, getpid(), today.tv_sec, today.tv_usec);
    return buff;
}
