/*
 * 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
// /////////////
package org.apache.jena.rdf.model.test;

// Imports
// /////////////
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.apache.jena.rdf.model.* ;
import org.apache.jena.rdf.model.test.helpers.TestingModelFactory ;
import org.apache.jena.vocabulary.RDF ;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * A collection of unit tests for the standard implementation of {@link RDFList}
 * .
 * </p>
 * 
 * 
 */
public class TestList extends AbstractModelTestBase
{
	// Constants
	// ////////////////////////////////

	public static final String NS = "uri:urn:x-rdf:test#";

	/** Test that an iterator delivers the expected values */
	protected static void iteratorTest( final Iterator<?> i, final Object[] expected )
	{
		final Logger logger = LoggerFactory.getLogger(TestList.class);
		final List<Object> expList = new ArrayList<>();
		for (final Object element : expected)
		{
			expList.add(element);
		}

		while (i.hasNext())
		{
			final Object next = i.next();

			// debugging
			if (!expList.contains(next))
			{
				logger.debug("TestList - Unexpected iterator result: " + next);
			}

			Assert.assertTrue("Value " + next
					+ " was not expected as a result from this iterator ",
					expList.contains(next));
			Assert.assertTrue("Value " + next
					+ " was not removed from the list ", expList.remove(next));
		}

		if (!(expList.size() == 0))
		{
			logger.debug("TestList - Expected iterator results not found");
			for (final Object object : expList)
			{
				logger.debug("TestList - missing: " + object);
			}
		}
		Assert.assertEquals(
				"There were expected elements from the iterator that were not found",
				0, expList.size());
	}

	// Static variables
	// ////////////////////////////////

	// Instance variables
	// ////////////////////////////////

	// Constructors
	// ////////////////////////////////

	// public TestList( String name ) {
	// super( name );
	// }

	// External signature methods
	// ////////////////////////////////
	/*
	 * public static TestSuite suite() {
	 * TestSuite s = new TestSuite( "TestList" );
	 * TestList tl = new TestList();
	 * for (int i = 0; i <= 5; i++) {
	 * s.addTest( new CountTest( i ) );
	 * s.addTest( new TailTest( i ) );
	 * }
	 * 
	 * s.addTest( new ValidityTest() );
	 * s.addTest( new HeadTest() );
	 * s.addTest( new SetHeadTest() );
	 * s.addTest( new SetTailTest() );
	 * s.addTest( new ConsTest() );
	 * s.addTest( new AddTest() );
	 * s.addTest( new TestListGet() );
	 * s.addTest( new ReplaceTest() );
	 * s.addTest( new IndexTest1() );
	 * s.addTest( new IndexTest2() );
	 * s.addTest( new AppendTest() );
	 * s.addTest( new ConcatenateTest() );
	 * s.addTest( new ConcatenateTest2() );
	 * s.addTest( new ApplyTest() );
	 * s.addTest( new ReduceTest() );
	 * s.addTest( new RemoveTest() );
	 * s.addTest( new Map1Test() );
	 * s.addTest( new ListEqualsTest() );
	 * s.addTest( new ListSubclassTest() );
	 * s.addTest( new UserDefinedListTest() );
	 * 
	 * return s;
	 * }
	 */

	// public ListTest( String n ) {super(n);}

	public TestList( final TestingModelFactory modelFactory, final String name )
	{
		super(modelFactory, name);
	}

	protected void checkValid( final String testName, final RDFList l,
			final boolean validExpected )
	{
		l.setStrict(true);
		final boolean valid = l.isValid();
		// for debugging ... String s = l.getValidityErrorMessage();
		Assert.assertEquals("Validity test " + testName
				+ " returned wrong isValid() result", validExpected, valid);
	}

	// Internal implementation methods
	// ////////////////////////////////

	protected RDFList getListRoot( final Model m )
	{
		final Resource root = m.getResource(TestList.NS + "root");
		Assert.assertNotNull("Root resource should not be null", root);

		final Resource listHead = root.getRequiredProperty(
				m.getProperty(TestList.NS + "p")).getResource();

		final RDFList l = listHead.as(RDFList.class);
		Assert.assertNotNull("as(RDFList) should not return null for root", l);

		return l;
	}

	public void testAdd()
	{

		final Resource root = model.createResource(TestList.NS + "root");
		final Property p = model.createProperty(TestList.NS, "p");

		final Resource nil = model.getResource(RDF.nil.getURI());
		RDFList list = nil.as(RDFList.class);

		final Resource[] toAdd = new Resource[] {
				model.createResource(TestList.NS + "a"),
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "e"), };

