/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Universite Bordeaux 1, Telecom SudParis
 * See COPYING in top-level directory.
 */

#include <otf2/otf2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <assert.h>
#include <wait.h>


#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h> 
#include <limits.h>
#include <time.h>
#include <inttypes.h> 
#include <sys/syscall.h>

#include "eztrace-lib/eztrace_otf2.h"
#include "eztrace-lib/eztrace.h"


#include "iotracer_to_otf2.h"

static _Atomic int next_string_id = 11;

enum attr_ids {
	       attr_timestamp,
	       attr_level,
	       attr_type,
	       attr_address,
	       attr_size,
	       attr_probe,
	       attr_label,
	       attr_pid,
	       attr_tid,
	       attr_command,
	       attr_inode,
	       attr_pinode,
	       NB_ATTRIBUTE
};

static char * attr_names [NB_ATTRIBUTE] = {"TimeStamp","Level", "Type",  "Address","Size", "Probe", "Label", "Pid", "Tid", "Command","inode", "p-inode"};
static char * attr_types [NB_ATTRIBUTE]= { "long",     "string","string","long"   ,"long", "int"  , "string","int", "int", "string", "long",  "long"};


static int vfs_write_id = -1;
static int vfs_read_id = -1;
static int fs_write_id = -1;
static int fs_read_id = -1;
static int blk_write_id = -1;
static int blk_read_id = -1;


static char ** getAttributes(char * line);
static void RegisterAttrValueAndAddAttribute(OTF2_GlobalDefWriter* global_def_writer,
					     OTF2_AttributeValue attr, char * type, char * value, 
					     OTF2_AttributeList* attr_list, OTF2_AttributeRef attr_id);
static int getRegion(char * level, char * request);
static char * getProbeName(int i);

static OTF2_TimeStamp gettimestamp( char * time );

static OTF2_FlushType
pre_flush( void*            userData,
           OTF2_FileType    fileType,
           OTF2_LocationRef location,
           void*            callerData,
           bool             final );

static OTF2_TimeStamp
post_flush( void*            userData,
            OTF2_FileType    fileType,
            OTF2_LocationRef location );


static char ** getAttributes(char * line){
  char ** attributes;
  attributes=malloc(NB_ATTRIBUTE * sizeof(char *));
   
  int index = 0;
  
  char * token = strtok(line, "\t");
  while( token != NULL ) {
    if(index >= NB_ATTRIBUTE) {
      printf("Warning: index %d is out of bound. Cannot store %s\n", index, token);
      break;
    }
    attributes[index] = token;
    index++;
    //printf( " %s\n", token ); //printing each token
    token = strtok(NULL, "\t");	
  }
  
  return attributes;
}


static char * getProbeName(int i){
  switch (i) {
  case 0:
    return "VFS_write\0";
  case 1:
    return "VFS_read\0";
  case 2:
    return "generic_perform_write\0";
  case 3:
    return "generic_file_write_iter\0";
  case 4:
    return "generic_file_read_iter\0";
  case 5:
    return "submit_bio\0";
  case 6:
    return "submit_bio_noacct\0";
  case 7:
    return "bio_endio\0";   
  default:
    eztrace_error( "Cannot find probe name for i=%d!\n",i);
    exit(EXIT_FAILURE);
  }
  //return string_liste;
  return NULL;
}

struct registered_string {
  char string[256];
  uint32_t string_ref;
};

#define NB_STRING_MAX 128
static struct registered_string registered_strings[NB_STRING_MAX];
static int nb_registered_strings = 0;
static int register_string(OTF2_GlobalDefWriter* global_def_writer, char* str);

static uint32_t _register_string(OTF2_GlobalDefWriter* global_def_writer, char* str) {
  for(int i=0; i< nb_registered_strings; i++) {
    if(strcmp(str, registered_strings[i].string) == 0)
      return registered_strings[i].string_ref;
  }

  uint32_t string_ref = (uint32_t) register_string(global_def_writer, str);
  if(nb_registered_strings<NB_STRING_MAX) {
    int i = nb_registered_strings++;
    strncpy(registered_strings[i].string, str, 256);    
  }
  return string_ref;    
}

