/*
 *  
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.chameleon.layers;

import com.sun.midp.chameleon.*;
import javax.microedition.lcdui.*;
import com.sun.midp.chameleon.skins.PTISkin;
import com.sun.midp.chameleon.skins.ScreenSkin;
import com.sun.midp.lcdui.EventConstants;
import com.sun.midp.configurator.Constants;
import com.sun.midp.chameleon.input.*;

/**
 * A "PTILayer" layer is a special kind of layer which can
 * be visible when the predictive text input mode is active.
 * This layer is added to a MIDPWindow when more than one match
 * exists for the predictive input method. This layer lists the
 * possible words to give user a chance to select word you like.
 * User can traverse the list of words using up/down navigation
 * keys. User may press select bhutton to accept highlighted word.
 */
public class PTILayer extends PopupLayer {
    /** Options have to be listed in the popup dialog */
    private String[] list;

    /** Selected option number */
    private int selId;

    /** Instance of current input mode */
    private TextInputSession iSession;

    /** max text width visible on the screen */
    private int widthMax; 

    /** separator character between words within the list */
    private static final String SEPARATOR = " ";

    /** pointer is clicked outside of any area */
    private static final int OUT_OF_BOUNDS = -1;

    /** pointer is clicked to left arrow */
    private static final int LEFT_ARROW_AREA = 0;

    /** pointer is clicked to right arrow */
    private static final int RIGHT_ARROW_AREA = 1;

    /** pointer is clicked to the word inside of list */
    private static final int LIST_MATCHES_AREA = 2;

    /** Flag indicates that pointer release event should be processed */
    private boolean checkReleased ; //= false;
    /**
     * Create an instance of PTILayer
     * @param inputSession current input session
     */
    public PTILayer(TextInputSession inputSession) {
        super(PTISkin.IMAGE_BG, PTISkin.COLOR_BG);
        iSession = inputSession;
    }

    /**
     * The setVisible() method is overridden in PTILayer
     * so as not to have any effect. PopupLayers are always
     * visible by their very nature. In order to hide a
     * PopupLayer, it should be removed from its containing
     * MIDPWindow.
     * @param visible if true the pti layer has to be shown,
     * if false the layer has to be hidden
     */
    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    /** PTI layer initialization: init selected id, calculate available size */
    protected void initialize() {
        super.initialize();

        setAnchor();
        selId = 0;
    }

    /**
     * Sets the anchor constants for rendering operation.
     */
    private void setAnchor() {
        bounds[W] = ScreenSkin.WIDTH;
        bounds[H] = PTISkin.HEIGHT;
        bounds[X] = (ScreenSkin.WIDTH - bounds[W]) >> 1;
        bounds[Y] = ScreenSkin.HEIGHT - bounds[H];
        widthMax = bounds[W] - PTISkin.MARGIN;
        if (PTISkin.LEFT_ARROW != null && PTISkin.RIGHT_ARROW != null) {
            widthMax -= 4 * PTISkin.MARGIN +
                PTISkin.LEFT_ARROW.getWidth() +
                PTISkin.RIGHT_ARROW.getWidth();
        }
    }

    /**
     * Set list of matches
     * @param l list of matches
     */
    public synchronized void setList(String[] l) {
        list = new String[l.length];
        System.arraycopy(l, 0, list, 0, l.length);
        visible = (list != null && list.length > 1);
        // IMPL_NOTE: has to be set externally as parameter 
        selId = 0;
        setDirty();
    }

    /**
     * Get list of matches
     * @return list of matches
     */
    public synchronized String[] getList() {
        return list;
    }

    /**
     * Handle key input from a keypad. Parameters describe
     * the type of key event and the platform-specific
     * code for the key. (Codes are translated using the
     * lcdui.Canvas) UP/DOWN/SELECT key press are processed if 
     * is visible. 
     *
     * @param type the type of key event
     * @param keyCode the numeric code assigned to the key
     * @return true if key has been handled by PTI layer, false otherwise
     */
    public boolean keyInput(int type, int keyCode) {
        boolean ret = false;
        String[] l = getList(); 
        if (type == EventConstants.PRESSED && visible) {
            switch (keyCode) {
            case Constants.KEYCODE_UP:
            case Constants.KEYCODE_LEFT:
                selId = (selId - 1 + l.length) % l.length;
                iSession.processKey(Canvas.UP, false);
                ret = true;
                break;
            case Constants.KEYCODE_DOWN:
            case Constants.KEYCODE_RIGHT:
                selId = (selId + 1) % l.length;
                iSession.processKey(Canvas.DOWN, false);
                ret = true;
                break;
            case Constants.KEYCODE_SELECT:
                iSession.processKey(keyCode, false);
                ret = true;
                break;
            default:
                break;
            }
        }
        // process key by input mode 
        requestRepaint();
        return ret;
    }

