package com.nibado.example.kafka.serialization;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.ByteBufferInput;
import com.esotericsoftware.kryo.io.ByteBufferOutput;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.nibado.example.kafka.Sensor;
import com.nibado.example.kafka.SensorReading;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.serialization.Serializer;

import java.io.Closeable;
import java.util.Map;

/**
 * (De)serializes SensorReadings using Kryo.
 */
public class KryoReadingSerializer implements Closeable, AutoCloseable, Serializer<SensorReading>, Deserializer<SensorReading> {
    private ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
        protected Kryo initialValue() {
            Kryo kryo = new Kryo();
            kryo.addDefaultSerializer(SensorReading.class, new KryoInternalSerializer());
            return kryo;
        };
    };

    @Override
    public void configure(Map<String, ?> map, boolean b) {
    }

    @Override
    public byte[] serialize(String s, SensorReading sensorReading) {
        ByteBufferOutput output = new ByteBufferOutput(100);
        kryos.get().writeObject(output, sensorReading);
        return output.toBytes();
    }

    @Override
    public SensorReading deserialize(String s, byte[] bytes) {
        try {
            return kryos.get().readObject(new ByteBufferInput(bytes), SensorReading.class);
        }
        catch(Exception e) {
            throw new IllegalArgumentException("Error reading bytes",e);
        }
    }

    @Override
    public void close() {

    }

    private static class KryoInternalSerializer extends com.esotericsoftware.kryo.Serializer<SensorReading> {
        @Override
        public void write(Kryo kryo, Output output, SensorReading sensorReading) {
            output.writeString(sensorReading.getSensor().getId());
            output.writeString(sensorReading.getSensor().getType().toString());
            output.writeLong(sensorReading.getTime(), true);
            output.writeDouble(sensorReading.getValue());
        }

        @Override
        public SensorReading read(Kryo kryo, Input input, Class<SensorReading> aClass) {
            String id = input.readString();
            Sensor.Type type = Sensor.Type.valueOf(input.readString());

            return new SensorReading(new Sensor(id, type), input.readLong(true), input.readDouble());
        }
    }
}