/* WCEComponentPeer.java
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

GNU Classpath 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 for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

package gnu.java.awt.peer.wce;

import gnu.java.awt.peer.wce.font.WCEFontPeer;

import java.awt.AWTError;
import java.awt.AWTEvent;
import java.awt.AWTException;
import java.awt.BufferCapabilities;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.PaintEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.awt.image.VolatileImage;
import java.awt.peer.ComponentPeer;
import java.awt.peer.ContainerPeer;
import java.lang.reflect.InvocationTargetException;

/**
 * WCEComponentPeerクラス。
 */
public abstract class WCEComponentPeer implements ComponentPeer {
	
	/**
	 * イベントキュー
	 */
	static final EventQueue queue = WCEToolkit.queue;
		
	/**
	 * 対応するComponent
	 */
	private final Component component;
	
	/**
	 * ネイティブウインドウハンドル。
	 */
	private final int hwnd;
	
	
	/**
	 * 今までに表示されたことがあるか？
	 */
	private boolean hasBeenShown;

	/**
	 * 指定されたコンポーネントに対応するpeerのインスタンスを作成する
	 *
	 * @param	c	コンポーネント
	 */
	protected WCEComponentPeer(Component c) {
		this.component = c;
		
		// 親のウインドウハンドルを得る
		int hParentWnd = searchParentWindowHandle();

		// ネイティブウインドウを生成する
		Rectangle r = c.getBounds();
		this.hwnd = createNative(hParentWnd, r.x, r.y, r.width, r.height);
		
		setBounds(r.x, r.y, r.width, r.height);
		
		// 色とフォントを設定する
		setForeground(c.getForeground());
		setBackground(c.getBackground());
		if (c.isFontSet()) {
			setFont(c.getFont());
		}
		
		// 可視状態を設定する
		setVisible(c.isVisible());

		// 有効／無効状態を設定する
		setEnabled(c.isEnabled());
	}
	
	/**
	 * このコンポーネントのウインドウハンドルを返す
	 */
	public int getWindowHandle() {
		return this.hwnd;
	}
	
	/**
	 * 親コンポーネントに対応するウインドウハンドルを取得する
	 * 直接の親がLightWeightコンポーネントの場合は、heavy-weight
	 * コンポーネントにたどり着くまでさかのぼり、そのウインドウハンドルを
	 * 返す。
	 * 親がいない、または全ての親がLight-weightの場合には0を返す。
	 *
	 * @return	親のウインドウハンドル
	 */
	protected int searchParentWindowHandle() {
		ComponentPeer p = null;
		int hwnd = 0;
		// heavyweightコンポーネントを検索する
		for (Component c = getComponent().getParent(); c != null; c = c.getParent()) {
			p = c.getPeer();
			if (p instanceof WCEComponentPeer) {
				hwnd = ((WCEComponentPeer) p).hwnd;
				break;
			}
		}
		return hwnd;
	}
	
	/**
	 * このピアに関連付けられたComponentを返す。
	 */
	protected Component getComponent() {
		return this.component;
	}
	
	/**
	 * 指定されたサイズのネイティブウインドウを作成し、
	 * そのウインドウハンドルを返す。
	 * 作成に失敗した場合は0を返す。
	 *
	 * @param hParentWnd	親Windowのハンドル。親が存在しない場合は0を指定する。
	 * @param x
	 * @param y
	 * @param width
	 * @param height
	 */
	protected abstract int createNative(int hParentWnd, int x, int y, int width, int height);
	
	public int checkImage(Image img, int width, int height, ImageObserver ob) {
		return Toolkit.getDefaultToolkit().checkImage(img, width, height, ob);
	}

	public Image createImage(ImageProducer prod) {
		return getToolkit().createImage(prod);
	}
	
	public Image createImage(int width, int height) {
		// オフスクリーンイメージのインスタンスを返す
		return new WCEOffscreenImage(this, width, height);
	}
	
	/**
	 * ピアを無効化する。
	 */
	public void disable() {
		setEnabled(false);
	}
	
	/**
	 * ピアが破棄される前に呼び出される
	 * 派生クラスは、ネイティブコンポーネントの状態をできる限り引数 c に保存する
	 *
	 * super.preDispose(c);
	 * // ... 保存処理 ...
	 */
	protected void preDispose(Component c) {
		// WCEComponetPeerの実装は何もしない
	}
	
	/**
	 * ピアを破棄する
	 */
	public void dispose() {
		Component c = getComponent();
		preDispose(c);
		Font f = c.getFont();
		if (f != null) {
			// ネイティブフォントリソースを開放する
			WCEFontPeer peer = (WCEFontPeer) f.getPeer();
			peer.dispose();
		}
		disposeNative(getWindowHandle());
	}
	
