// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/********************************************************************
 * COPYRIGHT: 
 * Copyright (c) 2001-2010, International Business Machines Corporation and
 * others. All Rights Reserved.
 ********************************************************************/
/************************************************************************
*   This test program is intended for testing Replaceable class.
*
*   Date        Name        Description
*   11/28/2001  hshih       Ported back from Java.
* 
************************************************************************/

#include "unicode/utypes.h"

#if !UCONFIG_NO_TRANSLITERATION

#include "ittrans.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "unicode/rep.h"
#include "reptest.h"

//---------------------------------------------
// runIndexedTest
//---------------------------------------------

    /**
     * This is a test class that simulates styled text.
     * It associates a style number (0..65535) with each character,
     * and maintains that style in the normal fashion:
     * When setting text from raw string or characters,<br>
     * Set the styles to the style of the first character replaced.<br>
     * If no characters are replaced, use the style of the previous character.<br>
     * If at start, use the following character<br>
     * Otherwise use NO_STYLE.
     */
class TestReplaceable : public Replaceable {
    UnicodeString chars;
    UnicodeString styles;
    
    static const char16_t NO_STYLE;

    static const char16_t NO_STYLE_MARK;

    /**
     * The address of this static class variable serves as this class's ID
     * for ICU "poor man's RTTI".
     */
    static const char fgClassID;

public:    
    TestReplaceable (const UnicodeString& text, 
                     const UnicodeString& newStyles) {
        chars = text;
        UnicodeString s;
        for (int i = 0; i < text.length(); ++i) {
            if (i < newStyles.length()) {
                s.append(newStyles.charAt(i));
            } else {
                if (text.charAt(i) == NO_STYLE_MARK) {
                    s.append(NO_STYLE);
                } else {
                    s.append(static_cast<char16_t>(i + 0x0031));
                }
            }
        }
        this->styles = s;
    }

    virtual TestReplaceable *clone() const override {
        return new TestReplaceable(chars, styles);
    }

    ~TestReplaceable() {}

    UnicodeString getStyles() {
        return styles;
    }
    
    UnicodeString toString() {
        UnicodeString s = chars;
        s.append("{");
        s.append(styles);
        s.append("}");
        return s;
    }

    void extractBetween(int32_t start, int32_t limit, UnicodeString& result) const override {
        chars.extractBetween(start, limit, result);
    }

    /**
     * ICU "poor man's RTTI", returns a UClassID for this class.
     *
     * @draft ICU 2.2
     */
    static inline UClassID getStaticClassID() { return (UClassID)&fgClassID; }

    /**
     * ICU "poor man's RTTI", returns a UClassID for the actual class.
     *
     * @draft ICU 2.2
     */
    virtual inline UClassID getDynamicClassID() const override { return getStaticClassID(); }

protected:
    virtual int32_t getLength() const override {
        return chars.length();
    }

    virtual char16_t getCharAt(int32_t offset) const override {
        return chars.charAt(offset);
    }

    virtual UChar32 getChar32At(int32_t offset) const override {
        return chars.char32At(offset);
    }

    void fixStyles(int32_t start, int32_t limit, int32_t newLen) {
        char16_t newStyle = NO_STYLE;
        if (start != limit && styles.charAt(start) != NO_STYLE) {
            newStyle = styles.charAt(start);
        } else if (start > 0 && getCharAt(start-1) != NO_STYLE_MARK) {
            newStyle = styles.charAt(start-1);
        } else if (limit < styles.length()) {
            newStyle = styles.charAt(limit);
        }
        // dumb implementation for now.
        UnicodeString s;
        for (int i = 0; i < newLen; ++i) {
            // this doesn't really handle an embedded NO_STYLE_MARK
            // in the middle of a long run of characters right -- but
            // that case shouldn't happen anyway
            if (getCharAt(start+i) == NO_STYLE_MARK) {
                s.append(NO_STYLE);
            } else {
                s.append(newStyle);
            }
        }
        styles.replaceBetween(start, limit, s);
    }

    virtual void handleReplaceBetween(int32_t start, int32_t limit, const UnicodeString& text) override {
        UnicodeString s;
        this->extractBetween(start, limit, s);
        if (s == text) return; // NO ACTION!
        this->chars.replaceBetween(start, limit, text);
        fixStyles(start, limit, text.length());
    }
    

    virtual void copy(int32_t start, int32_t limit, int32_t dest) override {
        chars.copy(start, limit, dest);
        styles.copy(start, limit, dest);
    }
};

const char TestReplaceable::fgClassID=0;

const char16_t TestReplaceable::NO_STYLE  = 0x005F;

const char16_t TestReplaceable::NO_STYLE_MARK = 0xFFFF;

void
ReplaceableTest::runIndexedTest(int32_t index, UBool exec,
                                      const char* &name, char* /*par*/) {
    switch (index) {
        TESTCASE(0,TestReplaceableClass);
        default: name = ""; break;
    }
}

/*
 * Dummy Replaceable implementation for better API/code coverage.
 */
class NoopReplaceable : public Replaceable {
public:
    virtual int32_t getLength() const override {
        return 0;
    }

    virtual char16_t getCharAt(int32_t /*offset*/) const override {
        return 0xffff;
    }

    virtual UChar32 getChar32At(int32_t /*offset*/) const override {
        return 0xffff;
    }

    void extractBetween(int32_t /*start*/, int32_t /*limit*/, UnicodeString& result) const override {
        result.remove();
    }

