/**
 * 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.apache.nutch.metadata;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Properties;

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

/**
 * JUnit based tests of class
 * {@link org.apache.nutch.metadata.SpellCheckedMetadata}.
 * 
 * @author Chris Mattmann
 * @author J&eacute;r&ocirc;me Charron
 */
public class TestSpellCheckedMetadata {

  private static final int NUM_ITERATIONS = 10000;

  /** Test for the <code>getNormalizedName(String)</code> method. */
  @Test
  public void testGetNormalizedName() {
    Assert.assertEquals("Content-Type",
        SpellCheckedMetadata.getNormalizedName("Content-Type"));
    Assert.assertEquals("Content-Type",
        SpellCheckedMetadata.getNormalizedName("ContentType"));
    Assert.assertEquals("Content-Type",
        SpellCheckedMetadata.getNormalizedName("Content-type"));
    Assert.assertEquals("Content-Type",
        SpellCheckedMetadata.getNormalizedName("contenttype"));
    Assert.assertEquals("Content-Type",
        SpellCheckedMetadata.getNormalizedName("contentype"));
    Assert.assertEquals("Content-Type",
        SpellCheckedMetadata.getNormalizedName("contntype"));
  }

  /** Test for the <code>add(String, String)</code> method. */
  @Test
  public void testAdd() {
    String[] values = null;
    SpellCheckedMetadata meta = new SpellCheckedMetadata();

    values = meta.getValues("contentype");
    Assert.assertEquals(0, values.length);

    meta.add("contentype", "value1");
    values = meta.getValues("contentype");
    Assert.assertEquals(1, values.length);
    Assert.assertEquals("value1", values[0]);

    meta.add("Content-Type", "value2");
    values = meta.getValues("contentype");
    Assert.assertEquals(2, values.length);
    Assert.assertEquals("value1", values[0]);
    Assert.assertEquals("value2", values[1]);

    // NOTE : For now, the same value can be added many times.
    // Should it be changed?
    meta.add("ContentType", "value1");
    values = meta.getValues("Content-Type");
    Assert.assertEquals(3, values.length);
    Assert.assertEquals("value1", values[0]);
    Assert.assertEquals("value2", values[1]);
    Assert.assertEquals("value1", values[2]);
  }

  /** Test for the <code>set(String, String)</code> method. */
  @Test
  public void testSet() {
    String[] values = null;
    SpellCheckedMetadata meta = new SpellCheckedMetadata();

    values = meta.getValues("contentype");
    Assert.assertEquals(0, values.length);

    meta.set("contentype", "value1");
    values = meta.getValues("contentype");
    Assert.assertEquals(1, values.length);
    Assert.assertEquals("value1", values[0]);

    meta.set("Content-Type", "value2");
    values = meta.getValues("contentype");
    Assert.assertEquals(1, values.length);
    Assert.assertEquals("value2", values[0]);

    meta.set("contenttype", "new value 1");
    meta.add("contenttype", "new value 2");
    values = meta.getValues("contentype");
    Assert.assertEquals(2, values.length);
    Assert.assertEquals("new value 1", values[0]);
    Assert.assertEquals("new value 2", values[1]);
  }

  /** Test for <code>setAll(Properties)</code> method. */
  @Test
  public void testSetProperties() {
    String[] values = null;
    SpellCheckedMetadata meta = new SpellCheckedMetadata();
    Properties props = new Properties();

    meta.setAll(props);
    Assert.assertEquals(0, meta.size());

    props.setProperty("name-one", "value1.1");
    meta.setAll(props);
    Assert.assertEquals(1, meta.size());
    values = meta.getValues("name-one");
    Assert.assertEquals(1, values.length);
    Assert.assertEquals("value1.1", values[0]);

    props.setProperty("name-two", "value2.1");
    meta.setAll(props);
    Assert.assertEquals(2, meta.size());
    values = meta.getValues("name-one");
    Assert.assertEquals(1, values.length);
    Assert.assertEquals("value1.1", values[0]);
    values = meta.getValues("name-two");
    Assert.assertEquals(1, values.length);
    Assert.assertEquals("value2.1", values[0]);
  }

  /** Test for <code>get(String)</code> method. */
  @Test
  public void testGet() {
    SpellCheckedMetadata meta = new SpellCheckedMetadata();
    Assert.assertNull(meta.get("a-name"));

    meta.add("a-name", "value-1");
    Assert.assertEquals("value-1", meta.get("a-name"));
    meta.add("a-name", "value-2");
    Assert.assertEquals("value-1", meta.get("a-name"));
  }

  /** Test for <code>isMultiValued()</code> method. */
  @Test
  public void testIsMultiValued() {
    SpellCheckedMetadata meta = new SpellCheckedMetadata();
    Assert.assertFalse(meta.isMultiValued("key"));
    meta.add("key", "value1");
    Assert.assertFalse(meta.isMultiValued("key"));
    meta.add("key", "value2");
    Assert.assertTrue(meta.isMultiValued("key"));
  }

  /** Test for <code>names</code> method. */
  @Test
  public void testNames() {
    String[] names = null;
    SpellCheckedMetadata meta = new SpellCheckedMetadata();
    names = meta.names();
    Assert.assertEquals(0, names.length);

    meta.add("name-one", "value");
    names = meta.names();
    Assert.assertEquals(1, names.length);
    Assert.assertEquals("name-one", names[0]);
    meta.add("name-two", "value");
    names = meta.names();
    Assert.assertEquals(2, names.length);
  }

