/*
 * 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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>

#include "Debug.h"

#ifdef _WINDOWS
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#endif
#include <time.h>

#include "Platform.h"
#include "ByteOrder.h"

#include "CheckVersionsMessage.h"
#include "ProtocolVersionMessage.h"
#include "ChooseTransportMessage.h"
#include "SwitchTransportMessage.h"
#include "FatalErrorMessage.h"
#include "FreeValueMessage.h"
#include "HostChannel.h"
#include "LoadJsniMessage.h"
#include "InvokeMessage.h"
#include "InvokeSpecialMessage.h"
#include "QuitMessage.h"
#include "ReturnMessage.h"
#include "Value.h"
#include "scoped_ptr/scoped_ptr.h"

using namespace std;

ByteOrder HostChannel::byteOrder;

bool HostChannel::connectToHost(const char* host, unsigned port) {
  if (!port) {
    port = 9997;
  }
  Debug::log(Debug::Info)
      << "Initiating GWT Development Mode connection to host " << host
      << ", port " << port << Debug::flush;
  return sock.connect(host, port);
}

bool HostChannel::init(SessionHandler* handler, int minProtoVers,
    int maxProtoVers, const std::string& hostedHtmlVers) {
  Debug::log(Debug::Debugging)
      << "  negotiating versions - we support protocol " << minProtoVers
      << " through " << maxProtoVers << ", hostedHtmlVersion=" << hostedHtmlVers
      << Debug::flush;
  // TODO(jat): support transport selection
  CheckVersionsMessage::send(*this, minProtoVers, maxProtoVers, hostedHtmlVers);
  flush();
  char type;
  if (!readByte(type)) {
    handler->fatalError(*this, "Failed to receive message type");
    Debug::log(Debug::Error) << "Failed to receive message type" << Debug::flush;
    disconnectFromHost();
    return false;
  }
  switch (type) {
    case MESSAGE_TYPE_PROTOCOL_VERSION:
    {
      scoped_ptr<ProtocolVersionMessage> imsg(ProtocolVersionMessage
          ::receive(*this));
      if (!imsg.get()) {
        Debug::log(Debug::Error) << "Failed to receive protocol version message"
            << Debug::flush;
        return false;
      }
      // TODO(jat): save selected protocol version when we support a range
      break;
    }
    case MESSAGE_TYPE_FATAL_ERROR:
    {
      scoped_ptr<FatalErrorMessage> imsg(FatalErrorMessage::receive(*this));
      if (!imsg.get()) {
        Debug::log(Debug::Error) << "Failed to receive fatal error message"
            << Debug::flush;
        return false;
      }
      handler->fatalError(*this, imsg.get()->getError());
      return false;
    }
    default:
      return false;
  }
  return true;
}

bool HostChannel::disconnectFromHost() {
  Debug::log(Debug::Debugging) << "Disconnecting channel" << Debug::flush;
  if (!isConnected()) {
    Debug::log(Debug::Error) << "Disconnecting already disconnected channel" << Debug::flush;
    return false;
  }
  QuitMessage::send(*this);
  flush();
  sock.disconnect();
  return true;
}

bool HostChannel::readInt(int32_t& data) {
  int32_t d;
  if (!readBytes(&d, sizeof(d))) return false;
  data = ntohl(d);
  return true;
}

bool HostChannel::sendInt(int32_t data) {
  uint32_t d = htonl(data);
  return sendBytes(&d, sizeof(d));
}

bool HostChannel::readShort(short& data) {
  int16_t d;
  if (!readBytes(&d, sizeof(d))) return false;
  data = ntohs(d);
  return true;
}

bool HostChannel::sendShort(const short data) {
  uint16_t d = htons(data);
  return sendBytes(&d, sizeof(d));
}

bool HostChannel::readLong(int64_t& data) {
  // network is big-endian
  int32_t d[2];
  if (!readInt(d[0])) return false;
  if (!readInt(d[1])) return false;
  data = (static_cast<int64_t>(d[0]) << 32) | ntohl(d[1]);
  return true;
}

bool HostChannel::sendLong(const int64_t data) {
  if (!sendInt(static_cast<int32_t>(data >> 32))) {
    return false;
  }
  return sendInt(static_cast<int32_t>(data));
}

bool HostChannel::readFloat(float& data) {
  char bytes[sizeof(data)];
  if (!readBytes(bytes, sizeof(bytes))) {
    return false;
  }
  data = byteOrder.floatFromBytes(bytes);
  return true;
}

bool HostChannel::sendFloat(const float data) {
  char bytes[sizeof(data)];
  byteOrder.bytesFromFloat(data, bytes);
  return sendBytes(bytes, sizeof(bytes));
}

bool HostChannel::readDouble(double& data) {
  char bytes[sizeof(data)];
  if (!readBytes(bytes, sizeof(bytes))) {
    return false;
  }
  data = byteOrder.doubleFromBytes(bytes);
  return true;
}

bool HostChannel::sendDouble(const double data) {
  char bytes[sizeof(data)];
  byteOrder.bytesFromDouble(data, bytes);
  return sendBytes(bytes, sizeof(bytes));
}

bool HostChannel::readStringLength(uint32_t& data) {
  int32_t val;
  if (!readInt(val)) return false;
  // TODO: assert positive?
  data = val;
  return true;
}

bool HostChannel::readStringBytes(char* data, const uint32_t len) {
  return readBytes(data, len);
}

bool HostChannel::readString(std::string& strRef) {
  uint32_t len;
  if (!readStringLength(len)) {
    Debug::log(Debug::Error) << "readString: failed to read length"
        << Debug::flush;
    return false;
  }
  // Allocating variable-length arrays on the stack is a GCC feature,
  // and is vulnerable to stack overflow attacks, so we allocate on the heap.
  scoped_array<char> buf(new char[len]);
  if (!readStringBytes(buf.get(), len)) {
    Debug::log(Debug::Error) << "readString: failed to read " << len
        << " bytes" << Debug::flush;
    return false;
  }
  strRef.assign(buf.get(), len);
  return true;
}

static inline double operator-(const struct timeval& end,
    const struct timeval& begin) {
  double us = end.tv_sec * 1000000.0 + end.tv_usec - begin.tv_sec * 1000000.0
      - begin.tv_usec;
  return us;
}

ReturnMessage* HostChannel::reactToMessages(SessionHandler* handler, bool expectReturn) {
  char type;
  while (true) {
    flush();
    Debug::log(Debug::Spam) << "Waiting for response, flushed output"
        << Debug::flush;
    if (!readByte(type)) {
      if (isConnected()) {
        Debug::log(Debug::Error) << "Failed to receive message type"
            << Debug::flush;
      }
      return 0;
    }
    switch (type) {
      case MESSAGE_TYPE_INVOKE:
        {
          scoped_ptr<InvokeMessage> imsg(InvokeMessage::receive(*this));
          if (!imsg.get()) {
            Debug::log(Debug::Error) << "Failed to receive invoke message" << Debug::flush;
            return 0;
          }
          Value returnValue;
          bool exception = handler->invoke(*this, imsg->getThis(), imsg->getMethodName(),
              imsg->getNumArgs(), imsg->getArgs(), &returnValue);
          handler->sendFreeValues(*this);
          ReturnMessage::send(*this, exception, returnValue);
        }
        break;
      case MESSAGE_TYPE_INVOKESPECIAL:
        {
          // scottb: I think this is never used; I think server never sends invokeSpecial
          scoped_ptr<InvokeSpecialMessage> imsg(InvokeSpecialMessage::receive(*this));
          if (!imsg.get()) {
            Debug::log(Debug::Error) << "Failed to receive invoke special message" << Debug::flush;
            return 0;
          }
          Value returnValue;
          bool exception = handler->invokeSpecial(*this, imsg->getDispatchId(),
              imsg->getNumArgs(), imsg->getArgs(), &returnValue);
          handler->sendFreeValues(*this);
          ReturnMessage::send(*this, exception, returnValue);
        }
        break;
      case MESSAGE_TYPE_FREEVALUE:
        {
          scoped_ptr<FreeValueMessage> freeMsg(FreeValueMessage::receive(*this));
          if (!freeMsg.get()) {
            Debug::log(Debug::Error) << "Failed to receive free value message" << Debug::flush;
            return 0;
          }
          handler->freeValue(*this, freeMsg->getIdCount(), freeMsg->getIds());
        }
        // do not send a response
        break;
      case MESSAGE_TYPE_LOADJSNI:
        {
          scoped_ptr<LoadJsniMessage> loadMsg(LoadJsniMessage::receive(*this));
          if (!loadMsg.get()) {
            Debug::log(Debug::Error) << "Failed to receive load JSNI message" << Debug::flush;
            return 0;
          }
          handler->loadJsni(*this, loadMsg->getJs());
        }
        // do not send a response
        break;
      case MESSAGE_TYPE_RETURN:
        if (!expectReturn) {
          Debug::log(Debug::Error) << "Received unexpected RETURN" << Debug::flush;
        }
        return ReturnMessage::receive(*this);
      case MESSAGE_TYPE_QUIT:
        if (expectReturn) {
          Debug::log(Debug::Error) << "Received QUIT while waiting for return" << Debug::flush;
        }
    	  disconnectFromHost();
        return 0;
      default:
        // TODO(jat): error handling
        Debug::log(Debug::Error) << "Unexpected message type " << type
            << ", expectReturn=" << expectReturn << Debug::flush;
        disconnectFromHost();
        return 0;
    }
  }
}

bool HostChannel::readValue(Value& valueRef) {
  char typeBuf;
  if (!readByte(typeBuf)) return false;
  Value::ValueType type = Value::ValueType(typeBuf);
  switch (type) {
    case Value::NULL_TYPE:
      valueRef.setNull();
      return true;
    case Value::UNDEFINED:
      valueRef.setUndefined();
      return true;
    case Value::BOOLEAN:
      {
        char val;
        if (!readByte(val)) return false;
        valueRef.setBoolean(val != 0);
      }
      return true;
    case Value::BYTE:
      {
        char val;
        if (!readByte(val)) return false;
        valueRef.setByte(val);
      }
      return true;
    case Value::CHAR:
      {
        short val;
        if (!readShort(val)) return false;
        valueRef.setChar(val);
      }
      return true;
    case Value::SHORT:
      {
        short val;
        if (!readShort(val)) return false;
        valueRef.setShort(val);
      }
      return true;
    case Value::STRING:
      {
        std::string val;
        if (!readString(val)) return false;
        valueRef.setString(val);
      }
      return true;
    case Value::INT:
      {
        int val;
        if (!readInt(val)) return false;
        valueRef.setInt(val);
      }
      return true;
    case Value::LONG:
      {
        int64_t val;
        if (!readLong(val)) return false;
        valueRef.setLong(val);
      }
      return true;
    case Value::DOUBLE:
      {
        double val;
        if (!readDouble(val)) return false;
        valueRef.setDouble(val);
      }
      return true;
    case Value::JAVA_OBJECT:
      {
        int objId;
        if (!readInt(objId)) return false;
        valueRef.setJavaObject(objId);
      }
      return true;
    case Value::JS_OBJECT:
      {
        int val;
        if (!readInt(val)) return false;
        valueRef.setJsObjectId(val);
      }
      return true;
    default:
      Debug::log(Debug::Error) << "Unhandled value type sent from server: " << type << Debug::flush;
      break;
  }
  return false;
}

bool HostChannel::sendValue(const Value& value) {
  Value::ValueType type = value.getType();
  if (!sendByte(type)) return false;
  switch (type) {
    case Value::NULL_TYPE:
    case Value::UNDEFINED:
      // Null and Undefined only have the tag byte, no data
      return true;
    case Value::BOOLEAN:
      return sendByte(value.getBoolean() ? 1 : 0);
    case Value::BYTE:
      return sendByte(value.getByte());
    case Value::CHAR:
      return sendShort(short(value.getChar()));
    case Value::SHORT:
      return sendShort(value.getShort());
    case Value::INT:
      return sendInt(value.getInt());
    case Value::LONG:
      return sendLong(value.getLong());
    case Value::STRING:
      return sendString(value.getString());
    case Value::DOUBLE:
      return sendDouble(value.getDouble());
    case Value::FLOAT:
      return sendFloat(value.getFloat());
    case Value::JS_OBJECT:
      return sendInt(value.getJsObjectId());
    case Value::JAVA_OBJECT:
      return sendInt(value.getJavaObjectId());
    default:
      Debug::log(Debug::Error) << "Unhandled value type sent to server: " << type << Debug::flush;
      break;
  }
  return false;
}
