// Copyright 2010 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.

#include "protobuf_v8.h"

#include <map>
#include <string>
#include <iostream>
#include <sstream>

#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>

#include "logging.h"
#include "util.h"

#include "node_buffer.h"
#include "node_object_wrap.h"

#include "node_util.h"

//#define PROTOBUF_V8_DEBUG
#ifdef  PROTOBUF_V8_DEBUG

#define DBG(...) LOGD(__VA_ARGS__)

#else

#define DBG(...)

#endif

using google::protobuf::Descriptor;
using google::protobuf::DescriptorPool;
using google::protobuf::DynamicMessageFactory;
using google::protobuf::FieldDescriptor;
using google::protobuf::FileDescriptorSet;
using google::protobuf::Message;
using google::protobuf::Reflection;

//using ObjectWrap;
//using Buffer;

using std::map;
using std::string;

using v8::Array;
using v8::AccessorInfo;
using v8::Arguments;
using v8::Boolean;
using v8::Context;
using v8::External;
using v8::Function;
using v8::FunctionTemplate;
using v8::Integer;
using v8::Handle;
using v8::HandleScope;
using v8::InvocationCallback;
using v8::Local;
using v8::NamedPropertyGetter;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::Persistent;
using v8::Script;
using v8::String;
using v8::Value;
using v8::V8;

namespace protobuf_v8 {

  template <typename T>
  static T* UnwrapThis(const Arguments& args) {
    return ObjectWrap::Unwrap<T>(args.This());
  }

  template <typename T>
  static T* UnwrapThis(const AccessorInfo& args) {
    return ObjectWrap::Unwrap<T>(args.This());
  }

  Persistent<FunctionTemplate> SchemaTemplate;
  Persistent<FunctionTemplate> TypeTemplate;
  Persistent<FunctionTemplate> ParseTemplate;
  Persistent<FunctionTemplate> SerializeTemplate;

  class Schema : public ObjectWrap {
  public:
    Schema(Handle<Object> self, const DescriptorPool* pool)
        : pool_(pool) {
      DBG("Schema::Schema E:");
      factory_.SetDelegateToGeneratedFactory(true);
      self->SetInternalField(1, Array::New());
      Wrap(self);
      DBG("Schema::Schema X:");
    }

    virtual ~Schema() {
      DBG("~Schema::Schema E:");
      if (pool_ != DescriptorPool::generated_pool())
        delete pool_;
      DBG("~Schema::Schema X:");
    }

    class Type : public ObjectWrap {
    public:
      Schema* schema_;
      const Descriptor* descriptor_;

      Message* NewMessage() const {
        DBG("Type::NewMessage() EX:");
        return schema_->NewMessage(descriptor_);
      }

      Handle<Function> Constructor() const {
        DBG("Type::Constrocutor() EX:");
        return Handle<Function>::Cast(handle_->GetInternalField(2));
      }

      Local<Object> NewObject(Handle<Value> properties) const {
        DBG("Type::NewObjext(properties) EX:");
        return Constructor()->NewInstance(1, &properties);
      }

