/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.openide.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.TreeMap;
import java.util.jar.Attributes;
import junit.framework.TestCase;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertNotEquals;
import org.openide.util.base.BundleClass;

// XXX testGetClassBundle
// XXX testGetLocalizedFile
// XXX testDebugLoader

/**
 * Test normal-mode functionality of {@link NbBundle}.
 * @author Jesse Glick
 */
public class NbBundleTest extends TestCase {

    public NbBundleTest(String name) {
        super(name);
    }

    protected void setUp() throws Exception {
        Locale.setDefault(Locale.US);
        NbBundle.setBranding(null);
        NbBundle.localizedFileCache.clear();
        NbBundle.bundleCache.clear();
        System.clearProperty("java.util.PropertyResourceBundle.encoding");
    }

    public static void testLocalizingSuffixes() throws Exception {
        assertEquals("_en_US,_en,", locSuff());
        Locale.setDefault(Locale.JAPAN);
        assertEquals("_ja_JP,_ja,", locSuff());
        NbBundle.setBranding("f4j_ce");
        Locale.setDefault(Locale.US);
        assertEquals("_f4j_ce_en_US,_f4j_ce_en,_f4j_ce,_f4j_en_US,_f4j_en,_f4j,_en_US,_en,", locSuff());
        Locale.setDefault(Locale.JAPAN);
        assertEquals("_f4j_ce_ja_JP,_f4j_ce_ja,_f4j_ce,_f4j_ja_JP,_f4j_ja,_f4j,_ja_JP,_ja,", locSuff());
    }

    private static String locSuff() {
        StringBuffer b = new StringBuffer();
        boolean first = true;
        Iterator<String> it = NbBundle.getLocalizingSuffixes();
        while (it.hasNext()) {
            if (first) {
                first = false;
            } else {
                b.append(',');
            }
            b.append(it.next());
        }
        return b.toString();
    }

    public void testGetBundle() throws Exception {
        ResourceBundle rb = NbBundle.getBundle("foo.Bundle", Locale.ENGLISH, fixedLoader(false, "foo/Bundle.properties:k=v"));
        assertEquals("v", rb.getString("k"));
        try {
            rb.getString("kkk");
            fail();
        } catch (MissingResourceException mre) {
            // OK
        }
        rb = NbBundle.getBundle("foo.Bundle", Locale.US, fixedLoader(false, "foo/Bundle.properties:k=v"));
        assertEquals("v", rb.getString("k"));
        rb = NbBundle.getBundle("foo.Bundle", Locale.JAPAN, fixedLoader(false, "foo/Bundle.properties:k=v"));
        assertEquals("v", rb.getString("k"));
        rb = NbBundle.getBundle("foo.Bundle", Locale.JAPAN, fixedLoader(false, "foo/Bundle.properties:k=v", "foo/Bundle_ja.properties:k=v2"));
        assertEquals("v2", rb.getString("k"));
        assertEquals(Locale.JAPAN, rb.getLocale());
        try {
            NbBundle.getBundle("foo.Bundle", Locale.ENGLISH, fixedLoader(false));
            fail();
        } catch (MissingResourceException mre) {
            // OK
        }
        NbBundle.setBranding("nb");
        rb = NbBundle.getBundle("foo.Bundle", Locale.US, fixedLoader(false, "foo/Bundle.properties:k1=v1\nk2=v2", "foo/Bundle_nb.properties:k1=v1 NB"));
        assertEquals("v1 NB", rb.getString("k1"));
        assertEquals("v2", rb.getString("k2"));
        List<String> keys = new ArrayList<String>(Collections.list(rb.getKeys()));
        Collections.sort(keys);
        assertEquals("[k1, k2]", keys.toString());
    }

