/*
 * 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/core/SkYUVAInfo.h"
#include "src/base/SkSafeMath.h"
#include "src/core/SkYUVAInfoLocation.h"

#include <algorithm>

static bool is_plane_config_compatible_with_subsampling(SkYUVAInfo::PlaneConfig config,
                                                        SkYUVAInfo::Subsampling subsampling) {
    if (config      == SkYUVAInfo::PlaneConfig::kUnknown ||
        subsampling == SkYUVAInfo::Subsampling::kUnknown) {
        return false;
    }
    return subsampling == SkYUVAInfo::Subsampling::k444 ||
           (config != SkYUVAInfo::PlaneConfig::kYUV  &&
            config != SkYUVAInfo::PlaneConfig::kYUVA &&
            config != SkYUVAInfo::PlaneConfig::kUYV  &&
            config != SkYUVAInfo::PlaneConfig::kUYVA);
}

std::tuple<int, int> SkYUVAInfo::SubsamplingFactors(Subsampling subsampling) {
    switch (subsampling) {
        case Subsampling::kUnknown: return {0, 0};
        case Subsampling::k444:     return {1, 1};
        case Subsampling::k422:     return {2, 1};
        case Subsampling::k420:     return {2, 2};
        case Subsampling::k440:     return {1, 2};
        case Subsampling::k411:     return {4, 1};
        case Subsampling::k410:     return {4, 2};
    }
    SkUNREACHABLE;
}

std::tuple<int, int> SkYUVAInfo::PlaneSubsamplingFactors(PlaneConfig planeConfig,
                                                         Subsampling subsampling,
                                                         int planeIdx) {
    if (!is_plane_config_compatible_with_subsampling(planeConfig, subsampling) ||
        planeIdx < 0                                                           ||
        planeIdx > NumPlanes(planeConfig)) {
        return {0, 0};
    }
    bool isSubsampledPlane = false;
    switch (planeConfig) {
        case PlaneConfig::kUnknown:     SkUNREACHABLE;

        case PlaneConfig::kY_U_V:
        case PlaneConfig::kY_V_U:
        case PlaneConfig::kY_U_V_A:
        case PlaneConfig::kY_V_U_A:
            isSubsampledPlane = planeIdx == 1 || planeIdx == 2;
            break;

        case PlaneConfig::kY_UV:
        case PlaneConfig::kY_VU:
        case PlaneConfig::kY_UV_A:
        case PlaneConfig::kY_VU_A:
            isSubsampledPlane = planeIdx == 1;
            break;

        case PlaneConfig::kYUV:
        case PlaneConfig::kUYV:
        case PlaneConfig::kYUVA:
        case PlaneConfig::kUYVA:
            break;
    }
    return isSubsampledPlane ? SubsamplingFactors(subsampling) : std::make_tuple(1, 1);
}

int SkYUVAInfo::PlaneDimensions(SkISize imageDimensions,
                                PlaneConfig planeConfig,
                                Subsampling subsampling,
                                SkEncodedOrigin origin,
                                SkISize planeDimensions[SkYUVAInfo::kMaxPlanes]) {
    std::fill_n(planeDimensions, SkYUVAInfo::kMaxPlanes, SkISize{0, 0});
    if (!is_plane_config_compatible_with_subsampling(planeConfig, subsampling)) {
        return 0;
    }

    int w = imageDimensions.width();
    int h = imageDimensions.height();
    if (origin >= kLeftTop_SkEncodedOrigin) {
        using std::swap;
        swap(w, h);
    }
    auto down2 = [](int x) { return (x + 1)/2; };
    auto down4 = [](int x) { return (x + 3)/4; };
    SkISize uvSize;
    switch (subsampling) {
        case Subsampling::kUnknown: SkUNREACHABLE;

        case Subsampling::k444: uvSize = {      w ,       h }; break;
        case Subsampling::k422: uvSize = {down2(w),       h }; break;
        case Subsampling::k420: uvSize = {down2(w), down2(h)}; break;
        case Subsampling::k440: uvSize = {      w , down2(h)}; break;
        case Subsampling::k411: uvSize = {down4(w),       h }; break;
        case Subsampling::k410: uvSize = {down4(w), down2(h)}; break;
    }
    switch (planeConfig) {
        case PlaneConfig::kUnknown: SkUNREACHABLE;

        case PlaneConfig::kY_U_V:
        case PlaneConfig::kY_V_U:
            planeDimensions[0] = {w, h};
            planeDimensions[1] = planeDimensions[2] = uvSize;
            return 3;

        case PlaneConfig::kY_UV:
        case PlaneConfig::kY_VU:
            planeDimensions[0] = {w, h};
            planeDimensions[1] = uvSize;
            return 2;

        case PlaneConfig::kY_U_V_A:
        case PlaneConfig::kY_V_U_A:
            planeDimensions[0] = planeDimensions[3] = {w, h};
            planeDimensions[1] = planeDimensions[2] = uvSize;
            return 4;

        case PlaneConfig::kY_UV_A:
        case PlaneConfig::kY_VU_A:
            planeDimensions[0] = planeDimensions[2] = {w, h};
            planeDimensions[1] = uvSize;
            return 3;

        case PlaneConfig::kYUV:
        case PlaneConfig::kUYV:
        case PlaneConfig::kYUVA:
        case PlaneConfig::kUYVA:
            planeDimensions[0] = {w, h};
            SkASSERT(planeDimensions[0] == uvSize);
            return 1;
    }
    SkUNREACHABLE;
}

static bool channel_index_to_channel(uint32_t channelFlags,
                                     int channelIdx,
                                     SkColorChannel* channel) {
    switch (channelFlags) {
        case kGray_SkColorChannelFlag:  // For gray returning any of R, G, or B for index 0 is ok.
        case kRed_SkColorChannelFlag:
            if (channelIdx == 0) {
                *channel = SkColorChannel::kR;
                return true;
            }
            return false;
        case kGrayAlpha_SkColorChannelFlags:
            switch (channelIdx) {
                case 0: *channel = SkColorChannel::kR; return true;
                case 1: *channel = SkColorChannel::kA; return true;

                default: return false;
            }
        case kAlpha_SkColorChannelFlag:
            if (channelIdx == 0) {
                *channel = SkColorChannel::kA;
                return true;
            }
            return false;
        case kRG_SkColorChannelFlags:
            if (channelIdx == 0 || channelIdx == 1) {
                *channel = static_cast<SkColorChannel>(channelIdx);
                return true;
            }
            return false;
        case kRGB_SkColorChannelFlags:
            if (channelIdx >= 0 && channelIdx <= 2) {
                *channel = static_cast<SkColorChannel>(channelIdx);
                return true;
            }
            return false;
        case kRGBA_SkColorChannelFlags:
            if (channelIdx >= 0 && channelIdx <= 3) {
                *channel = static_cast<SkColorChannel>(channelIdx);
                return true;
            }
            return false;
        default:
            return false;
    }
}

SkYUVAInfo::YUVALocations SkYUVAInfo::GetYUVALocations(PlaneConfig config,
                                                       const uint32_t* planeChannelFlags) {
    // Like YUVALocation but chanIdx refers to channels by index rather than absolute channel, e.g.
    // A is the 0th channel of an alpha-only texture. We'll use this plus planeChannelFlags to get
    // the actual channel.
    struct PlaneAndIndex {int plane, chanIdx;};
    const PlaneAndIndex* planesAndIndices = nullptr;
    switch (config) {
        case PlaneConfig::kUnknown:
            return {};

        case PlaneConfig::kY_U_V: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {2, 0}, {-1, -1}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kY_V_U: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {2, 0}, {1, 0}, {-1, -1}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kY_UV: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {1, 1}, {-1, -1}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kY_VU: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 1}, {1, 0}, {-1, -1}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kYUV: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {0, 1}, {0, 2}, {-1, -1}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kUYV: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 1}, {0, 0}, {0, 2}, {-1, -1}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kY_U_V_A: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kY_V_U_A: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {2, 0}, {1, 0}, {3, 0}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kY_UV_A: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kY_VU_A: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {1, 1}, {1, 0}, {2, 0}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kYUVA: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 0}, {0, 1}, {0, 2}, {0, 3}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
        case PlaneConfig::kUYVA: {
            static constexpr PlaneAndIndex kPlanesAndIndices[] = {{0, 1}, {0, 0}, {0, 2}, {0, 3}};
            planesAndIndices = kPlanesAndIndices;
            break;
        }
    }
    SkASSERT(planesAndIndices);
    YUVALocations yuvaLocations;
    for (int i = 0; i < SkYUVAInfo::kYUVAChannelCount; ++i) {
        auto [plane, chanIdx] = planesAndIndices[i];
        SkColorChannel channel;
        if (plane >= 0) {
            if (!channel_index_to_channel(planeChannelFlags[plane], chanIdx, &channel)) {
                return {};
            }
            yuvaLocations[i] = {plane, channel};
        } else {
            SkASSERT(i == 3);
            yuvaLocations[i] = {-1, SkColorChannel::kR};
        }
    }
    return yuvaLocations;
}

bool SkYUVAInfo::HasAlpha(PlaneConfig planeConfig) {
    switch (planeConfig) {
        case PlaneConfig::kUnknown: return false;

        case PlaneConfig::kY_U_V:   return false;
        case PlaneConfig::kY_V_U:   return false;
        case PlaneConfig::kY_UV:    return false;
        case PlaneConfig::kY_VU:    return false;
        case PlaneConfig::kYUV:     return false;
        case PlaneConfig::kUYV:     return false;

        case PlaneConfig::kY_U_V_A: return true;
        case PlaneConfig::kY_V_U_A: return true;
        case PlaneConfig::kY_UV_A:  return true;
        case PlaneConfig::kY_VU_A:  return true;
        case PlaneConfig::kYUVA:    return true;
        case PlaneConfig::kUYVA:    return true;
    }
    SkUNREACHABLE;
}

SkYUVAInfo::SkYUVAInfo(SkISize dimensions,
                       PlaneConfig planeConfig,
                       Subsampling subsampling,
                       SkYUVColorSpace yuvColorSpace,
                       SkEncodedOrigin origin,
                       Siting sitingX,
                       Siting sitingY)
        : fDimensions(dimensions)
        , fPlaneConfig(planeConfig)
        , fSubsampling(subsampling)
        , fYUVColorSpace(yuvColorSpace)
        , fOrigin(origin)
        , fSitingX(sitingX)
        , fSitingY(sitingY) {
    if (fDimensions.isEmpty() ||
        !is_plane_config_compatible_with_subsampling(planeConfig, subsampling)) {
        *this = {};
        SkASSERT(!this->isValid());
        return;
    }
    SkASSERT(this->isValid());
}

size_t SkYUVAInfo::computeTotalBytes(const size_t rowBytes[kMaxPlanes],
                                     size_t planeSizes[kMaxPlanes]) const {
    if (!this->isValid()) {
        return 0;
    }
    SkSafeMath safe;
    size_t totalBytes = 0;
    SkISize planeDimensions[kMaxPlanes];
    int n = this->planeDimensions(planeDimensions);
    for (int i = 0; i < n; ++i) {
        SkASSERT(!planeDimensions[i].isEmpty());
        SkASSERT(rowBytes[i]);
        size_t size = safe.mul(rowBytes[i], planeDimensions[i].height());
        if (planeSizes) {
            planeSizes[i] = size;
        }
        totalBytes = safe.add(totalBytes, size);
    }
    if (planeSizes) {
        if (safe.ok()) {
            for (int i = n; i < kMaxPlanes; ++i) {
                planeSizes[i] = 0;
            }
        } else {
            for (int i = 0; n < kMaxPlanes; ++i) {
                planeSizes[i] = SIZE_MAX;
            }
        }
    }

    return safe.ok() ? totalBytes : SIZE_MAX;
}

SkYUVAInfo::YUVALocations SkYUVAInfo::toYUVALocations(const uint32_t* channelFlags) const {
    return GetYUVALocations(fPlaneConfig, channelFlags);
}

SkYUVAInfo SkYUVAInfo::makeSubsampling(SkYUVAInfo::Subsampling subsampling) const {
    return {fDimensions, fPlaneConfig, subsampling, fYUVColorSpace, fOrigin, fSitingX, fSitingY};
}

SkYUVAInfo SkYUVAInfo::makeDimensions(SkISize dimensions) const {
    return {dimensions, fPlaneConfig, fSubsampling, fYUVColorSpace, fOrigin, fSitingX, fSitingY};
}

bool SkYUVAInfo::operator==(const SkYUVAInfo& that) const {
    return fPlaneConfig   == that.fPlaneConfig   &&
           fSubsampling   == that.fSubsampling  &&
           fYUVColorSpace == that.fYUVColorSpace &&
           fDimensions    == that.fDimensions    &&
           fSitingX       == that.fSitingX       &&
           fSitingY       == that.fSitingY       &&
           fOrigin        == that.fOrigin;
}
