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

import java.util.HashMap;
import java.util.Map;

/**
 * A utility for quickly searching for keyboard values.
 */
public class KeyboardDecoder {
    /**
     * Indicates this type is on the X-axis
     */
    private static final int X = 1;
    /**
     * Indicates this type is on the Y-axis
     */
    private static final int Y = 2;
    /**
     * Indicates this type is a most significant digit.
     */
    private static final int MSD = 4;
    /**
     * Indicates this type is a least significant digit.
     */
    private static final int LSD = 8;
    /**
     * Map of keyboard protocol values indexed on character values.
     */
    private final Map<Integer, KeyboardValue> values;
    
    /**
     * Small helper class used to store both the value and type of a keyboard character
     * in the values map.
     */
    private class KeyboardValue {
        /**
         * Value of the keyboard character.
         */
        private final int value;
        /**
         * Type of the keyboard character, (X or Y) | (MSD or LSD).
         */
        private final int type;
        
        /**
         * Constructor, creates a new instance of this class.
         * @param value Value of the keyboard character.
         * @param type Type of the keyboard character, (X or Y) | (MSD or LSD).
         */
        public KeyboardValue(int value, int type) {
            this.value = value;
            this.type = type;
        }
        
        /**
         * Gets the value of the keyboard character.
         * @return 
         */
        public int getValue() {
            return value;
        }
        
        /**
         * Gets the type of the keyboard character, (X or Y) | (MSD or LSD).
         * @return 
         */
        public int getType() {
            return type;
        }
    }
    
    /**
     * Constructor which remembers the keyboard tables for later search.
     * @param xValues A two dimensional table of keyboard values. [0] contains the MSDs, [1] the LSDs
     * @param yValues A two dimensional table of keyboard values. [0] contains the MSDs, [1] the LSDs
     */
    public KeyboardDecoder(byte[][] xValues, byte[][] yValues) {
        values = new HashMap<Integer, KeyboardValue>();
        for (int i = 0; i < xValues[0].length; i++) {
            values.put((int) xValues[0][i], new KeyboardValue(i * 23, MSD | X));
            values.put((int) yValues[0][i], new KeyboardValue(i * 23, MSD | Y));
        }

        for (int i = 0; i < xValues[1].length; i++) {
            values.put((int) xValues[1][i], new KeyboardValue(i, LSD | X));
            values.put((int) yValues[1][i], new KeyboardValue(i, LSD | Y));
        }
    }
   
    
    
    /**
     * Searches the tables for a value represented by the character.
     * @param c Character to get the value for.
     * @return The value of the character.
     * @throws SignatureInvalidException if the value is not found.
     */
    public int getValue(char c) throws SignatureInvalidException {
        KeyboardValue value = values.get((int)c);
        if (value == null) {
            throw new SignatureInvalidException("Unable to map character '"+c+"' to a value in the signature protocol table.", 0);
        }
        return value.getValue();
    }
    
    /**
     * This method takes four characters and attempts to decode them into
     * an x and y coordinate component used with the uncompressed EasyScript
     * protocol.
     * @param chrs Character array to decode.
     * @return An array with two elements, the first element being the x-axis component, the second being the y-axis component.
     * @throws SignatureInvalidException If any of the characters aren't found are aren't of the expected type.
     */
    public int[] getValues(char[] chrs) throws SignatureInvalidException {
        if(chrs.length != 4) {
            throw new IllegalArgumentException("Character array length was not what was expected (4): "+chrs.length);
        }
        
        int[] retr = new int[2];
        
        boolean hasXMSD, hasXLSD, hasYMSD, hasYLSD;
        hasXMSD = hasXLSD = hasYMSD = hasYLSD = false;
        
        //Ensure all characters map to the signature protocol table.
        for(int i = 0; i < chrs.length; i++) {
            KeyboardValue val = values.get((int)chrs[i]);
            if(val == null) {
                throw new SignatureInvalidException("Unable to map character '"+chrs[i]+"' to a value in the signature protocol table.", 0);
            }
            
            int type = val.getType();
            switch(type) {
                case X | MSD:
                    retr[0] += val.getValue();
                    hasXMSD = true;
                    break;
                case X | LSD:
                    retr[0] += val.getValue();
                    hasXLSD = true;
                    break;
                case Y | MSD:
                    retr[1] += val.getValue();
                    hasYMSD = true;
                    break;
                case Y | LSD:
                    retr[1] += val.getValue();
                    hasYLSD = true;
                    break;
                default:
                    throw new SignatureInvalidException("Detected strange type: "+getTypeName(type), 0);
            }
        }
        
        if(!(hasXMSD && hasXLSD && hasYMSD && hasYLSD)) {
            throw new SignatureInvalidException("Missing required coordinate component.", 0);
        }
        
        return retr;
    }
    
    /**
     * This method turns a type into a human-readable string.
     * @param type Type to convert.
     * @return Human readable string explaining the type. 
     */
    private String getTypeName(int type) {
        String retr = ((type & MSD) > 0) ? "Most significant digit" : "Least significant digit";
        retr += " of the coordinate on the ";
        retr += ((type & X) > 0) ? "X-axis" : "Y-axis";
        return retr;
    }
}
