#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <limits.h>
#include <assert.h>

#include <rk/rk.h>
#include <rk/rk_error.h>


//#define DEBUG

void
print_error(char *emsg){
	fprintf(stderr, "%s", emsg);
	exit(-1);
}

void
print_usage(char *pname)
{
	printf("Usage: %s [options] [--exec 'progname arguments']\n", pname);
	printf("  The options are:                   [ ] => default value\n");
	printf("\t--rset n\t   specifies existing resource set in hex.\n");
	printf("\t--newrset\t   create new resource set\n");
	printf("\t--cpu C\t\t   make CPU reservation with comp. time of C microsecs\n");
	printf("\t--cpu_period T\t   make CPU reservation with period of T microsecs. [100000]\n");
	printf("\t--cpu_deadline D   make CPU reservation with deadline of D microsecs. [100000]\n");
	printf("\t--cpu_bind C\t   bind CPU reservation to CPU C\n");
	printf("\t--cpu_soft\t   make reservation to be soft\n");
	printf("\t[--cpu_hard]\t   make reservation to be hard\n");
	printf("\t--net B\t\t   make NET reservation with rate of B bytes\n");
	printf("\t--net_period T\t   make NET reservation with period of T microsecs. [100000]\n");
	printf("\t--net_deadline D   make NET reservation with deadline of D microsecs. [100000]\n");
	printf("\t--net_soft\t   make reservation to be soft\n");
	printf("\t[--net_hard]\t   make reservation to be hard\n");
	printf("\t--rnet B\t   make R-NET reservation with rate of B bytes\n");
	printf("\t--rnet_period T\t   make R-NET reservation with period of T microsecs. [100000]\n");
	printf("\t--rnet_deadline D  make R-NET reservation with deadline of D microsecs. [100000]\n");
	printf("\t--rnet_soft\t   make reservation to be soft\n");
	printf("\t[--rnet_hard]\t   make reservation to be hard\n");
	printf("\t--detach_pid pid   detach process from resource set\n");
	printf("\t--from_rset  rset  detach above process from this resource set\n");
	printf("\t--destroy_set rset destroy resource set\n");
	printf("\t--help\t\t   this message\n");
	printf("\t--inherit\t   children of the process will inherit the reserve\n");
	printf("Examples...\n");  
	printf("\t %s --newrset --cpu 2500 --cpu_period 10000 --exec /usr/X11R6/lib/xscreensaver/kumppa\n", pname);
	printf("\t %s --newrset --cpu 5000 --cpu_period 10000 --cpu_deadline 8000 --cpu_hard --exec '/usr/X11R6/lib/xscreensaver/attraction -glow -points 500 -threshold 200 -delay 10'\n", pname);
	exit(0);
}



/*
 * rkexec -exec netscape ...
 */
