// Copyright (c) 2010 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_frame/cfproxy_private.h"

#include "base/tuple.h"
#include "ipc/ipc_sync_message.h"
#include "chrome/common/automation_messages.h"

CFProxy::CFProxy(CFProxyTraits* api) : ipc_thread_("ipc"),
                                       sync_dispatcher_(&tab2delegate_),
                                       ipc_sender_(NULL),
                                       api_(api),
                                       delegate_count_(0),
                                       is_connected_(false) {
}

CFProxy::~CFProxy() {
  ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
        &CFProxy::CleanupOnIoThread));
  // ipc_thread destructor will do the Stop anyway. this is for debug :)
  ipc_thread_.Stop();
}


void CFProxy::Init(const ProxyParams& params) {
  ipc_thread_.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0));
  ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
      &CFProxy::InitInIoThread, params));
}

int CFProxy::AddDelegate(ChromeProxyDelegate* delegate) {
  ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
      &CFProxy::AddDelegateOnIoThread, delegate));
  return ++delegate_count_;
}

int CFProxy::RemoveDelegate(ChromeProxyDelegate* delegate) {
  ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
      &CFProxy::RemoveDelegateOnIoThread, delegate));
  return --delegate_count_;
}

void CFProxy::AddDelegateOnIoThread(ChromeProxyDelegate* delegate) {
  DCHECK(CalledOnIpcThread());
  DelegateHolder::AddDelegate(delegate);
  if (is_connected_) {
    delegate->Connected(this);
  }
}

void CFProxy::RemoveDelegateOnIoThread(ChromeProxyDelegate* delegate) {
  DCHECK(CalledOnIpcThread());
  // Cancel any calls in progress.
  sync_dispatcher_.Cancel(delegate);
  DelegateHolder::RemoveDelegate(delegate);
  delegate->Disconnected();
}

void CFProxy::InitInIoThread(const ProxyParams& params) {
  DCHECK(CalledOnIpcThread());
  std::string channel_id = GenerateChannelId();
  ipc_sender_ = api_->CreateChannel(channel_id, this);
  std::wstring cmd_line = BuildCmdLine(channel_id, params.profile_path,
                                       params.extra_params);
  if (api_->LaunchApp(cmd_line)) {
    CancelableTask* launch_timeout = NewRunnableMethod(this,
        &CFProxy::LaunchTimeOut);
    ipc_thread_.message_loop()->PostDelayedTask(FROM_HERE, launch_timeout,
        params.timeout.InMilliseconds());
  } else {
    OnPeerLost(ChromeProxyDelegate::CHROME_EXE_LAUNCH_FAILED);
  }
}

void CFProxy::CleanupOnIoThread() {
  DCHECK(CalledOnIpcThread());
  if (ipc_sender_) {
    api_->CloseChannel(ipc_sender_);
    ipc_sender_ = NULL;
  }
  // TODO(stoyan): shall we notify delegates?
  // The object is dying, so under normal circumstances there should be
  // no delegates.
  DCHECK_EQ(0, delegate_count_);
  DCHECK_EQ(0u, delegate_list_.size());
  DCHECK_EQ(0u, tab2delegate_.size());
}

void CFProxy::LaunchTimeOut() {
  DCHECK(CalledOnIpcThread());
  if (!is_connected_) {
    OnPeerLost(ChromeProxyDelegate::CHROME_EXE_LAUNCH_TIMEOUT);
  }
}

void CFProxy::OnPeerLost(ChromeProxyDelegate::DisconnectReason reason) {
  // Kill the channel. Inform delegates
  DCHECK(CalledOnIpcThread());
  if (ipc_sender_) {
    api_->CloseChannel(ipc_sender_);
    ipc_sender_ = NULL;
  }

  for (DelegateList::iterator it = delegate_list_.begin();
       it != delegate_list_.end(); ++it) {
    (*it)->PeerLost(this, reason);
  }
}

void CFProxy::SendIpcMessage(IPC::Message* m) {
  ipc_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
      &CFProxy::SendIpcMessageOnIoThread, m));
}

void CFProxy::SendIpcMessageOnIoThread(IPC::Message* m) {
  DCHECK(CalledOnIpcThread());
  if (ipc_sender_) {
    ipc_sender_->Send(m);
  } else {
    delete m;
  }
}

//////////////////////////////////////////////////////////////////////////
// Sync messages.
void CFProxy::InstallExtension(ChromeProxyDelegate* delegate,
                               const FilePath& crx_path,
                               SyncMessageContext* ctx) {
  IPC::SyncMessage* m = new AutomationMsg_InstallExtension(crx_path, NULL);
  sync_dispatcher_.QueueSyncMessage(m, delegate, ctx);
  SendIpcMessage(m);
}

