/*=========================================================================
 *
 *  Copyright UMC Utrecht and contributors
 *
 *  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.txt
 *
 *  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.
 *
 *=========================================================================*/
#include "itkOpenCLContext.h"
#include "itkOpenCLKernels.h"
#include "itkOpenCLProfilingTimeProbe.h"

#include <iostream>
#include <fstream>

#include "itksys/MD5.h"
#include "itkOpenCLMacro.h"

namespace itk
{
// static variable initialization
OpenCLContext::Pointer OpenCLContext::m_Instance = nullptr;

//------------------------------------------------------------------------------
// Return the single instance of the OpenCLContext
OpenCLContext::Pointer
OpenCLContext::GetInstance()
{
  if (!OpenCLContext::m_Instance)
  {
    // Try the factory first
    OpenCLContext::m_Instance = ObjectFactory<Self>::Create();
    // if the factory did not provide one, then create it here
    if (!OpenCLContext::m_Instance)
    {
      // For the windows OS, use a special output window
      OpenCLContext::m_Instance = new OpenCLContext;
      // Remove extra reference from construction.
      OpenCLContext::m_Instance->UnRegister();
    }
  }
  // Return the instance
  return OpenCLContext::m_Instance;
}


//------------------------------------------------------------------------------
void
OpenCLContext::SetInstance(OpenCLContext * instance)
{
  if (OpenCLContext::m_Instance == instance)
  {
    return;
  }
  OpenCLContext::m_Instance = instance;
}


//------------------------------------------------------------------------------
// This just calls GetInstance
OpenCLContext::Pointer
OpenCLContext::New()
{
  return GetInstance();
}


//------------------------------------------------------------------------------
class OpenCLContextPimpl
{
public:
  OpenCLContextPimpl()
    : id(0)
    , is_created(false)
    , last_error(CL_SUCCESS)
  {}

  ~OpenCLContextPimpl()
  {
    // Release the command queues for the context.
    command_queue = OpenCLCommandQueue();
    default_command_queue = OpenCLCommandQueue();

    // Release the context.
    if (is_created)
    {
      clReleaseContext(id);
    }
  }


public:
  cl_context         id;
  bool               is_created;
  OpenCLCommandQueue command_queue;
  OpenCLCommandQueue default_command_queue;
  OpenCLDevice       default_device;
  cl_int             last_error;
};

//------------------------------------------------------------------------------
std::string
GetOpenCLDebugFileName(const std::string & source)
{
  // Create unique filename based on the source code
  const std::size_t sourceSize = source.size();
  itksysMD5 *       md5 = itksysMD5_New();

  itksysMD5_Initialize(md5);
  itksysMD5_Append(md5, (unsigned char *)source.c_str(), sourceSize);
  const std::size_t DigestSize = 32u;
  char              Digest[DigestSize];
  itksysMD5_FinalizeHex(md5, Digest);
  const std::string hex(Digest, DigestSize);

  // construct the name
  std::string fileName(itk::OpenCLKernelsDebugDirectory);
  fileName.append("/ocl-");
  fileName.append(hex);
  fileName.append(".cl");

  // free resources
  itksysMD5_Delete(md5);

  return fileName;
}


//------------------------------------------------------------------------------
OpenCLContext::OpenCLContext()
  : d_ptr(std::make_unique<OpenCLContextPimpl>())
{}

//------------------------------------------------------------------------------
// Destructor has to be in cxx, otherwise compiler will print warning messages.
OpenCLContext::~OpenCLContext() {}

//------------------------------------------------------------------------------
bool
OpenCLContext::IsCreated() const
{
  ITK_OPENCL_D(const OpenCLContext);
  return d->is_created;
}


//------------------------------------------------------------------------------
// This callback function will be used by the OpenCL implementation to report
// information on errors during context creation as well as errors that occur
// at runtime in this context. This callback function may be called
// asynchronously by the OpenCL implementation. It is the application's
// responsibility to ensure that the callback function is thread-safe.
void CL_CALLBACK
     opencl_context_notify(const char * errinfo, const void * /*private_info*/, std::size_t /*cb*/, void * /*user_data*/)
{
  itkOpenCLErrorMacroGeneric(<< "OpenCL error during context creation or runtime:\n" << errinfo);
}


//------------------------------------------------------------------------------
bool
OpenCLContext::Create(const OpenCLDevice::DeviceType type)
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->is_created)
  {
    return true;
  }

  // The "cl_khr_icd" extension says that a null platform cannot
  // be supplied to OpenCL any more, so find the first platform
  // that has devices that match "type".
  const std::list<OpenCLDevice> devices = OpenCLDevice::GetDevices(type);
  this->CreateContext(devices, d);

  // Check if OpenCL context has been created
  d->is_created = (d->id != 0);
  if (!d->is_created)
  {
    itkOpenCLWarningMacro(<< "OpenCLContext::Create(type:" << int(type) << "):" << this->GetErrorName(d->last_error));
  }
  else
  {
    this->SetUpProfiling();
  }

  return d->is_created;
}


//------------------------------------------------------------------------------
bool
OpenCLContext::Create(const std::list<OpenCLDevice> & devices)
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->is_created)
  {
    return true;
  }
  if (devices.empty())
  {
    this->ReportError(CL_INVALID_VALUE, __FILE__, __LINE__, ITK_LOCATION);
    return false;
  }
  std::vector<cl_device_id> devs;
  for (std::list<OpenCLDevice>::const_iterator dev = devices.begin(); dev != devices.end(); ++dev)
  {
    devs.push_back(dev->GetDeviceId());
  }

  cl_platform_id        platform = devices.front().GetPlatform().GetPlatformId();
  cl_context_properties props[] = { CL_CONTEXT_PLATFORM, intptr_t(platform), 0 };
  d->id = clCreateContext(props, devs.size(), &devs[0], opencl_context_notify, 0, &(d->last_error));
  d->is_created = (d->id != 0);
  if (!d->is_created)
  {
    itkOpenCLWarningMacro(<< "OpenCLContext::Create:" << this->GetErrorName(d->last_error));
  }
  else
  {
    this->SetUpProfiling();
  }

  return d->is_created;
}


