/*
 * 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.
 */

#include <string>

#include "Debug.h"
#include "FreeValueMessage.h"
#include "HostChannel.h"
#include "InvokeMessage.h"
#include "InvokeSpecialMessage.h"
#include "ReturnMessage.h"
#include "ServerMethods.h"
#include "scoped_ptr/scoped_ptr.h"

using std::string;

Value ServerMethods::getProperty(HostChannel& channel, SessionHandler* handler, int objectRef,
    int dispatchId) {
  if (!channel.isConnected()) {
    Debug::log(Debug::Debugging) << "Ignoring getProperty after disconnect"
        << Debug::flush;
    return Value();
  }
  Value args[2];
  args[0].setInt(objectRef);
  args[1].setInt(dispatchId);
  if (!InvokeSpecialMessage::send(channel, SPECIAL_GET_PROPERTY, 2, args)) {
    Debug::log(Debug::Error) << "  failed to send invoke of GetProperty(disp=" << dispatchId
        << ", obj=" << objectRef << ")" << Debug::flush;
    return Value();
  }
  scoped_ptr<ReturnMessage> retMsg(channel.reactToMessagesWhileWaitingForReturn(handler));
  if (!retMsg.get()) {
    Debug::log(Debug::Error) << "getProperty: get return value failed for GetProperty(disp="
        << dispatchId << ", obj=" << objectRef << ")" << Debug::flush;
    return Value();
  }
  return retMsg->getReturnValue();
}

int ServerMethods::hasMethod(HostChannel& channel, SessionHandler* handler, int classId,
    const std::string& name) {
  if (name != "toString" && name.find("::") == string::npos) {
    // only JSNI-style names and toString are valid
    return -1;
  }
  if (!channel.isConnected()) {
    Debug::log(Debug::Debugging) << "Ignoring hasMethod after disconnect"
        << Debug::flush;
    return -2;
  }
  Value arg;
  arg.setString(name);
  if (!InvokeSpecialMessage::send(channel, SPECIAL_HAS_METHOD, 1, &arg)) {
    Debug::log(Debug::Error) << "hasMethod: invoke(hasMethod) failed" << Debug::flush;
    return -2;
  }
  scoped_ptr<ReturnMessage> retMsg(channel.reactToMessagesWhileWaitingForReturn(handler));
  if (!retMsg.get()) {
    Debug::log(Debug::Error) << "hasMethod: get return value failed" << Debug::flush;
    return -2;
  }
  Value retval = retMsg->getReturnValue();
  // TODO(jat): better error handling?
  return retval.isInt() ? retval.getInt() : -2;
}

int ServerMethods::hasProperty(HostChannel& channel, SessionHandler* handler, int classId,
    const std::string& name) {
  if (name != "toString" && name.find("::") == string::npos) {
    // only JSNI-style names and toString are valid
    return -1;
  }
  if (!channel.isConnected()) {
    Debug::log(Debug::Debugging) << "Ignoring hasProperty after disconnect"
        << Debug::flush;
    return -2;
  }
  Value arg;
  arg.setString(name);
  if (!InvokeSpecialMessage::send(channel, SPECIAL_HAS_PROPERTY, 1, &arg)) {
    Debug::log(Debug::Error) << "hasProperty: invoke(hasProperty) failed" << Debug::flush;
    return -2;
  }
  scoped_ptr<ReturnMessage> retMsg(channel.reactToMessagesWhileWaitingForReturn(handler));
  if (!retMsg.get()) {
    Debug::log(Debug::Error) << "hasProperty: get return value failed" << Debug::flush;
    return -2;
  }
  Value retval = retMsg->getReturnValue();
  // TODO(jat): better error handling?
  return retval.isInt() ? retval.getInt() : -2;
}

bool ServerMethods::setProperty(HostChannel& channel, SessionHandler* handler, int objectRef,
    int dispatchId, const Value& value) {
  if (!channel.isConnected()) {
    Debug::log(Debug::Debugging) << "Ignoring setProperty after disconnect"
        << Debug::flush;
    return false;
  }
  // TODO(jat): error handling?
  Value args[3];
  args[0].setInt(objectRef);
  args[1].setInt(dispatchId);
  args[2] = value;
  if (!InvokeSpecialMessage::send(channel, SPECIAL_SET_PROPERTY, 3, args)) {
    Debug::log(Debug::Error) << "  failed to send invoke of SetProperty(disp=" << dispatchId
        << ", obj=" << objectRef << ")" << Debug::flush;
    return false;
  }
  scoped_ptr<ReturnMessage> retMsg(channel.reactToMessagesWhileWaitingForReturn(handler));
  if (!retMsg.get()) {
    Debug::log(Debug::Error) << "setProperty: get return value failed for SetProperty(disp="
        << dispatchId << ", obj=" << objectRef << ")" << Debug::flush;
    return false;
  }
  // TODO: use the returned exception?
  return !retMsg.get()->isException();
}

bool ServerMethods::freeJava(HostChannel& channel, SessionHandler* handler, int idCount,
      const int* ids) {
  // If we are disconnected, assume the server will free all of these anyway.
  // This deals with the problem of getting finalizers called after the channel is dropped.
  if (!channel.isConnected()) {
    Debug::log(Debug::Debugging) << "Ignoring freeJava after disconnect"
        << Debug::flush;
    return true;
  }
  if (!FreeValueMessage::send(channel, idCount, ids)) {
    Debug::log(Debug::Error) << "  failed to send FreeValues message" << Debug::flush;
    return false;
  }
  return true;
}
