/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 *
 * Contributor(s):
 *
 * Portions Copyrighted 2011 Sun Microsystems, Inc.
 */
package org.netbeans.modules.debugger.jpda.visual;

import com.sun.jdi.ArrayReference;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassObjectReference;
import com.sun.jdi.ClassType;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.netbeans.api.debugger.DebuggerEngine;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
import org.netbeans.modules.debugger.jpda.expr.EvaluatorVisitor;
import org.netbeans.modules.debugger.jpda.expr.InvocationExceptionTranslated;
import org.netbeans.modules.debugger.jpda.jdi.ArrayReferenceWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ClassNotPreparedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ClassObjectReferenceWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ClassTypeWrapper;
import org.netbeans.modules.debugger.jpda.jdi.InternalExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ObjectCollectedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ObjectReferenceWrapper;
import org.netbeans.modules.debugger.jpda.jdi.ReferenceTypeWrapper;
import org.netbeans.modules.debugger.jpda.jdi.StringReferenceWrapper;
import org.netbeans.modules.debugger.jpda.jdi.VMDisconnectedExceptionWrapper;
import org.netbeans.modules.debugger.jpda.models.JPDAThreadImpl;
import org.netbeans.modules.debugger.jpda.visual.RemoteServices.ServiceType;
import org.netbeans.modules.debugger.jpda.visual.actions.ComponentBreakpointActionProvider;
import org.netbeans.modules.debugger.jpda.visual.actions.CreateFixedWatchAction;
import org.netbeans.modules.debugger.jpda.visual.actions.GoToAddIntoHierarchyAction;
import org.netbeans.modules.debugger.jpda.visual.actions.GoToFieldDeclarationAction;
import org.netbeans.modules.debugger.jpda.visual.actions.GoToSourceAction;
import org.netbeans.modules.debugger.jpda.visual.actions.ShowListenersAction;
import org.netbeans.modules.debugger.jpda.visual.actions.ToggleComponentBreakpointAction;
import org.netbeans.modules.debugger.jpda.visual.breakpoints.ComponentBreakpoint;
import org.netbeans.modules.debugger.jpda.visual.models.ComponentBreakpointsActionsProvider;
import org.netbeans.modules.debugger.jpda.visual.spi.ComponentInfo;
import org.netbeans.modules.debugger.jpda.visual.spi.RemoteScreenshot;
import org.openide.actions.PropertiesAction;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.NodeAction;

/**
 * Takes screenshot of a remote application.
 * 
 * @author Martin Entlicher
 */
public class RemoteAWTScreenshot {
    
    private static final Logger logger = Logger.getLogger(RemoteAWTScreenshot.class.getName());
    
    static final String AWTThreadName = "AWT-EventQueue-";  // NOI18N
    
    private static final RemoteScreenshot[] NO_SCREENSHOTS = new RemoteScreenshot[] {};
    
    static final boolean FAST_SNAPSHOT_RETRIEVAL = getBooleanProperty("visualDebugger.fastSnapshot", true);   // NOI18N
    static final boolean FAST_FIELDS_SEARCH = getBooleanProperty("visualDebugger.fastFieldsSearch", true);   // NOI18N
    private static final char STRING_DELIMITER = (char) 3;   // ETX (end of text)
    
    public static boolean getBooleanProperty(String name, boolean defaultValue) {
        boolean result = defaultValue;
        String val = System.getProperty(name);
        if (val != null) {
            result = Boolean.parseBoolean(val);
        }
        return result;
    }
    

    private RemoteAWTScreenshot() {}
    
    private static RemoteScreenshot createRemoteAWTScreenshot(DebuggerEngine engine, String title, int width, int height, int[] dataArray, AWTComponentInfo componentInfo) {
        final BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        WritableRaster raster = bi.getRaster();
        raster.setDataElements(0, 0, width, height, dataArray);
        if (FAST_FIELDS_SEARCH) {
            ComponentsFieldFinder.findFieldsForComponents(componentInfo);
        }
        return new RemoteScreenshot(engine, title, width, height, bi, componentInfo);
    }
    
    public static RemoteScreenshot[] takeCurrent() throws RetrievalException {
        DebuggerEngine engine = DebuggerManager.getDebuggerManager().getCurrentEngine();
        if (engine != null) {
            JPDADebugger debugger = engine.lookupFirst(null, JPDADebugger.class);
            logger.fine("Debugger = "+debugger);
            if (debugger != null) {
                return takeCurrent(debugger, engine);
            }
        }
        return NO_SCREENSHOTS;
    }

    public static RemoteScreenshot[] takeCurrent(JPDADebugger debugger) throws RetrievalException {
        DebuggerEngine engine = ((JPDADebuggerImpl) debugger).getSession().getCurrentEngine();
        return takeCurrent(debugger, engine);
    }
    