  /** Test for <code>remove(String)</code> method. */
  @Test
  public void testRemove() {
    SpellCheckedMetadata meta = new SpellCheckedMetadata();
    meta.remove("name-one");
    Assert.assertEquals(0, meta.size());
    meta.add("name-one", "value-1.1");
    meta.add("name-one", "value-1.2");
    meta.add("name-two", "value-2.2");
    Assert.assertEquals(2, meta.size());
    Assert.assertNotNull(meta.get("name-one"));
    Assert.assertNotNull(meta.get("name-two"));
    meta.remove("name-one");
    Assert.assertEquals(1, meta.size());
    Assert.assertNull(meta.get("name-one"));
    Assert.assertNotNull(meta.get("name-two"));
    meta.remove("name-two");
    Assert.assertEquals(0, meta.size());
    Assert.assertNull(meta.get("name-one"));
    Assert.assertNull(meta.get("name-two"));
  }

  /** Test for <code>equals(Object)</code> method. */
  @Test
  public void testObject() {
    SpellCheckedMetadata meta1 = new SpellCheckedMetadata();
    SpellCheckedMetadata meta2 = new SpellCheckedMetadata();
    Assert.assertFalse(meta1.equals(null));
    Assert.assertFalse(meta1.equals("String"));
    Assert.assertTrue(meta1.equals(meta2));
    meta1.add("name-one", "value-1.1");
    Assert.assertFalse(meta1.equals(meta2));
    meta2.add("name-one", "value-1.1");
    Assert.assertTrue(meta1.equals(meta2));
    meta1.add("name-one", "value-1.2");
    Assert.assertFalse(meta1.equals(meta2));
    meta2.add("name-one", "value-1.2");
    Assert.assertTrue(meta1.equals(meta2));
    meta1.add("name-two", "value-2.1");
    Assert.assertFalse(meta1.equals(meta2));
    meta2.add("name-two", "value-2.1");
    Assert.assertTrue(meta1.equals(meta2));
    meta1.add("name-two", "value-2.2");
    Assert.assertFalse(meta1.equals(meta2));
    meta2.add("name-two", "value-2.x");
    Assert.assertFalse(meta1.equals(meta2));
  }

  /** Test for <code>Writable</code> implementation. */
  @Test
  public void testWritable() {
    SpellCheckedMetadata result = null;
    SpellCheckedMetadata meta = new SpellCheckedMetadata();
    result = writeRead(meta);
    Assert.assertEquals(0, result.size());
    meta.add("name-one", "value-1.1");
    result = writeRead(meta);
    meta.add("Contenttype", "text/html");
    Assert.assertEquals(1, result.size());
    Assert.assertEquals(1, result.getValues("name-one").length);
    Assert.assertEquals("value-1.1", result.get("name-one"));
    meta.add("name-two", "value-2.1");
    meta.add("name-two", "value-2.2");
    result = writeRead(meta);
    Assert.assertEquals(3, result.size());
    Assert.assertEquals(1, result.getValues("name-one").length);
    Assert.assertEquals("value-1.1", result.getValues("name-one")[0]);
    Assert.assertEquals(2, result.getValues("name-two").length);
    Assert.assertEquals("value-2.1", result.getValues("name-two")[0]);
    Assert.assertEquals("value-2.2", result.getValues("name-two")[1]);
    Assert.assertEquals("text/html", result.get(Metadata.CONTENT_TYPE));
  }

  /**
   * IO Test method, usable only when you plan to do changes in metadata to
   * measure relative performance impact.
   */
  @Test
  public final void testHandlingSpeed() {
    @SuppressWarnings("unused")
    SpellCheckedMetadata result;
    long start = System.currentTimeMillis();
    for (int i = 0; i < NUM_ITERATIONS; i++) {
      SpellCheckedMetadata scmd = constructSpellCheckedMetadata();
      result = writeRead(scmd);
    }
    System.out.println(NUM_ITERATIONS + " spellchecked metadata I/O time:"
        + (System.currentTimeMillis() - start) + "ms.");
  }

  private SpellCheckedMetadata writeRead(SpellCheckedMetadata meta) {
    SpellCheckedMetadata readed = new SpellCheckedMetadata();
    try {
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      meta.write(new DataOutputStream(out));
      readed.readFields(new DataInputStream(new ByteArrayInputStream(out
          .toByteArray())));
    } catch (IOException ioe) {
      Assert.fail(ioe.toString());
    }
    return readed;
  }

  /**
   * Assembles a Spellchecked metadata Object.
   */
  public static final SpellCheckedMetadata constructSpellCheckedMetadata() {
    SpellCheckedMetadata scmd = new SpellCheckedMetadata();
    scmd.add("Content-type", "foo/bar");
    scmd.add("Connection", "close");
    scmd.add("Last-Modified", "Sat, 09 Dec 2006 15:09:57 GMT");
    scmd.add("Server", "Foobar");
    scmd.add("Date", "Sat, 09 Dec 2006 18:07:20 GMT");
    scmd.add("Accept-Ranges", "bytes");
    scmd.add("ETag", "\"1234567-89-01234567\"");
    scmd.add("Content-Length", "123");
    scmd.add(Nutch.SEGMENT_NAME_KEY, "segmentzzz");
    scmd.add(Nutch.SIGNATURE_KEY, "123");
    return scmd;
  }

}