//------------------------------------------------------------------------------
bool
OpenCLContext::Create(const OpenCLContext::CreateMethod method)
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->is_created)
  {
    return true;
  }

  if (method == OpenCLContext::Default)
  {
    std::list<OpenCLDevice> devices;
    const OpenCLDevice      gpuDevice = OpenCLDevice::GetMaximumFlopsDevice(OpenCLDevice::GPU);
    if (!gpuDevice.IsNull())
    {
      devices.push_back(gpuDevice);
      this->CreateContext(devices, d);
    }
    else
    {
      const OpenCLDevice cpuDevice = OpenCLDevice::GetMaximumFlopsDevice(OpenCLDevice::CPU);
      if (!cpuDevice.IsNull())
      {
        devices.push_back(cpuDevice);
        this->CreateContext(devices, d);
      }
      else
      {
        const OpenCLDevice acceleratorDevice = OpenCLDevice::GetMaximumFlopsDevice(OpenCLDevice::Accelerator);
        if (!acceleratorDevice.IsNull())
        {
          devices.push_back(acceleratorDevice);
          this->CreateContext(devices, d);
        }
        else
        {
          itkGenericExceptionMacro(<< "Unable to create OpenCLContext with method MultipleMaximumFlopsDevices.");
        }
      }
    }
  }
  else if (method == OpenCLContext::DevelopmentSingleMaximumFlopsDevice)
  {
    OpenCLDevice device;

#ifdef OPENCL_USE_INTEL
#  ifdef OPENCL_USE_INTEL_GPU
    device = OpenCLDevice::GetMaximumFlopsDeviceByVendor(OpenCLDevice::GPU, OpenCLPlatform::Intel);
#  elif OPENCL_USE_INTEL_CPU
    device = OpenCLDevice::GetMaximumFlopsDeviceByVendor(OpenCLDevice::CPU, OpenCLPlatform::Intel);
#  else
    itkGenericExceptionMacro(<< "Unknown Intel OpenCL platform.");
#  endif

    // NVidia platform
#elif OPENCL_USE_NVIDIA
    device = OpenCLDevice::GetMaximumFlopsDeviceByVendor(OpenCLDevice::GPU, OpenCLPlatform::NVidia);
    // AMD platform
#elif OPENCL_USE_AMD
#  ifdef OPENCL_USE_AMD_GPU
    device = OpenCLDevice::GetMaximumFlopsDeviceByVendor(OpenCLDevice::GPU, OpenCLPlatform::AMD);
#  elif OPENCL_USE_AMD_CPU
    device = OpenCLDevice::GetMaximumFlopsDeviceByVendor(OpenCLDevice::CPU, OpenCLPlatform::AMD);
#  else
    itkGenericExceptionMacro(<< "Unknown AMD OpenCL platform.");
#  endif

    // Unknown platform
#else
    itkGenericExceptionMacro(<< "Not supported OpenCL platform by OpenCLContext.");
#endif

    std::list<OpenCLDevice> devices;
    devices.push_back(device);
    this->CreateContext(devices, d);
  }
  else if (method == OpenCLContext::DevelopmentMultipleMaximumFlopsDevices)
  {
    std::list<OpenCLDevice> devices;
    // Intel platform
#ifdef OPENCL_USE_INTEL
#  ifdef OPENCL_USE_INTEL_GPU
    devices = OpenCLDevice::GetDevices(OpenCLDevice::GPU, OpenCLPlatform::Intel);
#  elif OPENCL_USE_INTEL_CPU
    devices = OpenCLDevice::GetDevices(OpenCLDevice::CPU, OpenCLPlatform::Intel);
#  else
    itkGenericExceptionMacro(<< "Unknown Intel OpenCL platform.");
#  endif

    // NVidia platform
#elif OPENCL_USE_NVIDIA
    devices = OpenCLDevice::GetDevices(OpenCLDevice::GPU, OpenCLPlatform::NVidia);
    // AMD platform
#elif OPENCL_USE_AMD
#  ifdef OPENCL_USE_AMD_GPU
    devices = OpenCLDevice::GetDevices(OpenCLDevice::GPU, OpenCLPlatform::AMD);
#  elif OPENCL_USE_AMD_CPU
    devices = OpenCLDevice::GetDevices(OpenCLDevice::CPU, OpenCLPlatform::AMD);
#  else
    itkGenericExceptionMacro(<< "Unknown AMD OpenCL platform.");
#  endif

    // Unknown platform
#else
    itkGenericExceptionMacro(<< "Not supported OpenCL platform by OpenCLContext.");
#endif
    this->CreateContext(devices, d);
  }
  else if (method == OpenCLContext::SingleMaximumFlopsDevice)
  {
    std::list<OpenCLDevice> devices;
    const OpenCLDevice      device = OpenCLDevice::GetMaximumFlopsDevice(OpenCLDevice::GPU);
    devices.push_back(device);
    this->CreateContext(devices, d);
  }
  else if (method == OpenCLContext::MultipleMaximumFlopsDevices)
  {
    std::list<OpenCLDevice> devices;
    devices = OpenCLDevice::GetMaximumFlopsDevices(OpenCLDevice::GPU);
    if (!devices.empty())
    {
      this->CreateContext(devices, d);
    }
    else
    {
      devices = OpenCLDevice::GetMaximumFlopsDevices(OpenCLDevice::CPU);
      if (!devices.empty())
      {
        this->CreateContext(devices, d);
      }
      else
      {
        devices = OpenCLDevice::GetMaximumFlopsDevices(OpenCLDevice::Accelerator);
        if (!devices.empty())
        {
          this->CreateContext(devices, d);
        }
        else
        {
          itkGenericExceptionMacro(<< "Unable to create OpenCLContext with method MultipleMaximumFlopsDevices.");
        }
      }
    }
  }

  // Check if OpenCL context has been created
  d->is_created = (d->id != 0);
  if (!d->is_created)
  {
    itkOpenCLWarningMacro(<< "OpenCLContext::Create(method:" << int(method)
                          << "):" << this->GetErrorName(d->last_error));
  }
  else
  {
    this->SetUpProfiling();
  }

  return d->is_created;
}


//------------------------------------------------------------------------------
bool
OpenCLContext::Create(const OpenCLPlatform & platfrom, const OpenCLDevice::DeviceType type)
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->is_created)
  {
    return true;
  }

  this->CreateContext(platfrom, type, d);

  // Check if OpenCL context has been created
  d->is_created = (d->id != 0);
  if (!d->is_created)
  {
    itkOpenCLWarningMacro(<< "OpenCLContext::Create(platfrom id:" << platfrom.GetPlatformId()
                          << "):" << this->GetErrorName(d->last_error));
  }
  else
  {
    this->SetUpProfiling();
  }

  return d->is_created;
}


//------------------------------------------------------------------------------
bool
OpenCLContext::Create()
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->is_created)
  {
    return true;
  }

  OpenCLPlatform platform;

#ifdef OPENCL_USE_INTEL
#  ifdef OPENCL_USE_INTEL_GPU
  platform = OpenCLPlatform::GetPlatform(OpenCLPlatform::Intel);
  this->CreateContext(platform, OpenCLDevice::GPU, d);
