/*
 * Copyright 1999-2006 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code 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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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 in the LICENSE file that
 * accompanied this code).
 *
 * 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 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package sun.awt.im;

import java.awt.Component;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.event.InputMethodEvent;
import java.awt.event.InputMethodListener;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;
import java.awt.im.InputMethodRequests;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;

/**
 * A composition area handler handles events and input method requests for
 * the composition area. Typically each input method context has its own
 * composition area handler if it supports passive clients or below-the-spot
 * input, but all handlers share a single composition area.
 *
 * @author JavaSoft International
 */

class CompositionAreaHandler implements InputMethodListener,
                                                 InputMethodRequests {

    private static CompositionArea compositionArea;
    private static Object compositionAreaLock = new Object();
    private static CompositionAreaHandler compositionAreaOwner; // synchronized through compositionArea

    private AttributedCharacterIterator composedText;
    private TextHitInfo caret = null;
    private Component clientComponent = null;
    private InputMethodContext inputMethodContext;

    /**
     * Constructs the composition area handler.
     */
    CompositionAreaHandler(InputMethodContext context) {
        inputMethodContext = context;
    }

    /**
     * Creates the composition area.
     */
    private void createCompositionArea() {
        synchronized(compositionAreaLock) {
            compositionArea = new CompositionArea();
            if (compositionAreaOwner != null) {
                compositionArea.setHandlerInfo(compositionAreaOwner, inputMethodContext);
            }
            // If the client component is an active client using below-the-spot style, then
            // make the composition window undecorated without a title bar.
            if(clientComponent!=null){
                InputMethodRequests req = clientComponent.getInputMethodRequests();
                if (req != null && inputMethodContext.useBelowTheSpotInput()) {
                    setCompositionAreaUndecorated(true);
                }
            }
        }
    }

    void setClientComponent(Component clientComponent) {
        this.clientComponent = clientComponent;
    }

    /**
     * Grabs the composition area, makes this handler its owner, and installs
     * the handler and its input context into the composition area for event
     * and input method request handling.
     * If doUpdate is true, updates the composition area with previously sent
     * composed text.
     */

    void grabCompositionArea(boolean doUpdate) {
        synchronized (compositionAreaLock) {
            if (compositionAreaOwner != this) {
                compositionAreaOwner = this;
                if (compositionArea != null) {
                    compositionArea.setHandlerInfo(this, inputMethodContext);
                }
                if (doUpdate) {
                    // Create the composition area if necessary
                    if ((composedText != null) && (compositionArea == null)) {
                        createCompositionArea();
                    }
                    if (compositionArea != null) {
                        compositionArea.setText(composedText, caret);
                    }
                }
            }
        }
    }

    /**
     * Releases and closes the composition area if it is currently owned by
     * this composition area handler.
     */
    void releaseCompositionArea() {
        synchronized (compositionAreaLock) {
            if (compositionAreaOwner == this) {
                compositionAreaOwner = null;
                if (compositionArea != null) {
                    compositionArea.setHandlerInfo(null, null);
                    compositionArea.setText(null, null);
                }
            }
        }
    }

    /**
     * Releases and closes the composition area if it has been created,
     * independent of the current owner.
     */
    static void closeCompositionArea() {
        if (compositionArea != null) {
            synchronized (compositionAreaLock) {
                compositionAreaOwner = null;
                compositionArea.setHandlerInfo(null, null);
                compositionArea.setText(null, null);
            }
        }
    }

    /**
     * Returns whether the composition area is currently visible
     */
    boolean isCompositionAreaVisible() {
        if (compositionArea != null) {
            return compositionArea.isCompositionAreaVisible();
        }

        return false;
    }


    /**
     * Shows or hides the composition Area
     */
    void setCompositionAreaVisible(boolean visible) {
        if (compositionArea != null) {
            compositionArea.setCompositionAreaVisible(visible);
        }
    }

    void processInputMethodEvent(InputMethodEvent event) {
        if (event.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
            inputMethodTextChanged(event);
        } else {
            caretPositionChanged(event);
        }
    }

    /**
     * set the compositionArea frame decoration
     */
    void setCompositionAreaUndecorated(boolean undecorated) {
        if (compositionArea != null) {
            compositionArea.setCompositionAreaUndecorated(undecorated);
        }
    }

    //
    // InputMethodListener methods
    //

    private static final Attribute[] IM_ATTRIBUTES =
            { TextAttribute.INPUT_METHOD_HIGHLIGHT };

    public void inputMethodTextChanged(InputMethodEvent event) {
        AttributedCharacterIterator text = event.getText();
        int committedCharacterCount = event.getCommittedCharacterCount();

        // extract composed text and prepare it for display
        composedText = null;
        caret = null;
        if (text != null
                && committedCharacterCount < text.getEndIndex() - text.getBeginIndex()) {

            // Create the composition area if necessary
            if (compositionArea == null) {
                 createCompositionArea();
            }

            // copy the composed text
            AttributedString composedTextString;
            composedTextString = new AttributedString(text,
                    text.getBeginIndex() + committedCharacterCount, // skip over committed text
                    text.getEndIndex(), IM_ATTRIBUTES);
            composedTextString.addAttribute(TextAttribute.FONT, compositionArea.getFont());
            composedText = composedTextString.getIterator();
            caret = event.getCaret();
        }

        if (compositionArea != null) {
            compositionArea.setText(composedText, caret);
        }

        // send any committed text to the text component
        if (committedCharacterCount > 0) {
            inputMethodContext.dispatchCommittedText(((Component) event.getSource()),
                                                     text, committedCharacterCount);

            // this may have changed the text location, so reposition the window
            if (isCompositionAreaVisible()) {
                compositionArea.updateWindowLocation();
            }
        }

        // event has been handled, so consume it
        event.consume();
    }

    public void caretPositionChanged(InputMethodEvent event) {
        if (compositionArea != null) {
            compositionArea.setCaret(event.getCaret());
        }

        // event has been handled, so consume it
        event.consume();
    }

    //
    // InputMethodRequests methods
    //

    /**
     * Returns the input method request handler of the client component.
     * When using the composition window for an active client (below-the-spot
     * input), input method requests that do not relate to the display of
     * the composed text are forwarded to the client component.
     */
    InputMethodRequests getClientInputMethodRequests() {
        if (clientComponent != null) {
            return (InputMethodRequests) clientComponent.getInputMethodRequests();
        }

        return null;
    }

    public Rectangle getTextLocation(TextHitInfo offset) {
        synchronized (compositionAreaLock) {
            if (compositionAreaOwner == this && isCompositionAreaVisible()) {
                return compositionArea.getTextLocation(offset);
            } else if (composedText != null) {
                // there's composed text, but it's not displayed, so fake a rectangle
                return new Rectangle(0, 0, 0, 10);
            } else {
                InputMethodRequests requests = getClientInputMethodRequests();
                if (requests != null) {
                    return requests.getTextLocation(offset);
                } else {
                    // passive client, no composed text, so fake a rectangle
                    return new Rectangle(0, 0, 0, 10);
                }
            }
        }
    }

    public TextHitInfo getLocationOffset(int x, int y) {
        synchronized (compositionAreaLock) {
            if (compositionAreaOwner == this && isCompositionAreaVisible()) {
                return compositionArea.getLocationOffset(x, y);
            } else {
                return null;
            }
        }
    }

    public int getInsertPositionOffset() {
        InputMethodRequests req = getClientInputMethodRequests();
        if (req != null) {
            return req.getInsertPositionOffset();
        }

        // we don't have access to the client component's text.
        return 0;
    }

    private static final AttributedCharacterIterator EMPTY_TEXT =
            (new AttributedString("")).getIterator();

    public AttributedCharacterIterator getCommittedText(int beginIndex,
                                                       int endIndex,
                                                       Attribute[] attributes) {
        InputMethodRequests req = getClientInputMethodRequests();
        if(req != null) {
            return req.getCommittedText(beginIndex, endIndex, attributes);
        }

        // we don't have access to the client component's text.
        return EMPTY_TEXT;
    }

    public int getCommittedTextLength() {
        InputMethodRequests req = getClientInputMethodRequests();
        if(req != null) {
            return req.getCommittedTextLength();
        }

        // we don't have access to the client component's text.
        return 0;
    }


    public AttributedCharacterIterator cancelLatestCommittedText(Attribute[] attributes) {
        InputMethodRequests req = getClientInputMethodRequests();
        if(req != null) {
            return req.cancelLatestCommittedText(attributes);
        }

        // we don't have access to the client component's text.
        return null;
    }

    public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {
        InputMethodRequests req = getClientInputMethodRequests();
        if(req != null) {
            return req.getSelectedText(attributes);
        }

        // we don't have access to the client component's text.
        return EMPTY_TEXT;
    }

}
