package xtc.lang.blink;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import xtc.lang.blink.CallStack.ICallFrame;
import xtc.lang.blink.CallStack.JavaCallFrame;
import xtc.lang.blink.CallStack.JeannieCallFrame;
import xtc.lang.blink.CallStack.NativeCallFrame;
import xtc.lang.blink.SymbolMapper.SourceFileAndLine;
import xtc.lang.blink.SymbolMapper.TargetSourceLanguage;
import xtc.lang.blink.SymbolMapper.VariableRemapEntry;
import xtc.lang.blink.CallStack.MicroCallFrame;
import xtc.lang.blink.CallStack.LocalVariable;

/**
 * The debugger calling context. 
 *
 * @author Byeongcheol Lee
 */
public class DebuggerContext {

  /** The mixed call stack. */
  private final CallStack callStack;

  /** The currently chosen call frame number. */
  private int currentFrameNumber;

  /**
   * @param callStack The call stack.
   * @param where The source code location.
   */
  DebuggerContext(CallStack callStack) {
    this.callStack = callStack;
    this.currentFrameNumber = 0;
  }

  /**
   * Obtain the name and value pairs for the Jeannie frame.
   * 
   * @param jframe The Jeannie frame.
   * @param dbg The Blink debugger.
   * @param remapper The Jeannie Symbol remapper.
   * @return The list.
   */
  private List<LocalVariable> getJeannieLocals(JeannieCallFrame jframe, 
      Blink dbg, SymbolMapper remapper) throws IOException {
    LinkedList<LocalVariable> localList = new LinkedList<LocalVariable>();
  
    //Obtain symbol remap information.
    String sourceFile = jframe.getSourceFile();
    int line = jframe.getLineNumber();
    if (sourceFile == null || line <= 0) {
      dbg.err("no source file and info avaiable.\n");
    }
    assert sourceFile != null && line > 0;
    List<VariableRemapEntry> list = remapper.lookup(sourceFile, line);
  
    //Obtain values from Java side.
    dbg.ensureJDBContext();
    for(VariableRemapEntry v: list) {
      if (v.targetLanguage == TargetSourceLanguage.JAVA) {
        String name = v.sourceVariableName;
        String val = dbg.jdb.print(getCurrentJavaFrame(), v.targetLanguageExpression());
        localList.add(new LocalVariable(name, val));
      }
    }
  
    //obtain values from native side.
    dbg.ensureGDBContext();
    for(VariableRemapEntry v: list) {
      if (v.targetLanguage == TargetSourceLanguage.C) {
        String name = v.sourceVariableName;
        String val = dbg.ndb.eval(getCurrentNativeFrame(), 
            v.targetLanguageExpression());
        localList.add(new LocalVariable(name, val));
      }
    }
  
    return localList;
  }

  /**
   * Present the current calling context to the user.
   * 
   * @param dbg The debugger.
   */
  public void showWhere(Blink dbg) {
    StringBuffer sb = new StringBuffer();
    int nsize = callStack.size(); 
    for(int i = currentFrameNumber;i < nsize;i++) {
      CallStack.ICallFrame f = callStack.getCallFrame(i);
      sb.append("  [" + i + "] "+ f + "\n"); 
    }
    dbg.out(sb.toString());
  }

  /**
   * Unwind the current call stack n steps. If this n steps unwinding exceed the
   * bottom of the stack, this unwinding stops at the bottom of the stack..
   * 
   * @param dbg The debugger.
   * @param nSteps The number of steps to unwind.
   */
  public void unWindStack(Blink dbg, int nSteps) {
    int maxPosition = callStack.size() - 1;
    int expectedPosition = currentFrameNumber + nSteps;
    if (expectedPosition > maxPosition ) {
      int modifiedStep = maxPosition - currentFrameNumber;
      dbg.err("can not unwind the stack more than " + modifiedStep +"\n");
      currentFrameNumber = maxPosition;
    } else {
      currentFrameNumber = expectedPosition;
    }
    assert currentFrameNumber >= 0 && currentFrameNumber < callStack.size();
  }