    /**
     * Get id of the word inside of the list selected by pointer
     * @param x - x coordinate of pointer
     * @param y - y coordinate of pointer
     * @return word index in the range of 0 and list length  - 1.
     * If the pointer does not point to any word  return -1
     */ 
    private int getWordIdAtPointerPosition(int x, int y) {
        String[] l = getList();
        int id = 0;
        int start = PTISkin.MARGIN;
        if (PTISkin.LEFT_ARROW != null) {
            start += PTISkin.LEFT_ARROW.getWidth();
        }
        
        while (id < l.length) {
            int w = PTISkin.FONT.stringWidth(SEPARATOR + l[id]); 
            if (x > start && x <= start + w) {
                break;
            }
            start += w;
            id++;
        }
        
        return id < l.length ? id : -1;
    }

    /**
     * Utility method to determine if this layer wanna handle
     * the given point. PTI layer handles the point if it
     * lies within the bounds of this layer.  The point should be in
     * the coordinate space of this layer's containing CWindow.
     *
     * @param x the "x" coordinate of the point
     * @param y the "y" coordinate of the point
     * @return true if the coordinate lies in the bounds of this layer
     */
    public boolean handlePoint(int x, int y) {
        return containsPoint(x, y);
    }
    
    /**
     * Get the layer area the pointer is clicked in
     * @param x - x coordinate of pointer
     * @param y - y coordinate of pointer
     * @return retuen the area. It can be either OUT_OF_BOUNDS or
     * LEFT_ARROW_AREA or RIGHT_ARROW_AREA or LIST_MATCHES_AREA
     */ 
    private int getAreaAtPointerPosition(int x, int y) {
        int area = OUT_OF_BOUNDS;
        if (x >= PTISkin.MARGIN && x <= bounds[W] - PTISkin.MARGIN) {
            if (PTISkin.LEFT_ARROW != null &&
                x <= PTISkin.MARGIN + PTISkin.LEFT_ARROW.getWidth()) {
                area = LEFT_ARROW_AREA; 
            } else if (PTISkin.RIGHT_ARROW != null &&
                       x >= bounds[W] - PTISkin.MARGIN -
                       PTISkin.RIGHT_ARROW.getWidth()) {
                area = RIGHT_ARROW_AREA; 
            } else {
                area = LIST_MATCHES_AREA; 
            }
        }
        return area;
    }
    
    /**
     * Allow this window to process pointer input. The type of pointer input
     * will be press, release, drag, etc. The x and y coordinates will 
     * identify the point at which the pointer event occurred in the coordinate
     * system of this window. This window will translate the coordinates
     * appropriately for each layer contained in this window. This method will
     * return true if the event was processed by this window or one of its 
     * layers, false otherwise.
     *
     * @param type the type of pointer event (press, release, drag)
     * @param x the x coordinate of the location of the event
     * @param y the y coordinate of the location of the event
     * @return true if this window or one of its layers processed the event
     */
    public boolean pointerInput(int type, int x, int y) {
        if (visible) {
            String[] l = getList();
            
            int area = getAreaAtPointerPosition(x, y);
            
            switch(type) {
            case EventConstants.PRESSED:
                switch (area) {
                case LEFT_ARROW_AREA:
                    selId = (selId - 1 + l.length) % l.length;
                    iSession.processKey(Canvas.UP, false);
                    requestRepaint();
                    break;
                case RIGHT_ARROW_AREA:
                    selId = (selId + 1) % l.length;
                    iSession.processKey(Canvas.DOWN, false);
                    requestRepaint();
                    break;
                case LIST_MATCHES_AREA:
                    // move focus to the selected word
                    int id = getWordIdAtPointerPosition(x, y);
                    if (id >= 0) {
                        checkReleased = true;
                        int i = selId;
                        if (id  > selId) {
                            while (i < id) {
                                iSession.processKey(Canvas.DOWN, false);
                                i++;
                            }
                        } else if (id  < selId) {
                            while (i > id) {
                                iSession.processKey(Canvas.UP, false);
                                i--;
                            }
                        }
                        requestRepaint();
                    }
                    break;
                }
                break;
            case EventConstants.RELEASED:
                if (area == LIST_MATCHES_AREA &&
                    checkReleased
                    // IMPL_NOTE: move the focus in the standart maner,
                    // doon't move the selected item at the head of the list 
                    // && getWordIdAtPointerPosition(x, y) == selId
                    ) {
                    iSession.processKey(Constants.KEYCODE_SELECT, false);
                    requestRepaint();
                }
                checkReleased = false;
                break;
            default:
                break;
            }
        }
        return true;
    }

