package com.tecspy.protocol;

import java.io.IOException;

import org.apache.log4j.Logger;

/**
 * Is fed with bytes from an input stream and issues complete messages. The
 * messages are bounded by a single start byte, "stx", and a single end byte,
 * "etx".
 *
 * This class is coded to cope with the special case where stx and etx are the
 * same. Bytes that are received "outside" the message stx and etx are
 * considered to be erroneous and must be handled by the implementation.
 *
 * Test byte streams:
 *
 * normal : stx data etx | stx data etx | ...
 *
 * restart: stx data stx data etx - report discarded bytes
 *
 * quickrestart: stx stx data
 *
 * when stx == etx then this is ignored
 *
 * oob data: stx data etx data - report oob data
 *
 * random: ??????? - not a real test but may stress test
 *
 * join/part stream midway - quite possible at start of stream: data etx stx
 * data etx - special handling?
 *
 * tests for maximum message length being exceeded
 *
 * @author Michael Erskine
 *
 */
public abstract class StxEtxMessageByteSink implements ByteSink {
    /** Standard log4j logger */
    protected static Logger log = Logger.getLogger(StxEtxMessageByteSink.class);

    /**
    * Currently forming message buffer - should be constructed to the expected
    * size of typical messages rather than the maximum
    */
    protected StringBuilder msg;

    /** Message start byte */
    protected byte stx = (byte) 0x02;

    /** Message end byte */
    protected byte etx = (byte) 0x03;

    /** Maximum message length */
    protected int maxmsg = 1000;

    /**
    * State of read process: false = waiting for stx, true = waiting for data
    * or etx.
    */
    private boolean inmsg = false;

    /**
    * An indication of the number of bytes read. NB: not necessarily 100%
    * correct.
    */
    public long byteTotal = 0;

    protected byte lastChar;

    /**
    * A subclass constructor must create the message buffer e.g.: -
    *
    * <code>
    * super();
    * stx = (byte) 0xFE;
    * etx = (byte) 0xFE;
    * maxmsg = 1000;
    * msg = new StringBuilder(1000);
    * </code>
    *
    */
    public StxEtxMessageByteSink() {
    }

    protected abstract void reportUnexpectedRestart();

    protected abstract void reportUnexpectedEtx();

    protected abstract void reportUnexpectedData(byte b);

    protected abstract void handleMsg();

    public int processBytes(byte[] buf, int len) {
        // incrementing the byte total here rather than for each byte
        byteTotal += len;
        for (int i = 0; i < len; i++) { // foreach byte
            if (!inmsg) { // !inmsg == waiting for stx
                // we are waiting for stx to start the message
                if (buf[i] == stx) {
                    // stx is the normal condition
                    inmsg = true;
                } else if (buf[i] == etx) {
                    // we weren't expecting etx
                    // in the case where etx == stx, the byte will have been
                    // handled above
                    reportUnexpectedEtx();
                } else {
                    // we got unexpected data whilst waiting for stx
                    reportUnexpectedData(buf[i]);
                }
            } else { // inmsg == true
                // we are waiting for data or etx
                if (buf[i] == etx) {
                    // etx is a normal condition - flush
                    handleMsg();
                    msg.setLength(0);
                    inmsg = false;
                } else if (buf[i] == stx) {
                    // stx here is a message restart - report and reset
                    // in the case where etx == stx, the byte will have been
                    // handled above
                    reportUnexpectedRestart();
                    msg.setLength(0);
                } else {
                    // normal condition add byte to message
                    char c = (char) buf[i];
                    if (msg.length() >= maxmsg) {
                        log.error("maximum message length exceeded (" + maxmsg
                                + "): buffered data discarded");
                        msg.setLength(0);
                    }
                    msg.append(c);
                }
            }
            lastChar = buf[i];
        } // end of foreach byte
        return len;
    }

    public abstract void processEof();

    public abstract void processException(IOException e);
}