/**
 * 
 */
package navex.graphDatabase;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.ws.rs.core.MediaType;

import org.apache.http.NameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

import navex.HTMLForm;
import edu.uci.ics.crawler4j.crawler.Page;
import edu.uci.ics.crawler4j.url.WebURL;


/**
 * @author Abeer Alhuzali
 *  * this is the implementation of creating the navigation graph (a neo4j graph database) using Jason.
 *
 */
public class NeoGraphDatabase {

	protected final static Logger logger = LoggerFactory.getLogger(NeoGraphDatabase.class);
	private static final String SERVER_ROOT_URI = "http://localhost:7474/db/data/";

	HashSet<NavigationDatabaseNode> nodes ;//= new HashSet<URI>();
	HashMap<URI, NavigationDatabaseNode> navNodes ;//= new HashMap<>();
	HashMap<String, URI> urlUriMap ;//= new HashMap<>();



	public NeoGraphDatabase() {
		super();
		nodes = new HashSet<NavigationDatabaseNode>();
		navNodes = new HashMap<>();
		urlUriMap = new HashMap<String, URI>();
	}

	// START SNIPPET: createReltype
	private static enum RelTypes 
	{
		HAS_A_LINK_TO, HAS_A_FORM_TO;
	}
	// END SNIPPET: createReltype

	/**
	 * @param page
	 * @param form 
	 * @param type: either form or link 
	 * @param params : for post request in forms , if it is a link, then it will be null
	 * @param smethod : either get or post
	 */
	public void StartNeoDb(Page page, HTMLForm form, String type, List<NameValuePair> params, String method) 
	{
		checkDatabaseIsRunning();

		if (type == "link"){
			createDbforLink(page);
		}
		else if (type == "form") {
			createDbforForm(page ,form , params, method);
		}


	}


	private void createDbforForm(Page page, HTMLForm form, List<NameValuePair> params, 
			String method) {
		//creating source node
		String url = page.getWebURL().getURL();
		URI nodeUri = null;

		if (this.urlUriMap.containsKey(url) )
		{
			nodeUri = this.urlUriMap.get(url);
		}

		//creating dest node
		NavigationDatabaseNode ndn= new NavigationDatabaseNode (page, form, params, method);
		URI dest = createNode();

		this.nodes.add(ndn);
		this.navNodes.put(dest, ndn);
		this.urlUriMap.put(ndn.getUrl(), dest);

		try {
			addNavigationNodePropertyForm(ndn, dest);
		} catch (URISyntaxException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			addRelationship(nodeUri, dest, "HAS_A_FORM_TO" );
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}



	}