    private static RemoteScreenshot[] takeCurrent(JPDADebugger debugger, DebuggerEngine engine) throws RetrievalException {
        List<JPDAThread> allThreads = debugger.getThreadsCollector().getAllThreads();
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "Threads = {0}", allThreads);
        }
        RemoteScreenshot[] rs = NO_SCREENSHOTS;
        for (JPDAThread t : allThreads) {
            if (t.getName().startsWith(AWTThreadName)) {
                long t1 = System.nanoTime();
                try {
                    RemoteScreenshot[] rst = take(t, engine);
                    if (rst.length > 0) {
                        if (rs.length > 0) {
                            RemoteScreenshot[] nrs = new RemoteScreenshot[rs.length + rst.length];
                            System.arraycopy(rs, 0, nrs, 0, rs.length);
                            System.arraycopy(rst, 0, nrs, rs.length, rst.length);
                            rs = nrs;
                        } else {
                            rs = rst;
                        }
                    }
                    /*
                } catch (InvocationException iex) {
                    //ObjectReference exception = iex.exception();
                    Exceptions.printStackTrace(iex);

                    final InvocationExceptionTranslated iextr = new InvocationExceptionTranslated(iex, (JPDADebuggerImpl) debugger);
                    RequestProcessor.getDefault().post(new Runnable() {
                        @Override
                        public void run() {
                            iextr.getMessage();
                            iextr.getLocalizedMessage();
                            iextr.getCause();
                            iextr.getStackTrace();
                            Exceptions.printStackTrace(iextr);
                        }
                    }, 100);

                } catch (Exception ex) {
                    Exceptions.printStackTrace(ex);
                }
                     */
                //break;
                } finally {
                    long t2 = System.nanoTime();
                    long ns = t2 - t1;
                    long ms = ns/1000000;
                    logger.info("GUI Snaphot taken in "+((ms > 0) ? (ms + " ms "+(ns - ms*1000000)+" ns.") : (ns+" ns.")));
                }
            }
        }
        return rs;
    }
    
    public static RemoteScreenshot[] take(final JPDAThread t, final DebuggerEngine engine) throws RetrievalException {//throws ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException, InvocationException {
        //RemoteScreenshot[] screenshots = NO_SCREENSHOTS;
        final ThreadReference tawt = ((JPDAThreadImpl) t).getThreadReference();
        final VirtualMachine vm = tawt.virtualMachine();
        final ClassType windowClass;
        try {
            windowClass = RemoteServices.getClass(vm, "java.awt.Window");
        } catch (InternalExceptionWrapper ex) {
            return NO_SCREENSHOTS;
        } catch (VMDisconnectedExceptionWrapper ex) {
            return NO_SCREENSHOTS;
        } catch (ObjectCollectedExceptionWrapper ex) {
            return NO_SCREENSHOTS;
        }
        if (windowClass == null) {
            logger.fine("No Window");
            return NO_SCREENSHOTS;
        }

        final JPDADebugger debugger = ((JPDAThreadImpl) t).getDebugger();
        ClassObjectReference serviceClassObject = RemoteServices.getServiceClass(debugger);
        final ClassType serviceClass;
        try {
            serviceClass = (ClassType) ClassObjectReferenceWrapper.reflectedType(serviceClassObject);
        } catch (InternalExceptionWrapper | ObjectCollectedExceptionWrapper | VMDisconnectedExceptionWrapper ex) {
            return NO_SCREENSHOTS;
        }
        final List<RemoteScreenshot> screenshots = new ArrayList<RemoteScreenshot>();
        final RetrievalException[] retrievalExceptionPtr = new RetrievalException[] { null };
        try {
            RemoteServices.runOnStoppedThread(t, new Runnable() {
                @Override
                public void run() {
                    logger.fine("RemoteScreenshot.take("+t+")");
                    /*
                     * Run following code in the target VM:
                       Window[] windows = Window.getWindows();
                       for (Window w : windows) {
                           if (!w.isVisible()) {
                               continue;
                           }
                           Dimension d = w.getSize();
                           BufferedImage bi = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
                           Graphics g = bi.createGraphics();
                           w.paint(g);
                           Raster raster = bi.getData();
                           Object data = raster.getDataElements(0, 0, d.width, d.height, null);
                       }
                     */
                    try {
                        if (FAST_SNAPSHOT_RETRIEVAL) {
                            final Method getGUISnapshots = ClassTypeWrapper.concreteMethodByName(serviceClass, "getGUISnapshots", "()[Lorg/netbeans/modules/debugger/jpda/visual/remote/RemoteAWTService$Snapshot;");
                            ArrayReference snapshotsArray = (ArrayReference) ClassTypeWrapper.invokeMethod(serviceClass, tawt, getGUISnapshots, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                            List<Value> snapshots = ArrayReferenceWrapper.getValues(snapshotsArray);
                            for (Value snapshot : snapshots) {
                                ObjectReference snapshotObj = (ObjectReference) snapshot;
                                ReferenceType rt = ObjectReferenceWrapper.referenceType(snapshotObj);
                                StringReference allIntDataString = (StringReference) ObjectReferenceWrapper.getValue(snapshotObj, ReferenceTypeWrapper.fieldByName(rt, "allIntDataString"));
                                int[] allIntData = createIntArrayFromString(StringReferenceWrapper.value(allIntDataString));
                                StringReference allNamesString = (StringReference) ObjectReferenceWrapper.getValue(snapshotObj, ReferenceTypeWrapper.fieldByName(rt, "allNamesString"));
                                ArrayReference allComponentsArray = (ArrayReference) ObjectReferenceWrapper.getValue(snapshotObj, ReferenceTypeWrapper.fieldByName(rt, "allComponentsArray"));
                                StringReference componentsAddAtString = (StringReference) ObjectReferenceWrapper.getValue(snapshotObj, ReferenceTypeWrapper.fieldByName(rt, "componentsAddAt"));
                                String allNames = StringReferenceWrapper.value(allNamesString);
                                String componentsAddAt = StringReferenceWrapper.value(componentsAddAtString);
                                int ititle = allNames.indexOf(STRING_DELIMITER);
                                String title = new String(allNames.substring(0, ititle)).intern(); // Create a new String - do not hold the whole allNames.
                                if (title.length() == 1 && title.charAt(0) == 0) {
                                    title = null;
                                }
                                int ndata = allIntData[2];
                                int[] dataArray = new int[ndata];
                                System.arraycopy(allIntData, 3, dataArray, 0, ndata);
                                AWTComponentInfo componentInfo = new AWTComponentInfo((JPDAThreadImpl) t,
                                                                                      allIntData, new int[] { ndata + 3 },
                                                                                      allNames, new int[] { ititle + 1 },
                                                                                      allComponentsArray.getValues(), new int[] { 0 },
                                                                                      componentsAddAt, new int[] { 0 });
                                screenshots.add(createRemoteAWTScreenshot(engine,
                                                                          title,
                                                                          allIntData[0], allIntData[1],
                                                                          dataArray, componentInfo));
                            }
                            // Clear the snapshots reference to enable GC.
                            ClassTypeWrapper.setValue(serviceClass, ReferenceTypeWrapper.fieldByName(serviceClass, "lastGUISnapshots"), null);
                            return;
                        }
                        
                        //Method getWindows = null;//windowClass.concreteMethodByName("getOwnerlessWindows", "()[Ljava/awt/Window;");
                        final Method getWindows = windowClass.concreteMethodByName("getWindows", "()[Ljava/awt/Window;");
                        if (getWindows == null) {
                            logger.fine("No getWindows() method!");
                            String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingMethod", "java.awt.Window.getWindows()");
                            throw new RetrievalException(msg);
                        }
                        ArrayReference windowsArray = (ArrayReference) ((ClassType) windowClass).invokeMethod(tawt, getWindows, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                        List<Value> windows = windowsArray.getValues();
                        logger.fine("Have "+windows.size()+" window(s).");

                        Method isVisible = windowClass.concreteMethodByName("isVisible", "()Z");
                        if (isVisible == null) {
                            logger.fine("No isVisible() method!");
                            String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingMethod", "java.awt.Window.isVisible()");
                            throw new RetrievalException(msg);
                        }
                        Method getOwner = windowClass.concreteMethodByName("getOwner", "()Ljava/awt/Window;");
                        if (getOwner == null) {
                            logger.fine("No getOwner() method!");
                            String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingMethod", "java.awt.Window.getOwner()");
                            throw new RetrievalException(msg);
                        }
                        Method getSize = windowClass.concreteMethodByName("getSize", "()Ljava/awt/Dimension;");
                        if (getSize == null) {
                            logger.fine("No getSize() method!");
                            String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingMethod", "java.awt.Window.getSize()");
                            throw new RetrievalException(msg);
                        }
                        ClassType dimensionClass = RemoteServices.getClass(vm, "java.awt.Dimension");
                        if (dimensionClass == null) {
                            logger.fine("No Dimension");
                            String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingClass", "java.awt.Dimension");
                            throw new RetrievalException(msg);
                        }
                        ClassType bufferedImageClass = RemoteServices.getClass(vm, "java.awt.image.BufferedImage");
                        if (bufferedImageClass == null) {
                            logger.fine("No BufferedImage class.");
                            String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingClass", "java.awt.image.BufferedImage");
                            throw new RetrievalException(msg);
                        }
                        Method bufferedImageConstructor = bufferedImageClass.concreteMethodByName("<init>", "(III)V");
                        Method createGraphics = bufferedImageClass.concreteMethodByName("createGraphics", "()Ljava/awt/Graphics2D;");
                        if (createGraphics == null) {
                            logger.fine("createGraphics() method is not found!");
                            String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingMethod", "java.awt.image.BufferedImage.createGraphics()");
                            throw new RetrievalException(msg);
                        }

                        ClassType frameClass = RemoteServices.getClass(vm, "java.awt.Frame");
                        Method getFrameTitle = null;
                        if (frameClass != null) {
                            getFrameTitle = frameClass.concreteMethodByName("getTitle", "()Ljava/lang/String;");
                        }
                        ClassType dialogClass = RemoteServices.getClass(vm, "java.awt.Dialog");
                        Method getDialogTitle = null;
                        if (dialogClass != null) {
                            getDialogTitle = dialogClass.concreteMethodByName("getTitle", "()Ljava/lang/String;");
                        }

                        for (Value windowValue : windows) {
                            ObjectReference window = (ObjectReference) windowValue;
                            //dumpHierarchy(window);

                            BooleanValue visible = (BooleanValue) window.invokeMethod(tawt, isVisible, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                            if (!visible.value()) {
                                // Ignore windows that are not visible.
                                // TODO: mark them as not visible.
                                //continue;
                            }
                            Object owner = window.invokeMethod(tawt, getOwner, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                            if (owner != null) {
                                // An owned window
                                //continue;
                            }

                            ObjectReference sizeDimension = (ObjectReference) window.invokeMethod(tawt, getSize, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                            Field field = dimensionClass.fieldByName("width");
                            IntegerValue widthValue = (IntegerValue) sizeDimension.getValue(field);
                            int width = widthValue.value();
                            field = dimensionClass.fieldByName("height");
                            IntegerValue heightValue = (IntegerValue) sizeDimension.getValue(field);
                            int height = heightValue.value();
                            logger.log(Level.FINE, "The size is {0} x {1}", new Object[]{width, height});

                            List<? extends Value> args = Arrays.asList(widthValue, heightValue, vm.mirrorOf(BufferedImage.TYPE_INT_ARGB));
                            ObjectReference bufferedImage = bufferedImageClass.newInstance(tawt, bufferedImageConstructor, args, ObjectReference.INVOKE_SINGLE_THREADED);
                            ObjectReference graphics = (ObjectReference) bufferedImage.invokeMethod(tawt, createGraphics, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);


                            Method paint = windowClass.concreteMethodByName("paint", "(Ljava/awt/Graphics;)V");
                            window.invokeMethod(tawt, paint, Arrays.asList(graphics), ObjectReference.INVOKE_SINGLE_THREADED);

                            /*
                            // getPeer() - java.awt.peer.ComponentPeer, ComponentPeer.paint()
                            Method getPeer = windowClass.concreteMethodByName("getPeer", "()Ljava/awt/peer/ComponentPeer;");
                            ObjectReference peer = (ObjectReference) window.invokeMethod(tawt, getPeer, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                            Method paint = ((ClassType) peer.referenceType()).concreteMethodByName("paint", "(Ljava/awt/Graphics;)V");
                            peer.invokeMethod(tawt, paint, Arrays.asList(graphics), ObjectReference.INVOKE_SINGLE_THREADED);
                            - paints nothing! */

                            Method getData = bufferedImageClass.concreteMethodByName("getData", "()Ljava/awt/image/Raster;");
                            ObjectReference raster = (ObjectReference) bufferedImage.invokeMethod(tawt, getData, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);

                            Method getDataElements = ((ClassType) raster.referenceType()).concreteMethodByName("getDataElements", "(IIIILjava/lang/Object;)Ljava/lang/Object;");
                            IntegerValue zero = vm.mirrorOf(0);
                            ArrayReference data = (ArrayReference) raster.invokeMethod(tawt, getDataElements, Arrays.asList(zero, zero, widthValue, heightValue, null), ObjectReference.INVOKE_SINGLE_THREADED);

                            logger.log(Level.FINE, "Image data length = {0}", data.length());

                            List<Value> dataValues = data.getValues();
                            int[] dataArray = new int[data.length()];
                            int i = 0;
                            for (Value v : dataValues) {
                                dataArray[i++] = ((IntegerValue) v).value();
                            }

                            String title = null;
                            if (frameClass != null && EvaluatorVisitor.instanceOf(window.referenceType(), frameClass)) {
                                Value v = window.invokeMethod(tawt, getFrameTitle, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                                if (v instanceof StringReference) {
                                    StringReference sr = (StringReference) v;
                                    title = sr.value();
                                }
                            }
                            if (dialogClass != null && EvaluatorVisitor.instanceOf(window.referenceType(), dialogClass)) {
                                Value v = window.invokeMethod(tawt, getDialogTitle, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
                                if (v instanceof StringReference) {
                                    StringReference sr = (StringReference) v;
                                    title = sr.value();
                                }
                            }

                            AWTComponentInfo componentInfo = new AWTComponentInfo((JPDAThreadImpl) t, window);
                            screenshots.add(createRemoteAWTScreenshot(engine, title, width, height, dataArray, componentInfo));
                        }
                    } catch (RetrievalException rex) {
                        retrievalExceptionPtr[0] = rex;
                    } catch (InvocationException iex) {
                        //Exceptions.printStackTrace(iex);
                        ((JPDAThreadImpl) t).notifyMethodInvokeDone();
                        final InvocationExceptionTranslated iextr = new InvocationExceptionTranslated(iex, (JPDADebuggerImpl) debugger);
                        // Initialize the translated exception:
                        iextr.setPreferredThread((JPDAThreadImpl) t);
                        iextr.getMessage();
                        iextr.getLocalizedMessage();
                        iextr.getCause();
                        iextr.getStackTrace();
                        logger.log(Level.INFO, iex.getMessage(), iextr);
                        retrievalExceptionPtr[0] =  new RetrievalException(iex.getMessage(), iextr);
                    } catch (InvalidTypeException itex) {
                        retrievalExceptionPtr[0] = new RetrievalException(itex.getMessage(), itex);
                    } catch (ClassNotLoadedException cnlex) {
                        return ;//NO_SCREENSHOTS;
                    } catch (IncompatibleThreadStateException itsex) {
                        retrievalExceptionPtr[0] =  new RetrievalException(itsex.getMessage(), itsex);
                    } catch (ClassNotPreparedExceptionWrapper cnpex) {
                        return ;//NO_SCREENSHOTS;
                    } catch (InternalExceptionWrapper iex) {
                        return ;//NO_SCREENSHOTS;
                    } catch (ObjectCollectedExceptionWrapper ocex) {
                        Exceptions.printStackTrace(ocex);
                        return ;
                    } catch (VMDisconnectedExceptionWrapper vmdex) {
                        return ;
                    }
                }

            }, RemoteServices.ServiceType.AWT);
            
        } catch (PropertyVetoException pvex) {
            // Can not invoke methods
            throw new RetrievalException(pvex.getMessage(), pvex);
        }
        if (retrievalExceptionPtr[0] != null) {
            throw retrievalExceptionPtr[0];
        }
        return screenshots.toArray(new RemoteScreenshot[] {});
    }
    
    private static void retrieveComponents(final AWTComponentInfo ci, JPDAThreadImpl t, VirtualMachine vm,
                                           ClassType componentClass, ClassType containerClass, ObjectReference component,
                                           Method getComponents, Method getBounds,
                                           int shiftx, int shifty)
                                           throws InvalidTypeException, ClassNotLoadedException,
                                                  IncompatibleThreadStateException, InvocationException,
                                                  RetrievalException {
        
        ThreadReference tawt = t.getThreadReference();
        ObjectReference rectangle = (ObjectReference) component.invokeMethod(tawt, getBounds, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
        ClassType rectangleClass;
        try {
            rectangleClass = RemoteServices.getClass(vm, "java.awt.Rectangle");
        } catch (InternalExceptionWrapper ex) {
            return ;
        } catch (VMDisconnectedExceptionWrapper ex) {
            return ;
        } catch (ObjectCollectedExceptionWrapper ex) {
            return ;
        }
        Field fx = rectangleClass.fieldByName("x");
        Field fy = rectangleClass.fieldByName("y");
        Field fwidth = rectangleClass.fieldByName("width");
        Field fheight = rectangleClass.fieldByName("height");
        Map<Field, Value> rvalues = rectangle.getValues(Arrays.asList(new Field[] {fx, fy, fwidth, fheight}));
        Rectangle r = new Rectangle();
        r.x = ((IntegerValue) rvalues.get(fx)).value();
        r.y = ((IntegerValue) rvalues.get(fy)).value();
        r.width = ((IntegerValue) rvalues.get(fwidth)).value();
        r.height = ((IntegerValue) rvalues.get(fheight)).value();
        ci.setBounds(r);
        if (shiftx == Integer.MIN_VALUE && shifty == Integer.MIN_VALUE) {
            shiftx = shifty = 0; // Do not shift the window as such
        } else {
            shiftx += r.x;
            shifty += r.y;
            ci.setWindowBounds(new Rectangle(shiftx, shifty, r.width, r.height));
        }
        Method getName = componentClass.concreteMethodByName("getName", "()Ljava/lang/String;");
        StringReference name = (StringReference) component.invokeMethod(tawt, getName, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
        if (name != null) {
            ci.setName(name.value());
        }
        ci.setComponent(component);
        ci.setType(component.referenceType().name());
        logger.log(Level.FINE, "  Component ''{0}'' class=''{1}'' bounds = {2}", new Object[]{ci.getName(), ci.getType(), r});
        
//        ci.addPropertySet(new PropertySet("main", "Main", "The main properties") {
//            @Override
//            public Property<?>[] getProperties() {
//                return new Property[] {
//                    new ReadOnly("name", String.class, "Component Name", "The name of the component") {
//                        @Override
//                        public Object getValue() throws IllegalAccessException, InvocationTargetException {
//                            return ci.getName();
//                        }
//                    },
//                    new ReadOnly("type", String.class, "Component Type", "The type of the component") {
//                        @Override
//                        public Object getValue() throws IllegalAccessException, InvocationTargetException {
//                            return ci.getType();
//                        }
//                    },
//                    new ReadOnly("bounds", String.class, "Component Bounds", "The bounds of the component in the window.") {
//                        @Override
//                        public Object getValue() throws IllegalAccessException, InvocationTargetException {
//                            Rectangle r = ci.getWindowBounds();
//                            return "[x=" + r.x + ",y=" + r.y + ",width=" + r.width + ",height=" + r.height + "]";
//                        }
//                    },
//                };
//            }
//        });
        
        if (isInstanceOfClass((ClassType) component.referenceType(), containerClass)) {
            ArrayReference componentsArray = (ArrayReference) component.invokeMethod(tawt, getComponents, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
            List<Value> components = componentsArray.getValues();
            logger.log(Level.FINE, "Have {0} component(s).", components.size());
            if (components.size() > 0) {
                AWTComponentInfo[] cis = new AWTComponentInfo[components.size()];
                int i = 0;
                for(Value cv : components) {
                    ObjectReference c = (ObjectReference) cv;
                    cis[i] = new AWTComponentInfo(t, c, shiftx, shifty);
                    i++;
                }
                ci.setSubComponents(cis);
            }
        }
    }
    
    /*
    private static AWTComponentInfo[] retrieveComponents(ThreadReference tawt, VirtualMachine vm,
                                                      ClassType containerClass, ObjectReference window,
                                                      Method getBounds)
                                                      throws InvalidTypeException, ClassNotLoadedException,
                                                             IncompatibleThreadStateException, InvocationException,
                                                             RetrievalException {
        Method getComponents = containerClass.concreteMethodByName("getComponents", "()[Ljava/awt/Component;");
        if (getComponents == null) {
            logger.severe("No getComponents() method!");
            return new AWTComponentInfo[] {};
        }
        ArrayReference componentsArray = (ArrayReference) window.invokeMethod(tawt, getComponents, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
        List<Value> components = componentsArray.getValues();
        logger.severe("Have "+components.size()+" component(s).");
        
        AWTComponentInfo[] cis = new AWTComponentInfo[components.size()];
        int i = 0;
        for(Value cv : components) {
            cis[i] = new AWTComponentInfo();
            ObjectReference c = (ObjectReference) cv;
            ObjectReference rectangle = (ObjectReference) c.invokeMethod(tawt, getBounds, Collections.EMPTY_LIST, ObjectReference.INVOKE_SINGLE_THREADED);
            ClassType rectangleClass = getClass(vm, "java.awt.Rectangle");
            Field fx = rectangleClass.fieldByName("x");
            Field fy = rectangleClass.fieldByName("y");
            Field fwidth = rectangleClass.fieldByName("width");
            Field fheight = rectangleClass.fieldByName("height");
            Map<Field, Value> rvalues = rectangle.getValues(Arrays.asList(new Field[] {fx, fy, fwidth, fheight}));
            Rectangle r = new Rectangle();
            r.x = ((IntegerValue) rvalues.get(fx)).value();
            r.y = ((IntegerValue) rvalues.get(fy)).value();
            r.width = ((IntegerValue) rvalues.get(fwidth)).value();
            r.height = ((IntegerValue) rvalues.get(fheight)).value();
            cis[i].bounds = r;
            logger.severe("  Component "+i+": bounds = "+r);
            
            if (isInstanceOfClass((ClassType) c.referenceType(), containerClass)) {
                cis[i].subComponents = retrieveComponents(tawt, vm, containerClass, c, getBounds);
            }
            
            i++;
        }
        return cis;
    }
    */
    
    private static int[] createIntArrayFromString(String s) {
        int i1 = 0;
        int i2 = s.indexOf('[');
        int n = Integer.parseInt(s.substring(i1, i2));
        int[] array = new int[n];
        for (int i = 0; i < n; i++) {
            i1 = i2 + 1;
            i2 = s.indexOf(',', i1);
            if (i2 < 0) {
                i2 = s.indexOf(']', i1);
            }
            array[i] = Integer.parseInt(s.substring(i1, i2));
        }
        return array;
    }
    
    private static boolean isInstanceOfClass(ClassType c1, ClassType c2) {
        if (c1.equals(c2)) {
            return true;
        }
        c1 = c1.superclass();
        if (c1 == null) {
            return false;
        }
        return isInstanceOfClass(c1, c2);
    }
    
    public static class AWTComponentInfo extends JavaComponentInfo {
        private int shiftX, shiftY;
        private boolean visible;
        private VirtualMachine vm;
        private ClassType containerClass, componentClass;
        private Method getBounds, getComponents;
        private String addAtStr;
        private Stack addAt;
        
        public AWTComponentInfo(JPDAThreadImpl t, ObjectReference component) throws RetrievalException {
            this(t, component, Integer.MIN_VALUE, Integer.MIN_VALUE);
        }
        
        public AWTComponentInfo(JPDAThreadImpl t, ObjectReference component, int shiftX, int shiftY) throws RetrievalException {
            super(t, component, ServiceType.AWT);
            
            vm = getThread().getDebugger().getVirtualMachine();
            if (vm == null) {
                throw RetrievalException.disconnected();
            }
            try {
                containerClass = RemoteServices.getClass(vm, "java.awt.Container");
                componentClass = RemoteServices.getClass(vm, "java.awt.Component");
            } catch (InternalExceptionWrapper ex) {
                throw new RetrievalException(ex.getLocalizedMessage(), ex);
            } catch (VMDisconnectedExceptionWrapper ex) {
                throw new RetrievalException(ex.getLocalizedMessage(), ex);
            } catch (ObjectCollectedExceptionWrapper ex) {
                throw new RetrievalException(ex.getLocalizedMessage(), ex);
            }
            getBounds = componentClass.concreteMethodByName("getBounds", "()Ljava/awt/Rectangle;");
            getComponents = containerClass.concreteMethodByName("getComponents", "()[Ljava/awt/Component;");
            if (getComponents == null) {
                logger.fine("No getComponents() method!");
                String msg = NbBundle.getMessage(RemoteAWTScreenshot.class, "MSG_ScreenshotNotTaken_MissingMethod", "java.awt.Container.getComponents()");
                throw new RetrievalException(msg);
            }
            
            this.shiftX = shiftX;
            this.shiftY = shiftY;
            this.visible = true;
            init();
        }
        
        public AWTComponentInfo(JPDAThreadImpl t, int[] allDataArray, int[] dposPtr,
                                String allNames, int[] inamePtr,
                                List<Value> allComponentsArray, int[] cposPtr,
                                String componentsAddAt, int[] iaddPtr) throws RetrievalException {
//            this(t, allDataArray, dpos, allNames, allComponentsArray, 0, Integer.MIN_VALUE, Integer.MIN_VALUE);
//        }
//        
//        public AWTComponentInfo(JPDAThreadImpl t, int[] allDataArray, int dpos,
//                                String allNames, List<Value> allComponentsArray, int cpos,
//                                int shiftX, int shiftY) throws RetrievalException {
            super(t, (ObjectReference) allComponentsArray.get(cposPtr[0]++), ServiceType.AWT);
            //this.shiftX = shiftX;
            //this.shiftY = shiftY;
            Rectangle bounds = new Rectangle();
            int dpos = dposPtr[0];
            //if (shiftX == Integer.MIN_VALUE && shiftY == Integer.MIN_VALUE) {
            //    bounds.x = 0; // Move to the origin, we do not care where it's on the screen.
            //    bounds.y = 0;
            //    dpos += 2;
            //} else {
                bounds.x = allDataArray[dpos++];
                bounds.y = allDataArray[dpos++];
                bounds.width = allDataArray[dpos++];
                bounds.height = allDataArray[dpos++];
            //}
            setBounds(bounds);
            this.shiftX = allDataArray[dpos++];
            this.shiftY = allDataArray[dpos++];
            visible = (allDataArray[dpos++] == 0) ? false : true;
            setWindowBounds(new Rectangle(shiftX, shiftY, bounds.width, bounds.height));
            int iname = allNames.indexOf(STRING_DELIMITER, inamePtr[0]);
            String name = allNames.substring(inamePtr[0], iname);
            if (name.length() == 1 && name.charAt(0) == 0) {
                name = null;
            } else {
                name = new String(name).intern(); // Do not hold the whole original String
            }
            setName(name);
            inamePtr[0] = iname + 1;
            
            int iaddAt = componentsAddAt.indexOf(STRING_DELIMITER, iaddPtr[0]);
            String addAt = componentsAddAt.substring(iaddPtr[0], iaddAt);
            if ("null".equals(addAt)) {
                addAt = null;
            }
            setAddAt(addAt);
            iaddPtr[0] = iaddAt + 1;
            
            int nsc = allDataArray[dpos++];
            dposPtr[0] = dpos;
            if (nsc > 0) {
                AWTComponentInfo[] cis = new AWTComponentInfo[nsc];
                for (int i = 0; i < nsc; i++) {
                    cis[i] = new AWTComponentInfo(getThread(), allDataArray, dposPtr,
                                                  allNames, inamePtr,
                                                  allComponentsArray, cposPtr,
                                                  componentsAddAt, iaddPtr);
                }
                setSubComponents(cis);
            }
            init();
        }
        
        private void setAddAt(String addAtStr) {
            this.addAtStr = addAtStr;
        }

        @Override
        public Stack getAddCallStack() {
            if (!FAST_SNAPSHOT_RETRIEVAL) {
                return super.getAddCallStack();
            }
            if (addAtStr == null) {
                return null;
            }
            if (addAt == null) {
                Stack.Frame f = Stack.Frame.parseLine(addAtStr);
                Stack.Frame[] frames = new Stack.Frame[] { f };
                addAt = new Stack(frames);
            }
            return addAt;
        }
        
        public boolean isVisible() {
            return visible;
        }
        
        @Override
        public Action[] getActions(boolean context) {
            //FieldInfo fieldInfo = getField();
            ObjectReference component = getComponent();
            ComponentBreakpoint b = ComponentBreakpointActionProvider.findBreakpoint(component);
            
            List<Action> actions = new ArrayList<Action>();
            actions.add(GoToFieldDeclarationAction.get(GoToFieldDeclarationAction.class));
            actions.add(GoToSourceAction.get(GoToSourceAction.class));
            if (getAddCallStack() != null) {
                actions.add(GoToAddIntoHierarchyAction.get(GoToAddIntoHierarchyAction.class));
            }
            actions.add(null);
            actions.add(ShowListenersAction.get(ShowListenersAction.class));
            actions.add(CreateFixedWatchAction.get(CreateFixedWatchAction.class));
            actions.add(null);
            actions.add(ToggleComponentBreakpointAction.get(ToggleComponentBreakpointAction.class));
            if (b != null) {
                actions.add(CBP_CUSTOMIZE_ACTION);
            }
            actions.add(null);
            actions.add(PropertiesAction.get(PropertiesAction.class));
            return actions.toArray(new Action[] {});
        }

        @Override
        public ComponentInfo findAt(int x, int y) {
            Rectangle bounds = getBounds();
            if (!bounds.contains(x, y)) {
                return null;
            }
            x -= bounds.x;
            y -= bounds.y;
            ComponentInfo[] subComponents = getSubComponents();
            if (subComponents != null) {
                ComponentInfo invisible = null;
                for (int i = 0; i < subComponents.length; i++) {
                    Rectangle sb = subComponents[i].getBounds();
                    if (sb.contains(x, y)) {
                        if (subComponents[i] instanceof AWTComponentInfo &&
                            !((AWTComponentInfo) subComponents[i]).isVisible()) {
                            if (invisible == null) {
                                invisible = subComponents[i];
                            }
                            continue;
                        }
                        ComponentInfo tci = subComponents[i].findAt(x, y);
                        Rectangle tbounds = tci.getBounds();
                        if (tbounds.width < bounds.width || tbounds.height < bounds.height) {
                            return tci;
                        }
                    }
                }
                if (invisible != null) {
                    ComponentInfo tci = invisible.findAt(x, y);
                    Rectangle tbounds = tci.getBounds();
                    if (tbounds.width < bounds.width || tbounds.height < bounds.height) {
                        return tci;
                    }
                }
            }
            return this;
        }

        @Override
        protected void retrieve() throws RetrievalException {
            if (componentClass == null) {
                return ;
            }
            try {
                retrieveComponents(this, getThread(), vm, componentClass, containerClass, getComponent(), getComponents, getBounds,
                        shiftX, shiftY);
                if (shiftX == Integer.MIN_VALUE && shiftY == Integer.MIN_VALUE) {
                    getBounds().x = 0; // Move to the origin, we do not care where it's on the screen.
                    getBounds().y = 0;
                }
            } catch (RetrievalException e) {
                throw e;
            } catch (Exception e) {
                throw new RetrievalException(e.getMessage(), e);
            }
        }
        
        private static final Action CBP_CUSTOMIZE_ACTION = new NodeAction() {

            @Override
            public String getName() {
                return NbBundle.getMessage(RemoteAWTScreenshot.class, "CTL_Component_Breakpoint_Customize_Label");
            }

            @Override
            protected boolean enable(Node[] activatedNodes) {
                return true;
            }

            @Override
            protected boolean asynchronous() {
                return false;
            }
            
            @Override
            protected void performAction(Node[] activatedNodes) {
                for (Node n : activatedNodes) {
                    JavaComponentInfo ci = n.getLookup().lookup(JavaComponentInfo.class);
                    if (ci != null) {
                        ObjectReference component = ci.getComponent();
                        ComponentBreakpoint b = ComponentBreakpointActionProvider.findBreakpoint(component);
                        if (b != null) {
                            ComponentBreakpointsActionsProvider.customize(b);
                        }
                    }
                }
            }

            @Override
            public HelpCtx getHelpCtx() {
                return new HelpCtx("AWTComponentBreakpoint_Customize");
            }
            
        };
        
    }
}
