//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// CLContext.cpp: Implements the cl::Context class.

#include "libANGLE/CLContext.h"

#include "libANGLE/CLPlatform.h"

#include <cstring>

namespace cl
{

Context::~Context() = default;

bool Context::release()
{
    const bool released = removeRef();
    if (released)
    {
        mPlatform.destroyContext(this);
    }
    return released;
}

cl_int Context::getInfo(ContextInfo name, size_t valueSize, void *value, size_t *valueSizeRet)
{
    cl_uint numDevices    = 0u;
    const void *copyValue = nullptr;
    size_t copySize       = 0u;

    switch (name)
    {
        case ContextInfo::ReferenceCount:
            copyValue = getRefCountPtr();
            copySize  = sizeof(*getRefCountPtr());
            break;
        case ContextInfo::NumDevices:
            numDevices = static_cast<decltype(numDevices)>(mDevices.size());
            copyValue  = &numDevices;
            copySize   = sizeof(numDevices);
            break;
        case ContextInfo::Devices:
            static_assert(sizeof(decltype(mDevices)::value_type) == sizeof(Device *),
                          "Device::RefList has wrong element size");
            copyValue = mDevices.data();
            copySize  = mDevices.size() * sizeof(decltype(mDevices)::value_type);
            break;
        case ContextInfo::Properties:
            copyValue = mProperties.data();
            copySize  = mProperties.size() * sizeof(decltype(mProperties)::value_type);
            break;
        default:
            return CL_INVALID_VALUE;
    }

    if (value != nullptr)
    {
        if (valueSize < copySize)
        {
            return CL_INVALID_VALUE;
        }
        if (copyValue != nullptr)
        {
            std::memcpy(value, copyValue, copySize);
        }
    }
    if (valueSizeRet != nullptr)
    {
        *valueSizeRet = copySize;
    }
    return CL_SUCCESS;
}

bool Context::IsValid(const Context *context)
{
    const Platform::PtrList &platforms = Platform::GetPlatforms();
    return std::find_if(platforms.cbegin(), platforms.cend(), [=](const Platform::Ptr &platform) {
               return platform->hasContext(context);
           }) != platforms.cend();
}

Context::Context(Platform &platform,
                 PropArray &&properties,
                 Device::RefList &&devices,
                 ContextErrorCB notify,
                 void *userData,
                 bool userSync,
                 cl_int *errcodeRet)
    : _cl_context(platform.getDispatch()),
      mPlatform(platform),
      mImpl(platform.createContext(devices, ErrorCallback, this, userSync, errcodeRet)),
      mProperties(std::move(properties)),
      mDevices(std::move(devices)),
      mNotify(notify),
      mUserData(userData)
{}

Context::Context(Platform &platform,
                 PropArray &&properties,
                 cl_device_type deviceType,
                 ContextErrorCB notify,
                 void *userData,
                 bool userSync,
                 cl_int *errcodeRet)
    : _cl_context(platform.getDispatch()),
      mPlatform(platform),
      mImpl(platform.mImpl
                ->createContextFromType(deviceType, ErrorCallback, this, userSync, errcodeRet)),
      mProperties(std::move(properties)),
      mDevices(mImpl ? platform.mapDevices(mImpl->getDevices()) : Device::RefList{}),
      mNotify(notify),
      mUserData(userData)
{}

void Context::ErrorCallback(const char *errinfo, const void *privateInfo, size_t cb, void *userData)
{
    Context *const context = static_cast<Context *>(userData);
    if (!Context::IsValid(context))
    {
        WARN() << "Context error for invalid context";
        return;
    }
    if (context->mNotify != nullptr)
    {
        context->mNotify(errinfo, privateInfo, cb, context->mUserData);
    }
}

}  // namespace cl
