@Singleton
@Path("/v1/commands")
public class CommandResource implements PaxAppender, TaskObserver {


	@Context
	@NotNull
	private AvailableCommands available;

	@Context
	@NotNull
	private CommandExecutorTaskFactory ceTaskFactory;
	
	@Context
	@NotNull
	private SynchronousTaskManager<?> taskManager;


	private CustomFailureException taskException;
	private MessageHandler messageHandler;
	private boolean processingCommand = false;



	/**
	 * Method handling HTTP GET requests to enumerate all namespaces. The
	 * returned list will be sent to the client as "text/plain" media type.
	 * 
	 * @return String that will be returned as a text/plain response.
	 */
	@GET
	@Path("/")
	@Produces(MediaType.TEXT_PLAIN)
	public String enumerateNamespaces() {
		final MessageHandler handler = new TextPlainHandler();
		final List<String> namespaces = available.getNamespaces();

		handler.appendCommand("Available namespaces:");
		for (final String namespace : namespaces) {
			handler.appendMessage("  " + namespace);
		}

		return handler.getMessages();
	}

	/**
	 * Method handling HTTP GET requests to enumerate all namespaces. The
	 * returned list will be sent to the client as "text/html" media type.
	 * 
	 * @return String that will be returned as a text/html response.
	 */
	@GET
	@Path("/")
	@Produces(MediaType.TEXT_HTML)
	public String enumerateNamespacesHtml() {
		final MessageHandler handler = new TextHTMLHandler();
		final List<String> namespaces = available.getNamespaces();

		handler.appendCommand("Available namespaces:");
		for (final String namespace : namespaces) {
			handler.appendMessage(namespace);
		}

		return handler.getMessages();
	}


	/**
	 * Method to enumerate all commands for a given namespace
	 * 
	 * @param namespace
	 * @return list of commands as text/plain
	 */
	@GET
	@Path("/{namespace}")
	@Produces(MediaType.TEXT_PLAIN)
	public String enumerateCommands(@PathParam("namespace") String namespace) {
		final MessageHandler handler = new TextPlainHandler();
		final List<String> commands = available.getCommands(namespace);

		if (commands == null || commands.size() == 0)
			throw new WebApplicationException(404);

		handler.appendCommand("Available commands for '" + namespace + "':");
		for (final String command : commands) {
			handler.appendMessage("  " + command);
		}
		return handler.getMessages();
	}


	/**
	 * Method to enumerate all commands for a given namespace
	 * 
	 * @param namespace
	 * @return list of commands as text/html
	 */
	@GET
	@Path("/{namespace}")
	@Produces(MediaType.TEXT_HTML)
	public String enumerateHTMLCommands(@PathParam("namespace") String namespace) {
		final MessageHandler handler = new TextHTMLHandler();
		final List<String> commands = available.getCommands(namespace);

		if (commands == null || commands.size() == 0)
			throw new WebApplicationException(404);

		handler.appendCommand("Available commands for '" + namespace + "':");
		for (final String command : commands) {
			handler.appendMessage("  " + command);
		}
		
		return handler.getMessages();
	}


	/**
	 * Method to enumerate all arguments for a given namespace and command or
	 * execute a namespace and command if query strings are provided
	 * 
	 * @param namespace
	 * @param command
	 * @param uriInfo
	 *            this provides access to the query strings
	 * @return list of arguments as text/plain or the results of executing the
	 *         command
	 */
	@GET
	@Path("/{namespace}/{command}")
	@Produces(MediaType.TEXT_PLAIN)
	public String handleCommand(@PathParam("namespace") String namespace,
			@PathParam("command") String command, @Context UriInfo uriInfo) {
		
		final MessageHandler handler = new TextPlainHandler();
		final MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters(true);
		
		return handleCommand(namespace, command, queryParameters, handler);
	}


	/**
	 * Method to enumerate all arguments for a given namespace and command or
	 * execute a namespace and command if query strings are provided
	 * 
	 * @param namespace
	 * @param command
	 * @param uriInfo
	 *            this provides access to the query strings
	 * @return list of arguments as text/html or the results of executing the
	 *         command
	 */
	@GET
	@Path("/{namespace}/{command}")
	@Produces(MediaType.TEXT_HTML)
	public String handleHTMLCommand(@PathParam("namespace") String namespace,
			@PathParam("command") String command, @Context UriInfo uriInfo) {
		final MessageHandler handler = new TextHTMLHandler();
		final MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters(true);
		
		return handleCommand(namespace, command, queryParameters, handler);
	}


	private final String handleCommand(final String namespace, final String command,
			final MultivaluedMap<String, String> queryParameters,
			final MessageHandler handler) throws WebApplicationException {
		
		final List<String> args = available.getArguments(namespace, command);

		if ((queryParameters != null && queryParameters.size() > 0)
				|| (args == null || args.size() == 0)) {
			// Execute!
			return executeCommand(namespace, command, queryParameters, handler);
		}

		handler.appendCommand("Available arguments for '" + namespace + " " + command + "':");
		for (final String arg : args) {
			handler.appendMessage("  " + arg);
		}
		return handler.getMessages();
	}


	private final String executeCommand(
			final String namespace, final String command,
			final MultivaluedMap<String, String> args,
			final MessageHandler handler) throws WebApplicationException {

		final List<String> commands = available.getCommands(namespace);
		if (commands == null || commands.size() == 0) {
			throw new CustomNotFoundException("Error: no such namespace: '" + namespace + "'");
		}

		boolean nocom = true;
		for (String com : commands) {
			if (com.equalsIgnoreCase(command)) {
				nocom = false;
				break;
			}
		}
		if (nocom) {
			throw new CustomNotFoundException("Error: no such command: '" + command + "'");
		}

		List<String> argList = available.getArguments(namespace, command);
		Map<String, Object> modifiedSettings = new HashMap<String, Object>();

		for (String inputArg : args.keySet()) {
			boolean found = false;
			for (String arg : argList) {
				String[] bareArg = arg.split("=");
				if (bareArg[0].equalsIgnoreCase(inputArg)) {
					found = true;
					modifiedSettings.put(bareArg[0],
							stripQuotes(args.getFirst(inputArg)));
					break;
				}
			}
			if (!found) {
				throw new CustomNotFoundException(
						"Error: can't find argument '" + inputArg + "'");
			}
		}

		processingCommand = true;
		messageHandler = handler;
		taskException = null;

		taskManager.execute(ceTaskFactory.createTaskIterator(namespace,
				command, modifiedSettings, this), this);

		String messages = messageHandler.getMessages();
		processingCommand = false;
		if (taskException != null)
			throw taskException;
		return messages;
	}

	public void doAppend(PaxLoggingEvent event) {
		System.out
				.println(event.getLevel().toInt() + ": " + event.getMessage());
		// Get prefix
		// Handle levels
		if (!processingCommand) {
			return;
		}

		PaxLevel level = event.getLevel();
		if (level.toInt() == 40000)
			messageHandler.appendError(event.getMessage());
		else if (level.toInt() == 30000)
			messageHandler.appendWarning(event.getMessage());
		else
			messageHandler.appendMessage(event.getMessage());
	}

	////////////////// For Observable Task //////////////////////////
	
	@Override
	public void taskFinished(ObservableTask t) {
		final Object res = t.getResults(String.class);
		if (res != null)
			messageHandler.appendResult(res);
	}


	@Override
	public void allFinished(FinishStatus status) {
		if (status.getType().equals(FinishStatus.Type.SUCCEEDED))
			messageHandler.appendMessage("Finished");
		else if (status.getType().equals(FinishStatus.Type.CANCELLED))
			messageHandler.appendWarning("Cancelled by user");
		else if (status.getType().equals(FinishStatus.Type.FAILED)) {
			if (status.getException() != null) {
				messageHandler.appendError("Failed: "
						+ status.getException().getMessage());
				taskException = new CustomFailureException("Failed: "
						+ status.getException().getMessage());
			} else {
				messageHandler.appendError("Failed");
				taskException = new CustomFailureException();
			}
		}
	}

	private final String stripQuotes(final String quotedString) {
		String tqString = quotedString.trim();
		if (tqString.startsWith("\"") && tqString.endsWith("\""))
			return tqString.substring(1, tqString.length() - 1);
		return tqString;
	}

	public class CustomNotFoundException extends WebApplicationException {
		public CustomNotFoundException() {
			super(404);
		}

		public CustomNotFoundException(String message) {
			super(Response.status(Response.Status.NOT_FOUND).entity(message)
					.type("text/plain").build());
		}
	}

	public class CustomFailureException extends WebApplicationException {
		public CustomFailureException() {
			super(500);
		}

		public CustomFailureException(String message) {
			super(Response.status(Response.Status.INTERNAL_SERVER_ERROR)
					.entity(message).type("text/plain").build());
		}
	}
} @Singleton
@Path("/v1/apply")
public class AlgorithmicResource extends AbstractResource {

	@Context
	@NotNull
	private TaskMonitor headlessTaskMonitor;

	@Context
	@NotNull
	private CyLayoutAlgorithmManager layoutManager;

	@Context
	@NotNull
	private NetworkTaskFactory fitContent;

	@Context
	@NotNull
	private EdgeBundler edgeBundler;

