//
// Copyright (c) 2017 The Khronos Group Inc.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef _TESTNONUNIFORMWORKGROUP_H
#define _TESTNONUNIFORMWORKGROUP_H

#include "procs.h"
#include <vector>
#include "tools.h"
#include <algorithm>

#define MAX_SIZE_OF_ALLOCATED_MEMORY (400*1024*1024)

#define NUMBER_OF_REGIONS 8

#define BUILD_CL_STD_2_0 "-cl-std=CL2.0"

#define MAX_DIMS 3

// This structure reflects data received from kernel.
typedef struct _DataContainerAttrib
{
  cl_ulong get_global_size[MAX_DIMS];
  cl_ulong get_global_offset[MAX_DIMS];
  cl_ulong get_local_size[MAX_DIMS];
  cl_ulong get_enqueued_local_size[MAX_DIMS];
  cl_ulong get_global_id[MAX_DIMS];
  cl_ulong get_local_id[MAX_DIMS];
  cl_ulong get_group_id[MAX_DIMS];
  cl_ulong get_num_groups[MAX_DIMS];
  cl_ulong get_work_dim;
  cl_ushort test_local_barrier_result_bool;
  cl_ushort test_global_barrier_result_bool;
  cl_ushort test_local_atomic_result_value;
}DataContainerAttrib;

// Describes range of testing.
namespace Range {
  enum RangeEnum {
    BASIC = (1 << 0),
    BARRIERS = (1 << 1),
    ATOMICS = (1 << 2),

    ALL = Range::BASIC | Range::BARRIERS | Range::ATOMICS
  };
}

std::string showArray (const size_t *arr, cl_uint dims);

// Main class responsible for testing
class TestNonUniformWorkGroup {
public:

  TestNonUniformWorkGroup (const cl_device_id &device, const cl_context &context,
    const cl_command_queue &queue, const cl_uint dims, const size_t *globalSize,
    const size_t *localSize, const size_t *buffersSize, const size_t *globalWorkOffset,
    const size_t *reqdWorkGroupSize=NULL);

  ~TestNonUniformWorkGroup ();

  static size_t getMaxLocalWorkgroupSize (const cl_device_id &device);
  static void setMaxLocalWorkgroupSize (size_t workGroupSize) {
    TestNonUniformWorkGroup::_maxLocalWorkgroupSize = workGroupSize;
  }
  static void enableStrictMode (bool state);

  void setTestRange (int range) {_testRange = range;}
  int prepareDevice ();
  int verifyResults ();
  int runKernel ();

private:
  size_t _globalSize[MAX_DIMS];
  size_t _localSize[MAX_DIMS];
  size_t _globalWorkOffset[MAX_DIMS];
  bool _globalWorkOffset_IsNull;
  size_t _enqueuedLocalSize[MAX_DIMS];
  bool _localSize_IsNull;
  size_t _reqdWorkGroupSize[MAX_DIMS];
  static size_t _maxLocalWorkgroupSize;
  size_t _maxWorkItemSizes[MAX_DIMS];
  size_t _numOfGlobalWorkItems; // in global work group
  const cl_device_id _device;
  const cl_context _context;
  const cl_command_queue _queue;
  const cl_uint _dims;

  int _testRange;

  std::vector<DataContainerAttrib> _resultsRegionArray;
  std::vector<DataContainerAttrib> _referenceRegionArray;
  cl_uint _globalAtomicTestValue;

  clProgramWrapper _program;
  clKernelWrapper _testKernel;

  Error::ErrorClass _err;

  TestNonUniformWorkGroup ();

  static bool _strictMode;
  void setLocalWorkgroupSize (const size_t *globalSize, const size_t *localSize);
  void setGlobalWorkgroupSize (const size_t *globalSize);
  void verifyData (DataContainerAttrib * reference, DataContainerAttrib * results, short regionNumber);
  void calculateExpectedValues ();
  void showTestInfo ();
  size_t adjustLocalArraySize(size_t localArraySize);
};

// Class responsible for running subtest scenarios in test function
class SubTestExecutor {
public:
  SubTestExecutor(const cl_device_id &device, const cl_context &context, const cl_command_queue &queue)
    : _device (device), _context (context), _queue (queue), _failCounter (0), _overallCounter (0) {}

  void runTestNonUniformWorkGroup (const cl_uint dims, const size_t *globalSize,
    const size_t *localSize, int range);

  void runTestNonUniformWorkGroup (const cl_uint dims, const size_t *globalSize,
    const size_t *localSize, const size_t *globalWorkOffset,
    const size_t *reqdWorkGroupSize, int range);

  int calculateWorkGroupSize(size_t &maxWgSize, int testRange);
  int status();

private:
  SubTestExecutor();
  const cl_device_id _device;
  const cl_context _context;
  const cl_command_queue _queue;
  unsigned int _failCounter;
  unsigned int _overallCounter;
};

#endif // _TESTNONUNIFORMWORKGROUP_H
