#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include "pptrace.h"
#include "eztrace.h"

extern int pptrace_debug_level;

int file_exists(const char *pathname) {
  if (access(pathname, F_OK) != -1) {
    return 1; // file exists
  }
  return 0;
}

int str_begins_with(char *str, char *begin) {
  return strncmp(str, begin, strlen(begin)) == 0;
}

int str_ends_with(char *str, char *end) { 
  size_t str_len = strlen(str);
  size_t end_len = strlen(end); 
  char *str_end = str + str_len - end_len; 
  return strcmp(str_end, end) == 0; 
}

void usage(const char *prog_name) {
  printf("Usage: %s [OPTION] program [arg1 arg2 ...]\n", prog_name);
  printf("\t-t \"plugin1 plugin2 ... pluginN\" Select a list of plugins\n");
  printf("\t-o <directory>	       	      Select the output directory\n");
  printf("\t-l <directory>	       	      Select a plugin directory\n");
  printf("\t-f 			              Enable EZTRACE_FLUSH\n");
  printf("\t-d                                Debug mode\n");
  printf("\t-?  -h                            Display this help and exit\n");
  /* todo: add a verbose mode */
}

static char* get_env_str(const char* prefix,
		     const char* env_name,
		     const char* suffix) {
  char* tmp = getenv(env_name);
  int string_length = 1;

  string_length += strlen(prefix);
  if(tmp) {
    string_length += strlen(tmp);
  }
  string_length += strlen(suffix);


  char* res = malloc(sizeof(char) * string_length);
  res[0]='\0';
  strcat(res, prefix);
  if(tmp)
    strcat(res, tmp);
  strcat(res, suffix);

  return res;
}

/* allocate a string and copy str into it */
static char* alloc_and_copy(const char* str) {
  char* res = NULL;
  int len = 1;
  len += strlen(str);
  res = malloc(sizeof(char)*len);
  res[0]='\0'; // to make sure that the allocated string is really empty
  strcat(res, str);

  return res;
}

/* add to_add to an already allocated string */
static char* add_to_str(char* dest_str, const char* to_add) {
  int len = strlen(dest_str) + strlen(to_add) + 1;
  dest_str = realloc(dest_str, len);
  strcat(dest_str, to_add);
  return dest_str;
}

