/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2010 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]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * 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.
 */
package org.netbeans.modules.debugger.jpda;

import com.sun.jdi.AbsentInformationException;
import com.sun.source.util.TreePathScanner;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.function.Function;

import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.jpda.LineBreakpoint;
import org.netbeans.api.debugger.jpda.CallStackFrame;
import org.netbeans.api.debugger.jpda.InvalidExpressionException;
import org.netbeans.api.debugger.jpda.JPDAThread;
import org.netbeans.spi.debugger.jpda.EditorContext;
import org.netbeans.spi.debugger.jpda.EditorContext.MethodArgument;
import org.netbeans.spi.debugger.jpda.EditorContext.Operation;
import org.netbeans.spi.debugger.jpda.SourcePathProvider;
import org.openide.util.Exceptions;
import org.openide.util.Pair;


/**
 *
 * @author Jan Jancura
 */
public class EditorContextBridge {

    public static final String FIELD = "field";
    public static final String METHOD = "method";
    public static final String CLASS = "class";
    public static final String LINE = "line";

    private static EditorContext context;

    public static EditorContext getContext () {
        if (context == null) {
            List l = DebuggerManager.getDebuggerManager ().lookup
                (null, EditorContext.class);
            context = (EditorContext) l.get (0);
            int i, k = l.size ();
            for (i = 1; i < k; i++)
                context = new CompoundContextProvider (
                    (EditorContext) l.get (i),
                    context
                );
        }
        return context;
    }

    /**
     * Returns the parsed expression tree or <code>null</code>.
     *
     * @return the parsed expression tree or <code>null</code>
     *
    public static Tree getExpressionTree(final String expression, String url, final int line) {
        // TODO: return getContext ().getExpressionTree ();
        try {
            return (Tree) getContext ().getClass().getMethod("getExpressionTree", new Class[] { String.class, String.class, Integer.TYPE }).
                invoke(getContext(), new Object[] { expression, url, line });
        } catch (java.lang.reflect.InvocationTargetException itex) {
            Throwable tex = itex.getTargetException();
            if (tex instanceof RuntimeException) {
                throw (RuntimeException) tex;
            } else {
                ErrorManager.getDefault().notify(tex);
                return null;
            }
        } catch (Exception ex) {
            ErrorManager.getDefault().notify(ex);
            return null;
        }
    }
     */

    /**
     * Parse the expression into AST tree and traverse it via the provided visitor.
     *
     * @return the visitor value or <code>null</code>.
     */
    public static <R,D> R parseExpression(String expression, String url, final int line,
                                          TreePathScanner<R,D> visitor, D context,
                                          SourcePathProvider sp) throws InvalidExpressionException {

        // TODO: return getContext ().parseExpression ();
        try {
            return (R) getContext ().getClass().getMethod(
                    "parseExpression",
                    new Class[] { String.class, String.class, Integer.TYPE, TreePathScanner.class, Object.class, SourcePathProvider.class }).
                        invoke(getContext(), new Object[] { expression, url, line, visitor, context, sp });
        } catch (java.lang.reflect.InvocationTargetException itex) {
            Throwable tex = itex.getTargetException();
            if (tex instanceof RuntimeException) {
                throw (RuntimeException) tex;
            } else if (tex instanceof InvalidExpressionException) {
                throw ((InvalidExpressionException) tex);
            } else {
                Exceptions.printStackTrace(tex);
                return null;
            }
        } catch (Exception ex) {
            Exceptions.printStackTrace(ex);
            return null;
        }
    }

