// 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.sdk.internal;


import org.chromium.sdk.JsVariable;
import org.chromium.sdk.JsValue.Type;

/**
 * A generic implementation of the JsVariable interface.
 */
public class JsVariableImpl implements JsVariable {

  /**
   * The variable value data as reported by the JavaScript VM (is used to
   * construct the variable value.)
   */
  private final ValueMirror valueData;

  /** The context this variable belongs in. */
  private final InternalContext context;

  /** The fully qualified name of this variable. */
  private final String qualifiedName;

  /** The lazily constructed value of this variable. */
  private final JsValueImpl value;

  /** Variable name. */
  private final Object rawName;

  /** Variable name. */
  private final String decoratedName;

  /**
   * Constructs a variable contained in the given context with the given
   * value mirror.
   *
   * @param context that owns this variable
   * @param valueData value data for this variable
   */
  JsVariableImpl(InternalContext context, ValueMirror valueData, String name) {
    this(context, valueData, name, name, name);
  }

  /**
   * Constructs a variable contained in the given context with the given
   * value mirror.
   *
   * @param context that owns this variable
   * @param valueData for this variable
   * @param qualifiedName the fully qualified name of this variable
   */
  JsVariableImpl(InternalContext context, ValueMirror valueData, Object rawName,
      String decoratedName, String qualifiedName) {
    this.context = context;
    this.valueData = valueData;
    this.rawName = rawName;
    this.decoratedName = decoratedName;
    this.qualifiedName = qualifiedName;

    Type type = this.valueData.getType();
    switch (type) {
      case TYPE_FUNCTION:
        this.value = new JsFunctionImpl(context, this.qualifiedName, this.valueData);
        break;
      case TYPE_ERROR:
      case TYPE_OBJECT:
        this.value = new JsObjectImpl(context, this.qualifiedName, this.valueData);
        break;
      case TYPE_ARRAY:
        this.value = new JsArrayImpl(context, this.qualifiedName, this.valueData);
        break;
      default:
        this.value = new JsValueImpl(this.valueData);
    }
  }

  /**
   * @return a [probably compound] JsValue corresponding to this variable.
   *         {@code null} if there was an error lazy-loading the value data.
   */
  public JsValueImpl getValue() {
    return value;
  }

  public String getName() {
    return decoratedName;
  }

  public String getRawName() {
    return this.rawName.toString();
  }

  Object getRawNameAsObject() {
    return this.rawName;
  }

  public boolean isMutable() {
    return false; // TODO(apavlov): fix once V8 supports it
  }

  public boolean isReadable() {
    // TODO(apavlov): implement once the readability metadata are available
    return true;
  }

  public synchronized void setValue(String newValue, SetValueCallback callback) {
    // TODO(apavlov): currently V8 does not support it
    if (!isMutable()) {
      throw new UnsupportedOperationException();
    }
  }

  @Override
  public String toString() {
    return new StringBuilder()
        .append("[JsVariable: name=")
        .append(getName())
        .append(",value=")
        .append(getValue())
        .append(']')
        .toString();
  }

  /**
   * Returns the context owning this variable.
   */
  protected InternalContext getInternalContext() {
    return context;
  }

  public ValueMirror getMirror() {
    return valueData;
  }

  public String getFullyQualifiedName() {
    return qualifiedName != null
        ? qualifiedName
        : getName();
  }

  static class NameDecorator {
    static String decorateVarName(Object rawName) {
      if (rawName instanceof Number) {
        return OPEN_BRACKET + rawName + CLOSE_BRACKET;
      } else {
        return rawName.toString();
      }
    }
    static String buildAccessSuffix(Object rawName) {
      if (rawName instanceof Number) {
        return OPEN_BRACKET + rawName + CLOSE_BRACKET;
      } else {
        return "." + rawName;
      }
    }
    private static final String OPEN_BRACKET = "[";
    private static final String CLOSE_BRACKET = "]";
  }
}