	private native void disposeNative(int windowHandle);
	
	/**
	 * ピアを有効化する
	 */
	public void enable() {
		setEnabled(true);
	}
	
	public ColorModel getColorModel() {
		return ColorModel.getRGBdefault();
	}
	
	public FontMetrics getFontMetrics(Font f) {
		return getToolkit().getFontMetrics(f);
	}

	public Graphics getGraphics() {
		return new WCEGraphics2D(this);
	}
	
	/**
	 * 画面上における左上隅の座標を返す
	 */
	public Point getLocationOnScreen() {
		return getNativeLocationOnScreen(getWindowHandle());
	}
	
	private native Point getNativeLocationOnScreen(int windowHandle);
	
	/**
	 * 最小サイズを返す。
	 */
	public Dimension getMinimumSize() {
		return getPreferredSize();
	}
	
	/**
	 * 推奨サイズを返す。
	 */
	public Dimension getPreferredSize() {
		return getNativePreferredSize(getWindowHandle());
	}
	
	/**
	 * ネイティブコンポーネントの推奨サイズを返す
	 */
	protected Dimension getNativePreferredSize(int windowHandle) {
		Component c = getComponent();
		return c.getSize();
	}

	/**
	 * このピアのToolkitを返す。
	 */
	public Toolkit getToolkit() {
		return Toolkit.getDefaultToolkit();
	}
	
	/**
	 * イベントを処理する
	 */
	public void handleEvent(AWTEvent e) {
		int id = e.getID();

		switch (id) {
		case PaintEvent.PAINT:
		case PaintEvent.UPDATE:
			{
				Component c = getComponent();
				if (! c.isVisible() || c.getIgnoreRepaint()) {
					// 表示されていない場合
					// またはOSからの再描画メッセージを拒否している場合
					return;
				}
				try  {
					Graphics g = getGraphics();
					if (g == null) {
						break;
					}

					// クリッピング領域を設定する
					g.setClip(((PaintEvent) e).getUpdateRect());

					if (id == PaintEvent.PAINT) {
						c.paint(g);
					} else {
						c.update(g);
					}
					g.dispose ();
	            } catch (InternalError ie) {
					ie.printStackTrace();
				}
			}
			break;
		}
	}
	

	public boolean isFocusTraversable() {
		return true;
	}
	
	public boolean isFocusable() {
		return false;
	}
	
	public Dimension minimumSize() {
		return getMinimumSize();
	}
	
	public Dimension preferredSize() {
		return getPreferredSize();
	}
	
	public void paint(Graphics graphics) {
		// 何もしない
	}
	
	public boolean prepareImage(Image img, int width, int height, ImageObserver ob) {
		return Toolkit.getDefaultToolkit().prepareImage(img, width, height, ob);
	}
	
	public void print(Graphics graphics) {
		paint(graphics);
	}
	
	public void repaint(long tm, int x, int y, int width, int height) {
		if (width == 0 && height == 0) {
			// サイズ０のコンポーネントに対しては何もしない
			return;
		}
		// キューに PaintEvent.UPDATE を postする
		// （結果、update(Graphics) が呼び出されることになる）
		queue.postEvent (new PaintEvent (getComponent(),
									 PaintEvent.UPDATE,
									 new Rectangle (x, y, width, height)));
	}

	public void requestFocus() {
		assert false: "Call new requestFocus() method instead";
//		requestNativeFocus(getWindowHandle());
//		postFocusEvent(FocusEvent.FOCUS_GAINED, null);
	}
	
	/**
	 * 指定されたウインドウハンドルに対してSetFocus()を呼び出す
	 */
	private native void requestNativeFocus(int windowHandle);
	
	
	public boolean requestFocus (Component request,
								 boolean temporary,
								 boolean allowWindowFocus,
								 long time) {
		boolean retval = false;
		
		if (hasNativeFocus(getWindowHandle())) {
			KeyboardFocusManager kfm =
				KeyboardFocusManager.getCurrentKeyboardFocusManager();
			Component currentFocus = kfm.getFocusOwner();
			if (currentFocus == request) {
				// Nothing to do in this trivial case.
				retval = true;
			} else {
				// Requested component is a lightweight descendant of this one
				// or the actual heavyweight.
				// Since this (native) component is already focused, we simply
				// change the actual focus and be done.
				postFocusEvent(FocusEvent.FOCUS_GAINED, temporary, null);
				retval = true;
			}
		} else {
            // temporaryがfalseの場合にのみネイティブコンポーネントのフォーカスを
            // 取得する
            // そうしないと、以下の問題が起きる
            // ・JMenuの大きさがJFrameよりも大きな場合、JFrameが前面に表示されてしまう
            // これは、javax.swing.plaf.basic.BasicPopupMenuUIの以下の箇所が原因
            // if (invokerRootPane != null)
            //  {
            //    invokerRootPane.requestFocus(true);
            //    installKeyboardActionsImpl();
            //  }
			// 根本的な解決策が思いつかないので、とりあえずの対処
            if (! temporary) {
				requestNativeFocus(getWindowHandle());
			}
            retval = true;
		}
		return retval;
	}
	
