/*
 *     Copyright (c) 2024, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 *
 */

// NVHPC CUDA idx routines

#include "nvhpc_acc_device_type.h"

__DEVICE void __pgi_print_string(const signed char *p)
{
    printf("%s", p);
}

__DEVICE void __pgi_print_stringn(const char *p)
{
    printf("%s\n", p);
}

__DEVICE int __pgi_blockidx(int j)
{
    if (j == 3) {
        return blockIdx.z;
    }
    if (j == 2) {
        return blockIdx.y;
    }
    if (j == 1) {
        return blockIdx.x;
    }
    return 0;
}

__DEVICE int __pgi_threadidx(int j)
{
    if (j == 3) {
        return threadIdx.z;
    }
    if (j == 2) {
        return threadIdx.y;
    }
    if (j == 1) {
        return threadIdx.x;
    }
    return 0;
}

__DEVICE int __pgi_numthreads(int j)
{
    if (j == 3) {
        return blockDim.z;
    }
    if (j == 2) {
        return blockDim.y;
    }
    if (j == 1) {
        return blockDim.x;
    }
    return 0;
}

__DEVICE int __pgi_numblocks(int j)
{
    if (j == 3) {
        return gridDim.z;
    }
    if (j == 2) {
        return gridDim.y;
    }
    if (j == 1) {
        return gridDim.x;
    }
    return 0;
}

__DEVICE void __pgi_syncwarp()
{
    __syncwarp();
}

__DEVICE int acc_on_device(int device_type)
{
  // Since this file is being compiled for device code, it returns
  // true for all conditions which leads to this being executed.
  // This is consistent with accel-cg's compile time evaluation whose
  // comment notes that it returns 1 for the following:
  //   acc_on_device(acc_device_nvidia) or
  //   acc_on_device(acc_device_default) or
  //   acc_on_device(acc_device_not_host)
  // The OpenACC spec specifically says that behavior for "acc_device_default"
  // is undefined - thus it seems OK to continue returning 1 for that case
  // as accel-cg has been doing.
  if (device_type == nvhpc_acc_device_default ||
      device_type == nvhpc_acc_device_not_host ||
      device_type == nvhpc_acc_device_nvidia) {
    return 1;
  }
  return 0;
}

// Identical to acc_on_device, the definition of acc_on_device_ is
// intended for supporting user programs that don't "use openacc" module
// and may or may not include the header file "openacc_lib.h".
__DEVICE int acc_on_device_(int device_type)
{
  return acc_on_device(device_type);
}

#if !defined(_WIN64)
__DEVICE void __pgi_assert_fail(const char *__assertion, const char *__file, unsigned int __line, const char *__function)
{
    __assert_fail(__assertion, __file, __line, __function);
}
#else
__DEVICE void __pgi_wassert(const wchar_t *__assertion, const wchar_t *__file, unsigned __line)
{
  _wassert(__assertion, __file, __line);
}
#endif

__DEVICE signed char *pgf90_stop08a(signed char *exit_status, const signed char *str, size_t len)
{
    if (str) {
        if (*str) {
            printf("FORTRAN STOP: ");
            while (len--)
                printf("%c", *str++);
            printf(": ");
        }
    } else {
        int fstop = *((int *)exit_status);
        printf("FORTRAN STOP: %d: ", fstop);
    }
    printf("Block (%d,%d,%d), Thread (%d,%d,%d)\n",
           blockIdx.x + 1, blockIdx.y + 1, blockIdx.z + 1,
           threadIdx.x + 1, threadIdx.y + 1, threadIdx.z + 1);
    int FORTRAN_STOP_STATEMENT = 0;
    assert(FORTRAN_STOP_STATEMENT);

    return (signed char *)0;
}

__DEVICE signed char *pgf90_stop08(signed char *exit_status, const signed char *str, size_t len)
{
    return pgf90_stop08a(exit_status, str, len);
}

__DEVICE void __pgi_fatal(const char *str)
{
    printf("%s\n", str);
    printf("Block (%d,%d,%d), Thread (%d,%d,%d)\n",
           blockIdx.x + 1, blockIdx.y + 1, blockIdx.z + 1,
           threadIdx.x + 1, threadIdx.y + 1, threadIdx.z + 1);
    assert(0);
}
