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

#include "modules/skshaper/include/SkShaper.h"

#ifdef SK_BUILD_FOR_MAC
#import <ApplicationServices/ApplicationServices.h>
#endif

#ifdef SK_BUILD_FOR_IOS
#include <CoreText/CoreText.h>
#include <CoreText/CTFontManager.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
#endif

#include "include/ports/SkTypeface_mac.h"
#include "include/private/base/SkTemplates.h"
#include "src/base/SkUTF.h"
#include "src/utils/mac/SkCGBase.h"
#include "src/utils/mac/SkUniqueCFRef.h"

#include <vector>
#include <utility>

using namespace skia_private;

class SkShaper_CoreText : public SkShaper {
public:
    SkShaper_CoreText() {}
private:
    void shape(const char* utf8, size_t utf8Bytes,
               const SkFont& srcFont,
               bool leftToRight,
               SkScalar width,
               RunHandler*) const override;

    void shape(const char* utf8, size_t utf8Bytes,
               FontRunIterator&,
               BiDiRunIterator&,
               ScriptRunIterator&,
               LanguageRunIterator&,
               SkScalar width,
               RunHandler*) const override;

    void shape(const char* utf8, size_t utf8Bytes,
               FontRunIterator&,
               BiDiRunIterator&,
               ScriptRunIterator&,
               LanguageRunIterator&,
               const Feature*, size_t featureSize,
               SkScalar width,
               RunHandler*) const override;
};

std::unique_ptr<SkShaper> SkShaper::MakeCoreText() {
    return std::make_unique<SkShaper_CoreText>();
}

void SkShaper_CoreText::shape(const char* utf8, size_t utf8Bytes,
                              FontRunIterator& font,
                              BiDiRunIterator& bidi,
                              ScriptRunIterator&,
                              LanguageRunIterator&,
                              SkScalar width,
                              RunHandler* handler) const
{
    SkFont skfont;
    if (!font.atEnd()) {
        font.consume();
        skfont = font.currentFont();
    } else {
        skfont.setTypeface(sk_ref_sp(skfont.getTypefaceOrDefault()));
    }
    SkASSERT(skfont.getTypeface());
    bool skbidi = 0;
    if (!bidi.atEnd()) {
        bidi.consume();
        skbidi = (bidi.currentLevel() % 2) == 0;
    }
    return this->shape(utf8, utf8Bytes, skfont, skbidi, width, handler);
}

void SkShaper_CoreText::shape(const char* utf8, size_t utf8Bytes,
                              FontRunIterator& font,
                              BiDiRunIterator& bidi,
                              ScriptRunIterator&,
                              LanguageRunIterator&,
                              const Feature*, size_t,
                              SkScalar width,
                              RunHandler* handler) const {
    font.consume();
    SkASSERT(font.currentFont().getTypeface());
    bidi.consume();
    return this->shape(utf8, utf8Bytes, font.currentFont(), (bidi.currentLevel() % 2) == 0,
                       width, handler);
}

// CTFramesetter/CTFrame can do this, but require version 10.14
class LineBreakIter {
    CTTypesetterRef fTypesetter;
    double          fWidth;
    CFIndex         fStart;

public:
    LineBreakIter(CTTypesetterRef ts, SkScalar width) : fTypesetter(ts), fWidth(width) {
        fStart = 0;
    }

    SkUniqueCFRef<CTLineRef> nextLine() {
        CFRange stringRange {fStart, CTTypesetterSuggestLineBreak(fTypesetter, fStart, fWidth)};
        if (stringRange.length == 0) {
            return nullptr;
        }
        fStart += stringRange.length;
        return SkUniqueCFRef<CTLineRef>(CTTypesetterCreateLine(fTypesetter, stringRange));
    }
};

static void dict_add_double(CFMutableDictionaryRef d, const void* name, double value) {
    SkUniqueCFRef<CFNumberRef> number(
            CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value));
    CFDictionaryAddValue(d, name, number.get());
}

static SkUniqueCFRef<CTFontRef> create_ctfont_from_font(const SkFont& font) {
    auto typeface = font.getTypefaceOrDefault();
    auto ctfont = SkTypeface_GetCTFontRef(typeface);
    return SkUniqueCFRef<CTFontRef>(
            CTFontCreateCopyWithAttributes(ctfont, font.getSize(), nullptr, nullptr));
}

static SkFont run_to_font(CTRunRef run, const SkFont& orig) {
    CFDictionaryRef attr = CTRunGetAttributes(run);
    CTFontRef ct = (CTFontRef)CFDictionaryGetValue(attr, kCTFontAttributeName);
    if (!ct) {
        SkDebugf("no ctfont in Run Attributes\n");
        CFShow(attr);
        return orig;
    }
    // Do I need to add a local cache, or allow the caller to manage this lookup?
    SkFont font(orig);
    font.setTypeface(SkMakeTypefaceFromCTFont(ct));
    return font;
}

namespace {
class UTF16ToUTF8IndicesMap {
public:
    /** Builds a UTF-16 to UTF-8 indices map; the text is not retained
     * @return true if successful
     */
    bool setUTF8(const char* utf8, size_t size) {
        SkASSERT(utf8 != nullptr);

        if (!SkTFitsIn<int32_t>(size)) {
            SkDEBUGF("UTF16ToUTF8IndicesMap: text too long");
            return false;
        }

        auto utf16Size = SkUTF::UTF8ToUTF16(nullptr, 0, utf8, size);
        if (utf16Size < 0) {
            SkDEBUGF("UTF16ToUTF8IndicesMap: Invalid utf8 input");
            return false;
        }

        // utf16Size+1 to also store the size
        fUtf16ToUtf8Indices = std::vector<size_t>(utf16Size + 1);
        auto utf16 = fUtf16ToUtf8Indices.begin();
        auto utf8Begin = utf8, utf8End = utf8 + size;
        while (utf8Begin < utf8End) {
            *utf16 = utf8Begin - utf8;
            utf16 += SkUTF::ToUTF16(SkUTF::NextUTF8(&utf8Begin, utf8End), nullptr);
        }
        *utf16 = size;

        return true;
    }