    virtual void handleReplaceBetween(int32_t /*start*/, int32_t /*limit*/, const UnicodeString &/*text*/) override {
        /* do nothing */
    }

    virtual void copy(int32_t /*start*/, int32_t /*limit*/, int32_t /*dest*/) override {
        /* do nothing */
    }

    static inline UClassID getStaticClassID() { return (UClassID)&fgClassID; }
    virtual inline UClassID getDynamicClassID() const override { return getStaticClassID(); }

private:
    static const char fgClassID;
};

const char NoopReplaceable::fgClassID=0;

void ReplaceableTest::TestReplaceableClass() {
    char16_t rawTestArray[][6] = {
        {0x0041, 0x0042, 0x0043, 0x0044, 0x0000, 0x0000}, // ABCD
        {0x0061, 0x0062, 0x0063, 0x0064, 0x00DF, 0x0000}, // abcd\u00DF
        {0x0061, 0x0042, 0x0043, 0x0044, 0x0000, 0x0000}, // aBCD
        {0x0041, 0x0300, 0x0045, 0x0300, 0x0000, 0x0000}, // A\u0300E\u0300
        {0x00C0, 0x00C8, 0x0000, 0x0000, 0x0000, 0x0000}, // \u00C0\u00C8
        {0x0077, 0x0078, 0x0079, 0x0000, 0x0000, 0x0000}, /* "wxy" */
        {0x0077, 0x0078, 0x0079, 0x007A, 0x0000, 0x0000}, /* "wxyz" */
        {0x0077, 0x0078, 0x0079, 0x007A, 0x0075, 0x0000}, /* "wxyzu" */
        {0x0078, 0x0079, 0x007A, 0x0000, 0x0000, 0x0000}, /* "xyz" */
        {0x0077, 0x0078, 0x0079, 0x0000, 0x0000, 0x0000}, /* "wxy" */
        {0xFFFF, 0x0078, 0x0079, 0x0000, 0x0000, 0x0000}, /* "*xy" */
        {0xFFFF, 0x0078, 0x0079, 0x0000, 0x0000, 0x0000}, /* "*xy" */
    };
    check("Lower", rawTestArray[0], "1234");
    check("Upper", rawTestArray[1], "123455"); // must map 00DF to SS
    check("Title", rawTestArray[2], "1234");
    check("NFC",   rawTestArray[3], "13");
    check("NFD",   rawTestArray[4], "1122");
    check("*(x) > A $1 B", rawTestArray[5], "11223");
    check("*(x)(y) > A $2 B $1 C $2 D", rawTestArray[6], "113322334");
    check("*(x)(y)(z) > A $3 B $2 C $1 D", rawTestArray[7], "114433225");
    // Disabled for 2.4.  TODO Revisit in 2.6 or later.
    //check("*x > a", rawTestArray[8], "223"); // expect "123"?
    //check("*x > a", rawTestArray[9], "113"); // expect "123"?
    //check("*x > a", rawTestArray[10], "_33"); // expect "_23"?
    //check("*(x) > A $1 B", rawTestArray[11], "__223");

    // improve API/code coverage
    NoopReplaceable noop;
    Replaceable *p;
    if((p=noop.clone())!=nullptr) {
        errln("Replaceable::clone() does not return nullptr");
        delete p;
    }

    if(!noop.hasMetaData()) {
        errln("Replaceable::hasMetaData() does not return true");
    }

    // try to call the compiler-provided
    // UMemory/UObject/Replaceable assignment operators
    NoopReplaceable noop2;
    noop2=noop;
    if((p=noop2.clone())!=nullptr) {
        errln("noop2.Replaceable::clone() does not return nullptr");
        delete p;
    }

    // try to call the compiler-provided
    // UMemory/UObject/Replaceable copy constructors
    NoopReplaceable noop3(noop);
    if((p=noop3.clone())!=nullptr) {
        errln("noop3.Replaceable::clone() does not return nullptr");
        delete p;
    }
}

void ReplaceableTest::check(const UnicodeString& transliteratorName, 
                            const UnicodeString& test, 
                            const UnicodeString& shouldProduceStyles) 
{
    UErrorCode status = U_ZERO_ERROR;
    TestReplaceable *tr = new TestReplaceable(test, "");
    UnicodeString expectedStyles = shouldProduceStyles;
    UnicodeString original = tr->toString();

    Transliterator* t;
    if (transliteratorName.charAt(0) == 0x2A /*'*'*/) {
        UnicodeString rules(transliteratorName);
        rules.remove(0,1);
        UParseError pe;
        t = Transliterator::createFromRules("test", rules, UTRANS_FORWARD,
                                            pe, status);

        // test clone()
        TestReplaceable *tr2 = tr->clone();
        if(tr2 != nullptr) {
            delete tr;
            tr = tr2;
        }
    } else {
        t = Transliterator::createInstance(transliteratorName, UTRANS_FORWARD, status);
    }
    if (U_FAILURE(status)) {
        dataerrln("FAIL: failed to create the " + transliteratorName + " transliterator");
        delete tr;
        return;
    }
    t->transliterate(*tr);
    UnicodeString newStyles = tr->getStyles();
    if (newStyles != expectedStyles) {
        errln("FAIL Styles: " + transliteratorName + "{" + original + "} => "
            + tr->toString() + "; should be {" + expectedStyles + "}!");
    } else {
        log("OK: ");
        log(transliteratorName);
        log("(");
        log(original);
        log(") => ");
        logln(tr->toString());
    }
    delete tr;
    delete t;
}

#endif /* #if !UCONFIG_NO_TRANSLITERATION */
