/* -*- 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 <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;

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


//#define ENOUGH ((CHAR_BIT * sizeof(int) - 1) / 3 + 2)

#if 0
typedef struct String_value String_value;
struct String_value
{
    int id;
    int attr_id;
    int len;
    char * buffer;
    String_value *suivant;
};
#endif

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 ) {
        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++;
      
  //eztrace_log(dbg_lvl_normal, "New string: %s (id=%d)\n", str, 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_error("When merging iotracer trace: cannot find thread %d", tid);
  }

  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;

    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");


    unsigned long nb_events=0;
    while ((read = getline(&line, &len, iotracer_log_f)) != -1) {
      nb_events++;
      int attr_id = 0;
        attr_values = getAttributes(line);
        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[1], attr_values[2]);

      	//eztrace_log(dbg_lvl_normal, "New iotrace event: (%s, %s) (ts=%llu, region %d, event: %s)\n",
      		    //attr_values[1], attr_values[2],gettimestamp(attr_values[0]), region, line);

      	assert(region >= 0);
	//	pid_t tid = syscall(__NR_gettid); /* TODO: read the tid from the trace */
	pid_t tid = atoi(attr_values[7]);

	struct tootf2_thread_info* thread_info = get_thread(archive, tid);

        OTF2_EvtWriter_Enter( thread_info->evt_writer, attribute_list,
			      gettimestamp(attr_values[0]), getRegion(attr_values[1], attr_values[2]) /* region */ );

	OTF2_EvtWriter_Leave(thread_info->evt_writer, attribute_list,
			     gettimestamp(attr_values[0]), getRegion(attr_values[1], attr_values[2]) /* region */ );
        free(attr_values);
    }

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


    

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

    //int ezt_otf2_register_attribute(char* name, OTF2_Type type);


    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 );
    //return EXIT_SUCCESS;
}
