//
//  ========================================================================
//  Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Assert;
import org.junit.Test;

import static org.hamcrest.Matchers.nullValue;

public class MultiMapTest
{
    /**
     * Tests {@link MultiMap#put(Object, Object)}
     */
    @Test
    public void testPut()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        mm.put(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");
    }

    /**
     * Tests {@link MultiMap#put(Object, Object)}
     */
    @Test
    public void testPut_Null_String()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";
        String val = null;

        mm.put(key,val);
        assertMapSize(mm,1);
        assertNullValues(mm,key);
    }

    /**
     * Tests {@link MultiMap#put(Object, Object)}
     */
    @Test
    public void testPut_Null_List()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";
        List<String> vals = null;

        mm.put(key,vals);
        assertMapSize(mm,1);
        assertNullValues(mm,key);
    }

    /**
     * Tests {@link MultiMap#put(Object, Object)}
     */
    @Test
    public void testPut_Replace()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";
        Object ret;

        ret = mm.put(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");
        Assert.assertNull("Should not have replaced anything", ret);
        Object orig = mm.get(key);

        // Now replace it
        ret = mm.put(key,"jar");
        assertMapSize(mm,1);
        assertValues(mm,key,"jar");
        Assert.assertEquals("Should have replaced original", orig, ret);
    }

    /**
     * Tests {@link MultiMap#putValues(String, List)}
     */
    @Test
    public void testPutValues_List()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        List<String> input = new ArrayList<String>();
        input.add("gzip");
        input.add("jar");
        input.add("pack200");

        mm.putValues(key,input);
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");
    }

    /**
     * Tests {@link MultiMap#putValues(String, String...)}
     */
    @Test
    public void testPutValues_StringArray()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        String input[] = { "gzip", "jar", "pack200" };
        mm.putValues(key,input);
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");
    }

    /**
     * Tests {@link MultiMap#putValues(String, String...)}
     */
    @Test
    public void testPutValues_VarArgs()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        mm.putValues(key,"gzip", "jar", "pack200");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");
    }

    /**
     * Tests {@link MultiMap#add(String, Object)}
     */
    @Test
    public void testAdd()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.put(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");

        // Add to the key
        mm.add(key,"jar");
        mm.add(key,"pack200");

        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");
    }

    /**
     * Tests {@link MultiMap#addValues(String, List)}
     */
    @Test
    public void testAddValues_List()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.put(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");

        // Add to the key
        List<String> extras = new ArrayList<String>();
        extras.add("jar");
        extras.add("pack200");
        extras.add("zip");
        mm.addValues(key,extras);

        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200","zip");
    }

    /**
     * Tests {@link MultiMap#addValues(String, List)}
     */
    @Test
    public void testAddValues_List_Empty()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.put(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");

        // Add to the key
        List<String> extras = new ArrayList<String>();
        mm.addValues(key,extras);

        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");
    }

    /**
     * Tests {@link MultiMap#addValues(String, Object[])}
     */
    @Test
    public void testAddValues_StringArray()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.put(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");

        // Add to the key
        String extras[] = { "jar", "pack200", "zip" };
        mm.addValues(key,extras);

        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200","zip");
    }

    /**
     * Tests {@link MultiMap#addValues(String, Object[])}
     */
    @Test
    public void testAddValues_StringArray_Empty()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.put(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");

        // Add to the key
        String extras[] = new String[0];
        mm.addValues(key,extras);

        assertMapSize(mm,1);
        assertValues(mm,key,"gzip");
    }

    /**
     * Tests {@link MultiMap#removeValue(String, Object)}
     */
    @Test
    public void testRemoveValue()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.putValues(key,"gzip","jar","pack200");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");

        // Remove a value
        mm.removeValue(key,"jar");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","pack200");

    }

    /**
     * Tests {@link MultiMap#removeValue(String, Object)}
     */
    @Test
    public void testRemoveValue_InvalidItem()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.putValues(key,"gzip","jar","pack200");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");

        // Remove a value that isn't there
        mm.removeValue(key,"msi");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");
    }

    /**
     * Tests {@link MultiMap#removeValue(String, Object)}
     */
    @Test
    public void testRemoveValue_AllItems()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.putValues(key,"gzip","jar","pack200");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","jar","pack200");

        // Remove a value
        mm.removeValue(key,"jar");
        assertMapSize(mm,1);
        assertValues(mm,key,"gzip","pack200");

        // Remove another value
        mm.removeValue(key,"gzip");
        assertMapSize(mm,1);
        assertValues(mm,key,"pack200");

        // Remove last value
        mm.removeValue(key,"pack200");
        assertMapSize(mm,0);  // should be empty now
    }

    /**
     * Tests {@link MultiMap#removeValue(String, Object)}
     */
    @Test
    public void testRemoveValue_FromEmpty()
    {
        MultiMap<String> mm = new MultiMap<>();

        String key = "formats";

        // Setup the key
        mm.putValues(key,new String[0]);
        assertMapSize(mm,1);
        assertEmptyValues(mm,key);

        // Remove a value that isn't in the underlying values
        mm.removeValue(key,"jar");
        assertMapSize(mm,1);
        assertEmptyValues(mm,key);
    }

    /**
     * Tests {@link MultiMap#putAll(java.util.Map)}
     */
    @Test
    public void testPutAll_Map()
    {
        MultiMap<String> mm = new MultiMap<>();

        assertMapSize(mm,0); // Shouldn't have anything yet.

        Map<String,String> input = new HashMap<String,String>();
        input.put("food","apple");
        input.put("color","red");
        input.put("amount","bushel");

        mm.putAllValues(input);

        assertMapSize(mm,3);
        assertValues(mm,"food","apple");
        assertValues(mm,"color","red");
        assertValues(mm,"amount","bushel");
    }

    /**
     * Tests {@link MultiMap#putAll(java.util.Map)}
     */
    @Test
    public void testPutAll_MultiMap_Simple()
    {
        MultiMap<String> mm = new MultiMap<>();

        assertMapSize(mm,0); // Shouldn't have anything yet.

        MultiMap<String> input = new MultiMap<>();
        input.put("food","apple");
        input.put("color","red");
        input.put("amount","bushel");

        mm.putAll(input);

        assertMapSize(mm,3);
        assertValues(mm,"food","apple");
        assertValues(mm,"color","red");
        assertValues(mm,"amount","bushel");
    }

    /**
     * Tests {@link MultiMap#putAll(java.util.Map)}
     */
    @Test
    public void testPutAll_MultiMapComplex()
    {
        MultiMap<String> mm = new MultiMap<>();

        assertMapSize(mm,0); // Shouldn't have anything yet.

        MultiMap<String> input = new MultiMap<>();
        input.putValues("food","apple","cherry","raspberry");
        input.put("color","red");
        input.putValues("amount","bushel","pint");

        mm.putAll(input);

        assertMapSize(mm,3);
        assertValues(mm,"food","apple","cherry","raspberry");
        assertValues(mm,"color","red");
        assertValues(mm,"amount","bushel","pint");
    }

    /**
     * Tests {@link MultiMap#toStringArrayMap()}
     */
    @Test
    public void testToStringArrayMap()
    {
        MultiMap<String> mm = new MultiMap<>();
        mm.putValues("food","apple","cherry","raspberry");
        mm.put("color","red");
        mm.putValues("amount","bushel","pint");

        assertMapSize(mm,3);

        Map<String,String[]> sam = mm.toStringArrayMap();
        Assert.assertEquals("String Array Map.size",3,sam.size());

        assertArray("toStringArrayMap(food)", sam.get("food"), "apple","cherry","raspberry");
        assertArray("toStringArrayMap(color)", sam.get("color"), "red");
        assertArray("toStringArrayMap(amount)", sam.get("amount"), "bushel","pint");
    }

    /**
     * Tests {@link MultiMap#toString()}
     */
    @Test
    public void testToString()
    {
        MultiMap<String> mm = new MultiMap<>();
        mm.put("color","red");

        Assert.assertEquals("{color=red}", mm.toString());

        mm.putValues("food","apple","cherry","raspberry");

        Assert.assertEquals("{color=red, food=[apple, cherry, raspberry]}", mm.toString());
    }

    /**
     * Tests {@link MultiMap#clear()}
     */
    @Test
    public void testClear()
    {
        MultiMap<String> mm = new MultiMap<>();
        mm.putValues("food","apple","cherry","raspberry");
        mm.put("color","red");
        mm.putValues("amount","bushel","pint");

        assertMapSize(mm,3);

        mm.clear();

        assertMapSize(mm,0);
    }

    /**
     * Tests {@link MultiMap#containsKey(Object)}
     */
    @Test
    public void testContainsKey()
    {
        MultiMap<String> mm = new MultiMap<>();
        mm.putValues("food","apple","cherry","raspberry");
        mm.put("color","red");
        mm.putValues("amount","bushel","pint");

        Assert.assertTrue("Contains Key [color]", mm.containsKey("color"));
        Assert.assertFalse("Contains Key [nutrition]", mm.containsKey("nutrition"));
    }

    /**
     * Tests {@link MultiMap#containsSimpleValue(Object)}
     */
    @Test
    public void testContainsSimpleValue()
    {
        MultiMap<String> mm = new MultiMap<>();
        mm.putValues("food","apple","cherry","raspberry");
        mm.put("color","red");
        mm.putValues("amount","bushel","pint");

        Assert.assertTrue("Contains Value [red]", mm.containsSimpleValue("red"));
        Assert.assertFalse("Contains Value [nutrition]", mm.containsValue("nutrition"));
    }

    /**
     * Tests {@link MultiMap#containsValue(Object)}
     */
    @Test
    public void testContainsValue()
    {
        MultiMap<String> mm = new MultiMap<>();
        mm.putValues("food","apple","cherry","raspberry");
        mm.put("color","red");
        mm.putValues("amount","bushel","pint");

        List<String> acr = new ArrayList<>();
        acr.add("apple");
        acr.add("cherry");
        acr.add("raspberry");
        Assert.assertTrue("Contains Value [apple,cherry,raspberry]", mm.containsValue(acr));
        Assert.assertFalse("Contains Value [nutrition]", mm.containsValue("nutrition"));
    }

    /**
     * Tests {@link MultiMap#containsValue(Object)}
     */
    @Test
    public void testContainsValue_LazyList()
    {
        MultiMap<String> mm = new MultiMap<>();
        mm.putValues("food","apple","cherry","raspberry");
        mm.put("color","red");
        mm.putValues("amount","bushel","pint");

        Object list = LazyList.add(null, "bushel");
        list = LazyList.add(list, "pint");

        Assert.assertTrue("Contains Value [" + list + "]", mm.containsValue(list));
    }

    private void assertArray(String prefix, Object[] actualValues, Object ...expectedValues)
    {
        Assert.assertEquals(prefix + ".size",expectedValues.length,actualValues.length);
        int len = actualValues.length;
        for (int i = 0; i < len; i++)
        {
            Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],actualValues[i]);
        }
    }

    private void assertValues(MultiMap<String> mm, String key, Object... expectedValues)
    {
        List<String> values = mm.getValues(key);

        String prefix = "MultiMap.getValues(" + key + ")";

        Assert.assertEquals(prefix + ".size",expectedValues.length,values.size());
        int len = expectedValues.length;
        for (int i = 0; i < len; i++)
        {
            if(expectedValues[i] == null) {
                Assert.assertThat(prefix + "[" + i + "]",values.get(i),nullValue());
            } else {
                Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],values.get(i));
            }
        }
    }

    private void assertNullValues(MultiMap<String> mm, String key)
    {
        List<String> values = mm.getValues(key);

        String prefix = "MultiMap.getValues(" + key + ")";

        Assert.assertThat(prefix + ".size",values,nullValue());
    }

    private void assertEmptyValues(MultiMap<String> mm, String key)
    {
        List<String> values = mm.getValues(key);

        String prefix = "MultiMap.getValues(" + key + ")";

        Assert.assertEquals(prefix + ".size",0,LazyList.size(values));
    }

    private void assertMapSize(MultiMap<String> mm, int expectedSize)
    {
        Assert.assertEquals("MultiMap.size",expectedSize,mm.size());
    }
}
