/**
 * This work was created by participants in the DataONE project, and is
 * jointly copyrighted by participating institutions in DataONE. For 
 * more information on DataONE, see our web site at http://dataone.org.
 *
 *   Copyright ${year}
 *
 * Licensed 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.
 * 
 * $Id$
 */

package org.dataone.integration.tools;

import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.collections.CollectionUtils;
import org.dataone.client.v1.CNode;
import org.dataone.integration.APITestUtils;
import org.dataone.service.exceptions.BaseException;
import org.dataone.service.exceptions.NotImplemented;
import org.dataone.service.exceptions.ServiceFailure;
import org.dataone.service.types.v1.Node;
import org.dataone.service.types.v1.NodeType;
import org.dataone.service.types.v1.ObjectInfo;
import org.dataone.service.types.v1.ObjectList;
import org.dataone.service.types.v1.util.NodelistUtil;
import org.dataone.service.util.DateTimeMarshaller;
import org.junit.Test;


/**
 * The goal here is to test synchronization of metadata created on a MN
 * with a CN.  Synchronization is a CN scheduled cron job, so there is no
 * trigger for synchronization.  We have to wait for it to happen :-)
 * The testing approach is:
 *    1. create a new data on a MN
 *    2. periodically poll the CN for a period greater than the cron interval
 *       checking for presence of the new object there using:
 *       a). getSystemMetadata
 *       b). resolve
 *       c). search
 *       
 * @author rnahf
 *
 */
public class ProductionContentCheckingTools  {

