// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of dart2js.ir_pickler;

class Unpickler {
  final Compiler compiler;

  final IrConstantPool constantPool;

  Unpickler(this.compiler, this.constantPool);

  List<int> data;

  int offset;

  /** For each entry index, the corresponding unpickled object. */
  List<Object> unpickled;

  /** Counter for entries in [unpickled]. */
  int index;

  /**
   * This buffer is used in [readConstant] to reconstruct a double value from
   * a sequence of bytes.
   */
  ByteData doubleData = new ByteData(8);

  ConstantSystem get constantSystem => compiler.backend.constantSystem;

  // A partially constructed expression is one that has a single 'hole' where
  // there is an expression missing.  Just like the IR builder, the unpickler
  // represents such an expression by its root and by the 'current' expression
  // that immediately contains the hole.  If there is no hole (e.g., an
  // expression in tail position has been seen), then current is null.
  ir.Expression root;
  ir.Expression current;

  ir.Function unpickle(List<int> data) {
    this.data = data;
    offset = 0;
    int numEntries = readInt();
    unpickled = new List<Object>(numEntries);
    index = 0;
    root = current = null;
    return readFunctionNode();
  }

  int readByte() {
    return data[offset++];
  }

  int readInt() {
    int result = 0;
    int next;
    for (int i = 0; true; i += 7) {
      next = readByte();
      result |= (next >> 1) << i;
      if ((next & 1) == 0) break;
    }
    bool isNegative = (result & 1) == 1;
    result >>= 1;
    return isNegative ? -result : result;
  }

  String readString() {
    int tag = readByte();
    int length = readInt();
    List<int> bytes = new Uint8List(length);
    for (int i = 0; i < length; i++) {
      bytes[i] = readByte();
    }
    if (tag == Pickles.STRING_ASCII) {
      return new String.fromCharCodes(bytes);
    } else if (tag == Pickles.STRING_UTF8) {
      return UTF8.decode(bytes);
    } else {
      compiler.internalError(NO_LOCATION_SPANNABLE,
                             "Unexpected string tag: $tag");
      return null;
    }
  }

  Element readElement() {
    int elementIndex = readInt();
    return constantPool.get(elementIndex);
  }

  Selector readSelector() {
    int tag = readByte();
    if (tag == Pickles.BACKREFERENCE) {
      return readBackReference();
    }
    assert(tag == Pickles.SELECTOR_UNTYPED);
    int entryIndex = index++;
    SelectorKind kind = Pickles.selectorKindFromId[readInt()];
    String name = readString();
    Element library = readElement();
    int argumentsCount = readInt();
    int namedArgumentsCount = readInt();
    List<String> namedArguments = new List<String>(namedArgumentsCount);
    for (int i = 0; i < namedArgumentsCount; i++) {
      namedArguments[i] = readString();
    }
    Selector result = new Selector(
        kind, name, library, argumentsCount, namedArguments);
    unpickled[entryIndex] = result;
    return result;
  }

  void addExpression(ir.Expression expr) {
    if (root == null) {
      root = current = expr;
    } else {
      current = current.plug(expr);
    }
  }

  // Read a single expression and plug it into the outer context.
  ir.Expression readExpressionNode() {
    int tag = readByte();
    switch (tag) {
      case Pickles.NODE_CONSTANT:
        ir.Definition constant = readConstantNode();
        unpickled[index++] = constant;
        addExpression(new ir.LetPrim(constant));
        break;
      case Pickles.NODE_LET_CONT:
        ir.Parameter parameter = new ir.Parameter();
        ir.Continuation continuation = new ir.Continuation(parameter);
        unpickled[index++] = continuation;
        ir.Expression body = readDelimitedExpressionNode();
        unpickled[index++] = parameter;
        addExpression(new ir.LetCont(continuation, body));
        break;
      case Pickles.NODE_INVOKE_STATIC:
        addExpression(readInvokeStaticNode());
        current = null;
        break;
      case Pickles.NODE_INVOKE_CONTINUATION:
        addExpression(readInvokeContinuationNode());
        current = null;
        break;
      default:
        compiler.internalError(NO_LOCATION_SPANNABLE,
                               "Unexpected expression entry tag: $tag");
        break;
    }
  }

