/*
 * 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 "Platform.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>

#include "Socket.h"

void Socket::init() {
#ifdef _WINDOWS
  // version 2.2 supported on Win95OSR2/WinNT4 and up
  WORD winsockVers = MAKEWORD(2, 2);
  WSADATA wsaData;
  int err = WSAStartup(winsockVers, &wsaData);
  if (err) {
    // TODO(jat): report error
    Debug::log(Debug::Error) << "WSAStartup(vers=2.2): err=" << err << Debug::flush;
  }
#endif
}

bool Socket::connect(const char* host, int port) {
  Debug::log(Debug::Debugging) << "Socket::connect(host=" << host << ",port=" << port << ")"
      << Debug::flush;
  if (isConnected()) {
    Debug::log(Debug::Error) << "Socket::connect - already connected" << Debug::flush;
    return false;
  }

  SOCKETTYPE fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (fd < 0) {
    Debug::log(Debug::Error) << "Socket::connect - can't get socket" << Debug::flush;
    return false;
  }
#ifdef SO_NOSIGPIPE
  // On BSD, we need to suppress the SIGPIPE if the remote end disconnects.
  int option_value = 1;
  if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &option_value, sizeof(int))) {
    Debug::log(Debug::Error) << "Socket::connect - can't set NOSIGPIPE option" << Debug::flush;
    return false;
  }
#endif

  struct sockaddr_in sockAddr;
  memset(&sockAddr, 0, static_cast<int>(sizeof(sockAddr)));
  // check for numeric IP4 addresses first
  // TODO(jat): handle IPv6 addresses
  unsigned long numericAddr = inet_addr(host);
  if (numericAddr != 0xFFFFFFFF) {
    sockAddr.sin_addr.s_addr = numericAddr;
    sockAddr.sin_family = AF_INET;
  } else {
    struct hostent* hent = gethostbyname(host);
    if (!hent || !hent->h_addr_list[0]) {
      Debug::log(Debug::Error) << "Unable to get address for " << host << Debug::flush;
      return false;
    }
    memcpy(&(sockAddr.sin_addr), hent->h_addr_list[0], hent->h_length);
    sockAddr.sin_family = hent->h_addrtype;
  }
  sockAddr.sin_port = htons(port);

  if (::connect(fd, (struct sockaddr*) &sockAddr, sizeof(sockAddr)) < 0) {
#ifdef _WINDOWS
    char buf[256];
    strerror_s(buf, sizeof(buf), errno);
    Debug::log(Debug::Error) << "Can't connect to " << host << ":" << port << " -- "
        << buf << Debug::flush;
    closesocket(fd);
#else
    Debug::log(Debug::Error) << "Can't connect to " << host << ":" << port << " -- "
        << strerror(errno) << Debug::flush;
    close(fd);
#endif
    return false;
  }
  sock = fd;
  connected = true;
  readBufPtr = readValid = readBuf;
  writeBufPtr = writeBuf;
#ifdef _WINDOWS
  Debug::log(Debug::Spam) << "  connected" << Debug::flush;
#else
  Debug::log(Debug::Spam) << "  connected, fd=" << fd << Debug::flush;
#endif
  return true;
}

bool Socket::disconnect(bool doFlush) {
  if (connected) {
    Debug::log(Debug::Debugging) << "Disconnecting socket" << Debug::flush;
    if (doFlush) {
      flush();
    }
    connected = false;
#ifdef _WINDOWS
    closesocket(sock);
#else
    shutdown(sock, SHUT_RDWR);
    close(sock);
#endif
  }
  return true;
}
  
bool Socket::emptyWriteBuf() {
  size_t len = writeBufPtr - writeBuf;
  Debug::log(Debug::Spam) << "Socket::emptyWriteBuf: len=" << len << Debug::flush;
  ++numWrites;
  totWriteBytes += len;
  if (len > maxWriteBytes) {
    maxWriteBytes = len;
  }
  for (char* ptr = writeBuf; len > 0; ) {
    ssize_t n = send(sock, ptr, len, 0);
    if (n <= 0) {
      if (errno == EPIPE) {
        Debug::log(Debug::Warning) << "Other end of socket disconnected" << Debug::flush;
        disconnect(false);
        return false;
      }
      Debug::log(Debug::Error) << "Error " << errno << " writing " << len << " bytes to socket"
          << Debug::flush;
      return false;
    }
    ptr += n;
    len -= n;
  }
  writeBufPtr = writeBuf;
  return true;
}
  
bool Socket::fillReadBuf() {
  readBufPtr = readBuf;
  errno = 0;
  ssize_t n = recv(sock, readBuf, BUF_SIZE, 0);
  if (n <= 0) {
    // EOF results in no error
    if (!errno || errno == EPIPE) {
      Debug::log(Debug::Warning) << "Other end of socket disconnected" << Debug::flush;
      disconnect(false);
      return false;
    }
    Debug::log(Debug::Error) << "Error " << errno << " reading " << BUF_SIZE << " bytes from socket"
        << Debug::flush;
    return false;
  }
  ++numReads;
  totReadBytes += n;
  if (static_cast<size_t>(n) > maxReadBytes) {
    maxReadBytes = n;
  }
  readValid = readBuf + n;
  return true;
}