#  elif OPENCL_USE_INTEL_CPU
  platform = OpenCLPlatform::GetPlatform(OpenCLPlatform::Intel);
  this->CreateContext(platform, OpenCLDevice::CPU, d);
#  else
  itkGenericExceptionMacro(<< "Unknown Intel OpenCL platform.");
#  endif

  // NVidia platform
#elif OPENCL_USE_NVIDIA
  platform = OpenCLPlatform::GetPlatform(OpenCLPlatform::NVidia);
  this->CreateContext(platform, OpenCLDevice::GPU, d);
  // AMD platform
#elif OPENCL_USE_AMD
#  ifdef OPENCL_USE_AMD_GPU
  platform = OpenCLPlatform::GetPlatform(OpenCLPlatform::AMD);
  this->CreateContext(platform, OpenCLDevice::GPU, d);
#  elif OPENCL_USE_AMD_CPU
  platform = OpenCLPlatform::GetPlatform(OpenCLPlatform::AMD);
  this->CreateContext(platform, OpenCLDevice::CPU, d);
#  else
  itkGenericExceptionMacro(<< "Unknown AMD OpenCL platform.");
#  endif

  // Unknown platform
#else
  itkGenericExceptionMacro(<< "Not supported OpenCL platform by OpenCLContext.");
#endif

  // Check if OpenCL context has been created
  d->is_created = (d->id != 0);
  if (!d->is_created)
  {
    itkOpenCLWarningMacro(<< "OpenCLContext::Create():" << this->GetErrorName(d->last_error));
  }
  else
  {
    this->SetUpProfiling();
  }

  return d->is_created;
}


//------------------------------------------------------------------------------
// \internal
void
OpenCLContext::CreateContext(const std::list<OpenCLDevice> & devices, OpenCLContextPimpl * d)
{
  if (d->is_created)
  {
    return;
  }

  if (!devices.empty())
  {
    std::vector<cl_device_id> devs;
    for (std::list<OpenCLDevice>::const_iterator dev = devices.begin(); dev != devices.end(); ++dev)

    {
      devs.push_back(dev->GetDeviceId());
    }

    cl_context_properties props[] = { CL_CONTEXT_PLATFORM,
                                      cl_context_properties(devices.front().GetPlatform().GetPlatformId()),
                                      0 };
    d->id = clCreateContext(props, devs.size(), &devs[0], opencl_context_notify, 0, &(d->last_error));
  }
  else
  {
    d->last_error = CL_DEVICE_NOT_FOUND;
    d->id = 0;
  }
}


//------------------------------------------------------------------------------
void
OpenCLContext::CreateContext(const OpenCLPlatform &         platform,
                             const OpenCLDevice::DeviceType type,
                             OpenCLContextPimpl *           d)
{
  if (d->is_created)
  {
    return;
  }

  if (!platform.IsNull())
  {
    cl_context_properties props[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)(platform.GetPlatformId()), 0 };
    cl_device_type        device_type = cl_device_type(type);
    d->id = clCreateContextFromType(props, device_type, opencl_context_notify, 0, &(d->last_error));
  }
  else
  {
    d->last_error = CL_DEVICE_NOT_FOUND;
    d->id = 0;
  }
}


//------------------------------------------------------------------------------
void
OpenCLContext::Release()
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->is_created)
  {
    d->command_queue = OpenCLCommandQueue();
    d->default_command_queue = OpenCLCommandQueue();
    clReleaseContext(d->id);
    d->id = 0;
    d->default_device = OpenCLDevice();
    d->is_created = false;
  }
}


//------------------------------------------------------------------------------
cl_context
OpenCLContext::GetContextId() const
{
  ITK_OPENCL_D(const OpenCLContext);
  return d->id;
}


//------------------------------------------------------------------------------
void
OpenCLContext::SetContextId(cl_context id)
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->id == id || !id)
  {
    return;
  }
  this->Release();
  clRetainContext(id);
  d->id = id;
  d->is_created = true;
}


//------------------------------------------------------------------------------
std::list<OpenCLDevice>
OpenCLContext::GetDevices() const
{
  ITK_OPENCL_D(const OpenCLContext);
  std::list<OpenCLDevice> devs;
  if (d->is_created)
  {
    std::size_t size = 0;
    if (clGetContextInfo(d->id, CL_CONTEXT_DEVICES, 0, 0, &size) == CL_SUCCESS && size > 0)
    {
      std::vector<cl_device_id> buf(size);
      if (clGetContextInfo(d->id, CL_CONTEXT_DEVICES, size, &buf[0], 0) == CL_SUCCESS)
      {
        for (std::size_t index = 0; index < size; ++index)
        {
          if (buf[index] != 0)
          {
            devs.push_back(OpenCLDevice(buf[index]));
          }
        }
      }
    }
  }
  return devs;
}


//------------------------------------------------------------------------------
OpenCLDevice
OpenCLContext::GetDefaultDevice() const
{
  ITK_OPENCL_D(const OpenCLContext);
  if (d->is_created)
  {
    if (!d->default_device.IsNull())
    {
      return d->default_device;
    }
    std::size_t size = 0;
    if (clGetContextInfo(d->id, CL_CONTEXT_DEVICES, 0, 0, &size) == CL_SUCCESS && size > 0)
    {
      std::vector<cl_device_id> buf(size);
      if (clGetContextInfo(d->id, CL_CONTEXT_DEVICES, size, &buf[0], 0) == CL_SUCCESS)
      {
        return OpenCLDevice(buf[0]);
      }
    }
  }
  return OpenCLDevice();
}


//------------------------------------------------------------------------------
cl_int
OpenCLContext::GetLastError() const
{
  ITK_OPENCL_D(const OpenCLContext);
  return d->last_error;
}


//------------------------------------------------------------------------------
void
OpenCLContext::SetLastError(const cl_int error)
{
  ITK_OPENCL_D(OpenCLContext);
  d->last_error = error;
}


