package com.gentics.mesh.graphql;

import static graphql.GraphQL.newGraphQL;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;

import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Singleton;

import com.gentics.mesh.core.rest.error.PermissionException;
import com.gentics.mesh.graphdb.NoTx;
import com.gentics.mesh.graphdb.spi.Database;
import com.gentics.mesh.graphql.context.GraphQLContext;
import com.gentics.mesh.graphql.type.QueryTypeProvider;
import com.gentics.mesh.json.JsonUtil;

import graphql.ExceptionWhileDataFetching;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;

@Singleton
public class GraphQLHandler {

	private static final Logger log = LoggerFactory.getLogger(GraphQLHandler.class);

	@Inject
	public QueryTypeProvider typeProvider;

	@Inject
	public Database db;

	@Inject
	public GraphQLHandler() {
	}

	/**
	 * Handle the GraphQL query.
	 * 
	 * @param ac
	 * @param body
	 *            GraphQL query
	 */
	public void handleQuery(GraphQLContext gc, String body) {
		try (NoTx noTx = db.noTx()) {
			JsonObject queryJson = new JsonObject(body);
			String query = queryJson.getString("query");
			GraphQL graphQL = newGraphQL(typeProvider.getRootSchema(gc.getProject())).build();
			ExecutionResult result = graphQL.execute(query, gc);
			List<GraphQLError> errors = result.getErrors();
			JsonObject response = new JsonObject();
			if (!errors.isEmpty()) {
				log.error("Could not execute query {" + query + "}");
			}

			addErrors(errors, response);

			if (result.getData() != null) {
				Map<String, Object> data = (Map<String, Object>) result.getData();
				response.put("data", new JsonObject(JsonUtil.toJson(data)));
			}

			boolean hasErrors = result.getErrors() != null && !result.getErrors()
					.isEmpty();
			HttpResponseStatus code = hasErrors ? BAD_REQUEST : OK;
			gc.send(response.toString(), code);
		}

	}

	/**
	 * Add the listed errors to the response.
	 * 
	 * @param errors
	 * @param response
	 */
	private void addErrors(List<GraphQLError> errors, JsonObject response) {
		if (!errors.isEmpty()) {
			JsonArray jsonErrors = new JsonArray();
			response.put("errors", jsonErrors);
			for (GraphQLError error : errors) {
				JsonObject jsonError = new JsonObject();
				if (error instanceof ExceptionWhileDataFetching) {
					ExceptionWhileDataFetching dataError = (ExceptionWhileDataFetching) error;
					if (dataError.getException() instanceof PermissionException) {
						PermissionException restException = (PermissionException) dataError.getException();
						//TODO translate error
						//TODO add i18n parameters
						jsonError.put("message", restException.getI18nKey());
						jsonError.put("type", restException.getType());
						jsonError.put("elementId", restException.getElementId());
						jsonError.put("elementType", restException.getElementType());
					} else {
						log.error("Error while fetching data.", dataError.getException());
						jsonError.put("message", dataError.getMessage());
						jsonError.put("type", dataError.getErrorType());
					}
				} else {
					jsonError.put("message", error.getMessage());
					jsonError.put("type", error.getErrorType());
					if (error.getLocations() != null && !error.getLocations()
							.isEmpty()) {
						JsonArray errorLocations = new JsonArray();
						jsonError.put("locations", errorLocations);
						for (SourceLocation location : error.getLocations()) {
							JsonObject errorLocation = new JsonObject();
							errorLocation.put("line", location.getLine());
							errorLocation.put("column", location.getColumn());
							errorLocations.add(errorLocation);
						}
					}
				}
				jsonErrors.add(jsonError);
			}
		}
	}
}

@@ -210,16 +210,46 @@
 us.OK;%0A%0A
+import java.util.Collections;%0A
 import j
@@ -263,16 +263,16 @@
 l.List;%0A
-
 import j
@@ -1303,25 +1303,24 @@
 L query.%0A%09 *
- 
 %0A%09 * @param 
@@ -1319,17 +1319,17 @@
  @param 
-a
+g
 c%0A%09 * @p
@@ -1698,16 +1698,45 @@
 uery, gc
+, extractVariables(queryJson)
 );%0A%09%09%09Li
@@ -2319,24 +2319,406 @@
 );%0A%09%09%7D%0A%0A%09%7D%0A%0A
+%09/**%0A%09 * Extracts the variables of a query as a map. Returns empty map if no variables are found.%0A%09 *%0A%09 * @param request The request body%0A%09 */%0A%09private Map%3CString, Object%3E extractVariables(JsonObject request) %7B%0A%09%09JsonObject variables = request.getJsonObject(%22variables%22);%0A%09%09if (variables == null) %7B%0A%09%09%09return Collections.emptyMap();%0A%09%09%7D else %7B%0A%09%09%09return variables.getMap();%0A%09%09%7D%0A%09%7D%0A%0A
 %09/**%0A%09 * Add

Add variables to GraphQL execution