    public void testGetMessageISO() throws Exception {
        ClassLoader l = fixedLoader(false,
                "org/openide/util/Bundle.properties:"
                + "k1=v1\n"
                + "k2=v2 {0}\n"
                + "k3=v3 {0} {1} {2} {3} {4}",
                "org/openide/util/Bundle_ja.properties:"
                + "k1=v1 ja",
                "org/openide/util/Bundle_fr.properties:"
                + "k1=v1 ô Hélène"
        );
        Class<?> c = l.loadClass(Dummy.class.getName());
        assertEquals(l, c.getClassLoader());
        assertEquals("v1", NbBundle.getMessage(c, "k1"));
        Locale.setDefault(Locale.JAPAN);
        assertEquals("v1 ja", NbBundle.getMessage(c, "k1"));
        assertEquals("v2 x", NbBundle.getMessage(c, "k2", "x"));
        assertEquals("v3 a b c d e", NbBundle.getMessage(c, "k3", "a", "b", "c", "d", "e"));
        Locale.setDefault(Locale.FRENCH);
        assertEquals("v1 ô Hélène", NbBundle.getMessage(c, "k1"));
    }

    public void testGetMessageUTF8() throws Exception {
        ClassLoader l = fixedLoader(true,
                "org/openide/util/Bundle.properties:"
                + "k1=v1\n"
                + "k2=v2 {0}\n"
                + "k3=v3 {0} {1} {2} {3} {4}",
                "org/openide/util/Bundle_ja.properties:"
                + "k1=v1 ja",
                "org/openide/util/Bundle_fr.properties:"
                + "k2=v2 ô Hélène {0}"
        );
        Class<?> c = l.loadClass(Dummy.class.getName());
        assertEquals(l, c.getClassLoader());
        assertEquals("v1", NbBundle.getMessage(c, "k1"));
        Locale.setDefault(Locale.JAPAN);
        assertEquals("v1 ja", NbBundle.getMessage(c, "k1"));
        assertEquals("v2 x", NbBundle.getMessage(c, "k2", "x"));
        assertEquals("v3 a b c d e", NbBundle.getMessage(c, "k3", "a", "b", "c", "d", "e"));
        Locale.setDefault(Locale.FRENCH);
        assertEquals("v2 ô Hélène chérie", NbBundle.getMessage(c, "k2", "chérie"));
    }

    public void testSystemPropertyISO() throws Exception {
        ClassLoader l = fixedLoader(true,
                "org/openide/util/Bundle.properties:"
                + "k1=yo\n"
                + "k2=where and ouch",
                "org/openide/util/Bundle_fr.properties:"
                + "k1=fr yo\n"
                + "k2=où et aïe"
        );
        Class<?> c = l.loadClass(Dummy.class.getName());
        assertEquals(l, c.getClassLoader());
        Locale.setDefault(Locale.FRENCH);
        System.setProperty("java.util.PropertyResourceBundle.encoding", "ISO-8859-1");
        assertEquals("fr yo", NbBundle.getMessage(c, "k1"));
        assertNotEquals("où et aïe", NbBundle.getMessage(c, "k2"));
    }

    public void testSystemPropertyUTF() throws Exception {
        ClassLoader l = fixedLoader(false,
                "org/openide/util/Bundle.properties:"
                + "k1=yo\n"
                + "k2=where and ouch",
                "org/openide/util/Bundle_fr.properties:"
                + "k1=fr yo\n"
                + "k2=où et aïe"
        );
        Class<?> c = l.loadClass(Dummy.class.getName());
        assertEquals(l, c.getClassLoader());
        Locale.setDefault(Locale.FRENCH);
        System.setProperty("java.util.PropertyResourceBundle.encoding", "UTF-8");
        try {
            assertEquals("fr yo", NbBundle.getMessage(c, "k1"));
            fail();
        } catch (MissingResourceException mre) {
            // OK MalformedInputException
        }

    }

