/*******************************************************************************
 * Copyright (c) 2007,2008 CA, Inc. and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CA - initial API and implementation
 *     IBM - defect/enhancement fixes
 *******************************************************************************/
package org.eclipse.cosmos.internal.dr.drs.service.outputter;


import java.io.PrintWriter;
import java.io.StringReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.eclipse.cosmos.dc.provisional.cmdbf.query.client.QueryServiceClient;
import org.eclipse.cosmos.dc.provisional.cmdbf.registration.client.RegistrationServiceClient;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.common.CMDBfServicesUtil;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.deregistration.transform.input.artifacts.DeregisterInputArtifactFactory;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.deregistration.transform.input.artifacts.IDeregisterRequest;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.query.transform.QueryInputTransformer;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.query.transform.input.artifacts.IQuery;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.query.transform.response.artifacts.IEdges;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.query.transform.response.artifacts.INodes;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.query.transform.response.artifacts.IQueryResult;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.registration.transform.input.artifacts.IRegisterRequest;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.registration.transform.input.artifacts.RegisterInputArtifactFactory;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.registration.transform.output.artifacts.IAccepted;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.registration.transform.output.artifacts.IDeclined;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.registration.transform.output.artifacts.IInstanceResponse;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.registration.transform.output.artifacts.IResponse;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.transform.artifacts.IGraphElement;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.transform.artifacts.IInstanceId;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.transform.artifacts.IItem;
import org.eclipse.cosmos.dc.provisional.cmdbf.services.transform.artifacts.IRelationship;
import org.eclipse.cosmos.internal.dr.drs.service.handler.common.IOutputterConstants;
import org.eclipse.cosmos.internal.dr.drs.service.handler.common.JSONArray;
import org.eclipse.cosmos.internal.dr.drs.service.handler.common.JSONObject;
import org.eclipse.cosmos.provisional.dr.drs.service.handler.common.AbstractOutputter;
import org.eclipse.cosmos.provisional.dr.drs.service.handler.common.IParameters;


/**
 * This outputter generates the JSON structure used in rendering 
 * the response of the registration/deregistration request.
 */
public class StatusResponseOutputter extends AbstractOutputter implements IOutputterConstants
{
	private static final String DEREGISTER = "deregister"; //$NON-NLS-1$ 
	private static final String CMDBF_EPR = "cmdbfEPR"; //$NON-NLS-1$
	private static final String USERNAME="username"; //$NON-NLS-1$
	private static final String PASSWORD="password"; //$NON-NLS-1$
	private static final String SOAPVERSION="soapversion"; //$NON-NLS-1$
	private static final String ACCEPTED_ITEMS = "ACCEPTED_ITEMS"; //$NON-NLS-1$
	private static final String DECLINED_ITEMS = "DECLINED_ITEMS"; //$NON-NLS-1$

	protected static final String AllQuery="<?xml version=\"1.0\" encoding=\"UTF-8\"?><s:query xmlns:s=\"http://cmdbf.org/schema/1-0-0/datamodel\">        <s:itemTemplate id=\"all-items\">                 </s:itemTemplate>        <s:itemTemplate id=\"items-with-relationship\">                   </s:itemTemplate>        <s:relationshipTemplate id=\"all-relationships\">          <s:sourceTemplate ref=\"items-with-relationship\"/>          <s:targetTemplate ref=\"items-with-relationship\"/>        </s:relationshipTemplate></s:query>"; //$NON-NLS-1$
	
