/******************************************************************************
 *
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *****************************************************************************
 */
#include <fuzzer/FuzzedDataProvider.h>
#include <minikin/Hyphenator.h>

#include <iostream>
#include <string>

#include "HyphenatorMap.h"
#include "Locale.h"
#include "LocaleListCache.h"
#include "MinikinInternal.h"
#include "UnicodeUtils.h"
#include "minikin/LocaleList.h"
#include "minikin/U16StringPiece.h"

using namespace minikin;

const EndHyphenEdit EndHyphenEdits[] = {
        EndHyphenEdit::NO_EDIT,
        EndHyphenEdit::REPLACE_WITH_HYPHEN,
        EndHyphenEdit::INSERT_HYPHEN,
        EndHyphenEdit::INSERT_ARMENIAN_HYPHEN,
        EndHyphenEdit::INSERT_MAQAF,
        EndHyphenEdit::INSERT_UCAS_HYPHEN,
        EndHyphenEdit::INSERT_ZWJ_AND_HYPHEN,
};

const StartHyphenEdit StartHyphenEdits[] = {
        StartHyphenEdit::NO_EDIT,
        StartHyphenEdit::INSERT_HYPHEN,
        StartHyphenEdit::INSERT_ZWJ,
};

const HyphenationType HyphenationTypes[] = {
        HyphenationType::DONT_BREAK,
        HyphenationType::BREAK_AND_INSERT_HYPHEN,
        HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN,
        HyphenationType::BREAK_AND_INSERT_MAQAF,
        HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN,
        HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN,
        HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN,
        HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE,
        HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ,
};

uint16_t specialChars[] = {
        0x000A, 0x000D, 0x0009, 0x002D, 0x00A0, 0x00AD,
        0x00B7, 0x058A, 0x05BE, 0x1400, 0x200D, 0x2010,
};

const uint16_t MAX_STR_LEN = 256;

// Function to generate StringPiece from a vector by pushing random valued elements using fdp
U16StringPiece generateStringPiece(FuzzedDataProvider* fdp) {
    uint16_t size = fdp->ConsumeIntegralInRange<uint16_t>(0, (fdp->remaining_bytes() / 3));

    std::vector<uint16_t> v;
    for (uint16_t i = 0; i < size; ++i) {
        // To randomize the insertion of special characters
        if (fdp->ConsumeBool()) {
            v.push_back(fdp->PickValueInArray(specialChars));
        } else {
            v.push_back(fdp->ConsumeIntegral<uint16_t>());
        }
    }

    return U16StringPiece(v);
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
    FuzzedDataProvider fdp(data, size);

    uint8_t minPrefix = fdp.ConsumeIntegral<size_t>();
    uint8_t minSuffix = fdp.ConsumeIntegral<size_t>();
    std::string locale = fdp.ConsumeRandomLengthString(MAX_STR_LEN);
    std::vector<uint8_t> patternData(fdp.ConsumeIntegralInRange<uint32_t>(0, 256));

    Hyphenator* hyphenator = Hyphenator::loadBinary(&patternData[0], patternData.size(), minPrefix,
                                                    minSuffix, locale);

    // To randomize the API calls
    while (fdp.remaining_bytes() > 0) {
        auto func = fdp.PickValueInArray<const std::function<void()>>({
                [&]() { addHyphenator(locale, hyphenator); },
                [&]() {
                    auto fromLocaleString = fdp.ConsumeRandomLengthString(MAX_STR_LEN);
                    auto toLocaleString = fdp.ConsumeRandomLengthString(MAX_STR_LEN);
                    addHyphenatorAlias(fromLocaleString, toLocaleString);
                },
                [&]() {
                    packHyphenEdit(fdp.PickValueInArray(StartHyphenEdits),
                                   fdp.PickValueInArray(EndHyphenEdits));
                },
                [&]() {
                    auto textBuf = generateStringPiece(&fdp);
                    std::vector<HyphenationType> result;
                    result.push_back(fdp.PickValueInArray(HyphenationTypes));
                    hyphenator->hyphenate(textBuf, &result);
                },
                // Get the list of locales and invoke the API for each one of them
                [&]() {
                    uint32_t id = registerLocaleList(fdp.ConsumeRandomLengthString(MAX_STR_LEN));
                    const LocaleList& locales = LocaleListCache::getById(id);
                    for (size_t i = 0; i < locales.size(); ++i) {
                        HyphenatorMap::lookup(locales[i]);
                    }
                },
                [&]() { getHyphenString(endHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); },
                [&]() { getHyphenString(startHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); },
                [&]() { isInsertion(endHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); },
                [&]() { isInsertion(startHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); },
                [&]() { editForThisLine(fdp.PickValueInArray(HyphenationTypes)); },
                [&]() { editForNextLine(fdp.PickValueInArray(HyphenationTypes)); },
                [&]() { isReplacement(endHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); },
        });

        func();
    }

    return 0;
}
