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

#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/core/SkTypeface.h"
#include "src/core/SkFontDescriptor.h"
#include "src/core/SkOpts.h"
#include "src/sfnt/SkOTUtils.h"
#include "src/utils/SkUTF.h"

#include "SkWhitelistChecksums.inc"

#define WHITELIST_DEBUG 0

extern void WhitelistSerializeTypeface(const SkTypeface*, SkWStream* );
sk_sp<SkTypeface> WhitelistDeserializeTypeface(SkStream* );
extern bool CheckChecksums();
extern bool GenerateChecksums();

#if WHITELIST_DEBUG
static bool timesNewRomanSerializedNameOnly = false;
#endif

#define SUBNAME_PREFIX "sk_"

static bool font_name_is_local(const char* fontName, SkFontStyle style) {
    if (!strcmp(fontName, "DejaVu Sans")) {
        return true;
    }
    sk_sp<SkTypeface> defaultFace(SkTypeface::MakeFromName(nullptr, style));
    sk_sp<SkTypeface> foundFace(SkTypeface::MakeFromName(fontName, style));
    return defaultFace != foundFace;
}

static int whitelist_name_index(const SkTypeface* tf) {

    SkString fontNameStr;
    sk_sp<SkTypeface::LocalizedStrings> nameIter =
        SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(*tf);
    SkTypeface::LocalizedString familyNameLocalized;
    while (nameIter->next(&familyNameLocalized)) {
        fontNameStr = familyNameLocalized.fString;
        // check against permissible list of names
        for (int i = 0; i < whitelistCount; ++i) {
            if (fontNameStr.equals(whitelist[i].fFontName)) {
                return i;
            }
        }
    }
#if WHITELIST_DEBUG
    sk_sp<SkTypeface::LocalizedStrings> debugIter =
        SkOTUtils::LocalizedStrings_NameTable::MakeForFamilyNames(*tf);
    while (debugIter->next(&familyNameLocalized)) {
        SkDebugf("no match fontName=\"%s\"\n", familyNameLocalized.fString.c_str());
    }
#endif
    return -1;
}

static uint32_t compute_checksum(const SkTypeface* tf) {
    std::unique_ptr<SkFontData> fontData = tf->makeFontData();
    if (!fontData) {
        return 0;
    }
    SkStreamAsset* fontStream = fontData->getStream();
    if (!fontStream) {
        return 0;
    }
    SkTDArray<char> data;
    size_t length = fontStream->getLength();
    if (!length) {
        return 0;
    }
    data.setCount((int) length);
    if (!fontStream->peek(data.begin(), length)) {
        return 0;
    }
    return SkOpts::hash(data.begin(), length);
}

static void serialize_sub(const char* fontName, SkFontStyle style, SkWStream* wstream) {
    SkFontDescriptor desc;
    SkString subName(SUBNAME_PREFIX);
    subName.append(fontName);
    const char* familyName = subName.c_str();
    desc.setFamilyName(familyName);
    desc.setStyle(style);
    desc.serialize(wstream);
#if WHITELIST_DEBUG
    for (int i = 0; i < whitelistCount; ++i) {
        if (!strcmp(fontName, whitelist[i].fFontName)) {
            if (!whitelist[i].fSerializedSub) {
                whitelist[i].fSerializedSub = true;
                SkDebugf("%s %s\n", __FUNCTION__, familyName);
            }
            break;
        }
    }
#endif
}

static bool is_local(const SkTypeface* tf) {
    bool isLocal = false;
    SkFontDescriptor desc;
    tf->getFontDescriptor(&desc, &isLocal);
    return isLocal;
}

static void serialize_full(const SkTypeface* tf, SkWStream* wstream) {
    bool isLocal = false;
    SkFontDescriptor desc;
    tf->getFontDescriptor(&desc, &isLocal);

    // Embed font data if it's a local font.
    if (isLocal && !desc.hasFontData()) {
        desc.setFontData(tf->makeFontData());
    }
    desc.serialize(wstream);
}

static void serialize_name_only(const SkTypeface* tf, SkWStream* wstream) {
    bool isLocal = false;
    SkFontDescriptor desc;
    tf->getFontDescriptor(&desc, &isLocal);
    SkASSERT(!isLocal);
#if WHITELIST_DEBUG
    const char* familyName = desc.getFamilyName();
    if (familyName) {
        if (!strcmp(familyName, "Times New Roman")) {
            if (!timesNewRomanSerializedNameOnly) {
                timesNewRomanSerializedNameOnly = true;
                SkDebugf("%s %s\n", __FUNCTION__, familyName);
            }
        } else {
            for (int i = 0; i < whitelistCount; ++i) {
                if (!strcmp(familyName, whitelist[i].fFontName)) {
                    if (!whitelist[i].fSerializedNameOnly) {
                        whitelist[i].fSerializedNameOnly = true;
                        SkDebugf("%s %s\n", __FUNCTION__, familyName);
                    }
                    break;
                }
            }
        }
    }
#endif
    desc.serialize(wstream);
}

