/***
 * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource All rights reserved.
 *
 * 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 br.com.caelum.vraptor.serialization.gson;

import static br.com.caelum.vraptor.proxy.CDIProxies.isCDIProxy;
import static java.util.Collections.singletonList;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

import com.google.gson.ExclusionStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;

import br.com.caelum.vraptor.core.ReflectionProvider;
import br.com.caelum.vraptor.serialization.Serializee;

/**
 * Builder Wrapper for JSON using GSON.
 * 
 * @author Rafael Dipold
 */
@Dependent
public class GsonBuilderWrapper implements GsonSerializerBuilder, GsonDeserializerBuilder {
	
	private GsonBuilder builder = new GsonBuilder();
	private boolean withoutRoot;
	private String alias;
	private List<ExclusionStrategy> exclusions;
	
	private final Serializee serializee;
	private final Iterable<JsonSerializer<?>> jsonSerializers;
	private final Iterable<JsonDeserializer<?>> jsonDeserializers;

	@Inject
	public GsonBuilderWrapper(@Any Instance<JsonSerializer<?>> jsonSerializers, 
			@Any Instance<JsonDeserializer<?>> jsonDeserializers,
			Serializee serializee, ReflectionProvider reflectionProvider) {
		this.jsonSerializers = jsonSerializers;
		this.jsonDeserializers = jsonDeserializers;
		this.serializee = serializee;
		ExclusionStrategy exclusion = new Exclusions(serializee, reflectionProvider);
		exclusions = singletonList(exclusion);
	}

	@Override
	public Gson create() {
		for (JsonSerializer<?> adapter : jsonSerializers) {
			registerAdapter(getAdapterType(adapter), adapter);
		}

		for (JsonDeserializer<?> adapter : jsonDeserializers) {
			registerAdapter(getAdapterType(adapter), adapter);
		}
		
		for (ExclusionStrategy exclusion : exclusions) {
			getGsonBuilder().addSerializationExclusionStrategy(exclusion);
		}
		
		return getGsonBuilder().create();
	}
	
	private void registerAdapter(Class<?> adapterType, Object adapter) {
		RegisterStrategy registerStrategy = adapter.getClass().getAnnotation(RegisterStrategy.class);
		if ((registerStrategy != null) && (registerStrategy.value().equals(RegisterType.SINGLE))) {
			getGsonBuilder().registerTypeAdapter(adapterType, adapter);
		} else {
			getGsonBuilder().registerTypeHierarchyAdapter(adapterType, adapter);
		}	
	}
	
	private Class<?> getAdapterType(Object adapter) {
		final Class<?> klazz;
		if(isCDIProxy(adapter.getClass())){
			final String[] split = adapter.getClass().getName().split("\\$Proxy\\$");
			try {
				klazz = Class.forName(split[0]);
			} catch (final ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}else{
			klazz = adapter.getClass();
		}
		final Type[] genericInterfaces = klazz.getGenericInterfaces();
		final ParameterizedType type = (ParameterizedType) genericInterfaces[0];
		final Type actualType = type.getActualTypeArguments()[0];

		if (actualType instanceof ParameterizedType) {
			return (Class<?>) ((ParameterizedType) actualType).getRawType();
		} else {
			return (Class<?>) actualType;
		}
	}

	@Override
	public Serializee getSerializee() {
		return serializee;
	}

	@Override
	public boolean isWithoutRoot() {
		return withoutRoot;
	}

	@Override
	public void setWithoutRoot(boolean withoutRoot) {
		this.withoutRoot = withoutRoot;		
	}

	@Override
	public String getAlias() {
		return alias;
	}

	@Override
	public void setAlias(String alias) {
		this.alias = alias;
	}

	@Override
	public void indented() {
		getGsonBuilder().setPrettyPrinting();
	}

	@Override
	public void setExclusionStrategies(ExclusionStrategy... strategies) {
		getGsonBuilder().setExclusionStrategies(strategies);
	}

	protected GsonBuilder getGsonBuilder() {
		return builder;
	}

	@Override
	public void version(double versionNumber) {
		getGsonBuilder().setVersion(versionNumber);
	}

	@Override
	public void serializeNulls() {
		getGsonBuilder().serializeNulls();
	}

}

@@ -741,26 +741,40 @@
 Proxies.
-isCDIProxy
+extractRawTypeIfPossible
 ;%0Aimport
@@ -3241,277 +3241,36 @@
 lazz
-;%0A%09%09if(isCDIProxy(adapter.getClass()))%7B%0A%09%09%09final String%5B%5D split = adapter.getClass().getName().split(%22%5C%5C$Proxy%5C%5C$%22);%0A%09%09%09try %7B%0A%09%09%09%09klazz = Class.forName(split%5B0%5D);%0A%09%09%09%7D catch (final ClassNotFoundException e) %7B%0A%09%09%09%09throw new RuntimeException(e);%0A%09%09%09%7D%0A%09%09%7Delse%7B%0A%09%09%09klazz = 
+ = extractRawTypeIfPossible(
 adap
@@ -3283,21 +3283,18 @@
 tClass()
+)
 ;
-%0A%09%09%7D
 %0A%09%09final

Use CDIProxies.extractRawTypeIfPossible to retrieve original class in CDI proxy