/**
 * Copyright © 2013-2020 The OpenNTF Domino API Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openntf.domino.rest.resources.frames;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;

import org.openntf.domino.Database;
import org.openntf.domino.Document;
import org.openntf.domino.Session;
import org.openntf.domino.big.NoteCoordinate;
import org.openntf.domino.big.ViewEntryCoordinate;
import org.openntf.domino.contributor.DocumentBackupContributor;
import org.openntf.domino.exceptions.UserAccessException;
import org.openntf.domino.graph2.DGraphUtils;
import org.openntf.domino.graph2.DKeyResolver;
import org.openntf.domino.graph2.builtin.DEdgeFrame;
import org.openntf.domino.graph2.builtin.DVertexFrame;
import org.openntf.domino.graph2.impl.DEdgeEntryList.KeyNotFoundException;
import org.openntf.domino.graph2.impl.DFramedTransactionalGraph;
import org.openntf.domino.rest.json.JsonGraphFactory;
import org.openntf.domino.rest.json.JsonGraphWriter;
import org.openntf.domino.rest.resources.AbstractResource;
import org.openntf.domino.rest.service.Headers;
import org.openntf.domino.rest.service.ODAGraphService;
import org.openntf.domino.rest.service.Parameters;
import org.openntf.domino.rest.service.Parameters.ParamMap;
import org.openntf.domino.rest.service.Routes;
import org.openntf.domino.types.CaseInsensitiveString;
import org.openntf.domino.utils.DominoUtils;
import org.openntf.domino.utils.Factory;
import org.openntf.domino.utils.Factory.SessionType;
import org.openntf.domino.utils.TypeUtils;

import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.JsonJavaObject;
import com.ibm.commons.util.io.json.JsonParser;
import com.ibm.domino.das.utils.ErrorHelper;
import com.ibm.domino.httpmethod.PATCH;
import com.ibm.icu.text.SimpleDateFormat;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.frames.EdgeFrame;
import com.tinkerpop.frames.VertexFrame;

@Path(Routes.ROOT + "/" + Routes.FRAMED + "/" + Routes.NAMESPACE_PATH_PARAM)
public class FramedResource extends AbstractResource {

	public FramedResource(final ODAGraphService service) {
		super(service);
	}

	@SuppressWarnings("unchecked")
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response getFramedObject(@Context final UriInfo uriInfo, @PathParam(Routes.NAMESPACE) final String namespace,
			@Context final Request request) throws JsonException, IOException {
		@SuppressWarnings("rawtypes")
		DFramedTransactionalGraph graph = this.getGraph(namespace);
		ParamMap pm = Parameters.toParamMap(uriInfo);
		if (pm.getVersion() != null) {
			System.out.println("TEMP DEBUG Version number parameter detected " + pm.getVersion().get(0));
		}
		StringWriter sw = new StringWriter();
		JsonGraphWriter writer = new JsonGraphWriter(sw, graph, pm, false, true, false);
		Date lastModified = new Date();
		boolean getLastMod = false;
		try {
			if (pm.get(Parameters.ID) != null) {
				List<String> ids = pm.get(Parameters.ID);
				if (ids.size() == 0) {
					writer.outNull();
				} else if (ids.size() == 1) {
					String id = ids.get(0).trim();
					NoteCoordinate nc = null;
					if (id.startsWith("E")) {
						nc = ViewEntryCoordinate.Utils.getViewEntryCoordinate(id);
					} else if (id.startsWith("V")) {
						nc = ViewEntryCoordinate.Utils.getViewEntryCoordinate(id);
					} else {
						nc = NoteCoordinate.Utils.getNoteCoordinate(id);
						getLastMod = true;
						// System.out.println("TEMP DEBUG isIcon: " +
						// String.valueOf(nc.isIcon()));
					}
					if (nc == null) {
						// writer.outStringProperty("message", "NoteCoordinate
						// is null for id " + id);
					}
					if (graph == null) {
						// writer.outStringProperty("message", "Graph is null
						// for namespace " + namespace);
					}
					NoteCoordinate versionNC = null;
					if (pm.getVersion() != null) {
						String versionString = pm.getVersion().get(0).toString();
						System.out.println("Version parameter detected: " + versionString);
						SimpleDateFormat sdf = TypeUtils.getDefaultDateFormat();
						Date versionDate = sdf.parse(versionString);
						try {
							Session sess = Factory.getSession(SessionType.CURRENT);
							Document doc = sess.getDocumentByMetaversalID(nc.toString());
							Database db = doc.getAncestorDatabase();

							List<DocumentBackupContributor> contributors = Factory.findApplicationServices(DocumentBackupContributor.class);
							if(contributors != null) {
								for(DocumentBackupContributor contributor : contributors) {
									Optional<Document> versionDoc = contributor.createSidecarDocument(db, doc.getUniversalID(), versionDate);
									if(versionDoc.isPresent()) {
										versionNC = versionDoc.get().getNoteCoordinate();
										break;
									}
								}
							}
						} catch (Throwable t) {
							t.printStackTrace();
						}
					}
					Object elem = null;
					if (versionNC != null) {
						elem = graph.getElement(versionNC, null);
						//						System.out.println("Got an element from graph with id " + ((Element)elem).getId());
					} else {
						elem = graph.getElement(nc, null);
					}
					if (elem == null) {
						// builder = Response.status(Status.NOT_FOUND);
						// writer.outStringProperty("currentUsername",
						// Factory.getSession(SessionType.CURRENT).getEffectiveUserName());
						// throw new IllegalStateException();
						Response response = ErrorHelper.createErrorResponse(
								"Graph element not found for id " + String.valueOf(id), Response.Status.NOT_FOUND);
						throw new WebApplicationException(response);

					}
					try {
						if (elem instanceof DVertexFrame && getLastMod) {
							lastModified = ((DVertexFrame) elem).getModified();
						}
						if (elem instanceof DEdgeFrame && getLastMod) {
							lastModified = ((DEdgeFrame) elem).getModified();
						}
					} catch (UserAccessException uae) {
						return ErrorHelper
								.createErrorResponse("User " + Factory.getSession(SessionType.CURRENT).getEffectiveUserName()
										+ " is not authorized to access this resource", Response.Status.UNAUTHORIZED);
					} catch (Exception e) {
						throw new WebApplicationException(
								ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
					}
					writer.outObject(elem);
				} else {
					List<Object> maps = new ArrayList<Object>();
					for (String id : ids) {
						NoteCoordinate nc = NoteCoordinate.Utils.getNoteCoordinate(id.trim());
						maps.add(graph.getElement(nc, null));
					}
					writer.outArrayLiteral(maps);
				}

			} else if (pm.getKeys() != null) {
				Class<?> type = null;
				if (pm.getTypes() != null) {
					List<CharSequence> types = pm.getTypes();
					String typename = types.get(0).toString();
					type = graph.getTypeRegistry().findClassByName(typename);
				}
				DKeyResolver resolver = graph.getKeyResolver(type);
				List<CharSequence> keys = pm.getKeys();
				if (keys.size() == 0) {
					writer.outNull();
				} else if (keys.size() == 1) {
					CharSequence id = keys.get(0);
					NoteCoordinate nc = resolver.resolveKey(type, URLDecoder.decode(String.valueOf(id), "UTF-8"));
					Object elem = null;
					if (nc != null) {
						// writer.outStringProperty("message", "NoteCoordinate
						// is null for id " + id);
						try {
							elem = graph.getElement(nc);
						} catch (Exception e) {
							// NOOP NTF - this is possible and not an error condition. That's why we have .handleMissingKey.
						}
					}
					if (elem == null) {
						elem = resolver.handleMissingKey(type, id);
						if (elem == null) {
							Response response = ErrorHelper.createErrorResponse("Graph element not found for key " + id,
									Response.Status.NOT_FOUND);
							throw new WebApplicationException(response);
						}
					}
					if (elem instanceof Vertex) {
						// System.out.println("TEMP DEBUG Framing a vertex of
						// type "
						// + elem.getClass().getName());
						VertexFrame vf = (VertexFrame) graph.frame((Vertex) elem, type);
						if (vf instanceof DVertexFrame) {
							lastModified = ((DVertexFrame) vf).getModified();
						}
						writer.outObject(vf);
					} else if (elem instanceof Edge) {
						EdgeFrame ef = (EdgeFrame) graph.frame((Edge) elem, type);
						if (ef instanceof DEdgeFrame) {
							lastModified = ((DEdgeFrame) ef).getModified();
						}
						writer.outObject(ef);
					}
				} else {
					List<Object> maps = new ArrayList<Object>();
					for (CharSequence id : keys) {
						NoteCoordinate nc = resolver.resolveKey(type, id);
						maps.add(graph.getElement(nc, null));
					}
					writer.outArrayLiteral(maps);
				}

				graph.rollback();
			} else {
				MultivaluedMap<String, String> mvm = uriInfo.getQueryParameters();
				for (String key : mvm.keySet()) {
					// System.out.println("TEMP DEBUG: " + key + ": " +
					// mvm.getFirst(key));
				}
				Map<String, Object> jsonMap = new LinkedHashMap<String, Object>();
				jsonMap.put("namespace", namespace);
				jsonMap.put("status", "active");
				writer.outObject(jsonMap);
			}

		} catch (UserAccessException uae) {
			return ErrorHelper
					.createErrorResponse("User " + Factory.getSession(SessionType.CURRENT).getEffectiveUserName()
							+ " is not authorized to access this resource", Response.Status.UNAUTHORIZED);
		} catch (KeyNotFoundException knfe) {
			ResponseBuilder rb = Response.noContent();
			return rb.build();
		} catch (Exception e) {
			throw new WebApplicationException(
					ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
		}

		String jsonEntity = sw.toString();
		ResponseBuilder berg = getBuilder(jsonEntity, lastModified, true, request);
		Response response = berg.build();
		return response;
	}

	private ResponseBuilder getBuilder(final String jsonEntity, final Date lastMod, final boolean includeEtag, final Request request) {
		String etagSource = DominoUtils.md5(jsonEntity);
		EntityTag etag = new EntityTag(etagSource);
		ResponseBuilder berg = null;
		if (request != null) {
			berg = request.evaluatePreconditions(etag);
		}
		if (berg == null) {
			// System.out.println("TEMP DEBUG creating a new builder");
			berg = Response.ok();
			if (includeEtag) {
				berg.tag(etag);
			}
			berg.type(MediaType.APPLICATION_JSON_TYPE);
			berg.entity(jsonEntity);
			berg.lastModified(lastMod);
			CacheControl cc = new CacheControl();
			cc.setMustRevalidate(true);
			cc.setPrivate(true);
			cc.setMaxAge(86400);
			cc.setNoTransform(true);
			berg.cacheControl(cc);
		}
		return berg;
	}

	@PUT
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response putDocumentByUnid(final String requestEntity, @Context final UriInfo uriInfo,
			@PathParam(Routes.NAMESPACE) final String namespace,
			@HeaderParam(Headers.IF_UNMODIFIED_SINCE) final String ifUnmodifiedSince, @Context final Request request)
					throws JsonException, IOException {
		ParamMap pm = Parameters.toParamMap(uriInfo);
		Response response = updateFrameByMetaid(requestEntity, namespace, ifUnmodifiedSince, pm, true, request);

		return response;
	}

	// @OPTIONS
	@PATCH
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response patchDocumentByUnid(final String requestEntity, @Context final UriInfo uriInfo,
			@PathParam(Routes.NAMESPACE) final String namespace,
			@HeaderParam(Headers.IF_UNMODIFIED_SINCE) final String ifUnmodifiedSince, @Context final Request request)
					throws JsonException, IOException {
		ParamMap pm = Parameters.toParamMap(uriInfo);
		Response response = updateFrameByMetaid(requestEntity, namespace, ifUnmodifiedSince, pm, false, request);
		return response;
	}

	protected Response updateFrameByMetaid(final String requestEntity, final String namespace, final String ifUnmodifiedSince,
			final ParamMap pm, final boolean isPut, final Request request) throws JsonException, IOException {
		Response result = null;
		DFramedTransactionalGraph<?> graph = this.getGraph(namespace);
		JsonJavaObject jsonItems = null;
		List<Object> jsonArray = null;
		// ResponseBuilder builder = null;
		JsonGraphFactory factory = JsonGraphFactory.instance;
		StringWriter sw = new StringWriter();
		JsonGraphWriter writer = new JsonGraphWriter(sw, graph, pm, false, true, false);

		try {
			StringReader reader = new StringReader(requestEntity);
			try {
				Object jsonRaw = JsonParser.fromJson(factory, reader);
				if (jsonRaw instanceof JsonJavaObject) {
					jsonItems = (JsonJavaObject) jsonRaw;
				} else if (jsonRaw instanceof List) {
					jsonArray = (List) jsonRaw;
				} else if (jsonRaw instanceof String) {
					//NTF reparse
					reader = new StringReader((String)jsonRaw);
					Object jsonRaw2 = JsonParser.fromJson(factory, reader);
					if (jsonRaw2 instanceof JsonJavaObject) {
						jsonItems = (JsonJavaObject) jsonRaw2;
					} else if (jsonRaw2 instanceof List) {
						jsonArray = (List) jsonRaw2;
					} else {
						System.out.println("ALERT Got a jsonRaw2 of type " + (jsonRaw2 !=null?jsonRaw2.getClass().getName():"null") + ".  Value is: " + String.valueOf(jsonRaw));
					}
				}

			} catch (UserAccessException uae) {
				throw new WebApplicationException(ErrorHelper
						.createErrorResponse("User " + Factory.getSession(SessionType.CURRENT).getEffectiveUserName()
								+ " is not authorized to update this resource", Response.Status.UNAUTHORIZED));
			} catch (Exception e) {
				throw new WebApplicationException(
						ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));

			} finally {
				reader.close();
			}
		} catch (Exception ex) {
			throw new WebApplicationException(
					ErrorHelper.createErrorResponse(ex, Response.Status.INTERNAL_SERVER_ERROR));

		}
		if (jsonArray != null) {
			writer.startArray();
			for (Object raw : jsonArray) {
				if (raw instanceof JsonJavaObject) {
					writer.startArrayItem();
					processJsonUpdate((JsonJavaObject) raw, graph, writer, pm, isPut);
					writer.endArrayItem();
				}
			}
			writer.endArray();
		} else if (jsonItems != null) {
			processJsonUpdate(jsonItems, graph, writer, pm, isPut);
		} else {
			System.out.println("ALERT! Received a JSON object that was not expected!");
		}

		String jsonEntity = sw.toString();
		ResponseBuilder berg = getBuilder(jsonEntity, new Date(), false, request);
		Response response = berg.build();
		return response;
	}

	private void processJsonUpdate(final JsonJavaObject jsonItems, final DFramedTransactionalGraph graph, final JsonGraphWriter writer,
			final ParamMap pm, final boolean isPut) throws JsonException, IOException {
		Map<CaseInsensitiveString, Object> cisMap = new HashMap<CaseInsensitiveString, Object>();
		for (String jsonKey : jsonItems.keySet()) {
			CaseInsensitiveString cis = new CaseInsensitiveString(jsonKey);
			cisMap.put(cis, jsonItems.get(jsonKey));
		}
		List<String> ids = pm.get(Parameters.ID);
		boolean commit = true;
		if (ids.size() == 0) {
			// TODO no id
		} else {
			JsonFrameAdapter adapter = null;
			for (String id : ids) {
				NoteCoordinate nc = NoteCoordinate.Utils.getNoteCoordinate(id.trim());
				Object element = graph.getElement(nc, null);
				if (element instanceof EdgeFrame) {
					adapter = new JsonFrameAdapter(graph, (EdgeFrame) element, null, false);
				} else if (element instanceof VertexFrame) {
					adapter = new JsonFrameAdapter(graph, (VertexFrame) element, null, false);
				} else if (element == null) {
					throw new RuntimeException("Cannot force a metaversalid through REST API. Requested URL: "
							+ ODAGraphService.getCurrentRequest().getRequestURI());
				} else {
					throw new RuntimeException(
							"TODO. Requested URL: " + ODAGraphService.getCurrentRequest().getRequestURI()); // TODO
				}
				Iterator<String> frameProperties = adapter.getJsonProperties();
				CaseInsensitiveString actionName = null;
				CaseInsensitiveString preactionName = null;
				List<Object> actionArguments = null;
				for (CaseInsensitiveString cis : cisMap.keySet()) {
					if (cis.equals("%preaction")) {
						preactionName = new CaseInsensitiveString(String.valueOf(cisMap.get(cis)));
					}
					if (cis.equals("%args")) {
						Object result = cisMap.get(cis);
						if (result instanceof List) {
							actionArguments = (List) result;
						}
					}
				}
				if (preactionName != null) {
					if (actionArguments != null) {
						commit = adapter.runAction(preactionName, actionArguments);
					} else {
						commit = adapter.runAction(preactionName);
					}
				}
				if (commit) {
					while (frameProperties.hasNext()) {
						CaseInsensitiveString key = new CaseInsensitiveString(frameProperties.next());
						if (!key.startsWith("@") && !key.startsWith("%")) {
							Object value = cisMap.get(key);
							if (value != null) {
								adapter.putJsonProperty(key.toString(), value);
								cisMap.remove(key);
							} else if (isPut) {
								adapter.putJsonProperty(key.toString(), value);
							}
						}
					}
					for (CaseInsensitiveString cis : cisMap.keySet()) {
						if (cis.equals("%action")) {
							actionName = new CaseInsensitiveString(String.valueOf(cisMap.get(cis)));
						} else if (!cis.startsWith("@") && !cis.startsWith("%")) {
							Object value = cisMap.get(cis);
							if (value != null) {
								adapter.putJsonProperty(cis.toString(), value);
							}
						}
					}
					adapter.updateReadOnlyProperties();
					if (actionName != null) {
						if (actionArguments != null) {
							commit = adapter.runAction(actionName, actionArguments);
						} else {
							commit = adapter.runAction(actionName);
						}
					}
				}
				writer.outObject(element);
			}
			if (commit) {
				graph.commit();
			} else {
				graph.rollback();
			}
		}
	}

	@DELETE
	@Produces(MediaType.APPLICATION_JSON)
	@SuppressWarnings("rawtypes")
	public Response deleteFramedObject(final String requestEntity, @Context final UriInfo uriInfo,
			@PathParam(Routes.NAMESPACE) final String namespace, @Context final Request request)
					throws JsonException, IOException {
		DFramedTransactionalGraph graph = this.getGraph(namespace);
		String jsonEntity = null;
		ParamMap pm = Parameters.toParamMap(uriInfo);
		StringWriter sw = new StringWriter();
		JsonGraphWriter writer = new JsonGraphWriter(sw, graph, pm, false, true, false);

		JsonGraphFactory factory = JsonGraphFactory.instance;

		Map<String, String> report = new HashMap<String, String>();

		List<String> ids = pm.get(Parameters.ID);
		if (ids.size() == 0) {
			throw new WebApplicationException(ErrorHelper.createErrorResponse("No id specified for DELETE",
					Response.Status.INTERNAL_SERVER_ERROR));
		} else {
			for (String id : ids) {
				try {
					NoteCoordinate nc = NoteCoordinate.Utils.getNoteCoordinate(id.trim());
					Object element = graph.getElement(nc, null);
					if (element instanceof Element) {
						((Element) element).remove();
					} else if (element instanceof VertexFrame) {
						graph.removeVertexFrame((VertexFrame) element);
					} else if (element instanceof EdgeFrame) {
						graph.removeEdgeFrame((EdgeFrame) element);
					} else {
						if (element != null) {
							throw new WebApplicationException(ErrorHelper.createErrorResponse(
									"Graph returned unexpected object type " + element.getClass().getName(),
									Response.Status.INTERNAL_SERVER_ERROR));
						}
					}
					report.put(id, "deleted");
				} catch (Exception e) {
					throw new WebApplicationException(
							ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));

				}
			}
			graph.commit();
		}
		try {
			writer.outObject(report);
		} catch (JsonException e) {
			throw new WebApplicationException(
					ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));

		} catch (IOException e) {
			throw new WebApplicationException(
					ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
		}

		jsonEntity = sw.toString();
		ResponseBuilder bob = getBuilder(jsonEntity, new Date(), false, null);
		Response response = bob.build();
		return response;
	}

	@POST
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	@SuppressWarnings("rawtypes")
	public Response createFramedObject(final String requestEntity, @Context final UriInfo uriInfo,
			@PathParam(Routes.NAMESPACE) final String namespace, @Context final Request request)
					throws JsonException, IOException {
		// Factory.println("Processing a POST for " + namespace);
		DFramedTransactionalGraph graph = this.getGraph(namespace);
		ParamMap pm = Parameters.toParamMap(uriInfo);
		StringWriter sw = new StringWriter();
		JsonGraphWriter writer = new JsonGraphWriter(sw, graph, pm, false, true, false);

		JsonJavaObject jsonItems = null;
		List<Object> jsonArray = null;
		JsonGraphFactory factory = JsonGraphFactory.instance;
		Object jsonRaw = null;
		try {
			StringReader reader = new StringReader(requestEntity);
			try {
				jsonRaw = JsonParser.fromJson(factory, reader);
				if (jsonRaw instanceof JsonJavaObject) {
					jsonItems = (JsonJavaObject) jsonRaw;
				} else if (jsonRaw instanceof List) {
					jsonArray = (List) jsonRaw;
				} else if (jsonRaw instanceof String) {
					//NTF reparse
					reader = new StringReader((String)jsonRaw);
					Object jsonRaw2 = JsonParser.fromJson(factory, reader);
					if (jsonRaw2 instanceof JsonJavaObject) {
						jsonItems = (JsonJavaObject) jsonRaw2;
					} else if (jsonRaw2 instanceof List) {
						jsonArray = (List) jsonRaw2;
					} else {
						System.out.println("ALERT Got a jsonRaw2 of type " + (jsonRaw2 !=null?jsonRaw2.getClass().getName():"null") + ".  Value is: " + String.valueOf(jsonRaw));
					}
				}
			} catch (Exception e) {
				throw new WebApplicationException(
						ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
			} finally {
				reader.close();
			}
		} catch (Exception ex) {
			throw new WebApplicationException(
					ErrorHelper.createErrorResponse(ex, Response.Status.INTERNAL_SERVER_ERROR));
		}
		// Map<Object, Object> results = new LinkedHashMap<Object, Object>();
		if (jsonArray != null) {
			writer.startArray();
			for (Object raw : jsonArray) {
				if (raw instanceof JsonJavaObject) {
					writer.startArrayItem();
					processJsonObject((JsonJavaObject) raw, graph, writer, pm/* , results */);
					writer.endArrayItem();
				}
			}
			writer.endArray();
		} else if (jsonItems != null) {
			processJsonObject(jsonItems, graph, writer, pm/* , results */);
		} else {
			System.out.println("ALERT Got a jsonRaw of type " + (jsonRaw !=null?jsonRaw.getClass().getName():"null") + ".  Value is: " + String.valueOf(requestEntity));
		}

		String jsonEntity = sw.toString();
		ResponseBuilder berg = getBuilder(jsonEntity, new Date(), false, null);
		Response response = berg.build();
		return response;
	}

	private void processJsonObject(final JsonJavaObject jsonItems, final DFramedTransactionalGraph graph, final JsonGraphWriter writer,
			final ParamMap pm/* , Map<Object, Object> resultMap */) {
		Map<CaseInsensitiveString, Object> cisMap = new HashMap<CaseInsensitiveString, Object>();
		for (String jsonKey : jsonItems.keySet()) {
			CaseInsensitiveString cis = new CaseInsensitiveString(jsonKey);
			cisMap.put(cis, jsonItems.get(jsonKey));
		}
		List<String> ids = pm.get(Parameters.ID);
		boolean commit = true;
		if (ids == null) {
			Map<String, String> map = new HashMap<String, String>();
			map.put("error", "Cannot POST to frame without an id parameter");
			try {
				writer.outObject(map);
			} catch (JsonException e) {
				throw new WebApplicationException(
						ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
			} catch (IOException e) {
				throw new WebApplicationException(
						ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
			}
		} else {
			if (ids.size() == 0) {
				System.out.println("Cannot POST to frame without an id parameter");
				Map<String, String> map = new HashMap<String, String>();
				map.put("error", "Cannot POST to frame without an id parameter");
				try {
					writer.outObject(map);
				} catch (JsonException e) {
					throw new WebApplicationException(
							ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
				} catch (IOException e) {
					throw new WebApplicationException(
							ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
				}
			} else {
				for (String id : ids) {
					//					System.out.println("TEMP DEBUG POSTing to " + id);
					Class<?> type = null;
					if (pm.getTypes() != null) {
						List<CharSequence> types = pm.getTypes();
						String typename = types.get(0).toString();
						type = graph.getTypeRegistry().findClassByName(typename);
					}
					NoteCoordinate nc = NoteCoordinate.Utils.getNoteCoordinate(id.trim());
					Object element = graph.getElement(nc, type);
					if (element instanceof VertexFrame) {
						VertexFrame parVertex = (VertexFrame) element;
						Map<CaseInsensitiveString, Method> adders = graph.getTypeRegistry()
								.getAdders(parVertex.getClass());
						CaseInsensitiveString rawLabel = new CaseInsensitiveString(jsonItems.getAsString("@label"));
						Method method = adders.get(rawLabel);
						if (method == null) {
							method = adders.get(rawLabel + "In");
						}
						if (method != null) {
							String rawId = jsonItems.getAsString("@id");
							NoteCoordinate othernc = NoteCoordinate.Utils.getNoteCoordinate(rawId);
							Object otherElement = graph.getElement(othernc, null);
							if (otherElement instanceof VertexFrame) {
								VertexFrame otherVertex = (VertexFrame) otherElement;
								try {
									Object result = method.invoke(parVertex, otherVertex);
									if (result == null) {
										System.out.println("Invokation of method " + method.getName()
										+ " on a vertex of type " + DGraphUtils.findInterface(parVertex)
										+ " with an argument of type " + DGraphUtils.findInterface(otherVertex)
										+ " resulted in null when we expected an Edge");
									}
									JsonFrameAdapter adapter = new JsonFrameAdapter(graph, (EdgeFrame) result, null,
											false);
									Iterator<String> frameProperties = adapter.getJsonProperties();
									CaseInsensitiveString actionName = null;
									CaseInsensitiveString preactionName = null;
									for (CaseInsensitiveString cis : cisMap.keySet()) {
										if (cis.equals("%preaction")) {
											preactionName = new CaseInsensitiveString(String.valueOf(cisMap.get(cis)));
										}
									}
									if (preactionName != null) {
										commit = adapter.runAction(preactionName);
									}
									if (commit) {
										while (frameProperties.hasNext()) {
											CaseInsensitiveString key = new CaseInsensitiveString(
													frameProperties.next());
											if (!key.startsWith("@")) {
												Object value = cisMap.get(key);
												if (value != null) {
													adapter.putJsonProperty(key.toString(), value);
													cisMap.remove(key);
												}
											}
										}
										for (CaseInsensitiveString cis : cisMap.keySet()) {
											if (cis.equals("%action")) {
												actionName = new CaseInsensitiveString(String.valueOf(cisMap.get(cis)));
											} else if (!cis.startsWith("@")) {
												Object value = cisMap.get(cis);
												if (value != null) {
													adapter.putJsonProperty(cis.toString(), value);
												}
											}
										}
										adapter.updateReadOnlyProperties();
										if (actionName != null) {
											commit = adapter.runAction(actionName);
										}
									}
									writer.outObject(result);
								} catch (Exception e) {
									throw new WebApplicationException(
											ErrorHelper.createErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
								}
							} else {
								throw new WebApplicationException(
										ErrorHelper.createErrorResponse("otherElement is not a VertexFrame. It's a " + (otherElement == null
										? "null" : DGraphUtils.findInterface(otherElement).getName()), Response.Status.INTERNAL_SERVER_ERROR));
							}
						} else {
							Class[] interfaces = element.getClass().getInterfaces();
							String intList = "";
							for (Class inter : interfaces) {
								intList = intList + inter.getName() + ", ";
							}
							String methList = "";
							for (CaseInsensitiveString key : adders.keySet()) {
								methList = methList + key.toString() + ", ";
							}
							throw new WebApplicationException(
									ErrorHelper.createErrorResponse("No method found for " + rawLabel + " on element " + intList + ": "
											+ ((VertexFrame) element).asVertex().getId() + " methods " + methList, Response.Status.INTERNAL_SERVER_ERROR));
						}
					} else {
						throw new WebApplicationException(ErrorHelper.createErrorResponse("Element is null",
								Response.Status.INTERNAL_SERVER_ERROR));
					}
				}
			}
			if (commit) {
				graph.commit();
			} else {
				graph.rollback();
			}
		}
	}
}