    size_t mapIndex(size_t index) const {
        SkASSERT(index < fUtf16ToUtf8Indices.size());
        return fUtf16ToUtf8Indices[index];
    }

    std::pair<size_t, size_t> mapRange(size_t start, size_t size) const {
        auto utf8Start = mapIndex(start);
        return {utf8Start, mapIndex(start + size) - utf8Start};
    }
private:
    std::vector<size_t> fUtf16ToUtf8Indices;
};
} // namespace

// kCTTrackingAttributeName not available until 10.12
const CFStringRef kCTTracking_AttributeName = CFSTR("CTTracking");

void SkShaper_CoreText::shape(const char* utf8, size_t utf8Bytes,
                              const SkFont& font,
                              bool /* leftToRight */,
                              SkScalar width,
                              RunHandler* handler) const {
    SkUniqueCFRef<CFStringRef> textString(
            CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)utf8, utf8Bytes,
                                    kCFStringEncodingUTF8, false));

    UTF16ToUTF8IndicesMap utf8IndicesMap;
    if (!utf8IndicesMap.setUTF8(utf8, utf8Bytes)) {
        return;
    }

    SkUniqueCFRef<CTFontRef> ctfont = create_ctfont_from_font(font);

    SkUniqueCFRef<CFMutableDictionaryRef> attr(
            CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                      &kCFTypeDictionaryKeyCallBacks,
                                      &kCFTypeDictionaryValueCallBacks));
    CFDictionaryAddValue(attr.get(), kCTFontAttributeName, ctfont.get());
    if (false) {
        // trying to see what these affect
        dict_add_double(attr.get(), kCTTracking_AttributeName, 1);
        dict_add_double(attr.get(), kCTKernAttributeName, 0.0);
    }

    SkUniqueCFRef<CFAttributedStringRef> attrString(
            CFAttributedStringCreate(kCFAllocatorDefault, textString.get(), attr.get()));

    SkUniqueCFRef<CTTypesetterRef> typesetter(
            CTTypesetterCreateWithAttributedString(attrString.get()));

    // We have to compute RunInfos in a loop, and then reuse them in a 2nd loop,
    // so we store them in an array (we reuse the array's storage for each line).
    std::vector<SkFont> fontStorage;
    std::vector<SkShaper::RunHandler::RunInfo> infos;

    LineBreakIter iter(typesetter.get(), width);
    while (SkUniqueCFRef<CTLineRef> line = iter.nextLine()) {
        CFArrayRef run_array = CTLineGetGlyphRuns(line.get());
        CFIndex runCount = CFArrayGetCount(run_array);
        if (runCount == 0) {
            continue;
        }
        handler->beginLine();
        fontStorage.clear();
        fontStorage.reserve(runCount); // ensure the refs won't get invalidated
        infos.clear();
        for (CFIndex j = 0; j < runCount; ++j) {
            CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(run_array, j);
            CFIndex runGlyphs = CTRunGetGlyphCount(run);

            SkASSERT(sizeof(CGGlyph) == sizeof(uint16_t));

            AutoSTArray<4096, CGSize> advances(runGlyphs);
            CTRunGetAdvances(run, {0, runGlyphs}, advances.data());
            SkScalar adv = 0;
            for (CFIndex k = 0; k < runGlyphs; ++k) {
                adv += advances[k].width;
            }

            CFRange cfRange = CTRunGetStringRange(run);
            auto range = utf8IndicesMap.mapRange(cfRange.location, cfRange.length);

            fontStorage.push_back(run_to_font(run, font));
            infos.push_back({
                fontStorage.back(), // info just stores a ref to the font
                0,                  // need fBidiLevel
                {adv, 0},
                (size_t)runGlyphs,
                {range.first, range.second},
            });
            handler->runInfo(infos.back());
        }
        handler->commitRunInfo();

        // Now loop through again and fill in the buffers
        SkScalar lineAdvance = 0;
        for (CFIndex j = 0; j < runCount; ++j) {
            const auto& info = infos[j];
            auto buffer = handler->runBuffer(info);

            CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(run_array, j);
            CFIndex runGlyphs = info.glyphCount;
            SkASSERT(CTRunGetGlyphCount(run) == (CFIndex)info.glyphCount);

            CTRunGetGlyphs(run, {0, runGlyphs}, buffer.glyphs);

            AutoSTArray<4096, CGPoint> positions(runGlyphs);
            CTRunGetPositions(run, {0, runGlyphs}, positions.data());
            AutoSTArray<4096, CFIndex> indices;
            if (buffer.clusters) {
                indices.reset(runGlyphs);
                CTRunGetStringIndices(run, {0, runGlyphs}, indices.data());
            }

            for (CFIndex k = 0; k < runGlyphs; ++k) {
                buffer.positions[k] = {
                    buffer.point.fX + SkScalarFromCGFloat(positions[k].x) - lineAdvance,
                    buffer.point.fY,
                };
                if (buffer.offsets) {
                    buffer.offsets[k] = {0, 0}; // offset relative to the origin for this glyph
                }
                if (buffer.clusters) {
                    buffer.clusters[k] = utf8IndicesMap.mapIndex(indices[k]);
                }
            }
            handler->commitRunBuffer(info);
            lineAdvance += info.fAdvance.fX;
        }
        handler->commitLine();
    }
}
