// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "build/build_config.h"

#if defined(OS_WIN)
// winsock2.h must be included first in order to ensure it is included before
// windows.h.
#include <winsock2.h>
#elif defined(OS_POSIX)
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "net/base/net_errors.h"
#if defined(USE_SYSTEM_LIBEVENT)
#include <event.h>
#else
#include "third_party/libevent/event.h"
#endif
#endif

#include "base/eintr_wrapper.h"
#include "net/base/net_util.h"
#include "net/base/listen_socket.h"

#if defined(OS_WIN)
typedef int socklen_t;
#endif  // defined(OS_WIN)

namespace {

const int kReadBufSize = 200;

}  // namespace

#if defined(OS_WIN)
const SOCKET ListenSocket::kInvalidSocket = INVALID_SOCKET;
const int ListenSocket::kSocketError = SOCKET_ERROR;
#elif defined(OS_POSIX)
const SOCKET ListenSocket::kInvalidSocket = -1;
const int ListenSocket::kSocketError = -1;
#endif

ListenSocket::ListenSocket(SOCKET s, ListenSocketDelegate *del)
    : socket_(s),
      socket_delegate_(del),
      reads_paused_(false),
      has_pending_reads_(false) {
#if defined(OS_WIN)
  socket_event_ = WSACreateEvent();
  // TODO(ibrar): error handling in case of socket_event_ == WSA_INVALID_EVENT
  WatchSocket(NOT_WAITING);
#endif
}

ListenSocket::~ListenSocket() {
#if defined(OS_WIN)
  if (socket_event_) {
    WSACloseEvent(socket_event_);
    socket_event_ = WSA_INVALID_EVENT;
  }
#endif
  CloseSocket(socket_);
}

SOCKET ListenSocket::Listen(std::string ip, int port) {
  SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (s != kInvalidSocket) {
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    addr.sin_port = htons(port);
    if (bind(s, reinterpret_cast<sockaddr*>(&addr), sizeof(addr))) {
#if defined(OS_WIN)
      closesocket(s);
#elif defined(OS_POSIX)
      close(s);
#endif
      s = kInvalidSocket;
    }
  }
  return s;
}

ListenSocket* ListenSocket::Listen(std::string ip, int port,
                                   ListenSocketDelegate* del) {
  SOCKET s = Listen(ip, port);
  if (s == kInvalidSocket) {
    // TODO(erikkay): error handling
  } else {
    ListenSocket* sock = new ListenSocket(s, del);
    sock->Listen();
    return sock;
  }
  return NULL;
}

void ListenSocket::Listen() {
  int backlog = 10;  // TODO(erikkay): maybe don't allow any backlog?
  listen(socket_, backlog);
  // TODO(erikkay): error handling
#if defined(OS_POSIX)
  WatchSocket(WAITING_ACCEPT);
#endif
}

SOCKET ListenSocket::Accept(SOCKET s) {
  sockaddr_in from;
  socklen_t from_len = sizeof(from);
  SOCKET conn =
      HANDLE_EINTR(accept(s, reinterpret_cast<sockaddr*>(&from), &from_len));
  if (conn != kInvalidSocket) {
    net::SetNonBlocking(conn);
  }
  return conn;
}

void ListenSocket::Accept() {
  SOCKET conn = Accept(socket_);
  if (conn != kInvalidSocket) {
    scoped_refptr<ListenSocket> sock =
        new ListenSocket(conn, socket_delegate_);
    // it's up to the delegate to AddRef if it wants to keep it around
#if defined(OS_POSIX)
    sock->WatchSocket(WAITING_READ);
#endif
    socket_delegate_->DidAccept(this, sock);
  } else {
    // TODO(ibrar): some error handling required here
  }
}

