/*
 * Copyright 2018 The Bazel Authors. All rights reserved.
 *
 * Licensed 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 com.google.idea.blaze.skylark.debugger.impl;

import com.google.devtools.build.lib.starlarkdebugging.StarlarkDebuggingProtos;
import com.google.devtools.build.lib.starlarkdebugging.StarlarkDebuggingProtos.Location;
import com.google.idea.blaze.base.io.VfsUtils;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ColoredTextContainer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.XCompositeNode;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.intellij.xdebugger.frame.XValueGroup;
import java.io.File;
import javax.annotation.Nullable;
import javax.swing.Icon;

class SkylarkStackFrame extends XStackFrame {

  private static final Object STACK_FRAME_EQUALITY_OBJECT = new Object();

  final SkylarkDebugProcess debugProcess;
  final long threadId;
  final StarlarkDebuggingProtos.Frame frame;

  SkylarkStackFrame(
      SkylarkDebugProcess debugProcess, long threadId, StarlarkDebuggingProtos.Frame frame) {
    this.debugProcess = debugProcess;
    this.threadId = threadId;
    this.frame = frame;
  }

  @Override
  public Object getEqualityObject() {
    return STACK_FRAME_EQUALITY_OBJECT;
  }

  @Override
  public XDebuggerEvaluator getEvaluator() {
    return new DebuggerEvaluator(debugProcess);
  }

  @Nullable
  @Override
  public XSourcePosition getSourcePosition() {
    XSourcePosition base = fromLocationProto(frame.getLocation());
    return base == null ? null : new SkylarkSourcePosition(base, this);
  }

  @Override
  public void customizePresentation(ColoredTextContainer component) {
    if (frame.hasLocation()) {
      component.append(frame.getFunctionName() + " at ", SimpleTextAttributes.REGULAR_ATTRIBUTES);
    }
    super.customizePresentation(component);
  }

  /** Converts between source location formats used by the IDE and the Skylark debug server. */
  @Nullable
  static XSourcePosition fromLocationProto(Location location) {
    VirtualFile vf =
        VfsUtils.resolveVirtualFile(new File(location.getPath()), /* refreshIfNeeded= */ true);
    if (vf == null) {
      return null;
    }
    return XDebuggerUtil.getInstance().createPosition(vf, location.getLineNumber() - 1);
  }

  @Override
  public void computeChildren(XCompositeNode node) {
    if (node.isObsolete() || frame.getScopeCount() == 0) {
      return;
    }
    // show the scopes in reverse order (global first), with only the local scope auto-expanded
    XValueChildrenList children = new XValueChildrenList(frame.getScopeCount());
    for (int i = frame.getScopeCount() - 1; i >= 0; i--) {
      children.addTopGroup(new SkylarkFrameScope(frame.getScope(i), i == 0));
    }
    node.addChildren(children, true);
  }

  /** A group of variables in the debugger tree representing a single scope of a frame. */
  private class SkylarkFrameScope extends XValueGroup {

    private final boolean autoExpand;
    private final StarlarkDebuggingProtos.Scope scope;

    SkylarkFrameScope(StarlarkDebuggingProtos.Scope scope, boolean autoExpand) {
      super(scope.getName());
      this.scope = scope;
      this.autoExpand = autoExpand;
    }

    @Override
    public boolean isAutoExpand() {
      return autoExpand;
    }

    @Override
    public boolean isRestoreExpansion() {
      return true;
    }

    @Override
    public Icon getIcon() {
      return AllIcons.Debugger.Frame;
    }

    @Override
    public String getSeparator() {
      return "";
    }

    @Override
    public void computeChildren(XCompositeNode node) {
      XValueChildrenList children = new XValueChildrenList(scope.getBindingCount());
      scope
          .getBindingList()
          .forEach(v -> children.add(SkylarkDebugValue.fromProto(SkylarkStackFrame.this, v)));
      node.addChildren(children, true);
    }
  }
}