    static class Dummy {}
    /**
     * Creates a loader which can load just fixed resources you supply. Each
     * entry should be of the form
     * <pre>
     * path/to/res1:some contents
     * for res1
     * </pre> Also can define a class named Dummy.class.getName().
     *
     * @param useUTF8 If false use ISO-8859-1 encoding, otherwise UTF-8
     */
    private static ClassLoader fixedLoader(boolean useUTF8, String... entries) throws Exception {
        final Map<String, byte[]> data = new HashMap<String, byte[]>();
        for (String entry : entries) {
            int colon = entry.indexOf(':');
            data.put(entry.substring(0, colon), entry.substring(colon + 1).getBytes(useUTF8 ? "UTF-8" : "ISO-8859-1"));
        }
        return new ClassLoader() {
            @Override
            public URL getResource(final String res) {
                if (data.containsKey(res)) {
                    //System.err.println("hit for " + res);
                    try {
                        return new URL("dummy", null, 0, res, new URLStreamHandler() {
                            protected URLConnection openConnection(URL u) throws IOException {
                                return new URLConnection(u) {
                                    public void connect() throws IOException {}
                                    @Override
                                    public InputStream getInputStream() throws IOException {
                                        return new ByteArrayInputStream(data.get(res));
                                    }
                                };
                            }
                        });
                    } catch (MalformedURLException x) {
                        throw new AssertionError(x);
                    }
                } else {
                    //System.err.println("miss for " + res);
                    return null;
                }
            }

            @Override
            public Class loadClass(String n) throws ClassNotFoundException {
                if (n.equals(Dummy.class.getName())) {
                    InputStream is = NbBundleTest.class.getClassLoader().getResourceAsStream(n.replace('.', '/') + ".class");
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buf = new byte[4096];
                    int read;
                    try {
                        while ((read = is.read(buf)) != -1) {
                            baos.write(buf, 0, read);
                        }
                    } catch (IOException x) {
                        throw new AssertionError(x);
                    }
                    return defineClass(n, baos.toByteArray(), 0, baos.size());
                } else {
                    return super.loadClass(n);
                }
            }
        };
    }

    public static void testGetLocalizedValue() throws Exception {
        Map<String,String> m = new HashMap<String,String>();
        m.put("k1", "v1");
        m.put("k1_ja", "v1_ja");
        m.put("k1_ja_JP", "v1_ja_JP");
        m.put("k2", "v2");
        m.put("k3_ja", "v3_ja");
        assertEquals("v1", NbBundle.getLocalizedValue(m, "k1", Locale.ENGLISH));
        assertEquals("v1_ja", NbBundle.getLocalizedValue(m, "k1", Locale.JAPANESE));
        assertEquals("v1_ja_JP", NbBundle.getLocalizedValue(m, "k1", Locale.JAPAN));
        assertEquals("v2", NbBundle.getLocalizedValue(m, "k2", Locale.ENGLISH));
        assertEquals("v2", NbBundle.getLocalizedValue(m, "k2", Locale.JAPANESE));
        assertEquals("v2", NbBundle.getLocalizedValue(m, "k2", Locale.JAPAN));
        assertEquals(null, NbBundle.getLocalizedValue(m, "k3", Locale.ENGLISH));
        assertEquals("v3_ja", NbBundle.getLocalizedValue(m, "k3", Locale.JAPANESE));
        assertEquals("v3_ja", NbBundle.getLocalizedValue(m, "k3", Locale.JAPAN));
        Attributes attr = new Attributes();
        attr.putValue("k1", "v1");
        attr.putValue("k1_ja", "v1_ja");
        attr.putValue("k1_ja_JP", "v1_ja_JP");
        attr.putValue("k2", "v2");
        attr.putValue("k3_ja", "v3_ja");
        assertEquals("v1", NbBundle.getLocalizedValue(attr, new Attributes.Name("k1"), Locale.ENGLISH));
        assertEquals("v1_ja", NbBundle.getLocalizedValue(attr, new Attributes.Name("k1"), Locale.JAPANESE));
        assertEquals("v1_ja_JP", NbBundle.getLocalizedValue(attr, new Attributes.Name("k1"), Locale.JAPAN));
        assertEquals("v2", NbBundle.getLocalizedValue(attr, new Attributes.Name("k2"), Locale.ENGLISH));
        assertEquals("v2", NbBundle.getLocalizedValue(attr, new Attributes.Name("k2"), Locale.JAPANESE));
        assertEquals("v2", NbBundle.getLocalizedValue(attr, new Attributes.Name("k2"), Locale.JAPAN));
        assertEquals(null, NbBundle.getLocalizedValue(attr, new Attributes.Name("k3"), Locale.ENGLISH));
        assertEquals("v3_ja", NbBundle.getLocalizedValue(attr, new Attributes.Name("k3"), Locale.JAPANESE));
        assertEquals("v3_ja", NbBundle.getLocalizedValue(attr, new Attributes.Name("k3"), Locale.JAPAN));
        attr = new Attributes();
        attr.putValue("Think", "Smart");
        assertEquals("think", "THINK".toLowerCase());
        assertEquals("Smart", NbBundle.getLocalizedValue(attr, new Attributes.Name("think")));
        assertEquals("Smart", NbBundle.getLocalizedValue(attr, new Attributes.Name("THINK")));
        Locale.setDefault(new Locale("tr", "TR"));
        assertEquals("th\u0131nk", "THINK".toLowerCase());
        assertEquals("Smart", NbBundle.getLocalizedValue(attr, new Attributes.Name("think")));
        assertEquals("Smart", NbBundle.getLocalizedValue(attr, new Attributes.Name("THINK")));
    }

