#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include "../base.h"

void matrix_mult_4x4(const double *A, const double *B, double *T, int n)
{
	int i, j;

	/* T = A x B */
	T[0]	+= A[0]*B[0]	+ A[1]*B[n]			+ A[2]*B[2*n]		+ A[3]*B[3*n];
	T[1]	+= A[0]*B[1]	+ A[1]*B[n+1]		+ A[2]*B[2*n+1]		+ A[3]*B[3*n+1];
	T[2]	+= A[0]*B[2]	+ A[1]*B[n+2]		+ A[2]*B[2*n+2]		+ A[3]*B[3*n+2];
	T[3]	+= A[0]*B[3]	+ A[1]*B[n+3]		+ A[2]*B[2*n+3]		+ A[3]*B[3*n+3];
	T[n]	+= A[n]*B[0]	+ A[n+1]*B[n]		+ A[n+2]*B[2*n]		+ A[n+3]*B[3*n];
	T[n+1]	+= A[n]*B[1]	+ A[n+1]*B[n+1] 	+ A[n+2]*B[2*n+1]	+ A[n+3]*B[3*n+1];
	T[n+2]	+= A[n]*B[2]	+ A[n+1]*B[n+2] 	+ A[n+2]*B[2*n+2]	+ A[n+3]*B[3*n+2];
	T[n+3]	+= A[n]*B[3]	+ A[n+1]*B[n+3] 	+ A[n+2]*B[2*n+3]	+ A[n+3]*B[3*n+3];
	T[2*n]	+= A[2*n]*B[0]	+ A[2*n+1]*B[n] 	+ A[2*n+2]*B[2*n]	+ A[2*n+3]*B[3*n];
	T[2*n+1]+= A[2*n]*B[1]	+ A[2*n+1]*B[n+1]	+ A[2*n+2]*B[2*n+1]	+ A[2*n+3]*B[3*n+1];
	T[2*n+2]+= A[2*n]*B[2]	+ A[2*n+1]*B[n+2]	+ A[2*n+2]*B[2*n+2]	+ A[2*n+3]*B[3*n+2];
	T[2*n+3]+= A[2*n]*B[3]	+ A[2*n+1]*B[n+3]	+ A[2*n+2]*B[2*n+3]	+ A[2*n+3]*B[3*n+3];
	T[3*n]	+= A[3*n]*B[0]	+ A[3*n+1]*B[n] 	+ A[3*n+2]*B[2*n]	+ A[3*n+3]*B[3*n];
	T[3*n+1]+= A[3*n]*B[1]	+ A[3*n+1]*B[n+1]	+ A[3*n+2]*B[2*n+1]	+ A[3*n+3]*B[3*n+1];
	T[3*n+2]+= A[3*n]*B[2]	+ A[3*n+1]*B[n+2]	+ A[3*n+2]*B[2*n+2]	+ A[3*n+3]*B[3*n+2];
	T[3*n+3]+= A[3*n]*B[3]	+ A[3*n+1]*B[n+3]	+ A[3*n+2]*B[2*n+3]	+ A[3*n+3]*B[3*n+3];
}
void big_matrix_mult(const int N)
{
	int n = N/4; /* number of blocks */
	int i, j, k;
	double A[N*N], B[N*N], T[N*N];
	double *ma, *mb, *mt;

	/* Initialize, should be shifted to before */
	for (i=0; i<N*N; i++) {
		A[i] = (rand()/rand_max)*2 - 1;
		B[i] = (rand()/rand_max)*2 - 1;
		T[i] = 0.0f;
	}

	for (i=0; i<n; i++) {
		for (j=0; j<n; j++) {
			for (k=0; k<n; k++) {
				ma = &A[4*4*n*i+4*k];
				mb = &B[4*4*n*k+4*j];
				mt = &T[4*4*n*i+4*j];
				matrix_mult_4x4(ma, mb, mt, N);
			}
		}
	}
	for (i=0; i<25; i++)
		T[i] = T[i]*3;
	printf("%f\t", T[30]);
}


void get_battery_info()
{
	int timestamp, mVolt, mA_avg, mA, uAh, dC;
	int temp;
	FILE *fptr = fopen("/sys/kernel/debug/battery_log", "r");
	FILE *fptr2 = fopen("/data/local/tmp/abc", "a");

	/*
	 * The energy format like below:
	 *
	 * timestamp    mV     mA avg mA      uAh   dC   %   src  mode   reg full
	 *    106439  4138      0      0  1360000  303 100   usb  full  0x81 1
	 *
	 * */
	temp = fscanf(fptr, "%*[^\n]\n");
	while (1) {
		temp = fscanf(fptr, "%d%d%d%d%d%d%*[^\n]\n",
							&timestamp, &mVolt, &mA_avg, &mA, &uAh, &dC);
		if (temp == -1) {
			break;
		}
	}
	fprintf(fptr2, "%d\t%d\t%d\t%d\t%d\t%d\n", timestamp, mVolt, mA_avg, mA, uAh, dC);

	fclose(fptr);
}

/* Test energy consumption when computing on cpu
 * */
void test_on_cpu()
{
	int i;

	for (i=0; i<60; i++)
		big_matrix_mult(400);
}


/*
 * Receive data from cloud
 * */
void test_on_nic(char direction, int times)
{
	int sockfd;
	struct sockaddr_in serv_addr;
	int num_r, num_w;
	int errcode;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		system_error();
	}
	FILE *stream = fdopen(sockfd, "r+");

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_addr.s_addr = inet_addr("140.113.88.148");
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(23241);

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

	double buff[16];
	int i, j;
	fwrite(&direction, sizeof(char), 1, stream);
	fwrite(&times, sizeof(int), 1, stream);
	printf("d:%c t:%d\n", direction, times);
	if (direction == 't') {			/* tx */
		for (i=0; i<times; i++) {
			for (j=0; j<16; j++) {
				buff[j] = r();
			}
			num_w = fwrite(buff, sizeof(double), 16, stream);
		}
	}
	else if (direction == 'r') {	/* rx */
		for (i=0; i<times; i++) {
			num_r = fread(buff, sizeof(double), 16, stream);
		}
	}

	fclose(stream);
	close(sockfd);
}

/*
 * This function fetch the battery info when starting, then executing program
 *
 * */
int main(int argc, char **argv)
{
	srand(time(NULL));

	struct timespec start, stop;
	get_battery_info();

	clock_gettime(CLOCK_MONOTONIC, &start);

	if (argc == 3 && !strcmp(argv[1], "rx"))
		test_on_nic('r', atoi(argv[2]));
	else if (argc == 3 && !strcmp(argv[1], "tx"))
		test_on_nic('t', atoi(argv[2]));
	else if (argc == 2 && !strcmp(argv[1], "cpu"))
		test_on_cpu();

	clock_gettime(CLOCK_MONOTONIC, &stop);

	get_battery_info();

	double diff_time = calc_diff_time(&start, &stop);
	FILE *fptr2 = fopen("/data/local/tmp/abc", "a");
	fprintf(fptr2, "cost time: %f ns\n", diff_time);
	fclose(fptr2);

	return 0;
}