	private String currentUrl;	
//	@Rule 
//	public ErrorCollector errorCollector = new ErrorCollector();
	
    
    /**
     * test the unit test harness
     */
    @Test
    public void testTrue()
    {
 
    }


    
	private Set<String> buildSerializedObjectInfoSet(ObjectList ol) {
		
		Set<String> oiSet = new HashSet<String>();

		for (ObjectInfo oi: ol.getObjectInfoList()) {
			oiSet.add(
					String.format("%s\t%s\t%s\t%d\t%s\t%s",
			    			oi.getIdentifier().getValue(),
			    			oi.getFormatId().getValue(),
			    			DateTimeMarshaller.serializeDateToUTC(oi.getDateSysMetadataModified()),
			    			oi.getSize(),
			    			oi.getChecksum().getAlgorithm(),
			    			oi.getChecksum().getValue())
					);
		}
		return oiSet;
	}
	
	
	private Set<String> buildSerializedObjectInfoSet_cs(ObjectList ol) {
		
		Set<String> oiSet = new HashSet<String>();

		for (ObjectInfo oi: ol.getObjectInfoList()) {
			oiSet.add(
					String.format("%s\t%s\t%d\t%s\t%s\t%s",
							oi.getChecksum().getAlgorithm(),
			    			oi.getChecksum().getValue(),
			    			oi.getSize(),
			    			oi.getIdentifier().getValue(),
			    			oi.getFormatId().getValue(),
			    			DateTimeMarshaller.serializeDateToUTC(oi.getDateSysMetadataModified())
			    			)		
					);
		}
		return oiSet;
	}

	
	@Test
	public void reportObjectInfoDifferences() 
	{			
		try {
			ObjectList ol = null;
			HashMap<String,Set<String>> olMap = new HashMap<String,Set<String>>();
			Set<String> superSet = new TreeSet<String>();
			
			CNode rrCn = new CNode("https://cn.dataone.org/cn");
			Set<Node> cnSet = NodelistUtil.selectNodes(rrCn.listNodes(),NodeType.CN);
			for (Node n : cnSet) {
				String url = n.getBaseURL();
				// skip the round-robin cn
				if (url.equals("https://cn.dataone.org/cn"))
					continue;
				System.out.println(url + ": doing listObjects()");
				
				Set<String> oiSet = new HashSet<String>(89);
				try { 
					CNode cn = new CNode(url);
					
					int runningTotal = 0;
					int olTotal = 0;
					int i = 0;
					while (runningTotal < olTotal || olTotal == 0) {
						ol = cn.listObjects(null, null, null, null, runningTotal, 1000);
						olTotal = ol.getTotal();
						runningTotal += ol.getObjectInfoList().size();
						for (ObjectInfo oi : ol.getObjectInfoList()) {
							String entry = String.format("%s\t%s\t%s\t%d\t%s\t%s",
					    			oi.getIdentifier().getValue(),
					    			oi.getFormatId().getValue(),
					    			oi.getDateSysMetadataModified(),
					    			oi.getSize(),
					    			oi.getChecksum().getAlgorithm(),
					    			oi.getChecksum().getValue());
							i++;
							if (!oiSet.add(entry))
								System.out.println("   duplicate entry: " + i + " " + entry);
						}
						
						System.out.println("  running total: " + runningTotal);
					}
					olMap.put(url, oiSet);
					superSet.addAll(oiSet);
					System.out.println(String.format("cn: %s   ol size: %d",url, olMap.get(url).size()));
				} 
				catch (BaseException be) {
					System.out.println("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
				}
			}
			Object[] urls = olMap.keySet().toArray();
			
			System.out.println();
			for (int i=0; i<urls.length; i++) {
				System.out.println((char) (i+65) + " = " + urls[i]);
			}
			System.out.println();
			
			Iterator<String> it = superSet.iterator();
			while (it.hasNext()) {
				String coi = it.next();
				
				String membership = "";
				for (int i=0; i<urls.length; i++) {
					char c = olMap.get(urls[i]).contains(coi) ?  (char) (i+65) : '-';
					membership += c;
				}
				if (membership.contains("-")) {
					System.out.println(membership + "\t" + coi.toString());
				}
				membership = "";
			}

		}
		catch (BaseException be) {
			fail("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
		}
	}
	
	
	
	
	/**
	 * this test is sensitive to the size of the CN objectList, memory-wise
	 */
	@Test
	public void testAllObjectsSynchronized_via_ListObjects()
	{

		CNode rrCn = null;
		try {
			rrCn = new CNode("https://cn.dataone.org/cn");
			
			Set<Node> cnSet = NodelistUtil.selectNodes(rrCn.listNodes(),NodeType.CN);
			
			Date now = new Date();
			Date toDate = new Date(now.getTime() - 10 * 60 * 1000);

			System.out.println("date: " + now);
			HashMap<String,Set<String>> olMap = new HashMap<String,Set<String>>();
			Set<String> superSet = new TreeSet<String>();
			for (Node n : cnSet) {
				String url = n.getBaseURL();
				// skip the round-robin cn
				if (url.equals("https://cn.dataone.org/cn"))
					continue;
				System.out.println(url + ": doing listObjects()");
				try { 
					CNode cn = new CNode(url);
					Set<String> oiSet = buildSerializedObjectInfoSet(
						APITestUtils.pagedListObjects(cn, null, null, null, null, null, null));
					olMap.put(url, oiSet);
					superSet.addAll(oiSet);
					System.out.println(String.format("cn: %s   ol size: %d",url, olMap.get(url).size()));
				} 
				catch (BaseException be) {
					System.out.println("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
				}
			}
			
			Object[] urls = olMap.keySet().toArray();
			
			System.out.println();
			for (int i=0; i<urls.length; i++) {
				System.out.println((char) (i+65) + " = " + urls[i]);
			}
			System.out.println();
			
			Iterator<String> it = superSet.iterator();
			while (it.hasNext()) {
				String coi = it.next();
				
				String membership = "";
				for (int i=0; i<urls.length; i++) {
					char c = olMap.get(urls[i]).contains(coi) ?  (char) (i+65) : '-';
					membership += c;
				}
				if (membership.contains("-")) {
					System.out.println(membership + "\t" + coi.toString());
				}
				membership = "";
			}

		}
		catch (BaseException be) {
			String baseurl = rrCn != null ? rrCn.getNodeBaseServiceUrl() : "default CN";
			fail("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
		}
	}
	
	/**
	 * this test is sensitive to the size of the CN objectList, memory-wise
	 */
	@Test
	public void testAllObjectsSynchronized_via_ListObjects_cs() throws NotImplemented, ServiceFailure
	{

		CNode rrCn = null;
//		try {
			rrCn = new CNode("https://cn.dataone.org/cn");
			
			Set<Node> cnSet = NodelistUtil.selectNodes(rrCn.listNodes(),NodeType.CN);
			
			Date now = new Date();
			Date toDate = new Date(now.getTime() - 10 * 60 * 1000);

			System.out.println("date: " + now);
			HashMap<String,Set<String>> olMap = new HashMap<String,Set<String>>();
			Set<String> superSet = new TreeSet<String>();
			for (Node n : cnSet) {
				String url = n.getBaseURL();
				// skip the round-robin cn
				if (url.equals("https://cn.dataone.org/cn"))
					continue;
				System.out.println(url + "\n... doing listObjects()");
				try { 
					CNode cn = new CNode(url);
					ObjectList ol = APITestUtils.pagedListObjects(cn, null, null, null, null, null, null);
					System.out.println("... building serializedObjectInfo set");
					Set<String> oiSet = buildSerializedObjectInfoSet_cs(ol);
					olMap.put(url, oiSet);
					superSet.addAll(oiSet);
					System.out.println(String.format("cn: %s   ol size: %d",url, olMap.get(url).size()));
				} 
				catch (BaseException be) {
					be.printStackTrace();
					System.out.println("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
				}
			}
			
			Object[] urls = olMap.keySet().toArray();
			
			System.out.println();
			for (int i=0; i<urls.length; i++) {
				System.out.println((char) (i+65) + " = " + urls[i]);
			}
			System.out.println();
			
			Iterator<String> it = superSet.iterator();
			while (it.hasNext()) {
				String coi = it.next();
				
				String membership = "";
				for (int i=0; i<urls.length; i++) {
					char c = olMap.get(urls[i]).contains(coi) ?  (char) (i+65) : '-';
					membership += c;
				}
				if (membership.contains("-")) {
					System.out.println(membership + "\t" + coi.toString());
				}
				membership = "";
			}

//		}
//		catch (BaseException be) {
//			be.printStackTrace();
//			String baseurl = rrCn != null ? rrCn.getNodeBaseServiceUrl() : "default CN";
//			fail("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
//		}
	}
	
	
	/**
	 * this test is sensitive to the size of the CN objectList, memory-wise
	 */
	@Test
	public void testListObjectsUniqueness()
	{

		CNode rrCn = null;
		try {
			rrCn = new CNode("https://cn.dataone.org/cn");
			
			Set<Node> cnSet = NodelistUtil.selectNodes(rrCn.listNodes(),NodeType.CN);
			
			Date now = new Date();
			Date toDate = new Date(now.getTime() - 10 * 60 * 1000);

			System.out.println("date: " + now);
			HashMap<String,Set<String>> olMap = new HashMap<String,Set<String>>();
			Set<String> superSet = new TreeSet<String>();
			for (Node n : cnSet) {
				String url = n.getBaseURL();
				// skip the round-robin cn
				if (url.equals("https://cn.dataone.org/cn"))
					continue;
				System.out.println(url + ": doing listObjects()");
				try { 
					CNode cn = new CNode(url);
					List<ObjectInfo> oiList = 
						APITestUtils.pagedListObjects(cn, null, null, null, null, null, null).getObjectInfoList();
					
					System.out.println("  ObjectList size = " + oiList.size());
					
					List<String> idList = new ArrayList<String>();
					List<String> csList = new ArrayList<String>();
					for (ObjectInfo oi : oiList) {
						idList.add(oi.getIdentifier().getValue());	
						csList.add(oi.getChecksum().getAlgorithm() + ":" + oi.getChecksum().getValue());
					}
					Map<String,Integer> idCardMap = CollectionUtils.getCardinalityMap(idList);
					Map<String,Integer> csCardMap = CollectionUtils.getCardinalityMap(csList);
					
					System.out.println("  # identifiers = " + idCardMap.size());
					
					for (String key : idCardMap.keySet()) {
						if (idCardMap.get(key) > 1) {
							System.out.println(String.format("    %d of %s", idCardMap.get(key), key));
						}
					}
					
					System.out.println("  # checksums = " + csCardMap.size());
					for (String key : csCardMap.keySet()) {
						if (csCardMap.get(key) > 1) {
							System.out.println(String.format("    %d of %s", csCardMap.get(key), key));
						}
					}
				} 
				catch (BaseException be) {
					System.out.println("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
				}
			}
			
			Object[] urls = olMap.keySet().toArray();
			
			System.out.println();
			for (int i=0; i<urls.length; i++) {
				System.out.println((char) (i+65) + " = " + urls[i]);
			}
			System.out.println();
			
			Iterator<String> it = superSet.iterator();
			while (it.hasNext()) {
				String coi = it.next();
				
				String membership = "";
				for (int i=0; i<urls.length; i++) {
					char c = olMap.get(urls[i]).contains(coi) ?  (char) (i+65) : '-';
					membership += c;
				}
				if (membership.contains("-")) {
					System.out.println(membership + "\t" + coi.toString());
				}
				membership = "";
			}

		}
		catch (BaseException be) {
			String baseurl = rrCn != null ? rrCn.getNodeBaseServiceUrl() : "default CN";
			fail("problem getting an ObjectList from the cn. " + be.getClass() + ": " + be.getDescription());
		}
	}
	
	
	


}