	/**
	 * 
	 * @summary Apply layout to a network
	 * 
	 * @param algorithmName
	 *            Name of layout algorithm ("circular", "force-directed", etc.)
	 * @param networkId
	 *            Target network SUID
	 * @param column (URL Query Parameter) 
	 * 				Column name to be used by the layout algorithm
	 * 
	 * @return Success message
	 */
	@GET
	@Path("/layouts/{algorithmName}/{networkId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response applyLayout(
			@PathParam("algorithmName") String algorithmName,
			@PathParam("networkId") Long networkId,
			@QueryParam("column") String column) {
		final CyNetwork network = getCyNetwork(networkId);
		final Collection<CyNetworkView> views = 
				this.networkViewManager.getNetworkViews(network);
		if (views.isEmpty()) {
			throw new NotFoundException(
					"Could not find view for the network with SUID: " 
					+ networkId);
		}

		final CyNetworkView view = views.iterator().next();
		final CyLayoutAlgorithm layout = this.layoutManager.getLayout(algorithmName);
		if (layout == null) {
			throw new NotFoundException("No such layout algorithm: " + algorithmName);
		}

		String columnForLayout = column;
		if(columnForLayout == null) {
			columnForLayout = "";
		}
		
		final TaskIterator itr = layout.createTaskIterator(view,
				layout.getDefaultLayoutContext(),
				CyLayoutAlgorithm.ALL_NODE_VIEWS, columnForLayout);
		try {
			itr.next().run(headlessTaskMonitor);
		} catch (Exception e) {
			throw getError("Could not apply layout.", e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		final Map<String, String> successMessage = new HashMap<String, String>();
		successMessage.put("message", "Layout finished.");
		return Response.status(Response.Status.OK)
				.entity(successMessage)
				.type(MediaType.APPLICATION_JSON).build();
	}
	
	
	/**
	 * The return value is an map of all layout details, 
	 * and each of parameter entry includes:
	 * 
	 * <ul>
	 * 		<li>name: Unique name (ID) of the parameter</li>
	 * 		<li>description: Description for the parameter</li>
	 * 		<li>type: Java data type of the parameter</li>
	 * 		<li>value: current value for the parameter field</li>
	 * </ul>
	 * 
	 * @summary Get layout parameters for the algorithm
	 * 
	 * @param algorithmName Name of the layout algorithm
	 * 
	 * @return Editable layout parameters
	 */
	@GET
	@Path("/layouts/{algorithmName}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getLayout(@PathParam("algorithmName") String algorithmName) {
		final CyLayoutAlgorithm layout = this.layoutManager.getLayout(algorithmName);
		if (layout == null) {
			throw new NotFoundException("No such layout algorithm: " + algorithmName);
		}

		Map<String, Object> params;
		try {
			params = getLayoutDetails(layout);
		} catch (Exception e) {
			throw getError("Could not get layout parameters.", e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}
		return Response.status(Response.Status.OK)
				.entity(params)
				.type(MediaType.APPLICATION_JSON).build();
	}
	

	/**
	 * @summary Returns layout parameter list
	 * 
	 * @param algorithmName Name of layout algorithm
	 * 
	 * @return All editable parameters for this algorithm.
	 */
	@GET
	@Path("/layouts/{algorithmName}/parameters")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getLayoutParameters(@PathParam("algorithmName") String algorithmName) {
		final CyLayoutAlgorithm layout = this.layoutManager.getLayout(algorithmName);
		if (layout == null) {
			throw new NotFoundException("No such layout algorithm: " + algorithmName);
		}

		final List<Map<String, Object>> params;
		try {
			params = getLayoutParameterDetails(layout);
		} catch (Exception e) {
			throw getError("Could not get layout parameters.", e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		return Response.status(Response.Status.OK)
				.entity(params)
				.type(MediaType.APPLICATION_JSON).build();
	}
	
	/**
	 * @summary Column data types compatible with this algorithm
	 * 
	 * @param algorithmName Name of layout algorithm
	 * 
	 * @return List of all compatible column data types
	 */
	@GET
	@Path("/layouts/{algorithmName}/columntypes")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getCompatibleColumnDataTypes(
			@PathParam("algorithmName") String algorithmName) {

		final CyLayoutAlgorithm layout = this.layoutManager.getLayout(algorithmName);
		if (layout == null) {
			throw new NotFoundException("No such layout algorithm: " + algorithmName);
		}
		final Map<String, Set<String>> result = getCompatibleTypes(layout);
		return Response.status(Response.Status.OK)
				.entity(result)
				.type(MediaType.APPLICATION_JSON).build();
	}
	
	private Map<String, Set<String>> getCompatibleTypes(final CyLayoutAlgorithm layout) {
		final Map<String, Set<String>> result = new HashMap<>();
		Set<Class<?>> compatibleTypes = Collections.emptySet();
		if (layout.getSupportedNodeAttributeTypes().isEmpty() == false) {
			compatibleTypes = layout.getSupportedNodeAttributeTypes();
			final Set<String> nodeSet = compatibleTypes.stream().map(type -> type.getSimpleName())
					.collect(Collectors.toSet());
			result.put("compatibleNodeColumnDataTypes", nodeSet);
		}

		if (layout.getSupportedEdgeAttributeTypes().isEmpty() == false) {
			compatibleTypes = layout.getSupportedEdgeAttributeTypes();
			final Set<String> edgeSet = compatibleTypes.stream().map(type -> type.getSimpleName())
					.collect(Collectors.toSet());
			result.put("compatibleEdgeColumnDataTypes", edgeSet);
		}
		return result;
	}
	
	
	/**
	 * The body of your request should contain an array of new parameters.
	 * The data should look like the following:
	 * <br/>
	 * 
	 * [
	 * 		{
	 * 			"name": nodeHorizontalSpacing,
	 * 			"value": 40.0
	 * 		}, ...
	 * ]
	 * 
	 * where:
	 * <ul>
	 * 		<li>name: Unique name (ID) of the parameter</li>
	 * 		<li>value: New value for the parameter field</li>
	 * </ul>
	 * 
	 * @summary Update layout parameters for the algorithm
	 * 
	 * @param algorithmName Name of the layout algorithm
	 * 
	 * @return Response code 200 if success
	 */
	@PUT
	@Path("/layouts/{algorithmName}/parameters")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateLayoutParameters(@PathParam("algorithmName") String algorithmName, final InputStream is) {
		final ObjectMapper objMapper = new ObjectMapper();
		final CyLayoutAlgorithm layout = this.layoutManager.getLayout(algorithmName);
		final Object context = layout.getDefaultLayoutContext();
		
		try {
			final Map<String, Class<?>> params = getParameterTypes(layout);
			// This should be an JSON array.
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
			for (final JsonNode entry : rootNode) {
				final String parameterName = entry.get("name").asText();
				final Class<?> type = params.get(parameterName);
				
				if(type == null) {
					throw new NotFoundException("No such parameter: " + parameterName);
				}
				
				final JsonNode val = entry.get("value");
				final Object value = MapperUtil.getValue(val, params.get(parameterName));
				context.getClass().getField(parameterName).set(context, value);
			}
			
		} catch (Exception e) {
			throw getError(
					"Could not parse the input JSON for updating view because: "
							+ e.getMessage(), e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		return Response.status(Response.Status.OK)
				.type(MediaType.APPLICATION_JSON).build();
	}
	
	
	private final Map<String, Class<?>> getParameterTypes(final CyLayoutAlgorithm layout) {
		final Object context = layout.getDefaultLayoutContext();
		final List<Field> fields = Arrays.asList(context.getClass().getFields());
		return fields.stream()
			.filter(field -> field.getAnnotation(Tunable.class) != null)
			.collect(Collectors.toMap(Field::getName, Field::getType));
	}
	
	private final Map<String, Object> getLayoutDetails(final CyLayoutAlgorithm layout) throws NoSuchFieldException, SecurityException {
		final Map<String, Object> layoutInfo = new HashMap<String, Object>();
		layoutInfo.put("name", layout.getName());
		layoutInfo.put("longName", layout.toString());
		layoutInfo.put("parameters", getLayoutParameterDetails(layout));
		layoutInfo.put("compatibleColumnDataTypes", getCompatibleTypes(layout));
		return layoutInfo;
	}
	
	private final List<Map<String, Object>> getLayoutParameterDetails(final CyLayoutAlgorithm layout) {
		return Arrays.asList(
				layout.getDefaultLayoutContext().getClass().getFields()
			).stream()
				.filter(field -> field.getAnnotation(Tunable.class) != null)
				.map(field -> buildLayoutParamEntry(layout, field))
				.collect(Collectors.toList());
		
	}
	
	private final Map<String, Object> buildLayoutParamEntry(final CyLayoutAlgorithm layout, final Field field) {
		final Map<String, Object> entryMap = new HashMap<>();
		final Tunable tunable = field.getAnnotation(Tunable.class);
		
		entryMap.put("name", field.getName());
		entryMap.put("description", tunable.description());
		entryMap.put("type", field.getType().getSimpleName());
		try {
			entryMap.put("value", field.get(layout.getDefaultLayoutContext()));
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return entryMap;
	}

	/**
	 * 
	 * @summary Apply Visual Style to a network
	 * 
	 * @param styleName
	 *            Visual Style name (title)
	 * @param networkId
	 *            Target network SUID
	 * 
	 * @return Success message.
	 */
	@GET
	@Path("/styles/{styleName}/{networkId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response applyStyle(@PathParam("styleName") String styleName,
			@PathParam("networkId") Long networkId) {

		final CyNetwork network = getCyNetwork(networkId);
		final Set<VisualStyle> styles = vmm.getAllVisualStyles();
		VisualStyle targetStyle = null;
		for (final VisualStyle style : styles) {
			final String name = style.getTitle();
			if (name.equals(styleName)) {
				targetStyle = style;
				break;
			}
		}

		if (targetStyle == null) {
			throw new NotFoundException("Visual Style does not exist: "
					+ styleName);
		}

		Collection<CyNetworkView> views = this.networkViewManager
				.getNetworkViews(network);
		if (views.isEmpty()) {
			throw new NotFoundException(
					"Network view does not exist for the network with SUID: "
							+ networkId);
		}

		final CyNetworkView view = views.iterator().next();
		vmm.setVisualStyle(targetStyle, view);
		vmm.setCurrentVisualStyle(targetStyle);
		targetStyle.apply(view);

		return Response.status(Response.Status.OK)
				.type(MediaType.APPLICATION_JSON)
				.entity("{\"message\":\"Visual Style applied.\"}").build();
	}

	/**
	 * 
	 * Fit an existing network view to current window.
	 * 
	 * @summary Fit network to the window
	 * 
	 * @param networkId
	 *            Network SUID
	 * @return Success message
	 */
	@GET
	@Path("/fit/{networkId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response fitContent(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);

		Collection<CyNetworkView> views = this.networkViewManager
				.getNetworkViews(network);
		if (views.isEmpty()) {
			throw new NotFoundException(
					"Network view does not exist for the network with SUID: "
							+ networkId);
		}
		TaskIterator fit = fitContent.createTaskIterator(network);
		try {
			fit.next().run(headlessTaskMonitor);
		} catch (Exception e) {
			throw getError("Could not fit content.", e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}

		return Response.status(Response.Status.OK)
				.type(MediaType.APPLICATION_JSON)
				.entity("{\"message\":\"Fit content success.\"}").build();
	}

	/**
	 * Apply edge bundling with default parameters. Currently optional
	 * parameters are not supported.
	 * 
	 * @summary Apply Edge Bundling to a network
	 * 
	 * @param networkId
	 *            Target network SUID
	 * @return Success message
	 * 
	 */
	@GET
	@Path("/edgebundling/{networkId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response bundleEdge(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);

		Collection<CyNetworkView> views = this.networkViewManager
				.getNetworkViews(network);
		if (views.isEmpty()) {
			throw new NotFoundException(
					"Network view does not exist for the network with SUID: "
							+ networkId);
		}
		final TaskIterator bundler = edgeBundler.getBundlerTF()
				.createTaskIterator(network);
		try {
			bundler.next().run(headlessTaskMonitor);
		} catch (Exception e) {
			throw getError("Could not finish edge bundling.", e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}

		return Response.status(Response.Status.OK)
				.type(MediaType.APPLICATION_JSON)
				.entity("{\"message\":\"Edge bundling success.\"}").build();
	}

	/**
	 * List of all available layout algorithm names.
	 * 
	 * <h3>Important Note</h3>
	 * This <strong>does not include yFiles layout algorithms</strong> 
	 * due to license issues.
	 *
	 * @summary Get list of available layout algorithm names
	 *
	 * @return List of layout algorithm names.
	 */
	@GET
	@Path("/layouts")
	@Produces(MediaType.APPLICATION_JSON)
	public Collection<String> getLayoutNames() {
		return layoutManager.getAllLayouts().stream()
			.map(layout->layout.getName())
			.collect(Collectors.toList());
	}


	/**
	 * Get list of all Visual Style names. 
	 * Style names may not be unique.
	 * 
	 * @summary Get list of all Visual Style names
	 * 
	 * @return List of Visual Style names.
	 */
	@GET
	@Path("/styles")
	@Produces(MediaType.APPLICATION_JSON)
	public Collection<String> getStyleNames() {
		return vmm.getAllVisualStyles().stream()
				.map(style->style.getTitle())
				.collect(Collectors.toList());
	}
} @Singleton
@Path("/v1/tables")
public class GlobalTableResource extends AbstractResource {

	@Context
	@NotNull
	private CyTableFactory tableFactory;

	private final ObjectMapper tableObjectMapper;

	public GlobalTableResource() {
		super();
		this.tableObjectMapper = new ObjectMapper();
		this.tableObjectMapper.registerModule(new TableModule());
	}

	/**
	 * 
	 * @summary Get number of global tables
	 * 
	 * @return Number of global tables.
	 */
	@GET
	@Path("/count")
	@Produces(MediaType.APPLICATION_JSON)
	public String getTableCount() {
		final Set<CyTable> globals = tableManager.getGlobalTables();
		return getNumberObjectString(JsonTags.COUNT, globals.size());
	}
} @Singleton
@Path("/v1/networks/{networkId}/groups")
public class GroupResource extends AbstractResource {

	private final ObjectMapper groupMapper;
	private final GroupMapper mapper;

	@Context
	private CyGroupFactory groupFactory;

	@Context
	private CyGroupManager groupManager;

	public GroupResource() {
		super();
		this.groupMapper = new ObjectMapper();
		this.groupMapper.registerModule(new GroupModule());
		this.mapper = new GroupMapper();
	}

	/**
	 * @summary Get all groups in the network
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return List of all groups in the network
	 * 
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public String getAllGroups(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		final Set<CyGroup> groups = groupManager.getGroupSet(network);
		try {
			return this.groupMapper.writeValueAsString(groups);
		} catch (JsonProcessingException e) {
			throw getError("Could not serialize groups.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	/**
	 * 
	 * @summary Get number of groups in the network
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return Number of groups in the network
	 */
	@GET
	@Path("/count")
	@Produces(MediaType.APPLICATION_JSON)
	public String getGroupCount(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		return getNumberObjectString(JsonTags.COUNT, groupManager.getGroupSet(network).size());
	}

	/**
	 * @summary Get group for a node
	 * 
	 * @param networkId
	 *            Networks SUID
	 * @param groupNodeId
	 *            Group Node SUID
	 * 
	 * @return A group where the node belongs to
	 */
	@GET
	@Path("/{groupNodeId}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getGroup(@PathParam("networkId") Long networkId, @PathParam("groupNodeId") Long nodeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyNode node = network.getNode(nodeId);
		if (node == null) {
			throw new NotFoundException("Could not find the node with SUID: " + nodeId);
		}

		final CyGroup group = groupManager.getGroup(node, network);
		if (group == null) {
			throw new NotFoundException("Could not find group.");
		}
		try {
			return groupMapper.writeValueAsString(group);
		} catch (JsonProcessingException e) {
			throw getError("Could not serialize Group.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * @summary Delete all groups in the network
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 */
	@DELETE
	@Path("/")
	public void deleteAllGroups(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		final Set<CyGroup> groups = groupManager.getGroupSet(network);
		try {
			for (final CyGroup group : groups) {
				groupManager.destroyGroup(group);
			}
		} catch (Exception e) {
			throw getError("Could not delete group.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * 
	 * @summary Delete a group
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param groupNodeId
	 *            Group node SUID
	 */
	@DELETE
	@Path("/{groupNodeId}")
	public void deleteGroup(@PathParam("networkId") Long networkId, @PathParam("groupNodeId") Long groupNodeId) {
		final CyGroup group = getGroupById(networkId, groupNodeId);
		try {
			groupManager.destroyGroup(group);
		} catch (Exception e) {
			throw getError("Could not delete group.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * @summary Expand group nodes
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param groupNodeId
	 *            Group node SUID
	 * 
	 */
	@GET
	@Path("/{groupNodeId}/expand")
	public void expandGroup(@PathParam("networkId") Long networkId, @PathParam("groupNodeId") Long groupNodeId) {
		toggle(networkId, groupNodeId, false);
	}

	/**
	 * @summary Collapse group nodes
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param groupNodeId
	 *            Group node SUID
	 * 
	 */
	@GET
	@Path("/{groupNodeId}/collapse")
	public void collapseGroup(@PathParam("networkId") Long networkId, @PathParam("groupNodeId") Long groupNodeId) {
		toggle(networkId, groupNodeId, true);
	}

	private final void toggle(final Long networkId, final Long suid, boolean collapse) {
		final CyGroup group = getGroupById(networkId, suid);
		final CyNetwork network = getCyNetwork(networkId);
		try {
			if (collapse) {
				group.collapse(network);
			} else {
				group.expand(network);
			}
		} catch (Exception e) {
			throw getError("Could not toggle group state. Collapse: " + collapse, e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	private final CyGroup getGroupById(final Long networkId, final Long suid) {
		final CyNetwork network = getCyNetwork(networkId);
		CyNode node = network.getNode(suid);
		if (node == null) {
			node = ((CySubNetwork) network).getRootNetwork().getNode(suid);
			if (node == null)
				throw new NotFoundException("Could not find the node with SUID: " + suid);
		}

		final CyGroup group = groupManager.getGroup(node, network);
		if (group == null) {
			throw new NotFoundException("Could not find group.");
		}
		return group;
	}

	/**
	 * Create a new group from a list of nodes.  The Body should be in the following format:
	 * 
	 * <pre>
	 * 	{
	 * 		"name": (New group node name),
	 * 		"nodes": [
	 * 			nodeSUID1, nodeSUID2, ...
	 * 		]
	 * 	}
	 * </pre>
	 * 
	 * 
	 * @summary Create a new group
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return New group node's SUID
	 * 
	 */
	@POST
	@Path("/")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public String createGroup(@PathParam("networkId") Long networkId, final InputStream is) {
		final CyNetwork network = getCyNetwork(networkId);
		final ObjectMapper objMapper = new ObjectMapper();

		JsonNode rootNode = null;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
		} catch (IOException ex) {
			throw getError("Could not create JSON root node.", ex, Response.Status.INTERNAL_SERVER_ERROR);
		}
		try {
			final CyGroup newGroup = mapper.createGroup(rootNode, groupFactory, network);
			return getNumberObjectString("groupSUID", newGroup.getGroupNode().getSUID());
		} catch (Exception e) {
			throw getError("Could not create group.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}
} @Singleton
@Path("/v1")
public class MiscResource extends AbstractResource {

	/**
	 * @summary Cytoscape RESTful API server status
	 * 
	 * @return Summary of server status
	 * 
	 * @statuscode 500 If REST API Module is not working.
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public ServerStatus getStatus() {
		return new ServerStatus();
	}


	/**
	 * Run System.gc().  In general, this is not necessary.
	 * 
	 * @summary Force to run garbage collection to free up memory
	 */
	@GET
	@Path("/gc")
	@Produces(MediaType.APPLICATION_JSON)
	public void runGarbageCollection() {
		Runtime.getRuntime().gc();
	}
	
	
	/**
	 * 
	 * @summary Get Cytoscape and REST API version
	 * 
	 * @return Cytoscape version and REST API version
	 * 
	 */
	@GET
	@Path("/version")
	@Produces(MediaType.APPLICATION_JSON)
	public CytoscapeVersion getCytoscapeVersion() {

		if (props == null) {
			throw new InternalServerErrorException("Could not find CyProperty object.");
		}

		final Properties property = (Properties) this.props.getProperties();
		final Object versionNumber = property.get("cytoscape.version.number");
		if (versionNumber != null) {
			return new CytoscapeVersion(API_VERSION, versionNumber.toString());
		} else {
			throw new NotFoundException("Could not find Cytoscape version number property.");
		}
	}
	
	
	@PUT
	@Path("/ui/show-details")
	@Produces(MediaType.APPLICATION_JSON)
	public CytoscapeVersion updateShowGraphicsDetailsOption() {

		if (props == null) {
			throw new InternalServerErrorException("Could not find CyProperty object.");
		}

		final Properties property = (Properties) this.props.getProperties();
		final Object versionNumber = property.get("cytoscape.version.number");
		if (versionNumber != null) {
			return new CytoscapeVersion(API_VERSION, versionNumber.toString());
		} else {
			throw new NotFoundException("Could not find Cytoscape version number property.");
		}
	}
} @Singleton
@Path("/v1/networks.json")
public class NetworkFullResource extends AbstractResource {

	public NetworkFullResource() {
		super();
	}
	
	/**
	 * 
	 * Returns full list of network data as a JSON array.
	 * JSON is in <a href="http://cytoscape.github.io/cytoscape.js/">Cytoscape.js</a> 
	 * format.  If no query parameter is given, returns all networks in current session.
	 * 
	 * @summary Get networks in Cytoscape.js JSON format
	 * 
	 * @param column Optional.  Network table column name to be used for search.
	 * @param query Optional.  Search query.
	 * 
	 * @return Matched networks in Cytoscape.js JSON.  If no query is given, all networks.
	 * 
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8")
	@ReturnType("java.util.List<org.cytoscape.rest.internal.model.CyJsNetwork>")
	public String getNetworks(@QueryParam("column") String column, @QueryParam("query") String query) {
		Set<CyNetwork> networks;
		
		if (column == null && query == null) {
			networks = networkManager.getNetworkSet();
		} else {
			if(column == null || column.length() == 0) {
				throw getError("Column name parameter is missing.", new IllegalArgumentException(), Response.Status.INTERNAL_SERVER_ERROR);
			}
			if(query == null || query.length() == 0) {
				throw getError("Query parameter is missing.", new IllegalArgumentException(), Response.Status.INTERNAL_SERVER_ERROR);
			}
			networks = getNetworksByQuery(query, column);
		}
		
		return getNetworksAsString(networks);
	}


	private final String getNetworksAsString(final Set<CyNetwork> networks) {
		if (networks.isEmpty()) {
			return "[]";
		}

		final StringBuilder result = new StringBuilder();
		result.append("[");

		for (final CyNetwork network : networks) {
			result.append(getNetworkString(network));
			result.append(",");
		}
		String jsonString = result.toString();
		jsonString = jsonString.substring(0, jsonString.length() - 1);

		return jsonString + "]";
	}
} @Singleton
@Path("/v1/networks")
public class NetworkResource extends AbstractResource {
	
	@Context
	protected SelectFirstNeighborsTaskFactory selectFirstNeighborsTaskFactory;

	// Preset types
	private static final String DEF_COLLECTION_PREFIX = "Created by cyREST: ";

	public NetworkResource() {
		super();
	}

	/**
	 * 
	 * @summary Get number of networks in current session
	 * 
	 * @return Number of networks in current Cytoscape session
	 * 
	 */
	@GET
	@Path("/count")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getNetworkCount() {
		final String result = getNumberObjectString(JsonTags.COUNT, networkManager.getNetworkSet().size());
		return Response.ok(result).build();
	}

	/**
	 * 
	 * @summary Get number of nodes in the network
	 * 
	 * @param id
	 *            Network SUID
	 * @return Number of nodes in the network with given SUID
	 */
	@GET
	@Path("/{networkId}/nodes/count")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getNodeCount(@PathParam("networkId") Long networkId) {
		final String result = getNumberObjectString(JsonTags.COUNT, getCyNetwork(networkId).getNodeCount());
		return Response.ok(result).build();
	}

	/**
	 * 
	 * @summary Get number of edges in the network
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return number of edges in the network
	 */
	@GET
	@Path("/{networkId}/edges/count")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getEdgeCount(@PathParam("networkId") Long networkId) {
		final String result = getNumberObjectString(JsonTags.COUNT, getCyNetwork(networkId).getEdgeCount());
		return Response.ok(result).build();
	}

	private final Collection<Long> getByQuery(final Long id, final String objType, final String column,
			final String query) {
		final CyNetwork network = getCyNetwork(id);
		CyTable table = null;
		
		List<? extends CyIdentifiable> graphObjects;
		if (objType.equals("nodes")) {
			table = network.getDefaultNodeTable();
			graphObjects = network.getNodeList();
		} else if (objType.equals("edges")) {
			table = network.getDefaultEdgeTable();
			graphObjects = network.getEdgeList();
		} else {
			throw getError("Invalid graph object type: " + objType, new IllegalArgumentException(),
					Response.Status.INTERNAL_SERVER_ERROR);
		}

		if (query == null && column == null) {
			// Simply return rows
			return graphObjects.stream()
					.map(obj->obj.getSUID())
					.collect(Collectors.toList());
		} else if (query == null || column == null) {
			throw getError("Missing query parameter.", new IllegalArgumentException(),
					Response.Status.INTERNAL_SERVER_ERROR);
		} else {
			Object rawQuery = MapperUtil.getRawValue(query, table.getColumn(column).getType());
			final Collection<CyRow> rows = table.getMatchingRows(column, rawQuery);
			final Set<Long> selectedSuid = rows.stream()
				.map(row->row.get(CyIdentifiable.SUID, Long.class))
				.collect(Collectors.toSet());
			
			final Set<Long> allSuid = graphObjects.stream()
					.map(obj->obj.getSUID())
					.collect(Collectors.toSet());
			// Return intersection
			allSuid.retainAll(selectedSuid);
			return allSuid;
		}

	}


	/**
	 * 
	 * Returns list of networks as an array of network SUID.
	 * 
	 * @summary Get SUID list of networks
	 * 
	 * @param column Optional.  Network table column name to be used for search.
	 * @param query Optional.  Search query.
	 * 
	 * @return Matched networks as list of SUIDs.  If no query is given, returns all network SUIDs.
	 * 
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8")
	public Collection<Long> getNetworksAsSUID(@QueryParam("column") String column, @QueryParam("query") String query) {
		Collection<CyNetwork> networks = new HashSet<>();
		
		if (column == null && query == null) {
			networks = networkManager.getNetworkSet();
		} else {
			if(column == null || column.length() == 0) {
				throw getError("Column name parameter is missing.", new IllegalArgumentException(), Response.Status.INTERNAL_SERVER_ERROR);
			}
			if(query == null || query.length() == 0) {
				throw getError("Query parameter is missing.", new IllegalArgumentException(), Response.Status.INTERNAL_SERVER_ERROR);
			}

			try {
				networks = getNetworksByQuery(query, column);
			} catch(Exception e) {
				throw getError("Could not get networks.", e, Response.Status.INTERNAL_SERVER_ERROR);
			}
		}
		
		final Collection<Long> suids = new HashSet<Long>();
		for(final CyNetwork network: networks) {
			suids.add(network.getSUID());
		}
		
		return suids;
	}


	/**
	 * 
	 * @summary Get a network in Cytoscape.js format
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return Network with all associated tables in Cytoscape.js format.
	 * 
	 */
	@GET
	@Path("/{networkId}")
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	@ReturnType("org.cytoscape.rest.internal.model.CyJsNetwork")
	public String getNetwork(@PathParam("networkId") Long networkId) {
		return getNetworkString(getCyNetwork(networkId));
	}

	/**
	 * @summary Get matching nodes
	 * 
	 * @param networkId Network SUID
	 * @param column Optional.  Node table column name to be used for search.
	 * @param query Optional.  Search query.
	 * 
	 * @return List of matched node SUIDs.  If no parameter is given, returns all node SUIDs.
	 */
	@GET
	@Path("/{networkId}/nodes")
	@Produces(MediaType.APPLICATION_JSON)
	public Collection<Long> getNodes(@PathParam("networkId") Long networkId, @QueryParam("column") String column,
			@QueryParam("query") String query) {
		return getByQuery(networkId, "nodes", column, query);
	}


	/**
	 * @summary Utility to get selected nodes as SUID list
	 * 
	 * @param networkId Network SUID
	 * 
	 * @return Selected nodes as a list of SUID
	 */
	@GET
	@Path("/{networkId}/nodes/selected")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getSelectedNodes(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		final List<CyNode> selectedNodes = CyTableUtil.getNodesInState(network, CyNetwork.SELECTED, true);
		final List<Long> selectedNodeIds = selectedNodes.stream()
			.map(node -> node.getSUID())
			.collect(Collectors.toList());
		
		return Response.ok(selectedNodeIds).build();
	}
	
	
	/**
	 * 
	 * The return value does not includes originally selected nodes.
	 * 
	 * @summary Utility to get all neighbors of selected nodes
	 * 
	 * @param networkId Network SUID
	 * 
	 * @return Neighbors as list.  Note that this does not includes original nodes.
	 */
	@GET
	@Path("/{networkId}/nodes/selected/neighbors")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getNeighborsSelected(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		final List<CyNode> selectedNodes = CyTableUtil.getNodesInState(network, CyNetwork.SELECTED, true);
	
		final Set<Long> res = selectedNodes.stream()
			.map(node -> network.getNeighborList(node, Type.ANY))
			.flatMap(List::stream)
			.map(neighbor -> neighbor.getSUID())
			.collect(Collectors.toSet());
		
		return Response.ok(res).build();
	}


	/**
	 * @summary Utility to get all selected edges
	 * 
	 * @param networkId Network SUID
	 * 
	 * @return Selected edges as a list of SUID
	 */
	@GET
	@Path("/{networkId}/edges/selected")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getSelectedEdges(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		final List<CyEdge> selectedEdges = CyTableUtil.getEdgesInState(network, CyNetwork.SELECTED, true);
		final List<Long> selectedEdgeIds = selectedEdges.stream()
			.map(edge -> edge.getSUID())
			.collect(Collectors.toList());
		
		return Response.ok(selectedEdgeIds).build();
	}

	/**
	 * @summary Get matching edges
	 * 
	 * @param networkId Network SUID
	 * @param column Optional.  Edge table column name to be used for search.
	 * @param query Optional.  Search query.
	 * 
	 * @return List of matched edge SUIDs.  If no parameter is given, returns all edge SUIDs.
	 */
	@GET
	@Path("/{networkId}/edges")
	@Produces(MediaType.APPLICATION_JSON)
	public Collection<Long> getEdges(@PathParam("networkId") Long networkId, @QueryParam("column") String column,
			@QueryParam("query") String query) {
		return getByQuery(networkId, "edges", column, query);
	}

	/**
	 * 
	 * @summary Get a node
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param nodeId
	 *            Node SUID
	 * 
	 * @return Node with associated row data.
	 * 
	 */
	@GET
	@Path("/{networkId}/nodes/{nodeId}")
	@Produces(MediaType.APPLICATION_JSON)
	@ReturnType("org.cytoscape.rest.internal.model.Node")
	public String getNode(@PathParam("networkId") Long networkId, @PathParam("nodeId") Long nodeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyNode node = network.getNode(nodeId);
		if (node == null) {
			throw new NotFoundException("Could not find node with SUID: " + nodeId);
		}
		return getGraphObject(network, node);
	}

	/**
	 * 
	 * @summary Get an edge
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param edgeId
	 *            Edge SUID
	 * 
	 * @return Edge with associated row data
	 * 
	 */
	@GET
	@Path("/{networkId}/edges/{edgeId}")
	@Produces(MediaType.APPLICATION_JSON)
	@ReturnType("org.cytoscape.rest.internal.model.Edge")
	public String getEdge(@PathParam("networkId") Long networkId, @PathParam("edgeId") Long edgeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyEdge edge = network.getEdge(edgeId);
		if (edge == null) {
			throw new NotFoundException("Could not find edge with SUID: " + edgeId);
		}
		return getGraphObject(network, edge);
	}

	/**
	 * @summary Get source/target node of an edge
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param edgeId
	 *            Edge SUID
	 * @param type
	 *            "source" or "target"
	 * 
	 * @return SUID of the source/target node
	 * 
	 */
	@GET
	@Path("/{networkId}/edges/{edgeId}/{type}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getEdgeComponent(@PathParam("networkId") Long networkId, @PathParam("edgeId") Long edgeId,
			@PathParam("type") String type) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyEdge edge = network.getEdge(edgeId);

		if (edge == null) {
			throw getError("Could not find edge: " + edgeId, new RuntimeException(), Response.Status.NOT_FOUND);
		}

		Long nodeSUID = null;
		if (type.equals(JsonTags.SOURCE)) {
			nodeSUID = edge.getSource().getSUID();
		} else if (type.equals(JsonTags.TARGET)) {
			nodeSUID = edge.getTarget().getSUID();
		} else {
			throw getError("Invalid parameter for edge: " + type, new IllegalArgumentException(), Response.Status.INTERNAL_SERVER_ERROR);
		}
		return getNumberObjectString(type, nodeSUID);
	}

	/**
	 * 
	 * @summary Get edge directionality
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param edgeId
	 *            Target edge SUID
	 * 
	 * @return true if the edge is directed.
	 * 
	 */
	@GET
	@Path("/{networkId}/edges/{edgeId}/isDirected")
	@Produces(MediaType.APPLICATION_JSON)
	public Boolean getEdgeDirected(@PathParam("networkId") Long networkId, @PathParam("edgeId") Long edgeId) {
		final CyNetwork network = getCyNetwork(networkId);
		CyEdge edge = network.getEdge(edgeId);
		if (edge == null) {
			throw getError("Could not find edge: " + edgeId, new RuntimeException(), Response.Status.NOT_FOUND);
		}
		return edge.isDirected();
	}

	/**
	 * 
	 * @summary Get adjacent edges for a node
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param nodeId
	 *            Target node SUID
	 * 
	 * @return List of connected edges (as SUID)
	 * 
	 */
	@GET
	@Path("/{networkId}/nodes/{nodeId}/adjEdges")
	@Produces(MediaType.APPLICATION_JSON)
	public Collection<Long> getAdjEdges(@PathParam("networkId") Long networkId, @PathParam("nodeId") Long nodeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyNode node = getNode(network, nodeId);
		final List<CyEdge> edges = network.getAdjacentEdgeList(node, Type.ANY);
		return getGraphObjectArray(edges);
	}

	/**
	 * @summary Get network pointer (nested network SUID)
	 * 
	 * @param networkId
	 *            Network SUID.
	 * @param nodeId
	 *            target node SUID.
	 * 
	 * @return Nested network SUID
	 */
	@GET
	@Path("/{networkId}/nodes/{nodeId}/pointer")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getNetworkPointer(@PathParam("networkId") Long networkId, @PathParam("nodeId") Long nodeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyNode node = getNode(network, nodeId);
		final CyNetwork pointer = node.getNetworkPointer();
		if (pointer == null) {
			throw getError("Could not find network pointer.", new RuntimeException(), Response.Status.NOT_FOUND);
		}
		
		return Response.ok(getNumberObjectString(JsonTags.NETWORK_SUID, pointer.getSUID())).build();
	}

	/**
	 * 
	 * @summary Get first neighbors of the node
	 * 
	 * @param networkId
	 *            Target network SUID.
	 * @param nodeId
	 *            Node SUID.
	 * 
	 * @return Neighbors of the node as a list of SUIDs.
	 * 
	 */
	@GET
	@Path("/{networkId}/nodes/{nodeId}/neighbors")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getNeighbours(@PathParam("networkId") Long networkId, @PathParam("nodeId") Long nodeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyNode node = getNode(network, nodeId);
		final List<CyNode> nodes = network.getNeighborList(node, Type.ANY);
		
		return Response.status(Response.Status.OK).entity(getGraphObjectArray(nodes)).build();
	}

	/**
	 * 
	 * Get SUIDs for given collection of graph objects.
	 * 
	 * @param objects
	 * @return
	 */
	private final Collection<Long> getGraphObjectArray(final Collection<? extends CyIdentifiable> objects) {
		return objects.stream()
			.map(CyIdentifiable::getSUID)
			.collect(Collectors.toList());
	}

	/**
	 * Add new node(s) to the network.  Body should include an array of new node names.
	 * <br/>
	 * 
	 * <pre>
	 * [ "nodeName1", "nodeName2", ... ]
	 * </pre>
	 * 
	 * <br />
	 * Node name will be used for "name" column. 
	 * 
	 * @summary Add node(s) to existing network
	 * 
	 * @param networkId Network SUID
	 * 
	 * @return SUID of the new node(s) with the name.
	 */
	@POST
	@Path("/{networkId}/nodes")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response createNode(@PathParam("networkId") Long networkId, final InputStream is) {
		final CyNetwork network = getCyNetwork(networkId);
		final ObjectMapper objMapper = new ObjectMapper();
		JsonNode rootNode = null;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
		} catch (IOException e) {
			throw getError("Could not JSON root node.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}

		// Single or multiple
		if (rootNode.isArray()) {
			final JsonFactory factory = new JsonFactory();

			String result = null;
			try {
				ByteArrayOutputStream stream = new ByteArrayOutputStream();
				JsonGenerator generator = null;
				generator = factory.createGenerator(stream);
				generator.writeStartArray();
				for (final JsonNode node : rootNode) {
					final String nodeName = node.textValue();
					final CyNode newNode = network.addNode();
					network.getRow(newNode).set(CyNetwork.NAME, nodeName);
					generator.writeStartObject();
					generator.writeStringField(CyNetwork.NAME, nodeName);
					generator.writeNumberField(CyIdentifiable.SUID, newNode.getSUID());
					generator.writeEndObject();
				}
				generator.writeEndArray();
				generator.close();
				result = stream.toString("UTF-8");
				stream.close();
				updateViews(network);
			} catch (Exception e) {
				throw getError("Could not create node list.", e, Response.Status.INTERNAL_SERVER_ERROR);
			}
			
			return Response.status(Response.Status.CREATED).entity(result).build();
		} else {
			throw getError("Need to post as array.", new IllegalArgumentException(),
					Response.Status.PRECONDITION_FAILED);
		}
	}
	
	
	/**
	 * Add new edge(s) to the network.  Body should include an array of new node names.
	 * <pre>
	 * [
	 * 	{
	 * 		"source": SOURCE_NODE_SUID,
	 * 		"target": TARGET_NODE_SUID,
	 * 		"directed": (Optional boolean value.  Default is True),
	 * 		"interaction": (Optional.  Will be used for Interaction column.  Default value is '-')
	 * 	} ...
	 * ]
	 * </pre>
	 * 
	 * @summary Add edge(s) to existing network
	 * 
	 * @param networkId Network SUID
	 * 
	 * @return SUIDs of the new edges with source and target SUIDs.
	 * 
	 */
	@POST
	@Path("/{networkId}/edges")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response createEdge(@PathParam("networkId") Long networkId, final InputStream is) {
		final CyNetwork network = getCyNetwork(networkId);
		final ObjectMapper objMapper = new ObjectMapper();

		JsonNode rootNode = null;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
		} catch (IOException e) {
			throw getError("Could not find root node in the given JSON..", e, Response.Status.PRECONDITION_FAILED);
		}

		// Single or multiple
		if (rootNode.isArray()) {
			final JsonFactory factory = new JsonFactory();

			String result = null;
			try {
				ByteArrayOutputStream stream = new ByteArrayOutputStream();
				JsonGenerator generator = null;
				generator = factory.createGenerator(stream);
				generator.writeStartArray();
				for (final JsonNode node : rootNode) {
					JsonNode source = node.get(JsonTags.SOURCE);
					JsonNode target = node.get(JsonTags.TARGET);
					JsonNode interaction = node.get(CyEdge.INTERACTION);
					JsonNode isDirected = node.get(JsonTags.DIRECTED);
					if (source == null || target == null) {
						continue;
					}

					final Long sourceSUID = source.asLong();
					final Long targetSUID = target.asLong();
					final CyNode sourceNode = network.getNode(sourceSUID);
					final CyNode targetNode = network.getNode(targetSUID);

					final CyEdge edge;
					if (isDirected != null) {
						edge = network.addEdge(sourceNode, targetNode, isDirected.asBoolean());
					} else {
						edge = network.addEdge(sourceNode, targetNode, true);
					}
					
					final String sourceName = network.getRow(sourceNode).get(CyNetwork.NAME, String.class);
					final String targetName = network.getRow(targetNode).get(CyNetwork.NAME, String.class);
					
					final String interactionString;
					if (interaction != null) {
						interactionString = interaction.textValue();
					} else {
						interactionString = "-";
					}
					
					network.getRow(edge).set(CyEdge.INTERACTION, interactionString);
					network.getRow(edge).set(CyNetwork.NAME, sourceName + " (" + interactionString + ") " + targetName);

					generator.writeStartObject();
					generator.writeNumberField(CyIdentifiable.SUID, edge.getSUID());
					generator.writeNumberField(JsonTags.SOURCE, sourceSUID);
					generator.writeNumberField(JsonTags.TARGET, targetSUID);
					generator.writeEndObject();
				}
				generator.writeEndArray();
				generator.close();
				result = stream.toString("UTF-8");
				stream.close();
				updateViews(network);
			} catch (Exception e) {
				e.printStackTrace();
				throw getError("Could not create edge.", e, Response.Status.INTERNAL_SERVER_ERROR);
			}
			return Response.status(Response.Status.CREATED).entity(result).build();
		} else {
			throw getError("Need to POST as array.", new IllegalArgumentException(),
					Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	// //////////////// Delete //////////////////////////////////

	
	/**
	 * 
	 * @summary Delete all networks in current session
	 */
	@DELETE
	@Path("/")
	public Response deleteAllNetworks() {
		this.networkManager.getNetworkSet().stream()
			.forEach(network->this.networkManager.destroyNetwork(network));
		
		return Response.ok().build();
	}

	/**
	 * @summary Delete a network
	 * 
	 * @param networkId Network SUID
	 */
	@DELETE
	@Path("/{networkId}")
	public Response deleteNetwork(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		this.networkManager.destroyNetwork(network);
		return Response.ok().build();
	}

	/**
	 * @summary Delete all nodes in the network
	 * 
	 * @param networkId
	 *            Network SUID.
	 */
	@DELETE
	@Path("/{networkId}/nodes")
	public Response deleteAllNodes(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		network.removeNodes(network.getNodeList());
		updateViews(network);
		
		return Response.ok().build();
	}

	/**
	 * @summary Delete all edges in the network
	 * 
	 * @param networkId
	 *            Network SUID
	 */
	@DELETE
	@Path("/{networkId}/edges")
	public Response deleteAllEdges(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		network.removeEdges(network.getEdgeList());
		updateViews(network);
		
		return Response.ok().build();
	}


	/**
	 * 
	 * @summary Delete a node in the network
	 * 
	 * @param networkId Network SUID
	 * @param nodeId Node SUID
	 * 
	 */
	@DELETE
	@Path("/{networkId}/nodes/{nodeId}")
	public Response deleteNode(@PathParam("networkId") Long networkId, @PathParam("nodeId") Long nodeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyNode node = network.getNode(nodeId);
		if (node == null) {
			throw new NotFoundException("Node does not exist.");
		}
		final List<CyNode> nodes = new ArrayList<CyNode>();
		nodes.add(node);
		network.removeNodes(nodes);
		updateViews(network);
		
		return Response.ok().build();
	}


	/**
	 * 
	 * @summary Delete an edge in the network.
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param edgeId
	 *            SUID of the edge to be deleted
	 */
	@DELETE
	@Path("/{networkId}/edges/{edgeId}")
	public Response deleteEdge(@PathParam("networkId") Long networkId, @PathParam("edgeId") Long edgeId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyEdge edge = network.getEdge(edgeId);
		if (edge == null) {
			throw new NotFoundException("Edge does not exist.");
		}
		final List<CyEdge> edges = new ArrayList<CyEdge>();
		edges.add(edge);
		network.removeEdges(edges);
		updateViews(network);
		
		return Response.ok().build();
	}

	/**
	 * Update view of each network
	 * 
	 * @param network
	 */
	private final void updateViews(final CyNetwork network) {
		final Collection<CyNetworkView> views = networkViewManager.getNetworkViews(network);
		for (final CyNetworkView view : views) {
			view.updateView();
		}
	}

	// ///////////////////// Object Creation ////////////////////

	
	/**
	 * @summary Create a new network from Cytoscape.js JSON or Edgelist
	 * 
	 * @param collection Name of new network collection
	 * @param title Title of the new network
	 * @param source Optional.  "url"
	 * @param format "edgelist" or "json" 
	 * 
	 * @return SUID of the new network
	 */
	@POST
	@Path("/")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public String createNetwork(@DefaultValue(DEF_COLLECTION_PREFIX) @QueryParam("collection") String collection,
			@QueryParam("source") String source, @QueryParam("format") String format, 
			@QueryParam("title") String title, final InputStream is,
			@Context HttpHeaders headers) {

		// 1. If source is URL, load from the array of URL
		if (source != null && source.equals(JsonTags.URL)) {
			try {
				return loadNetwork(collection, is);
			} catch (IOException e) {
				throw getError("Could not load networks from given locations.", e,
						Response.Status.INTERNAL_SERVER_ERROR);
			}
		}

		// Check user agent if available
		final List<String> agent = headers.getRequestHeader("user-agent");
		String userAgent = "";
		if (agent != null) {
			userAgent = agent.get(0);
		}
		
		final String collectionName;
		if (collection == null) {
			collectionName = DEF_COLLECTION_PREFIX + userAgent;
		} else {
			collectionName = collection;
		}

		final TaskIterator it;
		if (format != null && format.trim().equals(JsonTags.FORMAT_EDGELIST)) {
			it = edgeListReaderFactory.createTaskIterator(is, collection);
		} else {
			it = cytoscapeJsReaderFactory.createTaskIterator(is, collection);
		}

		final CyNetworkReader reader = (CyNetworkReader) it.next();

		try {
			reader.run(new HeadlessTaskMonitor());
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not parse the given network JSON.", e, Response.Status.PRECONDITION_FAILED);
		}
		
		final CyNetwork[] networks = reader.getNetworks();
		final CyNetwork newNetwork = networks[0];
		
		if(title!= null && title.isEmpty() == false) {
			try {
			newNetwork.getTable(CyNetwork.class, CyNetwork.LOCAL_ATTRS).getRow(newNetwork.getSUID()).set(CyNetwork.NAME, title);
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		addNetwork(networks, reader, collectionName);

		try {
			is.close();
		} catch (IOException e) {
			throw getError("Could not close the network input stream.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}

		// Return SUID-to-Original map
		return getNumberObjectString(JsonTags.NETWORK_SUID, newNetwork.getSUID());
	}

	/**
	 * 
	 * If body is empty, it simply creates new network from current selection.
	 * Otherwise, select from the list of SUID.
	 * 
	 * @summary Create a subnetwork from selected nodes and edges
	 * 
	 * @param networkId Network SUID
	 * 
	 * @return SUID of the new network.
	 * 
	 */
	@POST
	@Path("/{networkId}")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public String createNetworkFromSelected(
			@PathParam("networkId") Long networkId,
			@QueryParam("title") String title,
			final InputStream is,
			@Context HttpHeaders headers) {

		final CyNetwork network = getCyNetwork(networkId);
		final TaskIterator itr = newNetworkSelectedNodesAndEdgesTaskFactory.createTaskIterator(network);

		// TODO: This is very hackey... We need a method to get the new network
		// SUID.
		AbstractNetworkCollectionTask viewTask = null;

		while (itr.hasNext()) {
			final Task task = itr.next();
			try {
				task.run(new HeadlessTaskMonitor());
				if (task instanceof AbstractNetworkCollectionTask && task instanceof ObservableTask) {
					viewTask = (AbstractNetworkCollectionTask) task;
				} else {
				}
			} catch (Exception e) {
				throw getError("Could not create sub network from selection.", e, Response.Status.INTERNAL_SERVER_ERROR);
			}
		}
		try {
			is.close();
		} catch (IOException e) {
			throw getError("Could not close the stream.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}

		if (viewTask != null) {
			final Collection<?> result = ((ObservableTask) viewTask).getResults(Collection.class);
			if (result.size() == 1) {
				final CyNetwork newSubNetwork = ((CyNetworkView) result.iterator().next()).getModel();
				final Long suid = newSubNetwork.getSUID();
				if(title != null) {
					newSubNetwork.getRow(newSubNetwork).set(CyNetwork.NAME, title);
				}
				return getNumberObjectString(JsonTags.NETWORK_SUID, suid);
			}
		}

		throw getError("Could not get new network SUID.", new IllegalStateException(), Response.Status.INTERNAL_SERVER_ERROR);
	}


	private final String loadNetwork(final String collectionName, final InputStream is) throws IOException {
		final ObjectMapper objMapper = new ObjectMapper();
		final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);

		final Map<String, Long[]> results = new HashMap<String, Long[]>();
		// Input should be array of URLs.
		for (final JsonNode node : rootNode) {
			final String sourceUrl = node.asText();
			TaskIterator itr = loadNetworkURLTaskFactory.loadCyNetworks(new URL(sourceUrl));
			CyNetworkReader currentReader = null;

			while (itr.hasNext()) {
				final Task task = itr.next();
				try {
					if (task instanceof CyNetworkReader) {
						currentReader = (CyNetworkReader) task;
						if(currentReader instanceof AbstractCyNetworkReader && collectionName != null) {
							((AbstractCyNetworkReader)currentReader).getRootNetworkList().setSelectedValue(collectionName);
						}
						currentReader.run(new HeadlessTaskMonitor());
					} else {
						task.run(new HeadlessTaskMonitor());
					}
				} catch (Exception e) {
					throw new IOException("Could not execute network reader.", e);
				}
			}

			final CyNetwork[] networks = currentReader.getNetworks();
			final Long[] suids = new Long[networks.length];
			int counter = 0;
			for (CyNetwork network : networks) {
				if(collectionName != null) {
					final CyRootNetwork rootNetwork = ((CySubNetwork)network).getRootNetwork();
					rootNetwork.getRow(rootNetwork).set(CyNetwork.NAME, collectionName);
				}
				suids[counter] = network.getSUID();
				counter++;
			}
			results.put(sourceUrl, suids);
		}

		is.close();
		return generateNetworkLoadResults(results);
	}

	private final String generateNetworkLoadResults(final Map<String, Long[]> results) {
		final JsonFactory factory = new JsonFactory();

		String result = null;
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		JsonGenerator generator = null;
		try {
			generator = factory.createGenerator(stream);

			generator.writeStartArray();

			for (final String url : results.keySet()) {
				generator.writeStartObject();

				generator.writeStringField("source", url);
				generator.writeArrayFieldStart(JsonTags.NETWORK_SUID);
				for (final Long suid : results.get(url)) {
					generator.writeNumber(suid);
				}
				generator.writeEndArray();

				generator.writeEndObject();
			}
			generator.writeEndArray();

			generator.close();
			result = stream.toString("UTF-8");
			stream.close();
		} catch (IOException e) {
			throw getError("Could not create object count.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}

		return result;
	}


	/**
	 * Add network to the manager
	 * 
	 * @param networks
	 * @param reader
	 * @param collectionName
	 */
	private final void addNetwork(final CyNetwork[] networks, final CyNetworkReader reader, final String collectionName) {

		final VisualStyle style = vmm.getCurrentVisualStyle();
		final List<CyNetworkView> results = new ArrayList<CyNetworkView>();

		final Set<CyRootNetwork> rootNetworks = new HashSet<CyRootNetwork>();

		for (final CyNetwork net : networkManager.getNetworkSet()) {
			final CyRootNetwork rootNet = cyRootNetworkManager.getRootNetwork(net);
			rootNetworks.add(rootNet);
		}

		for (final CyNetwork network : networks) {

			// Set network name
			String networkName = network.getRow(network).get(CyNetwork.NAME, String.class);
			if (networkName == null || networkName.trim().length() == 0) {
				if (networkName == null)
					networkName = collectionName;

				network.getRow(network).set(CyNetwork.NAME, networkName);
			}
			networkManager.addNetwork(network);

			final int numGraphObjects = network.getNodeCount() + network.getEdgeCount();
			int viewThreshold = 10000;
			if (numGraphObjects < viewThreshold) {
				final CyNetworkView view = reader.buildCyNetworkView(network);
				networkViewManager.addNetworkView(view);
				vmm.setVisualStyle(style, view);
				style.apply(view);

				if (!view.isSet(BasicVisualLexicon.NETWORK_CENTER_X_LOCATION)
						&& !view.isSet(BasicVisualLexicon.NETWORK_CENTER_Y_LOCATION)
						&& !view.isSet(BasicVisualLexicon.NETWORK_CENTER_Z_LOCATION))
					view.fitContent();
				results.add(view);
			} else {
				// results.add(nullNetworkViewFactory.createNetworkView(network));
			}
		}

		// If this is a subnetwork, and there is only one subnetwork in the
		// root, check the name of the root network
		// If there is no name yet for the root network, set it the same as its
		// base subnetwork
		if (networks.length == 1) {
			if (networks[0] instanceof CySubNetwork) {
				CySubNetwork subnet = (CySubNetwork) networks[0];
				final CyRootNetwork rootNet = subnet.getRootNetwork();
				String rootNetName = rootNet.getRow(rootNet).get(CyNetwork.NAME, String.class);
				rootNet.getRow(rootNet).set(CyNetwork.NAME, collectionName);
				if (rootNetName == null || rootNetName.trim().length() == 0) {
					// The root network does not have a name yet, set it the same
					// as the base subnetwork
					rootNet.getRow(rootNet).set(CyNetwork.NAME, collectionName);
				}
			}
		}
	}
} @Singleton
@Path("/v1/networks.names")
public class NetworkNameResource extends AbstractResource {

	public NetworkNameResource() {
		super();
	}

	/**
	 * 
	 * Returns full list of network data as a JSON array. JSON is in <a
	 * href="http://cytoscape.github.io/cytoscape.js/">Cytoscape.js</a> format.
	 * If no query parameter is given, returns all networks in current session.
	 * 
	 * @summary Get networks in Cytoscape.js JSON format
	 * 
	 * @param column
	 *            Optional. Network table column name to be used for search.
	 * @param query
	 *            Optional. Search query.
	 * 
	 * @return Matched networks in Cytoscape.js JSON. If no query is given, all
	 *         networks.
	 * 
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8")
	@ReturnType("java.util.List<org.cytoscape.rest.internal.model.CyJsNetwork>")
	public List<Map<String,?>> getNetworks(@QueryParam("column") String column,
			@QueryParam("query") String query) {
		Set<CyNetwork> networks;

		if (column == null && query == null) {
			networks = networkManager.getNetworkSet();
		} else {
			if (column == null || column.length() == 0) {
				throw getError("Column name parameter is missing.",
						new IllegalArgumentException(),
						Response.Status.INTERNAL_SERVER_ERROR);
			}
			if (query == null || query.length() == 0) {
				throw getError("Query parameter is missing.",
						new IllegalArgumentException(),
						Response.Status.INTERNAL_SERVER_ERROR);
			}
			networks = getNetworksByQuery(query, column);
		}

		return getNetworksAsSimpleList(networks);
	}

	@SuppressWarnings("unchecked")
	private final List<Map<String, ?>> getNetworksAsSimpleList(final Set<CyNetwork> networks) {
		if (networks.isEmpty()) {
			return Collections.EMPTY_LIST;
		}
		
		final List<Map<String, ?>> networksList = new ArrayList<>();
		for(final CyNetwork network: networks) {
			final Map<String, Object> values = new HashMap<>();
			values.put("SUID", network.getSUID());
			values.put("name", network.getRow(network).get(CyNetwork.NAME, String.class));
			networksList.add(values);
		}

		return networksList;
	}
} @Singleton
@Path("/v1/networks/{networkId}/views")
public class NetworkViewResource extends AbstractResource {

	// Filter rendering engine by ID.
	private static final String DING_ID = "org.cytoscape.ding";

	private static final String DEF_HEIGHT = "600";

	@Context
	@NotNull
	private RenderingEngineManager renderingEngineManager;
	
	@Context
	@NotNull
	private GraphicsWriterManager graphicsWriterManager;
	
	@Context
	@NotNull
	private ExportNetworkViewTaskFactory exportNetworkViewTaskFactory;
	
	
	private final VisualStyleMapper styleMapper;
	private final VisualStyleSerializer styleSerializer;

	private VisualLexicon lexicon;
	private Collection<VisualProperty<?>> nodeLexicon;
	private Collection<VisualProperty<?>> edgeLexicon;
	private Collection<VisualProperty<?>> networkLexicon;

	
	
	public NetworkViewResource() {
		super();
		this.styleMapper = new VisualStyleMapper();
		this.styleSerializer = new VisualStyleSerializer();
	}

	private final void initLexicon() {
		// Prepare lexicon
		this.lexicon = getLexicon();
		nodeLexicon = lexicon.getAllDescendants(BasicVisualLexicon.NODE);
		edgeLexicon = lexicon.getAllDescendants(BasicVisualLexicon.EDGE);
		networkLexicon = lexicon.getAllDescendants(BasicVisualLexicon.NETWORK).stream()
				.filter(vp->vp.getIdString().startsWith("NETWORK")).collect(Collectors.toSet());;
		
	}
	/**
	 * @summary Create view for the network
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return SUID for the new Network View.
	 * 
	 */
	@POST
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public Response createNetworkView(@PathParam("networkId") Long networkId) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyNetworkView view = networkViewFactory.createNetworkView(network);
		networkViewManager.addNetworkView(view);
		return Response.status(Response.Status.CREATED)
				.entity(getNumberObjectString("networkViewSUID", view.getSUID())).build();
	}

	/**
	 * Cytoscape can have multiple views per network model. This feature is not
	 * exposed to end-users, but you can access it from this API.
	 * 
	 * @summary Get number of views for the given network model
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return Number of views for the network model
	 * 
	 */
	@GET
	@Path("/count")
	@Produces(MediaType.APPLICATION_JSON)
	public String getNetworkViewCount(@PathParam("networkId") Long networkId) {
		return getNumberObjectString(JsonTags.COUNT, networkViewManager.getNetworkViews(getCyNetwork(networkId)).size());
	}

	/**
	 * 
	 * In general, a network has one view. But if there are multiple views, this
	 * deletes all of them.
	 * 
	 * @summary Delete all network views
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 */
	@DELETE
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public Response deleteAllNetworkViews(@PathParam("networkId") Long networkId) {
		try {
			final Collection<CyNetworkView> views = this.networkViewManager.getNetworkViews(getCyNetwork(networkId));
			final Set<CyNetworkView> toBeDestroyed = new HashSet<CyNetworkView>(views);
			for (final CyNetworkView view : toBeDestroyed) {
				networkViewManager.destroyNetworkView(view);
			}
			
			return Response.ok().build();
		} catch (Exception e) {
			throw getError("Could not delete network views for network with SUID: " + networkId, e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * 
	 * This returns the first view of the network.  As of Cytoscape 3.2.x, this is the 
	 * only view accessible Cytoscape GUI.
	 * 
	 * @summary Convenience method to get the first view model.
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param file (Optional) If you want to get the view as a file, you can specify output file name with extension.
	 * 				For example, file=test.sif creates new SIF file in the current directory. 
	 * 
	 * @return Network view in JSON or location of the file
	 */
	@GET
	@Path("/first")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getFirstNetworkView(@PathParam("networkId") Long networkId, @QueryParam("file") String file) {
		final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId);
		if (views.isEmpty()) {
			throw new NotFoundException("Could not find view for the network: " + networkId);
		}
		final CyNetworkView view = views.iterator().next();
		return getNetworkView(networkId, view.getSUID(), file);
	}

	/**
	 * @summary Delete a view whatever found first.
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 */
	@DELETE
	@Path("/first")
	@Produces(MediaType.APPLICATION_JSON)
	public Response deleteFirstNetworkView(@PathParam("networkId") Long networkId) {
		final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId);
		if (views.isEmpty() == false) {
			networkViewManager.destroyNetworkView(views.iterator().next());
		}
		
		return Response.ok().build();
	}

	/**
	 * To use this API, you need to know SUID of the CyNetworkView, in addition
	 * to CyNetwork SUID.
	 * 
	 * @summary Get a network view (as JSON or a file)
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param viewId
	 *            Network View SUID
	 *
	 * @param file (Optional) If you want to get the view as a file, you can specify output file name with extension.
	 * 				For example, file=test.sif creates new SIF file in the current directory. 
	 * 
	 * @return View in Cytoscape.js JSON. Currently, view information is (x, y)
	 *         location only.  If you specify <strong>file</strong> option, this returns absolute path to the file. 
	 */
	@GET
	@Path("/{viewId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getNetworkView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId,
			@QueryParam("file") String file) {
		final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId);
		
		CyNetworkView targetView = null;
		for (final CyNetworkView view : views) {
			final Long vid = view.getSUID();
			if (vid.equals(viewId)) {
				targetView = view;
				break;
			}
		}
		
		if(targetView == null) {
			return Response.ok("{}").build();
		} else {
			if(file != null) {
				return Response.ok(writeNetworkFile(file, targetView)).build();
			} else {
				return Response.ok(getNetworkViewString(targetView)).build();
			}
		}
	}
	
	private final Map<String, String> writeNetworkFile(String file, CyNetworkView view) {
		File networkFile = null;
		try {
			networkFile = new File(file);
			TaskIterator itr = exportNetworkViewTaskFactory.createTaskIterator(view, networkFile);
			while(itr.hasNext()) {
				final Task task = itr.next();
				task.run(new HeadlessTaskMonitor());
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not save network file.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		final Map<String, String> message = new HashMap<>();
		message.put("file", networkFile.getAbsolutePath());
		return message;
	}

	/**
	 * @summary Get SUID of all network views
	 * 
	 * @param networkId
	 *            Network SUID
	 * 
	 * @return Array of all network view SUIDs
	 * 
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public Collection<Long> getAllNetworkViews(@PathParam("networkId") Long networkId) {
		final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId);

		final Collection<Long> suids = new HashSet<Long>();

		for (final CyNetworkView view : views) {
			final Long viewId = view.getSUID();
			suids.add(viewId);
		}
		return suids;
	}

	private final String getNetworkViewString(final CyNetworkView networkView) {
		final ByteArrayOutputStream stream = new ByteArrayOutputStream();
		CyWriter writer = cytoscapeJsWriterFactory.createWriter(stream, networkView);
		String jsonString = null;
		try {
			writer.run(null);
			jsonString = stream.toString("UTF-8");
			stream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return jsonString;
	}

	/**
	 * Generate a PNG image as stream. Default size is 600 px.
	 * 
	 * @summary Get PNG image of a network view
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param viewId
	 *            Network View SUID
	 * @param height
	 *            Optional height of the image. Width will be set automatically.
	 * 
	 * @return PNG image stream.
	 */
	@GET
	@Path("/first.png")
	@Produces("image/png")
	public Response getFirstImageAsPng(@PathParam("networkId") Long networkId,
			@DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) {
		
		return getImage("png", networkId, height);
	}

	@GET
	@Path("/first.svg")
	@Produces("image/svg+xml")
	public Response getFirstImageAsSvg(@PathParam("networkId") Long networkId,
			@DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) {
		
		return getImage("svg", networkId, height);
	}

	@GET
	@Path("/first.pdf")
	@Produces("image/pdf")
	public Response getFirstImageAsPdf(@PathParam("networkId") Long networkId,
			@DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) {
		
		return getImage("pdf", networkId, height);
	}

	private final Response getImage(String fileType, Long networkId, int height) {
		final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId);
		if (views.isEmpty()) {
			throw getError("Could not create image.", new NotFoundException("Could not find view for the network: " + networkId),
					Response.Status.NOT_FOUND);
		}
		
		final PresentationWriterFactory factory = graphicsWriterManager.getFactory(fileType);
		final CyNetworkView view = views.iterator().next();
		
		return imageGenerator(fileType, factory, view, height, height);
	}

	/**
	 * Generate a PNG network image as stream. Default size is 600 px.
	 * 
	 * @summary Get PNG image of a network view
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param viewId
	 *            Network View SUID
	 * @param height
	 *            Optional height of the image. Width will be set automatically.
	 * 
	 * @return PNG image stream.
	 */
	@GET
	@Path("/{viewId}.png")
	@Produces("image/png")
	public Response getImageAsPng(@PathParam("networkId") Long networkId,
			@PathParam("viewId") Long viewId,
			@DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) {
		return getImageForView("png", networkId, viewId, height);
	}
	
	@GET
	@Path("/{viewId}.svg")
	@Produces("image/svg+xml")
	public Response getImageAsSvg(@PathParam("networkId") Long networkId,
			@PathParam("viewId") Long viewId,
			@DefaultValue(DEF_HEIGHT) @QueryParam("h") int height) {
		return getImageForView("svg", networkId, viewId, height);
	}
	
	@GET
	@Path("/{viewId}.pdf")
	@Produces("image/pdf")
	public Response getImageAsPdf(@PathParam("networkId") Long networkId,
			@PathParam("viewId") Long viewId) {
		return getImageForView("pdf", networkId, viewId, 500);
	}

	private Response getImageForView(String fileType, Long networkId, Long viewId, Integer height) {
		final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId);
		for (final CyNetworkView view : views) {
			final Long vid = view.getSUID();
			if (vid.equals(viewId)) {
				return getImage(fileType, networkId, height);
			}
		}

		throw new NotFoundException("Could not find view for the network: " + networkId);
	}

	private final Response imageGenerator(final String fileType, PresentationWriterFactory factory, 
			final CyNetworkView view, int width, int height) {
		final Collection<RenderingEngine<?>> re = renderingEngineManager.getRenderingEngines(view);
		if (re.isEmpty()) {
			throw new IllegalArgumentException("No rendering engine.");
		}
		try {
			RenderingEngine<?> engine = null;

			for (RenderingEngine<?> r : re) {
				if (r.getRendererId().equals(DING_ID)) {
					engine = r;
					break;
				}
			}
			if (engine == null) {
				throw new IllegalArgumentException("Could not find Ding rendering eigine.");
			}
			
			final ByteArrayOutputStream baos = new ByteArrayOutputStream();

			final CyWriter writer = factory.createWriter(baos, engine);
			
			if (fileType.equals("png")) {
				// Do some hack to set properties...
				// TODO: Are there cleaner way to access these props?
				final Object zl = writer.getClass().getMethod("getZoom").invoke(writer);
				final BoundedDouble bound = (BoundedDouble)zl; 
				//System.out.println("UB = " + bound.getUpperBound());
				//System.out.println("LB = " + bound.getLowerBound());
				
				// Set large upper bound for generating large PNG.  
				bound.setBounds(bound.getLowerBound(), 5000.0);
				writer.getClass().getMethod("setHeightInPixels", int.class).invoke(writer, height);
			}
			writer.run(new HeadlessTaskMonitor());

			final byte[] imageData = baos.toByteArray();
			return Response.ok(new ByteArrayInputStream(imageData)).build();
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not create image.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}
	
	/**
	 * By passing list of key-value pair for each Visual Property, update node view.
	 * 
	 * The body should have the following JSON:
	 * 
	 * <pre>
	 * [
	 * 		{
	 * 			"SUID": SUID of node,
	 * 			"view": [
	 * 				{
	 * 					"visualProperty": "Visual Property Name, like NODE_FILL_COLOR",
	 * 					"value": "Serialized form of value, like 'red.'"
	 * 				},
	 * 				...
	 * 				{}
	 * 			]
	 * 		},
	 * 		...
	 * 		{}
	 * ]
	 * </pre>
	 * 
	 * Note that this API directly set the value to the view objects, and once Visual Style applied, 
	 * those values are overridden by the Visual Style.
	 * 
	 * @summary Update node/edge view objects at once
	 * 
	 * @param networkId Network SUID
	 * @param viewId Network view SUID
	 * @param objectType Type of objects ("nodes" or "edges")
	 * 
	 */
	@PUT
	@Path("/{viewId}/{objectType}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateViews(@PathParam("networkId") Long networkId,
			@PathParam("viewId") Long viewId,
			@PathParam("objectType") String objectType, final InputStream is) {

		final CyNetworkView networkView = getView(networkId, viewId);

		final ObjectMapper objMapper = new ObjectMapper();

		try {
			// This should be an JSON array.
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);

			for (JsonNode entry : rootNode) {
				final Long objectId = entry.get(CyIdentifiable.SUID).asLong();
				final JsonNode viewNode = entry.get("view");
				if(objectId == null || viewNode == null) {
					continue;
				}

				View<? extends CyIdentifiable> view = null;
				if (objectType.equals("nodes")) {
					view = networkView.getNodeView(networkView.getModel().getNode(objectId));
				} else if (objectType.equals("edges")) {
					view = networkView.getEdgeView(networkView.getModel().getEdge(objectId));
				} else if(objectType.equals("network")) {
					view = networkView;
				} else {
					throw getError("Method not supported.",
							new IllegalStateException(),
							Response.Status.INTERNAL_SERVER_ERROR);
				}

				if (view == null) {
					throw getError("Could not find view.",
							new IllegalArgumentException(),
							Response.Status.NOT_FOUND);
				}
				styleMapper.updateView(view, viewNode, getLexicon());
			}
			
			// Repaint
			networkView.updateView();
		} catch (Exception e) {
			throw getError(
					"Could not parse the input JSON for updating view because: "
							+ e.getMessage(), e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}
		return Response.ok().build();
	}


	/**
	 * By passing a list of key-value pair for each Visual Property, update single node/edge view.
	 * 
	 * The body should have the following JSON:
	 * 
	 * <pre>
	 * [
	 * 		{
	 * 			"visualProperty": "Visual Property Name, like NODE_FILL_COLOR",
	 * 			"value": "Serialized form of value, like 'red.'"
	 * 		},
	 * 		...
	 * 		{}
	 * ]
	 * </pre>
	 * 
	 * Note that this API directly set the value to the view objects, and once Visual Style applied, 
	 * those values are overridden by the Visual Style.
	 * 
	 * @summary Update single node/edge view object
	 * 
	 * @param networkId Network SUID
	 * @param viewId Network view SUID
	 * @param objectType Type of objects (nodes, edges, or network)
	 * @param objectId node/edge SUID (NOT node/edge view SUID)
	 * 
	 */
	@PUT
	@Path("/{viewId}/{objectType}/{objectId}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId,
			@PathParam("objectType") String objectType, @PathParam("objectId") Long objectId,
			final InputStream is) {
		
		final CyNetworkView networkView = getView(networkId, viewId);
		
		View<? extends CyIdentifiable> view = null;
		if(objectType.equals("nodes")) {
			view = networkView.getNodeView(networkView.getModel().getNode(objectId));
		} else if(objectType.equals("edges")) {
			view = networkView.getEdgeView(networkView.getModel().getEdge(objectId));
		} else if(objectType.equals("network")) {
			view = networkView;
		}
		
		if(view == null) {
			throw getError("Could not find view.", new IllegalArgumentException(), Response.Status.NOT_FOUND);
		}
		
		final ObjectMapper objMapper = new ObjectMapper();

		try {
			// This should be an JSON array.
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
			styleMapper.updateView(view, rootNode, getLexicon());
		} catch (Exception e) {
			throw getError("Could not parse the input JSON for updating view because: " + e.getMessage(), e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		// Repaint
		networkView.updateView();
		return Response.ok().build();
	}

	/**
	 * 
	 * By passing a list of key-value pairs for each Visual Property, update network view.
	 * 
	 * The body should have the following JSON:
	 * 
	 * <pre>
	 * [
	 * 		{
	 * 			"visualProperty": "Visual Property Name, like NETWORK_BACKGROUND_PAINT",
	 * 			"value": "Serialized form of value, like 'red.'"
	 * 		},
	 * 		...
	 * 		{}
	 * ]
	 * </pre>
	 * 
	 * Note that this API directly set the value to the view objects, and once Visual Style applied, 
	 * those values are overridden by the Visual Style.
	 * 
	 * @summary Update single network view value, such as background color or zoom level.
	 * 
	 * @param networkId Network SUID
	 * @param viewId Network view SUID
	 * 
	 */
	@PUT
	@Path("/{viewId}/network")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateNetworkView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId, final InputStream is) {
		final CyNetworkView networkView = getView(networkId, viewId);
		final ObjectMapper objMapper = new ObjectMapper();

		try {
			// This should be an JSON array.
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
			styleMapper.updateView(networkView, rootNode, getLexicon());
		} catch (Exception e) {
			throw getError("Could not parse the input JSON for updating view because: " + e.getMessage(), e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		// Repaint
		networkView.updateView();
		
		return Response.ok().build();
	}


	@GET
	@Path("/{viewId}/network")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getNetworkVisualProps(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId) {
		return this.getViews(networkId, viewId, "network", null);
	}


	/**
	 * @summary Get view object for the specified type (node or edge)
	 * 
	 * @param networkId Network SUID
	 * @param viewId Network view SUID
	 * @param objectType nodes or edges
	 * @param objectId Object's SUID
	 * 
	 */
	@GET
	@Path("/{viewId}/{objectType}/{objectId}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getView(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId,
			@PathParam("objectType") String objectType, @PathParam("objectId") Long objectId) {
		final CyNetworkView networkView = getView(networkId, viewId);
		
		View<? extends CyIdentifiable> view = null;
		Collection<VisualProperty<?>> lexicon = null;
		if(nodeLexicon == null) {
			initLexicon();
		}
		
		if(objectType.equals("nodes")) {
			view = networkView.getNodeView(networkView.getModel().getNode(objectId));
			lexicon = nodeLexicon;
		} else if(objectType.equals("edges")) {
			view = networkView.getNodeView(networkView.getModel().getNode(objectId));
			lexicon = edgeLexicon;
		}
		
		if(view == null) {
			throw getError("Could not find view.", new IllegalArgumentException(), Response.Status.NOT_FOUND);
		}

		try {
			return styleSerializer.serializeView(view, lexicon);
		} catch (IOException e) {
			throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	@GET
	@Path("/{viewId}/{objectType}/{objectId}/{visualProperty}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getSingleVisualPropertyValue(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId,
			@PathParam("objectType") String objectType, @PathParam("objectId") Long objectId, 
			@PathParam("visualProperty") String visualProperty) {
		final CyNetworkView networkView = getView(networkId, viewId);
		
		Collection<VisualProperty<?>> vps = null;
		View<? extends CyIdentifiable> view = null;
		if(nodeLexicon == null) {
			initLexicon();
		}
		
		if(objectType.equals("nodes")) {
			view = networkView.getNodeView(networkView.getModel().getNode(objectId));
			vps = nodeLexicon;
		} else if(objectType.equals("edges")) {
			view = networkView.getNodeView(networkView.getModel().getNode(objectId));
			vps = edgeLexicon;
		}
		
		if(view == null) {
			throw getError("Could not find view.", new IllegalArgumentException(), Response.Status.NOT_FOUND);
		}

		return getSingleVp(visualProperty, view, vps);
	}

	@GET
	@Path("/{viewId}/network/{visualProperty}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getNetworkVisualProp(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId,
			@PathParam("visualProperty") String visualProperty) {
		final CyNetworkView networkView = getView(networkId, viewId);
		
		if(nodeLexicon == null) {
			initLexicon();
		}
		return getSingleVp(visualProperty, networkView, networkLexicon);
	}
	
	private String getSingleVp(final String visualProperty, final View<? extends CyIdentifiable> view, 
			final Collection<VisualProperty<?>> vps) {
		VisualProperty<?> targetVp = null;
		for(final VisualProperty<?> vp: vps) {
			if(vp.getIdString().equals(visualProperty)) {
				targetVp = vp;
				break;
			}
		}
		if(targetVp == null) {
			throw getError("Could not find such Visual Property: " + visualProperty , new NotFoundException(), Response.Status.NOT_FOUND);
		}

		try {
			return styleSerializer.serializeSingleVisualProp(view, targetVp);
		} catch (IOException e) {
			throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * @summary Get current values for a specific Visual Property 
	 * 
	 * @param networkId Network SUID
	 * @param viewId SUID of network view
	 * @param objectType nodes or edges
	 * 
	 * @param visualProperty Unique name of a Visual Property
	 */
	@GET
	@Path("/{viewId}/{objectType}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getViews(@PathParam("networkId") Long networkId, @PathParam("viewId") Long viewId,
			@PathParam("objectType") String objectType, @QueryParam("visualProperty") String visualProperty) {
		
		if(visualProperty != null) {
			final String result = getSingleVisualPropertyOfViews(networkId, viewId, objectType, visualProperty);
			return Response.ok(result).build();
		}
		
		if(nodeLexicon == null) {
			initLexicon();
		}
		
		Collection<VisualProperty<?>> vps = null;
		if(objectType.equals("nodes")) {
			vps = nodeLexicon;
		} else if(objectType.equals("edges")) {
			vps = edgeLexicon;
		} else if(objectType.equals("network")) {
			vps = networkLexicon;
		}
		
		return Response.ok(getViewForVPList(networkId, viewId, objectType, vps)).build();
	}

	private final String getViewForVPList(final Long networkId, final Long viewId, final String objectType, Collection<VisualProperty<?>> vps) {
		final CyNetworkView networkView = getView(networkId, viewId);
		Collection<? extends View<? extends CyIdentifiable>> graphObjects = null;
		
		if(objectType.equals("nodes")) {
			graphObjects = networkView.getNodeViews();
		} else if(objectType.equals("edges")) {
			graphObjects = networkView.getEdgeViews();
		} else if(objectType.equals("network")) {
			try {
				return styleSerializer.serializeView(networkView, vps);
			} catch (IOException e) {
				throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR);
			}
		} 
		
		if(graphObjects == null || graphObjects.isEmpty()) {
			throw getError("Could not find views.", new IllegalArgumentException(), Response.Status.NOT_FOUND);
		}
		try {
			return styleSerializer.serializeViews(graphObjects, vps);
		} catch (IOException e) {
			throw getError("Could not serialize the view object.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}	

	
	private final String getSingleVisualPropertyOfViews(Long networkId, Long viewId,
			String objectType, String visualPropertyName) {
		if(nodeLexicon == null) {
			initLexicon();
		}
	
		final Collection<VisualProperty<?>> vps = new HashSet<>();
		VisualProperty<?> vp = null;
		
		if(objectType.equals("nodes")) {
			vp = lexicon.lookup(CyNode.class, visualPropertyName);
		} else if(objectType.equals("edges")) {
			vp = lexicon.lookup(CyEdge.class, visualPropertyName);
		}
		
		if(vp == null) {
			throw getError("Visual Property does not exist: " + visualPropertyName, new NotFoundException(), Response.Status.NOT_FOUND);
		}
		
		vps.add(vp);
		return getViewForVPList(networkId, viewId, objectType, vps);

	}
	
	private final CyNetworkView getView(Long networkId, Long viewId) {
		final Collection<CyNetworkView> views = this.getCyNetworkViews(networkId);
		for (final CyNetworkView view : views) {
			final Long vid = view.getSUID();
			if (vid.equals(viewId)) {
				return view;
			}
		}
		throw new NotFoundException("Could not find view: " + viewId);
	}
} @Singleton
@Path("/v1/session")
public class SessionResource extends AbstractResource {

	@Context
	@NotNull
	private CySessionManager sessionManager;
	
	@Context
	@NotNull
	private SaveSessionAsTaskFactory saveSessionAsTaskFactory;
	
	@Context
	@NotNull
	private OpenSessionTaskFactory openSessionTaskFactory;
	
	@Context
	@NotNull
	private NewSessionTaskFactory newSessionTaskFactory;


	public SessionResource() {
		super();
	}


	/**
	 * 
	 * @summary Get current session name
	 * 
	 * @return Current session name
	 */
	@GET
	@Path("/name")
	@Produces(MediaType.APPLICATION_JSON)
	public String getSessionName() {
		String sessionName = sessionManager.getCurrentSessionFileName();
		if(sessionName == null || sessionName.isEmpty()) {
			sessionName = "";
		}
		
		return "{\"name\": \"" + sessionName +"\"}";
	}
	
	
	/**
	 * 
	 * @summary Delete current session and start new one
	 * 
	 * @return Success message
	 * 
	 */
	@DELETE
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public String deleteSession() {
		
		try {
			TaskIterator itr = newSessionTaskFactory.createTaskIterator(true);
			while(itr.hasNext()) {
				final Task task = itr.next();
				task.run(new HeadlessTaskMonitor());
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not delete current session.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		return "{\"message\": \"New session created.\"}";
	}
	
	
	/**
	 * This get method load a new session from a file
	 * 
	 * @summary Load new session from a local file
	 * 
	 * @param file File name (should be absolute path)
	 * 
	 * @return Session file name as string
	 * 
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public String getSessionFromFile(@QueryParam("file") String file) {
		File sessionFile = null;
		try {
			sessionFile = new File(file);
			TaskIterator itr = openSessionTaskFactory.createTaskIterator(sessionFile);
			while(itr.hasNext()) {
				final Task task = itr.next();
				task.run(new HeadlessTaskMonitor());
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not save session.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	
		return "{\"file\": \"" + sessionFile.getAbsolutePath() +"\"}";
	}
	
	
	/**
	 * 
	 * @summary Create a session file
	 * 
	 * @param file Session file location (should be absolute path)
	 * 
	 * @return Session file name
	 */
	@POST
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public String createSessionFile(@QueryParam("file") String file) {
		File sessionFile = null;
		try {
			sessionFile = new File(file);
			TaskIterator itr = saveSessionAsTaskFactory.createTaskIterator(sessionFile);
			while(itr.hasNext()) {
				final Task task = itr.next();
				task.run(new HeadlessTaskMonitor());
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not save session.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	
		return "{\"file\": \"" + sessionFile.getAbsolutePath() +"\"}";
	}
} @Singleton
@Path("/v1/styles")
public class StyleResource extends AbstractResource {

	private final VisualStyleSerializer styleSerializer = new VisualStyleSerializer();

	@Context
	private WriterListener writerListener;

	@Context
	private TaskMonitor tm;

	@Context
	private VisualStyleFactory vsFactory;

	@Context
	private MappingFactoryManager factoryManager;

	private final ObjectMapper styleMapper;
	private final VisualStyleMapper visualStyleMapper;
	private final VisualStyleSerializer visualStyleSerializer;

	public StyleResource() {
		super();
		this.styleMapper = new ObjectMapper();
		this.styleMapper.registerModule(new VisualStyleModule());

		this.visualStyleMapper = new VisualStyleMapper();
		this.visualStyleSerializer = new VisualStyleSerializer();
	}

	
	/**
	 * 
	 * @summary Get list of Visual Style titles
	 * 
	 * @return List of Visual Style titles.
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public String getStyleNames() {
		final Collection<VisualStyle> styles = vmm.getAllVisualStyles();
		final List<String> styleNames = new ArrayList<String>();
		for (final VisualStyle style : styles) {
			styleNames.add(style.getTitle());
		}
		try {
			return getNames(styleNames);
		} catch (IOException e) {
			throw getError("Could not get style names.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}

	}

	/**
	 * @summary Get number of Visual Styles
	 * 
	 * @return Number of Visual Styles available in current session.
	 * 
	 */
	@GET
	@Path("/count")
	@Produces(MediaType.APPLICATION_JSON)
	public String getStylCount() {
		return getNumberObjectString(JsonTags.COUNT, vmm.getAllVisualStyles().size());
	}

	/**
	 * 
	 * @summary Delete a Visual Style
	 * 
	 * @param name
	 */
	@DELETE
	@Path("/{name}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response deleteStyle(@PathParam("name") String name) {
		vmm.removeVisualStyle(getStyleByName(name));
		return Response.ok().build();
	}
	
	
	/**
	 * @summary Delete all Visual Styles except default style
	 * 
	 */
	@DELETE
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public Response deleteAllStyles() {
		Set<VisualStyle> styles = vmm.getAllVisualStyles();
		Set<VisualStyle> toBeDeleted = new HashSet<VisualStyle>();
		for(final VisualStyle style: styles) {
			if(style.getTitle().equals("default") == false) {
				toBeDeleted.add(style);
			}
		}
		toBeDeleted.stream()
			.forEach(style->vmm.removeVisualStyle(style));
		return Response.ok().build();
	}


	/**
	 * 
	 * @summary Delete a Visual Mapping from a Visual Style
	 * 
	 * @param name Name of the Visual Style
	 * @param vpName Visual Property name associated with the mapping
	 * 
	 */
	@DELETE
	@Path("/{name}/mappings/{vpName}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response deleteMapping(@PathParam("name") String name, @PathParam("vpName") String vpName) {
		final VisualStyle style = getStyleByName(name);
		final VisualProperty<?> vp = getVisualProperty(vpName);
		if(vp == null) {
			throw new NotFoundException("Could not find Visual Property: " + vpName);
		}
		final VisualMappingFunction<?,?> mapping = style.getVisualMappingFunction(vp);
		if(mapping == null) {
			throw new NotFoundException("Could not find mapping for: " + vpName);
		}
		style.removeVisualMappingFunction(vp);
		
		return Response.ok().build();
	}


	
	/**
	 * 
	 * @summary Get all default values for the Visual Style
	 * 
	 * @param name Name of the Visual Style
	 * 
	 * @return List of all default values
	 * 
	 */
	@GET
	@Path("/{name}/defaults")
	@Produces(MediaType.APPLICATION_JSON)
	public String getDefaults(@PathParam("name") String name) {
		return serializeDefaultValues(name);
	}

	
	/**
	 * 
	 * @summary Get a default value for the Visual Property
	 * 
	 * @param name Name of Visual Style
	 * @param vp Unique ID of the Visual Property.  This should be the unique ID of the VP.
	 * 
	 * @return Default value for the Visual Property
	 * 
	 */
	@GET
	@Path("/{name}/defaults/{vp}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getDefaultValue(@PathParam("name") String name, @PathParam("vp") String vp) {
		return serializeDefaultValue(name, vp);
	}


	/**
	 * This method is only for Visual Properties with DiscreteRange, such as 
	 * NODE_SHAPE or EDGE_LINE_TYPE.
	 * 
	 * @summary Get all available range values for the Visual Property
	 * 
	 * @param vp Visual Property ID
	 * 
	 * @return List of all available values for the visual property.
	 */
	@GET
	@Path("/visualproperties/{vp}/values")
	@Produces(MediaType.APPLICATION_JSON)
	public String getRangeValues(@PathParam("vp") String vp) {
		return serializeRangeValue(vp);
	}


	/**
	 * @summary Get all available Visual Properties
	 * 
	 * @return Array of Visual Properties
	 * 
	 */
	@GET
	@Path("/visualproperties")
	@Produces(MediaType.APPLICATION_JSON)
	public String getVisualProperties() {
		final Set<VisualProperty<?>> vps = getAllVisualProperties();
		try {
			return styleSerializer.serializeVisualProperties(vps);
		} catch (IOException e) {
			e.printStackTrace();
			throw getError("Could not serialize Visual Properties.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	/**
	 * @summary Get a Visual Property
	 * 
	 * @param visualProperty Target Visual Property ID
	 * 
	 * @return Visual Property as object
	 */
	@GET
	@Path("/visualproperties/{visualProperty}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getSingleVisualProperty(@PathParam("visualProperty") String visualProperty) {
		final VisualProperty<?> vp = getVisualProperty(visualProperty);
		try {
			return styleSerializer.serializeVisualProperty(vp);
		} catch (IOException e) {
			throw getError("Could not serialize Visual Properties.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	/**
	 * 
	 * @summary Get all Visual Mappings for the Visual Style
	 * 
	 * @param name Name of the Visual Style
	 * 
	 * @return List of all Visual Mappings.
	 * 
	 */
	@GET
	@Path("/{name}/mappings")
	@Produces(MediaType.APPLICATION_JSON)
	public String getMappings(@PathParam("name") String name) {
		final VisualStyle style = getStyleByName(name);
		final Collection<VisualMappingFunction<?, ?>> mappings = style.getAllVisualMappingFunctions();
		if(mappings.isEmpty()) {
			return "[]";
		}
		
		try {
			return styleMapper.writeValueAsString(mappings);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
			throw getError("Could not serialize Mappings.", e, Response.Status.INTERNAL_SERVER_ERROR);
		} catch(Exception ex) {
			ex.printStackTrace();
			throw getError("Could not serialize Mappings.", ex, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	
	/**
	 * 
	 * @summary Get a Visual Mapping associated with the Visual Property
	 * 
	 * @param name Name of the Visual Style
	 * @param vp Unique ID of the Visual Property.  This should be the unique ID of the VP.
	 * 
	 * @return Visual Mapping assigned to the Visual Property
	 * 
	 */
	@GET
	@Path("/{name}/mappings/{vp}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getMapping(@PathParam("name") String name, @PathParam("vp") String vp) {
		final VisualStyle style = getStyleByName(name);
		final VisualMappingFunction<?, ?> mapping = getMappingFunction(vp, style);
		
		try {
			return styleMapper.writeValueAsString(mapping);
		} catch (JsonProcessingException e) {
			throw getError("Could not serialize Mapping.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}
	
	private final VisualMappingFunction<?, ?> getMappingFunction(final String vp, final VisualStyle style) {
		final VisualLexicon lexicon = getLexicon();
		final Set<VisualProperty<?>> allVp = lexicon.getAllVisualProperties();
		VisualProperty<?> visualProp = null;
		for (VisualProperty<?> curVp : allVp) {
			if (curVp.getIdString().equals(vp)) {
				visualProp = curVp;
				break;
			}
		}
		if (visualProp == null) {
			throw new NotFoundException("Could not find VisualProperty: " + vp);
		}
		final VisualMappingFunction<?, ?> mapping = style.getVisualMappingFunction(visualProp);
		
		if(mapping == null) {
			throw new NotFoundException("Could not find visual mapping function for " + vp);
		}
		
		return mapping;
	}

	private final String serializeDefaultValues(String name) {
		final VisualStyle style = getStyleByName(name);
		final VisualLexicon lexicon = getLexicon();
		final Collection<VisualProperty<?>> vps = lexicon.getAllVisualProperties();
		try {
			return styleSerializer.serializeDefaults(vps, style);
		} catch (IOException e) {
			throw getError("Could not serialize default values.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	private final String serializeDefaultValue(final String styleName, final String vpName) {
		final VisualStyle style = getStyleByName(styleName);
		VisualProperty<Object> vp = (VisualProperty<Object>) getVisualProperty(vpName);
		try {
			return styleSerializer.serializeDefault(vp, style);
		} catch (IOException e) {
			throw getError("Could not serialize default value for " + vpName, e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	private final String serializeRangeValue(final String vpName) {
		VisualProperty<Object> vp = (VisualProperty<Object>) getVisualProperty(vpName);
		Range<Object> range = vp.getRange();
		if (range.isDiscrete()) {
			final DiscreteRange<Object> discRange = (DiscreteRange<Object>)range;
			try {
				return styleSerializer.serializeDiscreteRange(vp, discRange);
			} catch (IOException e) {
				throw getError("Could not serialize default value for "
						+ vpName, e, Response.Status.INTERNAL_SERVER_ERROR);
			}
		} else {
			throw new NotFoundException("Range object is not available for " + vpName);
		}
	}

	private final Set<VisualProperty<?>> getAllVisualProperties() {
		final VisualLexicon lexicon = getLexicon();
		return lexicon.getAllVisualProperties();
	}

	private final VisualProperty<?> getVisualProperty(String vpName) {
		final VisualLexicon lexicon = getLexicon();
		final Collection<VisualProperty<?>> vps = lexicon.getAllVisualProperties();
		for (VisualProperty<?> vp : vps) {
			if (vp.getIdString().equals(vpName)) {
				return vp;
			}
		}

		return null;
	}

	// /////////// Update Default Values ///////////////

	
	/**
	 * 
	 * @summary Update a default value for the Visual Property
	 * 
	 * The body of the request should be a list of key-value pair:
	 * <pre>
	 * 		[
	 * 			{
	 * 				"visualProperty": VISUAL_PROPERTY_ID,
	 * 				"value": value
	 * 			}, ...
	 * 		]
	 * </pre> 
	 * 
	 * @param name Name of the Visual Style
	 * 
	 */
	@PUT
	@Path("/{name}/defaults")
	@Produces(MediaType.APPLICATION_JSON)
	public Response updateDefaults(@PathParam("name") String name, InputStream is) {
		final VisualStyle style = getStyleByName(name);
		final ObjectMapper objMapper = new ObjectMapper();
		try {
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
			updateVisualProperties(rootNode, style);
		} catch (Exception e) {
			throw getError("Could not update default values.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		return Response.ok().build();
	}

	@SuppressWarnings("unchecked")
	private final void updateVisualProperties(final JsonNode rootNode, VisualStyle style) {
		for(JsonNode defaultNode: rootNode) {
			final String vpName = defaultNode.get(VisualStyleMapper.MAPPING_VP).textValue();

			@SuppressWarnings("rawtypes")
			final VisualProperty vp = getVisualProperty(vpName);
			if (vp == null) {
				continue;
			}
			final Object value = vp.parseSerializableString(defaultNode.get("value").asText());
			if(value != null) {
				style.setDefaultValue(vp, value);
			}
		}
	}


	/**
	 * 
	 * @summary Get a Visual Style in Cytoscape.js CSS format.
	 * 
	 * @param name Name of the Visual Style
	 * 
	 * @return Visual Style in Cytoscape.js CSS format.  This is always in an array.
	 * 
	 */
	@GET
	@Path("/{name}.json")
	@Produces(MediaType.APPLICATION_JSON)
	public String getStyle(@PathParam("name") String name) {
		if(networkViewManager.getNetworkViewSet().isEmpty()) {
			throw getError("You need at least one view object to use this feature."
					, new IllegalStateException(), Response.Status.INTERNAL_SERVER_ERROR);
		}
		final VisualStyle style = getStyleByName(name);
		final VizmapWriterFactory jsonVsFact = this.writerListener.getFactory();
		final ByteArrayOutputStream os = new ByteArrayOutputStream();
		final Set<VisualStyle> styleCollection = new HashSet<VisualStyle>();
		styleCollection.add(style);
		try {
			final CyWriter styleWriter = jsonVsFact.createWriter(os, styleCollection);
			styleWriter.run(new HeadlessTaskMonitor());
			String jsonString = os.toString("UTF-8");
			os.close();
			return jsonString;
		} catch (Exception e) {
			throw getError("Could not get Visual Style in Cytoscape.js format.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * 
	 * This returns JSON version of Visual Style object with full details.  Format is simple:
	 * 
	 * <pre>
	 * {
	 * 	"title": (name of this Visual Style),
	 * 	"defaults": [ default values ],
	 * 	"mappings": [ Mappings ]
	 * }
	 * </pre>
	 * 
	 * Essentially, this is a JSON version of the Visual Style XML file.
	 * 
	 * 
	 * @summary Get a Visual Style with full details
	 * 
	 * @param name Name of the Visual Style
	 * 
	 * @return Visual Style in Cytoscape JSON format 
	 */
	@GET
	@Path("/{name}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getStyleFull(@PathParam("name") String name) {
		final VisualStyle style = getStyleByName(name);
		try {
			return styleSerializer.serializeStyle(getLexicon().getAllVisualProperties(), style);
		} catch (IOException e) {
			throw getError("Could not get Visual Style JSON.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	/**
	 * @summary Create a new Visual Style from JSON.
	 * 
	 * @return Title of the new Visual Style.
	 */
	@POST
	@Path("/")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response createStyle(InputStream is) {
		final ObjectMapper objMapper = new ObjectMapper();
		JsonNode rootNode;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
			VisualStyle style = this.visualStyleMapper.buildVisualStyle(factoryManager, vsFactory, getLexicon(),
					rootNode);
			vmm.addVisualStyle(style);
			
			// This may be different from the original one if same name exists.
			final String result = "{\"title\": \""+ style.getTitle() + "\"}";
			
			return Response.status(Response.Status.CREATED).entity(result).build();
		} catch (Exception e) {
			throw getError("Could not create new Visual Style.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}



	/**
	 * 
	 * Create a new Visual Mapping function from JSON and add it to the Style.
	 * 
	 * <h3>Discrete Mapping</h3>
	 * <h3>Continuous Mapping</h3>
	 * <h3>Passthrough Mapping</h3>
	 * 
	 * @summary Add a new Visual Mapping
	 * 
	 * @param name Name of the Visual Style
	 */
	@POST
	@Path("/{name}/mappings")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response createMappings(@PathParam("name") String name,InputStream is) {
		final VisualStyle style = getStyleByName(name);
		final ObjectMapper objMapper = new ObjectMapper();
		JsonNode rootNode;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
			this.visualStyleMapper.buildMappings(style, factoryManager, getLexicon(),rootNode);
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not create new Mapping.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		return Response.status(Response.Status.CREATED).build();
	}


	/**
	 * 
	 * @summary Update name of a Visual Style
	 * 
	 * @param name Original name of the Visual Style
	 * 
	 */
	@PUT
	@Path("/{name}")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public void updateStyleName(@PathParam("name") String name, InputStream is) {
		final VisualStyle style = getStyleByName(name);
		final ObjectMapper objMapper = new ObjectMapper();
		JsonNode rootNode;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
			this.visualStyleMapper.updateStyleName(style, getLexicon(), rootNode);
		} catch (Exception e) {
			throw getError("Could not update Visual Style title.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	/**
	 * Currently, this is same as POST; it simply replaces existing mapping.  
	 * You need to send complete information for the new mappings.
	 * 
	 * @summary Update an existing Visual Mapping
	 * 
	 * @param name Name of visual Style
	 * @param vp Target Visual Property
	 * 
	 */
	@PUT
	@Path("/{name}/mappings/{vp}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateMapping(@PathParam("name") String name,  @PathParam("vp") String vp, InputStream is) {
		final VisualStyle style = getStyleByName(name);
		final VisualMappingFunction<?, ?> currentMapping = getMappingFunction(vp, style);
	
		final ObjectMapper objMapper = new ObjectMapper();
		JsonNode rootNode;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
			this.visualStyleMapper.buildMappings(style, factoryManager, getLexicon(),rootNode);
		} catch (Exception e) {
			e.printStackTrace();
			throw getError("Could not update Mapping.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		return Response.ok().build();
	}


	private final VisualStyle getStyleByName(final String name) {
		final Collection<VisualStyle> styles = vmm.getAllVisualStyles();
		for (final VisualStyle style : styles) {
			if (style.getTitle().equals(name)) {
				return style;
			}
		}

		throw new NotFoundException("Could not find Visual Style: " + name);
	}

	
	
	/**
	 * 
	 * Check status of Visual Property Dependencies.  If a dependency is enables, it has true for "enabled."
	 * 
	 * @summary Get all Visual Property Dependency status
	 * 
	 * @param name Name of the Visual Style
	 * 
	 * @return List of the status of all Visual Property dependencies.
	 * 
	 */
	@GET
	@Path("/{name}/dependencies")
	@Produces(MediaType.APPLICATION_JSON)
	public String getAllDependencies(@PathParam("name") String name) {
		final VisualStyle style = getStyleByName(name);
		
		final Set<VisualPropertyDependency<?>> dependencies = style.getAllVisualPropertyDependencies();
		try {
			return visualStyleSerializer.serializeDependecies(dependencies);
		} catch (IOException e) {
			throw getError("Could not get Visual Property denendencies.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	/**
	 * 
	 * The body should be the following format:
	 * <pre>
	 * [
	 * 	{
	 * 		"visualPropertyDependnecy" : "DEPENDENCY_ID",
	 * 		"enabled" : true or false
	 * 	}, ... {}
	 * ]
	 * </pre>
	 * 
	 * @summary Set Visual Property Dependency flags
	 * 
	 * @param name Name of Visual Style
	 * 
	 */
	@PUT
	@Path("/{name}/dependencies")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public void updateDependencies(@PathParam("name") String name, InputStream is) {
		final VisualStyle style = getStyleByName(name);
		
		final ObjectMapper objMapper = new ObjectMapper();
		JsonNode rootNode;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
			this.visualStyleMapper.updateDependencies(style, rootNode);
		} catch (Exception e) {
			throw getError("Could not update Visual Style title.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}
} @Singleton
@Path("/v1/networks/{networkId}/tables")
public class TableResource extends AbstractResource {

	private final static Logger logger = LoggerFactory.getLogger(TableResource.class);

	private static enum TableType {
		DEFAULT_NODE("defaultnode"), DEFAULT_EDGE("defaultedge"), DEFAULT_NETWORK("defaultnetwork");

		private final String type;

		private TableType(final String type) {
			this.type = type;
		}

		private String getType() {
			return this.type;
		}
	}

	private final TableMapper tableMapper;
	private final ObjectMapper tableObjectMapper;
	private final CyTableSerializer tableSerializer;

	public TableResource() {
		super();
		this.tableMapper = new TableMapper();
		this.tableObjectMapper = new ObjectMapper();
		this.tableObjectMapper.registerModule(new TableModule());
		this.tableSerializer = new CyTableSerializer();
	}

	/**
	 * Create new, empty column in an assigned table.
	 * This accepts the following object OR allay of this objects:
	 * 
	 * <pre>
	 * 		{
	 * 			"name":"COLUMN NAME",
	 * 			"type":"data type, Double, String, Boolean, Long, Integer",
	 * 			"immutable": "Optional: boolean value to specify immutable or not",
	 * 			"list": "Optional.  If true, return create List column for the given type."
	 * 			"local": "Optional.  If true, it will be a local column"
	 * 		}
	 * </pre>
	 * 
	 * @summary Create new column(s) in the table
	 * 
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type: "defaultnode", "defaultedge" or "defaultnetwork"
	 * 
	 */
	@POST
	@Path("/{tableType}/columns")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response createColumn(@PathParam("networkId") Long networkId, 
			@PathParam("tableType") String tableType,
			final InputStream is) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		final CyTable localTable = getTableByType(
				network, tableType, JsonTags.COLUMN_IS_LOCAL);
		
		final ObjectMapper objMapper = new ObjectMapper();
		try {
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
			if(rootNode.isArray()) {
				for(JsonNode node: rootNode) {
					tableMapper.createNewColumn(node, table, localTable);
				}
			} else {
				tableMapper.createNewColumn(rootNode, table, localTable);
			}
			
			// Use 201 for created resource
			return Response.status(Response.Status.CREATED).build();
			
		} catch (Exception e) {
			throw getError("Could not process column JSON.", 
					e, Response.Status.PRECONDITION_FAILED);
		}
	}

	/**
	 * 
	 * @summary Delete a column in a table
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type: "defaultnode", "defaultedge" or "defaultnetwork"
	 * @param columnName
	 *            Name of the column to be deleted
	 * 
	 */
	@DELETE
	@Path("/{tableType}/columns/{columnName}")
	public Response deleteColumn(@PathParam("networkId") Long networkId, 
			@PathParam("tableType") String tableType,
			@PathParam("columnName") String columnName) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		if (table != null) {
			table.deleteColumn(columnName);
			return Response.ok().build();
		} else {
			logger.error("Failed to delete a column. (Missing table?)");
			throw new NotFoundException("Could not find the table.  (This should not happen!)");
		}
	}

	/**
	 * To update the column name, you need to provide the parameters in the body:
	 * 
	 * <pre>
	 * {
	 * 		"oldName": OLD_COLUMN_NAME,
	 * 		"newName": NEW_COLUMN_NAME
	 * }
	 * </pre>
	 * 
	 * Both parameters are required.
	 * 
	 * 
	 * @summary Update a column name
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type: "defaultnode", "defaultedge" or "defaultnetwork"
	 * 
	 */
	@PUT
	@Path("/{tableType}/columns")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateColumnName(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType,
			final InputStream is) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		final ObjectMapper objMapper = new ObjectMapper();
		
		try {
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
			tableMapper.updateColumnName(rootNode, table);
			return Response.ok().build();
		} catch (Exception e) {
			throw getError("Could not parse the input JSON for updating "
					+ "column name.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}


	/**
	 * 
	 * @summary Update values in a column
	 * 
	 * By default, you need to provide key-value pair to set values.
	 * However, if "default" is provided, it will be used for the entire column.
	 * 
	 * This is useful to set columns like "selected."
	 * 
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type: "defaultnode", "defaultedge" or "defaultnetwork"
	 * @param columnName
	 *            Target column name
	 * @param default
	 *            Optional. If this value is provided, all cells will be set to this.
	 * 
	 */
	@PUT
	@Path("/{tableType}/columns/{columnName}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateColumnValues(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType,
			@PathParam("columnName") String columnName,
			@QueryParam("default") String defaultValue, final InputStream is) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);

		if (defaultValue != null) {
			tableMapper.updateAllColumnValues(defaultValue, table, columnName);
		} else {
			final ObjectMapper objMapper = new ObjectMapper();
			try {
				final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
				tableMapper.updateColumnValues(rootNode, table, columnName);
			} catch (IOException e) {
				throw getError(
						"Could not parse the input JSON for updating column values.",
						e, Response.Status.INTERNAL_SERVER_ERROR);
			}
		}
		return Response.ok().build();
	}


	/**
	 * 
	 * This API is for updating default node/edge/network data table.  New columns will be created if they
	 * does not exist in the target table.  
	 * 
	 * The BODY of the data should be in the following format:<br/>
	 * 
	 * <pre>
	 * 	{
	 * 		"key":"SUID",  		// This is the unique key column in the existing table
	 * 		"dataKey": "id",		// Mapping key for the new values
	 * 		"data": [
	 * 			{
	 * 				"id": 12345,		// Required. Field name should be same as "dataKey."
	 * 								// In this case, it is "id," but can be anything.
	 * 				"gene_name": "brca1",
	 * 				"exp1": 0.11,
	 * 				"exp2": 0.2
	 *  			}, ...
	 * 			
	 * 		]
	 * 	}
	 * </pre>
	 * 
	 * Current limitations:
	 * <ul>
	 * 	<li>	If key is not specified, SUID will be used for mapping</li>
	 * 	<li>Numbers are handled as Double</li>
	 * 	<li>List column is not supported in this version</li>
	 * </ul>
	 *
	 * @summary Update default table with new values.
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 * @param class Optional.  If this query parameter is set to local, 
	 * 				local table column will be updated.
	 */
	@PUT
	@Path("/{tableType}")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updateTable(@PathParam("networkId") Long networkId, 
			@PathParam("tableType") String tableType,
			@QueryParam("class") String tableClass,
			final InputStream is) {
		
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, tableClass);
		final ObjectMapper objMapper = new ObjectMapper();

		try {
			// This should be an JSON array.
			final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
			tableMapper.updateTableValues(rootNode, table);
		} catch (Exception e) {
			throw getError("Could not parse the input JSON for updating table because: " + e.getMessage(), e, Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		return Response.ok().build();
	}


	/**
	 * @summary Get a row in a table
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 * @param primaryKey
	 *            Name of primary key column
	 * 
	 * @return A row in the table
	 */
	@GET
	@Path("/{tableType}/rows/{primaryKey}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getRow(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType,
			@PathParam("primaryKey") Long primaryKey) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		if (!table.rowExists(primaryKey)) {
			throw new NotFoundException("Could not find the row "
					+ "with primary key: " + primaryKey);
		}

		final CyRow row = table.getRow(primaryKey);

		try {
			return this.serializer.serializeRow(row);
		} catch (IOException e) {
			throw getError("Could not serialize a row with primary key: " + primaryKey, e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * 
	 * @summary Get a value in the cell
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 * @param primaryKey
	 *            Name of primary key column
	 * @param columnName
	 *            Name of the column
	 * 
	 * @return Value in the cell. String, Boolean, Number, or List.
	 * 
	 */
	@GET
	@Path("/{tableType}/rows/{primaryKey}/{columnName}")
	@Produces(MediaType.APPLICATION_JSON)
	public Object getCell(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType,
			@PathParam("primaryKey") Long primaryKey,
			@PathParam("columnName") String columnName) {
		final CyNetwork network = getCyNetwork(networkId);

		final CyTable table = getTableByType(network, tableType, null);
		if (!table.rowExists(primaryKey)) {
			throw new NotFoundException("Could not find the row with promary key: " + primaryKey);
		}

		final CyColumn column = table.getColumn(columnName);
		if (column == null) {
			throw new NotFoundException("Could not find the column: " + columnName);
		}

		final CyRow row = table.getRow(primaryKey);

		if (column.getType() == List.class) {
			List<?> listCell = row.getList(columnName, column.getListElementType());
			if (listCell == null) {
				throw new NotFoundException("Could not find list value.");
			} else {
				return listCell;
			}
		} else {
			final Object cell = row.get(columnName, column.getType());
			if (cell == null) {
				throw new NotFoundException("Could not find value.");
			}

			if (column.getType() == String.class) {
				return cell.toString();
			} else {
				return cell;
			}
		}
	}


	/**
	 * 
	 * @summary Get all rows in a table
	 * 
	 * @param networkId Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 *
	 * @return All rows in the table
	 * 
	 */
	@GET
	@Path("/{tableType}/rows")
	@Produces(MediaType.APPLICATION_JSON)
	public String getRows(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		try {
			return this.serializer.serializeAllRows(table.getAllRows());
		} catch (IOException e) {
			throw getError("Could not serialize rows.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	
	/**
	 * 
	 * @summary Get all columns in a table
	 * 
	 * @param networkId Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 *
	 * @return All columns in the specified table.
	 * 
	 */
	@GET
	@Path("/{tableType}/columns")
	@Produces(MediaType.APPLICATION_JSON)
	public String getColumnNames(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		try {
			return this.serializer.serializeColumns(table.getColumns());
		} catch (IOException e) {
			throw getError("Could not serialize column names.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	
	/**
	 * 
	 * @summary Get all values in a column
	 * 
	 * @param networkId Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 * @param columnName Column name
	 *
	 * @return All values under the specified column.
	 * 
	 */
	@GET
	@Path("/{tableType}/columns/{columnName}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getColumnValues(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType,
			@PathParam("columnName") String columnName) {
		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		final CyColumn column = table.getColumn(columnName);
		final List<Object> values = column.getValues(column.getType());

		try {
			return this.serializer.serializeColumnValues(column, values);
		} catch (IOException e) {
			throw getError("Could not serialize column values.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	
	/**
	 * @summary Get all tables assigned to the network
	 * 
	 * @param networkId network SUID
	 * 
	 * @return All tables in JSON
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON)
	public String getTables(@PathParam("networkId") Long networkId) {
		final Set<CyTable> tables = this.tableManager.getAllTables(true);

		try {
			return tableObjectMapper.writeValueAsString(tables);
		} catch (IOException e) {
			throw getError("Could not serialize tables.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	private final CyTable getTableByType(final CyNetwork network, 
			final String tableType, final String tableClass) {
		
		// Check local or not
		final Boolean isLocal;
		if(tableClass == null || tableClass.isEmpty()) {
			isLocal = false;
		} else if(tableClass.equals("local")) {
			isLocal = true;
		} else {
			isLocal = false;
		}
		
		CyTable table;
		if (tableType.equals(TableType.DEFAULT_NODE.getType())) {
			if(isLocal) {
				table = network.getTable(CyNode.class, CyNetwork.LOCAL_ATTRS);
			} else {
				table = network.getDefaultNodeTable();
			}
		} else if (tableType.equals(TableType.DEFAULT_EDGE.getType())) {
			if(isLocal) {
				table = network.getTable(CyEdge.class, CyNetwork.LOCAL_ATTRS);
			} else {
				table = network.getDefaultEdgeTable();
			}
		} else if (tableType.equals(TableType.DEFAULT_NETWORK.getType())) {
			if(isLocal) {
				table = network.getTable(CyNetwork.class, CyNetwork.LOCAL_ATTRS);
			} else {
				table = network.getDefaultNetworkTable();
			}
		} else {
			// No such table.
			throw new NotFoundException("No such table type: " + tableType);
		}
		return table;
	}


	/**
	 * @summary Get a default table
	 * 
	 * @param networkId Network SUID
	 * @param tableType Table type (defaultnode, defaultedge or defaultnetwork)
	 * 
	 * @return The Table in JSON
	 * 
	 */
	@GET
	@Path("/{tableType}")
	@Produces(MediaType.APPLICATION_JSON)
	public String getTable(@PathParam("networkId") Long networkId,
			@PathParam("tableType") String tableType) {

		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);

		try {
			return this.tableObjectMapper.writeValueAsString(table);
		} catch (JsonProcessingException e) {
			throw getError("Could not serialize table.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}

	/**
	 * @summary Get a table as CSV
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 * 
	 * @return Table in CSV format
	 * 
	 */
	@GET
	@Path("/{tableType}.csv")
	@Produces(MediaType.TEXT_PLAIN)
	public String getTableAsCsv(@PathParam("networkId") Long networkId, @PathParam("tableType") String tableType) {
		return getTableString(networkId, tableType, ",");
	}
	
	
	/**
	 * 
	 * @summary Get a table as TSV (tab delimited text)
	 * 
	 * @param networkId
	 *            Network SUID
	 * @param tableType
	 *            Table type (defaultnode, defaultedge or defaultnetwork)
	 * 
	 * @return Table in TSV format
	 * 
	 */
	@GET
	@Path("/{tableType}.tsv")
	@Produces(MediaType.TEXT_PLAIN)
	public String getTableAsTsv(@PathParam("networkId") Long networkId, @PathParam("tableType") String tableType) {
		return getTableString(networkId, tableType, "\t");
	}
	
	
	/**
	 * Actual function to generate CSV/TSV
	 * 
	 * @param networkId
	 * @param tableType
	 * @param separator
	 * @return
	 */
	private final String getTableString(final Long networkId, 
			final String tableType, final String separator) {

		final CyNetwork network = getCyNetwork(networkId);
		final CyTable table = getTableByType(network, tableType, null);
		try {
			final String result = tableSerializer.toCSV(table, separator);
			return result;
		} catch (Exception e) {
			throw getError("Could not serialize table into CSV.", e, Response.Status.INTERNAL_SERVER_ERROR);
		}
	}
} @Singleton
@Path("/v1/ui")
public class UIResource extends AbstractResource {

	@Context
	@NotNull
	protected CySwingApplication desktop;
	
	@Context
	@NotNull
	protected LevelOfDetails detailsTF;
	
	@Context
	@NotNull
	private TaskMonitor headlessTaskMonitor;


	/**
	 * 
	 * @summary Get status of Desktop
	 * 
	 * @return An object with isDesktopAvailable field.  
	 * 		This value is true if Cytoscape Desktop is available.
	 * 		And it is false if Cytoscape is running in headless mode (not available yet). 
	 * 
	 */
	@GET
	@Path("/")
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public Map<String, Boolean> getDesktop() {
		final Map<String, Boolean> status = new HashMap<>();
		boolean desktopAvailable = false;
		if(desktop != null) {
			desktopAvailable = true;
		}
		status.put("isDesktopAvailable", desktopAvailable);
		return status;
	}


	/**
	 * 
	 * Switch between full graphics details <---> fast rendering mode.
	 * 
	 * @summary Toggle level of graphics details (LoD).
	 * 
	 * @return Success message.
	 */
	@PUT
	@Path("/lod")
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public Response updateLodState() {
		final TaskIterator lod = detailsTF.getLodTF().createTaskIterator(null);
		
		try {
			lod.next().run(headlessTaskMonitor);
		} catch (Exception e) {
			throw getError("Could not toggle LOD.", e,
					Response.Status.INTERNAL_SERVER_ERROR);
		}

		return Response.status(Response.Status.OK)
				.type(MediaType.APPLICATION_JSON)
				.entity("{\"message\":\"Toggled Graphics level of details.\"}").build();
	}

	/**
	 * The return value will includes status of all CytoPanels.
	 * Each entry includes:
	 * 
	 * <ul>
	 * 		<li>
	 * 			name: Official name of the CytoPanel:
	 * 			<ul>
	 * 				<li>SOUTH</li>
	 * 				<li>EAST</li>
	 * 				<li>WEST</li>
	 * 				<li>SOUTH_WEST</li>
	 * 			</ul>
	 * 		</li>
	 * 		<li>
	 * 			state: State of the CytoPanel:
	 * 			<ul>
	 * 				<li>FLOAT</li>
	 * 				<li>DOCK</li>
	 * 				<li>HIDE</li>
	 * 			</ul>
	 * 		</li>
	 * </ul>
	 * 
	 * @summary Get status of all CytoPanels 
	 * 
	 * @return Panel status as an array
	 */
	@GET
	@Path("/panels")
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public List<Map<String, String>> getAllPanelStatus() {
		try {
		return Arrays.asList(CytoPanelName.values()).stream()
			.map(panelName->desktop.getCytoPanel(panelName))
			.map(panel->getMap(panel))
			.collect(Collectors.toList());
		} catch(Exception ex) {
			throw getError("Could not getpanel status", ex, Status.INTERNAL_SERVER_ERROR);
		}
	}


	private final Map<String, String> getMap(final CytoPanel panel) {
		final Map<String, String> values = new HashMap<>();
		values.put("name", panel.getCytoPanelName().name());
		values.put("state", panel.getState().name());
		return values;
	}
	
	
	/**
	 * 
	 * @summary Get status of a CytoPanel
	 * 
	 * @param panelName official name of the CytroPanel
	 * 
	 * @return Status of the CytoPanel (name-state pair)
	 */
	@GET
	@Path("/panels/{panelName}")
	@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
	public Response getPanelStatus(@PathParam("panelName") String panelName) {
		final CytoPanelName panel = CytoPanelName.valueOf(panelName);
		System.out.println(panel);
		if(panel == null) {
			return Response.status(Response.Status.NOT_FOUND).build();
		}
		final CytoPanel panelObject = desktop.getCytoPanel(panel);
		return Response.ok(getMap(panelObject)).build();
	}


	/**
	 * 
	 * You can update multiple panel states at once.
	 * Body of your request should have same format as the return value of GET method.
	 * 
	 * @summary Update CytoPanel states
	 * 
	 * @return Response 200 if success
	 */
	@PUT
	@Path("/panels")
	@Consumes(MediaType.APPLICATION_JSON)
	public Response updatePanelStatus(final InputStream is) {
		
		final ObjectMapper objMapper = new ObjectMapper();

		JsonNode rootNode = null;
		try {
			rootNode = objMapper.readValue(is, JsonNode.class);
		} catch (IOException e) {
			throw new InternalServerErrorException("Could not parse input JSON.", e);
		}
		
		for (final JsonNode entry : rootNode) {
			final JsonNode panelName = entry.get("name");
			final JsonNode panelStatus = entry.get("state");
			
			if(panelName == null || panelStatus == null) {
				throw new IllegalArgumentException("Imput parameters are missing."); 
			}
			
			final CytoPanelName panel = CytoPanelName.valueOf(panelName.asText());
			if(panel == null) {
				throw new IllegalArgumentException("Could not find panel: " + panelName.asText()); 
			}
			
			final CytoPanelState state = CytoPanelState.valueOf(panelStatus.asText());
			if(state == null) {
				throw new IllegalArgumentException("Invalid Panel State: " + panelStatus.asText()); 
			}
			
			final CytoPanel panelObject = desktop.getCytoPanel(panel);
			panelObject.setState(state);
		}
		
		return Response.ok().build();
	}
}
import java.net.URI;
import java.util.Properties;
import javax.ws.rs.core.UriBuilder;
import org.cytoscape.property.CyProperty;
import org.cytoscape.rest.internal.commands.resources.CommandResource;
import org.cytoscape.rest.internal.resource.AlgorithmicResource;
import org.cytoscape.rest.internal.resource.GlobalTableResource;
import org.cytoscape.rest.internal.resource.GroupResource;
import org.cytoscape.rest.internal.resource.MiscResource;
import org.cytoscape.rest.internal.resource.NetworkFullResource;
import org.cytoscape.rest.internal.resource.NetworkResource;
import org.cytoscape.rest.internal.resource.NetworkNameResource;
import org.cytoscape.rest.internal.resource.NetworkViewResource;
import org.cytoscape.rest.internal.resource.RootResource;
import org.cytoscape.rest.internal.resource.SessionResource;
import org.cytoscape.rest.internal.resource.StyleResource;
import org.cytoscape.rest.internal.resource.TableResource;
import org.cytoscape.rest.internal.resource.UIResource;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.hk2.utilities.Binder;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
package org.cytoscape.rest.internal.task;




/**
 * Task to start Grizzly server.
 * 
 */
public final class GrizzlyServerManager {

	private final static Logger logger = LoggerFactory.getLogger(GrizzlyServerManager.class);

	public static final String PORT_NUMBER_PROP = "rest.port";
	public static final Integer DEF_PORT_NUMBER = 1234;
	
	private String baseURL = "http://0.0.0.0";
	private Integer portNumber = DEF_PORT_NUMBER;

	private final Binder binder;

	private HttpServer server = null;

	public GrizzlyServerManager(final Binder binder, CyProperty<Properties> props) {
		this.binder = binder;

		// Get property from property
		Object portNumberProp = props.getProperties().get(PORT_NUMBER_PROP);
		if(portNumberProp != null) {
			try {
				portNumber = Integer.parseInt(portNumberProp.toString());
			} catch(Exception ex) {
				portNumber = DEF_PORT_NUMBER;
			}
		}
	}


	public void startServer() throws Exception {
		if (server == null) {
			final URI baseURI = UriBuilder.fromUri(baseURL).port(portNumber).build();
			final ResourceConfig rc = new ResourceConfig(
					RootResource.class,
					NetworkResource.class,
					NetworkFullResource.class,
					NetworkViewResource.class,
					TableResource.class, 