package net.freertr.cry;

import net.freertr.util.bits;

/**
 * cyclic redundancy check
 *
 * @author matecsaba
 */
public class cryHashCrc16 extends cryHashGeneric {

    /**
     * crc16ccitt (rfc1662) polynominal: 16bits x**0 + x**5 + x**12 + x**16
     */
    public final static cryHashCrc16 polyCrc16c = new cryHashCrc16(0x1021, 0xffff, 0xffff, false);

    private final int[] tab;

    private final boolean ord;

    private final int ini;

    private final int xor;

    private int crc;

    /**
     * create instance
     *
     * @param po polynominal
     * @param in initializer
     * @param xr xorer
     * @param or byte order, true=msb, false=lsb
     */
    public cryHashCrc16(int po, int in, int xr, boolean or) {
        ord = or;
        ini = in;
        xor = xr;
        tab = new int[256];
        for (int i = 0; i < tab.length; i++) {
            tab[i] = mkTabEntry(po, i);
        }
    }

    /**
     * create instance
     *
     * @param o where to clone from
     */
    public cryHashCrc16(cryHashCrc16 o) {
        tab = o.tab;
        ord = o.ord;
        ini = o.ini;
        xor = o.xor;
    }

    /**
     * reverse bits
     *
     * @param b value
     * @return reversed
     */
    public static int reverse(int b) {
        int r = 0;
        for (int i = 0; i < 16; i++) {
            if ((b & 1) != 0) {
                r |= 1 << (15 - i);
            }
            b >>>= 1;
        }
        return r;
    }

    private int mkTabEntry(int p, int v) {
        v <<= 8;
        for (int i = 0; i < 8; ++i) {
            v <<= 1;
            if ((v & 0x10000) != 0) {
                v ^= p;
            }
        }
        return v & 0xffff;
    }

    /**
     * initialize
     */
    public void init() {
        crc = ini;
    }

    /**
     * set frame checksum
     *
     * @param i new value
     */
    public void setCrc(int i) {
        crc = i;
    }

    /**
     * get frame checksum
     *
     * @return value
     */
    public int getCrc() {
        return crc;
    }

    /**
     * get name
     *
     * @return name
     */
    public String getName() {
        return "crc16";
    }

    /**
     * read oid of hash
     *
     * @return name of hash
     */
    public byte[] getPkcs() {
        return null;
    }

    /**
     * get hash size
     *
     * @return size
     */
    public int getHashSize() {
        return 2;
    }

    /**
     * get block size
     *
     * @return size
     */
    public int getBlockSize() {
        return 1;
    }

    private void updateByte(int i) {
        crc = ((crc << 8) & 0xffff) ^ tab[(crc >>> 8) ^ (i & 0xff)];
    }

    /**
     * compute block
     *
     * @param buf buffer
     * @param ofs offset
     * @param siz size
     */
    public void update(byte[] buf, int ofs, int siz) {
        for (int i = 0; i < siz; i++) {
            updateByte(buf[ofs + i]);
        }
    }

    /**
     * finish
     *
     * @return computed
     */
    public byte[] finish() {
        byte[] buf = new byte[2];
        if (ord) {
            bits.msbPutW(buf, 0, crc ^ xor);
        } else {
            bits.lsbPutW(buf, 0, crc ^ xor);
        }
        return buf;
    }

}
