package net.freertr.pack;

import net.freertr.pipe.pipeSide;
import net.freertr.util.bits;

/**
 * x25 over tcp (rfc1613) packet
 *
 * @author matecsaba
 */
public class packXotPad {

    /**
     * port number
     */
    public static final int port = 1998;

    /**
     * max data size
     */
    public static final int datMax = 128;

    private final pipeSide conn;

    private int lci;

    private int seqTx;

    private int seqRx;

    private boolean canTx;

    /**
     * create instance
     *
     * @param pipe pipeline to use
     */
    public packXotPad(pipeSide pipe) {
        conn = pipe;
    }

    /**
     * receive one packet
     *
     * @return packet, null if error
     */
    public packHolder recvPack() {
        byte[] buf = new byte[4];
        if (conn.moreGet(buf, 0, buf.length) != buf.length) {
            return null;
        }
        if (bits.msbGetW(buf, 0) != 0) { // version
            return null;
        }
        int len = bits.msbGetW(buf, 2);
        packHolder pck = new packHolder(true, true);
        if (pck.pipeRecv(conn, 0, len, 144) != len) {
            return null;
        }
        return pck;
    }

    /**
     * send one packet
     *
     * @param pck packet
     */
    public void sendPack(packHolder pck) {
        pck.merge2end();
        pck.msbPutW(0, 0); // version
        pck.msbPutW(2, pck.dataSize());
        pck.putSkip(4);
        pck.merge2beg();
        pck.pipeSend(conn, 0, pck.dataSize(), 2);
    }

    /**
     * close connection
     */
    public void setClose() {
        conn.setClose();
        canTx = true;
    }

    /**
     * parse call request
     *
     * @param pck packet to read
     * @return true on error, false on success
     */
    public boolean parseCallReq(packHolder pck) {
        if (pck == null) {
            return true;
        }
        if (pck.getByte(0) != 0x10) { // info
            return true;
        }
        lci = pck.getByte(1);
        if (pck.getByte(2) != 0x0b) { // call request
            return true;
        }
        return false;
    }

    /**
     * create call request
     *
     * @param called called number
     * @param calling calling number
     * @return packet
     */
    public packHolder createCallReq(String called, String calling) {
        lci = bits.randomB();
        packHolder pck = new packHolder(true, true);
        pck.putByte(0, 0x10); // info
        pck.putByte(1, lci);
        pck.putByte(2, 0x0b); // call request
        pck.putByte(3, called.length() | (calling.length() << 4));
        pck.putSkip(4);
        called = called + calling;
        if ((called.length() & 1) != 0) {
            called += "0";
        }
        for (; called.length() > 0;) {
            calling = called.substring(0, 2);
            called = called.substring(2, called.length());
            pck.putByte(0, bits.fromHex(calling));
            pck.putSkip(1);
        }
        pck.putByte(0, 0x00); // facility length
        pck.putSkip(1);
        return pck;
    }

    /**
     * parse call accept
     *
     * @param pck packet
     * @return true on error, false on success
     */
    public boolean parseCallAcc(packHolder pck) {
        if (pck == null) {
            return true;
        }
        if (pck.getByte(0) != 0x10) { // info
            return true;
        }
        if (lci != pck.getByte(1)) {
            return true;
        }
        if (pck.getByte(2) != 0x0f) { // call accept
            return true;
        }
        canTx = true;
        return false;
    }

    /**
     * create call accept
     *
     * @return packet
     */
    public packHolder createCallAcc() {
        packHolder pck = new packHolder(true, true);
        pck.putByte(0, 0x10); // info
        pck.putByte(1, lci);
        pck.putByte(2, 0x0f); // call connected
        pck.putByte(3, 0x00); // address length
        pck.putByte(4, 0x00); // facility length
        pck.putSkip(5);
        canTx = true;
        return pck;
    }

    /**
     * parse data packet
     *
     * @param pck packet
     * @return null on error, bytes received
     */
    public byte[] parseData(packHolder pck) {
        if (pck == null) {
            return null;
        }
        if (pck.getByte(0) != 0x10) { // info
            return null;
        }
        if (lci != pck.getByte(1)) {
            return null;
        }
        int typ = pck.getByte(2);
        if ((typ & 0x1f) == 1) { // receiver ready
            canTx = true;
            return new byte[0];
        }
        if ((typ & 1) != 0) { // data
            return new byte[0];
        }
        pck.getSkip(3);
        byte[] res = pck.getCopy();
        pck.clear();
        pck.putByte(0, 0x10); // info
        pck.putByte(1, lci);
        pck.putByte(2, (typ << 4) | 1); // receiver ready
        pck.putSkip(3);
        sendPack(pck);
        seqRx = (typ >>> 1) & 7;
        return res;
    }

    /**
     * create data packet
     *
     * @param buf bytes to send
     * @return packet
     */
    public packHolder createData(byte[] buf) {
        packHolder pck = new packHolder(true, true);
        pck.putByte(0, 0x10); // info
        pck.putByte(1, lci);
        pck.putByte(2, (seqTx << 1) | (seqRx << 7)); // data
        pck.putSkip(3);
        pck.putCopy(buf, 0, 0, buf.length);
        pck.putSkip(buf.length);
        seqTx = (seqTx + 1) & 7;
        canTx = false;
        return pck;
    }

    /**
     * do receive work
     *
     * @param psx pipe side
     */
    public void doerRx(pipeSide psx) {
        for (;;) {
            byte[] buf = parseData(recvPack());
            if (buf == null) {
                return;
            }
            psx.morePut(buf, 0, buf.length);
        }
    }

    /**
     * do transmit work
     *
     * @param psx pipe side
     */
    public void doerTx(pipeSide psx) {
        for (;;) {
            if (!canTx) {
                bits.sleep(100);
                continue;
            }
            int len = psx.ready2rx();
            if (len > 128) {
                len = 128;
            }
            if (len < 1) {
                len = 1;
            }
            byte[] buf = new byte[len];
            if (psx.moreGet(buf, 0, buf.length) != buf.length) {
                return;
            }
            sendPack(createData(buf));
        }
    }

}
