/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

package com.nokia.mid.ui;

import org.eclipse.swt.widgets.Internal_PackageSupport;
import com.nokia.mid.ui.internal.OS;

;

/**
 * This class is a Java extension to support tactile feedback (audio, vibra,
 * piezo, etc.) on touch-enabled devices. Tactile feedback is meant to give user
 * more clear feeling of touch events, like tapping and dragging. The actual
 * physical tactile feedback depends on the device tactile feedback
 * implementation, hardware and device settings. A device may implement the
 * feedback with different hardware methods, for example, vibra, audio or piezo
 * hardware.
 * <p>
 * The API supports both LCDUI and eSWT UI toolkits.
 * <p>
 * Using this class is safe when a device does not support tactile feedback. In
 * this case methods of this class can be called but this has no effect.
 * <p>
 * Tactile feedback can be generated in two ways:
 * <ul>
 * <li>By producing direct feedback from the application.</li>
 * <li>By adding feedback areas to area registry, in which case the feedback
 * will be produced by the tactile feedback system automatically when the
 * defined screen area (with defined feedback) is touched.</li>
 * </ul>
 * <p>
 * Direct feedback is given instantly with {@link #directFeedback
 * directFeedback} specifying the feedback type. Direct feedback can be used
 * when the user action is not just a tap in a certain area. For example if the
 * user drags from one menu item to the other the application may give direct
 * feedback to emphasize focus traverse.
 * <p>
 * In case of area registry for eSWT Control, the registry needs to be updated
 * every time when the size or position of a UI component changes, or when e.g.
 * a state of a UI component changes in such way that feedback type also
 * changes. Typically the area update may be implemented in
 * <code>controlMoved</code> and <code>controlResized</code> methods of
 * <code>ControlListener</code> interface.
 * <p>
 * In case of area registry for LCDUI Canvas or CustomItem no further actions
 * are required to update tactile feedback areas when a CustomItem is moved in a
 * Form or when a Canvas is changed due to e.g. orientation switch.
 * <p>
 * There are quite tight latency requirements for tactile feedback, and in most
 * cases feedback should be triggered in less than 30ms from the time when user
 * touched the screen. For the area registry the feedback system is responsible
 * of fulfilling this requirement, but in case of direct feedback it is the
 * responsibility of the user of this API.
 * <p>
 * The tactile feedback has 2 different styles: Basic and Sensitive. Each type
 * is defined in the device settings, not by this API. Each style of feedback is
 * used in different cases. Typically Basic style is used to emphasize the tap
 * event, while Sensitive style is used mostly at dragging operations, to
 * emphasize focus traverse, text selection, etc. Choosing a specific style for
 * each use case in up to developer, however it is good to follow the behavior
 * of native applications.
 * <p>
 * The goal of the API is to enable user-created UI components to have tactile
 * feedback. The UI component types supported by the API are limited to
 * user-defined components only: <code>javax.microedition.lcdui.Canvas</code>,
 * <code>javax.microedition.lcdui.CustomItem</code>,
 * <code>org.eclipse.swt.widgets.Control</code>. Other UI components (i.e. List,
 * TextBox, etc.) get default tactile feedback automatically from the platform.
 *
 * @version 0.001
 * @since 1.2
 */

public class TactileFeedback
{

    /**
     * Constant for specifying basic tactile feedback style. Actual style
     * behaviour is set through device settings.
     */
    public static final int FEEDBACK_STYLE_BASIC = 1;

    /**
     * Constant for specifying sensitive tactile feedback style. Actual style
     * behaviour is set through device settings.
     */
    public static final int FEEDBACK_STYLE_SENSITIVE = 2;

    private final String invalidFeedbackStyleMsg = "Invalid feedback style specified";
    private final String invalidControlTypeMsg = "Invalid object provided for tactile feedback registration";

    private static final int TYPE_INVALID = 0;
    private static final int TYPE_ESWT = 1;
    private static final int TYPE_LCDUI = 2;
    private static boolean feedbackEnabled;