  // Iteratively read expressions until an expression in a tail position
  // (e.g., an invocation) is found.  Do not change the outer context.
  ir.Expression readDelimitedExpressionNode() {
    ir.Expression previous_root = root;
    ir.Expression previous_current = current;
    root = current = null;
    do {
      readExpressionNode();
    } while (current != null);
    ir.Expression result = root;
    root = previous_root;
    current = previous_current;
    return result;
  }

  Object readBackReference() {
    int indexDelta = readInt();
    int entryIndex = index - indexDelta;
    assert(unpickled[entryIndex] != null);
    return unpickled[entryIndex];
  }

  List<ir.Definition> readBackReferenceList() {
    int length = readInt();
    List<ir.Definition> result = new List<ir.Definition>(length);
    for (int i = 0; i < length; i++) {
      result[i] = readBackReference();
    }
    return result;
  }

  ir.Function readFunctionNode() {
    int endOffset = readInt();
    int namePosition = readInt();
    // There is implicitly a return continuation which can be the target of
    // back references.
    ir.Continuation continuation = new ir.Continuation.retrn();
    unpickled[index++] = continuation;

    ir.Expression body = readDelimitedExpressionNode();
    return new ir.Function(endOffset, namePosition, continuation, body);
  }

  ir.Constant readConstantNode() {
    Constant constant = readConstant();
    return new ir.Constant(constant);
  }

  ir.InvokeStatic readInvokeStaticNode() {
    FunctionElement functionElement = readElement();
    Selector selector = readSelector();
    ir.Continuation continuation = readBackReference();
    List<ir.Definition> arguments = readBackReferenceList();
    return new ir.InvokeStatic(functionElement, selector, continuation,
                               arguments);
  }

  ir.InvokeContinuation readInvokeContinuationNode() {
    ir.Continuation continuation = readBackReference();
    ir.Definition argument = readBackReference();
    return new ir.InvokeContinuation(continuation, argument);
  }

  Constant readConstant() {
    int tag = readByte();
    switch(tag) {
      case Pickles.CONST_BOOL:
        return constantSystem.createBool(readByte() == 1);
      case Pickles.CONST_INT:
        return constantSystem.createInt(readInt());
      case Pickles.CONST_DOUBLE:
        for (int i = 0; i < 8; i++) {
          doubleData.setUint8(i, readByte());
        }
        double value = doubleData.getFloat64(0, Endianness.BIG_ENDIAN);
        return constantSystem.createDouble(value);
      case Pickles.CONST_STRING_LITERAL:
      case Pickles.CONST_STRING_RAW:
      case Pickles.CONST_STRING_ESCAPED:
      case Pickles.CONST_STRING_CONS:
        return constantSystem.createString(readDartString(tag));
      case Pickles.CONST_NULL:
        return constantSystem.createNull();
      default:
        compiler.internalError(NO_LOCATION_SPANNABLE,
                               "Unexpected constant tag: $tag");
        return null;
    }
  }

  ast.DartString readDartString(int tag) {
    switch(tag) {
      case Pickles.CONST_STRING_LITERAL:
        return new ast.LiteralDartString(readString());
      case Pickles.CONST_STRING_RAW:
        return new ast.RawSourceDartString(readString(), readInt());
      case Pickles.CONST_STRING_ESCAPED:
        return new ast.EscapedSourceDartString(readString(), readInt());
      case Pickles.CONST_STRING_CONS:
        return new ast.ConsDartString(
            readDartString(readByte()), readDartString(readByte()));
      default:
        compiler.internalError(NO_LOCATION_SPANNABLE,
                               "Unexpected dart string tag: $tag");
        return null;
    }
  }
}
