#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/time.h>
#include <pthread.h>

#define SHM_NAME "shm_1"
#define MTX_NAME "mtx"

double getDeltaTime(struct timeval *begin, struct timeval *end)
{
    return (end->tv_sec + end->tv_usec * 1.0 / 1000000) -
           (begin->tv_sec + begin->tv_usec * 1.0 / 1000000);
}

int main(int argc, char const *argv[])
{
    int shm_id, sh_mtx, count, i, size, res;
    struct timeval begin, end;
    unsigned *addr;

    if (argc != 3)
    {
        printf("usage: ./shm_mtx <size> <count>\n");
        return EXIT_FAILURE;
    }

    size = atoi(argv[1]);
    count = atoi(argv[2]);
    unsigned char buf[size];

	pthread_mutex_t *p_mutex = (void*) 0;
	pthread_mutex_t mutex;
	pthread_mutexattr_t mtx_attr;

	sh_mtx = shm_open(MTX_NAME, O_RDWR | O_CREAT, 0x777);
	if (shm_id == -1)
	{
		perror("super_parent shm_open sh_mtx");
		return -1;
	}
	res = ftruncate(sh_mtx, sizeof(pthread_mutex_t));
	if (res == -1)
	{
		perror("super_parent ftruncate sh_mtx");
		return -1;
	}
	addr = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE,
	MAP_SHARED, sh_mtx, 0);
	if (addr == MAP_FAILED)
	{
		perror("super_parent mmap sh_mtx");
		return -1;
	}
	p_mutex = (pthread_mutex_t*) addr;
	res = pthread_mutexattr_init(&mtx_attr);
	if (res != EOK)
	{
		perror("super_parent pthread_mutexattr_init");
		return -1;
	}
	res = pthread_mutexattr_setpshared(&mtx_attr, PTHREAD_PROCESS_SHARED);
	if (res != EOK)
	{
		perror("super_parent pthread_mutexattr_setpshared");
		return -1;
	}
	//pthread_mutexattr_setrobust(&mtx_attr, PTHREAD_MUTEX_ROBUST);
	//pthread_mutexattr_setprotocol(&mtx_attr, PTHREAD_PRIO_NONE);
	res = pthread_mutex_init(p_mutex, &mtx_attr);
	//memcpy(p_mutex, &mutex, sizeof(pthread_mutex_t));
	if (res != EOK)
	{
		perror("super_parent pthread_mutex_init");
		return -1;
	}

	close(sh_mtx);

	shm_id = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0x777);
	if (shm_id == -1)
	{
		perror("shm_open shm_id");
		return -1;
	}
	res = ftruncate(shm_id, (off_t) size);
	if (shm_id == -1)
	{
		perror("ftruncate shm_id");
		return -1;
	}
	addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0);
	if (addr == MAP_FAILED)
	{
		perror("mmap shm_id");
		return -1;
	}
	close(shm_id);


    int pid = fork();
    if (pid == -1)
    {
        perror("fork");
        return -1;
    }
    else if (pid != 0) // parent
    {
		sh_mtx = shm_open(MTX_NAME, O_RDWR | O_EXCL, 0x777);
		if (sh_mtx == -1)
		{
			perror("parent: shm_open sh_mtx");
			return -1;
		}
		p_mutex = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE,
				MAP_SHARED, sh_mtx, 0);
		if (p_mutex == MAP_FAILED)
		{
			perror("parent: mmap sh_mtx");
			return -1;
		}

		shm_id = shm_open(SHM_NAME, O_RDWR | O_EXCL, 0x777);
		if (shm_id == -1)
		{
			perror("parent: shm_open shm_id");
			return -1;
		}
		addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0);
		if (addr == MAP_FAILED)
		{
			perror("parent: mmap shm_id");
			return -1;
		}

        for (i = 0; i < count; i++)
        {
        	pthread_mutex_lock(p_mutex);
        	memcpy(buf, addr, size);
			pthread_mutex_unlock(p_mutex);
		}

        sleep(1);
		shm_unlink(SHM_NAME);

		pthread_mutexattr_destroy(&mtx_attr);
		pthread_mutex_destroy(p_mutex);
		shm_unlink(MTX_NAME);
    }
    else // child
    {
        sh_mtx = shm_open(MTX_NAME, O_RDWR | O_EXCL, 0x777);
		if (sh_mtx == -1)
		{
			perror("child: shm_open sh_mtx");
			return -1;
		}
		p_mutex = (pthread_mutex_t*)mmap(NULL, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE, MAP_SHARED, sh_mtx, 0);
		if (p_mutex == MAP_FAILED)
		{
			perror("child: mmap sh_mtx");
			return -1;
		}

		shm_id = shm_open(SHM_NAME, O_RDWR | O_EXCL, 0x777);
		if (shm_id == -1)
		{
			perror("child: shm_open shm_id");
			return -1;
		}
		addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0);
		if (addr == MAP_FAILED)
		{
			perror("child: mmap shm_id");
			return -1;
		}

        gettimeofday(&begin, NULL);

        for (i = 0; i < count; i++)
        {
        	pthread_mutex_lock(p_mutex);
        	memcpy(addr, buf, size);
			pthread_mutex_unlock(p_mutex);
        }
		gettimeofday(&end, NULL);

		double tm = getDeltaTime(&begin, &end);
		printf("%.3fMB/s %.0fmsg/s\n", count * size * 1.0 / (tm * 1024 * 1024),
				count * 1.0 / tm);
        close(sh_mtx);
        close(shm_id);
    }

    return 0;
}