      Type(Schema* schema, const Descriptor* descriptor, Handle<Object> self)
        : schema_(schema), descriptor_(descriptor) {
        DBG("Type::Type(schema, descriptor, self) E:");
        // Generate functions for bulk conversion between a JS object
        // and an array in descriptor order:
        //   from = function(arr) { this.f0 = arr[0]; this.f1 = arr[1]; ... }
        //   to   = function()    { return [ this.f0, this.f1, ... ] }
        // This is faster than repeatedly calling Get/Set on a v8::Object.
        std::ostringstream from, to;
        from << "(function(arr) { if(arr) {";
        to << "(function() { return [ ";

        for (int i = 0; i < descriptor->field_count(); i++) {
          from <<
            "var x = arr[" << i << "]; "
            "if(x !== undefined) this['" <<
            descriptor->field(i)->camelcase_name() <<
            "'] = x; ";

          if (i > 0) to << ", ";
          to << "this['" << descriptor->field(i)->camelcase_name() << "']";
          DBG("field name=%s", descriptor->field(i)->name().c_str());
        }

        from << " }})";
        to << " ]; })";

        // managed type->schema link
        self->SetInternalField(1, schema_->handle_);

        Handle<Function> constructor = Handle<Function>::Cast(
          Script::Compile(String::New(from.str().c_str()))->Run());
        constructor->SetHiddenValue(String::New("type"), self);

        Handle<Function> bind = Handle<Function>::Cast(
          Script::Compile(String::New(
              "(function(self) {"
              "  var f = this;"
              "  return function(arg) {"
              "    return f.call(self, arg);"
              "  };"
              "})"))->Run());
        Handle<Value> arg = self;
        constructor->Set(String::New("parse"), bind->Call(ParseTemplate->GetFunction(), 1, &arg));
        constructor->Set(String::New("serialize"), bind->Call(SerializeTemplate->GetFunction(), 1, &arg));
        self->SetInternalField(2, constructor);
        self->SetInternalField(3, Script::Compile(String::New(to.str().c_str()))->Run());

        Wrap(self);
        DBG("Type::Type(schema, descriptor, self) X:");
      }

#define GET(TYPE)                                                        \
      (index >= 0 ?                                                      \
       reflection->GetRepeated##TYPE(instance, field, index) :           \
       reflection->Get##TYPE(instance, field))

      static Handle<Value> ToJs(const Message& instance,
                                const Reflection* reflection,
                                const FieldDescriptor* field,
                                const Type* message_type,
                                int index) {
        DBG("Type::ToJs(instance, refelction, field, message_type) E:");
        switch (field->cpp_type()) {
        case FieldDescriptor::CPPTYPE_MESSAGE:
          DBG("Type::ToJs CPPTYPE_MESSAGE");
          return message_type->ToJs(GET(Message));
        case FieldDescriptor::CPPTYPE_STRING: {
          DBG("Type::ToJs CPPTYPE_STRING");
          const string& value = GET(String);
          return String::New(value.data(), value.length());
        }
        case FieldDescriptor::CPPTYPE_INT32:
          DBG("Type::ToJs CPPTYPE_INT32");
          return Integer::New(GET(Int32));
        case FieldDescriptor::CPPTYPE_UINT32:
          DBG("Type::ToJs CPPTYPE_UINT32");
          return Integer::NewFromUnsigned(GET(UInt32));
        case FieldDescriptor::CPPTYPE_INT64:
          DBG("Type::ToJs CPPTYPE_INT64");
          return Number::New(GET(Int64));
        case FieldDescriptor::CPPTYPE_UINT64:
          DBG("Type::ToJs CPPTYPE_UINT64");
          return Number::New(GET(UInt64));
        case FieldDescriptor::CPPTYPE_FLOAT:
          DBG("Type::ToJs CPPTYPE_FLOAT");
          return Number::New(GET(Float));
        case FieldDescriptor::CPPTYPE_DOUBLE:
          DBG("Type::ToJs CPPTYPE_DOUBLE");
          return Number::New(GET(Double));
        case FieldDescriptor::CPPTYPE_BOOL:
          DBG("Type::ToJs CPPTYPE_BOOL");
          return Boolean::New(GET(Bool));
        case FieldDescriptor::CPPTYPE_ENUM:
          DBG("Type::ToJs CPPTYPE_ENUM");
          return String::New(GET(Enum)->name().c_str());
        }

        return Handle<Value>();  // NOTREACHED
      }
#undef GET

      Handle<Object> ToJs(const Message& instance) const {
        DBG("Type::ToJs(Message) E:");
        const Reflection* reflection = instance.GetReflection();
        const Descriptor* descriptor = instance.GetDescriptor();

        Handle<Array> properties = Array::New(descriptor->field_count());
        for (int i = 0; i < descriptor->field_count(); i++) {
          HandleScope scope;

          const FieldDescriptor* field = descriptor->field(i);
          bool repeated = field->is_repeated();
          if (repeated && !reflection->FieldSize(instance, field)) {
            DBG("Ignore repeated field with no size in reflection data");
            continue;
          }
          if (!repeated && !reflection->HasField(instance, field)) {
            DBG("Ignore field with no field in relfection data");
            continue;
          }

          const Type* child_type =
            (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
            schema_->GetType(field->message_type()) : NULL;

          Handle<Value> value;
          if (field->is_repeated()) {
            int size = reflection->FieldSize(instance, field);
            Handle<Array> array = Array::New(size);
            for (int j = 0; j < size; j++) {
              Handle<Value> index = Number::New(i);
              array->Set(index, ToJs(instance, reflection, field, child_type, j));
            }
            value = array;
          } else {
            value = ToJs(instance, reflection, field, child_type, -1);
          }

          DBG("Type::ToJs: set property[%d]=%s", i, ToCString(value));
          Handle<Value> key = Number::New(i);
          properties->Set(key, value);
        }

        DBG("Type::ToJs(Message) X:");
        return NewObject(properties);
      }

      static Handle<Value> Parse(const Arguments& args) {
        DBG("Type::Parse(args) E:");
        Type* type = UnwrapThis<Type>(args);
        Buffer* buf = ObjectWrap::Unwrap<Buffer>(args[0]->ToObject());

        Message* message = type->NewMessage();
        message->ParseFromArray(buf->data(), buf->length());
        Handle<Object> result = type->ToJs(*message);
        delete message;

        DBG("Type::Parse(args) X:");
        return result;
      }

#define SET(TYPE, EXPR)                                                 \
      if (repeated) reflection->Add##TYPE(instance, field, EXPR);       \
      else reflection->Set##TYPE(instance, field, EXPR)

      static bool ToProto(Message* instance,
                          const FieldDescriptor* field,
                          Handle<Value> value,
                          const Type* type,
                          bool repeated) {
        DBG("Type::ToProto(instance, field, value, type, repeated) E:");
        bool ok = true;
        HandleScope scope;

        DBG("Type::ToProto field->name()=%s", field->name().c_str());
        const Reflection* reflection = instance->GetReflection();
        switch (field->cpp_type()) {
        case FieldDescriptor::CPPTYPE_MESSAGE:
          DBG("Type::ToProto CPPTYPE_MESSAGE");
          ok = type->ToProto(repeated ?
                                reflection->AddMessage(instance, field) :
                                reflection->MutableMessage(instance, field),
                                    Handle<Object>::Cast(value));
          break;
        case FieldDescriptor::CPPTYPE_STRING: {
          DBG("Type::ToProto CPPTYPE_STRING");
          String::AsciiValue ascii(value);
          SET(String, string(*ascii, ascii.length()));
          break;
        }
        case FieldDescriptor::CPPTYPE_INT32:
          DBG("Type::ToProto CPPTYPE_INT32");
          SET(Int32, value->NumberValue());
          break;
        case FieldDescriptor::CPPTYPE_UINT32:
          DBG("Type::ToProto CPPTYPE_UINT32");
          SET(UInt32, value->NumberValue());
          break;
        case FieldDescriptor::CPPTYPE_INT64:
          DBG("Type::ToProto CPPTYPE_INT64");
          SET(Int64, value->NumberValue());
          break;
        case FieldDescriptor::CPPTYPE_UINT64:
          DBG("Type::ToProto CPPTYPE_UINT64");
          SET(UInt64, value->NumberValue());
          break;
        case FieldDescriptor::CPPTYPE_FLOAT:
          DBG("Type::ToProto CPPTYPE_FLOAT");
          SET(Float, value->NumberValue());
          break;
        case FieldDescriptor::CPPTYPE_DOUBLE:
          DBG("Type::ToProto CPPTYPE_DOUBLE");
          SET(Double, value->NumberValue());
          break;
        case FieldDescriptor::CPPTYPE_BOOL:
          DBG("Type::ToProto CPPTYPE_BOOL");
          SET(Bool, value->BooleanValue());
          break;
        case FieldDescriptor::CPPTYPE_ENUM:
          DBG("Type::ToProto CPPTYPE_ENUM");

          // Don't use SET as vd can be NULL
          char error_buff[256];
          const google::protobuf::EnumValueDescriptor* vd;
          int i32_value = 0;
          const char *str_value = NULL;
          const google::protobuf::EnumDescriptor* ed = field->enum_type();

          if (value->IsNumber()) {
            i32_value = value->Int32Value();
            vd = ed->FindValueByNumber(i32_value);
            if (vd == NULL) {
              snprintf(error_buff, sizeof(error_buff),
                  "Type::ToProto Bad enum value, %d is not a member of enum %s",
                      i32_value, ed->full_name().c_str());
            }
          } else {
            str_value = ToCString(value);
            // TODO: Why can str_value be corrupted sometimes?
            LOGD("str_value=%s", str_value);
            vd = ed->FindValueByName(str_value);
            if (vd == NULL) {
              snprintf(error_buff, sizeof(error_buff),
                  "Type::ToProto Bad enum value, %s is not a member of enum %s",
                      str_value, ed->full_name().c_str());
            }
          }
          if (vd != NULL) {
            if (repeated) {
               reflection->AddEnum(instance, field, vd);
            } else {
               reflection->SetEnum(instance, field, vd);
            }
          } else {
            v8::ThrowException(String::New(error_buff));
            ok = false;
          }
          break;
        }
        DBG("Type::ToProto(instance, field, value, type, repeated) X: ok=%d", ok);
        return ok;
      }
#undef SET

      bool ToProto(Message* instance, Handle<Object> src) const {
        DBG("ToProto(Message *, Handle<Object>) E:");

        Handle<Function> to_array = Handle<Function>::Cast(handle_->GetInternalField(3));
        Handle<Array> properties = Handle<Array>::Cast(to_array->Call(src, 0, NULL));
        bool ok = true;
        for (int i = 0; ok && (i < descriptor_->field_count()); i++) {
          Handle<Value> value = properties->Get(Number::New(i));
          if (value->IsUndefined()) continue;

          const FieldDescriptor* field = descriptor_->field(i);
          const Type* child_type =
            (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) ?
            schema_->GetType(field->message_type()) : NULL;
          if (field->is_repeated()) {
            if(!value->IsArray()) {
              ok = ToProto(instance, field, value, child_type, true);
            } else {
              Handle<Array> array = Handle<Array>::Cast(value);
              int length = array->Length();
              for (int j = 0; ok && (j < length); j++) {
                ok = ToProto(instance, field, array->Get(Number::New(j)), child_type, true);
              }
            }
          } else {
            ok = ToProto(instance, field, value, child_type, false);
          }
        }
        DBG("ToProto(Message *, Handle<Object>) X: ok=%d", ok);
        return ok;
      }

      static Handle<Value> Serialize(const Arguments& args) {
        Handle<Value> result;
        DBG("Serialize(Arguments&) E:");
        if (!args[0]->IsObject()) {
          DBG("Serialize(Arguments&) X: not an object");
          return v8::ThrowException(args[0]);
        }

        Type* type = UnwrapThis<Type>(args);
        Message* message = type->NewMessage();
        if (type->ToProto(message, Handle<Object>::Cast(args[0]))) {
          int length = message->ByteSize();
          Buffer* buffer = Buffer::New(length);
          message->SerializeWithCachedSizesToArray((google::protobuf::uint8*)buffer->data());
          delete message;

          result = buffer->handle_;
        } else {
          result = v8::Undefined();
        }
        DBG("Serialize(Arguments&) X");
        return result;
      }

      static Handle<Value> ToString(const Arguments& args) {
        return String::New(UnwrapThis<Type>(args)->descriptor_->full_name().c_str());
      }
    };

    Message* NewMessage(const Descriptor* descriptor) {
      DBG("Schema::NewMessage(descriptor) EX:");
      return factory_.GetPrototype(descriptor)->New();
    }

    Type* GetType(const Descriptor* descriptor) {
      DBG("Schema::GetType(descriptor) E:");
      Type* result = types_[descriptor];
      if (result) return result;

      result = types_[descriptor] =
        new Type(this, descriptor, TypeTemplate->GetFunction()->NewInstance());

      // managed schema->[type] link
      Handle<Array> types = Handle<Array>::Cast(handle_->GetInternalField(1));
      Handle<Value> key = Number::New(types->Length());
      types->Set(key, result->handle_);
      DBG("Schema::GetType(descriptor) X:");
      return result;
    }

    const DescriptorPool* pool_;
    map<const Descriptor*, Type*> types_;
    DynamicMessageFactory factory_;

    static Handle<Value> GetType(const Local<String> name,
                                 const AccessorInfo& args) {
      DBG("Schema::GetType(name, args) E:");
      Schema* schema = UnwrapThis<Schema>(args);
      const Descriptor* descriptor =
        schema->pool_->FindMessageTypeByName(*String::AsciiValue(name));

      DBG("Schema::GetType(name, args) X:");
      return descriptor ?
        schema->GetType(descriptor)->Constructor() :
        Handle<Function>();
    }

    static Handle<Value> NewSchema(const Arguments& args) {
      DBG("Schema::NewSchema E: args.Length()=%d", args.Length());
      if (!args.Length()) {
        return (new Schema(args.This(),
                           DescriptorPool::generated_pool()))->handle_;
      }

      Buffer* buf = ObjectWrap::Unwrap<Buffer>(args[0]->ToObject());

      FileDescriptorSet descriptors;
      if (!descriptors.ParseFromArray(buf->data(), buf->length())) {
        DBG("Schema::NewSchema X: bad descriptor");
        return v8::ThrowException(String::New("Malformed descriptor"));
      }

      DescriptorPool* pool = new DescriptorPool;
      for (int i = 0; i < descriptors.file_size(); i++) {
        pool->BuildFile(descriptors.file(i));
      }

      DBG("Schema::NewSchema X");
      return (new Schema(args.This(), pool))->handle_;
    }
  };