  /**
   * Wind the current call stack n steps. If this n steps exceeds the top of the
   * stack, this winding stops at the top of the stack.
   * 
   * @param dbg The debugger.
   * @param nSteps The number of steps to wind.
   */
  public void windStack(Blink dbg, int nSteps) {
    int minPosition = 0;
    int expectedPosition = currentFrameNumber - nSteps;
    if (expectedPosition < minPosition ) {
      int modifiedStep = currentFrameNumber;
      dbg.err("can not wind stack more than " + modifiedStep + "\n");
      currentFrameNumber = minPosition;
    } else {
      currentFrameNumber = expectedPosition;
    }
    assert currentFrameNumber >= 0 && currentFrameNumber < callStack.size();
  }
  
  /**
   * Show local variables in the currently chosen call stack.
   *  
   * @param dbg The debugger.
   * @param remapper The symbol remapper.
   */
  public void showLocals(Blink dbg, SymbolMapper remapper) 
    throws IOException {
    
    //obtain a list of local variable info.
    List<LocalVariable> l = null;
    ICallFrame f = callStack.getCallFrame(currentFrameNumber);
    if (f instanceof JavaCallFrame) {
      JavaCallFrame jframe = (JavaCallFrame)f;
      dbg.ensureJDBContext();
      l = dbg.jdb.getLocals(jframe);
    } else if (f instanceof NativeCallFrame) {
      NativeCallFrame nframe = (NativeCallFrame)f;
      dbg.ensureGDBContext();
      l = dbg.ndb.getLocals(nframe);
    } else if ( f instanceof JeannieCallFrame) {
      l = getJeannieLocals((JeannieCallFrame)f, dbg, remapper);
    } else {
      assert false : "can not deal with this call frame: " + f + "\n";
    }

    // show the result.
    if (l.size() == 0) {
      dbg.out("No Locals\n");
    } else {
      for(final LocalVariable lv : l) {
        dbg.out(lv.getName() + " = "  + lv.getValue() + "\n");
      }
    }
  }
  
  /**
   * The source code for the currently chosen frame.
   *
   * @param dbg The debugger.
   */
  public void showSourceCode(Blink dbg) throws IOException{
    MicroCallFrame frame = callStack.getCallFrame(currentFrameNumber).getTopMicroFrame();
    if (frame instanceof JavaCallFrame) {
      JavaCallFrame jframe = (JavaCallFrame)frame;
      dbg.ensureJDBContext();
      String msg = dbg.jdb.list(jframe);
      dbg.out(msg);
    } else {
      assert frame instanceof NativeCallFrame;
      dbg.ensureGDBContext();
      NativeCallFrame nframe = (NativeCallFrame)frame;
      String sourceFile = nframe.getSourceFile();
      int line = nframe.getLineNumber(); 
      if (sourceFile != null && line > 0) {
        String lines = dbg.ndb.getSourceLines(sourceFile, line, 10);
        dbg.out(lines);
      } else {
        dbg.err("line number is not availble.\n");
      }
    }
  }

  /** Find the current Java frame. */
  public JavaCallFrame getCurrentJavaFrame() throws IOException {
    return callStack.getJavaCallFrame(currentFrameNumber);
  }
  
  /** Find the current native frame. */
  public NativeCallFrame getCurrentNativeFrame() throws IOException{
    return callStack.getNativeCallFrame(currentFrameNumber);
  }

  /** Getter method for the user readable location. */
  public SourceFileAndLine getCurrentLocation() {
    ICallFrame f = callStack.getCallFrame(currentFrameNumber);
    if (f != null) {
      return new SourceFileAndLine(f.getSourceFile(), f.getLineNumber());
    } else {
      return null;
    }
  }
}