void CFProxy::LoadExtension(ChromeProxyDelegate* delegate,
                            const FilePath& path, SyncMessageContext* ctx) {
  IPC::SyncMessage* m = new AutomationMsg_LoadExpandedExtension(path, 0);
  sync_dispatcher_.QueueSyncMessage(m, delegate, ctx);
  SendIpcMessage(m);
}

void CFProxy::GetEnabledExtensions(ChromeProxyDelegate* delegate,
                                   SyncMessageContext* ctx) {
  IPC::SyncMessage* m = new AutomationMsg_GetEnabledExtensions(NULL);
  sync_dispatcher_.QueueSyncMessage(m, delegate, ctx);
  SendIpcMessage(m);
}

void CFProxy::Tab_Find(int tab, const string16& search_string,
                       FindInPageDirection forward, FindInPageCase match_case,
                       bool find_next) {
  AutomationMsg_Find_Params params;
  params.unused = 0;
  params.search_string = search_string;
  params.find_next = find_next;
  params.match_case = (match_case == CASE_SENSITIVE);
  params.forward = (forward == FWD);
  IPC::SyncMessage* m = new AutomationMsg_Find(tab, params, NULL, NULL);
  // Not interested in result.
  sync_dispatcher_.QueueSyncMessage(m, NULL, NULL);
  SendIpcMessage(m);
}

void CFProxy::Tab_OverrideEncoding(int tab, const char* encoding) {
  IPC::SyncMessage* m = new AutomationMsg_OverrideEncoding(tab, encoding, NULL);
  // Not interested in result.
  sync_dispatcher_.QueueSyncMessage(m, NULL, NULL);
  SendIpcMessage(m);
}

void CFProxy::Tab_Navigate(int tab, const GURL& url, const GURL& referrer) {
  IPC::SyncMessage* m = new AutomationMsg_NavigateInExternalTab(
      tab, url, referrer, NULL);
  // We probably are not interested in result since provider just checks
  // whether tab handle is valid.
  sync_dispatcher_.QueueSyncMessage(m, NULL, NULL);
  SendIpcMessage(m);
}

void CFProxy::CreateTab(ChromeProxyDelegate* delegate,
                        const ExternalTabSettings& p) {
  IPC::SyncMessage* m = new AutomationMsg_CreateExternalTab(p, 0, 0, 0, 0);
  sync_dispatcher_.QueueSyncMessage(m, delegate, NULL);
  SendIpcMessage(m);
}

void CFProxy::ConnectTab(ChromeProxyDelegate* delegate, HWND hwnd,
                         uint64 cookie) {
  IPC::SyncMessage* m = new AutomationMsg_ConnectExternalTab(cookie, true,
      hwnd, NULL, NULL, NULL, 0);
  sync_dispatcher_.QueueSyncMessage(m, delegate, NULL);
  SendIpcMessage(m);
}

void CFProxy::BlockTab(uint64 cookie) {
  IPC::SyncMessage* m = new AutomationMsg_ConnectExternalTab(cookie, false,
      NULL, NULL, NULL, NULL, 0);
  sync_dispatcher_.QueueSyncMessage(m, NULL, NULL);
  SendIpcMessage(m);
}

void CFProxy::Tab_RunUnloadHandlers(int tab) {
  IPC::SyncMessage* m = new AutomationMsg_RunUnloadHandlers(tab, 0);
  ChromeProxyDelegate* p = Tab2Delegate(tab);
  sync_dispatcher_.QueueSyncMessage(m, p, NULL);
  SendIpcMessage(m);
}

// IPC::Channel::Listener
bool CFProxy::OnMessageReceived(const IPC::Message& message) {
  // Handle sync message reply.
  bool done = sync_dispatcher_.OnReplyReceived(&message);
  if (done)
    return true;

  // Handle tab related message.
  ChromeProxyDelegate* d = Tab2Delegate(message.routing_id());
  if (d)
    return d->OnMessageReceived(message);

  DLOG(WARNING) << "Unknown message received!";
  return false;
}

void CFProxy::OnChannelConnected(int32 peer_pid) {
  is_connected_ = true;
  // TODO(stoyan): May be we should wait for Hello message.
  for (DelegateList::iterator it = delegate_list_.begin();
       it != delegate_list_.end(); ++it) {
    (*it)->Connected(this);
  }
}

void CFProxy::OnChannelError() {
  is_connected_ = false;

  // Inform the sync message callbacks that there are not going to see
  // any reply.
  sync_dispatcher_.OnChannelClosed();
  OnPeerLost(ChromeProxyDelegate::CHANNEL_ERROR);

  // TODO(stoyan): Relaunch?
}