// 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 "chrome/browser/extensions/api/terminal/terminal_private_api.h"

#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/terminal/terminal_extension_helper.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/terminal_private.h"
#include "chromeos/process_proxy/process_proxy_registry.h"
#include "content/public/browser/browser_thread.h"

namespace terminal_private = extensions::api::terminal_private;
namespace OnTerminalResize =
    extensions::api::terminal_private::OnTerminalResize;
namespace OpenTerminalProcess =
    extensions::api::terminal_private::OpenTerminalProcess;
namespace SendInput = extensions::api::terminal_private::SendInput;

namespace {

const char kCroshName[] = "crosh";
const char kCroshCommand[] = "/usr/bin/crosh";
// We make stubbed crosh just echo back input.
const char kStubbedCroshCommand[] = "cat";

const char* GetCroshPath() {
  if (base::SysInfo::IsRunningOnChromeOS())
    return kCroshCommand;
  else
    return kStubbedCroshCommand;
}

const char* GetProcessCommandForName(const std::string& name) {
  if (name == kCroshName)
    return GetCroshPath();
  else
    return NULL;
}

void NotifyProcessOutput(Profile* profile,
                         const std::string& extension_id,
                         pid_t pid,
                         const std::string& output_type,
                         const std::string& output) {
  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
        base::Bind(&NotifyProcessOutput, profile, extension_id,
                                         pid, output_type, output));
    return;
  }

  scoped_ptr<base::ListValue> args(new base::ListValue());
  args->Append(new base::FundamentalValue(pid));
  args->Append(new base::StringValue(output_type));
  args->Append(new base::StringValue(output));

  if (profile &&
      extensions::ExtensionSystem::Get(profile)->event_router()) {
    scoped_ptr<extensions::Event> event(new extensions::Event(
        terminal_private::OnProcessOutput::kEventName, args.Pass()));
    extensions::ExtensionSystem::Get(profile)->event_router()->
        DispatchEventToExtension(extension_id, event.Pass());
  }
}

}  // namespace

namespace extensions {

TerminalPrivateFunction::TerminalPrivateFunction() {}

TerminalPrivateFunction::~TerminalPrivateFunction() {}

bool TerminalPrivateFunction::RunImpl() {
  return RunTerminalFunction();
}

TerminalPrivateOpenTerminalProcessFunction::
    TerminalPrivateOpenTerminalProcessFunction() : command_(NULL) {}

TerminalPrivateOpenTerminalProcessFunction::
    ~TerminalPrivateOpenTerminalProcessFunction() {}

bool TerminalPrivateOpenTerminalProcessFunction::RunTerminalFunction() {
  scoped_ptr<OpenTerminalProcess::Params> params(
      OpenTerminalProcess::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  command_ = GetProcessCommandForName(params->process_name);
  if (!command_) {
    error_ = "Invalid process name.";
    return false;
  }

  // Registry lives on FILE thread.
  content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
      base::Bind(&TerminalPrivateOpenTerminalProcessFunction::OpenOnFileThread,
                 this));
  return true;
}

void TerminalPrivateOpenTerminalProcessFunction::OpenOnFileThread() {
  DCHECK(command_);

  chromeos::ProcessProxyRegistry* registry =
      chromeos::ProcessProxyRegistry::Get();
  pid_t pid;
  if (!registry->OpenProcess(
          command_, &pid,
          base::Bind(&NotifyProcessOutput, profile_, extension_id()))) {
    // If new process could not be opened, we return -1.
    pid = -1;
  }

  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
      base::Bind(&TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread,
                 this, pid));
}

TerminalPrivateSendInputFunction::~TerminalPrivateSendInputFunction() {}

void TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread(pid_t pid) {
  SetResult(new base::FundamentalValue(pid));
  SendResponse(true);
}

bool TerminalPrivateSendInputFunction::RunTerminalFunction() {
  scoped_ptr<SendInput::Params> params(SendInput::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  // Registry lives on the FILE thread.
  content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
      base::Bind(&TerminalPrivateSendInputFunction::SendInputOnFileThread,
                 this, params->pid, params->input));
  return true;
}

void TerminalPrivateSendInputFunction::SendInputOnFileThread(pid_t pid,
    const std::string& text) {
  bool success = chromeos::ProcessProxyRegistry::Get()->SendInput(pid, text);

  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
      base::Bind(&TerminalPrivateSendInputFunction::RespondOnUIThread, this,
      success));
}

void TerminalPrivateSendInputFunction::RespondOnUIThread(bool success) {
  SetResult(new base::FundamentalValue(success));
  SendResponse(true);
}

TerminalPrivateCloseTerminalProcessFunction::
    ~TerminalPrivateCloseTerminalProcessFunction() {}

bool TerminalPrivateCloseTerminalProcessFunction::RunTerminalFunction() {
  if (args_->GetSize() != 1)
    return false;
  pid_t pid;
  if (!args_->GetInteger(0, &pid))
    return false;

  // Registry lives on the FILE thread.
  content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
      base::Bind(&TerminalPrivateCloseTerminalProcessFunction::
                 CloseOnFileThread, this, pid));

  return true;
}

void TerminalPrivateCloseTerminalProcessFunction::CloseOnFileThread(pid_t pid) {
  bool success = chromeos::ProcessProxyRegistry::Get()->CloseProcess(pid);

  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
      base::Bind(&TerminalPrivateCloseTerminalProcessFunction::
                 RespondOnUIThread, this, success));
}

void TerminalPrivateCloseTerminalProcessFunction::RespondOnUIThread(
    bool success) {
  SetResult(new base::FundamentalValue(success));
  SendResponse(true);
}

TerminalPrivateOnTerminalResizeFunction::
    ~TerminalPrivateOnTerminalResizeFunction() {}

bool TerminalPrivateOnTerminalResizeFunction::RunTerminalFunction() {
  scoped_ptr<OnTerminalResize::Params> params(
      OnTerminalResize::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  // Registry lives on the FILE thread.
  content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
      base::Bind(&TerminalPrivateOnTerminalResizeFunction::OnResizeOnFileThread,
                 this, params->pid, params->width, params->height));

  return true;
}

void TerminalPrivateOnTerminalResizeFunction::OnResizeOnFileThread(pid_t pid,
                                                    int width, int height) {
  bool success = chromeos::ProcessProxyRegistry::Get()->OnTerminalResize(
      pid, width, height);

  content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
      base::Bind(&TerminalPrivateOnTerminalResizeFunction::RespondOnUIThread,
                 this, success));
}

void TerminalPrivateOnTerminalResizeFunction::RespondOnUIThread(bool success) {
  SetResult(new base::FundamentalValue(success));
  SendResponse(true);
}

}  // namespace extensions