package htsjdk.samtools.cram.compression;

import htsjdk.samtools.cram.compression.rans.RANSNx16Decode;
import htsjdk.samtools.cram.compression.rans.RANSNx16Encode;
import htsjdk.samtools.cram.compression.rans.RANSNx16Params;
import htsjdk.samtools.cram.structure.CRAMCodecModelContext;
import htsjdk.samtools.cram.structure.block.BlockCompressionMethod;
import java.util.Objects;

/**
 * CRAM external compressor that uses the rANS Nx16 entropy coder (CRAM 3.1).
 * Supports order-0, order-1, bit-packing, RLE, striping, and CAT modes via
 * flag combinations. Wraps shared {@link RANSNx16Encode} and {@link RANSNx16Decode}
 * instances to avoid repeated allocation of large internal tables.
 */
public final class RANSNx16ExternalCompressor extends ExternalCompressor {
    private final int flags;
    private final RANSNx16Encode ransEncode;
    private final RANSNx16Decode ransDecode;

    /**
     * We use a shared RANS instance for all compressors.
     *
     * @param ransEncode
     * @param ransDecode
     */
    public RANSNx16ExternalCompressor(final RANSNx16Encode ransEncode, final RANSNx16Decode ransDecode) {
        this(0, ransEncode, ransDecode); // order 0
    }

    /**
     * Create a rANS Nx16 compressor with the specified flag combination.
     *
     * @param flags bitmask of rANS Nx16 flags (order, pack, RLE, stripe, etc.)
     * @param ransEncode shared encoder instance
     * @param ransDecode shared decoder instance
     */
    public RANSNx16ExternalCompressor(
            final int flags, final RANSNx16Encode ransEncode, final RANSNx16Decode ransDecode) {
        super(BlockCompressionMethod.RANSNx16);
        this.ransEncode = ransEncode;
        this.ransDecode = ransDecode;
        this.flags = flags;
    }

    @Override
    public byte[] compress(final byte[] data, final CRAMCodecModelContext unused_contextModel) {
        return ransEncode.compress(data, new RANSNx16Params(flags));
    }

    @Override
    public byte[] uncompress(byte[] data) {
        return ransDecode.uncompress(data);
    }

    @Override
    public String toString() {
        return String.format("%s(%x)", this.getMethod(), flags);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        RANSNx16ExternalCompressor that = (RANSNx16ExternalCompressor) o;

        return this.flags == that.flags;
    }

    @Override
    public int hashCode() {
        return Objects.hash(getMethod(), flags);
    }
}
