package org.freertr.ifc;

import org.freertr.addr.addrMac;
import org.freertr.pack.packHolder;
import org.freertr.user.userFormat;
import org.freertr.util.bits;
import org.freertr.util.logger;

/**
 * packet loss detector protocol
 *
 * @author matecsaba
 */
public class ifcLossDet {

    /**
     * create instance
     */
    public ifcLossDet() {
    }

    /**
     * size of header
     */
    public final static int size = 10;

    /**
     * type of header
     */
    public final static int type = 0x80c0;

    /**
     * lost packets
     */
    public int packets;

    /**
     * time for block
     */
    public int blocking;

    private long blocked;

    private addrMac myaddr = addrMac.getRandom();

    private addrMac bcast = addrMac.getBroadcast();

    private int rxMine;

    private int txMine;

    private int rxRem;

    private int txRem;

    private int rxMineO;

    private int txMineO;

    private int rxRemO;

    private int txRemO;

    private int rxBlock;

    private int txBlock;

    private ifcEthTyp upper;

    public String toString() {
        if (packets < 1) {
            return "";
        }
        return packets + " " + blocking;
    }

    /**
     * initialize the engine
     *
     * @param eth ethertype to use
     */
    public void doInit(ifcEthTyp eth) {
        upper = eth;
        try {
            myaddr = (addrMac) eth.getHwAddr().copyBytes();
        } catch (Exception e) {
            myaddr = addrMac.getBroadcast();
        }
    }

    /**
     * get show
     *
     * @param l list
     */
    public void getShow(userFormat l) {
        l.add("local|" + txMine + "|" + rxMine);
        l.add("remote|" + txRem + "|" + rxRem);
        l.add("block|" + txBlock + "|" + rxBlock);
    }

    /**
     * encode one packet
     *
     * @param pck packet to encrypt
     * @return false on success, true on error
     */
    public synchronized boolean doEncode(packHolder pck) {
        pck.msbPutW(0, type);
        pck.msbPutD(2, rxMine);
        pck.msbPutD(6, txMine);
        pck.putSkip(size);
        pck.merge2beg();
        txMine++;
        return false;
    }

    /**
     * decode one packet
     *
     * @param pck packet to decrypt
     * @return false on success, true on error
     */
    public synchronized boolean doDecode(packHolder pck) {
        int typ = pck.msbGetW(0);
        if (typ != type) {
            logger.info("bad type (" + bits.toHexW(typ) + ")");
            return true;
        }
        if (pck.dataSize() < size) {
            return true;
        }
        rxMine++;
        rxRem = pck.msbGetD(2);
        txRem = pck.msbGetD(6);
        pck.getSkip(size);
        return blocked > 0;
    }

    /**
     * generate sync packet
     *
     * @return packet to send, null if nothing
     */
    public synchronized packHolder doSync() {
        packHolder pck = new packHolder(true, true);
        pck.msbPutW(0, type);
        pck.putSkip(2);
        pck.merge2beg();
        pck.ETHsrc.setAddr(myaddr);
        pck.ETHtrg.setAddr(bcast);
        doEncode(pck);
        long tim = bits.getTime();
        if (packets > 0) {
            int i = rxMine - rxMineO;
            int o = txRem - txRemO;
            if ((o - i) > packets) {
                logger.info("blocking " + upper + " because of rx loss");
                rxBlock++;
                blocked = tim;
            }
            i = rxRem - rxRemO;
            o = txMine - txMineO;
            if ((o - i) > packets) {
                logger.info("blocking " + upper + " because of tx loss");
                txBlock++;
                blocked = tim;
            }
        }
        if (blocked > 0) {
            if ((tim - blocked) > blocking) {
                logger.info("unblocking " + upper);
                blocked = 0;
            }
        }
        rxMineO = rxMine;
        txMineO = txMine;
        rxRemO = rxRem;
        txRemO = txRem;
        return pck;
    }

}