//------------------------------------------------------------------------------
std::string
OpenCLContext::GetErrorName(const cl_int code)
{
  static const char * errorString[] = {
    "CL_SUCCESS",                                   // 0
    "CL_DEVICE_NOT_FOUND",                          // -1
    "CL_DEVICE_NOT_AVAILABLE",                      // -2
    "CL_COMPILER_NOT_AVAILABLE",                    // -3
    "CL_MEM_OBJECT_ALLOCATION_FAILURE",             // -4
    "CL_OUT_OF_RESOURCES",                          // -5
    "CL_OUT_OF_HOST_MEMORY",                        // -6
    "CL_PROFILING_INFO_NOT_AVAILABLE",              // -7
    "CL_MEM_COPY_OVERLAP",                          // -8
    "CL_IMAGE_FORMAT_MISMATCH",                     // -9
    "CL_IMAGE_FORMAT_NOT_SUPPORTED",                // -10
    "CL_BUILD_PROGRAM_FAILURE",                     // -11
    "CL_MAP_FAILURE",                               // -12
    "CL_MISALIGNED_SUB_BUFFER_OFFSET",              // -13
    "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST", // -14
    "CL_COMPILE_PROGRAM_FAILURE",                   // -15
    "CL_LINKER_NOT_AVAILABLE",                      // -16
    "CL_LINK_PROGRAM_FAILURE",                      // -17
    "CL_DEVICE_PARTITION_FAILED",                   // -18
    "CL_KERNEL_ARG_INFO_NOT_AVAILABLE",             // -19
    "",                                             // -20
    "",                                             // -21
    "",                                             // -22
    "",                                             // -23
    "",                                             // -24
    "",                                             // -25
    "",                                             // -26
    "",                                             // -27
    "",                                             // -28
    "",                                             // -29
    "CL_INVALID_VALUE",                             // -30
    "CL_INVALID_DEVICE_TYPE",                       // -31
    "CL_INVALID_PLATFORM",                          // -32
    "CL_INVALID_DEVICE",                            // -33
    "CL_INVALID_CONTEXT",                           // -34
    "CL_INVALID_QUEUE_PROPERTIES",                  // -35
    "CL_INVALID_COMMAND_QUEUE",                     // -36
    "CL_INVALID_HOST_PTR",                          // -37
    "CL_INVALID_MEM_OBJECT",                        // -38
    "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR",           // -39
    "CL_INVALID_IMAGE_SIZE",                        // -40
    "CL_INVALID_SAMPLER",                           // -41
    "CL_INVALID_BINARY",                            // -42
    "CL_INVALID_BUILD_OPTIONS",                     // -43
    "CL_INVALID_PROGRAM",                           // -44
    "CL_INVALID_PROGRAM_EXECUTABLE",                // -45
    "CL_INVALID_KERNEL_NAME",                       // -46
    "CL_INVALID_KERNEL_DEFINITION",                 // -47
    "CL_INVALID_KERNEL",                            // -48
    "CL_INVALID_ARG_INDEX",                         // -49
    "CL_INVALID_ARG_VALUE",                         // -50
    "CL_INVALID_ARG_SIZE",                          // -51
    "CL_INVALID_KERNEL_ARGS",                       // -52
    "CL_INVALID_WORK_DIMENSION",                    // -53
    "CL_INVALID_WORK_GROUP_SIZE",                   // -54
    "CL_INVALID_WORK_ITEM_SIZE",                    // -55
    "CL_INVALID_GLOBAL_OFFSET",                     // -56
    "CL_INVALID_EVENT_WAIT_LIST",                   // -57
    "CL_INVALID_EVENT",                             // -58
    "CL_INVALID_OPERATION",                         // -59
    "CL_INVALID_GL_OBJECT",                         // -60
    "CL_INVALID_BUFFER_SIZE",                       // -61
    "CL_INVALID_MIP_LEVEL",                         // -62
    "CL_INVALID_GLOBAL_WORK_SIZE",                  // -63
    "CL_INVALID_PROPERTY",                          // -64
    "CL_INVALID_IMAGE_DESCRIPTOR",                  // -65
    "CL_INVALID_COMPILER_OPTIONS",                  // -66
    "CL_INVALID_LINKER_OPTIONS",                    // -67
    "CL_INVALID_DEVICE_PARTITION_COUNT",            // -68
  };

  if (code != CL_SUCCESS)
  {
    const int errorCount = sizeof(errorString) / sizeof(errorString[0]);
    const int index = -code;
    if (index >= 0 && index < errorCount)
    {
      return errorString[index];
    }
    else
    {
      return "Unspecified Error";
    }
  }
  else
  {
    return "No Error";
  }
}


//------------------------------------------------------------------------------
OpenCLCommandQueue
OpenCLContext::GetCommandQueue()
{
  ITK_OPENCL_D(OpenCLContext);
  if (!d->command_queue.IsNull())
  {
    return d->command_queue;
  }
  else
  {
    return this->GetDefaultCommandQueue();
  }
}


//------------------------------------------------------------------------------
void
OpenCLContext::SetCommandQueue(const OpenCLCommandQueue & queue)
{
  ITK_OPENCL_D(OpenCLContext);
  d->command_queue = queue;
}


//------------------------------------------------------------------------------
OpenCLCommandQueue
OpenCLContext::GetDefaultCommandQueue()
{
  ITK_OPENCL_D(OpenCLContext);
  if (d->default_command_queue.IsNull())
  {
    if (!d->is_created)
    {
      return OpenCLCommandQueue();
    }
    OpenCLDevice dev = this->GetDefaultDevice();
    if (dev.IsNull())
    {
      return OpenCLCommandQueue();
    }
    cl_command_queue queue;
#ifdef OPENCL_PROFILING
    queue = clCreateCommandQueue(d->id, dev.GetDeviceId(), CL_QUEUE_PROFILING_ENABLE, &(d->last_error));
#else
    queue = clCreateCommandQueue(d->id, dev.GetDeviceId(), 0, &(d->last_error));
#endif

    if (!queue)
    {
      itkOpenCLWarningMacro(<< "OpenCLContext::GetDefaultCommandQueue:" << this->GetErrorName(d->last_error));
      return OpenCLCommandQueue();
    }
    d->default_command_queue = OpenCLCommandQueue(this, queue);
  }
  return d->default_command_queue;
}


//------------------------------------------------------------------------------
// Returns the active queue handle without incurring retain/release overhead.
cl_command_queue
OpenCLContext::GetActiveQueue()
{
  ITK_OPENCL_D(OpenCLContext);
  cl_command_queue queue = d->command_queue.GetQueueId();
  if (queue)
  {
    return queue;
  }
  queue = d->default_command_queue.GetQueueId();
  if (queue)
  {
    return queue;
  }
  return this->GetDefaultCommandQueue().GetQueueId();
}


//------------------------------------------------------------------------------
OpenCLCommandQueue
OpenCLContext::CreateCommandQueue(const cl_command_queue_properties properties, const OpenCLDevice & device)
{
  ITK_OPENCL_D(OpenCLContext);
  cl_command_queue queue;
  if (device.IsNull())
  {
    queue = clCreateCommandQueue(d->id, this->GetDefaultDevice().GetDeviceId(), properties, &(d->last_error));
  }
  else
  {
    queue = clCreateCommandQueue(d->id, device.GetDeviceId(), properties, &(d->last_error));
  }
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (queue)
  {
    return OpenCLCommandQueue(this, queue);
  }
  else
  {
    return OpenCLCommandQueue();
  }
}


