// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2014-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*
* simpleformattertest.cpp
*
********************************************************************************
*/

#include "unicode/msgfmt.h"
#include "unicode/unistr.h"
#include "cstring.h"
#include "intltest.h"
#include "unicode/simpleformatter.h"

class SimpleFormatterTest : public IntlTest {
public:
    SimpleFormatterTest() {
    }
    void TestNoArguments();
    void TestSyntaxErrors();
    void TestOneArgument();
    void TestBigArgument();
    void TestManyArguments();
    void TestTooFewArgumentValues();
    void TestBadArguments();
    void TestTextWithNoArguments();
    void TestFormatReplaceNoOptimization();
    void TestFormatReplaceNoOptimizationLeadingText();
    void TestFormatReplaceOptimization();
    void TestFormatReplaceNoOptimizationLeadingArgumentUsedTwice();
    void TestFormatReplaceOptimizationNoOffsets();
    void TestFormatReplaceNoOptimizationNoOffsets();
    void TestQuotingLikeMessageFormat();
    void runIndexedTest(int32_t index, UBool exec, const char*& name, char* par = nullptr) override;

private:
    void verifyOffsets(
            const int32_t *expected,
            const int32_t *actual,
            int32_t count);
};

void SimpleFormatterTest::runIndexedTest(int32_t index, UBool exec, const char* &name, char* /*par*/) {
  TESTCASE_AUTO_BEGIN;
  TESTCASE_AUTO(TestNoArguments);
  TESTCASE_AUTO(TestSyntaxErrors);
  TESTCASE_AUTO(TestOneArgument);
  TESTCASE_AUTO(TestBigArgument);
  TESTCASE_AUTO(TestManyArguments);
  TESTCASE_AUTO(TestTooFewArgumentValues);
  TESTCASE_AUTO(TestBadArguments);
  TESTCASE_AUTO(TestTextWithNoArguments);
  TESTCASE_AUTO(TestFormatReplaceNoOptimization);
  TESTCASE_AUTO(TestFormatReplaceNoOptimizationLeadingText);
  TESTCASE_AUTO(TestFormatReplaceOptimization);
  TESTCASE_AUTO(TestFormatReplaceNoOptimizationLeadingArgumentUsedTwice);
  TESTCASE_AUTO(TestFormatReplaceOptimizationNoOffsets);
  TESTCASE_AUTO(TestFormatReplaceNoOptimizationNoOffsets);
  TESTCASE_AUTO(TestQuotingLikeMessageFormat);
  TESTCASE_AUTO_END;
}

void SimpleFormatterTest::TestNoArguments() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt("This doesn''t have templates '{0}", status);
    assertEquals("getArgumentLimit", 0, fmt.getArgumentLimit());
    UnicodeString appendTo;
    assertEquals(
            "format",
            "This doesn't have templates {0}", 
            fmt.format("unused", appendTo, status));
    appendTo.remove();
    int32_t offsets[] = { 0 };
    assertEquals(
            "formatAndAppend",
            "This doesn't have templates {0}", 
            fmt.formatAndAppend(nullptr, 0, appendTo, offsets, 1, status));
    assertEquals("formatAndAppend offsets[0]", -1, offsets[0]);
    assertEquals(
            "formatAndReplace",
            "This doesn't have templates {0}", 
            fmt.formatAndReplace(nullptr, 0, appendTo, nullptr, 0, status));
    assertSuccess("Status", status);
}

void SimpleFormatterTest::TestSyntaxErrors() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt("{}", status);
    assertEquals("syntax error {}", static_cast<int32_t>(U_ILLEGAL_ARGUMENT_ERROR), status);
    status = U_ZERO_ERROR;
    fmt.applyPattern("{12d", status);
    assertEquals("syntax error {12d", static_cast<int32_t>(U_ILLEGAL_ARGUMENT_ERROR), status);
}

void SimpleFormatterTest::TestOneArgument() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt;
    fmt.applyPattern("{0} meter", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    assertEquals("getArgumentLimit", 1, fmt.getArgumentLimit());
    UnicodeString appendTo;
    assertEquals(
            "format",
            "1 meter",
            fmt.format("1", appendTo, status));

    // assignment
    SimpleFormatter s;
    s = fmt;
    appendTo.remove();
    assertEquals(
            "Assignment",
            "1 meter",
            s.format("1", appendTo, status));

    // Copy constructor
    SimpleFormatter r(fmt);
    appendTo.remove();
    assertEquals(
            "Copy constructor",
            "1 meter",
            r.format("1", appendTo, status));
    assertSuccess("Status", status);
}

