package the8472.bencode;
import static the8472.bencode.Utils.str2buf;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.stream.Stream;
public class BEncoder {
private ByteBuffer buf;
public static class RawData {
ByteBuffer rawBuf;
public RawData(ByteBuffer b) {
rawBuf = b;
}
}
public static interface StringWriter {
int length();
void writeTo(ByteBuffer buf);
}
public ByteBuffer encode(Map<String, Object> toEnc, int maxSize) {
buf = ByteBuffer.allocate(maxSize);
encodeMap(toEnc);
buf.flip();
return buf;
}
public void encodeInto(Map<String, Object> toEnc, ByteBuffer target) {
buf = target;
encodeMap(toEnc);
buf.flip();
}
public ByteBuffer encode(Object toEnc, int maxSize) {
buf = ByteBuffer.allocate(maxSize);
encodeInternal(toEnc);
buf.flip();
return buf;
}
private void encodeInternal(Object o) {
if(o instanceof Map) {
encodeMap((Map<String, Object>) o);
return;
}
if(o instanceof List) {
encodeList((List<Object>) o);
return;
}
if(o instanceof String) {
encodeString((String) o);
return;
}
if(o instanceof byte[]) {
byte[] b = (byte[]) o;
encodeInt(b.length, (byte) ':');
buf.put(b);
return;
}
if(o instanceof ByteBuffer) {
ByteBuffer clone = ((ByteBuffer) o).slice();
encodeInt(clone.remaining(), (byte) ':');
buf.put(clone);
return;
}
if(o instanceof Integer) {
buf.put((byte) 'i');
encodeInt(((Integer) o).intValue(),(byte) 'e');
return;
}
if(o instanceof Long) {
buf.put((byte) 'i');
encodeLong(((Long) o).longValue(), 'e');
return;
}
if(o instanceof RawData) {
ByteBuffer raw = ((RawData) o).rawBuf;
buf.put(raw.duplicate());
return;
}
if(o instanceof StringWriter) {
StringWriter w = (StringWriter) o;
encodeInt(w.length(), (byte)':');
w.writeTo(buf);
return;
}
throw new RuntimeException("unknown object to encode " + o);
}
private void encodeList(List<Object> l) {
buf.put((byte) 'l');
l.forEach(this::encodeInternal);
buf.put((byte) 'e');
}
private void encodeString(String str) {
encodeInt(str.length(), (byte) ':');
str2buf(str, buf);
}
private void encodeMap(Map<String, Object> map) {
buf.put((byte) 'd');
Stream<Entry<String,Object>> str;
if(map instanceof SortedMap<?, ?> && ((SortedMap<?, ?>) map).comparator() == null)
str = map.entrySet().stream();
else
str = map.entrySet().stream().sorted(Map.Entry.comparingByKey());
str.forEachOrdered(e -> {
encodeString(e.getKey());
encodeInternal(e.getValue());
});
buf.put((byte) 'e');
}
private final static byte[] MIN_INT = str2buf(Integer.toString(Integer.MIN_VALUE)).array();
private void encodeInt(int val, byte terminator) {
if(val == Integer.MIN_VALUE)
buf.put(MIN_INT);
else {
if(val < 0) {
buf.put((byte) '-');
val = -val;
}
int numChars = 10;
int probe = 10;
for (int i=1; i<10; i++) {
if (val < probe) {
numChars = i;
break;
}
probe = 10*probe;
}
int pos = buf.position() + numChars;
buf.position(pos);
for(int i=1; i <= numChars; i++) {
int reduced = val / 10;
int remainder = val - (reduced * 10);
buf.put(pos - i, (byte) ('0' + remainder));
val = reduced;
}
}
buf.put(terminator);
}
private void encodeLong(long val, char terminator) {
str2buf(Long.toString(val), buf);
buf.put((byte) terminator);
}
}