//------------------------------------------------------------------------------
OpenCLBuffer
OpenCLContext::CreateBufferDevice(const OpenCLMemoryObject::Access access, const std::size_t size)
{
  if (size == 0)
  {
    return OpenCLBuffer();
  }

  ITK_OPENCL_D(OpenCLContext);
  cl_mem_flags flags = cl_mem_flags(access);
  cl_mem       mem = clCreateBuffer(d->id, flags, size, 0, &(d->last_error));
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (mem)
  {
    return OpenCLBuffer(this, mem);
  }
  else
  {
    return OpenCLBuffer();
  }
}


//------------------------------------------------------------------------------
OpenCLBuffer
OpenCLContext::CreateBufferHost(void * data, const OpenCLMemoryObject::Access access, const std::size_t size)
{
  if (size == 0)
  {
    return OpenCLBuffer();
  }

  ITK_OPENCL_D(OpenCLContext);
  cl_mem_flags flags = cl_mem_flags(access);
  if (data)
  {
    flags |= CL_MEM_USE_HOST_PTR;
  }
  else
  {
    flags |= CL_MEM_ALLOC_HOST_PTR;
  }
  cl_mem mem = clCreateBuffer(d->id, flags, size, data, &(d->last_error));
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (mem)
  {
    return OpenCLBuffer(this, mem);
  }
  else
  {
    return OpenCLBuffer();
  }
}


//------------------------------------------------------------------------------
OpenCLBuffer
OpenCLContext::CreateBufferCopy(const void * data, const OpenCLMemoryObject::Access access, const std::size_t size)
{
  if (size == 0)
  {
    return OpenCLBuffer();
  }

  ITK_OPENCL_D(OpenCLContext);
  cl_mem_flags flags = cl_mem_flags(access);
  flags |= CL_MEM_COPY_HOST_PTR;
  cl_mem mem = clCreateBuffer(d->id, flags, size, const_cast<void *>(data), &(d->last_error));
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (mem)
  {
    return OpenCLBuffer(this, mem);
  }
  else
  {
    return OpenCLBuffer();
  }
}


//------------------------------------------------------------------------------
OpenCLImage
OpenCLContext::CreateImageDevice(const OpenCLImageFormat &        format,
                                 const OpenCLMemoryObject::Access access,
                                 const OpenCLSize &               size)
{
  if (size.IsZero())
  {
    return OpenCLImage();
  }

  ITK_OPENCL_D(OpenCLContext);
  cl_mem_flags flags = cl_mem_flags(access);

#ifdef CL_VERSION_1_2
  // Define image description
  cl_image_desc imageDesc;
  OpenCLImage::SetImageDescription(imageDesc, format, size);
  cl_mem mem = clCreateImage(d->id, flags, &(format.m_Format), &imageDesc, 0, &(d->last_error));
#else
  cl_mem mem = nullptr;
  if (size.GetDimension() == 1)
  {
    itkGenericExceptionMacro(<< "OpenCLContext::CreateImageDevice() not supported for 1D.");
  }
  else if (size.GetDimension() == 2)
  {
    mem = clCreateImage2D(d->id, flags, &(format.m_Format), size[0], size[1], 0, 0, &(d->last_error));
  }
  else if (size.GetDimension() == 3)
  {
    clCreateImage3D(d->id, flags, &(format.m_Format), size[0], size[1], size[2], 0, 0, 0, &(d->last_error));
  }
#endif

  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (mem)
  {
    return OpenCLImage(this, mem);
  }
  else
  {
    return OpenCLImage();
  }
}


//------------------------------------------------------------------------------
OpenCLImage
OpenCLContext::CreateImageHost(const OpenCLImageFormat &        format,
                               void *                           data,
                               const OpenCLSize &               size,
                               const OpenCLMemoryObject::Access access)
{
  if (size.IsZero())
  {
    return OpenCLImage();
  }

  ITK_OPENCL_D(OpenCLContext);
  cl_mem_flags flags = cl_mem_flags(access);
  if (data)
  {
    flags |= CL_MEM_USE_HOST_PTR;
  }
  else
  {
    flags |= CL_MEM_ALLOC_HOST_PTR;
  }

#ifdef CL_VERSION_1_2
  // Define image description
  cl_image_desc imageDesc;
  OpenCLImage::SetImageDescription(imageDesc, format, size);
  cl_mem mem = clCreateImage(d->id, flags, &(format.m_Format), &imageDesc, data, &(d->last_error));
#else
  cl_mem mem = nullptr;
  if (size.GetDimension() == 1)
  {
    itkGenericExceptionMacro(<< "OpenCLContext::CreateImageHost() not supported for 1D.");
  }
  else if (size.GetDimension() == 2)
  {
    mem = clCreateImage2D(d->id, flags, &(format.m_Format), size[0], size[1], 0, data, &(d->last_error));
  }
  else if (size.GetDimension() == 3)
  {
    clCreateImage3D(d->id, flags, &(format.m_Format), size[0], size[1], size[2], 0, 0, data, &(d->last_error));
  }
#endif

  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (mem)
  {
    return OpenCLImage(this, mem);
  }
  else
  {
    return OpenCLImage();
  }
}


