// Copyright (c) 2012 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 "net/spdy/spdy_websocket_stream.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_protocol.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_stream.h"
#include "url/gurl.h"

namespace net {

SpdyWebSocketStream::SpdyWebSocketStream(
    const base::WeakPtr<SpdySession>& spdy_session, Delegate* delegate)
    : spdy_session_(spdy_session),
      pending_send_data_length_(0),
      delegate_(delegate),
      weak_ptr_factory_(this) {
  DCHECK(spdy_session_.get());
  DCHECK(delegate_);
}

SpdyWebSocketStream::~SpdyWebSocketStream() {
  delegate_ = NULL;
  Close();
}

int SpdyWebSocketStream::InitializeStream(const GURL& url,
                                          RequestPriority request_priority,
                                          const BoundNetLog& net_log) {
  if (!spdy_session_)
    return ERR_SOCKET_NOT_CONNECTED;

  int rv = stream_request_.StartRequest(
      SPDY_BIDIRECTIONAL_STREAM, spdy_session_, url, request_priority, net_log,
      base::Bind(&SpdyWebSocketStream::OnSpdyStreamCreated,
                 weak_ptr_factory_.GetWeakPtr()));

  if (rv == OK) {
    stream_ = stream_request_.ReleaseStream();
    DCHECK(stream_.get());
    stream_->SetDelegate(this);
  }
  return rv;
}

int SpdyWebSocketStream::SendRequest(scoped_ptr<SpdyHeaderBlock> headers) {
  if (!stream_.get()) {
    NOTREACHED();
    return ERR_UNEXPECTED;
  }
  int result = stream_->SendRequestHeaders(headers.Pass(), MORE_DATA_TO_SEND);
  if (result < OK && result != ERR_IO_PENDING)
    Close();
  return result;
}

int SpdyWebSocketStream::SendData(const char* data, int length) {
  if (!stream_.get()) {
    NOTREACHED();
    return ERR_UNEXPECTED;
  }
  DCHECK_GE(length, 0);
  pending_send_data_length_ = static_cast<size_t>(length);
  scoped_refptr<IOBuffer> buf(new IOBuffer(length));
  memcpy(buf->data(), data, length);
  stream_->SendData(buf.get(), length, MORE_DATA_TO_SEND);
  return ERR_IO_PENDING;
}

void SpdyWebSocketStream::Close() {
  if (stream_.get()) {
    stream_->Close();
    DCHECK(!stream_.get());
  }
}

void SpdyWebSocketStream::OnRequestHeadersSent() {
  DCHECK(delegate_);
  delegate_->OnSentSpdyHeaders();
}

SpdyResponseHeadersStatus SpdyWebSocketStream::OnResponseHeadersUpdated(
    const SpdyHeaderBlock& response_headers) {
  DCHECK(delegate_);
  delegate_->OnSpdyResponseHeadersUpdated(response_headers);
  return RESPONSE_HEADERS_ARE_COMPLETE;
}

void SpdyWebSocketStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
  DCHECK(delegate_);
  delegate_->OnReceivedSpdyData(buffer.Pass());
}

void SpdyWebSocketStream::OnDataSent() {
  DCHECK(delegate_);
  delegate_->OnSentSpdyData(pending_send_data_length_);
  pending_send_data_length_ = 0;
}

void SpdyWebSocketStream::OnClose(int status) {
  stream_.reset();

  // Destruction without Close() call OnClose() with delegate_ being NULL.
  if (!delegate_)
    return;
  Delegate* delegate = delegate_;
  delegate_ = NULL;
  delegate->OnCloseSpdyStream();
}

void SpdyWebSocketStream::OnSpdyStreamCreated(int result) {
  DCHECK_NE(ERR_IO_PENDING, result);
  if (result == OK) {
    stream_ = stream_request_.ReleaseStream();
    DCHECK(stream_.get());
    stream_->SetDelegate(this);
  }
  DCHECK(delegate_);
  delegate_->OnCreatedSpdyStream(result);
}

}  // namespace net