	/**
	 * 指定されたComponentが存在するWindowを返す。
	 */
	private Window getWindowFor(Component c) {
		Component comp = c;
		while (! (comp instanceof Window))
			comp = comp.getParent();
		return (Window) comp;
	}

	public void reshape(int x, int y, int width, int height) {
		setBounds(x, y, width, height);
	}
	
	/**
	 * ピアの背景色を設定する
	 */
	public void setBackground(Color color) {
		if (color == null) {
			// since GNU Classpath 0.18
			color = SystemColor.window;
		}
		setNativeBackground(getWindowHandle(),
							color.getRGB());
	}
	
	private native void setNativeBackground(int windowHandle,
											int rgb);

	/**
	 * ネイティブコンポーネントのサイズを設定する
	 */
	protected native void setNativeBounds(int windowHandle, int x, int y, int width, int height);
	
	/**
	 * ネイティブコンポーネントの大きさを設定する
	 */
	public void setBounds(int x, int y, int width, int height) {
		Container parent = getComponent().getParent();
		Insets i;
		while (parent != null && parent.isLightweight ()) {
			i = parent.getInsets ();
			x += parent.getX () + i.left;
			y += parent.getY () + i.top;
			parent = parent.getParent ();
		}
		
		if (parent != null && ! (getComponent() instanceof Dialog)) {
			ContainerPeer peer = (ContainerPeer) parent.getPeer();
			if (peer == null) {
				// デバッグ用
				throw new AWTError("parent.getPeer() is null. parent=" + parent);
			} else {
				// HeavyweightコンポーネントのInsets分 (x, y)から減算する
				i = peer.getInsets();
				x -= i.left;
				y -= i.top;
			}
		}

		setNativeBounds(getWindowHandle(), x, y, width, height);
	}
	
	
	public void setCursor(Cursor cursor) {
		setNativeCursor(getWindowHandle(), cursor.getType());
	}
	
	/**
	 * ネイティブコンポーネントのカーソルを設定する
	 */
	private native void setNativeCursor(int windowHandle, int type);
	
	/**
	 * ネイティブコンポーネントの有効／無効状態を変更する
	 */
	public void setEnabled(boolean enabled) {
		setNativeEnabled(getWindowHandle(), enabled);
	}
	
	private native void setNativeEnabled(int windowHandle,
										 boolean enabled);
	
	/**
	 * ピアのフォントを設定する。
	 */
	public void setFont(Font font) {
		WCEFontPeer peer = (WCEFontPeer) font.getPeer();
		setNativeFontHandle(getWindowHandle(), peer.getFontHandle());
	}
	
	private native void setNativeFontHandle(int windowHandle, int fontHandle);
	
	/**
	 * ピアのフォアグラウンドカラーを設定する
	 */
	public void setForeground(Color color) {
		// Since GNU Classpath 0.19
		if (color == null) {
			color = SystemColor.windowText;
		}
		setNativeForeground(getWindowHandle(), color.getRGB());
	}
	
	private native void setNativeForeground(int windowHandle,
											int rgb);

	/**
	 * ピアの表示状態を変更する
	 */
	public void setVisible(final boolean visible) {
		setNativeVisible(getWindowHandle(), visible);

		// GNU ClasspathのComponent実装は、isShowing()がfalseの間は
		// ComponentEvent.MOVED, RESIZEDをpostしてくれない。
		// 最初に表示された際にComponentEventがpostされないとうまく動作しないアプリケーションが
		// あるため、1回目の表示時にはComponentEventをpostする
		if (visible && ! this.hasBeenShown) {
			Component c = getComponent();
			queue.postEvent(new ComponentEvent(c, ComponentEvent.COMPONENT_MOVED));
			queue.postEvent(new ComponentEvent(c, ComponentEvent.COMPONENT_RESIZED));
			this.hasBeenShown = true;
		}
	}
	
	private native void setNativeVisible(int windowHandle, boolean visible);
	
	/**
	 * このピアを表示する
	 */
	public void show() {
		setVisible(true);
	}
	
	/**
	 * このピアを非表示にする
	 */
	public void hide() {
		setVisible(false);
	}

