// 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 <deque>
#include <string>
#include <vector>

#include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/api/api_resource_event_notifier.h"
#include "chrome/browser/extensions/api/serial/serial_api.h"
#include "chrome/browser/extensions/api/serial/serial_connection.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_function.h"
#include "chrome/browser/extensions/extension_function_test_utils.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
#include "chrome/browser/ui/browser.h"
#include "content/public/browser/browser_thread.h"
#include "testing/gmock/include/gmock/gmock.h"

using testing::_;
using testing::Return;

using content::BrowserThread;

namespace {

class SerialApiTest : public PlatformAppApiTest {
 public:
  SerialApiTest() {}
};

}  // namespace

namespace extensions {

class FakeSerialGetPortsFunction : public AsyncExtensionFunction {
 public:
  virtual bool RunImpl() {
    ListValue* ports = new ListValue();
    ports->Append(Value::CreateStringValue("/dev/fakeserial"));
    ports->Append(Value::CreateStringValue("\\\\COM800\\"));
    SetResult(ports);
    SendResponse(true);
    return true;
  }
 protected:
  virtual ~FakeSerialGetPortsFunction() {}
};

class FakeEchoSerialConnection : public SerialConnection {
 public:
  explicit FakeEchoSerialConnection(
      const std::string& port,
      int bitrate,
      const std::string& owner_extension_id,
      ApiResourceEventNotifier* event_notifier)
      : SerialConnection(port, bitrate, owner_extension_id, event_notifier),
        opened_(true) {
    Flush();
    opened_ = false;
  }

  virtual ~FakeEchoSerialConnection() {
  }

  virtual bool Open() {
    DCHECK(!opened_);
    opened_ = true;
    return true;
  }

  virtual void Close() {
    DCHECK(opened_);
  }

  virtual void Flush() {
    DCHECK(opened_);
    buffer_.clear();
  }

  virtual int Read(scoped_refptr<net::IOBufferWithSize> io_buffer) {
    DCHECK(io_buffer->data());

    if (buffer_.empty()) {
      return 0;
    }
    char *data = io_buffer->data();
    int bytes_to_copy = io_buffer->size();
    while (bytes_to_copy-- && !buffer_.empty()) {
      *data++ = buffer_.front();
      buffer_.pop_front();
    }
    return io_buffer->size();
  }

  virtual int Write(scoped_refptr<net::IOBuffer> io_buffer, int byte_count) {
    DCHECK(io_buffer.get());
    DCHECK(byte_count >= 0);

    char *data = io_buffer->data();
    int count = byte_count;
    while (count--)
      buffer_.push_back(*data++);
    return byte_count;
  }

  MOCK_METHOD1(GetControlSignals, bool(ControlSignals &));
  MOCK_METHOD1(SetControlSignals, bool(const ControlSignals &));

 private:
  bool opened_;
  std::deque<char> buffer_;

  DISALLOW_COPY_AND_ASSIGN(FakeEchoSerialConnection);
};

class FakeSerialOpenFunction : public SerialOpenFunction {
 protected:
  virtual SerialConnection* CreateSerialConnection(
      const std::string& port,
      int bitrate,
      const std::string& owner_extension_id,
      ApiResourceEventNotifier* event_notifier) OVERRIDE {
    FakeEchoSerialConnection* serial_connection =
        new FakeEchoSerialConnection(port, bitrate, owner_extension_id,
                                     event_notifier);
    EXPECT_CALL(*serial_connection, GetControlSignals(_)).
        Times(1).WillOnce(Return(true));
    EXPECT_CALL(*serial_connection, SetControlSignals(_)).
        Times(1).WillOnce(Return(true));
    return serial_connection;
  }
  virtual bool DoesPortExist(const std::string& port) OVERRIDE {
    return true;
  }

 protected:
  virtual ~FakeSerialOpenFunction() {}
};

}  // namespace extensions

ExtensionFunction* FakeSerialGetPortsFunctionFactory() {
  return new extensions::FakeSerialGetPortsFunction();
}

ExtensionFunction* FakeSerialOpenFunctionFactory() {
  return new extensions::FakeSerialOpenFunction();
}

// Disable SIMULATE_SERIAL_PORTS only if all the following are true:
//
// 1. You have an Arduino or compatible board attached to your machine and
// properly appearing as the first virtual serial port ("first" is very loosely
// defined as whichever port shows up in serial.getPorts). We've tested only
// the Atmega32u4 Breakout Board and Arduino Leonardo; note that both these
// boards are based on the Atmel ATmega32u4, rather than the more common
// Arduino '328p with either FTDI or '8/16u2 USB interfaces. TODO: test more
// widely.
//
// 2. Your user has permission to read/write the port. For example, this might
// mean that your user is in the "tty" or "uucp" group on Ubuntu flavors of
// Linux, or else that the port's path (e.g., /dev/ttyACM0) has global
// read/write permissions.
//
// 3. You have uploaded a program to the board that does a byte-for-byte echo
// on the virtual serial port at 57600 bps. An example is at
// chrome/test/data/extensions/api_test/serial/api/serial_arduino_test.ino.
//
#define SIMULATE_SERIAL_PORTS (1)
IN_PROC_BROWSER_TEST_F(SerialApiTest, SerialFakeHardware) {
  ResultCatcher catcher;
  catcher.RestrictToProfile(browser()->profile());

#if SIMULATE_SERIAL_PORTS
  ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction(
      "serial.getPorts",
      FakeSerialGetPortsFunctionFactory));
  ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction(
      "serial.open",
      FakeSerialOpenFunctionFactory));
#endif

  ASSERT_TRUE(RunExtensionTest("serial/api")) << message_;
}

IN_PROC_BROWSER_TEST_F(SerialApiTest, SerialRealHardware) {
  ResultCatcher catcher;
  catcher.RestrictToProfile(browser()->profile());

  ASSERT_TRUE(RunExtensionTest("serial/real_hardware")) << message_;
}