/*
 * Copyright 2015 - Scriptel Corporation
 */
package com.scriptel.easyscript;

import java.util.ArrayList;
import java.util.List;

/**
 * This class is responsible for decoding an uncompressed EasyScript stream which
 * encompasses protocols A, B, and C.
 */
public class EasyScriptUncompressedDecoder implements EasyScriptDecoder {
    /**
     * The maximum number that it is possible to encode using the numbering
     * scheme used by the ScripTouch EasyScript devices.
     */
    private static final int MAX_NUM = (21 * 23) + 22;
    /**
     * The protocol by which to decode incoming signature strings.
     */
    private final SignatureProtocol signatureProtocol;
    /**
     * This is the position of the character in the signature.
     */
    private int position;
    /**
     * A list of Coordinate Receiver listeners.
    */
    private List<EasyScriptEventListener> coordinateReceiverListeners; 
    /**
     * This is s string builder that contains the characters of the incoming signature. 
     */
    private final StringBuilder signatureBuilder = new StringBuilder(4);
    /**
     * Used to decode keyboard codes.
     */
    private final KeyboardDecoder keyboardDecoder;
    /**
     * Determines if the next coordinate will generate a new stroke.
     */
    private boolean newStroke;
    
    /**
     * The constructor.
     * @param signatureProtocol The signature protocol that is used (STNSignatureProtocol).
     * @param position Position in the string.
     * @param listOfCoordinateReceiverListener A list of the CoordinateReceiver listeners.
     */
    public EasyScriptUncompressedDecoder(SignatureProtocol signatureProtocol, int position, List<EasyScriptEventListener> listOfCoordinateReceiverListener) {
        coordinateReceiverListeners = listOfCoordinateReceiverListener;
        this.signatureProtocol = signatureProtocol;
        newStroke = true;
        signatureBuilder.setLength(0);
        if (coordinateReceiverListeners == null) {
            coordinateReceiverListeners = new ArrayList<EasyScriptEventListener>();
        }
        this.position = position;
        keyboardDecoder = new KeyboardDecoder(signatureProtocol.getXValues(), signatureProtocol.getYValues());
    }
    
    /**
     * This method attempts to take a four character point and decode it using
     * the protocol's value tables.
     * @param ePoint Array of four characters from the signature array to decode.
     * @return Coordinate representing the decoded point in percentage of the screen (0-&gt;1).
     * @throws SignatureInvalidException Thrown in the event there was a problem reading the point.
     */
    protected Coordinate decodePoint(char[] ePoint) throws SignatureInvalidException {
        if (ePoint.length != 4) {
            throw new SignatureInvalidException("Didn't find exactly 4 characters", position);
        }
        int[] values;
        try {
            values = keyboardDecoder.getValues(ePoint);
        } catch (SignatureInvalidException ex) {
            SignatureInvalidException rethrow = new SignatureInvalidException(ex.getMessage(), position);
            rethrow.initCause(ex);
            throw rethrow;
        }
        
        double x = ((double)(values[0]) / MAX_NUM) * signatureProtocol.getWidth();
        double y = ((double)(values[1]) / MAX_NUM) * signatureProtocol.getHeight();
        
        return new Coordinate(x,y);
    }
    
    /**
     * Gets the current signature protocol.
     * @return the signatureProtocol
     */
    public SignatureProtocol getSignatureProtocol() {
        return signatureProtocol;
    }
    
    /**
     * This method parses the signature character by character to decode it. 
     * @param ch A char read from the signature.
     * @throws SignatureInvalidException Thrown in the event of a problem while parsing the signature.
     */
    @Override
    public void parseSignature(char ch) throws SignatureInvalidException {
        position++;
        if(ch == signatureProtocol.getPenUp()) {
            newStroke = true;
            if(signatureBuilder.length() != 0) {
                throw new SignatureInvalidException("Buffer wasn't empty when a new stroke was found.", position);
            }
        } else if(ch == signatureProtocol.getEndStream()) {
            for(EasyScriptEventListener r : coordinateReceiverListeners) {
                r.endOfSignature();
            }
        } else if(ch == signatureProtocol.getCancel()) {
            for(EasyScriptEventListener r : coordinateReceiverListeners) {
                r.cancel();
            }
        } else {
            signatureBuilder.append(ch);
            if(signatureBuilder.length() == 4) {
                Coordinate c = decodePoint(new char[] { signatureBuilder.charAt(0), signatureBuilder.charAt(1), signatureBuilder.charAt(2), signatureBuilder.charAt(3) });
                signatureBuilder.setLength(0);
                if(newStroke) {
                    for(EasyScriptEventListener r : coordinateReceiverListeners) {
                        r.newStroke();
                    }
                    newStroke = false;
                }
                
                for(EasyScriptEventListener r : coordinateReceiverListeners) {
                    r.receiveCoordinate(c);
                }
            }
        }
    }
}
