/**
 * @author Daniel Wigdor
 *
 * updated 2003 07 10
 * All code by DANIEL WIGDOR
 * Dynamic Graphics Project, University of Toronto
 * dwigdor@dgp.toronto.edu
 * ALL RIGHTS RESERVED
 *
 * The TiltSensorInterface is used to get tilt values from an ADXL202EB-232 board.
 * The 202EB is a 2-axis tilt sensor, so all "tilts" described herein are for
 * both "X" and "Y" orientations.  A "tilt" is always stored as absolute tilt
 * magnitude of x and y in an array of ints.  int[0] = x tilt, int[1] = y tilt.
 * 
 * Tilt direction is always a single value: DEVICE_{FORWARD,BACK,LEFT,RIGHT}.  
 * DEVICE_{LEFT,RIGHT} are tilts in the X axis, DEVICE_{FORWARD,BACK} are tilts
 * in the Y axis.
 * 
 * It stores a "level", which can be the origin against all subsequent tilt measures
 * are compared to get tilt direction.
 */


import javax.microedition.io.*;
import java.io.OutputStream;
import java.io.InputStream;


class TiltSensorInterface {

    // there is a bias in user wrists that causes them to tilt a little to the dominant
    // side when entering text
    final static int Y_WINDOW_SIZE_BOOST = 0;

    // these are used when using the tilt to pick which of the available
    // characters on the button the user wanted to press.  These are set
    // so that they can be used as an index to the array of characters
    // available
    public final static int DEVICE_LEFT = 0;
    public final static int DEVICE_FORWARD = 1;
    public final static int DEVICE_RIGHT = 2;
    public final static int DEVICE_BACK = 3;

    // The string that is used to open the phone's comm port to communicate with
    // the tilt sensort.  Will be passed as an argument to the Connector's open method.
    // This is unlikely to change unless the tilt sensor changes
    private final static String TILT_INIT =
        "comm:0;baudrate=38400;stopbits=1;parity=n;databits=8;";

    // the stream connector that will be used to communicate via the comm port
    // to the tilt sensor.
    StreamConnection sc;

    // The streams for output to & input from the tilt sensor.
    OutputStream os;
    InputStream is;

    // Stores the last tilt position of the phone.  Tilt is measured
    // relative to this value if the client calls methods without a "level"
    // argument.
    private int[] level = { 0, 0 };

    /**
     * Method TiltSensorInterface.  Initializes communication with the tilt
     * sensor, and sets the level to the current orientation.
     */
    public TiltSensorInterface() {

        // open the streams for communication with the 
        // sensor

        try {
            // open the stream connector to the comm port
            sc =
                (StreamConnection) Connector.open(
                    TILT_INIT,
                    Connector.READ_WRITE,
                    false);

            // attach an output stream to the adxl board
            os = sc.openOutputStream();

            // attach an input stream to the adxl board
            is = sc.openInputStream();

        } catch (Exception e) {

        }
        // set the level of the tilt sensor to the present tilt
        this.setLevel();

    }

    /**
     *  closes the connection with the tilt sensor 
     */
    public void close() {
        try {
            os.close();
            is.close();
        } catch (Exception e) { /*shit*/
        }
    }

    /**
     * Returns the level.
     * @return int[]
     */
    public int[] getLevel() {
        return level;
    }

    /**
     * Method getCurrentTilt.  Gets the current tilt values in the X and Y.
     * int[0] = X, int[1] = Y
     * @return int[]
     */
    public int[] getCurrentTilt() {

        byte[] currentTilt = new byte[4];
        int bytesRead = 0;
        for (int i = 0; i < 25 && bytesRead < 4; i++) {

            try {

                // Send the 'G' character down the pipe to the sensor - it will
                // reply with the current tilt.   Cast to int as req'd param type
                // to the write method.   
                os.write((int) 'G');
            } catch (Exception e) {
                //textOutput+="error 188";
            }

            // Read the 4 bytes of data that is the tilt information

            try {
                bytesRead = is.read(currentTilt);
            } catch (Exception e) {
                //textOutput+="error 197";
            }
        }


        /* formula for converting 4 bytes of input into a tilt:
         * (this comes from documentation of the tilt sensor board)
         *
         * tiltX percentage = (256 * byte1 + byte2) / 100
         * tiltY percentage = (256 * byte3 + byte4) / 100
         */

        // this will store the tilt values:      
        int[] outputArray = new int[2];
        outputArray[0] =
            (256 * unsignedByteToInt(currentTilt[0])
                + (unsignedByteToInt(currentTilt[1])))
                / 100;
        outputArray[1] =
            (256 * unsignedByteToInt(currentTilt[2])
                + (unsignedByteToInt(currentTilt[3])))
                / 100;

        return outputArray;
    }
	