    /** @see "#57815" */
    public static void testSystemClassLoaderLoadedClasses() throws Exception {
        assertNotNull("OK to ask for message from class on app CP", NbBundle.getMessage(BundleClass.class, "OpenIDE-Module-Name"));
        try {
            NbBundle.getMessage(Object.class, "whatever");
            fail();
        } catch (MissingResourceException x) {
            // OK
        }
    }

    public void testDebugInputStream() throws Exception {
        assertEquals("basic annotation works", "{key=val (17:1)}", debugIS("key=val\n", true));
        assertEquals("annotation disabled unless loc", "{key=val}", debugIS("key=val\n", false));
        assertEquals("comments ignored", "{key=val (17:2)}", debugIS("# some comment\nkey=val\n", true));
        assertEquals("simple multiline values handled", "{k1=vee one (17:1)}", debugIS("k1=vee \\\none\n", true));
        assertEquals("whitespace starting cont lines ignored", "{k1=vee one (17:1)}", debugIS("k1=vee \\\n    one\n", true));
        assertEquals("whitespace around key ignored", "{k1=v1 (17:1)}", debugIS(" k1 =v1\n", true));
        assertEquals("whitespace before value ignored", "{k1=v1 (17:1)}", debugIS("k1= v1\n", true));
        assertEquals("whitespace after value significant", "{k1=v1  (17:1)}", debugIS("k1=v1 \n", true));
        assertEquals("trailing newline not required", "{k1=v1 (17:1)}", debugIS("k1=v1", true));
        assertEquals("complex case", "{k1=vee one (17:1), k2=vee two  (17:3)}", debugIS(" k1 = vee \\\n    one\n k2  =  vee \\\n    two ", true));
        assertEquals("NOI18N works", "{k1=v1}", debugIS("#NOI18N\nk1=v1\n", true));
        assertEquals("I18N works", "{k1=v1 (17:2)}", debugIS("#I18N\nk1=v1\n", false));
        // XXX \ in key
        // XXX #PART{,NO}I18N
        // XXX Unicode escapes
        // XXX ':' rather than '='
        // XXX \r\n
        // XXX key with no value, or empty value
        // XXX value beginning with \
    }
    private static String debugIS(String s, boolean loc) throws IOException {
        InputStream dis = new NbBundle.DebugLoader.DebugInputStream(new ByteArrayInputStream(s.getBytes("ISO-8859-1")), 17, loc);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buf = new byte[4096];
        int read;
        while ((read = dis.read(buf)) != -1) {
            baos.write(buf, 0, read);
        }
        Properties p = new Properties();
        p.load(new ByteArrayInputStream(baos.toByteArray()));
        return new TreeMap<String,String>(NbCollections.checkedMapByFilter(p, String.class, String.class, true)).toString();
    }

}