void WhitelistSerializeTypeface(const SkTypeface* tf, SkWStream* wstream) {
    if (!is_local(tf)) {
        serialize_name_only(tf, wstream);
        return;
    }
    int whitelistIndex = whitelist_name_index(tf);
    if (whitelistIndex < 0) {
        serialize_full(tf, wstream);
        return;
    }
    const char* fontName = whitelist[whitelistIndex].fFontName;
    if (!font_name_is_local(fontName, tf->fontStyle())) {
#if WHITELIST_DEBUG
        SkDebugf("name not found locally \"%s\" style=%d\n", fontName, tf->style());
#endif
        serialize_full(tf, wstream);
        return;
    }
    uint32_t checksum = compute_checksum(tf);
    if (whitelist[whitelistIndex].fChecksum != checksum) {
#if WHITELIST_DEBUG
        if (whitelist[whitelistIndex].fChecksum) {
            SkDebugf("!!! checksum changed !!!\n");
        }
        SkDebugf("checksum updated\n");
        SkDebugf("    { \"%s\", 0x%08x },\n", fontName, checksum);
#endif
        whitelist[whitelistIndex].fChecksum = checksum;
    }
    serialize_sub(fontName, tf->fontStyle(), wstream);
}

sk_sp<SkTypeface> WhitelistDeserializeTypeface(SkStream* stream) {
    SkFontDescriptor desc;
    if (!SkFontDescriptor::Deserialize(stream, &desc)) {
        return nullptr;
    }

    std::unique_ptr<SkFontData> data = desc.detachFontData();
    if (data) {
        sk_sp<SkTypeface> typeface(SkTypeface::MakeFromFontData(std::move(data)));
        if (typeface) {
            return typeface;
        }
    }
    const char* familyName = desc.getFamilyName();
    if (!strncmp(SUBNAME_PREFIX, familyName, sizeof(SUBNAME_PREFIX) - 1)) {
        familyName += sizeof(SUBNAME_PREFIX) - 1;
    }
    return SkTypeface::MakeFromName(familyName, desc.getStyle());
}

bool CheckChecksums() {
    for (int i = 0; i < whitelistCount; ++i) {
        const char* fontName = whitelist[i].fFontName;
        sk_sp<SkTypeface> tf(SkTypeface::MakeFromName(fontName, SkFontStyle()));
        uint32_t checksum = compute_checksum(tf.get());
        if (whitelist[i].fChecksum != checksum) {
            return false;
        }
    }
    return true;
}

const char checksumFileName[] = "SkWhitelistChecksums.inc";

const char checksumHeader[] =
"/*"                                                                        "\n"
" * Copyright 2015 Google Inc."                                             "\n"
" *"                                                                        "\n"
" * Use of this source code is governed by a BSD-style license that can be" "\n"
" * found in the LICENSE file."                                             "\n"
" *"                                                                        "\n"
" * %s() in %s generated %s."                                               "\n"
" * Run 'whitelist_typefaces --generate' to create anew."                   "\n"
" */"                                                                       "\n"
""                                                                          "\n"
"#include \"SkTDArray.h\""                                                  "\n"
""                                                                          "\n"
"struct Whitelist {"                                                        "\n"
"    const char* fFontName;"                                                "\n"
"    uint32_t fChecksum;"                                                   "\n"
"    bool fSerializedNameOnly;"                                             "\n"
"    bool fSerializedSub;"                                                  "\n"
"};"                                                                        "\n"
""                                                                          "\n"
"static Whitelist whitelist[] = {"                                          "\n";

const char checksumEntry[] =
"    { \"%s\", 0x%08x, false, false },"                                     "\n";

const char checksumTrailer[] =
"};"                                                                        "\n"
""                                                                          "\n"
"static const int whitelistCount = (int) SK_ARRAY_COUNT(whitelist);"        "\n";


#include "src/core/SkOSFile.h"

bool GenerateChecksums() {
    FILE* file = sk_fopen(checksumFileName, kWrite_SkFILE_Flag);
    if (!file) {
        SkDebugf("Can't open %s for writing.\n", checksumFileName);
        return false;
    }
    SkString line;
    line.printf(checksumHeader, __FUNCTION__, __FILE__, checksumFileName);
    sk_fwrite(line.c_str(), line.size(), file);
    for (int i = 0; i < whitelistCount; ++i) {
        const char* fontName = whitelist[i].fFontName;
        sk_sp<SkTypeface> tf(SkTypeface::MakeFromName(fontName, SkFontStyle()));
        uint32_t checksum = compute_checksum(tf.get());
        line.printf(checksumEntry, fontName, checksum);
        sk_fwrite(line.c_str(), line.size(), file);
    }
    sk_fwrite(checksumTrailer, sizeof(checksumTrailer) - 1, file);
    sk_fclose(file);
    return true;
}
