/*
 * Copyright (C) 2016 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 "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "compile/PseudolocaleGenerator.h"
#include "compile/Pseudolocalizer.h"

#include <algorithm>

namespace aapt {

std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string,
                                                         Pseudolocalizer::Method method,
                                                         StringPool* pool) {
    Pseudolocalizer localizer(method);

    const StringPiece16 originalText = *string->value->str;

    StyleString localized;

    // Copy the spans. We will update their offsets when we localize.
    localized.spans.reserve(string->value->spans.size());
    for (const StringPool::Span& span : string->value->spans) {
        localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar });
    }

    // The ranges are all represented with a single value. This is the start of one range and
    // end of another.
    struct Range {
        size_t start;

        // Once the new string is localized, these are the pointers to the spans to adjust.
        // Since this struct represents the start of one range and end of another, we have
        // the two pointers respectively.
        uint32_t* updateStart;
        uint32_t* updateEnd;
    };

    auto cmp = [](const Range& r, size_t index) -> bool {
        return r.start < index;
    };

    // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7]
    // The ranges are the spaces in between. In this example, with a total string length of 9,
    // the vector represents: (0,1], (2,4], (5,6], (7,9]
    //
    std::vector<Range> ranges;
    ranges.push_back(Range{ 0 });
    ranges.push_back(Range{ originalText.size() - 1 });
    for (size_t i = 0; i < string->value->spans.size(); i++) {
        const StringPool::Span& span = string->value->spans[i];

        // Insert or update the Range marker for the start of this span.
        auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp);
        if (iter != ranges.end() && iter->start == span.firstChar) {
            iter->updateStart = &localized.spans[i].firstChar;
        } else {
            ranges.insert(iter,
                          Range{ span.firstChar, &localized.spans[i].firstChar, nullptr });
        }

        // Insert or update the Range marker for the end of this span.
        iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp);
        if (iter != ranges.end() && iter->start == span.lastChar) {
            iter->updateEnd = &localized.spans[i].lastChar;
        } else {
            ranges.insert(iter,
                          Range{ span.lastChar, nullptr, &localized.spans[i].lastChar });
        }
    }

    localized.str += localizer.start();

    // Iterate over the ranges and localize each section.
    for (size_t i = 0; i < ranges.size(); i++) {
        const size_t start = ranges[i].start;
        size_t len = originalText.size() - start;
        if (i + 1 < ranges.size()) {
            len = ranges[i + 1].start - start;
        }

        if (ranges[i].updateStart) {
            *ranges[i].updateStart = localized.str.size();
        }

        if (ranges[i].updateEnd) {
            *ranges[i].updateEnd = localized.str.size();
        }

        localized.str += localizer.text(originalText.substr(start, len));
    }

    localized.str += localizer.end();

    std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>(
            pool->makeRef(localized));
    localizedString->setSource(string->getSource());
    return localizedString;
}

namespace {

struct Visitor : public RawValueVisitor {
    StringPool* mPool;
    Pseudolocalizer::Method mMethod;
    Pseudolocalizer mLocalizer;

    // Either value or item will be populated upon visiting the value.
    std::unique_ptr<Value> mValue;
    std::unique_ptr<Item> mItem;

    Visitor(StringPool* pool, Pseudolocalizer::Method method) :
            mPool(pool), mMethod(method), mLocalizer(method) {
    }

    void visit(Array* array) override {
        std::unique_ptr<Array> localized = util::make_unique<Array>();
        localized->items.resize(array->items.size());
        for (size_t i = 0; i < array->items.size(); i++) {
            Visitor subVisitor(mPool, mMethod);
            array->items[i]->accept(&subVisitor);
            if (subVisitor.mItem) {
                localized->items[i] = std::move(subVisitor.mItem);
            } else {
                localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool));
            }
        }
        localized->setSource(array->getSource());
        localized->setWeak(true);
        mValue = std::move(localized);
    }

    void visit(Plural* plural) override {
        std::unique_ptr<Plural> localized = util::make_unique<Plural>();
        for (size_t i = 0; i < plural->values.size(); i++) {
            Visitor subVisitor(mPool, mMethod);
            if (plural->values[i]) {
                plural->values[i]->accept(&subVisitor);
                if (subVisitor.mValue) {
                    localized->values[i] = std::move(subVisitor.mItem);
                } else {
                    localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool));
                }
            }
        }
        localized->setSource(plural->getSource());
        localized->setWeak(true);
        mValue = std::move(localized);
    }

    void visit(String* string) override {
        if (!string->isTranslateable()) {
            return;
        }

        std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) +
                mLocalizer.end();
        std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
        localized->setSource(string->getSource());
        localized->setWeak(true);
        mItem = std::move(localized);
    }

    void visit(StyledString* string) override {
        if (!string->isTranslateable()) {
            return;
        }

        mItem = pseudolocalizeStyledString(string, mMethod, mPool);
        mItem->setWeak(true);
    }
};

ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
                                              Pseudolocalizer::Method m) {
    ConfigDescription modified = base;
    switch (m) {
    case Pseudolocalizer::Method::kAccent:
        modified.language[0] = 'e';
        modified.language[1] = 'n';
        modified.country[0] = 'X';
        modified.country[1] = 'A';
        break;

    case Pseudolocalizer::Method::kBidi:
        modified.language[0] = 'a';
        modified.language[1] = 'r';
        modified.country[0] = 'X';
        modified.country[1] = 'B';
        break;
    default:
        break;
    }
    return modified;
}

void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
                            ResourceConfigValue* originalValue,
                            StringPool* pool,
                            ResourceEntry* entry) {
    Visitor visitor(pool, method);
    originalValue->value->accept(&visitor);

    std::unique_ptr<Value> localizedValue;
    if (visitor.mValue) {
        localizedValue = std::move(visitor.mValue);
    } else if (visitor.mItem) {
        localizedValue = std::move(visitor.mItem);
    }

    if (!localizedValue) {
        return;
    }

    ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
            originalValue->config, method);

    ResourceConfigValue* newConfigValue = entry->findOrCreateValue(
            configWithAccent, originalValue->product);
    if (!newConfigValue->value) {
        // Only use auto-generated pseudo-localization if none is defined.
        newConfigValue->value = std::move(localizedValue);
    }
}

} // namespace

bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
    for (auto& package : table->packages) {
        for (auto& type : package->types) {
            for (auto& entry : type->entries) {
                std::vector<ResourceConfigValue*> values = entry->findAllValues(
                        ConfigDescription::defaultConfig());
                for (ResourceConfigValue* value : values) {
                    pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
                                           &table->stringPool, entry.get());
                    pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
                                           &table->stringPool, entry.get());
                }
            }
        }
    }
    return true;
}

} // namespace aapt