// RUN: %libomptarget-compile-run-and-check-generic
// REQUIRES: gpu

#include <assert.h>
#include <stdio.h>

#define TEST_VAL 5

#pragma omp declare target indirect
int func_a(int x) { return x + 1; }
int func_b(int x) { return x + 2; }
int func_c(int x) { return x + 3; }
int func_d(int x) { return x * 2; }
int func_e(int x) { return x * 3; }
int func_f(int x) { return x * 4; }
#pragma omp end declare target

void test_array_explicit_mapping() {
  int (*local_fptr_array[3])(int) = {func_a, func_b, func_c};

  int results[3];
  int expected[3];

  expected[0] = func_a(TEST_VAL);
  expected[1] = func_b(TEST_VAL);
  expected[2] = func_c(TEST_VAL);

#pragma omp target map(local_fptr_array, local_fptr_array[0 : 3])              \
    map(from : results)
  {
    for (int i = 0; i < 3; i++) {
      results[i] = local_fptr_array[i](TEST_VAL);
    }
  }

  for (int i = 0; i < 3; i++) {
    assert(results[i] == expected[i] &&
           "Error: local array function pointer returned incorrect value on "
           "device");
  }

  // Change function pointers and re-test
  local_fptr_array[0] = func_d;
  local_fptr_array[1] = func_e;
  local_fptr_array[2] = func_f;

  expected[0] = func_d(TEST_VAL);
  expected[1] = func_e(TEST_VAL);
  expected[2] = func_f(TEST_VAL);

#pragma omp target map(local_fptr_array, local_fptr_array[0 : 3])              \
    map(from : results)
  {
    for (int i = 0; i < 3; i++) {
      results[i] = local_fptr_array[i](TEST_VAL);
    }
  }

  for (int i = 0; i < 3; i++) {
    assert(results[i] == expected[i] &&
           "Error: local array function pointer returned incorrect value on "
           "device after update");
  }
}

struct with_fptr_array {
  int buffer;
  int (*fptrs[3])(int);
};

void test_struct_containing_array() {
  struct with_fptr_array val = {.buffer = 0, .fptrs = {func_a, func_b, func_c}};

  int results[3];
  int expected[3];

  expected[0] = func_a(TEST_VAL);
  expected[1] = func_b(TEST_VAL);
  expected[2] = func_c(TEST_VAL);

#pragma omp target map(val, val.fptrs[0 : 3]) map(from : results)
  {
    results[0] = val.fptrs[0](TEST_VAL);
    results[1] = val.fptrs[1](TEST_VAL);
    results[2] = val.fptrs[2](TEST_VAL);
  }

  for (int i = 0; i < 3; i++) {
    assert(results[i] == expected[i] &&
           "Error: struct array function pointer returned incorrect value");
  }

  // Update and re-test
  val.fptrs[0] = func_d;
  val.fptrs[1] = func_e;
  val.fptrs[2] = func_f;

  expected[0] = func_d(TEST_VAL);
  expected[1] = func_e(TEST_VAL);
  expected[2] = func_f(TEST_VAL);

#pragma omp target map(val, val.fptrs[0 : 3]) map(from : results)
  {
    results[0] = val.fptrs[0](TEST_VAL);
    results[1] = val.fptrs[1](TEST_VAL);
    results[2] = val.fptrs[2](TEST_VAL);
  }

  for (int i = 0; i < 3; i++) {
    assert(results[i] == expected[i] &&
           "Error: struct array function pointer returned incorrect value "
           "after update");
  }
}

int main() {
  test_array_explicit_mapping();
  test_struct_containing_array();

  // CHECK: PASS
  printf("PASS\n");
  return 0;
}
