// © 2022 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

package com.ibm.icu.dev.test.message2;

import java.util.Locale;
import java.util.Map;
import java.util.Objects;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.message2.FormattedPlaceholder;
import com.ibm.icu.message2.Formatter;
import com.ibm.icu.message2.FormatterFactory;
import com.ibm.icu.message2.Mf2FunctionRegistry;
import com.ibm.icu.message2.PlainStringFormattedValue;

/**
 * Showing a custom formatter for a user defined class.
 */
@RunWith(JUnit4.class)
@SuppressWarnings("javadoc")
public class CustomFormatterPersonTest extends TestFmwk {

    public static class Person {
        final String title;
        final String firstName;
        final String lastName;

        public Person(String title, String firstName, String lastName) {
            this.title = title;
            this.firstName = firstName;
            this.lastName = lastName;
        }

        @Override
        public String toString() {
            return "Person {title='" + title + "', firstName='" + firstName + "', lastName='" + lastName + "'}";
        }
    }

    public static class PersonNameFormatterFactory implements FormatterFactory {
        @Override
        public Formatter createFormatter(Locale locale, Map<String, Object> fixedOptions) {
            return new PersonNameFormatterImpl(fixedOptions.get("formality"), fixedOptions.get("length"));
        }

        static class PersonNameFormatterImpl implements Formatter {
            boolean useFormal = false;
            final String length;

            public PersonNameFormatterImpl(Object level, Object length) {
                this.useFormal = "formal".equals(level);
                this.length = Objects.toString(length);
            }

            @Override
            public String formatToString(Object toFormat, Map<String, Object> variableOptions) {
                return format(toFormat, variableOptions).toString();
            }

            // Very-very primitive implementation of the "CLDR Person Name Formatting" spec:
            // https://docs.google.com/document/d/1uvv6gdkuFwtbNV26Pk7ddfZult4unYwR6DnnKYbujUo/
            @Override
            public FormattedPlaceholder format(Object toFormat, Map<String, Object> variableOptions) {
                String result;
                if (toFormat instanceof Person) {
                    Person person = (Person) toFormat;
                    switch (length) {
                        case "long":
                            result = person.title + " " + person.firstName + " " + person.lastName;
                            break;
                        case "medium":
                            result = useFormal
                                    ? person.firstName + " " + person.lastName
                                    : person.title + " " + person.firstName;
                            break;
                        case "short": // intentional fall-through
                        default:
                            result = useFormal
                                    ? person.title + " " + person.lastName
                                    : person.firstName;
                    }
                } else {
                    result = Objects.toString(toFormat);
                }
                return new FormattedPlaceholder(toFormat, new PlainStringFormattedValue(result));
            }
        }
    }

    private static final Mf2FunctionRegistry CUSTOM_FUNCTION_REGISTRY = Mf2FunctionRegistry.builder()
            .setFormatter("person", new PersonNameFormatterFactory())
            .setDefaultFormatterNameForType(Person.class, "person")
            .build();

    @Test
    public void testCustomFunctions() {
        Person who = new Person("Mr.", "John", "Doe");

        TestUtils.runTestCase(new TestCase.Builder()
                .pattern("{Hello {$name :person formality=formal}}")
                .arguments(Args.of("name", who))
                .expected("Hello {$name}")
                .errors("person function unknown when called without a custom registry")
                .build());

        TestUtils.runTestCase(new TestCase.Builder()
                .pattern("{Hello {$name :person formality=informal}}")
                .arguments(Args.of("name", who))
                .expected("Hello {$name}")
                .errors("person function unknown when called without a custom registry")
                .build());

        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern("{Hello {$name :person formality=formal}}")
                .arguments(Args.of("name", who))
                .expected("Hello Mr. Doe")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern("{Hello {$name :person formality=informal}}")
                .arguments(Args.of("name", who))
                .expected("Hello John")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern("{Hello {$name :person formality=formal length=long}}")
                .arguments(Args.of("name", who))
                .expected("Hello Mr. John Doe")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern("{Hello {$name :person formality=formal length=medium}}")
                .arguments(Args.of("name", who))
                .expected("Hello John Doe")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern("{Hello {$name :person formality=formal length=short}}")
                .arguments(Args.of("name", who))
                .expected("Hello Mr. Doe")
                .build());
    }

    @Test
    public void testCustomFunctionsComplexMessage() {
        Person femalePerson = new Person("Ms.", "Jane", "Doe");
        Person malePerson = new Person("Mr.", "John", "Doe");
        Person unknownPerson = new Person("Mr./Ms.", "Anonymous", "Doe");
        String message = ""
                + "let $hostName = {$host :person length=long}\n"
                + "let $guestName = {$guest :person length=long}\n"
                + "let $guestsOther = {$guestCount :number offset=1}\n"
              //  + "\n"
                + "match {$hostGender :gender} {$guestCount :plural}\n"
              //  + "\n"
                + "when female 0 {{$hostName} does not give a party.}\n"
                + "when female 1 {{$hostName} invites {$guestName} to her party.}\n"
                + "when female 2 {{$hostName} invites {$guestName} and one other person to her party.}\n"
                + "when female * {{$hostName} invites {$guestName} and {$guestsOther} other people to her party.}\n"
              //  + "\n"
                + "when male 0 {{$hostName} does not give a party.}\n"
                + "when male 1 {{$hostName} invites {$guestName} to his party.}\n"
                + "when male 2 {{$hostName} invites {$guestName} and one other person to his party.}\n"
                + "when male * {{$hostName} invites {$guestName} and {$guestsOther} other people to his party.}\n"
              //  + "\n"
                + "when * 0 {{$hostName} does not give a party.}\n"
                + "when * 1 {{$hostName} invites {$guestName} to their party.}\n"
                + "when * 2 {{$hostName} invites {$guestName} and one other person to their party.}\n"
                + "when * * {{$hostName} invites {$guestName} and {$guestsOther} other people to their party.}\n";

        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern(message)
                .arguments(Args.of("hostGender", "female", "host", femalePerson, "guest", malePerson, "guestCount", 3))
                .expected("Ms. Jane Doe invites Mr. John Doe and 2 other people to her party.")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern(message)
                .arguments(Args.of("hostGender", "female", "host", femalePerson, "guest", malePerson, "guestCount", 2))
                .expected("Ms. Jane Doe invites Mr. John Doe and one other person to her party.")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern(message)
                .arguments(Args.of("hostGender", "female", "host", femalePerson, "guest", malePerson, "guestCount", 1))
                .expected("Ms. Jane Doe invites Mr. John Doe to her party.")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern(message)
                .arguments(Args.of("hostGender", "male", "host", malePerson, "guest", femalePerson, "guestCount", 3))
                .expected("Mr. John Doe invites Ms. Jane Doe and 2 other people to his party.")
                .build());
        TestUtils.runTestCase(CUSTOM_FUNCTION_REGISTRY, new TestCase.Builder()
                .pattern(message)
                .arguments(Args.of("hostGender", "unknown", "host", unknownPerson, "guest", femalePerson, "guestCount", 2))
                .expected("Mr./Ms. Anonymous Doe invites Ms. Jane Doe and one other person to their party.")
                .build());
    }
}
