#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <time.h>

#define rdtscll(val) \
     __asm__ __volatile__("rdtsc" : "=A" (val))

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

typedef short int s16;
typedef int s32;

#if 0
#define CONFIG_SMP
#endif

#ifdef CONFIG_SMP
#define LOCK_PREFIX "lock ; "
#else
#define LOCK_PREFIX ""
#endif

struct __xchg_dummy { unsigned long a[100]; };
#define __xg(x) ((struct __xchg_dummy *)(x))

static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old,
				      unsigned long new, int size)
{
	unsigned long prev;
	switch (size) {
	case 1:
		__asm__ __volatile__(LOCK_PREFIX "cmpxchgb %b1,%2"
				     : "=a"(prev)
				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
				     : "memory");
		return prev;
	case 2:
		__asm__ __volatile__(LOCK_PREFIX "cmpxchgw %w1,%2"
				     : "=a"(prev)
				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
				     : "memory");
		return prev;
	case 4:
		__asm__ __volatile__(LOCK_PREFIX "cmpxchgl %1,%2"
				     : "=a"(prev)
				     : "q"(new), "m"(*__xg(ptr)), "0"(old)
				     : "memory");
		return prev;
	}
	return old;
}

#define cmpxchg(ptr,o,n)\
	((__typeof__(*(ptr)))__cmpxchg((ptr),(unsigned long)(o),\
				       (unsigned long)(n),sizeof(*(ptr))))

static inline void atomic_add(volatile int *dst, int v)
{
	__asm__ __volatile__(
		LOCK_PREFIX "addl %1,%0"
		:"=m" (*dst)
		:"ir" (v), "m" (*dst));
}

static double detect_cpu_clock()
{
	struct timespec tm_begin, tm_end;
	unsigned long long tsc_begin, tsc_end;

	/* Warm cache */
	clock_gettime(CLOCK_MONOTONIC, &tm_begin);

	rdtscll(tsc_begin);
	clock_gettime(CLOCK_MONOTONIC, &tm_begin);

	usleep(1000000);

	rdtscll(tsc_end);
	clock_gettime(CLOCK_MONOTONIC, &tm_end);

	return (tsc_end - tsc_begin) / (tm_end.tv_sec - tm_begin.tv_sec + (tm_end.tv_nsec - tm_begin.tv_nsec) / 1e9);
}

void mix_areas_srv(unsigned int size,
		   const s16 *src,
		   volatile s32 *sum,
		   unsigned int src_step, unsigned int sum_step)
{
	src_step /= sizeof(*src);
	sum_step /= sizeof(*sum);
        while (size-- > 0) {
                atomic_add(sum, *src);
                src += src_step;
                sum += sum_step;
        }
}

void saturate(unsigned int size,
              s16 *dst, const s32 *sum,
              unsigned int dst_step, unsigned int sum_step)
{
	dst_step /= sizeof(*dst);
	sum_step /= sizeof(*sum);
        while (size-- > 0) {
                s32 sample = *sum;
                if (unlikely(sample < -0x8000))
                        *dst = -0x8000;
                else if (unlikely(sample > 0x7fff))
                        *dst = 0x7fff;
                else
                        *dst = sample;
                dst += dst_step;
                sum += sum_step;
        }
}

void mix_areas0(unsigned int size,
		volatile s16 *dst, s16 *src,
		volatile s32 *sum,
		unsigned int dst_step,
		unsigned int src_step,
		unsigned int sum_step)
{
	dst_step /= sizeof(*dst);
	src_step /= sizeof(*src);
	sum_step /= sizeof(*sum);
	while (size-- > 0) {
		s32 sample = *dst + *src;
		if (unlikely(sample < -0x8000))
			*dst = -0x8000;
		else if (unlikely(sample > 0x7fff))
			*dst = 0x7fff;
		else
			*dst = sample;
		dst += dst_step;
		src += src_step;
		sum += sum_step;
	}
}

#define MIX_AREAS_16 mix_areas1
#define MIX_AREAS_16_MMX mix_areas1_mmx
#define MIX_AREAS_32 mix_areas1_32
#define MIX_AREAS_24 mix_areas1_24
#define MIX_AREAS_24_CMOV mix_areas1_24_cmov
#define XADD "addl"
#define XSUB "subl"
#include "../src/pcm/pcm_dmix_i386.h"
static void *ptr_mix_areas1_32 __attribute__((unused)) = &mix_areas1_32;
static void *ptr_mix_areas1_24 __attribute__((unused)) = &mix_areas1_24;
static void *ptr_mix_areas1_24_cmov __attribute__((unused)) = &mix_areas1_24_cmov;

void mix_areas2(unsigned int size,
		volatile s16 *dst, const s16 *src,
		volatile s32 *sum,
		unsigned int dst_step,
		unsigned int src_step)
{
	dst_step /= sizeof(*dst);
	src_step /= sizeof(*src);
	while (size-- > 0) {
		s32 sample = *src;
		s32 old_sample = *sum;
		if (cmpxchg(dst, 0, 1) == 0)
			sample -= old_sample;
		atomic_add(sum, sample);
		do {
			sample = *sum;
			if (unlikely(sample < -0x8000))
				*dst = -0x8000;
			else if (unlikely(sample > 0x7fff))
				*dst = 0x7fff;
			else
				*dst = sample;
		} while (unlikely(sample != *sum));
		sum++;
		dst += dst_step;
		src += src_step;
	}
}