    public static <R,D> R interpretOrCompileCode(final String code, String url, final int line,
                                                 final TreePathScanner<Boolean,D> canInterpret,
                                                 final TreePathScanner<R,D> interpreter,
                                                 final D context, final boolean staticContext,
                                                 final Function<Pair<String, byte[]>, Boolean> compiledClassHandler,
                                                 final SourcePathProvider sp) throws InvalidExpressionException {
        try {
            return (R) getContext ().getClass().getMethod(
                    "interpretOrCompileCode",
                    new Class[] { String.class, String.class, Integer.TYPE,
                                  TreePathScanner.class, TreePathScanner.class,
                                  Object.class, Boolean.TYPE, Function.class,
                                  SourcePathProvider.class }).
                        invoke(getContext(), new Object[] { code, url, line,
                                                            canInterpret,
                                                            interpreter,
                                                            context, staticContext,
                                                            compiledClassHandler,
                                                            sp });
        } catch (java.lang.reflect.InvocationTargetException itex) {
            Throwable tex = itex.getTargetException();
            if (tex instanceof RuntimeException) {
                throw (RuntimeException) tex;
            } else if (tex instanceof InvalidExpressionException) {
                throw ((InvalidExpressionException) tex);
            } else {
                Exceptions.printStackTrace(tex);
                return null;
            }
        } catch (Exception ex) {
            Exceptions.printStackTrace(ex);
            return null;
        }
    }

    // Utility methods .........................................................