void SimpleFormatterTest::TestBigArgument() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt("a{20}c", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    assertEquals("{20} count", 21, fmt.getArgumentLimit());
    UnicodeString b("b");
    UnicodeString *values[] = {
        nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
        nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
        &b
    };
    UnicodeString result;
    assertEquals("{20}=b", "abc", fmt.formatAndAppend(values, 21, result, nullptr, 0, status));
    assertSuccess("Status", status);
}

void SimpleFormatterTest::TestManyArguments() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt;
    fmt.applyPattern(
            "Templates {2}{1}{5} and {4} are out of order.", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    assertEquals("getArgumentLimit", 6, fmt.getArgumentLimit());
    UnicodeString values[] = {
            "freddy", "tommy", "frog", "billy", "leg", "{0}"};
    UnicodeString *params[] = {
           &values[0], &values[1], &values[2], &values[3], &values[4], &values[5]}; 
    int32_t offsets[6];
    int32_t expectedOffsets[6] = {-1, 22, 18, -1, 35, 27};
    UnicodeString appendTo("Prefix: ");
    assertEquals(
            "format",
            "Prefix: Templates frogtommy{0} and leg are out of order.",
            fmt.formatAndAppend(
                    params,
                    UPRV_LENGTHOF(params),
                    appendTo,
                    offsets,
                    UPRV_LENGTHOF(offsets),
                    status));
    if (!assertSuccess("Status", status)) {
        return;
    }
    verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets));
    appendTo.remove();

    // Ensure we don't write to offsets array beyond its length.
    status = U_ZERO_ERROR;
    offsets[UPRV_LENGTHOF(offsets) - 1] = 289;
    appendTo.remove();
    fmt.formatAndAppend(
            params,
            UPRV_LENGTHOF(params),
            appendTo,
            offsets,
            UPRV_LENGTHOF(offsets) - 1,
            status);
    assertEquals("Offsets buffer length", 289, offsets[UPRV_LENGTHOF(offsets) - 1]);

    // Test assignment
    SimpleFormatter s;
    s = fmt;
    appendTo.remove();
    assertEquals(
            "Assignment",
            "Templates frogtommy{0} and leg are out of order.",
            s.formatAndAppend(
                    params,
                    UPRV_LENGTHOF(params),
                    appendTo,
                    nullptr,
                    0,
                    status));

    // Copy constructor
    SimpleFormatter r(fmt);
    appendTo.remove();
    assertEquals(
            "Copy constructor",
            "Templates frogtommy{0} and leg are out of order.",
            r.formatAndAppend(
                    params,
                    UPRV_LENGTHOF(params),
                    appendTo,
                    nullptr,
                    0,
                    status));
    r.applyPattern("{0} meter", status);
    assertEquals("getArgumentLimit", 1, r.getArgumentLimit());
    appendTo.remove();
    assertEquals(
            "Replace with new applyPattern",
            "freddy meter",
            r.format("freddy", appendTo, status));
    r.applyPattern("{0}, {1}", status);
    assertEquals("getArgumentLimit", 2, r.getArgumentLimit());
    appendTo.remove();
    assertEquals(
            "2 arg",
            "foo, bar",
            r.format("foo", "bar", appendTo, status));
    r.applyPattern("{0}, {1} and {2}", status);
    assertEquals("getArgumentLimit", 3, r.getArgumentLimit());
    appendTo.remove();
    assertEquals(
            "3 arg",
            "foo, bar and baz",
            r.format("foo", "bar", "baz", appendTo, status));
    assertSuccess("Status", status);
}

void SimpleFormatterTest::TestTooFewArgumentValues() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt("{0} and {1}", status);
    UnicodeString appendTo;
    UnicodeString firstValue;
    UnicodeString *params[] = {&firstValue};

    fmt.format(
            firstValue, appendTo, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR");
    }

    status = U_ZERO_ERROR;
    fmt.formatAndAppend(
            params, UPRV_LENGTHOF(params), appendTo, nullptr, 0, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR");
    }

    status = U_ZERO_ERROR;
    fmt.formatAndReplace(
            params, UPRV_LENGTHOF(params), appendTo, nullptr, 0, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR");
    }
}