void setscheduler(void)
{
	struct sched_param sched_param;

	if (sched_getparam(0, &sched_param) < 0) {
		printf("Scheduler getparam failed...\n");
		return;
	}
	sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
	if (!sched_setscheduler(0, SCHED_RR, &sched_param)) {
		printf("Scheduler set to Round Robin with priority %i...\n", sched_param.sched_priority);
		fflush(stdout);
		return;
	}
	printf("!!!Scheduler set to Round Robin with priority %i FAILED!!!\n", sched_param.sched_priority);
}

int cache_size = 1024*1024;

void init(s16 *dst, s32 *sum, int size)
{
	int count;
	char *a;
	
	for (count = size - 1; count >= 0; count--)
		*sum++ = 0;
	for (count = size - 1; count >= 0; count--)
		*dst++ = 0;
	a = malloc(cache_size);
	for (count = cache_size - 1; count >= 0; count--) {
		a[count] = count & 0xff;
		a[count] ^= 0x55;
		a[count] ^= 0xaa;
	}
	free(a);
}

int main(int argc, char **argv)
{
	int size = 2048, n = 4, max = 32267;
	int LOOP = 100;
	int i, t;
	unsigned long long begin, end, diff, diffS, diff0, diff1, diff1_mmx, diff2;
        double cpu_clock = detect_cpu_clock();
	s16 *dst = malloc(sizeof(*dst) * size);
	s32 *sum = calloc(size, sizeof(*sum));
	s16 **srcs = malloc(sizeof(*srcs) * n);

	setscheduler();
#ifndef CONFIG_SMP
        printf("CPU clock: %fMhz (UP)\n\n", cpu_clock / 10e5);
#else
        printf("CPU clock: %fMhz (SMP)\n\n", cpu_clock / 10e5);
#endif
	if (argc > 3) {
		size = atoi(argv[1]);
		n = atoi(argv[2]);
		max = atoi(argv[3]);
	}
	if (argc > 4)
		cache_size = atoi(argv[4]) * 1024;
	for (i = 0; i < n; i++) {
		int k;
		s16 *s;
		srcs[i] = s = malloc(sizeof(s16) * size);
		for (k = 0; k < size; ++k, ++s) {
			*s = (rand() % (max * 2)) - max;
		}
	}

	for (t = 0, diffS = -1; t < LOOP; t++) {
		init(dst, sum, size);
		rdtscll(begin);
		for (i = 0; i < n; i++) {
			mix_areas_srv(size, srcs[i], sum, 2, 4);
		}
		saturate(size, dst, sum, 2, 4);
		rdtscll(end);
		diff = end - begin;
		if (diff < diffS)
			diffS = diff;
		printf("mix_areas_srv : %llu               \r", diff); fflush(stdout);
	}

	for (t = 0, diff0 = -1; t < LOOP; t++) {
		init(dst, sum, size);
		rdtscll(begin);
		for (i = 0; i < n; i++) {
			mix_areas0(size, dst, srcs[i], sum, 2, 2, 4);
		}
		rdtscll(end);
		diff = end - begin;
		if (diff < diff0)
			diff0 = diff;
		printf("mix_areas0    : %llu               \r", diff); fflush(stdout);
	}

	for (t = 0, diff1 = -1; t < LOOP; t++) {
		init(dst, sum, size);
		rdtscll(begin);
		for (i = 0; i < n; i++) {
			mix_areas1(size, dst, srcs[i], sum, 2, 2, 4);
		}
		rdtscll(end);
		diff = end - begin;
		if (diff < diff1)
			diff1 = diff;
		printf("mix_areas1    : %llu              \r", diff); fflush(stdout);
	}

	for (t = 0, diff1_mmx = -1; t < LOOP; t++) {
		init(dst, sum, size);
		rdtscll(begin);
		for (i = 0; i < n; i++) {
			mix_areas1_mmx(size, dst, srcs[i], sum, 2, 2, 4);
		}
		rdtscll(end);
		diff = end - begin;
		if (diff < diff1_mmx)
			diff1_mmx = diff;
		printf("mix_areas1_mmx: %llu              \r", diff); fflush(stdout);
	}

	for (t = 0, diff2 = -1; t < LOOP; t++) {
		init(dst, sum, size);
		rdtscll(begin);
		for (i = 0; i < n; i++) {
			mix_areas2(size, dst, srcs[i], sum, 2, 2);
		}
		rdtscll(end);
		diff = end - begin;
		if (diff < diff2)
			diff2 = diff;
		printf("mix_areas2    : %llu              \r", diff); fflush(stdout);
	}

	printf("                                                                           \r");
	printf("Summary (the best times):\n");
	printf("mix_areas_srv  : %8llu %f%%\n", diffS, 100*2*44100.0*diffS/(size*n*cpu_clock));
	printf("mix_areas0     : %8llu %f%%\n", diff0, 100*2*44100.0*diff0/(size*n*cpu_clock));
	printf("mix_areas1     : %8llu %f%%\n", diff1, 100*2*44100.0*diff1/(size*n*cpu_clock));
	printf("mix_areas1_mmx : %8llu %f%%\n", diff1_mmx, 100*2*44100.0*diff1_mmx/(size*n*cpu_clock));
	printf("mix_areas2     : %8llu %f%%\n", diff2, 100*2*44100.0*diff2/(size*n*cpu_clock));

	printf("\n");
	printf("areas1/srv ratio     : %f\n", (double)diff1 / diffS);
	printf("areas1_mmx/srv ratio : %f\n", (double)diff1_mmx / diffS);

	return 0;
}
