/*
 * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
 */

package com.sun.javafx.tk.quantum;

import java.nio.IntBuffer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javafx.application.Platform;
import javafx.event.EventType;
import javafx.scene.input.InputEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.Dragboard;

import com.sun.javafx.cursor.CursorFrame;
import com.sun.javafx.embed.*;
import com.sun.javafx.scene.traversal.Direction;
import com.sun.javafx.sg.PGNode;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.Toolkit;
import com.sun.prism.impl.PrismSettings;
import com.sun.prism.render.ToolkitInterface;

/**
 * @author Artem Ananiev
 */
public class EmbeddedScene extends GlassScene implements EmbeddedSceneInterface {

    private AbstractPainter         painter;
    private PaintRenderJob          paintRenderJob;

    protected Lock          sizeLock = new ReentrantLock();
    protected IntBuffer     textureBits;
    // TODO: synchronize access to embedder from ET and RT
    protected HostInterface host;
    protected boolean       needsReset = true;
    protected int           width;
    protected int           height;

    private final EmbeddedSceneDnD dndDelegate;

    public EmbeddedScene(HostInterface host) {
        this(host, false);
    }

    public EmbeddedScene(HostInterface host, boolean depthBuffer) {
        super(PrismSettings.verbose, depthBuffer);
        this.dndDelegate = new EmbeddedSceneDnD(this);

        this.host = host;
        this.host.setEmbeddedScene(this);

        PaintCollector collector = PaintCollector.getInstance();
        painter = new EmbeddedPainter(this);
        paintRenderJob = new PaintRenderJob(this, collector.getRendered(), (Runnable)painter);
    }

    @Override protected boolean isSynchronous() {
        return false;
    }

    @Override public void setRoot(PGNode root) {
        super.setRoot(root);
        painter.setRoot((NGNode)root);
    }

    @Override
    public void markDirty() {
        super.markDirty();
        host.repaint();
    }
    
    @Override
    public Dragboard createDragboard() {
        return dndDelegate.createDragboard();
    }

    @Override
    public void enableInputMethodEvents(boolean enable) {
        if (verbose) {
            System.err.println("EmbeddedScene.enableInputMethodEvents " + enable);
        }
    }

    @Override
    void stageVisible(boolean visible) {
        if (!visible) {
            host.setEmbeddedScene(null);
            host = null;
        }
        super.stageVisible(visible);
    }

    // EmbeddedSceneInterface methods

    @Override
    public void repaint() {
        Toolkit tk = Toolkit.getToolkit();
        ToolkitInterface renderer = (ToolkitInterface)tk;
        renderer.addRenderJob(paintRenderJob);
    }

    @Override
    public boolean traverseOut(Direction dir) {
        if (dir == Direction.NEXT) {
            return host.traverseFocusOut(true);
        } else if (dir == Direction.PREVIOUS) {
            return host.traverseFocusOut(false);
        }
        return false;
    }

    @Override
    public void setSize(final int width, final int height) {
        sizeLock.lock();
        try {
            this.width = width;
            this.height = height;
            needsReset = true;
        } finally {
            sizeLock.unlock();
        }

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                if (sceneListener != null) {
                    sceneListener.changedSize(width, height);
                }
            }
        });

        entireSceneNeedsRepaint();
    }

    @Override
    public boolean getPixels(IntBuffer dest, int width, int height) {
        sizeLock.lock();
        try {
            if ((textureBits == null) || needsReset ||
                (this.width != width) || (this.height != height))
            {
                return false;
            }
            dest.rewind();
            textureBits.rewind();
            dest.put(textureBits);
            return true;
        } finally {
            sizeLock.unlock();
        }
    }

    @Override
    public void mouseEvent(final int type, final int button,
                           final boolean primaryBtnDown, final boolean middleBtnDown, final boolean secondaryBtnDown,
                           final int clickCount, final int x, final int y, final int xAbs, final int yAbs,
                           final boolean shift, final boolean ctrl, final boolean alt, final boolean meta,
                           final int wheelRotation, final boolean popupTrigger)
    {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                if (sceneListener != null) {
                    EventType<? extends InputEvent> eventType =
                            AbstractEvents.mouseIDToFXEventID(type);

                    if (eventType == ScrollEvent.SCROLL) {
                        sceneListener.scrollEvent(ScrollEvent.SCROLL, 0, -wheelRotation, 0, 0, 40.0, 40.0,
                                0, 0, 0, 0, 0,
                                x, y, xAbs, yAbs, shift, ctrl, alt, meta, false, false);
                    } else {
                        MouseEvent mouseEvent = MouseEvent.impl_mouseEvent(
                                x, y, xAbs, yAbs,
                                AbstractEvents.mouseButtonToFXMouseButton(button), clickCount,
                                shift, ctrl, alt, meta, popupTrigger,
                                primaryBtnDown, middleBtnDown, secondaryBtnDown,
                                false, // do we know if it's synthesized? RT-20142
                                (EventType<MouseEvent>) eventType);
                        sceneListener.mouseEvent(mouseEvent);
                    }
                }
            }
        });
    }

    @Override
    public void keyEvent(final int type, final int key, final char[] ch, final int modifiers) {
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                if (sceneListener != null) {
                    String chars =
                            (type == AbstractEvents.KEYEVENT_TYPED) ?
                                    new String(ch) :
                                    Character.toString((char)key);
                    String text = new String(ch);
                    boolean shiftDown = (modifiers & AbstractEvents.MODIFIER_SHIFT) != 0;
                    boolean controlDown = (modifiers & AbstractEvents.MODIFIER_CONTROL) != 0;
                    boolean altDown = (modifiers & AbstractEvents.MODIFIER_ALT) != 0;
                    boolean metaDown = (modifiers & AbstractEvents.MODIFIER_META) != 0;
                    KeyEvent keyEvent = KeyEvent.impl_keyEvent(null, chars, text, key,
                            shiftDown, controlDown, altDown, metaDown,
                            AbstractEvents.keyIDToFXEventType(type));
                    sceneListener.keyEvent(keyEvent);
                }
            }
        });
    }

    @Override
    public void setCursor(final Object cursor) {
        super.setCursor(cursor);
        host.setCursor((CursorFrame) cursor);
    }

    @Override
    public void setDragStartListener(EmbeddedSceneDragStartListenerInterface l) {
        dndDelegate.setDragStartListener(l);
    }

    @Override
    public EmbeddedSceneDropTargetInterface createDropTarget() {
        return dndDelegate.createDropTarget();
    }
}