#ifndef SWITCH_CASE_INIT
#define SWITCH_CASE_INIT
#define SWITCH(X) for (char* _switch_p__ = X, _switch_next__=1 ; _switch_p__ ; _switch_p__=0, _switch_next__=1) { {
#define CASE(X)     } if (!_switch_next__ || !(_switch_next__ = strcmp(_switch_p__, X))) {
#define DEFAULT       } {
#define END     }}
#endif

static void RegisterAttrValueAndAddAttribute(OTF2_GlobalDefWriter* global_def_writer,
					     OTF2_AttributeValue attr, char * type, char * value, 
					     OTF2_AttributeList* attr_list,
					     OTF2_AttributeRef attr_id){

  SWITCH (type)
    CASE ("long")
        uint64_t out;
        sscanf(value, "%" SCNu64, &out);
        attr.uint64 = (uint64_t) out;
        OTF2_AttributeList_AddAttribute( attr_list, attr_id, OTF2_TYPE_UINT64, attr);
        break;

    CASE ("int")    
        uint32_t out;
        sscanf(value, "%" SCNu32, &out);     
        attr.uint32 = (uint32_t) out;
        OTF2_AttributeList_AddAttribute( attr_list, attr_id, OTF2_TYPE_UINT32, attr);
        break;

    CASE ("string")
      attr.stringRef = (uint32_t) _register_string(global_def_writer, value);
        OTF2_AttributeList_AddAttribute( attr_list, attr_id, OTF2_TYPE_STRING, attr);
        break;

    DEFAULT
        eztrace_error( "OTF2_AttributeValue type Cannot be identified!\n");
        exit(EXIT_FAILURE);
  END
}

static int getRegion(char * level, char * request){
  SWITCH (level)
    CASE ("V")
        SWITCH (request)
          CASE ("W")
            return vfs_write_id;

          CASE ("R")
            return vfs_read_id;

          DEFAULT
            eztrace_error( "Region cannot be identified!\n");
            exit(EXIT_FAILURE);
        END

    CASE ("F")
        SWITCH (request)
          CASE ("W")
            return fs_write_id;

          CASE ("R")
            return fs_read_id;

          DEFAULT
            eztrace_error( "Region cannot be identified!\n");
            exit(EXIT_FAILURE);
        END

    CASE ("B")
        SWITCH (request)
          CASE ("W")
            return blk_write_id;

          CASE ("R")
            return blk_read_id;

          DEFAULT
            eztrace_error( "Region cannot be identified!\n");
            exit(EXIT_FAILURE);
        END

    DEFAULT
        eztrace_error( "Region cannot be identified!\n");
        exit(EXIT_FAILURE);
  END
    return -1;
}


static OTF2_TimeStamp gettimestamp(char * time) {
  uint64_t out;
  sscanf(time, "%" SCNu64, &out);
  return (uint64_t) out;
}

static OTF2_FlushType pre_flush( void*            userData __attribute__((unused)),
				 OTF2_FileType    fileType __attribute__((unused)),
				 OTF2_LocationRef location __attribute__((unused)),
				 void*            callerData __attribute__((unused)),
				 bool             final  __attribute__((unused))) {
    return OTF2_FLUSH;
}

static OTF2_TimeStamp post_flush( void*            userData __attribute__((unused)),
				  OTF2_FileType    fileType __attribute__((unused)),
				  OTF2_LocationRef location __attribute__((unused)) ) {
  return 0;
}

static OTF2_FlushCallbacks flush_callbacks = {
  .otf2_pre_flush  = pre_flush,
  .otf2_post_flush = post_flush
};


static int register_string(OTF2_GlobalDefWriter* global_def_writer, char* str) {
  static _Atomic int next_id = 0;
  int id = next_id++;
      
  OTF2_ErrorCode err = OTF2_GlobalDefWriter_WriteString( global_def_writer, id, str);
  assert(err == OTF2_SUCCESS);
  return id;
}

