/*
 * 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.stanbol.entityhub.test.yard;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.apache.stanbol.entityhub.test.Utils.asCollection;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.yard.Yard;
import org.apache.stanbol.entityhub.servicesapi.yard.YardException;
import org.junit.Test;

public abstract class YardTest {

    /**
     * Getter for the {@link Yard} instance to be tested
     * 
     * @return the {@link Yard} instance to be tested
     */
    protected abstract Yard getYard();

    /**
     * Stores all the IDs of Representations created by the create(..) methods. This collection is used to
     * clean up the store after all the unit tests are executed.
     */
    protected static Collection<String> representationIds = new HashSet<String>();

    protected Representation create() throws YardException {
        Representation r = getYard().create();
        representationIds.add(r.getId());
        return r;
    }

    protected Representation create(String id, boolean store) throws YardException {
        Representation r;
        if (store) {
            r = getYard().create(id);
        } else if (id != null && !id.isEmpty()) {
            r = getYard().getValueFactory().createRepresentation(id);
        } else {
            throw new IllegalArgumentException("If store is FALSE the id MUST NOT be NULL nor EMPTY!");
        }
        representationIds.add(r.getId());
        return r;
    }

    @Test
    public void testGetValueFactory() {
        assertNotNull("The ValueFactory MUST NOT be NULL", getYard().getValueFactory());
    }

    @Test
    public void testGetQueryFactory() {
        assertNotNull("The QueryFactory MUST NOT be NULL", getYard().getQueryFactory());
    }

    @Test
    public void testYardId() {
        assertNotNull("The ID of the Yard MUST NOT be NULL", getYard().getId());
    }

    @Test
    public void testYardName() {
        assertNotNull("The Name of the Yard MUST NOT be NULL", getYard().getName());
        assertFalse("The Name of the Yard MUST NOT be an empty String", getYard().getName().isEmpty());
    }

    @Test
    public void testDefaultCreate() throws YardException {
        Yard yard = getYard();
        Representation test = yard.create();
        assertNotNull(test);
        Representation test2 = yard.create();
        assertNotNull(test2);
        assertNotSame(test, test2);
        assertFalse("Two Representation created with create() MUST NOT be equals", test.equals(test2));
    }

    @Test
    public void testCreateWithNull() throws YardException {
        Yard yard = getYard();
        Representation test = yard.create(null);
        assertNotNull("Parsing NULL to create(String) MUST create a valid Representation", test);
        representationIds.add(test.getId()); // add id to cleanup list
        Representation test2 = yard.create(null);
        assertNotNull("Parsing NULL to create(String) MUST create a valid Representation", test2);
        representationIds.add(test2.getId()); // add id to cleanup list
        assertNotSame(test, test2);
        assertFalse("Two Representation created with create(null) MUST NOT be equals", test.equals(test2));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testCreateWithEmptyString() throws YardException {
        getYard().create("");// throws an IllegalArgumentException
    }

    @Test(expected = IllegalArgumentException.class)
    public void testCreateWithExistingId() throws YardException {
        Yard yard = getYard();
        Representation test = create();
        assertNotNull(test);
        yard.create(test.getId()); // throws an IllegalArgumentException
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIsRepresentationWithNull() throws YardException {
        getYard().isRepresentation(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testIsRepresentationWithEmptyString() throws YardException {
        getYard().isRepresentation("");
    }

    @Test(expected = IllegalArgumentException.class)
    public void testStoreRepresentationWithNull() throws YardException {
        getYard().store((Representation) null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testStoreRepresentationsWithNull() throws YardException {
        getYard().store((Iterable<Representation>) null);
    }

    @Test
    public void testStoreRepresentation() throws YardException {
        // NOTE: this does not test if the updated view of the representation is
        // stored, but only that the store method works for representations
        // that are already in the Yard AND representations that are not yet
        // present within the yard
        String testId = "urn:yard.test.testStoreRepresentation:representation.id1";
        String testId2 = "urn:yard.test.testStoreRepresentation:representation.id2";
        Yard yard = getYard();
        Representation test = create(testId, false);
        Representation added = yard.store(test); // this adds the representation
        assertEquals(test, added);
        Representation test2 = create(testId2, true); // this creates and adds the representation
        // now test that the representation can also be updated
        added = yard.store(test2);
        assertEquals(test2, added);
    }

    @Test
    public void testStoreRepresentations() throws YardException {
        // NOTE: this does not test if the updated view of the representation is
        // stored, but only that the store method works for representations
        // that are already in the Yard AND representations that are not yet
        // present within the yard
        String testId = "urn:yard.test.testStoreRepresentations:representation.id1";
        String testId2 = "urn:yard.test.testStoreRepresentations:representation.id2";
        String field = "urn:the.field:used.for.this.Test";
        Yard yard = getYard();
        Representation test = create(testId, false);
        Representation test2 = create(testId2, true); // this creates and adds the representation
        // now add both and mix Representations that are already present in the yard
        // with an other that is not yet present in the yard

        // change the representations to be sure to force an update even if the
        // implementation checks for changes before updating a representation
        test2.add(field, "test value 2");
        Iterable<Representation> addedIterable = yard.store(Arrays.asList(test, test2));
        assertNotNull(addedIterable);
        Collection<Representation> added = asCollection(addedIterable.iterator());
        // test that both the parsed Representations where stored (updated & created)
        assertTrue(added.remove(test));
        assertTrue(added.remove(test2));
        assertTrue(added.isEmpty());
    }

    @Test
    public void testStoreRepresentationsWithNullElement() throws YardException {
        String testId = "urn:yard.test.testStoreRepresentationsWithNullElement:representation.id";
        Yard yard = getYard();
        Representation test = create(testId, false);
        Iterable<Representation> added = yard.store(Arrays.asList(test, null));
        // now test that only the valid representation was added and the null
        // value was ignored
        assertNotNull(added);
        Iterator<Representation> addedIt = added.iterator();
        assertTrue(addedIt.hasNext());
        assertEquals(test, addedIt.next());
        assertFalse(addedIt.hasNext());
    }

    @Test(expected = IllegalArgumentException.class)
    public void testGetRepresentationWithNull() throws YardException {
        getYard().getRepresentation(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testGetRepresentationWithEmptyString() throws YardException {
        getYard().getRepresentation("");
    }

    @Test
    public void testGetNonExistantRepresentation() throws YardException {
        String id = "urn:yard.test.testGetNonExistantRepresentation:representation.id";
        assertNull(getYard().getRepresentation(id));
    }

    /**
     * This tests that {@link Representation} retrieved from the Yard do not influence other
     * {@link Representation} instances for the same ID. It is important, that when different
     * {@link Representation} instances are returned to different caller, that these do not influence each
     * other.
     * <p>
     * It also tests, that any update to the {@link Representation} as managed by the {@link Yard} does also
     * not influence {@link Representation} instances that where created before the change.
     * 
     * @throws YardException
     */
    @Test
    public void testGetRepresentation() throws YardException {
        String id = "urn:yard.test.testGetRepresentation.id";
        Yard yard = getYard();
        String field = "urn:the.field:used.for.this.Test";
        String value1 = "this is a test";
        // Representations created via the yard need to be created (as empty
        // representation within the yard
        Representation test = create(id, true);
        // retrieve the representation from the store
        Representation retrieved = yard.getRepresentation(id);
        assertNotNull(retrieved);
        // they MUST NOT be the same, but the MUST be equals
        // Note:
        // the fact that two representations with the same id are equals is tested
        // by the unit tests for the representation interface
        assertEquals(test, retrieved);
        assertNotSame("getRepresentation MUST return an new instance for each "
                      + "call, so that changes in one return instance do not influence "
                      + "an other returned instance!", test, retrieved);
        // now add a property to the original one
        test.add(field, value1);
        // and check that the retrieved does not have the value
        assertFalse(retrieved.get(field).hasNext());
        // now store the representation and check that updated are not reflected
        // within the retrieved one
        yard.store(test);
        assertFalse(retrieved.get(field).hasNext());
        // now retrieve again an representation
        retrieved = null;
        retrieved = yard.getRepresentation(id);
        // now the Representation MUST HAVE the new field
        assertTrue(retrieved.get(field).hasNext());
        assertEquals(value1, retrieved.getFirst(field));

        // finally retrieve a second and perform the change test again
        Representation retrieved2 = yard.getRepresentation(id);
        retrieved.removeAll(field);
        // check the value is still in retrieved2
        assertTrue(retrieved2.get(field).hasNext());
        assertEquals(value1, retrieved2.getFirst(field));

    }

    @Test
    public void testIsRepresentation() throws YardException {
        String id = "urn:yard.test.testIsRepresentation:representation.id";
        Yard yard = getYard();
        // Representations created via the yard need to be created (as empty
        // representation within the yard
        Representation test = create();
        assertTrue(yard.isRepresentation(test.getId()));
        // Representations created via the ValueFactory MUST NOT be added to the
        // Yard
        Representation test2 = create(id, false);
        assertFalse(yard.isRepresentation(test2.getId()));
        // now store test2 and test again
        yard.store(test2);
        assertTrue(yard.isRepresentation(test2.getId()));
        // now remove test and test again
        yard.remove(test.getId());
        assertFalse(yard.isRepresentation(test.getId()));
        yard.remove(test2.getId());
        assertFalse(yard.isRepresentation(test2.getId()));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUpdateRepresentationWithNull() throws YardException {
        getYard().update((Representation) null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUpdateRepresentationWithNonPresent() throws YardException {
        String id = "urn:yard.test.testUpdateRepresentationWithNonPresent:representation.id";
        Representation test = create(id, false);
        getYard().update(test); // throws an Exception because test is not part of the yard
    }

    @Test(expected = IllegalArgumentException.class)
    public void testUpdateRepresentationsWithNull() throws YardException {
        getYard().update((Iterable<Representation>) null);
    }

    @Test
    public void testUpdateRepresentations() throws YardException {
        // NOTE: this does not test if the updated view of the representation is
        // stored, but only that the update method works correctly
        Yard yard = getYard();
        String id1 = "urn:yard.test.testUpdateRepresentations:representation.id1";
        String id2 = "urn:yard.test.testUpdateRepresentations:representation.id2";
        String field = "urn:the.field:used.for.this.Test";
        Representation test1 = create(id1, true);
        Representation test2 = create(id2, true);
        // change the representations to be sure to force an update even if the
        // implementation checks for changes before updating a representation
        test1.add(field, "test value 1");
        test2.add(field, "test value 2");
        Iterable<Representation> updatedIterable = yard.update(Arrays.asList(test1, test2));
        assertNotNull(updatedIterable);
        Collection<Representation> updated = asCollection(updatedIterable.iterator());
        // test that both the parsed Representations where stored (updated & created)
        assertTrue(updated.remove(test1));
        assertTrue(updated.remove(test2));
        assertTrue(updated.isEmpty());
    }

    /**
     * When updating multiple null values need to be ignored.
     * 
     * @throws YardException
     */
    @Test
    public void testUpdateRepresentationsWithNullElement() throws YardException {
        // NOTE: this does not test if the updated view of the representation is
        // stored, but only that the update method works correctly
        Yard yard = getYard();
        String id1 = "urn:yard.test.testUpdateRepresentationsWithNullElement:representation.id";
        String field = "urn:the.field:used.for.this.Test";
        Representation test1 = create(id1, true);
        // change the representations to be sure to force an update even if the
        // implementation checks for changes before updating a representation
        test1.add(field, "test value 1");
        Iterable<Representation> updated = yard.update(Arrays.asList(test1, null));
        assertNotNull(updated);
        Iterator<Representation> updatedIt = updated.iterator();
        assertTrue(updatedIt.hasNext());
        assertEquals(test1, updatedIt.next());
        assertFalse(updatedIt.hasNext());
    }

    /**
     * When updating multiple non present representations need to be ignored.
     * 
     * @throws YardException
     */
    @Test
    public void testUpdateRepresentationsWithNonPresent() throws YardException {
        // NOTE: this does not test if the updated view of the representation is
        // stored, but only that the update method works correctly
        Yard yard = getYard();
        String id1 = "urn:yard.test.testUpdateRepresentationsWithNonPresent:representation.id1";
        String id2 = "urn:yard.test.testUpdateRepresentationsWithNonPresent:representation.id2";
        String field = "urn:the.field:used.for.this.Test";
        Representation test1 = create(id1, true);
        // change the representations to be sure to force an update even if the
        // implementation checks for changes before updating a representation
        test1.add(field, "test value 1");
        // create a 2nd Representation by using the ValueFactory (will not add it
        // to the yard!)
        Representation test2 = create(id2, false);
        // now test1 is present and test2 is not.
        Iterable<Representation> updated = yard.update(Arrays.asList(test1, test2));
        // We expect that only test1 is updated and test2 is ignored
        assertNotNull(updated);
        Iterator<Representation> updatedIt = updated.iterator();
        assertTrue(updatedIt.hasNext());
        assertEquals(test1, updatedIt.next());
        assertFalse(updatedIt.hasNext());
    }

    @Test(expected = IllegalArgumentException.class)
    public void testRemoveRepresentationWithNull() throws YardException {
        getYard().remove((String) null);
    }

    /**
     * Tests two things:
     * <ol>
     * <li>if representations are removed form the yard
     * <li>if the representation instance is still valid after it is removed
     * </ol>
     * The second is very important, because Representations might still be used by other components after
     * they are remove from the store they where created in
     * 
     * @throws YardException
     */
    @Test
    public void testRemoveRepresentation() throws YardException {
        // NOTE: This test needs not to use the create(..) method, because we
        // remove the created representation form the store anyway as part of the
        // test
        String id = "urn:yard.test.tesrRemoveRepresentation:representation.id";
        String field = "urn:the.field:used.for.this.Test";
        String testValue = "This is a test";
        Yard yard = getYard();
        Representation test = yard.getValueFactory().createRepresentation(id);
        test.add(field, testValue);
        yard.store(test); // store the representation
        assertTrue(yard.isRepresentation(test.getId())); // test if stored
        test = null; // free the initial
        // create the instance form the store to test (2)
        test = yard.getRepresentation(id);
        assertEquals(id, test.getId()); // only to be sure ...
        yard.remove(test.getId()); // test (1) - the remove
        assertFalse(yard.isRepresentation(test.getId()));// test if removed
        // test if the test object is not destroyed by removing the representation
        // it represents form the store (2)
        assertEquals(testValue, test.getFirst(field));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testRemoveRepresentationsWithNull() throws YardException {
        getYard().remove((Iterable<String>) null);
    }

    /**
     * Tests that multiple Representations are removed.
     * 
     * @throws YardException
     */
    @Test
    public void testRemoveRepresentations() throws YardException {
        // NOTE: This test needs not to use the create(..) method, because we
        // remove the created representation form the store anyway as part of the
        // test
        String id = "urn:yard.test.testRemoveRepresentationsWithNullValue:representation.id1";
        String id2 = "urn:yard.test.testRemoveRepresentationsWithNullValue:representation.id2";
        String field = "urn:the.field:used.for.this.Test";
        String testValue = "This is a test";
        Yard yard = getYard();
        // use both ways to add the two Representations (should make no differences,
        // but one never can know ...
        Representation test1 = yard.create(id); // create and add
        Representation test2 = yard.getValueFactory().createRepresentation(id2); // create
        test2.add(field, testValue); // add value
        yard.store(test2);// store
        assertTrue(yard.isRepresentation(test1.getId())); // test if stored
        assertTrue(yard.isRepresentation(test2.getId()));
        yard.remove(Arrays.asList(test1.getId(), test2.getId())); // remove
        assertFalse(yard.isRepresentation(test1.getId())); // test if removed
        assertFalse(yard.isRepresentation(test2.getId()));
    }
    /**
     * Tests that multiple Representations are removed.
     * 
     * @throws YardException
     */
    @Test
    public void testRemoveAll() throws YardException {
        // NOTE: This test needs not to use the create(..) method, because we
        // remove the created representation form the store anyway as part of the
        // test
        String id = "urn:yard.test.testRemoveAll:representation.id1";
        String id2 = "urn:yard.test.testRemoveAll:representation.id2";
        String field = "urn:the.field:used.for.this.Test";
        String testValue = "This is a test";
        Yard yard = getYard();
        // use both ways to add the two Representations (should make no differences,
        // but one never can know ...
        Representation test1 = yard.create(id); // create and add
        test1.add(field, testValue); // add value
        yard.store(test1);// store
        Representation test2 = yard.getValueFactory().createRepresentation(id2); // create
        test2.add(field, testValue); // add value
        yard.store(test2);// store
        assertTrue(yard.isRepresentation(test1.getId())); // test if stored
        assertTrue(yard.isRepresentation(test2.getId()));
        yard.removeAll(); // remove
        assertFalse(yard.isRepresentation(test1.getId())); // test if removed
        assertFalse(yard.isRepresentation(test2.getId()));
        //test that Yard is still useable after removeAll
        yard.store(test1);// store
        assertTrue(yard.isRepresentation(test1.getId())); // test if stored
        yard.removeAll(); // remove
        assertFalse(yard.isRepresentation(test1.getId()));
    }
    /**
     * Tests if <code>null</code> values within the Iterable are ignored and do not cause an Exception
     * 
     * @throws YardException
     */
    @Test
    public void testRemoveRepresentationsWithNullValue() throws YardException {
        // NOTE: This test needs not to use the create(..) method, because we
        // remove the created representation form the store anyway as part of the
        // test
        String id = "urn:yard.test.testRemoveRepresentationsWithNullValue:representation.id";
        Yard yard = getYard();
        Representation test = yard.create(id); // create and add
        assertTrue(yard.isRepresentation(test.getId()));
        yard.remove(Arrays.asList(test.getId(), null));
        assertFalse(yard.isRepresentation(test.getId()));
    }

    /**
     * Tests that {@link Representation} IDs that are not stored by the yard are ignored by the multiple
     * remove method
     * 
     * @throws YardException
     */
    @Test
    public void testRemoveRepresentationsWithNonStoredValue() throws YardException {
        // NOTE: This test needs not to use the create(..) method, because we
        // remove the created representation form the store anyway as part of the
        // test
        String id = "urn:yard.test.testRemoveRepresentationsWithNullValue:representation.stored";
        String id2 = "urn:yard.test.testRemoveRepresentationsWithNullValue:representation.notStored";
        Yard yard = getYard();
        Representation test = yard.create(id); // create and add
        assertTrue(yard.isRepresentation(test.getId()));
        yard.remove(Arrays.asList(test.getId(), id2));
        assertFalse(yard.isRepresentation(test.getId()));
        assertFalse(yard.isRepresentation(id2));
    }

    /**
     * The {@link Representation} as stored in the Yard MUST NOT change if the source {@link Representation}
     * stored to the {@link Yard} or an Representation retrieved from the Yard is changed. Only the
     * {@link Yard#store(Representation))} and the {@link Yard#update(Representation)} methods are allowed to
     * synchronised the Representation within the Yard with the state (changes) of the parsed value.
     * 
     * @throws YardException
     */
    @Test
    public void testChangeRepresentation() throws YardException {
        String id = "urn:yard.test.testChangeRepresentation:representation.id";
        String field = "urn:the.field:used.for.this.Test";
        String testValue = "This is a test";
        Yard yard = getYard();
        // use the ValueFactory to create the representation to ensure that this
        // instance has nothing to do with the store
        Representation test = create(id, false);
        // now store the empty Representation
        yard.store(test);
        // now add a values
        test.add(field, testValue);
        // now get the representation from the yard
        Representation stored = yard.getRepresentation(id);
        // test if the stored version does not contain the value
        assertFalse(stored.get(field).hasNext());
        stored = null;
        // now store the updated version
        yard.store(test);
        // now check that the updated value is stored
        stored = yard.getRepresentation(id);
        assertEquals(testValue, stored.getFirst(field));

        // now we need to test if modifications of an Representation returned
        test = stored;
        stored = null;
        String testValue2 = "This is an ohter test value";
        test.add(field, testValue2);

        // now get the representation from the yard and check that it still has
        // only one value
        stored = yard.getRepresentation(id);
        Collection<Object> values = asCollection(stored.get(field));
        assertTrue(values.remove(testValue)); // test that it contains the value
        assertTrue(values.isEmpty()); // and that there is only this value
        values = null;
        // now update the stored version with the new state
        stored = null;
        stored = yard.update(test);
        values = asCollection(stored.get(field));
        assertTrue(values.remove(testValue)); // test that it contains the original
        assertTrue(values.remove(testValue2)); // test that it contains the 2nd value
        assertTrue(values.isEmpty()); // and that there are only these two values
        values = null;
    }
}