  void Init() {
    DBG("Init E:");
    HandleScope handle_scope;

    TypeTemplate = Persistent<FunctionTemplate>::New(FunctionTemplate::New());
    TypeTemplate->SetClassName(String::New("Type"));
    // native self
    // owning schema (so GC can manage our lifecyle)
    // constructor
    // toArray
    TypeTemplate->InstanceTemplate()->SetInternalFieldCount(4);

    SchemaTemplate = Persistent<FunctionTemplate>::New(FunctionTemplate::New(Schema::NewSchema));
    SchemaTemplate->SetClassName(String::New("Schema"));
    // native self
    // array of types (so GC can manage our lifecyle)
    SchemaTemplate->InstanceTemplate()->SetInternalFieldCount(2);
    SchemaTemplate->InstanceTemplate()->SetNamedPropertyHandler(Schema::GetType);

    ParseTemplate = Persistent<FunctionTemplate>::New(FunctionTemplate::New(Schema::Type::Parse));
    SerializeTemplate = Persistent<FunctionTemplate>::New(FunctionTemplate::New(Schema::Type::Serialize));

    DBG("Init X:");
  }

}  // namespace protobuf_v8

extern "C" void SchemaObjectTemplateInit(Handle<ObjectTemplate> target) {
  DBG("SchemaObjectTemplateInit(target) EX:");
  target->Set(String::New("Schema"), protobuf_v8::SchemaTemplate);
}
