package org.msgpack.jruby;

import java.util.Arrays; import java.nio.ByteBuffer;

import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyObject; import org.jruby.RubyFixnum; import org.jruby.RubyString; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.util.ByteList;

import static org.jruby.runtime.Visibility.PRIVATE;

import org.jcodings.Encoding;

import static org.msgpack.jruby.Types.*;

@JRubyClass(name=“MessagePack::ExtensionValue”) public class ExtensionValue extends RubyObject {

private final Encoding binaryEncoding;

private RubyFixnum type;
private RubyString payload;

public ExtensionValue(Ruby runtime, RubyClass type) {
  super(runtime, type);
  this.binaryEncoding = runtime.getEncodingService().getAscii8bitEncoding();
}

public static class ExtensionValueAllocator implements ObjectAllocator {
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
    return new ExtensionValue(runtime, klass);
  }
}

public static ExtensionValue newExtensionValue(Ruby runtime, int type, byte[] payload) {
  ExtensionValue v = new ExtensionValue(runtime, runtime.getModule("MessagePack").getClass("ExtensionValue"));
  ByteList byteList = new ByteList(payload, runtime.getEncodingService().getAscii8bitEncoding());
  v.initialize(runtime.getCurrentContext(), runtime.newFixnum(type), runtime.newString(byteList));
  return v;
}

@JRubyMethod(name = "initialize", required = 2, visibility = PRIVATE)
public IRubyObject initialize(ThreadContext ctx, IRubyObject type, IRubyObject payload) {
  this.type = (RubyFixnum) type;
  this.payload = (RubyString) payload;
  return this;
}

@JRubyMethod(name = "to_msgpack")
public IRubyObject toMsgpack() {
  ByteList payloadBytes = payload.getByteList();
  int payloadSize = payloadBytes.length();
  int outputSize = 0;
  boolean fixSize = payloadSize == 1 || payloadSize == 2 || payloadSize == 4 || payloadSize == 8 || payloadSize == 16;
  if (fixSize) {
    outputSize = 2 + payloadSize;
  } else if (payloadSize < 0x100) {
    outputSize = 3 + payloadSize;
  } else if (payloadSize < 0x10000) {
    outputSize = 4 + payloadSize;
  } else {
    outputSize = 6 + payloadSize;
  }
  byte[] bytes = new byte[outputSize];
  ByteBuffer buffer = ByteBuffer.wrap(bytes);
  if (payloadSize == 1) {
    buffer.put(FIXEXT1);
    buffer.put((byte) type.getLongValue());
    buffer.put((byte) payloadBytes.get(0));
  } else if (payloadSize == 2) {
    buffer.put(FIXEXT2);
    buffer.put((byte) type.getLongValue());
    buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 2);
  } else if (payloadSize == 4) {
    buffer.put(FIXEXT4);
    buffer.put((byte) type.getLongValue());
    buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 4);
  } else if (payloadSize == 8) {
    buffer.put(FIXEXT8);
    buffer.put((byte) type.getLongValue());
    buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 8);
  } else if (payloadSize == 16) {
    buffer.put(FIXEXT16);
    buffer.put((byte) type.getLongValue());
    buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), 16);
  } else if (payloadSize < 0x100) {
    buffer.put(VAREXT8);
    buffer.put((byte) payloadSize);
    buffer.put((byte) type.getLongValue());
    buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), payloadSize);
  } else if (payloadSize < 0x10000) {
    buffer.put(VAREXT16);
    buffer.putShort((short) payloadSize);
    buffer.put((byte) type.getLongValue());
    buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), payloadSize);
  } else {
    buffer.put(VAREXT32);
    buffer.putInt(payloadSize);
    buffer.put((byte) type.getLongValue());
    buffer.put(payloadBytes.unsafeBytes(), payloadBytes.begin(), payloadSize);
  }
  return getRuntime().newString(new ByteList(bytes, binaryEncoding, false));
}

@JRubyMethod(name = {"to_s", "inspect"})
@Override
public IRubyObject to_s() {
  IRubyObject payloadStr = payload.callMethod(getRuntime().getCurrentContext(), "inspect");
  return getRuntime().newString(String.format("#<MessagePack::ExtensionValue @type=%d, @payload=%s>", type.getLongValue(), payloadStr));
}

@JRubyMethod(name = "hash")
@Override
public RubyFixnum hash() {
  long hash = payload.hashCode() & (type.getLongValue() << 56);
  return RubyFixnum.newFixnum(getRuntime(), hash);
}

@JRubyMethod(name = "eql?")
public IRubyObject eql_p(ThreadContext ctx, IRubyObject o) {
  if (o instanceof ExtensionValue) {
    ExtensionValue other = (ExtensionValue) o;
    return getRuntime().newBoolean(this.type.callMethod(ctx, "eql?", other.type).isTrue() && this.payload.callMethod(ctx, "eql?", other.payload).isTrue());
  }
  return getRuntime().getFalse();
}

}