	public void render(PrintWriter output, IParameters input) throws Exception 
	{
		Configuration configuration = new Configuration(input);		
		
		JSONObject rootObject = new JSONObject();
		rootObject.put(IDENTIFIER, ID);
		rootObject.put(LABEL, LABEL);


		if (configuration.isRegister)
		{
			// Register everything configuration items of all MDRs
			for (int i = 0; i < configuration.mdrEPRs.length; i++)
			{	
				IQueryResult graphResponse = submitQuery(configuration.mdrEPRs[i], AllQuery, configuration);
				registerDerigisterCI(graphResponse, rootObject, configuration);
			}			
		} 
		else 
		{
			String cmdbfQueryService = input.getParameter("queryEPR");
			
			for (int x = 0; x < configuration.mdrEPRs.length; x++){
				String mdrEPR = configuration.mdrEPRs[x];
				//submit the all query to the mdr
				IQueryResult graphResponse = submitQuery(mdrEPR, AllQuery, configuration);
				
				String mdrId = getMdrId(graphResponse);
				String ciQuery = String.format("<?xml version=\"1.0\" encoding=\"UTF-8\"?><s:query xmlns:s=\"http://cmdbf.org/schema/1-0-0/datamodel\">   <s:itemTemplate id=\"items\" suppressFromResult=\"false\" >      <s:instanceIdConstraint>         <s:instanceId>         <s:mdrId>%s</s:mdrId>         <s:localId>*</s:localId>        </s:instanceId>      </s:instanceIdConstraint>   </s:itemTemplate>   <s:itemTemplate id=\"itemswithrel\" suppressFromResult=\"false\" >      <s:instanceIdConstraint>         <s:instanceId>         <s:mdrId>%s</s:mdrId>         <s:localId>*</s:localId>         </s:instanceId>      </s:instanceIdConstraint>   </s:itemTemplate>   <s:relationshipTemplate id=\"relationship\">      <s:sourceTemplate ref=\"itemswithrel\"/>      <s:targetTemplate ref=\"itemswithrel\"/>   </s:relationshipTemplate></s:query>", mdrId, mdrId, mdrId); //$NON-NLS-1$
				
				//submit query for matching CIs to the CMDBf
				IQueryResult cmdbfResponse = submitQuery(cmdbfQueryService, ciQuery, configuration);
				registerDerigisterCI(cmdbfResponse, rootObject, configuration);
			}
		}
		
		output.print(rootObject.toString());
	}
	
	
	public String getMdrId(IQueryResult queryResult)  throws Exception
	{
		URI mdrId = new URI(""); //$NON-NLS-1$

 		INodes[] nodes = queryResult.getAllNodes();
 		for (INodes inode: nodes){
 			IGraphElement[] graphElements = inode.getElements();
 			
 			for (IGraphElement graphElement: graphElements ){
 				//get the first instanceid
 				if ((graphElement.getInstanceIds() != null) && (graphElement.getInstanceIds().length > 0)){ 		
 					mdrId = graphElement.getInstanceIds()[0].getMdrId();
 					break;
 				}
 			}
 		}

 		return mdrId.toString();
	}
	

