/*
 * COPYRIGHT
 * ---------
 * Copyright (C) 2015-2020, OpenACC-Standard.Org. All rights reserved.
 *
 * LICENSE
 * -------
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of the OpenACC-Standard.Org nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Basic sample profile callback routine for OpenACC
 * Showing how to initialize and get callbacks.
 */

/*
 * define TARGET_WIN to build for windows
 * define STATICLIB to build a static library; else, this builds as for a shared
 * object
 */

#if defined TARGET_WIN
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <openacc.h>
#include "acc_prof.h"

/*
 * This routine gets called back for each and every event
 * Depending on the event type, it does (slightly) different things
 */

static void
callback(acc_prof_info *profinfo, acc_event_info *eventinfo,
         acc_api_info *apiinfo)
{
  char *t;
  acc_data_event_info *datainfo = NULL;
  acc_launch_event_info *launchinfo = NULL;
  void *check_tool_info = (void *)0;
  void *set_tool_info = (void *)0;
  switch (profinfo->event_type) {
  case acc_ev_device_init_start:
    t = "Init Start";
    set_tool_info = (void *)1;
    break;
  case acc_ev_device_init_end:
    t = "Init End";
    check_tool_info = (void *)1;
    break;
  case acc_ev_device_shutdown_start:
    t = "Shutdown Start";
    set_tool_info = (void *)2;
    break;
  case acc_ev_device_shutdown_end:
    t = "Shutdown End";
    check_tool_info = (void *)2;
    break;
  case acc_ev_runtime_shutdown:
    t = "Done";
    break;
  case acc_ev_enqueue_launch_start:
    t = "Enqueue Launch Start";
    set_tool_info = (void *)3;
    break;
  case acc_ev_enqueue_launch_end:
    t = "Enqueue Launch End";
    check_tool_info = (void *)3;
    break;
  case acc_ev_enqueue_upload_start:
    t = "Enqueue Upload Start";
    set_tool_info = (void *)4;
    break;
  case acc_ev_enqueue_upload_end:
    t = "Enqueue Upload End";
    check_tool_info = (void *)4;
    break;
  case acc_ev_enqueue_download_start:
    t = "Enqueue Download Start";
    set_tool_info = (void *)5;
    break;
  case acc_ev_enqueue_download_end:
    t = "Enqueue Download End";
    check_tool_info = (void *)5;
    break;
  case acc_ev_wait_start:
    t = "Wait Start";
    set_tool_info = (void *)6;
    break;
  case acc_ev_wait_end:
    t = "Wait End";
    check_tool_info = (void *)6;
    break;
  case acc_ev_compute_construct_start:
    t = "Compute Construct Start";
    set_tool_info = (void *)8;
    break;
  case acc_ev_compute_construct_end:
    t = "Compute Construct End";
    check_tool_info = (void *)8;
    break;
  case acc_ev_update_start:
    t = "Update Start";
    set_tool_info = (void *)9;
    break;
  case acc_ev_update_end:
    t = "Update End";
    check_tool_info = (void *)9;
    break;
  case acc_ev_enter_data_start:
    t = "Enter Data Start";
    set_tool_info = (void *)10;
    break;
  case acc_ev_enter_data_end:
    t = "Enter Data End";
    check_tool_info = (void *)10;
    break;
  case acc_ev_exit_data_start:
    t = "Exit Data Start";
    set_tool_info = (void *)11;
    break;
  case acc_ev_exit_data_end:
    t = "Exit Data End";
    check_tool_info = (void *)11;
    break;
  case acc_ev_create:
    t = "Create";
    break;
  case acc_ev_delete:
    t = "Delete";
    break;
  case acc_ev_alloc:
    t = "Alloc";
    break;
  case acc_ev_free:
    t = "Free";
    break;
  default:
    t = NULL;
    break;
  }
  if (t) {
    printf("%s", t);
  } else {
    printf("Event Type=%d", (int)profinfo->event_type);
  }
  if (eventinfo->other_event.implicit)
    printf(" (implicit)");

  switch (profinfo->device_type) {
  case acc_device_default:
    t = "Default";
    break;
  case acc_device_host:
    t = "Host";
    break;
  case acc_device_not_host:
    t = "Nothost";
    break;
  case acc_device_nvidia:
    t = "NVIDIA";
    break;
  default:
    t = NULL;
    break;
  }

  if (t) {
    printf(" %s", t);
  } else {
    printf(" Device Type=%d", (int)profinfo->device_type);
  }

  printf(" device_number=%d", profinfo->device_number);
  printf(" thread_id=%d", profinfo->thread_id);
  printf(" async=%ld", profinfo->async);
  printf(" async_queue=%ld", profinfo->async_queue);
  printf("\n");
  if (profinfo->src_file)
    printf("  src_file=%s\n", profinfo->src_file);
  if (profinfo->func_name)
    printf("  func_name=%s", profinfo->func_name);
  if (profinfo->line_no && profinfo->end_line_no) {
    printf(" lines=%d:%d", profinfo->line_no, profinfo->end_line_no);
    if (profinfo->line_no > profinfo->end_line_no)
      printf(" ********");
  } else if (profinfo->line_no) {
    printf(" line=%d", profinfo->line_no);
  }
  if (profinfo->func_line_no && profinfo->func_end_line_no) {
    printf(" func_lines=%d:%d", profinfo->func_line_no,
           profinfo->func_end_line_no);
    if (profinfo->func_line_no > profinfo->func_end_line_no)
      printf(" ********");
  } else if (profinfo->func_line_no) {
    printf(" func_line=%d", profinfo->func_line_no);
  }
  printf("\n");

  switch (profinfo->event_type) {
  case acc_ev_enqueue_launch_start:
  case acc_ev_enqueue_launch_end:
    launchinfo = (acc_launch_event_info *)eventinfo;
    break;
  case acc_ev_enqueue_upload_start:
  case acc_ev_enqueue_upload_end:
  case acc_ev_enqueue_download_start:
  case acc_ev_enqueue_download_end:
  case acc_ev_create:
  case acc_ev_delete:
  case acc_ev_alloc:
  case acc_ev_free:
    datainfo = (acc_data_event_info *)eventinfo;
    break;
  default:
    break;
  }

  if (launchinfo) {
    if (launchinfo->valid_bytes >
            offsetof(acc_launch_event_info, kernel_name) &&
        launchinfo->kernel_name)
      printf(" kernel=%s", launchinfo->kernel_name);
    if (launchinfo->valid_bytes > offsetof(acc_launch_event_info, num_gangs) &&
        launchinfo->num_gangs)
      printf(" num_gangs=%ld", launchinfo->num_gangs);
    if (launchinfo->valid_bytes >
            offsetof(acc_launch_event_info, num_workers) &&
        launchinfo->num_workers)
      printf(" num_workers=%ld", launchinfo->num_workers);
    if (launchinfo->valid_bytes >
            offsetof(acc_launch_event_info, vector_length) &&
        launchinfo->vector_length)
      printf(" vector_length=%ld", launchinfo->vector_length);
    if (launchinfo->valid_bytes > offsetof(acc_launch_event_info, grid)) {
      if (launchinfo->grid[0] || launchinfo->grid[1] || launchinfo->grid[2]) {
        printf(" grid=%ld", launchinfo->grid[0]);
        if (launchinfo->grid[1] > 1 || launchinfo->grid[2] > 1) {
          printf("x%ld", launchinfo->grid[1]);
          if (launchinfo->grid[2] > 1)
            printf("x%ld", launchinfo->grid[2]);
        }
      }
    }
    if (launchinfo->valid_bytes > offsetof(acc_launch_event_info, block)) {
      if (launchinfo->block[0] || launchinfo->block[1] ||
          launchinfo->block[2]) {
        printf(" block=%ld", launchinfo->block[0]);
        if (launchinfo->block[1] > 1 || launchinfo->block[2] > 1) {
          printf("x%ld", launchinfo->block[1]);
          if (launchinfo->block[2] > 1)
            printf("x%ld", launchinfo->block[2]);
        }
      }
    }
    if (launchinfo->valid_bytes > offsetof(acc_launch_event_info, smem))
      printf(" smem=%ld", launchinfo->smem);
    printf("\n");
  }
  if (datainfo) {
    printf(" ");
    if (datainfo->valid_bytes > offsetof(acc_data_event_info, var_name) &&
        datainfo->var_name)
      printf(" name=%s", datainfo->var_name);
    if (datainfo->valid_bytes > offsetof(acc_data_event_info, bytes) &&
        datainfo->bytes)
      printf(" bytes=%ld", datainfo->bytes);
    if (datainfo->valid_bytes > offsetof(acc_data_event_info, transfers) &&
        datainfo->transfers)
      printf(" transfers=%ld", datainfo->transfers);
    printf("\n");
  }

  if (apiinfo) {
    switch (apiinfo->device_api) {
    case acc_devapi_native:
      t = "native";
      break;
    case acc_devapi_cuda:
      t = "cuda";
      break;
    case acc_devapi_opencl:
      t = "opencl";
      break;
    default:
      t = NULL;
      break;
    }
    if (t) {
      printf("  api=%s", t);
    } else {
      printf("  api=%d", (int)apiinfo->device_api);
    }
    if (apiinfo->valid_bytes > offsetof(acc_api_info, device_handle) &&
        apiinfo->device_handle)
      printf(" device=%p", apiinfo->device_handle);
    if (apiinfo->valid_bytes > offsetof(acc_api_info, context_handle) &&
        apiinfo->context_handle)
      printf(" context=%p", apiinfo->context_handle);
    if (apiinfo->valid_bytes > offsetof(acc_api_info, async_handle) &&
        apiinfo->async_handle)
      printf(" async=%p", apiinfo->async_handle);
    printf("\n");
  }
  if (eventinfo) {
    if (set_tool_info) {
      eventinfo->other_event.tool_info = set_tool_info;
    } else if (check_tool_info) {
      if (eventinfo->other_event.tool_info != check_tool_info) {
        printf("*** tool_info = %p, expecting %p\n",
               eventinfo->other_event.tool_info, check_tool_info);
      }
    }
  } else if (check_tool_info) {
    printf("*** no eventinfo struct, expecting tool_info=%p\n",
           check_tool_info);
  } else if (set_tool_info) {
    printf("*** no eventinfo struct, setting tool_info=%p\n", set_tool_info);
  }
} /* callback */

