#ifndef _H_Value
#define _H_Value
/*
 * Copyright 2008 Google Inc.
 *
 * 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.
 */

#ifndef _WINDOWS
// TODO(jat): remove; for abort() which should probably go away
#include <stdlib.h>
#endif

#include <string>

#include "Debug.h"

#include "BrowserChannel.h"

class Value {
public:
  enum ValueType {
    NULL_TYPE = VALUE_TYPE_NULL,
    BOOLEAN = VALUE_TYPE_BOOLEAN,
    BYTE = VALUE_TYPE_BYTE,
    CHAR = VALUE_TYPE_CHAR,
    SHORT = VALUE_TYPE_SHORT,
    INT = VALUE_TYPE_INT,
    LONG = VALUE_TYPE_LONG,
    FLOAT = VALUE_TYPE_FLOAT,
    DOUBLE = VALUE_TYPE_DOUBLE,
    STRING = VALUE_TYPE_STRING,
    JAVA_OBJECT = VALUE_TYPE_JAVA_OBJECT,
    JS_OBJECT = VALUE_TYPE_JS_OBJECT,
    UNDEFINED = VALUE_TYPE_UNDEFINED
  };

private:
  ValueType type;
  union {
    bool  boolValue;
    unsigned char byteValue;
    unsigned short charValue;
    double doubleValue;
    float floatValue;
    int32_t intValue;
    int64_t longValue;
    short shortValue;
    std::string* stringValue;
  } value;

public:
  Value() {
    type = UNDEFINED;
  }

  Value(const Value& other) {
    copyValue(other);
  }

  Value& operator=(const Value& other) {
    clearOldValue();
    copyValue(other);
    return *this;
  }

  ~Value() {
    clearOldValue();
  }

  bool getBoolean() const {
    assertType(BOOLEAN);
    return value.boolValue;
  }

  unsigned char getByte() const {
    assertType(BYTE);
    return value.byteValue;
  }

  unsigned short getChar() const {
    assertType(CHAR);
    return value.charValue;
  }

  double getDouble() const {
    assertType(DOUBLE);
    return value.doubleValue;
  }

  float getFloat() const {
    assertType(FLOAT);
    return value.floatValue;
  }

  int getInt() const {
    assertType(INT);
    return value.intValue;
  }

  int getJavaObjectId() const {
    assertType(JAVA_OBJECT);
    return value.intValue;
  }

  int getJsObjectId() const {
    assertType(JS_OBJECT);
    return value.intValue;
  }

  int64_t getLong() const {
    assertType(LONG);
    return value.longValue;
  }

  short getShort() const {
    assertType(SHORT);
    return value.shortValue;
  }

  const std::string getString() const {
    assertType(STRING);
    return std::string(*value.stringValue);
  }

  ValueType getType() const {
    return type;
  }

  bool isBoolean() const {
    return type == BOOLEAN;
  }

  bool isByte() const {
    return type == BYTE;
  }

  bool isChar() const {
    return type == CHAR;
  }

  bool isDouble() const {
    return type == DOUBLE;
  }

  bool isFloat() const {
    return type == FLOAT;
  }

  bool isInt() const {
    return type == INT;
  }

  bool isJavaObject() const {
    return type == JAVA_OBJECT;
  }

  bool isJsObject() const {
    return type == JS_OBJECT;
  }

  bool isLong() const {
    return type == LONG;
  }

  bool isNull() const {
    return type == NULL_TYPE;
  }

  bool isNumber() const {
    switch (type) {
      case BYTE:
      case CHAR:
      case DOUBLE:
      case FLOAT:
      case INT:
      case LONG:
      case SHORT:
        return true;
      default:
        return false;
    }
  }

  bool isPrimitive() const {
    switch (type) {
      case BOOLEAN:
      case BYTE:
      case CHAR:
      case DOUBLE:
      case FLOAT:
      case INT:
      case LONG:
      case SHORT:
        return true;
      default:
        return false;
    }
  }

  bool isShort() const {
    return type == SHORT;
  }

  bool isString() const {
    return type == STRING;
  }

  bool isUndefined() const {
    return type == UNDEFINED;
  }