    /**
     * Paint layer body.
     * @param g - Graphics
     */
    protected void paintBody(Graphics g) {
        String[] l = getList();
        if (l == null || l.length < 1)
            return;

        // draw outer frame
        g.setColor(PTISkin.COLOR_BDR);
        g.drawRect(0, 0, bounds[W] - 1, bounds[H] - 1);

        // draw arrows
        if (PTISkin.LEFT_ARROW != null) {
            g.drawImage(PTISkin.LEFT_ARROW, PTISkin.MARGIN, bounds[H] >> 1,
                        Graphics.VCENTER | Graphics.LEFT);
        }
        
        if (PTISkin.RIGHT_ARROW != null) {
            g.drawImage(PTISkin.RIGHT_ARROW, bounds[W] - PTISkin.MARGIN,
                        bounds[H] >> 1, Graphics.VCENTER | Graphics.RIGHT);
        }

        int x = 0, y = 0;

        String text_b = "", text_a = "";

        for (int i = -1; ++i < l.length; ) {
            if (i < selId) {
                text_a += l[i] + SEPARATOR;
            } else if (i > selId) {
                text_b += l[i] + SEPARATOR;
            }
        }

        g.translate((bounds[W] - widthMax) >> 1, 0);
        g.setClip(0, 0, widthMax, bounds[H]);

        x = 0;
        y = PTISkin.FONT.getHeight() < bounds[H] ?
            (bounds[H] - PTISkin.FONT.getHeight()) >> 1 : 0;

        // draw before words
        if (text_a.length() > 0) {
            g.setColor(PTISkin.COLOR_FG);
            g.drawString(text_a, x, y, Graphics.LEFT | Graphics.TOP);
            x += PTISkin.FONT.stringWidth(text_a);
        }

        if (l[selId].length() > 0) {
            // draw highlighted word
            // draw highlighted fill rectangle
            g.setColor(PTISkin.COLOR_BG_HL);

            g.fillRect(x - PTISkin.FONT.stringWidth(SEPARATOR) / 2,
                       y < PTISkin.MARGIN ? y : PTISkin.MARGIN,
                       PTISkin.FONT.stringWidth(l[selId] + SEPARATOR),
                       bounds[H] - (y < PTISkin.MARGIN ? y :
                                    PTISkin.MARGIN) * 2);

            g.setColor(PTISkin.COLOR_FG_HL);
            g.drawString(l[selId] + SEPARATOR, x, y,
                         Graphics.LEFT | Graphics.TOP);
            x += PTISkin.FONT.stringWidth(l[selId] + SEPARATOR);
        }

        // draw after words

        if (text_b.length() > 0) {
            g.setColor(PTISkin.COLOR_FG);
            g.drawString(text_b, x, y, Graphics.LEFT | Graphics.TOP);
        }

        g.translate(-((bounds[W] - widthMax) >> 1), 0);
        g.setClip(0, 0, bounds[W], bounds[H]);
    }

    /**
     * Update bounds of layer 
     * @param layers - current layer can be dependant on this parameter
     */
    public void update(CLayer[] layers) {
        super.update(layers);
        if (visible) {
            setAnchor();
            bounds[Y] -= (layers[MIDPWindow.BTN_LAYER].isVisible() ?
                    layers[MIDPWindow.BTN_LAYER].bounds[H] : 0) +
                    (layers[MIDPWindow.TICKER_LAYER].isVisible() ?
                            layers[MIDPWindow.TICKER_LAYER].bounds[H] : 0);

        }
    }
}


