/*
 * 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.Frame;
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.BufferedImage;
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);
    // createImage(int x, int y) should return BufferedImage
    // Bug #15171
    return new BufferedImage(width, height, BufferedImage.TYPE_USHORT_565_RGB);
  }

  /**
   * disable this peer.
   */
  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 Window))
      {
        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)
  {
    return new WCEVolatileImage(new WCEOffscreenImage(this, 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);

  /**
   * ネイティブの親ウインドウを変更する
   */
  protected 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;
  }

}