	public void registerDerigisterCI(IQueryResult queryResult, JSONObject rootObject, Configuration config) throws Exception
	{		
		// construct the register/deregister request
		IRegisterRequest registerRequest = RegisterInputArtifactFactory.getInstance().createRegisterRequest();			
		IDeregisterRequest deregisterRequest = DeregisterInputArtifactFactory.getInstance().createDeregisterRequest();
		URI mdrId = new URI(""); //$NON-NLS-1$
 		// add the items (nodes) 
 		INodes[] nodes = queryResult.getAllNodes();
 		for (INodes inode: nodes){
 			IGraphElement[] graphElements = inode.getElements();
 			
 			for (IGraphElement graphElement: graphElements ){
 					//get the first instanceid
 					if ((graphElement.getInstanceIds() != null) && (graphElement.getInstanceIds().length > 0)){ 		
 						mdrId = graphElement.getInstanceIds()[0].getMdrId();
 					}
 					if (config.isRegister)
 						registerRequest.addItem( (IItem) graphElement );
 					else
 						deregisterRequest.addItem( (IItem) graphElement );
 			}
 		}
 		
 		// add the relationships (edges) 
 		IEdges[] edges = queryResult.getAllEdges();
		for (IEdges edge: edges){
 			IGraphElement[] graphElements = edge.getElements();
 			
 			for (IGraphElement graphElement: graphElements ){
 					if ((graphElement.getInstanceIds() != null) && (graphElement.getInstanceIds().length > 0)){ 		
						mdrId = graphElement.getInstanceIds()[0].getMdrId();
 					}
 					if (config.isRegister)
 						registerRequest.addRelationship( (IRelationship) graphElement );
 					else
 						deregisterRequest.addRelationship( (IRelationship) graphElement );

 			}
 		}
		
		// Set a dummy mdr id
		registerRequest.setMdrId(mdrId);
		deregisterRequest.setMdrId(mdrId);
		
		// get Federating CMDB client using EPR
		RegistrationServiceClient client = new RegistrationServiceClient(config.cmdbfEPR);
		if (config.username != null)
			client.setUsername(config.username);
		if (config.password != null)
			client.setPassword(config.password);
		if (config.soapVersion != null)
			client.setSoapVersion(Integer.parseInt(config.soapVersion));
		
		IResponse result = config.isRegister ? 
			client.register(registerRequest) : 
			client.deregister(deregisterRequest);
		
 		// convert the result to JSON
		renderStatusResult(rootObject, result);					
	}
				
	
	protected IQueryResult submitQuery(String mdrEPR, String query, Configuration config) throws Exception
	{
		StringReader queryReader = new StringReader(query);
		QueryServiceClient queryService =   new QueryServiceClient(mdrEPR);
		if (config.username != null)
			queryService.setUsername(config.username);
		if (config.password != null)
			queryService.setPassword(config.password);
		if (config.soapVersion != null)
			queryService.setSoapVersion(Integer.parseInt(config.soapVersion));

		IQuery iquery = QueryInputTransformer.transform(queryReader);
	    return queryService.graphQuery(iquery);
	}
		
	
	public static JSONObject renderStatusResult(JSONObject rootJSONObject, IResponse result) throws Exception 
	{
		if (result == null)
		{
			throw new Exception(Messages.getString("RegisterError.0")); //$NON-NLS-1$
		}
		
		if (rootJSONObject == null)
		{
			rootJSONObject = new JSONObject();
			rootJSONObject.put(IDENTIFIER, ID);
			rootJSONObject.put(LABEL, LABEL);
		}		
		
		
		IInstanceResponse[] instanceResponse = result.getInstanceResponses();
		boolean acceptedItemsPresent = true;
		boolean declinedItemsPresent = true;
		
		JSONArray acceptedItems = retrieveArray(rootJSONObject, ACCEPTED_ITEMS);
		JSONArray declinedItems = retrieveArray(rootJSONObject, DECLINED_ITEMS);
		
		if (acceptedItems == null)
		{
			acceptedItemsPresent = false;
			acceptedItems = new JSONArray();
		}
		if (declinedItems == null)
		{
			declinedItemsPresent = false;
			declinedItems = new JSONArray();
		}
		
		Map<String, Boolean> idMap = new Hashtable<String, Boolean>();
		
		// For each instance response
		for (int i = 0; i < instanceResponse.length; i++)
		{
			JSONObject item = createItem(idMap, instanceResponse[i]);
			if (item == null)
			{
				continue;
			}
			
			// Is this item declined?
			IDeclined declined = instanceResponse[i].getDeclined();
			IAccepted accepted = instanceResponse[i].getAccepted();
			if (declined != null)
			{
				// Add reasons if there are any
				String[] reasons = declined.getReasons();
				JSONArray reasonsArray = new JSONArray();
				for (int j = 0; j < reasons.length; j++)
				{
					reasonsArray.add(createJSONObject(new String[][]
	                {        
					        new String[] {ID, "REASON" + j}, //$NON-NLS-1$
					        new String[] {LABEL, reasons[j]}
					}));
				}
				
				addItem(declinedItems, item, reasonsArray);
			}
			// Otherwise it's been accepted
			else if (accepted != null)
			{
				IInstanceId[] alternativeInstanceIds = accepted.getAlternateInstanceIds();
				JSONArray alternativeIdArray = new JSONArray();
				
				// For each alternative instance id
				for (int j = 0; j < alternativeInstanceIds.length; j++)
				{
					String mdrId = CMDBfServicesUtil.toString(alternativeInstanceIds[j].getMdrId());
					String localId = CMDBfServicesUtil.toString(alternativeInstanceIds[j].getLocalId());
					
					alternativeIdArray.add(createJSONObject(new String[][]
						 {
							new String[] {ID, mdrId+localId},
							new String[] {MDR_ID, mdrId},
							new String[] {LOCAL_ID, localId},
							new String[] {LABEL, localId}
						 }));					
				}
				
				addItem(acceptedItems, item, alternativeIdArray);				
			}
		}
		
		JSONArray rootJSONArray = new JSONArray();
		
		if (!acceptedItemsPresent)
		{
			createItemStore(rootJSONArray, acceptedItems, true);
		}
		
		if (!declinedItemsPresent)
		{
			createItemStore(rootJSONArray, declinedItems, false);
		}
		
		if (!acceptedItemsPresent || !declinedItemsPresent)
		{
			addArrayToObject(ITEMS, rootJSONObject, rootJSONArray);	
		}
		
		return rootJSONObject;
	}