  void setBoolean(bool val) {
    clearOldValue();
    type = BOOLEAN;
    value.boolValue = val;
  }

  void setByte(unsigned char val) {
    clearOldValue();
    type = BYTE;
    value.byteValue = val;
  }

  void setChar(unsigned short val) {
    clearOldValue();
    type = CHAR;
    value.charValue = val;
  }

  void setDouble(double val) {
    clearOldValue();
    type = DOUBLE;
    value.doubleValue = val;
  }

  void setFloat(float val) {
    clearOldValue();
    type = FLOAT;
    value.floatValue = val;
  }

  void setInt(int val) {
    clearOldValue();
    type = INT;
    value.intValue = val;
  }

  void setJavaObject(int objectId) {
    clearOldValue();
    type = JAVA_OBJECT;
    value.intValue = objectId;
  }

  void setJsObjectId(int val) {
    clearOldValue();
    type = JS_OBJECT;
    value.intValue = val;
  }

  void setLong(int64_t val) {
    clearOldValue();
    type = LONG;
    value.longValue = val;
  }

  void setNull() {
    clearOldValue();
    type = NULL_TYPE;
  }

  void setShort(short val) {
    clearOldValue();
    type = SHORT;
    value.shortValue = val;
  }

  void setString(const char* chars, int len) {
    setString(std::string(chars, len));
  }

  void setString(const std::string& val) {
    clearOldValue();
    type = STRING;
    value.stringValue = new std::string(val);
  }

  void setUndefined() {
    clearOldValue();
    type = UNDEFINED;
  }

  std::string toString() const {
    char buf[30];
    switch (type) {
      case NULL_TYPE:
        return "null";
      case BOOLEAN:
        snprintf(buf, sizeof(buf), "boolean(%s)", getBoolean() ? "true"
            : "false");
        return std::string(buf);
      case BYTE:
        snprintf(buf, sizeof(buf), "byte(%d)", getByte());
        return std::string(buf);
      case CHAR:
        snprintf(buf, sizeof(buf), "char(%d)", getChar());
        return std::string(buf);
      case SHORT:
        snprintf(buf, sizeof(buf), "short(%d)", getShort());
        return std::string(buf);
      case INT:
        snprintf(buf, sizeof(buf), "int(%d)", getInt());
        return std::string(buf);
      case LONG:
        snprintf(buf, sizeof(buf), "long(%lld)",
            static_cast<long long>(getLong()));
        return std::string(buf);
      case FLOAT:
        snprintf(buf, sizeof(buf), "float(%f)", getFloat());
        return std::string(buf);
      case DOUBLE:
        snprintf(buf, sizeof(buf), "double(%lf)", getDouble());
        return std::string(buf);
      case STRING:
        snprintf(buf, sizeof(buf), "string(%.20s)", getString().c_str());
        return std::string(buf);
      case JAVA_OBJECT:
        snprintf(buf, sizeof(buf), "JavaObj(%d)", getJavaObjectId());
        return std::string(buf);
      case JS_OBJECT:
        snprintf(buf, sizeof(buf), "JsObj(%d)", getJsObjectId());
        return std::string(buf);
      case UNDEFINED:
        return "undefined";
      default:
        return "Unknown type";
    }
  }

private:
  void assertType(ValueType reqType) const {
    if (type != reqType) {
      Debug::log(Debug::Error) << "Value::assertType - expecting type "
          << int(reqType) << ", was " << int(type) << Debug::flush;
      // TODO(jat): is this portable?  Should we do something else here?
      abort();
    }
  }

  void clearOldValue() {
    if (type == STRING) {
      delete value.stringValue;
      type = UNDEFINED;
    }
  }

  // Precondition: existing value, if any, has been cleared
  void copyValue(const Value& other) {
    type = other.type;
    value = other.value;
    // handle deep copies of value types that need it
    switch (type) {
      case STRING:
        value.stringValue = new std::string(*value.stringValue);
        break;
      default:
        // no other values need deep copies
        break;
    }
  }
};

inline Debug::DebugStream& operator<<(Debug::DebugStream& dbg, const Value& val) {
  return dbg << val.toString();
}

#endif
