// Copyright 2014 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 "device/bluetooth/bluetooth_l2cap_channel_mac.h"

#include "base/logging.h"
#include "base/mac/sdk_forward_declarations.h"
#include "device/bluetooth/bluetooth_device_mac.h"
#include "device/bluetooth/bluetooth_socket_mac.h"

// A simple delegate class for an open L2CAP channel that forwards methods to
// its wrapped |channel_|.
@interface BluetoothL2capChannelDelegate
    : NSObject <IOBluetoothL2CAPChannelDelegate> {
 @private
  device::BluetoothL2capChannelMac* channel_;  // weak
}

- (id)initWithChannel:(device::BluetoothL2capChannelMac*)channel;

@end

@implementation BluetoothL2capChannelDelegate

- (id)initWithChannel:(device::BluetoothL2capChannelMac*)channel {
  if ((self = [super init]))
    channel_ = channel;

  return self;
}

- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)l2capChannel
                          status:(IOReturn)error {
  channel_->OnChannelOpenComplete(l2capChannel, error);
}

- (void)l2capChannelWriteComplete:(IOBluetoothL2CAPChannel*)l2capChannel
                           refcon:(void*)refcon
                           status:(IOReturn)error {
  channel_->OnChannelWriteComplete(l2capChannel, refcon, error);
}

- (void)l2capChannelData:(IOBluetoothL2CAPChannel*)l2capChannel
                    data:(void*)dataPointer
                  length:(size_t)dataLength {
  channel_->OnChannelDataReceived(l2capChannel, dataPointer, dataLength);
}

- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
  channel_->OnChannelClosed(l2capChannel);
}

// These methods are marked as optional in the 10.8 SDK, but not in the 10.6
// SDK. These empty implementations can be removed once we drop the 10.6 SDK.
- (void)l2capChannelReconfigured:(IOBluetoothL2CAPChannel*)l2capChannel {
}
- (void)l2capChannelQueueSpaceAvailable:(IOBluetoothL2CAPChannel*)l2capChannel {
}

@end

namespace device {

BluetoothL2capChannelMac::BluetoothL2capChannelMac(
    BluetoothSocketMac* socket,
    IOBluetoothL2CAPChannel* channel)
    : channel_(channel),
      delegate_(nil) {
  SetSocket(socket);
}

BluetoothL2capChannelMac::~BluetoothL2capChannelMac() {
  [channel_ setDelegate:nil];
  [channel_ closeChannel];
}

// static
scoped_ptr<BluetoothL2capChannelMac> BluetoothL2capChannelMac::OpenAsync(
    BluetoothSocketMac* socket,
    IOBluetoothDevice* device,
    BluetoothL2CAPPSM psm,
    IOReturn* status) {
  DCHECK(socket);
  scoped_ptr<BluetoothL2capChannelMac> channel(
      new BluetoothL2capChannelMac(socket, nil));

  // Retain the delegate, because IOBluetoothDevice's
  // |-openL2CAPChannelAsync:withPSM:delegate:| assumes that it can take
  // ownership of the delegate without calling |-retain| on it...
  DCHECK(channel->delegate_);
  [channel->delegate_ retain];
  IOBluetoothL2CAPChannel* l2cap_channel;
  *status = [device openL2CAPChannelAsync:&l2cap_channel
                                  withPSM:psm
                                 delegate:channel->delegate_];
  if (*status == kIOReturnSuccess)
    channel->channel_.reset([l2cap_channel retain]);
  else
    channel.reset();

  return channel.Pass();
}

void BluetoothL2capChannelMac::SetSocket(BluetoothSocketMac* socket) {
  BluetoothChannelMac::SetSocket(socket);
  if (!this->socket())
    return;

  // Now that the socket is set, it's safe to associate a delegate, which can
  // call back to the socket.
  DCHECK(!delegate_);
  delegate_.reset(
      [[BluetoothL2capChannelDelegate alloc] initWithChannel:this]);
  [channel_ setDelegate:delegate_];
}

IOBluetoothDevice* BluetoothL2capChannelMac::GetDevice() {
  return [channel_ getDevice];
}

uint16_t BluetoothL2capChannelMac::GetOutgoingMTU() {
  return [channel_ outgoingMTU];
}

IOReturn BluetoothL2capChannelMac::WriteAsync(void* data,
                                              uint16_t length,
                                              void* refcon) {
  DCHECK_LE(length, GetOutgoingMTU());
  return [channel_ writeAsync:data length:length refcon:refcon];
}

void BluetoothL2capChannelMac::OnChannelOpenComplete(
    IOBluetoothL2CAPChannel* channel,
    IOReturn status) {
  if (channel_) {
    DCHECK_EQ(channel_, channel);
  } else {
    // The (potentially) asynchronous connection occurred synchronously.
    // Should only be reachable from OpenAsync().
    DCHECK_EQ(status, kIOReturnSuccess);
  }

  socket()->OnChannelOpenComplete(
      BluetoothDeviceMac::GetDeviceAddress([channel getDevice]), status);
}

void BluetoothL2capChannelMac::OnChannelClosed(
    IOBluetoothL2CAPChannel* channel) {
  DCHECK_EQ(channel_, channel);
  socket()->OnChannelClosed();
}

void BluetoothL2capChannelMac::OnChannelDataReceived(
    IOBluetoothL2CAPChannel* channel,
    void* data,
    size_t length) {
  DCHECK_EQ(channel_, channel);
  socket()->OnChannelDataReceived(data, length);
}

void BluetoothL2capChannelMac::OnChannelWriteComplete(
    IOBluetoothL2CAPChannel* channel,
    void* refcon,
    IOReturn status) {
  // Note: We use "CHECK" below to ensure we never run into unforeseen
  // occurrences of asynchronous callbacks, which could lead to data
  // corruption.
  CHECK_EQ(channel_, channel);
  socket()->OnChannelWriteComplete(refcon, status);
}

}  // namespace device
