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

#include "modules/svg/include/SkSVGOpenTypeSVGDecoder.h"

#include "include/codec/SkCodec.h"
#include "include/codec/SkJpegDecoder.h"
#include "include/codec/SkPngDecoder.h"
#include "include/core/SkColor.h"
#include "include/core/SkData.h"
#include "include/core/SkOpenTypeSVGDecoder.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkSpan.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypes.h"
#include "modules/skresources/include/SkResources.h"
#include "modules/svg/include/SkSVGAttribute.h"
#include "modules/svg/include/SkSVGDOM.h"
#include "modules/svg/include/SkSVGRenderContext.h"
#include "modules/svg/include/SkSVGTypes.h"
#include "src/base/SkBase64.h"
#include "src/core/SkEnumerate.h"
#include "src/core/SkTHash.h"

#include <array>
#include <cstring>
#include <memory>
#include <utility>

using namespace skia_private;

namespace {
class DataResourceProvider final : public skresources::ResourceProvider {
public:
    static sk_sp<skresources::ResourceProvider> Make() {
        return sk_sp<skresources::ResourceProvider>(new DataResourceProvider());
    }

    sk_sp<skresources::ImageAsset> loadImageAsset(const char rpath[],
                                                  const char rname[],
                                                  const char rid[]) const override {
        if (auto data = decode_datauri("data:image/", rname)) {
            std::unique_ptr<SkCodec> codec = nullptr;
            if (SkPngDecoder::IsPng(data->bytes(), data->size())) {
                codec = SkPngDecoder::Decode(data, nullptr);
            } else if (SkJpegDecoder::IsJpeg(data->bytes(), data->size())) {
                codec = SkJpegDecoder::Decode(data, nullptr);
            } else {
                // The spec says only JPEG or PNG should be used to encode the embedded data.
                // https://learn.microsoft.com/en-us/typography/opentype/spec/svg#svg-capability-requirements-and-restrictions
                SkDEBUGFAIL("Unsupported codec");
                return nullptr;
            }
            if (!codec) {
                return nullptr;
            }
            return skresources::MultiFrameImageAsset::Make(std::move(codec));
        }
        return nullptr;
    }

private:
    DataResourceProvider() = default;

    static sk_sp<SkData> decode_datauri(const char prefix[], const char uri[]) {
        // We only handle B64 encoded image dataURIs: data:image/<type>;base64,<data>
        // (https://en.wikipedia.org/wiki/Data_URI_scheme)
        static constexpr char kDataURIEncodingStr[] = ";base64,";

        const size_t prefixLen = strlen(prefix);
        if (strncmp(uri, prefix, prefixLen) != 0) {
            return nullptr;
        }

        const char* encoding = strstr(uri + prefixLen, kDataURIEncodingStr);
        if (!encoding) {
            return nullptr;
        }

        const char* b64Data = encoding + std::size(kDataURIEncodingStr) - 1;
        size_t b64DataLen = strlen(b64Data);
        size_t dataLen;
        if (SkBase64::Decode(b64Data, b64DataLen, nullptr, &dataLen) != SkBase64::kNoError) {
            return nullptr;
        }

        sk_sp<SkData> data = SkData::MakeUninitialized(dataLen);
        void* rawData = data->writable_data();
        if (SkBase64::Decode(b64Data, b64DataLen, rawData, &dataLen) != SkBase64::kNoError) {
            return nullptr;
        }

        return data;
    }

    using INHERITED = ResourceProvider;
};
}  // namespace

SkSVGOpenTypeSVGDecoder::SkSVGOpenTypeSVGDecoder(sk_sp<SkSVGDOM> skSvg, size_t approximateSize)
    : fSkSvg(std::move(skSvg))
    , fApproximateSize(approximateSize)
{}

SkSVGOpenTypeSVGDecoder::~SkSVGOpenTypeSVGDecoder() = default;

std::unique_ptr<SkOpenTypeSVGDecoder> SkSVGOpenTypeSVGDecoder::Make(const uint8_t* svg,
                                                                    size_t svgLength) {
    std::unique_ptr<SkStreamAsset> stream = SkMemoryStream::MakeDirect(svg, svgLength);
    if (!stream) {
        return nullptr;
    }
    SkSVGDOM::Builder builder;
    builder.setResourceProvider(DataResourceProvider::Make());
    // We shouldn't need to set this builder's font manager or shaping utils because hopefully
    // the SVG we are decoding doesn't itself have <text> tags.
    sk_sp<SkSVGDOM> skSvg = builder.make(*stream);
    if (!skSvg) {
        return nullptr;
    }
    return std::unique_ptr<SkOpenTypeSVGDecoder>(
        new SkSVGOpenTypeSVGDecoder(std::move(skSvg), svgLength));
}

size_t SkSVGOpenTypeSVGDecoder::approximateSize() {
    // TODO
    return fApproximateSize;
}

bool SkSVGOpenTypeSVGDecoder::render(SkCanvas& canvas, int upem, SkGlyphID glyphId,
                                     SkColor foregroundColor, SkSpan<SkColor> palette) {
    SkSize emSize = SkSize::Make(SkScalar(upem), SkScalar(upem));
    fSkSvg->setContainerSize(emSize);

    SkSVGPresentationContext pctx;
    pctx.fInherited.fColor.set(foregroundColor);

    THashMap<SkString, SkSVGColorType> namedColors;
    if (!palette.empty()) {
        for (auto&& [i, color] : SkMakeEnumerate(palette)) {
            constexpr const size_t colorStringLen = sizeof("color") - 1;
            char colorIdString[colorStringLen + kSkStrAppendU32_MaxSize + 1] = "color";
            *SkStrAppendU32(colorIdString + colorStringLen, i) = 0;

            namedColors.set(SkString(colorIdString), color);
        }
        pctx.fNamedColors = &namedColors;
    }

    constexpr const size_t glyphStringLen = sizeof("glyph") - 1;
    char glyphIdString[glyphStringLen + kSkStrAppendU32_MaxSize + 1] = "glyph";
    *SkStrAppendU32(glyphIdString + glyphStringLen, glyphId) = 0;

    fSkSvg->renderNode(&canvas, pctx, glyphIdString);
    return true;
}
