/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/gpu/ganesh/GrYUVABackendTextures.h"

#include "include/core/SkColor.h"
#include "include/core/SkSize.h"
#include "include/private/base/SkDebug.h"
#include "src/core/SkYUVAInfoLocation.h"

#include <algorithm>
#include <cstdint>

static int num_channels(const GrBackendFormat& format) {
    switch (format.channelMask()) {
        case kRed_SkColorChannelFlag        : return 1;
        case kAlpha_SkColorChannelFlag      : return 1;
        case kGray_SkColorChannelFlag       : return 1;
        case kGrayAlpha_SkColorChannelFlags : return 2;
        case kRG_SkColorChannelFlags        : return 2;
        case kRGB_SkColorChannelFlags       : return 3;
        case kRGBA_SkColorChannelFlags      : return 4;
        default                             : return 0;
    }
}

GrYUVABackendTextureInfo::GrYUVABackendTextureInfo(const SkYUVAInfo& yuvaInfo,
                                                   const GrBackendFormat formats[kMaxPlanes],
                                                   skgpu::Mipmapped mipmapped,
                                                   GrSurfaceOrigin origin)
        : fYUVAInfo(yuvaInfo), fMipmapped(mipmapped), fTextureOrigin(origin) {
    if (!yuvaInfo.isValid()) {
        *this = {};
        SkASSERT(!this->isValid());
        return;
    }
    int n = yuvaInfo.numPlanes();
    for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
        if (!formats[i].isValid() || formats[i].backend() != formats[0].backend()) {
            *this = {};
            SkASSERT(!this->isValid());
            return;
        }
        int numRequiredChannels = yuvaInfo.numChannelsInPlane(i);
        SkASSERT(numRequiredChannels > 0);
        int numActualChannels = num_channels(formats[i]);
        if (numActualChannels < numRequiredChannels) {
            *this = {};
            SkASSERT(!this->isValid());
            return;
        }
        fPlaneFormats[i] = formats[i];
    }
    SkASSERT(this->isValid());
}

bool GrYUVABackendTextureInfo::operator==(const GrYUVABackendTextureInfo& that) const {
    if (fYUVAInfo != that.fYUVAInfo ||
        fMipmapped != that.fMipmapped ||
        fTextureOrigin != that.fTextureOrigin) {
        return false;
    }
    int n = fYUVAInfo.numPlanes();
    return std::equal(fPlaneFormats, fPlaneFormats + n, that.fPlaneFormats);
}

SkYUVAInfo::YUVALocations GrYUVABackendTextureInfo::toYUVALocations() const {
    uint32_t channelFlags[] = {fPlaneFormats[0].channelMask(),
                               fPlaneFormats[1].channelMask(),
                               fPlaneFormats[2].channelMask(),
                               fPlaneFormats[3].channelMask()};
    auto result = fYUVAInfo.toYUVALocations(channelFlags);
    SkDEBUGCODE(int numPlanes;)
    SkASSERT(SkYUVAInfo::YUVALocation::AreValidLocations(result, &numPlanes));
    SkASSERT(numPlanes == this->numPlanes());
    return result;
}

//////////////////////////////////////////////////////////////////////////////

GrYUVABackendTextures::GrYUVABackendTextures(
        const SkYUVAInfo& yuvaInfo,
        const GrBackendTexture textures[SkYUVAInfo::kMaxPlanes],
        GrSurfaceOrigin textureOrigin)
        : fYUVAInfo(yuvaInfo), fTextureOrigin(textureOrigin) {
    if (!fYUVAInfo.isValid()) {
        return;
    }
    SkISize planeDimensions[SkYUVAInfo::kMaxPlanes];
    int numPlanes = yuvaInfo.planeDimensions(planeDimensions);
    for (int i = 0; i < numPlanes; ++i) {
        int numRequiredChannels = fYUVAInfo.numChannelsInPlane(i);
        if (!textures[i].isValid() ||
            textures[i].dimensions() != planeDimensions[i] ||
            textures[i].backend() != textures[0].backend() ||
            num_channels(textures[i].getBackendFormat()) < numRequiredChannels) {
            *this = {};
            return;
        }
        fTextures[i] = textures[i];
    }
}

SkYUVAInfo::YUVALocations GrYUVABackendTextures::toYUVALocations() const {
    uint32_t channelFlags[] = {fTextures[0].getBackendFormat().channelMask(),
                               fTextures[1].getBackendFormat().channelMask(),
                               fTextures[2].getBackendFormat().channelMask(),
                               fTextures[3].getBackendFormat().channelMask()};
    auto result = fYUVAInfo.toYUVALocations(channelFlags);
    SkDEBUGCODE(int numPlanes;)
    SkASSERT(SkYUVAInfo::YUVALocation::AreValidLocations(result, &numPlanes));
    SkASSERT(numPlanes == this->numPlanes());
    return result;
}