static int register_function(OTF2_GlobalDefWriter* global_def_writer, char* name) {
  static _Atomic int next_id = 0;
  int id = next_id ++;
  int str_id = register_string(global_def_writer, name);
  eztrace_log(dbg_lvl_normal, "New function: %s (id=%d)\n", name, id);

  OTF2_ErrorCode err = OTF2_GlobalDefWriter_WriteRegion( global_def_writer,
							 id /* id */,
							 str_id /* region name  */,
							 str_id /* alternative name */,
							 str_id /* description */,
							 OTF2_REGION_ROLE_FUNCTION,
							 OTF2_PARADIGM_USER,
							 OTF2_REGION_FLAG_NONE,
							 0 /* source file */,
							 0 /* begin lno */,
							 0 /* end lno */ );
  assert(err == OTF2_SUCCESS);
  return id;
}

struct tootf2_thread_info {
  int thread_rank;
  pid_t tid;
  char thread_name[50];
  int otf2_thread_id;
  OTF2_EvtWriter* evt_writer;
};

static struct tootf2_thread_info thread_infos[1024];
static int nb_registered_threads = 0;

static struct tootf2_thread_info* get_thread(OTF2_Archive* archive, pid_t tid) {
  for(int i=0; i<nb_registered_threads; i++) {
    if(thread_infos[i].tid == tid)
      return &thread_infos[i];
  }

  struct ezt_thread_info* ezt_info = get_thread_info_by_pid(tid);
  if(ezt_info == NULL) {
    eztrace_warn("When merging iotracer trace: cannot find thread %d\n", tid);
    return NULL;
  }

  struct tootf2_thread_info * infos = &thread_infos[nb_registered_threads++];
  infos->thread_rank = ezt_info->thread_rank;
  infos->tid = tid;
  strncpy(infos->thread_name, ezt_info->thread_name, 50);
  infos->otf2_thread_id = ezt_info->otf2_thread_id;

  eztrace_log(dbg_lvl_normal,"TOOTF2: New Thread: rank=%d, tid=%d, name=%s\n", infos->thread_rank, infos->tid, infos->thread_name);
  infos->evt_writer = OTF2_Archive_GetEvtWriter( archive, infos->otf2_thread_id);
  assert(infos->evt_writer);

  return infos;
}