    /**
     * Constructs tactile feedback engine object. The object may be used in both
     * LCDUI and eSWT java UI toolkits. TactileFeedback object must be
     * constructed before using any tactile feedback methods.
     */
    public TactileFeedback()
    {
    }

    /**
     * Triggers direct tactile feedback of the specified style.
     *
     * @param style
     *            The style of the direct feedback. Use predefined values
     *            FEEDBACK_STYLE_BASIC, FEEDBACK_STYLE_SENSITIVE. Actual style
     *            behavior is set through device settings.
     * @throws IllegalArgumentException
     *             if the style parameter has invalid type.
     */
    public void directFeedback(int style) throws IllegalArgumentException
    {
        if ((style != FEEDBACK_STYLE_BASIC)
                && (style != FEEDBACK_STYLE_SENSITIVE))
            throw new IllegalArgumentException(invalidFeedbackStyleMsg);
        if (org.eclipse.swt.widgets.Display.getCurrent() == null)
        {
            final int fStyle = style;
            com.nokia.mj.impl.nokialcdui.LCDUIInvoker
            .eSWTUIThreadRunnerSyncExec(new Runnable()
            {
                public void run()
                {
                    //  OS.MTouchFeedback_InstantFeedback(fStyle);
                }
            });

        }
        else
        {
            //OS.MTouchFeedback_InstantFeedback(style);
        }
    }

    /**
     * Queries the device if it supports tactile feedback. It is safe to use
     * this class even if the device does not support tactile feedback. Methods
     * of this class can be called but this has no effect.
     *
     * @return true if the device supports tactile feedback, false otherwise.
     */
    public boolean isTouchFeedbackSupported()
    {
        if (org.eclipse.swt.widgets.Display.getCurrent() == null)
        {
            com.nokia.mj.impl.nokialcdui.LCDUIInvoker
            .eSWTUIThreadRunnerSyncExec(new Runnable()
            {

                public void run()
                {
                    // feedbackEnabled = OS
                    //       .MTouchFeedback_TouchFeedbackSupported();
                }
            });
        }
        else
        {
            //  feedbackEnabled = OS.MTouchFeedback_TouchFeedbackSupported();
        }
        return feedbackEnabled;
    }