	private static JSONArray retrieveArray(JSONObject root, String id)
	{		
		if (root == null || !root.containsKey(IOutputterConstants.ITEMS))
		{
			return null;
		}

		JSONArray itemsArray = (JSONArray)root.get(IOutputterConstants.ITEMS);
		for (int i = 0, count = itemsArray.size(); i < count; i++)
		{
			JSONObject jsonObject = (JSONObject)itemsArray.get(i);
			if (id.equals(jsonObject.get(IOutputterConstants.ID)))
			{
				return (JSONArray)jsonObject.get(IOutputterConstants.RESPONSE);
			}
		}
		
		return null;
	}
	
	
	private static void createItemStore(JSONArray rootArray, JSONArray items, boolean accepted)
	{		
		if (items.size() <= 0)
		{
			return;
		}
		
		final String ID = accepted ? ACCEPTED_ITEMS : DECLINED_ITEMS; 
		final String label = accepted ? Messages.getString("AcceptedItems") : Messages.getString("DeclinedItems"); //$NON-NLS-1$ //$NON-NLS-2$
		JSONObject itemsObject = createJSONObject(new String[][]{
			new String[] {TYPE, RESPONSE},
			new String[] {IOutputterConstants.ID, ID},
			new String[] {LABEL, label}			
		});
		
		itemsObject.put(RESPONSE, items);
		rootArray.add(itemsObject);
	}


	private static void addItem(JSONArray itemsArray, JSONObject item, JSONArray innerArray)
	{
		addArrayToObject (RESPONSE, item, innerArray);
		itemsArray.add(item);
	}

	
	private static void addArrayToObject (String key, JSONObject jsonObject, JSONArray jsonArray)
	{
		if (jsonArray.size() > 0)
		{
			jsonObject.put(key, jsonArray);
		}
	}

	private static JSONObject createItem(Map<String, Boolean> idMap, IInstanceResponse instanceResponse)
	{
		IInstanceId instanceId = null;
		if (instanceResponse == null || (instanceId = instanceResponse.getInstanceId()) == null)
		{
			return null;
		}		

		String mdrId = CMDBfServicesUtil.toString(instanceId.getMdrId());
		String localId = CMDBfServicesUtil.toString(instanceId.getLocalId());
		
		// Make sure the ID used for the JSON structure is unique
		// Typically this should not be needed because the local id is expected to be unique in 
		// the scope of an MDR
		String id = mdrId+localId;	
		if (idMap.get(id) != null)
		{
			int counter = 0; 
			while (idMap.get(id+counter) != null)
			{
				counter++;
			}
			
			id += counter;
		}
		idMap.put(id, Boolean.TRUE);
				
		return createJSONObject(new String[][]
		       {
					new String[]{ID, id},
					new String[]{MDR_ID, mdrId},
					new String[]{LOCAL_ID, localId},					
					new String[]{LABEL, CMDBfServicesUtil.toString(instanceId.getLocalId())}
		       });		
	}

	private static JSONObject createJSONObject(String[][] pairs)
	{
		JSONObject jsonObject = new JSONObject();
		
		for (int i = 0; i < pairs.length; i++)
		{
			jsonObject.put(pairs[i][0], pairs[i][1]);			
		}
		return jsonObject;
	}
	
	
	/**
	 * The configuration associated with an MDR
	 * 
	 * @author Ali Mehregani
	 */
	private static class Configuration
	{
		private String username;
		private String password;
		private String soapVersion;
		private String cmdbfEPR;
		private boolean isRegister;
		private String[] mdrEPRs;
				
		public Configuration(IParameters input)
		{
			this.username = input.getParameter(USERNAME);
			this.password = input.getParameter(PASSWORD);
			this.soapVersion = input.getParameter(SOAPVERSION);
			this.cmdbfEPR = input.getParameter(CMDBF_EPR); 
			this.isRegister = input.getParameter(DEREGISTER) == null;
			
			// Get MDR EPR parameters
			List<String> mdrEPRList = new ArrayList<String>();
			for(Integer i = 0;; i++) 
			{
				String mdrParm = input.getParameter( "mdrEPR" + i.toString() ); //$NON-NLS-1$
				if(mdrParm == null)
					break;

				mdrEPRList.add(mdrParm);
			}
			
			this.mdrEPRs = mdrEPRList.toArray(new String[mdrEPRList.size()]);
		}
	}
}
