package peergos.shared.io.ipfs.cid;

import peergos.shared.io.ipfs.multibase.*;
import peergos.shared.io.ipfs.multihash.*;
import peergos.shared.util.*;

import java.io.*;
import java.util.*;

public class Cid extends Multihash {

    public static final class CidEncodingException extends RuntimeException {

        public CidEncodingException(String message) {
            super(message);
        }
    }

    public enum Codec {
        Raw(0x55),
        DagProtobuf(0x70),
        DagCbor(0x71),
        EthereumBlock(0x90),
        EthereumTx(0x91),
        BitcoinBlock(0xb0),
        BitcoinTx(0xb1),
        ZcashBlock(0xc0),
        ZcashTx(0xc1);

        public long type;

        Codec(long type) {
            this.type = type;
        }

        private static Map<Long, Codec> lookup = new TreeMap<>();
        static {
            for (Codec c: Codec.values())
                lookup.put(c.type, c);
        }

        public static Codec lookup(long c) {
            if (!lookup.containsKey(c))
                throw new IllegalStateException("Unknown Codec type: " + c);
            return lookup.get(c);
        }
    }

    public final long version;
    public final Codec codec;

    public Cid(long version, Codec codec, Multihash hash) {
        super(hash);
        this.version = version;
        this.codec = codec;
    }

    public Cid(long version, Codec codec, Multihash.Type type, byte[] hash) {
        super(type, hash);
        this.version = version;
        this.codec = codec;
    }

    public Cid(Multihash h) {
        this(0, Codec.DagProtobuf, h);
    }

    private byte[] toBytesV0() {
        return super.toBytes();
    }

    private static final int MAX_VARINT_LEN64 = 10;

    private byte[] toBytesV1() {
        byte[] hashBytes = super.toBytes();
        byte[] res = new byte[2 * MAX_VARINT_LEN64 + hashBytes.length];
        int index = putUvarint(res, 0, version);
        index = putUvarint(res, index, codec.type);
        System.arraycopy(hashBytes, 0, res, index, hashBytes.length);
        return Arrays.copyOfRange(res, 0, index + hashBytes.length);
    }

    @Override
    public byte[] toBytes() {
        if (version == 0)
            return toBytesV0();
        else if (version == 1)
            return toBytesV1();
        throw new IllegalStateException("Unknown cid version: " + version);
    }

    @Override
    public String toString() {
        if (version == 0) {
            return super.toString();
        } else if (version == 1) {
            return Multibase.encode(Multibase.Base.Base58BTC, toBytesV1());
        }
        throw new IllegalStateException("Unknown Cid version: " + version);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (! (o instanceof Multihash)) return false;
        if (!super.equals(o)) return false;

        if (o instanceof Cid) {
            Cid cid = (Cid) o;

            if (version != cid.version) return false;
            return codec == cid.codec;
        }
        // o must be a Multihash
        return version == 0 && super.equals(o);
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        if (version == 0)
            return result;
        result = 31 * result + (int) (version ^ (version >>> 32));
        result = 31 * result + (codec != null ? codec.hashCode() : 0);
        return result;
    }

    public static Cid buildCidV0(Multihash h) {
        return new Cid(h);
    }

    public static Cid buildCidV1(Codec c, Multihash.Type type, byte[] hash) {
        return new Cid(1, c, type, hash);
    }

    public static Cid decode(String v) {
        if (v.length() < 2)
            throw new IllegalStateException("Cid too short!");

        // support legacy format
        if (v.length() == 46 && v.startsWith("Qm"))
            return buildCidV0(Multihash.fromBase58(v));

        byte[] data = Multibase.decode(v);
        return cast(data);
    }

    public static Cid cast(byte[] data) {
        if (data.length == 34 && data[0] == 0x18 && data[1] == 32)
            return buildCidV0(new Multihash(data));

        InputStream in = new ByteArrayInputStream(data);
        try {
            long version = readVarint(in);
            if (version != 0 && version != 1)
                throw new CidEncodingException("Invalid Cif version number: " + version);

            long codec = readVarint(in);
            if (version != 0 && version != 1)
                throw new CidEncodingException("Invalid Cif version number: " + version);

            Multihash hash = Multihash.deserialize(new DataInputStream(in));

            return new Cid(version, Codec.lookup(codec), hash);
        } catch (Exception e) {
            throw new CidEncodingException("Invalid cid bytes: " + ArrayOps.bytesToHex(data));
        }
    }

    private static long readVarint(InputStream in) throws IOException {
        long x = 0;
        int s=0;
        for (int i=0; i < 10; i++) {
            int b = in.read();
            if (b == -1)
                throw new EOFException();
            if (b < 0x80) {
                if (i > 9 || i == 9 && b > 1) {
                    throw new IllegalStateException("Overflow reading varint" +(-(i + 1)));
                }
                return x | (((long)b) << s);
            }
            x |= ((long)b & 0x7f) << s;
            s += 7;
        }
        throw new IllegalStateException("Varint too long!");
    }

    private static int putUvarint(byte[] buf, int index, long x) {
        while (x >= 0x80) {
            buf[index] = (byte)(x | 0x80);
            x >>= 7;
            index++;
        }
        buf[index] = (byte)x;
        return index + 1;
    }
}

@@ -4068,18 +4068,16 @@
 a%5B0%5D == 
-0x
 18 && da

Fix to legacy support in Cid