		// add each of these resources onto the end of the list
		for (final Resource element : toAdd)
		{
			final RDFList list0 = list.with(element);

			checkValid("addTest0", list0, true);
			Assert.assertTrue("added'ed lists should be equal",
					list.equals(nil) || list0.equals(list));

			list = list0;
		}

		// relate the root to the list
		model.add(root, p, list);

		// should be isomorphic with list 5
		final Model m0 = ModelFactory.createDefaultModel();
		m0.read( getFileName( "ontology/list5.rdf"));

		Assert.assertTrue("Add'ed and read models should be the same",
				m0.isIsomorphicWith(model));

	}

	public void testAppend()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final Resource nil = model.getResource(RDF.nil.getURI());
		RDFList list = nil.as(RDFList.class);

		final Resource r = model.createResource(TestList.NS + "foo");

		// create a list of foos
		for (int i = 0; i < 5; i++)
		{
			list = list.cons(r);
		}

		final int listLen = list.size();

		// now append foos to the root list
		final RDFList root = getListRoot(model);
		final int rootLen = root.size();

		final RDFList appended = root.append(list);

		// original list should be unchanged
		checkValid("appendTest0", root, true);
		Assert.assertEquals("Original list should be unchanged", rootLen,
				root.size());

		checkValid("appendTest1", list, true);
		Assert.assertEquals("Original list should be unchanged", listLen,
				list.size());

		// new list should be length of combined
		checkValid("appendTest2", appended, true);
		Assert.assertEquals("Appended list not correct length", rootLen
				+ listLen, appended.size());
	}

	public void testApply()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final RDFList root = getListRoot(model);

		class MyApply implements RDFList.ApplyFn
		{
			String collect = "";

			@Override
			public void apply( final RDFNode n )
			{
				collect = collect + ((Resource) n).getLocalName();
			}
		}

		final MyApply f = new MyApply();
		root.apply(f);

		Assert.assertEquals(
				"Result of apply should be concatentation of local names",
				"abcde", f.collect);

	}

	public void testConcatenate()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final Resource nil = model.getResource(RDF.nil.getURI());
		RDFList list = nil.as(RDFList.class);

		final Resource r = model.createResource(TestList.NS + "foo");

		// create a list of foos
		for (int i = 0; i < 5; i++)
		{
			list = list.cons(r);
		}

		final int listLen = list.size();

		// now append foos to the root list
		final RDFList root = getListRoot(model);
		final int rootLen = root.size();
		root.concatenate(list);

		// original list should be unchanged
		checkValid("concatTest0", list, true);
		Assert.assertEquals("Original list should be unchanged", listLen,
				list.size());

		// but lhs list has changed
		checkValid("concatTest1", root, true);
		Assert.assertEquals("Root list should be new length",
				rootLen + listLen, root.size());
	}

	public void testConcatenate2()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final Resource a = model.createResource(TestList.NS + "a");

		// create a list of foos
		final Resource[] rs = new Resource[] {
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "e") };

		final RDFList aList = model.createList().cons(a);
		final RDFList rsList = model.createList(rs);

		// concatenate the above resources onto the empty list
		aList.concatenate(rsList);
		checkValid("concatTest3", aList, true);

		final RDFList root = getListRoot(model);
		Assert.assertTrue("Constructed and loaded lists should be the same",
				aList.sameListAs(root));
	}

	public void testCons()
	{
		final Resource root = model.createResource(TestList.NS + "root");
		final Property p = model.createProperty(TestList.NS, "p");

		final Resource nil = model.getResource(RDF.nil.getURI());
		RDFList list = nil.as(RDFList.class);

		final Resource[] toAdd = new Resource[] {
				model.createResource(TestList.NS + "e"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "a"), };

		// cons each of these resources onto the front of the list
		for (final Resource element : toAdd)
		{
			final RDFList list0 = list.cons(element);

			checkValid("constest1", list0, true);
			Assert.assertTrue("cons'ed lists should not be equal",
					!list0.equals(list));

			list = list0;
		}

		// relate the root to the list
		model.add(root, p, list);

		// should be isomorphic with list 5
		final Model m0 = ModelFactory.createDefaultModel();
		m0.read(getFileName("ontology/list5.rdf"));

		Assert.assertTrue("Cons'ed and read models should be the same",
				m0.isIsomorphicWith(model));
	}

	public void testCount()
	{
		for (int i = 0; i <= 5; i++)
		{
			model.removeAll();
			model.read( getFileName("ontology/list" + i + ".rdf"));

			final RDFList l0 = getListRoot(model);
			Assert.assertEquals("List size should be " + i, i, l0.size());
		}

	}

	public void testHead()
	{
		model.read(getFileName("ontology/list5.rdf"));

		RDFList l0 = getListRoot(model);

		final String[] names = { "a", "b", "c", "d", "e" };
		for (final String name : names)
		{
			Assert.assertEquals("head of list has incorrect URI", TestList.NS
					+ name, ((Resource) l0.getHead()).getURI());
			l0 = l0.getTail();
		}
	}

	public void testIndex1()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final Resource[] toGet = new Resource[] {
				model.createResource(TestList.NS + "a"),
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "e"), };

		final RDFList l1 = getListRoot(model);

		// check the indexes are correct
		for (int i = 0; i < toGet.length; i++)
		{
			Assert.assertTrue("list should contain element " + i,
					l1.contains(toGet[i]));
			Assert.assertEquals("list element " + i + " is not correct", i,
					l1.indexOf(toGet[i]));
		}
	}

	public void testIndex2()
	{

		final Resource nil = model.getResource(RDF.nil.getURI());
		RDFList list = nil.as(RDFList.class);

		final Resource r = model.createResource(TestList.NS + "a");

		// cons each a's onto the front of the list
		for (int i = 0; i < 10; i++)
		{
			list = list.cons(r);
		}

		// now index them back again
		for (int j = 0; j < 10; j++)
		{
			Assert.assertEquals("index of j'th item should be j", j,
					list.indexOf(r, j));
		}

	}

	public void testListEquals()
	{
		final Resource nil = model.getResource(RDF.nil.getURI());
		final RDFList nilList = nil.as(RDFList.class);

		// create a list of foos
		final Resource[] r0 = new Resource[] {
				model.createResource(TestList.NS + "a"), // canonical
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "e") };
		final Resource[] r1 = new Resource[] {
				model.createResource(TestList.NS + "a"), // same
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "e") };
		final Resource[] r2 = new Resource[] {
				model.createResource(TestList.NS + "a"), // one shorter
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "d") };
		final Resource[] r3 = new Resource[] {
				model.createResource(TestList.NS + "a"), // elements
				// swapped
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "e") };
		final Resource[] r4 = new Resource[] {
				model.createResource(TestList.NS + "a"), // different
				// name
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "D"),
				model.createResource(TestList.NS + "e") };

		final Object[][] testSpec = new Object[][] { { r0, r1, Boolean.TRUE },
				{ r0, r2, Boolean.FALSE }, { r0, r3, Boolean.FALSE },
				{ r0, r4, Boolean.FALSE }, { r1, r2, Boolean.FALSE },
				{ r1, r3, Boolean.FALSE }, { r1, r4, Boolean.FALSE },
				{ r2, r3, Boolean.FALSE }, { r2, r4, Boolean.FALSE }, };

		for (int i = 0; i < testSpec.length; i++)
		{
			final RDFList l0 = nilList.append(Arrays.asList(
					(Resource[]) testSpec[i][0]).iterator());
			final RDFList l1 = nilList.append(Arrays.asList(
					(Resource[]) testSpec[i][1]).iterator());
			final boolean expected = ((Boolean) testSpec[i][2]).booleanValue();

			Assert.assertEquals("sameListAs testSpec[" + i + "] incorrect",
					expected, l0.sameListAs(l1));
			Assert.assertEquals("sameListAs testSpec[" + i
					+ "] (swapped) incorrect", expected, l1.sameListAs(l0));
		}
	}

	public void testListGet()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final Resource[] toGet = new Resource[] {
				model.createResource(TestList.NS + "a"),
				model.createResource(TestList.NS + "b"),
				model.createResource(TestList.NS + "c"),
				model.createResource(TestList.NS + "d"),
				model.createResource(TestList.NS + "e"), };

		final RDFList l1 = getListRoot(model);

		// test normal gets
		for (int i = 0; i < toGet.length; i++)
		{
			Assert.assertEquals("list element " + i + " is not correct",
					toGet[i], l1.get(i));
		}

		// now test we get an exception for going beyong the end of the list
		boolean gotEx = false;
		try
		{
			l1.get(toGet.length + 1);
		}
		catch (final ListIndexException e)
		{
			gotEx = true;
		}

		Assert.assertTrue(
				"Should see exception raised by accessing beyond end of list",
				gotEx);
	}

	public void testMap1()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final RDFList root = getListRoot(model);
		TestList.iteratorTest(root.mapWith(n ->((Resource) n).getLocalName()), new Object[] { "a", "b", "c", "d", "e" });

	}

	public void testReduce()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final RDFList root = getListRoot(model);

		final RDFList.ReduceFn f = new RDFList.ReduceFn() {
			@Override
			public Object reduce( final RDFNode n, final Object acc )
			{
				return ((String) acc) + ((Resource) n).getLocalName();
			}
		};

		Assert.assertEquals(
				"Result of reduce should be concatentation of local names",
				"abcde", root.reduce(f, ""));
	}

	public void testRemove()
	{

		final Resource nil = model.getResource(RDF.nil.getURI());
		RDFList list0 = nil.as(RDFList.class);
		RDFList list1 = nil.as(RDFList.class);

		final Resource r0 = model.createResource(TestList.NS + "x");
		final Resource r1 = model.createResource(TestList.NS + "y");
		final Resource r2 = model.createResource(TestList.NS + "z");

		for (int i = 0; i < 10; i++)
		{
			list0 = list0.cons(r0);
			list1 = list1.cons(r1);
		}

		// delete the elements of list0 one at a time
		while (!list0.isEmpty())
		{
			list0 = list0.removeHead();
			checkValid("removeTest0", list0, true);
		}

		// delete all of list1 in one go
		list1.removeList();

		// model should now be empty
		Assert.assertEquals("Model should be empty after deleting two lists",
				0, model.size());

		// selective remove
		RDFList list2 = (nil.as(RDFList.class)).cons(r2).cons(r1).cons(r0);

		Assert.assertTrue("list should contain x ", list2.contains(r0));
		Assert.assertTrue("list should contain y ", list2.contains(r1));
		Assert.assertTrue("list should contain z ", list2.contains(r2));

		list2 = list2.remove(r1);
		Assert.assertTrue("list should contain x ", list2.contains(r0));
		Assert.assertTrue("list should contain y ", !list2.contains(r1));
		Assert.assertTrue("list should contain z ", list2.contains(r2));

		list2 = list2.remove(r0);
		Assert.assertTrue("list should contain x ", !list2.contains(r0));
		Assert.assertTrue("list should contain y ", !list2.contains(r1));
		Assert.assertTrue("list should contain z ", list2.contains(r2));

		list2 = list2.remove(r2);
		Assert.assertTrue("list should contain x ", !list2.contains(r0));
		Assert.assertTrue("list should contain y ", !list2.contains(r1));
		Assert.assertTrue("list should contain z ", !list2.contains(r2));
		Assert.assertTrue("list should be empty", list2.isEmpty());
	}

	public void testReplace()
	{
		model.read(getFileName("ontology/list5.rdf"));

		final Literal[] toSet = new Literal[] { model.createLiteral("a"),
				model.createLiteral("b"), model.createLiteral("c"),
				model.createLiteral("d"), model.createLiteral("e"), };

		final RDFList l1 = getListRoot(model);

		// change all the values
		for (int i = 0; i < toSet.length; i++)
		{
			l1.replace(i, toSet[i]);
		}

		// then check them
		for (int i = 0; i < toSet.length; i++)
		{
			Assert.assertEquals("list element " + i + " is not correct",
					toSet[i], l1.get(i));
		}

		// now test we get an exception for going beyong the end of the list
		boolean gotEx = false;
		try
		{
			l1.replace(toSet.length + 1, toSet[0]);
		}
		catch (final ListIndexException e)
		{
			gotEx = true;
		}

		Assert.assertTrue(
				"Should see exception raised by accessing beyond end of list",
				gotEx);

	}

	public void testSetHead()
	{

		final Resource root = model.createResource(TestList.NS + "root");
		final Property p = model.createProperty(TestList.NS, "p");

		// a list of the nil object, but not typed
		final Resource nil = RDF.nil;
		model.add(nil, RDF.type, RDF.List);

		final Resource list = model.createResource();
		model.add(list, RDF.type, RDF.List);
		model.add(list, RDF.first, "fred");
		model.add(list, RDF.rest, nil);

		model.add(root, p, list);
		final RDFList l1 = getListRoot(model);
		checkValid("sethead1", l1, true);

		Assert.assertEquals("List head should be 'fred'", "fred",
				((Literal) l1.getHead()).getString());

		l1.setHead(model.createTypedLiteral(42));
		checkValid("sethead2", l1, true);
		Assert.assertEquals("List head should be '42'", 42,
				((Literal) l1.getHead()).getInt());

	}

	public void testSetTail()
	{
		final Model m = ModelFactory.createDefaultModel();

		final Resource root = m.createResource(TestList.NS + "root");
		final Property p = m.createProperty(TestList.NS, "p");

		final Resource nil = RDF.nil;
		m.add(nil, RDF.type, RDF.List);

		final Resource list0 = m.createResource();
		m.add(list0, RDF.type, RDF.List);
		m.add(list0, RDF.first, "fred");
		m.add(list0, RDF.rest, nil);

		m.add(root, p, list0);
		final RDFList l1 = getListRoot(m);
		checkValid("settail1", l1, true);

		final Resource list1 = m.createResource();
		m.add(list1, RDF.type, RDF.List);
		m.add(list1, RDF.first, "george");
		m.add(list1, RDF.rest, nil);

		final RDFList l2 = list1.as(RDFList.class);
		Assert.assertNotNull("as(RDFList) should not return null for root", l2);
		checkValid("settail2", l2, true);

		Assert.assertEquals("l1 should have length 1", 1, l1.size());
		Assert.assertEquals("l2 should have length 1", 1, l2.size());

		// use set tail to join the lists together
		l1.setTail(l2);

		checkValid("settail3", l1, true);
		checkValid("settail4", l2, true);

		Assert.assertEquals("l1 should have length 2", 2, l1.size());
		Assert.assertEquals("l2 should have length 1", 1, l2.size());

	}

	public void testTail()
	{
		for (int i = 0; i <= 5; i++)
		{
			model.read( getFileName("ontology/list" + i + ".rdf"));

			RDFList l0 = getListRoot(model);

			// get the tail n times, should be nil at the end
			for (int j = 0; j < i; j++)
			{
				l0 = l0.getTail();
			}

			Assert.assertTrue("Should have reached the end of the list after "
					+ i + " getTail()'s", l0.isEmpty());
		}
	}

	public void testValidity()
	{
		final Resource root = model.createResource(TestList.NS + "root");
		final Property p = model.createProperty(TestList.NS, "p");

		// a list of the nil object, but not typed
		final Resource nil = RDF.nil;
		model.add(root, p, nil);
		final RDFList l0 = getListRoot(model);
		checkValid("valid1", l0, true);

		// add another node to the head of the list
		final Resource badList = model.createResource();
		model.getRequiredProperty(root, p).remove();
		model.add(root, p, badList);
		model.add(badList, RDF.type, RDF.List);

		final RDFList l1 = getListRoot(model);
		checkValid("valid2", l1, false);

		// checkValid( "valid3", l1, false );

		model.add(badList, RDF.first, "fred");
		checkValid("valid4", l1, false);

		model.add(badList, RDF.rest, nil);
		checkValid("valid5", l1, true);
	}
	
	public void testStmtGetList()
	{
	    Resource root = model.createResource(TestList.NS + "root");
	    Property p = model.createProperty(TestList.NS, "p");
	    Resource r = model.createResource(TestList.NS + "r");
	    Resource r1 = model.createResource(TestList.NS + "r1");
	    Resource r2 = model.createResource(TestList.NS + "r2");
	    
	    RDFList list0 = model.createList(r1, r2);
        model.add(r, p, list0);
        
        Resource obj = model.listStatements(r, p, (Resource)null).next().getResource();
        
        RDFList list1 = model.getList(obj);
        
        boolean b = list0.sameListAs(list1);
        assertTrue("Different lists: expected: "+list0+" : got: "+list1, b);
	}
	
	public void testModelGetList()
	{
	    Resource root = model.createResource(TestList.NS + "root");
	    Property p = model.createProperty(TestList.NS, "p");
	    Resource r = model.createResource(TestList.NS + "r");
	    Resource r1 = model.createResource(TestList.NS + "r1");
	    Resource r2 = model.createResource(TestList.NS + "r2");

	    RDFList list0 = model.createList(r1, r2);
	    model.add(r, p, list0);

	    RDFList list1 = model.listStatements(r, p, (Resource)null).next().getList();

	    boolean b = list0.sameListAs(list1);
	    assertTrue("Different lists: expected: "+list0+" : got: "+list1, b);
	}

	public void testModelGetEmptyList()
	{
	    Resource root = model.createResource(TestList.NS + "root");
	    Property p = model.createProperty(TestList.NS, "p");
	    Resource r = model.createResource(TestList.NS + "r");
	    
	    RDFList list0 = model.createList();
	    model.add(r, p, list0);

	    RDFList list1 = model.listStatements(r, p, (Resource)null).next().getList();

	    boolean b = list0.sameListAs(list1);
	    assertTrue("Different lists: expected: "+list0+" : got: "+list1, b);
	}

	
}
