package org.springframework.cloud.stream.codec.avro;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.avro.io.DatumWriter;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.converter.AbstractFromMessageConverter;
import org.springframework.cloud.stream.schema.CachingSchemaRegistryClient;
import org.springframework.cloud.stream.schema.ConfluentSchemaRegistryClient;
import org.springframework.cloud.stream.schema.SchemaRegistryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.integration.codec.Codec;
import org.springframework.messaging.Message;
import org.springframework.util.MimeType;

/**
 * @author Vinicius Carvalho
 */
@Configuration
@EnableConfigurationProperties({AvroCodecProperties.class})
@ConditionalOnClass(DatumWriter.class)
public class AvroCodecAutoConfiguration {

	//@Bean
	public Codec avroCodec(AvroCodecProperties properties, SchemaRegistryClient schemaRegistryClient, ApplicationContext ctx) throws Exception{
		AvroCodec codec = new AvroCodec();
		codec.setProperties(properties);
		codec.setSchemaRegistryClient(schemaRegistryClient);
		codec.setResolver(new PathMatchingResourcePatternResolver(ctx));
		codec.init();
		return codec;
	}

	@Bean
	public PojoToAvroMessageConverter avroMessageConverter(AvroCodecProperties properties, SchemaRegistryClient schemaRegistryClient, ApplicationContext ctx) throws Exception{
		AvroCodec codec = new AvroCodec();
		codec.setProperties(properties);
		codec.setSchemaRegistryClient(schemaRegistryClient);
		codec.setResolver(new PathMatchingResourcePatternResolver(ctx));
		codec.init();
		return new PojoToAvroMessageConverter(codec);
	}
	@Bean
	public AvroToPojoMessageConverter avroToPojoMessageConverter(AvroCodecProperties properties, SchemaRegistryClient schemaRegistryClient, ApplicationContext ctx) throws Exception{
		AvroCodec codec = new AvroCodec();
		codec.setProperties(properties);
		codec.setSchemaRegistryClient(schemaRegistryClient);
		codec.setResolver(new PathMatchingResourcePatternResolver(ctx));
		codec.init();
		return new AvroToPojoMessageConverter(codec);
	}

	@Bean
	@ConditionalOnProperty("confluent.schemaregistry.endpoint")
	public SchemaRegistryClient confluentClient(@Value("${confluent.schemaregistry.endpoint}") String endpoint){
		ConfluentSchemaRegistryClient registryClient = new ConfluentSchemaRegistryClient(endpoint);
		return new CachingSchemaRegistryClient(registryClient);
	}

//


}

class PojoToAvroMessageConverter extends AbstractFromMessageConverter{

	private AvroCodec codec;

	public PojoToAvroMessageConverter(AvroCodec codec) {
		super(MimeType.valueOf("avro/binary"));
		this.codec = codec;
	}

	@Override
	protected Class<?>[] supportedTargetTypes() {
		return new Class[] { byte[].class };

	}

	@Override
	protected Class<?>[] supportedPayloadTypes() {
		return null;
	}

	@Override
	protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			codec.encode(message.getPayload(),baos);
		}
		catch (IOException e) {
			return null;
		}
		return baos.toByteArray();
	}
}

class AvroToPojoMessageConverter extends AbstractFromMessageConverter{

	private AvroCodec codec;

	public AvroToPojoMessageConverter(AvroCodec codec) {
		super(MimeType.valueOf("avro/binary"));
		this.codec = codec;
	}

	@Override
	protected Class<?>[] supportedTargetTypes() {
		return null;

	}

	@Override
	protected Class<?>[] supportedPayloadTypes() {
		return new Class[] { byte[].class };
	}

	@Override
	protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
		try {
			return codec.decode((byte[]) message.getPayload(),targetClass);
		}
		catch (IOException e) {
			return null;
		}
	}
}