void convert_iotracer_log_to_otf2(){
#define STR_SIZE 1024
  char iotracer_trace_path[STR_SIZE];
  char otf2_trace_path[STR_SIZE];
  char* res = getenv("EZTRACE_TRACE_DIR");
  if (!res) {
    strncpy(iotracer_trace_path, IOTRACER_TRACE_PATH, STR_SIZE);
    strncpy(otf2_trace_path, "IOtracer_output", STR_SIZE);
  } else {
    strncpy(iotracer_trace_path, res, STR_SIZE);
    strncat(iotracer_trace_path, "/", STR_SIZE-1);
    strncat(iotracer_trace_path, IOTRACER_TRACE_PATH, STR_SIZE-1);

    strncpy(otf2_trace_path, res, STR_SIZE);
    strncat(otf2_trace_path, "/IOtracer_output", STR_SIZE-1);
  }

  eztrace_log(dbg_lvl_normal, "IOtracer: converting IOtracer log file (%s) to otf2 format (%s)\n",
	      iotracer_trace_path, otf2_trace_path);
  OTF2_Archive* archive = OTF2_Archive_Open( otf2_trace_path,
                                               "log",
                                               OTF2_FILEMODE_WRITE,
                                               1024 * 1024 /* event chunk size */,
                                               4 * 1024 * 1024 /* def chunk size */,
                                               OTF2_SUBSTRATE_POSIX,
                                               OTF2_COMPRESSION_NONE );
    OTF2_Archive_SetFlushCallbacks( archive, &flush_callbacks, NULL );
    OTF2_Archive_SetSerialCollectiveCallbacks( archive );
    OTF2_Archive_OpenEvtFiles( archive );

  /***
      IOtracer log format: TimeStamp, Level, Type, Address, Size, Probe, Pid, Command, inode, P-inode
      IOtracer log example:63072550856527, 282959872, 8192, V, W, 14237, dd, 1, 1712052, 1703937
      IOtracer log file path: iotracer_trace.txt
    
  ***/

  char *line = NULL;
  size_t len = 0;
  ssize_t read;
  char ** attr_values;

  struct stat stat_buf;
  stat(iotracer_trace_path, &stat_buf);
  eztrace_log(dbg_lvl_debug, "%s contains %lu bytes\n", iotracer_trace_path, stat_buf.st_size);

  FILE * iotracer_log_f;
  iotracer_log_f =  fopen(iotracer_trace_path , "r");

  if (iotracer_log_f == NULL){
    eztrace_error( "IOtracer: cannot open the IOtracer log file");
    exit(EXIT_FAILURE);
    
  }

  //    OTF2_EvtWriter* evt_writer = OTF2_Archive_GetEvtWriter( archive, 0 );    

  OTF2_GlobalDefWriter* global_def_writer = OTF2_Archive_GetGlobalDefWriter( archive );
    
  OTF2_GlobalDefWriter_WriteClockProperties( global_def_writer,
					     1e9 /* 1 tick per nanosecond */,
					     0 /* epoch */,
					     2 /* length */
#if OTF2_MAJOR_VERSION >= 3
					     , OTF2_UNDEFINED_TIMESTAMP
#endif
					     );

  //////////////////////////////////////////////////////

  int program_str_id =    register_string(global_def_writer, "Program");
  vfs_write_id = register_function(global_def_writer, "VFS_WRITE");
  vfs_read_id =  register_function(global_def_writer, "VFS_READ");
  fs_write_id =  register_function(global_def_writer, "FS_WRITE");
  fs_read_id =   register_function(global_def_writer, "FS_READ");
  blk_write_id = register_function(global_def_writer, "BLK_WRITE");
  blk_read_id =  register_function(global_def_writer, "BLK_READ");

  eztrace_log(dbg_lvl_normal, "Converting the iotracer trace (%s) to otf2:\n", iotracer_trace_path);
  unsigned long nb_events=0;
  unsigned long nb_ignored_events=0;
    
  while ((read = getline(&line, &len, iotracer_log_f)) != -1) {
    eztrace_log(dbg_lvl_debug, "Read: '%s'\n", line);
    if(strlen(line) == 0) {
      printf("Empty line\n");
      continue;
    }

    int attr_id = 0;
    attr_values = getAttributes(line);

    eztrace_log(dbg_lvl_debug, "Event(timestamp: %s, level: %s, type: %s, address: %s, size: %s, probe: %s, label: %s, pid: %s, tid: %s, command: %s, inode: %s, p-inode: %s)\n",
		attr_values[attr_timestamp],
		attr_values[attr_level],
		attr_values[attr_type],
		attr_values[attr_address],
		attr_values[attr_size],
		attr_values[attr_probe],
		attr_values[attr_label],
		attr_values[attr_pid],
		attr_values[attr_tid],
		attr_values[attr_command],
		attr_values[attr_inode],
		attr_values[attr_pinode]);

    OTF2_AttributeList* attribute_list = OTF2_AttributeList_New();

    for(int i=0; i<NB_ATTRIBUTE; i++){
      if(i<3)
	continue;

      OTF2_AttributeRef attribute_id = attr_id ++;

      OTF2_AttributeValue attr_value;
      RegisterAttrValueAndAddAttribute(global_def_writer, attr_value, attr_types[i], attr_values[i], attribute_list, attribute_id);
    } 

    // An event looks like this:
    //#     timestamp       Level  op(r/w)  address  size  probe   pid       tid    command     inode   inodep
    //	80094337320451  F       W       0       512     4       28360   28469   fio     42731217        42731213

    int region = getRegion(attr_values[attr_level], attr_values[attr_type]);

    assert(region >= 0);
    pid_t tid = atoi(attr_values[attr_tid]);

    struct tootf2_thread_info* thread_info = get_thread(archive, tid);
    if(thread_info) {
      nb_events++;
      OTF2_EvtWriter_Enter( thread_info->evt_writer, attribute_list,
			    gettimestamp(attr_values[attr_timestamp]), getRegion(attr_values[attr_level], attr_values[attr_type]) /* region */ );

      OTF2_EvtWriter_Leave(thread_info->evt_writer, attribute_list,
			   gettimestamp(attr_values[attr_timestamp]), getRegion(attr_values[attr_level], attr_values[attr_type]) /* region */ );
    } else {	
      nb_ignored_events++;
    }
    free(attr_values);
  }

  eztrace_log(dbg_lvl_normal,"%lu IOTrace events (excluding %lu ignored events)\n", nb_events, nb_ignored_events);
    
  free(line);
  fclose(iotracer_log_f);


    

  /////////////////////////////////////////////////////



  int str_id_addr = register_string(global_def_writer, "Address");
  int str_id_size = register_string(global_def_writer, "Size");
  int str_id_probe = register_string(global_def_writer, "Probe");
  int str_id_pid = register_string(global_def_writer, "Pid");
  int str_id_comm = register_string(global_def_writer, "Comm");
  int str_id_f_inode = register_string(global_def_writer, "F-inode");
  int str_id_d_inode = register_string(global_def_writer, "D-inode");

  //address
  OTF2_GlobalDefWriter_WriteAttribute ( global_def_writer,
					0,
					str_id_addr,
					0,
					OTF2_TYPE_UINT64
					);
  //size
  OTF2_GlobalDefWriter_WriteAttribute ( global_def_writer,
					1,
					str_id_size,
					0,
					OTF2_TYPE_UINT64
					);
  //probe
  OTF2_GlobalDefWriter_WriteAttribute ( global_def_writer,
					2,
					str_id_probe,
					0,
					OTF2_TYPE_UINT64
					);
  //pid
  OTF2_GlobalDefWriter_WriteAttribute ( global_def_writer,
					3,
					str_id_pid,
					0,
					OTF2_TYPE_UINT64
					);

  //command
  OTF2_GlobalDefWriter_WriteAttribute ( global_def_writer,
					4,
					str_id_comm,
					0,
					OTF2_TYPE_STRING
					);
  //f_inode
  OTF2_GlobalDefWriter_WriteAttribute ( global_def_writer,
					5,
					str_id_f_inode,
					0,
					OTF2_TYPE_UINT64
					);

  //d_ionde
  OTF2_GlobalDefWriter_WriteAttribute ( global_def_writer,
					6,
					str_id_d_inode,
					0,
					OTF2_TYPE_UINT64
					);


  /////////////////////////////////////////


  int program_id = 0;
  int process_id = 0;
  int thread_id = 0;
    
  OTF2_GlobalDefWriter_WriteSystemTreeNode( global_def_writer,
					    program_id /* id */,
					    program_str_id /* name */,
					    7 /* class */,
					    OTF2_UNDEFINED_SYSTEM_TREE_NODE /* parent */ );

  char process_name[128];
  snprintf(process_name, 128, "P#%d", ezt_mpi_rank);
  int process_string = register_string(global_def_writer, process_name);

  OTF2_GlobalDefWriter_WriteLocationGroup( global_def_writer,
					   process_id /* id */,
					   process_string /* name */,
					   OTF2_LOCATION_GROUP_TYPE_PROCESS,
					   program_id /* system tree */
#if OTF2_MAJOR_VERSION >= 3
					   , OTF2_UNDEFINED_LOCATION_GROUP
#endif
					   );


  eztrace_log(dbg_lvl_normal, "registering %d threads\n", nb_registered_threads);
  for(int thread_id =0; thread_id < nb_registered_threads; thread_id++) {
    struct tootf2_thread_info *info = &thread_infos[thread_id];
    eztrace_log(dbg_lvl_normal, "t[%d]: rank=%d, tid=%d, name=%s\n",
		thread_id, info->thread_rank, info->tid, info->thread_name);
    OTF2_StringRef thread_string = register_string(global_def_writer, info->thread_name);

    OTF2_GlobalDefWriter_WriteLocation( global_def_writer,
					info->otf2_thread_id /* id */,
					thread_string /* name */,
					OTF2_LOCATION_TYPE_CPU_THREAD,
					2 /* # events */,
					process_id /* location group */ );
      
    OTF2_DefWriter *def_writer = OTF2_Archive_GetDefWriter(archive,
							   info->otf2_thread_id);
    OTF2_Archive_CloseDefWriter( archive, def_writer );

    OTF2_Archive_CloseEvtWriter( archive, info->evt_writer );

  }
  OTF2_Archive_CloseEvtFiles( archive );
  OTF2_Archive_CloseGlobalDefWriter( archive, global_def_writer );

  OTF2_Archive_Close( archive );
}