    public static String getFileName (LineBreakpoint b) {
        try {
            return new File (new URL (b.getURL ()).getFile ()).getName ();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    public static boolean showSource (LineBreakpoint b, Object timeStamp) {
        if (b.getLineNumber () < 1)
            return getContext().showSource (
                b.getURL (),
                1,
                timeStamp
            );
        return getContext().showSource (
            b.getURL (),
            b.getLineNumber (),
            timeStamp
        );
    }

    public static String getRelativePath (
        JPDAThread thread,
        String stratumn
    ) {
        try {
            return convertSlash (thread.getSourcePath (stratumn));
        } catch (AbsentInformationException e) {
            return getRelativePath (thread.getClassName ());
        }
    }

    public static String getRelativePath (
        CallStackFrame csf,
        String stratumn
    ) {
        try {
            return convertSlash (csf.getSourcePath (stratumn));
        } catch (AbsentInformationException e) {
            return getRelativePath (csf.getClassName ());
        }
    }

    public static String getRelativePath (
        String className
    ) {
        int i = className.indexOf ('$');
        if (i > 0) className = className.substring (0, i);
        String sourceName = className.replace
            ('.', '/') + ".java";
        return sourceName;
    }

    private static String convertSlash (String original) {
        return original.replace (File.separatorChar, '/');
    }

    public static int getCurrentOffset() {
        // TODO: return getContext ().getCurrentOffset();
        try {
            return (Integer) getContext ().getClass().getMethod("getCurrentOffset", new Class[] {}).
                    invoke(getContext(), new Object[] {});
        } catch (java.lang.reflect.InvocationTargetException itex) {
            Throwable tex = itex.getTargetException();
            if (tex instanceof RuntimeException) {
                throw (RuntimeException) tex;
            } else {
                Exceptions.printStackTrace(tex);
                return 0;
            }
        } catch (Exception ex) {
            Exceptions.printStackTrace(ex);
            return 0;
        }
    }


    // innerclasses ............................................................

    private static class CompoundContextProvider extends EditorContext {

        private EditorContext cp1, cp2;

        CompoundContextProvider (
            EditorContext cp1,
            EditorContext cp2
        ) {
            this.cp1 = cp1;
            this.cp2 = cp2;
        }

        public void createTimeStamp (Object timeStamp) {
            cp1.createTimeStamp (timeStamp);
            cp2.createTimeStamp (timeStamp);
        }

        public void disposeTimeStamp (Object timeStamp) {
            cp1.disposeTimeStamp (timeStamp);
            cp2.disposeTimeStamp (timeStamp);
        }

        public void updateTimeStamp (Object timeStamp, String url) {
            cp1.updateTimeStamp (timeStamp, url);
            cp2.updateTimeStamp (timeStamp, url);
        }

        public String getCurrentClassName () {
            String s = cp1.getCurrentClassName ();
            if (s.trim ().length () < 1)
                return cp2.getCurrentClassName ();
            return s;
        }

        public String getCurrentURL () {
            String s = cp1.getCurrentURL ();
            if (s.trim ().length () < 1)
                return cp2.getCurrentURL ();
            return s;
        }

        public String getCurrentFieldName () {
            String s = cp1.getCurrentFieldName ();
            if ( (s == null) || (s.trim ().length () < 1))
                return cp2.getCurrentFieldName ();
            return s;
        }

        public int getCurrentLineNumber () {
            int i = cp1.getCurrentLineNumber ();
            if (i < 1)
                return cp2.getCurrentLineNumber ();
            return i;
        }

        public int getCurrentOffset() {
            Integer i = null;
            try {
                i = (Integer) cp1.getClass().getMethod("getCurrentOffset", new Class[] {}).
                        invoke(cp1, new Object[] {});
            } catch (java.lang.reflect.InvocationTargetException itex) {
                Throwable tex = itex.getTargetException();
                if (tex instanceof RuntimeException) {
                    throw (RuntimeException) tex;
                } else {
                    Exceptions.printStackTrace(tex);
                    return 0;
                }
            } catch (Exception ex) {
                // Ignore, we have another attempt with cp2
                //ErrorManager.getDefault().notify(ex);
            }
            if (i == null || i.intValue() < 1) {
                try {
                    i = (Integer) cp2.getClass().getMethod("getCurrentOffset", new Class[] {}).
                            invoke(cp2, new Object[] {});
                } catch (java.lang.reflect.InvocationTargetException itex) {
                    Throwable tex = itex.getTargetException();
                    if (tex instanceof RuntimeException) {
                        throw (RuntimeException) tex;
                    } else {
                        Exceptions.printStackTrace(tex);
                        return 0;
                    }
                } catch (Exception ex) {
                    Exceptions.printStackTrace(ex);
                    return 0;
                }
            }
            return i.intValue();
        }

        public String getCurrentMethodName () {
            String s = cp1.getCurrentMethodName ();
            if ( (s == null) || (s.trim ().length () < 1))
                return cp2.getCurrentMethodName ();
            return s;
        }

        public String getSelectedIdentifier () {
            String s = cp1.getSelectedIdentifier ();
            if ( (s == null) || (s.trim ().length () < 1))
                return cp2.getSelectedIdentifier ();
            return s;
        }

        public String getSelectedMethodName () {
            String s = cp1.getSelectedMethodName ();
            if ( (s == null) || (s.trim ().length () < 1))
                return cp2.getSelectedMethodName ();
            return s;
        }

        public void removeAnnotation (Object annotation) {
            CompoundAnnotation ca = (CompoundAnnotation) annotation;
            cp1.removeAnnotation (ca.annotation1);
            cp2.removeAnnotation (ca.annotation2);
        }

        public Object annotate (
            String sourceName,
            int lineNumber,
            String annotationType,
            Object timeStamp
        ) {
            CompoundAnnotation ca = new CompoundAnnotation ();
            ca.annotation1 = cp1.annotate
                (sourceName, lineNumber, annotationType, timeStamp);
            ca.annotation2 = cp2.annotate
                (sourceName, lineNumber, annotationType, timeStamp);
            return ca;
        }

        public <R,D> R parseExpression(String expression, String url, final int line,
                                              TreePathScanner<R,D> visitor, D context,
                                              SourcePathProvider sp) {
            R ret = null;
            try {
                ret = (R) cp1.getClass().getMethod(
                    "parseExpression",
                    new Class[] { String.class, String.class, Integer.TYPE, TreePathScanner.class, Object.class, SourcePathProvider.class }).
                        invoke(cp1, new Object[] { expression, url, line, visitor, context, sp });
            } catch (java.lang.reflect.InvocationTargetException itex) {
                Throwable tex = itex.getTargetException();
                if (tex instanceof RuntimeException) {
                    throw (RuntimeException) tex;
                } else {
                    Exceptions.printStackTrace(tex);
                }
            } catch (Exception ex) {
                // Ignore, we have another attempt with cp2
            }
            if (ret == null) {
                try {
                    ret = (R) cp2.getClass().getMethod(
                    "parseExpression",
                    new Class[] { String.class, String.class, Integer.TYPE, TreePathScanner.class, Object.class, SourcePathProvider.class }).
                        invoke(cp2, new Object[] { expression, url, line, visitor, context, sp });
                } catch (java.lang.reflect.InvocationTargetException itex) {
                    Throwable tex = itex.getTargetException();
                    if (tex instanceof RuntimeException) {
                        throw (RuntimeException) tex;
                    } else {
                        Exceptions.printStackTrace(tex);
                    }
                } catch (Exception ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
            return ret;
        }

        public int getLineNumber (Object annotation, Object timeStamp) {
            int ln = cp1.getLineNumber (annotation, timeStamp);
            if (ln >= 0) return ln;
            return cp2.getLineNumber (annotation, timeStamp);
        }

        public boolean showSource (String sourceName, int lineNumber, Object timeStamp) {
            return cp1.showSource (sourceName, lineNumber, timeStamp) |
                   cp2.showSource (sourceName, lineNumber, timeStamp);
        }

        public int getFieldLineNumber (
            String url,
            String className,
            String fieldName
        ) {
            int ln = cp1.getFieldLineNumber (url, className, fieldName);
            if (ln != -1) return ln;
            return cp2.getFieldLineNumber (url, className, fieldName);
        }

        public String getClassName (
            String url,
            int lineNumber
        ) {
            String className = cp1.getClassName (url, lineNumber);
            if (className != null && className.length() > 0) return className;
            return cp2.getClassName (url, lineNumber);
        }

        public String[] getImports (String url) {
            String[] r1 = cp1.getImports (url);
            String[] r2 = cp2.getImports (url);
            String[] r = new String [r1.length + r2.length];
            System.arraycopy (r1, 0, r, 0, r1.length);
            System.arraycopy (r2, 0, r, r1.length, r2.length);
            return r;
        }

        public void addPropertyChangeListener (PropertyChangeListener l) {
            cp1.addPropertyChangeListener (l);
            cp2.addPropertyChangeListener (l);
        }

        public void removePropertyChangeListener (PropertyChangeListener l) {
            cp1.removePropertyChangeListener (l);
            cp2.removePropertyChangeListener (l);
        }

        public void addPropertyChangeListener (
            String propertyName,
            PropertyChangeListener l
        ) {
            cp1.addPropertyChangeListener (propertyName, l);
            cp2.addPropertyChangeListener (propertyName, l);
        }

        public void removePropertyChangeListener (
            String propertyName,
            PropertyChangeListener l
        ) {
            cp1.removePropertyChangeListener (propertyName, l);
            cp2.removePropertyChangeListener (propertyName, l);
        }

        @Override
        public Operation[] getOperations(String url, int lineNumber, BytecodeProvider bytecodeProvider) {
            Operation[] operations = cp1.getOperations(url, lineNumber, bytecodeProvider);
            if (operations != null) {
                return operations;
            } else {
                return cp2.getOperations(url, lineNumber, bytecodeProvider);
            }
        }

        @Override
        public MethodArgument[] getArguments(String url, Operation operation) {
            MethodArgument[] args;
            try {
                args = cp1.getArguments(url, operation);
            } catch (UnsupportedOperationException uoex) {
                args = cp2.getArguments(url, operation);
            }
            return args;
        }

        @Override
        public MethodArgument[] getArguments(String url, int methodLineNumber) {
            MethodArgument[] args;
            try {
                args = cp1.getArguments(url, methodLineNumber);
            } catch (UnsupportedOperationException uoex) {
                args = cp2.getArguments(url, methodLineNumber);
            }
            return args;
        }



    }

    private static class CompoundAnnotation {
        public CompoundAnnotation() {}

        Object annotation1;
        Object annotation2;
    }
}