void SimpleFormatterTest::TestBadArguments() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt("pickle", status);
    UnicodeString appendTo;

    // These succeed
    fmt.formatAndAppend(
            nullptr, 0, appendTo, nullptr, 0, status);
    fmt.formatAndReplace(
            nullptr, 0, appendTo, nullptr, 0, status);
    assertSuccess("", status);
    status = U_ZERO_ERROR;

    // fails
    fmt.formatAndAppend(
            nullptr, 1, appendTo, nullptr, 0, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR: formatAndAppend() values=nullptr but length=1");
    }
    status = U_ZERO_ERROR;
   
    // fails
    fmt.formatAndAppend(
            nullptr, 0, appendTo, nullptr, 1, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR: formatAndAppend() offsets=nullptr but length=1");
    }
    status = U_ZERO_ERROR;

    // fails because appendTo used as a parameter value
    SimpleFormatter fmt2("Arguments {0} and {1}", status);
    UnicodeString frog("frog");
    const UnicodeString *params[] = { &appendTo, &frog };
    fmt2.formatAndAppend(params, 2, appendTo, nullptr, 0, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR: formatAndAppend() value=appendTo");
    }
    status = U_ZERO_ERROR;

   
    // fails
    fmt.formatAndReplace(
            nullptr, 1, appendTo, nullptr, 0, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR: formatAndReplace() values=nullptr but length=1");
    }
    status = U_ZERO_ERROR;
   
    // fails
    fmt.formatAndReplace(
            nullptr, 0, appendTo, nullptr, 1, status);
    if (status != U_ILLEGAL_ARGUMENT_ERROR) {
        errln("Expected U_ILLEGAL_ARGUMENT_ERROR: formatAndReplace() offsets=nullptr but length=1");
    }
}

void SimpleFormatterTest::TestTextWithNoArguments() {
    IcuTestErrorCode status(*this, "TestTextWithNoArguments");
    SimpleFormatter fmt("{0} has no {1} arguments.", status);
    assertEquals("String output 1",
        " has no  arguments.", fmt.getTextWithNoArguments());

    // Test offset positions
    int32_t offsets[3];
    assertEquals("String output 2",
        u" has no  arguments.", fmt.getTextWithNoArguments(offsets, 3));
    assertEquals("Offset at 0",
        0, offsets[0]);
    assertEquals("Offset at 1",
        8, offsets[1]);
    assertEquals("Offset at 2",
        -1, offsets[2]);
}

void SimpleFormatterTest::TestFormatReplaceNoOptimization() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt;
    fmt.applyPattern("{2}, {0}, {1} and {3}", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    UnicodeString result("original");
    int32_t offsets[4];
    UnicodeString freddy("freddy");
    UnicodeString frog("frog");
    UnicodeString by("by");
    const UnicodeString *params[] = {&result, &freddy, &frog, &by};
    assertEquals(
            "",
            "frog, original, freddy and by",
            fmt.formatAndReplace(
                    params,
                    UPRV_LENGTHOF(params),
                    result,
                    offsets,
                    UPRV_LENGTHOF(offsets),
                    status));
    if (!assertSuccess("Status", status)) {
        return;
    }
    int32_t expectedOffsets[] = {6, 16, 0, 27};
    verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets));
}

void SimpleFormatterTest::TestFormatReplaceNoOptimizationLeadingText() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt;
    fmt.applyPattern("boo {2}, {0}, {1} and {3}", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    UnicodeString result("original");
    int32_t offsets[4];
    UnicodeString freddy("freddy");
    UnicodeString frog("frog");
    UnicodeString by("by");
    const UnicodeString *params[] = {&freddy, &frog, &result, &by};
    assertEquals(
            "",
            "boo original, freddy, frog and by",
            fmt.formatAndReplace(
                    params,
                    UPRV_LENGTHOF(params),
                    result,
                    offsets,
                    UPRV_LENGTHOF(offsets),
                    status));
    if (!assertSuccess("Status", status)) {
        return;
    }
    int32_t expectedOffsets[] = {14, 22, 4, 31};
    verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets));
}