//------------------------------------------------------------------------------
OpenCLImage
OpenCLContext::CreateImageCopy(const OpenCLImageFormat &        format,
                               const void *                     data,
                               const OpenCLSize &               size,
                               const OpenCLMemoryObject::Access access)
{
  if (size.IsZero())
  {
    return OpenCLImage();
  }

  ITK_OPENCL_D(OpenCLContext);
  cl_mem_flags flags = cl_mem_flags(access) | CL_MEM_COPY_HOST_PTR;

#ifdef CL_VERSION_1_2
  // Define image description
  cl_image_desc imageDesc;
  OpenCLImage::SetImageDescription(imageDesc, format, size);
  cl_mem mem = clCreateImage(d->id, flags, &(format.m_Format), &imageDesc, const_cast<void *>(data), &(d->last_error));
#else
  cl_mem mem = nullptr;
  if (size.GetDimension() == 1)
  {
    itkGenericExceptionMacro(<< "OpenCLContext::CreateImageCopy() not supported for 1D.");
  }
  else if (size.GetDimension() == 2)
  {
    mem = clCreateImage2D(
      d->id, flags, &(format.m_Format), size[0], size[1], 0, const_cast<void *>(data), &(d->last_error));
  }
  else if (size.GetDimension() == 3)
  {
    clCreateImage3D(
      d->id, flags, &(format.m_Format), size[0], size[1], size[2], 0, 0, const_cast<void *>(data), &(d->last_error));
  }
#endif

  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (mem)
  {
    return OpenCLImage(this, mem);
  }
  else
  {
    return OpenCLImage();
  }
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::CreateProgramFromSourceCode(const std::string & sourceCode,
                                           const std::string & prefixSourceCode,
                                           const std::string & postfixSourceCode)
{
  if (sourceCode.empty())
  {
    itkOpenCLWarningMacro(<< "The source code is empty for the OpenCL program.");
    return OpenCLProgram();
  }

#ifdef OPENCL_PROFILING
  itk::OpenCLProfilingTimeProbe timer("Creating OpenCL program using clCreateProgramWithSource");
#endif

  std::stringstream sstream;

  // Prepends prefix source code if provided
  if (!prefixSourceCode.empty())
  {
    sstream << prefixSourceCode << std::endl;
  }

  // Add the main source code
  sstream << sourceCode;

  // Appends postfix source code if provided
  if (!postfixSourceCode.empty())
  {
    sstream << '\n' << postfixSourceCode;
  }

  const std::string oclSource = sstream.str();
  const std::size_t oclSourceSize = oclSource.size();

  if (oclSourceSize == 0)
  {
    itkOpenCLWarningMacro(<< "Cannot build OpenCL brogram from empty source.");
    return OpenCLProgram();
  }

#if (defined(_WIN32) && defined(_DEBUG)) || !defined(NDEBUG)
  // To work with the Intel SDK for OpenCL* - Debugger plug-in, the OpenCL*
  // kernel code must exist in a text file separate from the code of the host.
  // Also the full path to the file has to be provided.
  const std::string fileName = GetOpenCLDebugFileName(oclSource);
  if (!prefixSourceCode.empty())
  {
    std::ofstream debugfile(fileName.c_str());
    if (debugfile.is_open() == false)
    {
      itkOpenCLWarningMacro(<< "Cannot create OpenCL debug source file: " << fileName);
      return OpenCLProgram();
    }
    debugfile << oclSource;
    debugfile.close();

    itkOpenCLWarningMacro(<< "For Debugging your OpenCL kernel use:\n" << fileName);
  }

  std::cout << "Creating OpenCL program from source." << std::endl;
#endif

  // Create OpenCL program and return
#if (defined(_WIN32) && defined(_DEBUG)) || !defined(NDEBUG)
  return this->CreateOpenCLProgram(fileName, oclSource, oclSourceSize);
#else
  return this->CreateOpenCLProgram(std::string(), oclSource, oclSourceSize);
#endif
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::CreateProgramFromSourceFile(const std::string & filename,
                                           const std::string & prefixSourceCode,
                                           const std::string & postfixSourceCode)
{
  if (filename.empty())
  {
    itkOpenCLWarningMacro(<< "The filename must be specified.");
    return OpenCLProgram();
  }

  // open the file
  std::ifstream inputFile(filename.c_str(), std::ifstream::in | std::ifstream::binary);
  if (inputFile.is_open() == false)
  {
    itkOpenCLWarningMacro(<< "Cannot open OpenCL source file: " << filename);
    return OpenCLProgram();
  }

  std::stringstream sstream;

  // Prepends prefix source code if provided
  if (!prefixSourceCode.empty())
  {
    sstream << prefixSourceCode << std::endl;
  }

  // Add the main source code
  sstream << inputFile.rdbuf();

  // Appends postfix source code if provided
  if (!postfixSourceCode.empty())
  {
    sstream << '\n' << postfixSourceCode;
  }

  inputFile.close();

  const std::string oclSource = sstream.str();
  const std::size_t oclSourceSize = oclSource.size();

  if (oclSourceSize == 0)
  {
    itkOpenCLWarningMacro(<< "Cannot build OpenCL source file: " << filename << " is empty.");
    return OpenCLProgram();
  }

  std::string fileName(filename);

#if (defined(_WIN32) && defined(_DEBUG)) || !defined(NDEBUG)
  // To work with the Intel SDK for OpenCL* - Debugger plug-in, the OpenCL*
  // kernel code must exist in a text file separate from the code of the host.
  // Also the full path to the file has to be provided.
  if (!prefixSourceCode.empty())
  {
    fileName = GetOpenCLDebugFileName(oclSource);
  }
  if (!prefixSourceCode.empty())
  {
    std::ofstream debugfile(fileName.c_str());
    if (debugfile.is_open() == false)
    {
      itkOpenCLWarningMacro(<< "Cannot create OpenCL debug source file: " << fileName);
      return OpenCLProgram();
    }
    debugfile << oclSource;
    debugfile.close();

    itkOpenCLWarningMacro(<< "For Debugging your OpenCL kernel use:\n" << fileName << " , not original .cl file.");
  }

  std::cout << "Creating OpenCL program from : " << fileName << std::endl;
#endif

  // Create OpenCL program and return
  return this->CreateOpenCLProgram(fileName, oclSource, oclSourceSize);
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::CreateOpenCLProgram(const std::string & filename,
                                   const std::string & source,
                                   const std::size_t   sourceSize)
{
  if (source.empty())
  {
    itkOpenCLWarningMacro(<< "The source is empty for the OpenCL program in filename: '" << filename << "'");
    return OpenCLProgram();
  }

#ifdef OPENCL_PROFILING
  itk::OpenCLProfilingTimeProbe timer("Creating OpenCL program using clCreateProgramWithSource");
#endif

  ITK_OPENCL_D(OpenCLContext);
  const char * code = source.c_str();

  this->OpenCLDebug("clCreateProgramWithSource");
  cl_program program = clCreateProgramWithSource(d->id, 1, &code, &sourceSize, &(d->last_error));
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);

  if (d->last_error != CL_SUCCESS)
  {
    itkOpenCLWarningMacro("Cannot create OpenCL program, filename: '" << filename << "'");
    return OpenCLProgram();
  }
  else
  {
    return OpenCLProgram(this, program, filename);
  }
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::CreateProgramFromBinaryCode(const unsigned char * binary, const std::size_t size)
{
  ITK_OPENCL_D(OpenCLContext);
  cl_device_id device = GetDefaultDevice().GetDeviceId();

  this->OpenCLDebug("clCreateProgramWithBinary");
  cl_program program = clCreateProgramWithBinary(d->id, 1, &device, &size, &binary, 0, &(d->last_error));
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (d->last_error != CL_SUCCESS)
  {
    itkOpenCLWarningMacro("Cannot create OpenCL program from binary.");
    return OpenCLProgram();
  }
  else
  {
    return OpenCLProgram(this, program);
  }
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::BuildProgramFromSourceCode(const std::string & sourceCode,
                                          const std::string & prefixSourceCode,
                                          const std::string & postfixSourceCode)
{
  OpenCLProgram program = this->CreateProgramFromSourceCode(sourceCode, prefixSourceCode, postfixSourceCode);

  if (program.IsNull() || program.Build())
  {
    return program;
  }
  return OpenCLProgram();
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::BuildProgramFromSourceCode(const std::list<OpenCLDevice> & devices,
                                          const std::string &             sourceCode,
                                          const std::string &             prefixSourceCode,
                                          const std::string &             postfixSourceCode,
                                          const std::string &             extraBuildOptions)
{
  OpenCLProgram program = this->CreateProgramFromSourceCode(sourceCode, prefixSourceCode, postfixSourceCode);

  if (program.IsNull() || program.Build(devices, extraBuildOptions))
  {
    return program;
  }
  return OpenCLProgram();
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::BuildProgramFromSourceFile(const std::string & filename,
                                          const std::string & prefixSourceCode,
                                          const std::string & postfixSourceCode)
{
  OpenCLProgram program = this->CreateProgramFromSourceFile(filename, prefixSourceCode, postfixSourceCode);

  if (program.IsNull() || program.Build())
  {
    return program;
  }
  return OpenCLProgram();
}


//------------------------------------------------------------------------------
OpenCLProgram
OpenCLContext::BuildProgramFromSourceFile(const std::list<OpenCLDevice> & devices,
                                          const std::string &             fileName,
                                          const std::string &             prefixSourceCode,
                                          const std::string &             postfixSourceCode,
                                          const std::string &             extraBuildOptions)
{
  OpenCLProgram program = this->CreateProgramFromSourceFile(fileName, prefixSourceCode, postfixSourceCode);

  if (program.IsNull() || program.Build(devices, extraBuildOptions))
  {
    return program;
  }
  return OpenCLProgram();
}


//------------------------------------------------------------------------------
std::list<OpenCLImageFormat>
open_cl_get_supported_image_formats(const cl_context ctx, const cl_mem_flags flags, const cl_mem_object_type image_type)
{
  cl_uint count = 0;

  std::list<OpenCLImageFormat> list;

  if (clGetSupportedImageFormats(ctx, flags, image_type, 0, 0, &count) != CL_SUCCESS || !count)
  {
    return list;
  }
  std::vector<cl_image_format> buf(count);
  if (clGetSupportedImageFormats(ctx, flags, image_type, count, &buf[0], 0) != CL_SUCCESS)
  {
    return list;
  }
  for (cl_uint index = 0; index < count; ++index)
  {
    list.push_back(OpenCLImageFormat(OpenCLImageFormat::ChannelOrder(buf[index].image_channel_order),
                                     OpenCLImageFormat::ChannelType(buf[index].image_channel_data_type)));
  }
  return list;
}


//------------------------------------------------------------------------------
std::list<OpenCLImageFormat>
OpenCLContext::GetSupportedImageFormats(const OpenCLImageFormat::ImageType image_type, const cl_mem_flags flags) const
{
  ITK_OPENCL_D(const OpenCLContext);

  std::list<OpenCLImageFormat> list_image_formats;
  switch (image_type)
  {
    case OpenCLImageFormat::BUFFER:
      list_image_formats = open_cl_get_supported_image_formats(d->id, flags, CL_MEM_OBJECT_BUFFER);
      break;
    case OpenCLImageFormat::IMAGE2D:
      list_image_formats = open_cl_get_supported_image_formats(d->id, flags, CL_MEM_OBJECT_IMAGE2D);
      break;
    case OpenCLImageFormat::IMAGE3D:
      list_image_formats = open_cl_get_supported_image_formats(d->id, flags, CL_MEM_OBJECT_IMAGE3D);
      break;
    case OpenCLImageFormat::IMAGE2D_ARRAY:
      list_image_formats = open_cl_get_supported_image_formats(d->id, flags, CL_MEM_OBJECT_IMAGE2D_ARRAY);
      break;
    case OpenCLImageFormat::IMAGE1D:
      list_image_formats = open_cl_get_supported_image_formats(d->id, flags, CL_MEM_OBJECT_IMAGE1D);
      break;
    case OpenCLImageFormat::IMAGE1D_ARRAY:
      list_image_formats = open_cl_get_supported_image_formats(d->id, flags, CL_MEM_OBJECT_IMAGE1D_ARRAY);
      break;
    case OpenCLImageFormat::IMAGE1D_BUFFER:
      list_image_formats = open_cl_get_supported_image_formats(d->id, flags, CL_MEM_OBJECT_IMAGE1D_BUFFER);
      break;
  }

  // Compose the final list
  std::list<OpenCLImageFormat> list;
  for (std::list<OpenCLImageFormat>::const_iterator it = list_image_formats.begin(); it != list_image_formats.end();
       ++it)
  {
    list.push_back(OpenCLImageFormat(OpenCLImageFormat::ImageType(image_type),
                                     OpenCLImageFormat::ChannelOrder(it->GetChannelOrder()),
                                     OpenCLImageFormat::ChannelType(it->GetChannelType())));
  }

  return list;
}


//------------------------------------------------------------------------------
OpenCLSampler
OpenCLContext::CreateSampler(const bool                          normalizedCoordinates,
                             const OpenCLSampler::AddressingMode addressingMode,
                             const OpenCLSampler::FilterMode     filterMode)
{
  ITK_OPENCL_D(OpenCLContext);
  cl_sampler sampler = clCreateSampler(d->id,
                                       normalizedCoordinates ? CL_TRUE : CL_FALSE,
                                       cl_addressing_mode(addressingMode),
                                       cl_filter_mode(filterMode),
                                       &(d->last_error));
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  if (sampler)
  {
    return OpenCLSampler(this, sampler);
  }
  else
  {
    return OpenCLSampler();
  }
}


//------------------------------------------------------------------------------
OpenCLUserEvent
OpenCLContext::CreateUserEvent()
{
  ITK_OPENCL_D(OpenCLContext);
  cl_event event = clCreateUserEvent(d->id, &(d->last_error));
  this->ReportError(d->last_error, __FILE__, __LINE__, ITK_LOCATION);
  return OpenCLUserEvent(event, true);
}


//------------------------------------------------------------------------------
void
OpenCLContext::Flush()
{
  clFlush(this->GetActiveQueue());
}


//------------------------------------------------------------------------------
void
OpenCLContext::Finish()
{
  clFinish(this->GetActiveQueue());
}


//------------------------------------------------------------------------------
cl_int
OpenCLContext::Marker(const OpenCLEventList & event_list)
{
  if (event_list.IsEmpty())
  {
    return CL_SUCCESS;
  }

  cl_event event;

#ifdef CL_VERSION_1_2
  const cl_int error =
    clEnqueueMarkerWithWaitList(this->GetActiveQueue(), event_list.GetSize(), event_list.GetEventData(), &event);
#else
  const cl_int error = clEnqueueMarker(this->GetActiveQueue(), &event);
#endif

  this->ReportError(error, __FILE__, __LINE__, ITK_LOCATION);
  if (error == CL_SUCCESS)
  {
    clWaitForEvents(1, &event);
    clReleaseEvent(event);
  }
  return error;
}


//------------------------------------------------------------------------------
OpenCLEvent
OpenCLContext::MarkerAsync(const OpenCLEventList & event_list)
{
  if (event_list.IsEmpty())
  {
    return OpenCLEvent();
  }

  cl_event event;

#ifdef CL_VERSION_1_2
  const cl_int error =
    clEnqueueMarkerWithWaitList(this->GetActiveQueue(), event_list.GetSize(), event_list.GetEventData(), &event);
#else
  const cl_int error = clEnqueueMarker(this->GetActiveQueue(), &event);
#endif

  this->ReportError(error, __FILE__, __LINE__, ITK_LOCATION);
  if (error != CL_SUCCESS)
  {
    return OpenCLEvent();
  }
  else
  {
    return OpenCLEvent(event);
  }
}


//------------------------------------------------------------------------------
cl_int
OpenCLContext::Barrier(const OpenCLEventList & event_list)
{
  if (event_list.IsEmpty())
  {
    return CL_SUCCESS;
  }

#ifdef CL_VERSION_1_2
  cl_event     event;
  const cl_int error =
    clEnqueueBarrierWithWaitList(this->GetActiveQueue(), event_list.GetSize(), event_list.GetEventData(), &event);
#else
  const cl_int error = clEnqueueBarrier(this->GetActiveQueue());
#endif

  this->ReportError(error, __FILE__, __LINE__, ITK_LOCATION);

#ifdef CL_VERSION_1_2
  if (error == CL_SUCCESS)
  {
    clWaitForEvents(1, &event);
    clReleaseEvent(event);
  }
  return error;
#else
  return CL_SUCCESS;
#endif
}


//------------------------------------------------------------------------------
OpenCLEvent
OpenCLContext::BarrierAsync(const OpenCLEventList & event_list)
{
  if (event_list.IsEmpty())
  {
    return OpenCLEvent();
  }

#ifdef CL_VERSION_1_2
  cl_event     event;
  const cl_int error =
    clEnqueueBarrierWithWaitList(this->GetActiveQueue(), event_list.GetSize(), event_list.GetEventData(), &event);
#else
  const cl_int error = clEnqueueBarrier(this->GetActiveQueue());
#endif

  this->ReportError(error, __FILE__, __LINE__, ITK_LOCATION);

#ifdef CL_VERSION_1_2
  if (error != CL_SUCCESS)
  {
    return OpenCLEvent();
  }
  else
  {
    return OpenCLEvent(event);
  }
#else
  return OpenCLEvent();
#endif
}


//------------------------------------------------------------------------------
cl_int
OpenCLContext::WaitForFinished(const OpenCLEventList & event_list)
{
  if (event_list.IsEmpty())
  {
    return CL_SUCCESS;
  }
  const cl_int error = clWaitForEvents(event_list.GetSize(), event_list.GetEventData());
  if (error != CL_SUCCESS)
  {
    itkOpenCLErrorMacroGeneric(<< "OpenCLContext::WaitForFinished:" << OpenCLContext::GetErrorName(error));
  }
  return error;
}


//------------------------------------------------------------------------------
void
OpenCLContext::SetDefaultDevice(const OpenCLDevice & device)
{
  ITK_OPENCL_D(OpenCLContext);
  d->default_device = device;
}


//------------------------------------------------------------------------------
// \internal
void
OpenCLContext::OpenCLDebug(const std::string & callname)
{
#if (defined(_WIN32) && defined(_DEBUG)) || !defined(NDEBUG)
  std::cout << callname << "..." << std::endl;
#endif
}


//------------------------------------------------------------------------------
void
OpenCLContext::ReportError(const cl_int code, const char * fileName, const int lineNumber, const char * location)
{
  ITK_OPENCL_D(OpenCLContext);
  d->last_error = code;
  if (code != CL_SUCCESS)
  {
    ExceptionObject e_(fileName, lineNumber, this->GetErrorName(code).c_str(), location);
    throw e_;
  }
}


//------------------------------------------------------------------------------
// \internal
void
OpenCLContext::OpenCLProfile(cl_event clEvent, const std::string & message, const bool releaseEvent)
{
#ifdef OPENCL_PROFILING
  if (!clEvent)
  {
    return;
  }

  cl_int errid;

  // Execution time
  errid = clWaitForEvents(1, &clEvent);
  this->ReportError(errid, __FILE__, __LINE__, ITK_LOCATION);
  if (errid != CL_SUCCESS)
  {
    itkOpenCLWarningMacro("clWaitForEvents failed");
    return;
  }
  cl_ulong start, end;
  errid = clGetEventProfilingInfo(clEvent, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &end, nullptr);
  errid |= clGetEventProfilingInfo(clEvent, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &start, nullptr);
  this->ReportError(errid, __FILE__, __LINE__, ITK_LOCATION);
  if (errid != CL_SUCCESS)
  {
    itkOpenCLWarningMacro("clGetEventProfilingInfo failed");
    return;
  }

  const double dSeconds = 1.0e-9 * (double)(end - start);
  std::cout << "GPU " << message << " execution took " << dSeconds << " seconds." << std::endl;

  // Release event if required
  if (releaseEvent)
  {
    errid = clReleaseEvent(clEvent);
    this->ReportError(errid, __FILE__, __LINE__, ITK_LOCATION);
    if (errid != CL_SUCCESS)
    {
      itkOpenCLWarningMacro("clReleaseEvent failed");
      return;
    }
    clEvent = 0;
  }
#endif
}


//------------------------------------------------------------------------------
// \internal
void
OpenCLContext::SetUpProfiling()
{
#ifdef OPENCL_PROFILING
  OpenCLCommandQueue queue = this->CreateCommandQueue(CL_QUEUE_PROFILING_ENABLE);

  if (!queue.IsProfilingEnabled())
  {
    itkOpenCLWarningMacro(
      << "OpenCLContext attempted to create OpenCL command queue with CL_QUEUE_PROFILING_ENABLE, but failed.");
  }
  else
  {
    this->SetCommandQueue(queue);
  }
#endif
}


} // namespace itk
