package org.infinispan.server.hotrod;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collections;
import java.util.Set;

import org.infinispan.commons.io.UnsignedNumeric;
import org.infinispan.commons.marshall.AbstractExternalizer;
import org.infinispan.container.versioning.NumericVersion;
import org.infinispan.metadata.Metadata;
import org.infinispan.notifications.cachelistener.filter.CacheEventConverter;
import org.infinispan.notifications.cachelistener.filter.EventType;

class KeyValueVersionConverter implements CacheEventConverter<byte[], byte[], byte[]> {
   private KeyValueVersionConverter() {
   }

   public static KeyValueVersionConverter SINGLETON = new KeyValueVersionConverter();

   @Override
   public byte[] convert(byte[] key, byte[] oldValue, Metadata oldMetadata, byte[] newValue, Metadata newMetadata, EventType eventType) {
      int capacity = UnsignedNumeric.sizeUnsignedInt(key.length) + key.length +
            (newValue != null ? UnsignedNumeric.sizeUnsignedInt(newValue.length) + newValue.length + 8 : 0);

      byte[] out = new byte[capacity];
      int offset = UnsignedNumeric.writeUnsignedInt(out, 0, key.length);
      offset += putBytes(key, offset, out);
      if (newValue != null) {
         offset += UnsignedNumeric.writeUnsignedInt(out, offset, newValue.length);
         offset += putBytes(newValue, offset, out);
         putLong(((NumericVersion) newMetadata.version()).getVersion(), offset, out);
      }
      return out;
   }

   private int putBytes(byte[] bytes, int offset, byte[] out) {
      int localOffset = offset;
      for (byte b : bytes) {
         out[localOffset] = b;
         localOffset += 1;
      }
      return localOffset - offset;
   }

   private int putLong(long l, int offset, byte[] out) {
      out[offset] = (byte) (l >> 56);
      out[offset + 1] = (byte) (l >> 48);
      out[offset + 2] = (byte) (l >> 40);
      out[offset + 3] = (byte) (l >> 32);
      out[offset + 4] = (byte) (l >> 24);
      out[offset + 5] = (byte) (l >> 16);
      out[offset + 6] = (byte) (l >> 8);
      out[offset + 7] = (byte) l;
      return offset + 8;
   }

   static class Externalizer extends AbstractExternalizer<KeyValueVersionConverter> {
      @Override
      public Set<Class<? extends KeyValueVersionConverter>> getTypeClasses() {
         return Collections.singleton(KeyValueVersionConverter.class);
      }

      @Override
      public void writeObject(ObjectOutput output, KeyValueVersionConverter object) throws IOException {

      }

      @Override
      public KeyValueVersionConverter readObject(ObjectInput input) throws IOException, ClassNotFoundException {
         return KeyValueVersionConverter.SINGLETON;
      }
   }
}