void SimpleFormatterTest::TestFormatReplaceOptimization() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt;
    fmt.applyPattern("{2}, {0}, {1} and {3}", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    UnicodeString result("original");
    int32_t offsets[4];
    UnicodeString freddy("freddy");
    UnicodeString frog("frog");
    UnicodeString by("by");
    const UnicodeString *params[] = {&freddy, &frog, &result, &by};
    assertEquals(
            "",
            "original, freddy, frog and by",
            fmt.formatAndReplace(
                    params,
                    UPRV_LENGTHOF(params),
                    result,
                    offsets,
                    UPRV_LENGTHOF(offsets),
                    status));
    if (!assertSuccess("Status", status)) {
        return;
    }
    int32_t expectedOffsets[] = {10, 18, 0, 27};
    verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets));
}

void SimpleFormatterTest::TestFormatReplaceNoOptimizationLeadingArgumentUsedTwice() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt;
    fmt.applyPattern("{2}, {0}, {1} and {3} {2}", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    UnicodeString result("original");
    int32_t offsets[4];
    UnicodeString freddy("freddy");
    UnicodeString frog("frog");
    UnicodeString by("by");
    const UnicodeString *params[] = {&freddy, &frog, &result, &by};
    assertEquals(
            "",
            "original, freddy, frog and by original",
            fmt.formatAndReplace(
                    params,
                    UPRV_LENGTHOF(params),
                    result,
                    offsets,
                    UPRV_LENGTHOF(offsets),
                    status));
    if (!assertSuccess("Status", status)) {
        return;
    }
    int32_t expectedOffsets[] = {10, 18, 30, 27};
    verifyOffsets(expectedOffsets, offsets, UPRV_LENGTHOF(expectedOffsets));
}

void SimpleFormatterTest::TestFormatReplaceOptimizationNoOffsets() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt;
    fmt.applyPattern("{2}, {0}, {1} and {3}", status);
    if (!assertSuccess("Status", status)) {
        return;
    }
    UnicodeString result("original");
    UnicodeString freddy("freddy");
    UnicodeString frog("frog");
    UnicodeString by("by");
    const UnicodeString *params[] = {&freddy, &frog, &result, &by};
    assertEquals(
            "",
            "original, freddy, frog and by",
            fmt.formatAndReplace(
                    params,
                    UPRV_LENGTHOF(params),
                    result,
                    nullptr,
                    0,
                    status));
    assertSuccess("Status", status);
}

void SimpleFormatterTest::TestFormatReplaceNoOptimizationNoOffsets() {
    UErrorCode status = U_ZERO_ERROR;
    SimpleFormatter fmt("Arguments {0} and {1}", status);
    UnicodeString result("previous:");
    UnicodeString frog("frog");
    const UnicodeString *params[] = {&result, &frog};
    assertEquals(
            "",
            "Arguments previous: and frog",
            fmt.formatAndReplace(
                    params,
                    UPRV_LENGTHOF(params),
                    result,
                    nullptr,
                    0,
                    status));
    assertSuccess("Status", status);
}

void SimpleFormatterTest::TestQuotingLikeMessageFormat() {
#if !UCONFIG_NO_FORMATTING
    UErrorCode status = U_ZERO_ERROR;
    UnicodeString pattern = "{0} don't can''t '{5}''}{a' again '}'{1} to the '{end";
    SimpleFormatter spf(pattern, status);
    MessageFormat mf(pattern, Locale::getRoot(), status);
    UnicodeString expected = "X don't can't {5}'}{a again }Y to the {end";
    UnicodeString x("X"), y("Y");
    Formattable values[] = { x, y };
    UnicodeString result;
    FieldPosition ignore(FieldPosition::DONT_CARE);
    assertEquals("MessageFormat", expected, mf.format(values, 2, result, ignore, status));
    assertEquals("SimpleFormatter", expected, spf.format(x, y, result.remove(), status));
#endif /* !UCONFIG_NO_FORMATTING */
}

void SimpleFormatterTest::verifyOffsets(
        const int32_t *expected, const int32_t *actual, int32_t count) {
    for (int32_t i = 0; i < count; ++i) {
        if (expected[i] != actual[i]) {
            errln("Expected %d, got %d", expected[i], actual[i]);
        }
    }
}

extern IntlTest *createSimpleFormatterTest() {
    return new SimpleFormatterTest();
}
