/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.spi.debugger.jpda;

import com.sun.jdi.ClassType;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.StackFrame;
import com.sun.jdi.Value;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.netbeans.api.debugger.jpda.CallStackFrame;
import org.netbeans.api.debugger.jpda.InvalidExpressionException;
import org.netbeans.api.debugger.jpda.ObjectVariable;
import org.netbeans.api.debugger.jpda.Variable;
import org.netbeans.spi.debugger.ContextProvider;
import org.openide.util.Lookup;

/**
 * Evaluator service for a language that compiles into bytecode.
 * Implementation class should register using the annotation {@link Registration} for the desired language.
 *
 * @author Martin Entlicher
 *
 * @since 2.21
 */
public interface Evaluator<PreprocessedInfo> {

    /**
     * Evaluates given expression and provide the result.
     *
     * @param expression the expression to be evaluated
     * @param context the context in which the expression is evaluated
     * @return value of evaluated expression
     * @throws InvalidExpressionException when the expression is invalid or other
     * error occurs during the evaluation process.
     */
    Result evaluate(Expression<PreprocessedInfo> expression, Context context) throws InvalidExpressionException;

    /**
     * Representation of an expression that is a subject of evaluation.
     * String expressions are evaluated. But this class allows to attach
     * a custom preprocessed structure to the expression,
     * which can be used on subsequent evaluations of the same expression.
     * Clients can, but does not have to, set a pre-processed object and
     * during repeated evaluations of the same expression use that
     * pre-processed object to speed up the evaluation. The preprocessed
     * object should not be {@link Context}-sensitive.
     */
    public static final class Expression<PreprocessedInfo> {

        private String expression;
        private PreprocessedInfo preprocessed;

        /**
         * Creates a new expression from the string representation.
         * @param expression The string expression.
         */
        public Expression(String expression) {
            this.expression = expression;
        }

        /**
         * Get the string representation of this expression.
         * @return string expression
         */
        public String getExpression() {
            return expression;
        }

        /**
         * Set a pre-processed object of the string expression.
         * A custom, client-specific object is expected, which represents
         * the expression. This can be a syntax tree structure or whatever
         * that speeds up repeated evaluations of the same expression.
         * 
         * @param preprocessed object holding the information about pre-processed
         *        expression
         */
        public void setPreprocessedObject(PreprocessedInfo preprocessed) {
            this.preprocessed = preprocessed;
        }

        /**
         * Get the pre-processed object of the string expression.
         * The object set by {@link #setPreprocessedObject(java.lang.Object)} is
         * returned.
         *
         * @return the preprocessed object or <code>null</code>.
         */
        public PreprocessedInfo getPreprocessedObject() {
            return preprocessed;
        }
    }

    /**
     * Context of the evaluation.
     * This class provides the evaluation context - stack frame and context variable.
     * Two sets of APIs can be used during the evaluation:
     * <ul>
     *    <li>JPDA Debugger API (which is a safe abstraction of JDI,
     *                           but with limited functionality)<br>
     *        Provided {@link CallStackFrame} and {@link ObjectVariable}
     *        can be used to compute the resulting {@link Variable}.
     *    </li>
     *    <li>JDI API (providing full access, but can throw unexpected exceptions
     *                 and clients must notify the context about method invocations)<br>
     *        Provided {@link StackFrame} and {@link ObjectReference} can be used
     *        to compute the resulting {@link Value}. When a method invocation
     *        is necessary, {@link Context#notifyMethodToBeInvoked()} must be called
     *        before the method invocation.
     *    </li>
     * </ul>
     */
    public static final class Context {

        private CallStackFrame callStackFrame;
        private ObjectVariable contextVariable;
        private StackFrame stackFrame;
        private int stackDepth;
        private ObjectReference contextObject;
        private Runnable methodToBeInvokedNotifier;

        /** Creates the context, do not call directly */
        public Context(Lookup context) {
            this.callStackFrame = context.lookup(CallStackFrame.class);
            this.contextVariable = context.lookup(ObjectVariable.class);
            this.stackFrame = context.lookup(StackFrame.class);
            this.stackDepth = context.lookup(Integer.class);
            this.contextObject = context.lookup(ObjectReference.class);
            this.methodToBeInvokedNotifier = context.lookup(Runnable.class);
        }

        /**
         * Get the context call stack frame.
         * This frame corresonds to the JDI frame returned from {@link #getStackFrame()}.
         * @return call stack frame in which the evaluation is performed
         */
        public CallStackFrame getCallStackFrame() {
            return callStackFrame;
        }

        /**
         * Get an optional context variable. When non-null,
         * all methods and fields should be treated relative to the variable
         * instance.
         * This variable corresonds to the JDI reference returned from {@link #getContextObject()}.
         * @return optional context variable or <code>null</code>.
         */
        public ObjectVariable getContextVariable() {
            return contextVariable;
        }

        /**
         * Get the context stack frame in JDI APIs.
         * This frame corresonds to the JPDA frame returned from {@link #getCallStackFrame()}.
         * @return stack frame in which the evaluation is performed
         */
        public StackFrame getStackFrame() {
            return stackFrame;
        }

        /**
         * Get the depth of stack frame returned from {@link #getStackFrame()}.
         * @return the depth of stack frame
         */
        public int getStackDepth() {
            return stackDepth;
        }

        /**
         * Get an optional context object. When non-null,
         * all methods and fields should be treated relative to the object
         * instance.
         * This object corresonds to the JPDA variable returned from {@link #getContextVariable()}.
         * @return optional context object or <code>null</code>.
         */
        public ObjectReference getContextObject() {
            return contextObject;
        }

        /**
         * This method is <b>required</b> to be called before a call to JDI
         * that cause the current thread (<code>sf.thread()</code>) to resume - e.g.
         * {@link ObjectReference#invokeMethod(com.sun.jdi.ThreadReference, com.sun.jdi.Method, java.util.List, int)},
         * {@link ClassType#invokeMethod(com.sun.jdi.ThreadReference, com.sun.jdi.Method, java.util.List, int)},
         * {@link ClassType#newInstance(com.sun.jdi.ThreadReference, com.sun.jdi.Method, java.util.List, int)}.
         */
        public void notifyMethodToBeInvoked() {
            methodToBeInvokedNotifier.run();
        }
    }

    /**
     * Evaluation result.
     * Depending on the APIs used by the evaluation, result is either
     * a {@link Variable} or {@link Value}.
     */
    public static final class Result {

        private Variable var;
        private Value v;

        /**
         * Create result from {@link Variable}.
         * @param var result variable
         */
        public Result(Variable var) {
            this.var = var;
        }

        /**
         * Create result from {@link Value}.
         * @param v result value
         */
        public Result(Value v) {
            this.v = v;
        }

        /**
         * Get the result variable
         * @return the variable or <code>null</code>.
         */
        public Variable getVariable() {
            return var;
        }

        /**
         * Get the result value
         * @return the value or <code>null</code>.
         */
        public Value getValue() {
            return v;
        }
    }
    
    /**
     * Declarative registration of Evaluator implementation.
     * By marking the implementation class with this annotation,
     * you automatically register that implementation for use by debugger.
     * The class must be public and have a public constructor which takes
     * no arguments or takes {@link ContextProvider} as an argument.
     */
    @Retention(RetentionPolicy.SOURCE)
    @Target({ElementType.TYPE})
    public @interface Registration {
        /**
         * The language to register this evaluator for.
         */
        String language();

    }

}