/*
 * If loaded into a dynamic library, the runtime initialization will
 * call this routine automatically.  It registers the callback routine
 * for all the events.
 */

void
acc_register_library(acc_prof_reg reg, acc_prof_reg unreg,
                     acc_prof_lookup lookup)
{
  reg(acc_ev_device_init_start, callback, acc_reg);
  reg(acc_ev_device_init_end, callback, acc_reg);
  reg(acc_ev_device_shutdown_start, callback, acc_reg);
  reg(acc_ev_device_shutdown_end, callback, acc_reg);
  reg(acc_ev_runtime_shutdown, callback, acc_reg);
  reg(acc_ev_enqueue_launch_start, callback, acc_reg);
  reg(acc_ev_enqueue_launch_end, callback, acc_reg);
  reg(acc_ev_enqueue_upload_start, callback, acc_reg);
  reg(acc_ev_enqueue_upload_end, callback, acc_reg);
  reg(acc_ev_enqueue_download_start, callback, acc_reg);
  reg(acc_ev_enqueue_download_end, callback, acc_reg);
  reg(acc_ev_wait_start, callback, acc_reg);
  reg(acc_ev_wait_end, callback, acc_reg);
  reg(acc_ev_compute_construct_start, callback, acc_reg);
  reg(acc_ev_compute_construct_end, callback, acc_reg);
  reg(acc_ev_update_start, callback, acc_reg);
  reg(acc_ev_update_end, callback, acc_reg);
  reg(acc_ev_enter_data_start, callback, acc_reg);
  reg(acc_ev_enter_data_end, callback, acc_reg);
  reg(acc_ev_exit_data_start, callback, acc_reg);
  reg(acc_ev_exit_data_end, callback, acc_reg);
  reg(acc_ev_create, callback, acc_reg);
  reg(acc_ev_delete, callback, acc_reg);
  reg(acc_ev_alloc, callback, acc_reg);
  reg(acc_ev_free, callback, acc_reg);

} /* acc_register_library */

#ifdef STATICLIB
/*
 * If not loaded into a static library, this routine
 * can be called by the application to register the callback routine
 * for all the events
 */

extern void acc_prof_register(acc_event_t, acc_prof_callback_t, int);
extern void acc_prof_unregister(acc_event_t, acc_prof_callback_t, int);

void
register_callbacks()
{
  acc_register_library(acc_prof_register, acc_prof_unregister);
} /* register_callbacks */
#endif
