/**
 * Copyright (C) 2013 cherimojava (http://github.com/cherimojava/cherimodata/spring)
 *
 * 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 com.github.cherimojava.data.spring;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import com.github.cherimojava.data.mongo.entity.Entity;
import com.github.cherimojava.data.mongo.entity.EntityFactory;
import com.google.common.base.Charsets;

/**
 * Converts a JSON HTTPMessage to and from Entity. If you want to return/read a list of Entities you must wrap them due
 * to Java limitations into a custom Entity which holds a list of entities. To enable this converter you need to add it
 * to your {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter}. This can happen
 * through overriding the appropriate
 * {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport} methods like
 * {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#configureMessageConverters} or
 * {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHttpMessageConverters}
 *
 * @author philnate
 * @since 1.0.0
 */
public class EntityConverter extends AbstractHttpMessageConverter<Object> implements
		GenericHttpMessageConverter<Object> {

	private final EntityFactory factory;

	/**
	 * creates a new EntityConverter which utilizes the given @{link
	 * com.github.cherimojava.data.mongo.entity.EntityFactory}
	 *
	 * @param factory
	 *            to be used to convert from/to HTTPMessage/Entity
	 */
	public EntityConverter(EntityFactory factory) {
		super(MediaType.APPLICATION_JSON);
		this.factory = factory;
	}

	/**
	 * Checks if the given class can be handled by this Converter or not. Returns true for all
	 * {@link com.github.cherimojava.data.mongo.entity.Entity} based classes, false otherwise
	 *
	 * @param clazz
	 *            to check if it's supported
	 * @return true if the given class can be assigned to Entity, false otherwise
	 */
	@Override
	protected boolean supports(Class<?> clazz) {
		return Entity.class.isAssignableFrom(clazz);
	}

	@Override
	protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException,
			HttpMessageNotReadableException {
		return fromJson((Class<? extends Entity>) clazz, inputMessage);
	}

	@Override
	public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException,
			HttpMessageNotReadableException {
		// as this method is only called after we decided that we can decode the requested type, we only need to check
		// what we have (plain entity/list of entities)
		if (type instanceof Class) {
			// simple class
			return factory.readEntity((Class<? extends Entity>) type, IOUtils.toString(inputMessage.getBody()));
		} else {
			// collection
			return factory.readList((Class<? extends Entity>) ((ParameterizedType) type).getActualTypeArguments()[0],
					IOUtils.toString(inputMessage.getBody()));
		}
	}

	private Entity fromJson(Class<? extends Entity> clazz, HttpInputMessage inputMessage) throws IOException {
		return factory.readEntity(clazz, IOUtils.toString(inputMessage.getBody(), Charsets.UTF_8.name()));
	}

	@Override
	protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException,
			HttpMessageNotWritableException {
		try (OutputStreamWriter osw = new OutputStreamWriter(outputMessage.getBody())) {
			osw.write(o.toString());
		}
	}

	@Override
	public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
		if (MediaType.APPLICATION_JSON.equals(mediaType)) {
			if (type instanceof Class) {
				// check if this is a simple entity class
				return Entity.class.isAssignableFrom((Class) type);
			}
			if (type instanceof ParameterizedType) {
				// is this a parameterized type
				ParameterizedType pt = (ParameterizedType) type;
				if (pt.getRawType() instanceof Class && Collection.class.isAssignableFrom((Class) pt.getRawType())) {
					// is this rawtype a class and is this class some collection
					Type generic = pt.getActualTypeArguments()[0];
					if (generic instanceof Class && Entity.class.isAssignableFrom((Class) generic)) {
						// is this collection generic an entity
						return true;
					}
				}
			}
		}
		return false;
	}

	@Override
	public boolean canWrite(Class<?> clazz, MediaType mediaType) {
		// this is rather ugly, would be great if we could get hold of the Type rather than class, so that we can check
		// for the generic type and decide upon this
		return MediaType.APPLICATION_JSON.equals(mediaType)
				&& (Entity.class.isAssignableFrom(clazz) || Collection.class.isAssignableFrom(clazz));
	}
}

@@ -1555,153 +1555,8 @@
 ity.
- If you want to return/read a list of Entities you must wrap them due%0A * to Java limitations into a custom Entity which holds a list of entities.
  To 
@@ -1599,19 +1599,16 @@
 d it
-%0A *
  to your
  %7B@l
@@ -1603,16 +1603,19 @@
  to your
+%0A *
  %7B@link 
@@ -1718,19 +1718,16 @@
 ppen
-%0A *
  through
  ove
@@ -1722,16 +1722,19 @@
  through
+%0A *
  overrid
@@ -1752,19 +1752,16 @@
 ropriate
-%0A *
  %7B@link 
@@ -1837,16 +1837,19 @@
 Support%7D
+%0A *
  methods

correct javadoc for EntityConverter as it works with plain lists too; closes #14