int
main(int argc, char *argv[])
{
	int          c, status;
	char         *cmd = NULL;
	char         newrset = 0x0;
	char         cpu_reserve_mode = RSV_HARD;
	char         net_reserve_mode = RSV_HARD;
	char         rnet_reserve_mode = RSV_HARD;

	int          cpu_period_us = 100000;    /* 100 ms by default */
	int          cpu_deadline_us = 100000;  /* 100 ms by default */
	int          cpu_start_us = 0;          /* start asap        */
	int	     cpu_us = -1;       	
	
	int          net_period_us = 100000;    /* 100 ms by default */
	int          net_deadline_us = 100000;  /* 100 ms by default */
	int          net_start_us = 0;          /* start asap        */
	int	     net_us = -1;       	

	int          rnet_period_us = 100000;    /* 100 ms by default */
	int          rnet_deadline_us = 100000;  /* 100 ms by default */
	int          rnet_start_us = 0;          /* start asap        */
	int	     rnet_us = -1;       	
	
	int          inherit = 0;
	//int cbind = RK_ANY_CPU;
	pid_t        pid;
	pid_t        arg_pid = -1;
	char 	       name[RSET_NAME_LEN];

	rk_reserve_t       cpu_rsv;
	rk_reserve_t       net_rsv;
	rk_reserve_t       rnet_rsv;
	rk_resource_set_t  rset = NULL;
	rk_resource_set_t  d_rset = NULL;
	rk_resource_set_t  p_rset = NULL;

	name[0] = '\0';
  
	while(1){
		int option_index = 0;
		static struct option long_options[] = 
		{
			{"exec", 1, 0, 0},
			{"rset", 1, 0, 0},
			{"newrset", 0, 0, 0},
			{"name", 1, 0, 0},
			{"cpu", 1, 0, 0},
			{"cpu_period", 1, 0, 0},
			{"cpu_deadline", 1, 0, 0},
			{"cpu_start-time", 1, 0, 0},
			{"help", 0, 0, 0},
			{"cpu_hard", 0, 0, 0},
			{"cpu_firm", 0, 0, 0},
			{"cpu_soft", 0, 0, 0},
			{"inherit", 0, 0, 0},
			{"cpu_bind", 1, 0, 0},
			{"net", 1, 0, 0},
			{"net_period", 1, 0, 0},
			{"net_deadline", 1, 0, 0},
			{"net_start-time", 1, 0, 0},
			{"net_hard", 0, 0, 0},
			{"net_firm", 0, 0, 0},
			{"net_soft", 0, 0, 0},
			{"rnet", 1, 0, 0},
			{"rnet_period", 1, 0, 0},
			{"rnet_deadline", 1, 0, 0},
			{"rnet_start-time", 1, 0, 0},
			{"rnet_hard", 0, 0, 0},
			{"rnet_firm", 0, 0, 0},
			{"rnet_soft", 0, 0, 0},
			{"detach_pid", 0, 0, 0},
			{"from_rset", 0, 0, 0},
			{"destroy_rset", 0, 0, 0},
			{0, 0, 0, 0}
		};

#define OPT_EXEC 	0
#define OPT_RSET 	1
#define OPT_NEWRSET	2
#define OPT_NAME	3
#define OPT_CPU		4
#define OPT_CPU_PERIOD	5
#define OPT_CPU_DEADLINE	6   
#define OPT_CPU_START	7   
#define OPT_HELP	8
#define OPT_CPU_HARD	9
#define OPT_CPU_FIRM	10
#define OPT_CPU_SOFT	11
#define OPT_INHERIT 12
#define OPT_BIND     13
#define OPT_NET		 14
#define OPT_NET_PERIOD	15
#define OPT_NET_DEADLINE	16   
#define OPT_NET_START	17   
#define OPT_NET_HARD    18
#define OPT_NET_FIRM	19
#define OPT_NET_SOFT	20
#define OPT_RNET		21 
#define OPT_RNET_PERIOD	22
#define OPT_RNET_DEADLINE	23   
#define OPT_RNET_START	 24  
#define OPT_RNET_HARD    25
#define OPT_RNET_FIRM	 26
#define OPT_RNET_SOFT	27
#define OPT_DETACH_PID  28
#define OPT_FROM_RSET  29
#define OPT_DESTROY_RSET 30
   
		c = getopt_long_only(argc, argv, "",
							 long_options, &option_index);

		/* bunch of option handling... */
		if (c == -1)
			break;
		switch (c) {
		case 0:

#ifdef DEBUG
			printf("option[%d] %s", option_index, long_options[option_index].name);
			if(optarg)
				printf(" with arg %s", optarg);
			printf("\n");
#endif
	
			switch (option_index) {
			case OPT_EXEC:
				cmd = optarg; 
				break;

			case OPT_RSET:
				/* we want base 16 */
				rset = (rk_resource_set_t)strtoul(optarg, NULL, 16);
				if (rset == (rk_resource_set_t)ULONG_MAX){
					perror("ULONG_MAX");
					exit(-1);
				}
				break;

			case OPT_NEWRSET: 
				newrset = 0x1;
				break;

			case OPT_NAME: 
				strncpy(name, optarg, RSET_NAME_LEN); 
				break;

			case OPT_CPU:
				cpu_us = atoi(optarg);
				break;

			case OPT_CPU_PERIOD:
				cpu_period_us = atoi(optarg);
				break;

			case OPT_CPU_DEADLINE:
				cpu_deadline_us = atoi(optarg);
				break;

			case OPT_CPU_START:
				cpu_start_us = atoi(optarg);
				break;

			case OPT_HELP:
				print_usage(argv[0]);
				break;

			case OPT_CPU_HARD:
				cpu_reserve_mode = RSV_HARD;
				break;

			case OPT_CPU_FIRM:
				cpu_reserve_mode = RSV_FIRM;
				break;

			case OPT_CPU_SOFT:
				cpu_reserve_mode = RSV_SOFT;
				break;
	
			case OPT_INHERIT:
				inherit = 1;
				break;

			case OPT_BIND:
				//cbind = atoi(optarg);
				break;
			
			case OPT_NET:
				net_us = atoi(optarg);
				printf("net_us %d\n",net_us);
				break;
			
			case OPT_NET_PERIOD:
				net_period_us = atoi(optarg);
				break;
			
			case OPT_NET_DEADLINE:
				net_deadline_us = atoi(optarg);
				break;
			
			case OPT_NET_START:
				net_start_us = atoi(optarg);
				break;
			
			case OPT_NET_HARD:
				net_reserve_mode = RSV_HARD;
				break;
			
			case OPT_NET_FIRM:
				net_reserve_mode = RSV_FIRM;
				break;
		
			case OPT_NET_SOFT:
				net_reserve_mode = RSV_SOFT;
				break;
			
			case OPT_RNET:
				rnet_us = atoi(optarg);
				break;
			
			case OPT_RNET_PERIOD:
				rnet_period_us = atoi(optarg);
				break;
			
			case OPT_RNET_DEADLINE:
				rnet_deadline_us = atoi(optarg);
				break;
			
			case OPT_RNET_START:
				rnet_start_us = atoi(optarg);
				break;
			
			case OPT_RNET_HARD:
				rnet_reserve_mode = RSV_HARD;
				break;
			
			case OPT_RNET_FIRM:
				rnet_reserve_mode = RSV_FIRM;
				break;
		
			case OPT_RNET_SOFT:
				rnet_reserve_mode = RSV_SOFT;
				break;
			
			case OPT_DETACH_PID:
				arg_pid = atoi(optarg);
				break;
			
			case OPT_FROM_RSET:
				p_rset = (rk_resource_set_t)strtoul(optarg, NULL, 16);
				if (p_rset == (rk_resource_set_t)ULONG_MAX){
					perror("ULONG_MAX");
					exit(-1);
				}
				break;
			
			case OPT_DESTROY_RSET:
				d_rset = (rk_resource_set_t)strtoul(optarg, NULL, 16);
				if (d_rset == (rk_resource_set_t)ULONG_MAX){
					perror("ULONG_MAX");
					exit(-1);
				}
				break;
			}
		case '?':
			break;
		default:
			printf("?? getopt returned character code 0%o ??\n", c);
			print_usage(argv[0]);
		}
	}

	if (optind < argc)
	{
		printf ("undefined arguments....ignoring   ");
		while (optind < argc)
			printf ("%s ", argv[optind++]);
		printf ("\n");
	}


	/*if (newrset == 0x0 && rset == NULL && cmd == NULL)
	  print_usage(argv[0]);*/
	if (cmd == NULL)
		print_usage(argv[0]);

	rk_inherit_mode(inherit);

	if (name[0] == '\0') sprintf(name, "Rsrc Set x");
	/* resource set */
	if (newrset == 0x1) {
		if (rset != NULL) {
			print_error("--rset and --newrset cannot be both set\n");
			exit(-1);
		}
		if ((rset = rk_resource_set_create(name)) == NULL) {
			fprintf(stderr, "Could not create resource set, quitting...\n");
			exit(-2);
		}
		if( cpu_us > cpu_period_us ) {
			fprintf(stderr,"Can't create cpu reservation greater than period...\n");
			exit(-1);
		}
		if( cpu_deadline_us > cpu_period_us) {
			fprintf(stdout,"Taking deadline = period...\n");
			cpu_deadline_us = cpu_period_us;
		}
		if( net_deadline_us > net_period_us) {
			fprintf(stdout,"Taking deadline = period...\n");
			net_deadline_us = net_period_us;
		}
		if( rnet_deadline_us > rnet_period_us) {
			fprintf(stdout,"Taking deadline = period...\n");
			rnet_deadline_us = rnet_period_us;
		}

#ifdef DEBUG
		printf("Resource Set Created (%p)=(%d)\n", rset, (int)rset);
#endif
		if(cpu_us != -1) {
			/* CPU reserve */
			rk_reserve_param_data_t p;
			cpu_reserve_attr_data_t attr;
			long int csec = cpu_us / 1000000;
			long int tsec = cpu_period_us / 1000000;
			long int dsec = cpu_deadline_us / 1000000;

			assert (rset != NULL);

			p.enf_mode = cpu_reserve_mode;
			p.sch_mode = cpu_reserve_mode;
			p.rep_mode = cpu_reserve_mode;

			attr.compute_time.tv_sec = csec;
			attr.compute_time.tv_nsec = (cpu_us - (csec * 1000000)) * 1000;
			attr.period.tv_sec = tsec;
			attr.period.tv_nsec = (cpu_period_us - (tsec * 1000000)) * 1000;
			attr.deadline.tv_sec = dsec;
			attr.deadline.tv_nsec = (cpu_deadline_us - (dsec * 1000000)) * 1000;
			attr.blocking_time.tv_sec = (long int) 0;
			attr.blocking_time.tv_nsec = (long int) 0;
			attr.start_time.tv_sec = (long int) 0;
			attr.start_time.tv_nsec = (long int) 0;
			attr.reserve_type = p;
			//attr.processor = cbind;
			if ((cpu_rsv = rk_cpu_reserve_create(rset, &attr)) != NULL) {
				fprintf(stderr, "Could not create cpu reserve, quitting...\n");
				rk_resource_set_destroy(rset);
				exit(-2);
			}
		}


		if(net_us != -1) {
			/* NET reserve */
			rk_reserve_param_data_t p;
			net_reserve_attr_data_t net_attr;

		   	
			long int tsec = net_period_us / 1000000;
			long int dsec = net_deadline_us / 1000000;
			
			bzero(&net_attr, sizeof(net_attr));
			
			p.enf_mode = net_reserve_mode;
			p.sch_mode = net_reserve_mode;
			p.rep_mode = net_reserve_mode;

			net_attr.amount = net_us;
			net_attr.period.tv_sec = tsec;
  			net_attr.period.tv_nsec = (net_period_us - (tsec * 1000000)) * 1000; 
  			net_attr.deadline.tv_sec = dsec;
  			net_attr.deadline.tv_nsec = (net_deadline_us - (dsec * 1000000)) * 1000; 
  			net_attr.start_time.tv_sec = 0;
  			net_attr.start_time.tv_nsec = 0;
  			net_attr.blocking_time.tv_sec = 0;
  			net_attr.blocking_time.tv_nsec = 0;
  			net_attr.reserve_type= p;
			if((net_rsv = rk_net_reserve_create(rset, &net_attr))== NULL) {
				fprintf(stderr, "Could not create net reserve, quitting...\n");
				/* Should i destroy resource set ? */
				rk_resource_set_destroy(rset);
				exit(-2);
			}
		}
#if 1
		if(rnet_us != -1) {
			/* RNET reserve */


			rk_reserve_param_data_t p;
			net_reserve_attr_data_t rnet_attr;
		   	
			long int tsec = rnet_period_us / 1000000;
			long int dsec = rnet_deadline_us / 1000000;
			
			if(cpu_us == -1) {
				fprintf(stderr, "Could not create rnet reserve, please first create cpu reserve...\n");
				return -2;
			}
			
			bzero(&rnet_attr, sizeof(rnet_attr));
			
			p.enf_mode = rnet_reserve_mode;
			p.sch_mode = rnet_reserve_mode;
			p.rep_mode = rnet_reserve_mode;

			rnet_attr.amount = rnet_us;
			rnet_attr.period.tv_sec = tsec;
  			rnet_attr.period.tv_nsec = (rnet_period_us - (tsec * 1000000)) * 1000 ;
  			rnet_attr.deadline.tv_sec = dsec;
  			rnet_attr.deadline.tv_nsec = (rnet_deadline_us - (dsec * 1000000)) * 1000 ;
  			rnet_attr.start_time.tv_sec = 0;
  			rnet_attr.start_time.tv_nsec = 0;
  			rnet_attr.blocking_time.tv_sec = 0;
  			rnet_attr.blocking_time.tv_nsec = 0;
  			rnet_attr.reserve_type= p;
			
			if((rnet_rsv = rk_rnet_reserve_create(rset, &rnet_attr)) == NULL) {
				fprintf(stderr, "Could not create rnet reserve, quitting...\n");
				/* Should i destroy resource set ? */
				rk_resource_set_destroy(rset);
				exit(-2);
			}
		}
	
#endif
	}  

	if(d_rset != NULL) {
		/* destroy resourceset */
		if( rk_resource_set_destroy(d_rset) == RK_ERROR)
			fprintf(stderr, " Could not destroy resource set %p\n", d_rset);
	}

	if(arg_pid != -1) {
		if(p_rset != NULL) {
			if( rk_resource_set_detach_process(p_rset, arg_pid) == RK_ERROR) 
				fprintf(stderr, "%d:Could not detach porcess from resource set \n", arg_pid);
		}
	}

	/* execute the new program */
	if (cmd != NULL && newrset != 0x0 && rset != NULL ) {
		char *progname; 
		printf("cmd = %s\n", cmd); 
		progname = strsep(&cmd, (const char *)" ");
		printf("progname = %s\n", progname);
		if (cmd != NULL)
			printf("argument = %s\n", cmd); 
    
		if ((pid = fork()) < 0){
			perror("fork");
			exit(-1);
		}

#define MAXARG 20

		if (pid == 0) {
			/* child process */
			int i;
			char const *argument[MAXARG];
			argument[0] = progname;

			/* attach to a resource set */
			/* TODO
			 * unfortunately, I cannot attach to rset at this point.
			 * should look into the code.....
			 */
			if (rk_resource_set_attach_process(rset, getpid()) != 0) {
				fprintf(stderr, "Could not attach process %d to reserve\n", getpid()); 
				rk_resource_set_destroy(rset);
				exit(-2);
			}

			/* build the argument vector */
			for (i=1; i<MAXARG; i++) {
				argument[i] = strsep(&cmd, (const char *)" ");
				if (argument[i] == NULL)
					break;
				/* skip spaces */
				if (*argument[i] == 0)
					i--;
			}
			execvp(progname, (char **const)argument); 
			perror(progname);
			exit(1);
		}


		/* rk_resource_set_attach_process(rset, pid); */
		while (wait(&status) != pid) 
			;

		/* destroy resourceset */
		rk_resource_set_destroy(rset);
	}
  
	exit(0);
}
