package org.openmrs.module.initializer.api;

import java.io.InputStream;

import org.openmrs.GlobalProperty;
import org.openmrs.module.idgen.IdentifierSource;
import org.openmrs.module.initializer.api.gp.GlobalPropertiesConfig;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;

/**
 * Use this serializer instead of a bare {@link XStream} if you want to ignore unmapped fields.
 */
public class InitializerSerializer extends XStream {
	
	public InitializerSerializer() {
		super();
	}
	
	public InitializerSerializer(HierarchicalStreamDriver hierarchicalStreamDriver) {
		super(hierarchicalStreamDriver);
	}
	
	@Override
	protected MapperWrapper wrapMapper(MapperWrapper next) {
		return new MapperWrapper(next) {
			
			@Override
			public boolean shouldSerializeMember(Class definedIn, String fieldName) {
				if (definedIn == Object.class) {
					// This is not compatible with implicit collections where item name is not
					// defined
					return false;
				} else {
					return super.shouldSerializeMember(definedIn, fieldName);
				}
			}
		};
	}
	
	/**
	 * @return Deserializer for {@link GlobalPropertiesConfig}
	 */
	public static XStream getGlobalPropertiesConfigSerializer() {
		final XStream xs = new InitializerSerializer();
		xs.alias("config", GlobalPropertiesConfig.class);
		xs.alias("globalProperty", GlobalProperty.class);
		xs.aliasField("value", GlobalProperty.class, "propertyValue");
		return xs;
	}
	
	public static GlobalPropertiesConfig getGlobalPropertiesConfig(InputStream is) {
		return (GlobalPropertiesConfig) getGlobalPropertiesConfigSerializer().fromXML(is);
	}
	
	/**
	 * XStream converter that ensures that missing <retired/> tags lead to retired set to false on
	 * children instances of {@link IdentifierSource}.
	 */
	public static class IdentifierSourceConverter extends ReflectionConverter {
		
		public IdentifierSourceConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
			super(mapper, reflectionProvider);
		}
		
		@Override
		public boolean canConvert(Class type) {
			return IdentifierSource.class.isAssignableFrom(type);
		}
		
		@Override
		public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
			IdentifierSource src = (IdentifierSource) super.unmarshal(reader, context);
			if (src.isRetired() == null) {
				src.setRetired(false);
			}
			return src;
		}
	}
}