// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.debug.ui;

import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.debug.core.model.DebugElementImpl;
import org.chromium.debug.core.model.EvaluateContext;
import org.chromium.debug.core.model.Variable;
import org.chromium.sdk.JsEvaluateContext;
import org.chromium.sdk.JsVariable;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IWatchExpressionDelegate;
import org.eclipse.debug.core.model.IWatchExpressionListener;
import org.eclipse.debug.core.model.IWatchExpressionResult;

/**
 * Performs the Watch expression evaluation while debugging Chromium JavaScript.
 */
public class JsWatchExpressionDelegate implements IWatchExpressionDelegate {

  private static final String[] EMPTY_STRINGS = new String[0];

  private static final class GoodWatchExpressionResult implements IWatchExpressionResult {

    private final Variable variable;

    private final String expression;

    private IValue value;

    private DebugException exception;

    private GoodWatchExpressionResult(Variable variable, String expression) {
      this.variable = variable;
      this.expression = expression;
    }

    public String[] getErrorMessages() {
      return exception == null
          ? EMPTY_STRINGS
          : new String[] { exception.getStatus().getMessage() };
    }

    public DebugException getException() {
      getValue();
      return exception;
    }

    public String getExpressionText() {
      return expression;
    }

    public synchronized IValue getValue() {
      if (value == null && exception == null) {
        value = variable.getValue();
      }
      return value;
    }

    public boolean hasErrors() {
      getValue();
      return exception != null;
    }
  }

  private static final class BadWatchExpressionResult implements IWatchExpressionResult {

    private final DebugException exception;

    private final String expressionText;

    private BadWatchExpressionResult(DebugException exception, String expressionText) {
      this.exception = exception;
      this.expressionText = expressionText;
    }

    public String[] getErrorMessages() {
      return new String[] { exception.getStatus().getMessage() };
    }

    public DebugException getException() {
      return exception;
    }

    public String getExpressionText() {
      return expressionText;
    }

    public IValue getValue() {
      return null;
    }

    public boolean hasErrors() {
      return true;
    }
  }

  public void evaluateExpression(final String expression, final IDebugElement context,
      final IWatchExpressionListener listener) {
    final DebugElementImpl contextImpl = (DebugElementImpl) context;
    if (!contextImpl.getDebugTarget().isSuspended()) {
      // can only evaluate while suspended. Notify empty result.
      listener.watchEvaluationFinished(new IWatchExpressionResult() {

        public String[] getErrorMessages() {
          return EMPTY_STRINGS;
        }

        public DebugException getException() {
          return null;
        }

        public String getExpressionText() {
          return expression;
        }

        public IValue getValue() {
          return null;
        }

        public boolean hasErrors() {
          return false;
        }
      });
      return;
    }

    final EvaluateContext evaluateContext =
        (EvaluateContext) contextImpl.getAdapter(EvaluateContext.class);
    if (evaluateContext == null) {
      listener.watchEvaluationFinished(new BadWatchExpressionResult(
          new DebugException(new Status(Status.ERROR,
              ChromiumDebugUIPlugin.PLUGIN_ID,"Bad debug context")), //$NON-NLS-1$
          expression));
      return;
    }

    evaluateContext.getJsEvaluateContext().evaluateAsync(
        expression, null,
        new JsEvaluateContext.EvaluateCallback() {
          public void success(JsVariable variable) {
            final Variable var = Variable.forRealValue(evaluateContext, variable, false, null);
            listener.watchEvaluationFinished(new GoodWatchExpressionResult(var, expression));
          }

          public void failure(String message) {
            listener.watchEvaluationFinished(new BadWatchExpressionResult(new DebugException(
                createErrorStatus(message == null
                    ? Messages.JsWatchExpressionDelegate_ErrorEvaluatingExpression
                    : message, null)), expression));
            return;
          }
        },
        null);
  }

  private static Status createErrorStatus(String message, Exception e) {
    return new Status(Status.ERROR, ChromiumDebugPlugin.PLUGIN_ID, message, e);
  }

}
