// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/node/binding/GPUAdapterInfo.h"

#include <cassert>
#include <cctype>
#include <iomanip>
#include <sstream>

#include "src/utils/compiler.h"

namespace wgpu::binding {

////////////////////////////////////////////////////////////////////////////////
// wgpu::bindings::GPUAdapterInfo
////////////////////////////////////////////////////////////////////////////////

namespace {

interop::GPUSubgroupMatrixComponentType SubgroupMatrixComponentType(
    wgpu::SubgroupMatrixComponentType c) {
    switch (c) {
        case SubgroupMatrixComponentType::F32:
            return interop::GPUSubgroupMatrixComponentType::kF32;
        case SubgroupMatrixComponentType::F16:
            return interop::GPUSubgroupMatrixComponentType::kF16;
        case SubgroupMatrixComponentType::U32:
            return interop::GPUSubgroupMatrixComponentType::kU32;
        case SubgroupMatrixComponentType::I32:
            return interop::GPUSubgroupMatrixComponentType::kI32;
        case SubgroupMatrixComponentType::U8:
            return interop::GPUSubgroupMatrixComponentType::kU8;
        case SubgroupMatrixComponentType::I8:
            return interop::GPUSubgroupMatrixComponentType::kI8;
    }
    assert(false);
    return interop::GPUSubgroupMatrixComponentType::kF32;
}

struct GPUSubgroupMatrixConfig : public interop::GPUSubgroupMatrixConfig {
    interop::GPUSubgroupMatrixComponentType componentType;
    interop::GPUSubgroupMatrixComponentType resultComponentType;
    uint32_t M;
    uint32_t N;
    uint32_t K;

    explicit GPUSubgroupMatrixConfig(const wgpu::SubgroupMatrixConfig& config)
        : componentType(SubgroupMatrixComponentType(config.componentType)),
          resultComponentType(SubgroupMatrixComponentType(config.resultComponentType)),
          M(config.M),
          N(config.N),
          K(config.K) {}

    interop::GPUSubgroupMatrixComponentType getComponentType(Napi::Env) override {
        return componentType;
    }
    interop::GPUSubgroupMatrixComponentType getResultComponentType(Napi::Env) override {
        return resultComponentType;
    }
    uint32_t getM(Napi::Env) override { return M; }
    uint32_t getN(Napi::Env) override { return N; }
    uint32_t getK(Napi::Env) override { return K; }
};

// Normalize according to https://gpuweb.github.io/gpuweb/#normalized-identifier-string
std::string NormalizeIdentifierString(wgpu::StringView s) {
    std::ostringstream o;

    // Used to concatenate multiple non-alnum into a single dash.
    bool lastWasDash = false;
    // Used to start adding dashes only after we had one alnum.
    bool hadAlnum = false;

    for (char c : std::string_view(s)) {
        if (std::isalnum(c)) {
            o << static_cast<char>(std::tolower(c));
            lastWasDash = false;
            hadAlnum = true;
        } else if (!lastWasDash && hadAlnum) {
            o << '-';
            lastWasDash = true;
        }
    }

    return o.str();
}

}  // namespace

GPUAdapterInfo::GPUAdapterInfo(const wgpu::AdapterInfo& info)
    : vendor_(NormalizeIdentifierString(info.vendor)),
      architecture_(NormalizeIdentifierString(info.architecture)),
      device_(NormalizeIdentifierString(info.device)),
      description_(info.description),
      subgroup_min_size_(info.subgroupMinSize),
      subgroup_max_size_(info.subgroupMaxSize),
      is_fallback_adapter_(info.adapterType == wgpu::AdapterType::CPU) {
    auto* next = info.nextInChain;
    while (next) {
        if (next->sType == SType::AdapterPropertiesSubgroupMatrixConfigs) {
            auto* configs = static_cast<wgpu::AdapterPropertiesSubgroupMatrixConfigs*>(next);
            subgroup_matrix_configs_.reserve(configs->configCount);
            for (uint32_t i = 0; i < configs->configCount; i++) {
                DAWN_UNSAFE_TODO(subgroup_matrix_configs_.push_back(configs->configs[i]));
            }
        }
        next = next->nextInChain;
    }
}

std::string GPUAdapterInfo::getVendor(Napi::Env) {
    return vendor_;
}

std::string GPUAdapterInfo::getArchitecture(Napi::Env) {
    return architecture_;
}

std::string GPUAdapterInfo::getDevice(Napi::Env) {
    return device_;
}

std::string GPUAdapterInfo::getDescription(Napi::Env) {
    return description_;
}

uint32_t GPUAdapterInfo::getSubgroupMinSize(Napi::Env) {
    return subgroup_min_size_;
}

uint32_t GPUAdapterInfo::getSubgroupMaxSize(Napi::Env) {
    return subgroup_max_size_;
}

bool GPUAdapterInfo::getIsFallbackAdapter(Napi::Env) {
    return is_fallback_adapter_;
}

GPUAdapterInfo::SubgroupMatrixConfigs GPUAdapterInfo::getSubgroupMatrixConfigs(Napi::Env env) {
    SubgroupMatrixConfigs out;
    out.reserve(subgroup_matrix_configs_.size());
    for (auto& config : subgroup_matrix_configs_) {
        out.emplace_back(
            interop::GPUSubgroupMatrixConfig::Create<GPUSubgroupMatrixConfig>(env, config));
    }
    return out;
}

}  // namespace wgpu::binding
