#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <time.h>
#include "../base.h"

#define PORT 23240

#define rand_max (double)RAND_MAX
#define r() ((rand()/rand_max)*2-1)

int main()
{
	int sockfd, newsockfd;
	int num_r, num_w;
	int errcode;
	socklen_t clilen=0;
	struct sockaddr_in cli_addr, serv_addr;
	char buff[BUFF_SIZE];
	struct timespec start[3], stop[3];
	double diff_time;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		system_error();
	}

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(PORT);
	int optval = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

	errcode = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr));
	if (errcode == -1) {
		system_error();
	}

	if (listen(sockfd, 5) == -1) {
		system_error();
	}

	while (1) {
		newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);
		if (newsockfd == -1) {
			system_error();
		}
		FILE *stream = fdopen(newsockfd, "r+");

		
		fwrite("GRANT SERVICE", sizeof(char), 14, stream);

		/* Get number of matrices */
		int times;
		num_r = fread(&times, sizeof(int), 1, stream);
		
		double exec_time = 0.0;
		double m1[16], m2[16], result[16];
		/* Receive matrices */
		for (int i=0; i<times; i++) {
			num_r = fread(m1, sizeof(double), 16, stream);
			for (int j=1; j<1000; j++) {
				clock_gettime(CLOCK_MONOTONIC, &start[0]);
				for (int k=0; k<16; k++)
					m2[k] = r();
//				num_r = fread(m2, sizeof(double), 16, stream);
				/* result = m1 x m2 */
				result[0]   = m1[0]*m2[0]   + m1[1]*m2[4]   + m1[2]*m2[8]   + m1[3]*m2[12];
				result[1]   = m1[0]*m2[1]   + m1[1]*m2[5]   + m1[2]*m2[9]   + m1[3]*m2[13];
				result[2]   = m1[0]*m2[2]   + m1[1]*m2[6]   + m1[2]*m2[10]  + m1[3]*m2[14];
				result[3]   = m1[0]*m2[3]   + m1[1]*m2[7]   + m1[2]*m2[11]  + m1[3]*m2[15];
				result[4]   = m1[4]*m2[0]   + m1[5]*m2[4]   + m1[6]*m2[8]   + m1[7]*m2[12];
				result[5]   = m1[4]*m2[1]   + m1[5]*m2[5]   + m1[6]*m2[9]   + m1[7]*m2[13];
				result[6]   = m1[4]*m2[2]   + m1[5]*m2[6]   + m1[6]*m2[10]  + m1[7]*m2[14];
				result[7]   = m1[4]*m2[3]   + m1[5]*m2[7]   + m1[6]*m2[11]  + m1[7]*m2[15];
				result[8]   = m1[8]*m2[0]   + m1[9]*m2[4]   + m1[10]*m2[8]  + m1[11]*m2[12];
				result[9]   = m1[8]*m2[1]   + m1[9]*m2[5]   + m1[10]*m2[9]  + m1[11]*m2[13];
				result[10]  = m1[8]*m2[2]   + m1[9]*m2[6]   + m1[10]*m2[10] + m1[11]*m2[14];
				result[11]  = m1[8]*m2[3]   + m1[9]*m2[7]   + m1[10]*m2[11] + m1[11]*m2[15];
				result[12]  = m1[12]*m2[0]  + m1[13]*m2[4]  + m1[14]*m2[8]  + m1[15]*m2[12];
				result[13]  = m1[12]*m2[1]  + m1[13]*m2[5]  + m1[14]*m2[9]  + m1[15]*m2[13];
				result[14]  = m1[12]*m2[2]  + m1[13]*m2[6]  + m1[14]*m2[10] + m1[15]*m2[14];
				result[15]  = m1[12]*m2[3]  + m1[13]*m2[7]  + m1[14]*m2[11] + m1[15]*m2[15];

				/* Copy result to m1 */
				memcpy(m1, result, sizeof(double)*16);
				clock_gettime(CLOCK_MONOTONIC, &stop[0]);
				exec_time += calc_diff_time(&start[0], &stop[0]);
			}
		}
//		diff_time = calc_diff_time(&start[0], &stop[0]);

		exec_time = exec_time/1000; /* us */
		fwrite(&exec_time, sizeof(double), 1, stream);
		printf("%f\n", exec_time);

		fwrite(result, sizeof(double), 16, stream);
//		for (int i=0; i<16; i++)
//			printf("%d: %f\n", i, result[i]);

		fclose(stream);
		close(newsockfd);

	}
	close(sockfd);

	return 0;
}