	private void createDbforLink(Page page) {
		NavigationDatabaseNode ndn = NavigationDatabaseNode.findNode(this.nodes, page.getWebURL().getURL());
		URI firstNode ; 
		if ( ndn != null){
			System.out.println("found the navigation node in DB "+ndn.getUrl());
			for (Entry<URI, NavigationDatabaseNode> map : this.navNodes.entrySet()){
				if (map.getValue().equals(ndn))
				{firstNode = map.getKey();
				System.out.println("used an already created node ndn is"+ ndn.toString());
				try {
					addNavigationNodeProperty(ndn, firstNode);
				} catch (URISyntaxException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				}
			}
		}
		else {

			System.out.println("Did Not find the navigation node in DB ");

			ndn= new NavigationDatabaseNode (page);


			ndn.initialize(page);

			// START SNIPPET: nodesAndProps
			firstNode = createNode();

			this.nodes.add(ndn);
			this.navNodes.put(firstNode, ndn);
			this.urlUriMap.put(ndn.getUrl(), firstNode);

			try {
				addNavigationNodeProperty(ndn, firstNode);
			} catch (URISyntaxException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}




	private static URI createNode()
	{
		// START SNIPPET: createNode
		final String nodeEntryPointUri = SERVER_ROOT_URI + "node";
		// http://localhost:7474/db/data/node

		WebResource resource = Client.create()
				.resource( nodeEntryPointUri );
		// POST {} to the node entry point URI
		ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
				.type( MediaType.APPLICATION_JSON )
				.entity( "{}" )
				.post( ClientResponse.class );

		final URI location = response.getLocation();
		System.out.println( String.format(
				"POST to [%s], status code [%d], location header [%s]",
				nodeEntryPointUri, response.getStatus(), location.toString() ) );
		response.close();

		return location;
		// END SNIPPET: createNode
	}


	public static void addNavigationNodeProperty(NavigationDatabaseNode ndn, URI nodeUri) throws URISyntaxException {


		Map<String, Object> properties = ndn.createProperties();

		for (Entry<String, Object> map :properties.entrySet()){
			if (map.getKey()!= null && map.getValue() != null)
			{ addProperty(nodeUri, map.getKey(), map.getValue().toString());
			}

		}
		addLable(nodeUri);
	}

	private void addNavigationNodePropertyForm(NavigationDatabaseNode ndn, URI dest) throws URISyntaxException {
		Map<String, Object> properties = ndn.createPropertiesForms();

		for (Entry<String, Object> map :properties.entrySet()){
			if (map.getKey()!= null && map.getValue() != null)
			{ addProperty(dest, map.getKey(), map.getValue().toString());
			}

		}
		addLable(dest);
	}

	private static void addLable(URI nodeUri) throws URISyntaxException {

		// START SNIPPET: addlable
		URI Uri = new URI( nodeUri.toString() + "/labels");
		// e.g : http://localhost:7474/db/data/node/59/labels

		WebResource resource = Client.create()
				.resource( Uri );
		ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
				.type( MediaType.APPLICATION_JSON )
				.entity( "\"Dynamic\"" )
				.post( ClientResponse.class );

		System.out.println( String.format( "PUT to [%s], status code [%d]",
				Uri, response.getStatus() ) );
		response.close();
		// END SNIPPET: addlable

	}


	private static void addProperty( URI nodeUri, String propertyName,
			String propertyValue )
	{
		// START SNIPPET: addProp
		String propertyUri = nodeUri.toString() + "/properties/" + propertyName;
		// http://localhost:7474/db/data/node/{node_id}/properties/{property_name}

		WebResource resource = Client.create()
				.resource( propertyUri );
		ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
				.type( MediaType.APPLICATION_JSON )
				.entity( "\"" + propertyValue + "\"" )
				.put( ClientResponse.class );

		System.out.println( String.format( "PUT to [%s], status code [%d]",
				propertyUri, response.getStatus() ) );
		response.close();
		// END SNIPPET: addProp
	}




	public void addLinksStart() {
		logger.info("adding links to Neo4j graph database nodes  ");

		for (Entry<URI, NavigationDatabaseNode> map : this.navNodes.entrySet()){
			// System.out.println("nav map uri"+map.getKey()+"---"+map.getValue().getUrl());
			if (map.getValue().getOutLinks() != null ){
				for(WebURL wu :map.getValue().getOutLinks()){
					String u = wu.getURL();
					//System.out.println("Trying to link parent"+map.getValue().getUrl());
					//System.out.println("   with out going url " + u);
					NavigationDatabaseNode tmp = NavigationDatabaseNode.findNode(this.nodes, u);
					if (tmp != null){
						URI childNode = findURI((String)tmp.getUrl());

						if (childNode != null){
							try {
								addRelationship(map.getKey(), childNode, "HAS_A_LINK_TO" );
							} catch (URISyntaxException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}
						else 
							logger.debug("Could not link the two nodes because parent could not be found!!!!!!!");
					}
				}
			}
		}
	}	


	private URI findURI(String url) {
		for (Entry<URI, NavigationDatabaseNode> map : this.navNodes.entrySet()){
			//Abeer : casting to string is important, otherwise, the condition will never 
			//be satisfied
			if (((String)map.getValue().getUrl()).equals(url))
				return map.getKey();

		}
		return null;
	}



	// START SNIPPET: insideAddRel
	private  URI addRelationship( URI startNode, URI endNode,
			String relationshipType )
					throws URISyntaxException
	{
		URI fromUri = new URI( startNode.toString() + "/relationships" );
		String relationshipJson = generateJsonRelationship( endNode,
				relationshipType);

		WebResource resource = Client.create()
				.resource( fromUri );
		// POST JSON to the relationships URI
		ClientResponse response = resource.accept( MediaType.APPLICATION_JSON )
				.type( MediaType.APPLICATION_JSON )
				.entity( relationshipJson )
				.post( ClientResponse.class );

		final URI location = response.getLocation();
		System.out.println( String.format(
				"POST to [%s], status code [%d], location header [%s]",
				fromUri, response.getStatus(), location.toString() ) );

		response.close();
		return location;
	}
	// END SNIPPET: insideAddRel

	private  String generateJsonRelationship( URI endNode,
			String relationshipType )
	{
		StringBuilder sb = new StringBuilder();
		sb.append( "{ \"to\" : \"" );
		sb.append( endNode.toString() );
		sb.append( "\", " );

		sb.append( "\"type\" : \"" );
		sb.append( relationshipType );
		sb.append( "\"" );

		sb.append( " }" );
		return sb.toString();
	}




	private static void checkDatabaseIsRunning()
	{
		// START SNIPPET: checkServer
		WebResource resource = Client.create()
				.resource( SERVER_ROOT_URI );
		ClientResponse response = resource.get( ClientResponse.class );

		System.out.println( String.format( "GET on [%s], status code [%d]",
				SERVER_ROOT_URI, response.getStatus() ) );
		response.close();
		// END SNIPPET: checkServer
	}


}