    /**
     * Registers area within a UI component for tactile feedback. If areas
     * overlap then the feedback is given from the last registered area.
     * {@link #moveFeedbackAreaToFirstPriority moveFeedbackAreaToFirstPriority}
     * can be used to bring a certain area covered by another area into action.
     * If an area with specified <code>id</code> is already registered it is
     * updated. When updating an existing area there is no need to unregister it
     * before re-registering again with new coordinates and/or feedback style.
     * The area specified by <code>x</code>, <code>y</code>, <code>width</code>
     * and <code>height</code> parameters may be located on the screen just
     * partially.
     *
     * <p>
     * In case of area registry for eSWT Control, the registry needs to be
     * updated every time when the size or position of a UI component changes,
     * or when e.g. a state of a UI component changes in such way that feedback
     * type also changes. Typically the area update may be implemented in
     * <code>controlMoved</code> and <code>controlResized</code> methods of
     * <code>ControlListener</code> interface. The update of an area may be
     * implemented by calling registerFeedbackArea with the same area id but new
     * coordinates and/or feedback style.
     * <p>
     * In case of area registry for LCDUI Canvas or CustomItem no further
     * actions are required to update tactile feedback areas when a CustomItem
     * is moved in a Form or when a Canvas is changed due to e.g. orientation
     * switch.
     *
     * @param uiObject
     *            The UI component for tactile feedback registration. Valid
     *            object types are: <code>javax.microedition.lcdui.Canvas</code>
     *            , <code>javax.microedition.lcdui.CustomItem</code>,
     *            <code>org.eclipse.swt.widgets.Control</code>.
     * @param id
     *            Id of the new tactile feedback area to be registered. Id's are
     *            used to identify particular tactile feedback area within one
     *            UI component. Id's do not need to be consecutive numbers.
     * @param x
     *            x-coordinate of the top-left corner of tactile feedback
     *            rectangle to register.
     * @param y
     *            y-coordinate of the top-left corner of tactile feedback
     *            rectangle to register.
     * @param width
     *            Width of tactile feedback rectangle to register.
     * @param height
     *            Height of tactile feedback rectangle to register.
     * @param style
     *            The style of the feedback for specified area. Use predefined
     *            values <code>FEEDBACK_STYLE_BASIC</code>,
     *            <code>FEEDBACK_STYLE_SENSITIVE</code>. Actual style behaviour
     *            is defined through device settings.
     * @throws IllegalArgumentException
     *             if the uiObject parameter has invalid type.
     * @throws IllegalArgumentException
     *             if the style parameter has invalid type.
     */
    public void registerFeedbackArea(Object uiObject, int id, int x, int y,
                                     int width, int height, int style) throws IllegalArgumentException
    {
        int type = controlType(uiObject);
        if (type == TYPE_INVALID)
            throw new IllegalArgumentException(invalidControlTypeMsg);

        if ((style != FEEDBACK_STYLE_BASIC)
                && (style != FEEDBACK_STYLE_SENSITIVE))
            throw new IllegalArgumentException(invalidFeedbackStyleMsg);

        int controlHandle = getControlHandle(uiObject);
        if (type == TYPE_LCDUI)
        {
            final int fControlHandle = controlHandle;
            final int fId = id;
            final int fX = x;
            final int fY = y;
            final int fWidth = width;
            final int fHeight = height;
            final int fStyle = style;
            com.nokia.mj.impl.nokialcdui.LCDUIInvoker
            .eSWTUIThreadRunnerSyncExec(new Runnable()
            {

                public void run()
                {
                    //      OS.MTouchFeedback_SetFeedbackArea(fControlHandle,
                    //            fId, fX, fY, fWidth, fHeight, fStyle);
                }
            });
        }
        else
        {
            //OS.MTouchFeedback_SetFeedbackArea(controlHandle, id, x, y, width,
            //      height, style);
        }
    }

    /**
     * Unregisters tactile feedback area within a UI component.
     *
     * @param uiObject
     *            The UI component for tactile feedback area de-registration.
     *            Valid object types are:
     *            <code>javax.microedition.lcdui.Canvas</code>,
     *            <code>javax.microedition.lcdui.CustomItem</code>,
     *            <code>org.eclipse.swt.widgets.Control</code>.
     * @param id
     *            Id of the tactile feedback area to be unregistered. Id's are
     *            used to identify particular tactile feedback area within one
     *            UI component. If given id was not registered by
     *            {@link #registerFeedbackArea registerFeedbackArea} then the
     *            call has no effect.
     * @throws IllegalArgumentException
     *             if the uiObject parameter has invalid type.
     */
    public void unregisterFeedbackArea(Object uiObject, int id)
    throws IllegalArgumentException
    {

        int type = controlType(uiObject);
        if (type == TYPE_INVALID)
            throw new IllegalArgumentException(invalidControlTypeMsg);

        int controlHandle = getControlHandle(uiObject);
        if (type == TYPE_LCDUI)
        {
            final int fControlHandle = controlHandle;
            final int fId = id;
            com.nokia.mj.impl.nokialcdui.LCDUIInvoker
            .eSWTUIThreadRunnerSyncExec(new Runnable()
            {

                public void run()
                {
                    //  OS.MTouchFeedback_RemoveFeedbackArea(
                    //        fControlHandle, fId);
                }
            });
        }
        else
        {
            //OS.MTouchFeedback_RemoveFeedbackArea(controlHandle, id);
        }

    }