	/** 
	 * Get the graphics configuration of the component. The color model
	 * of the component can be derived from the configuration.
	 */
	public GraphicsConfiguration getGraphicsConfiguration() {
		return GraphicsEnvironment.getLocalGraphicsEnvironment()
                                                 .getDefaultScreenDevice()
                                                 .getDefaultConfiguration();
	}

	/**
	 * Part of an older API, no longer needed.
	 */
	public void setEventMask (long mask) {
		// 未実装
	}

  // Methods below are introduced since 1.1

	public boolean isObscured() {
		return true;
	}

	public boolean canDetermineObscurity() {
		return false;
	}

	public void coalescePaintEvent(PaintEvent e) {
		// 何もしない
	}

	public void updateCursorImmediately() {
		// 何もしない
	}

	public VolatileImage createVolatileImage(int width, int height) {
		GraphicsConfiguration config = getGraphicsConfiguration();
		if (config == null) {
			return null;
		} else {
			return config.createCompatibleVolatileImage(width, height);
		}
	}
	
	public boolean handlesWheelScrolling() {
		// 未実装
		return false;
	}

	public void createBuffers(int x, BufferCapabilities capabilities) throws AWTException {
		// 未実装
		throw new UnsupportedOperationException("Not implemented");
	}

	public Image getBackBuffer() {
		// 未実装
		throw new UnsupportedOperationException("Not implemented");
	}

	public void flip(BufferCapabilities.FlipContents contents) {
		// 未実装
		throw new UnsupportedOperationException("Not implemented");
	}

	public void destroyBuffers() {
		// 未実装
		throw new UnsupportedOperationException("Not implemented");
	}
 
	protected native String getNativeText(int windowHandle);
	
 	protected native void setNativeText(int windowHandle, String text); 


	
	/**
	 * ネイティブの親ウインドウを変更する
	 */
	private native void setNativeParent(int childWindowHandle,
										int newParentWindowHandle);

	/**
	 * MouseEventをEventQueueにpostする
	 */
	void postMouseEvent(int id,
						int modifiers,
						int x,
						int y, 
						int clickCount,
						boolean popupTrigger,
						int button) {
	
		queue.postEvent(
			new MouseEvent(getComponent(),
						   id,
						   System.currentTimeMillis(),
						   modifiers,
						   x,
						   y,
						   clickCount,
						   popupTrigger,
						   button)
	    );
	}

	/**
	 * MouseWheelEventをEventQueueにpostする
	 */
	void postMouseWheelEvent(int id,
							 int modifiers,
							 int x,
							 int y, 
							 int clickCount,
							 boolean popupTrigger,
							 int scrollType,
							 int scrollAmount,
							 int wheelRotation) {
	
		queue.postEvent(
			new MouseWheelEvent(getComponent(),
							   id,
							   System.currentTimeMillis(),
							   modifiers,
							   x,
							   y,
							   clickCount,
							   popupTrigger,
							   scrollType,
							   scrollAmount,
							   wheelRotation)
	    );
	}

	/**
	 * PaintEventをpostする
	 */
	void postPaintEvent(int id,
								  int x,
								  int y,
								  int width,
								  int height) {
		queue.postEvent(
			new PaintEvent(getComponent(),
				   		   id,
						   new Rectangle(x, y, width, height)));
	}
	
	/**
	 * FocusEventをpostする
	 */
	protected void postFocusEvent(int id, boolean temporary, WCEComponentPeer oppositePeer) {
		Component opposite = (oppositePeer != null) ? oppositePeer.getComponent() : null;
		queue.postEvent(new FocusEvent(getComponent(), id, temporary, opposite));
	}

	/**
	 * KeyEventをpostする
	 */
	void postKeyEvent(int id,
					  long when,
					  int modifiers,
					  int keyCode,
					  char keyChar,
					  int keyLocation) {
		queue.postEvent(new KeyEvent(getComponent(),
									 id,
									 System.currentTimeMillis(),
									 modifiers,
									 keyCode,
									 keyChar,
									 keyLocation));
	}
						

  public Rectangle getBounds()
  {
      // FIXME: implement
    return null;
  }
  public void reparent(ContainerPeer parent)
  {
    // FIXME: implement
  
  }
  public void setBounds(int x, int y, int width, int height, int z)
  {
    // FIXME: implement
      setBounds (x, y, width, height);
   
  }
  public boolean isReparentSupported()
  {
    // FIXME: implement

    return false;
  }
  public void layout()
  {
    // FIXME: implement
 
  }

	protected native boolean hasNativeFocus(int windowHandle);

  public boolean requestFocus(Component lightweightChild, boolean temporary,
                              boolean focusedWindowChangeAllowed,
                              long time, sun.awt.CausedFocusEvent.Cause cause)
  {
    // TODO: Implement this properly and remove the other requestFocus()
    // methods.
    return true;
  }
	
}