void ListenSocket::Read() {
  char buf[kReadBufSize + 1];  // +1 for null termination
  int len;
  do {
    len = HANDLE_EINTR(recv(socket_, buf, kReadBufSize, 0));
    if (len == kSocketError) {
#if defined(OS_WIN)
      int err = WSAGetLastError();
      if (err == WSAEWOULDBLOCK) {
#elif defined(OS_POSIX)
      if (errno == EWOULDBLOCK || errno == EAGAIN) {
#endif
        break;
      } else {
        // TODO(ibrar): some error handling required here
        break;
      }
    } else if (len == 0) {
      // In Windows, Close() is called by OnObjectSignaled.  In POSIX, we need
      // to call it here.
#if defined(OS_POSIX)
      Close();
#endif
    } else {
      // TODO(ibrar): maybe change DidRead to take a length instead
      DCHECK(len > 0 && len <= kReadBufSize);
      buf[len] = 0;  // already create a buffer with +1 length
      socket_delegate_->DidRead(this, buf);
    }
  } while (len == kReadBufSize);
}

void ListenSocket::CloseSocket(SOCKET s) {
  if (s && s != kInvalidSocket) {
    UnwatchSocket();
#if defined(OS_WIN)
    closesocket(s);
#elif defined(OS_POSIX)
    close(s);
#endif
  }
}

void ListenSocket::Close() {
#if defined(OS_POSIX)
  if (wait_state_ == WAITING_CLOSE)
    return;
  wait_state_ = WAITING_CLOSE;
#endif
  socket_delegate_->DidClose(this);
}

void ListenSocket::UnwatchSocket() {
#if defined(OS_WIN)
  watcher_.StopWatching();
#elif defined(OS_POSIX)
  watcher_.StopWatchingFileDescriptor();
#endif
}

void ListenSocket::WatchSocket(WaitState state) {
#if defined(OS_WIN)
  WSAEventSelect(socket_, socket_event_, FD_ACCEPT | FD_CLOSE | FD_READ);
  watcher_.StartWatching(socket_event_, this);
#elif defined(OS_POSIX)
  // Implicitly calls StartWatchingFileDescriptor().
  MessageLoopForIO::current()->WatchFileDescriptor(
      socket_, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
  wait_state_ = state;
#endif
}

void ListenSocket::SendInternal(const char* bytes, int len) {
  int sent = HANDLE_EINTR(send(socket_, bytes, len, 0));
  if (sent == kSocketError) {
#if defined(OS_WIN)
  int err = WSAGetLastError();
  if (err == WSAEWOULDBLOCK) {
#elif defined(OS_POSIX)
    if (errno == EWOULDBLOCK || errno == EAGAIN) {
#endif
    // TODO(ibrar): there should be logic here to handle this because
    // it is not an error
    }
  } else if (sent != len) {
    LOG(ERROR) << "send failed: ";
  }
}

void ListenSocket::Send(const char* bytes, int len, bool append_linefeed) {
  SendInternal(bytes, len);
  if (append_linefeed) {
    SendInternal("\r\n", 2);
  }
}

void ListenSocket::Send(const std::string& str, bool append_linefeed) {
  Send(str.data(), static_cast<int>(str.length()), append_linefeed);
}

void ListenSocket::PauseReads() {
  DCHECK(!reads_paused_);
  reads_paused_ = true;
}

void ListenSocket::ResumeReads() {
  DCHECK(reads_paused_);
  reads_paused_ = false;
  if (has_pending_reads_) {
    has_pending_reads_ = false;
    Read();
  }
}

// TODO(ibrar): We can add these functions into OS dependent files
#if defined(OS_WIN)
// MessageLoop watcher callback
void ListenSocket::OnObjectSignaled(HANDLE object) {
  WSANETWORKEVENTS ev;
  if (kSocketError == WSAEnumNetworkEvents(socket_, socket_event_, &ev)) {
    // TODO
    return;
  }

  // The object was reset by WSAEnumNetworkEvents.  Watch for the next signal.
  watcher_.StartWatching(object, this);

  if (ev.lNetworkEvents == 0) {
    // Occasionally the event is set even though there is no new data.
    // The net seems to think that this is ignorable.
    return;
  }
  if (ev.lNetworkEvents & FD_ACCEPT) {
    Accept();
  }
  if (ev.lNetworkEvents & FD_READ) {
    if (reads_paused_) {
      has_pending_reads_ = true;
    } else {
      Read();
    }
  }
  if (ev.lNetworkEvents & FD_CLOSE) {
    Close();
  }
}
#elif defined(OS_POSIX)
void ListenSocket::OnFileCanReadWithoutBlocking(int fd) {
  if (wait_state_ == WAITING_ACCEPT) {
    Accept();
  }
  if (wait_state_ == WAITING_READ) {
    if (reads_paused_) {
      has_pending_reads_ = true;
    } else {
      Read();
    }
  }
  if (wait_state_ == WAITING_CLOSE) {
    // Close() is called by Read() in the Linux case.
    // TODO(erikkay): this seems to get hit multiple times after the close
  }
}

void ListenSocket::OnFileCanWriteWithoutBlocking(int fd) {
  // MessagePumpLibevent callback, we don't listen for write events
  // so we shouldn't ever reach here.
  NOTREACHED();
}

#endif