    /**
     * Removes all tactile feedback for a UI component.
     *
     * @param uiObject
     *            The UI component for tactile feedback area de-registration.
     *            Valid object types are:
     *            <code>javax.microedition.lcdui.Canvas</code>,
     *            <code>javax.microedition.lcdui.CustomItem</code>,
     *            <code>org.eclipse.swt.widgets.Control</code>.
     * @throws IllegalArgumentException
     *             if the uiObject parameter has invalid type.
     */
    public void removeFeedbackForComponent(Object uiObject)
    {
        if (isTouchFeedbackSupported() == false)
            return;

        int type = controlType(uiObject);
        if (type == TYPE_INVALID)
            throw new IllegalArgumentException(invalidControlTypeMsg);

        int controlHandle = getControlHandle(uiObject);

        if (type == TYPE_LCDUI)
        {
            final int fControlHandle = controlHandle;
            com.nokia.mj.impl.nokialcdui.LCDUIInvoker
            .eSWTUIThreadRunnerSyncExec(new Runnable()
            {

                public void run()
                {
                    //    OS.MTouchFeedback_RemoveFeedbackForControl(fControlHandle);
                }
            });
        }
        else
        {
            //OS.MTouchFeedback_RemoveFeedbackForControl(controlHandle);
        }

    }

    /**
     * Moves the specified tactile feedback area to first priority. Priority is
     * significant in case of overlapping tactile feedback areas. If the tactile
     * feedback styles of registered areas are different, the feedback from
     * first-priority area will be given.
     *
     * @param uiObject
     *            The UI component for tactile feedback area de-registration.
     *            Valid object types are:
     *            <code>javax.microedition.lcdui.Canvas</code>,
     *            <code>javax.microedition.lcdui.CustomItem</code>,
     *            <code>org.eclipse.swt.widgets.Control</code>.
     * @param id
     *            Id of the tactile feedback area to be unregistered. Id's are
     *            used to identify particular tactile feedback area within one
     *            UI component. If given id was not registered by
     *            {@link #registerFeedbackArea registerFeedbackArea} then the
     *            call has no effect.
     * @throws IllegalArgumentException
     *             if the uiObject parameter has invalid type.
     */
    public void moveFeedbackAreaToFirstPriority(Object uiObject, int id)
    {
        if (isTouchFeedbackSupported() == false)
            return;

        int type = controlType(uiObject);
        if (type == TYPE_INVALID)
            throw new IllegalArgumentException(invalidControlTypeMsg);

        int controlHandle = getControlHandle(uiObject);
        if (type == TYPE_LCDUI)
        {
            final int fControlHandle = controlHandle;
            final int fId = id;
            com.nokia.mj.impl.nokialcdui.LCDUIInvoker
            .eSWTUIThreadRunnerSyncExec(new Runnable()
            {

                public void run()
                {
                    //     OS.MTouchFeedback_MoveFeedbackAreaToFirstPriority(
                    //           fControlHandle, fId);
                }
            });
        }
        else
        {
            //  OS.MTouchFeedback_MoveFeedbackAreaToFirstPriority(
            //                controlHandle, id);
        }

    }


    private int controlType(Object obj)
    {
        if ((obj instanceof javax.microedition.lcdui.Canvas)
                || (obj instanceof javax.microedition.lcdui.CustomItem))
        {
            return TYPE_LCDUI;
        }
        else if ((obj instanceof org.eclipse.swt.widgets.Control))
        {
            return TYPE_ESWT;
        }
        return TYPE_INVALID;
    }

    private int getControlHandle(Object uiObject)
    {
        int controlHandle = 0;
        org.eclipse.swt.widgets.Control eSwtControl = null;
        if (uiObject instanceof javax.microedition.lcdui.Canvas
                || uiObject instanceof javax.microedition.lcdui.CustomItem)
        {
            eSwtControl = com.nokia.mj.impl.nokialcdui.LCDUIInvoker
                          .getEswtControl(uiObject);
        }
        else if (uiObject instanceof org.eclipse.swt.widgets.Control)
        {
            eSwtControl = (org.eclipse.swt.widgets.Control) uiObject;
        }

        if (eSwtControl != null)
        {
            controlHandle = Internal_PackageSupport.topHandle(eSwtControl);
        }
        return controlHandle;
    }

}