int main(int argc, char **argv) {
  int debug = 0;
  int use_pp = 0; // for pptrace
  int i, j;
  int test=0;

  // options
  int nb_opts=0;
  for (i=1; i<argc; i++) {
    if (!strcmp(argv[i], "-d")) {
      debug=1;
      nb_opts++;
    } else if (!strcmp(argv[i], "-p")) {
      test=1;
      nb_opts++;
    } else if (!strcmp(argv[i], "-t")) {
      setenv("EZTRACE_TRACE", argv[i+1], 1);
      i++;
      nb_opts+=2;
    } else if (!strcmp(argv[i], "-o")) {
      setenv("EZTRACE_TRACE_DIR", argv[i+1], 1);
      i++;
      nb_opts+=2;
    } else if (!strcmp(argv[i], "-l")) {
      setenv("EZTRACE_LIBRARY_PATH", argv[i+1], 1);
      i++;
      nb_opts+=2;
    } else if (!strcmp(argv[i], "-f")) {
      setenv("EZTRACE_FLUSH", "1", 1);
      nb_opts++;
    } else if (!strcmp(argv[i], "-?") || !strcmp(argv[i], "-h")) {
      usage(argv[0]);
      return EXIT_SUCCESS;
    } else if(argv[i][0] == '-' && argv[i][1] == 'v') {
      nb_opts++;
      for(j = 1; argv[i][j] == 'v'; j++)
        pptrace_debug_level++;
    }
  }

  // make sure eztrace libs are available
  char *ld_library_path = get_env_str("", "LD_LIBRARY_PATH", (test==0) ? ":/home/trahay/Soft/opt/eztrace/trunk/build/../install/lib" : "");
  // other env variables
  char *ezt_trace = get_env_str("", "EZTRACE_TRACE", "");
  char *ezt_library_path = get_env_str("", "EZTRACE_LIBRARY_PATH", (test==0) ? ":/home/trahay/Soft/opt/eztrace/trunk/build/../install/lib" : "");

  // prog_name
  char *prog_name = argv[nb_opts+1];
  if (prog_name == NULL) {
    usage(argv[0]);
    return EXIT_SUCCESS;
  }
  
  // init binary & catch all modules
  void *bin = pptrace_prepare_binary(prog_name);
  if(!bin) {
    fprintf(stderr, "Unable to load binary %s\n", prog_name);
    return EXIT_FAILURE;
  }

  // args of prog
  int nb_args = argc-nb_opts-2; // 2 : argv[0], prog_name

  int arg_length=0;
  for (i=0; i<nb_args; i++) {
    // let's take a few more bytes, just in case I didn't do the math correctly :-)
    arg_length += strlen(argv[nb_opts+2+i]) + 10;
  }
  char *args_concat = malloc(sizeof(char)*(arg_length+1));

  for (i=0; i<nb_args; i++) {
    strcat(args_concat, argv[nb_opts+2+i]);
    if (i != nb_args-1) {
      strcat(args_concat, " ");
    }
  }

  // modules in ezt_trace
  int nb_modules = 0;
  if (strcmp(ezt_trace, "")) {
    char *module = NULL;
    module = strtok(ezt_trace, " ");
    while(module != NULL) {
      if (strcmp(module, "")) {
	      nb_modules++;
      }
      module = strtok(NULL, " ");
    }
  }

  char *modules[nb_modules];
  if(nb_modules != 0) {
    // we have to get EZTRACE_TRACE again because we strtok-ed it
    free(ezt_trace);
    ezt_trace = get_env_str("", "EZTRACE_TRACE", "");

    char *module = NULL;
    module = strtok(ezt_trace, " ");
    i=0;
    while(module != NULL) {
      if (strcmp(module, "")) {
	      modules[i] = module;
	      i++;
      }
      module = strtok(NULL, " ");
    }
  }

  // dirs in ezt_library_path
  int nb_dirs = 0;
  if (strcmp(ezt_library_path, "")) {
    char *dir = NULL;
    dir = strtok(ezt_library_path, ":");
    while(dir != NULL) {
      if (strcmp(dir, "")) {
	nb_dirs++;
      }
      dir = strtok(NULL, ":");
    }
  }

  char *dirs[nb_dirs];
  if(nb_dirs != 0) {
    // we have to get EZTRACE_LIBRARY_PATH again because we strtok-ed it
    free(ezt_library_path);
    ezt_library_path = get_env_str("", "EZTRACE_LIBRARY_PATH", (test==0) ? ":/home/trahay/Soft/opt/eztrace/trunk/build/../install/lib" : "");

    char *dir = NULL;
    dir = strtok(ezt_library_path, ":");
    i=0;
    while(dir != NULL) {
      if (strcmp(dir, "")) {
	dirs[i] = dir;
	i++;
      }
      dir = strtok(NULL, ":");
    }
  }
  
  // libeztrace should always be preloaded!
  char *files = NULL;
  if (test) {
    fprintf(stderr, "Eztrace test Mode\n");
    files = alloc_and_copy("/home/trahay/Soft/opt/eztrace/trunk/build/src/core/.libs/libeztrace-autostart.so");
  }else{
    files = alloc_and_copy("/home/trahay/Soft/opt/eztrace/trunk/build/../install/lib/libeztrace-autostart.so");
  }
  if(pptrace_add_preload(bin, files)){
    fprintf(stderr, "Unable to add %s to LD_PRELOAD\n", files);
    return EXIT_FAILURE;
  }
  set_launcher_env();
  if (strcmp(ezt_trace, "")) {
    for (i=0; i<nb_modules; i++) {
      for (j=0; j<nb_dirs; j++) {
	char *pathname = alloc_and_copy(dirs[j]);
	pathname = add_to_str(pathname, "/libeztrace-autostart-");
	pathname = add_to_str(pathname, modules[i]);
	pathname = add_to_str(pathname, ".so");
	if(file_exists(pathname)) {
	  if(pptrace_load_module(bin, pathname)){
	    fprintf(stderr, "Unable to add %s to LD_PRELOAD\n", pathname);
	    return EXIT_FAILURE;
	  }

	  add_to_str(files, ":");
	  add_to_str(files, pathname);
	}
	free(pathname);
      }
    }
  }else {
    for(i=0; i<nb_dirs; i++) {
      char *pathname = dirs[i];
      struct dirent *dirent;
      DIR *d;
      d = opendir(pathname);
      while ((dirent = readdir(d))) {
	      if(str_begins_with(dirent->d_name, "libeztrace-autostart-") && str_ends_with(dirent->d_name, ".so")) {
	        char *file = alloc_and_copy(pathname);
		file = add_to_str(file, "/");
		file = add_to_str(file, dirent->d_name);
		if(pptrace_load_module(bin, file)) {
	          fprintf(stderr, "Unable to add %s to LD_PRELOAD\n", file);
	          return EXIT_FAILURE;
	        }

		files = add_to_str(files, ":");
		files = add_to_str(files, file);

		free(file);
	      }
      }
      closedir(d);
    }
  }
  unset_launcher_env();
  // run
  if (debug) {
	// TODO: make it configurable
  	char *debugger[5];
  	debugger[0] = "gdb";
  	debugger[1] = "-quiet";
  	debugger[2] = "{name}";
  	debugger[3] = "{pid}";
  	debugger[4] = NULL;
  	pptrace_add_debugger(bin, debugger);
  }
  if(pptrace_run(bin, argv+nb_opts+1, environ)) {
    fprintf(stderr, "Unable to run the target\n");
    return EXIT_FAILURE;
  }
  
  return EXIT_SUCCESS;
}