    /**
     * Method setLevel.  Sets the level of the tilt sensor
     * @param level: the level to set the tilt sensor to.
     */
    public void setLevel(int[] level) {
        this.level = level;
    }

    /**
     * Method setLevel.  Sets the level of the tilt sensor to the current
     * tilt levels.
     */
    public void setLevel() {
        setLevel(this.getCurrentTilt());
    }


    /**
     * Method tiltMagnitude.  Returns the magnitude of the larger tilt
     * @param start: the beginning orientation
     * @param end: the ending orientation
     * @return int: the magnitude of the larger tilt.
     */
	public static int tiltMagnitude(int[] start, int[] end) {

        // calculate how far x has tilted
        int xTilt = end[0] - start[0];

        // calculate how far y has tilted
        int yTilt = end[1] - start[1];

        if ((Math.abs(xTilt) > Math.abs(yTilt) + Y_WINDOW_SIZE_BOOST)) {
            return Math.abs(xTilt);
        } else {
            return Math.abs(yTilt) + Y_WINDOW_SIZE_BOOST;
        }
    }

    /**
     * Method tiltDirection.  Returns the direction of highest magnitude tilt
     * relative to the level (one of DEVICE_{FORWARD,BACK,LEFT,RIGHT})
     * @param current: the current tilt
     * @param ignoreBack: whether DEVICE_BACK should be ignored
     * @return int: the direction of the greater tilt
     */
    public int tiltDirection(int[] current, boolean ignoreBack) {
        return tiltDirection(level, current, ignoreBack);
    }

    /**
     * Method tiltDirection.  Returns the direction of the highest magnitude tilt
     * relative to the start (one of DEVICE_{FORWARD,BACK,LEFT,RIGHT})
     * @param start: the beginning orientation
     * @param end: the ending orientation
     * @return int: the direction of the tilt
     */
    // public face to get tilt direction
    public static int tiltDirection(int[] start, int[] end) {
        return tiltDirection(start, end, false);
    }

    /**
     * Method tiltDirection.  Returns the direction of the highest magnitude tilt
     * relative to the start (one of DEVICE_{FORWARD,BACK,LEFT,RIGHT})
     * @param start: the beginning orientation
     * @param end: the ending orientation
     * @param ignoreBack: whether to consider DEVICE_BACK
     * @return int: the direction of the tilt
     */
    public static int tiltDirection(int[] start, int[] end, boolean ignoreBack) {
        // calculate how far x has tilted
        int xTilt = end[0] - start[0];

        // calculate how far y has tilted
        int yTilt = end[1] - start[1];

        // used to lookup the tilt in the string for that
        // button
        int tiltIndex;

        // Determine on which axis the tilt was most:

        // if the xtilt is greater (in either direction), or yTilt was greater
        // but there is no character in that direction...
        if ((Math.abs(xTilt) > Math.abs(yTilt) + Y_WINDOW_SIZE_BOOST)
            || (yTilt < 0 && ignoreBack)) {

            // assert: tilted either left or right
            if (xTilt > 0) { // tilted left
                tiltIndex = DEVICE_LEFT;
            } else { // tilted right
                tiltIndex = DEVICE_RIGHT;
            }
        } else { // tilted either forward or back
            if (yTilt > 0) { //tilted forward
                tiltIndex = DEVICE_FORWARD;
            } else { // tilted back
                tiltIndex = DEVICE_BACK;
            }
        }
        return tiltIndex;
    }

    /* 
     * converts an usigned byte to an int
     */
    private static int unsignedByteToInt(byte b) {
        return (int) b & 0xff;
    }

}
