// Copyright 2011 Google Inc. All Rights Reserved.

#ifndef TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
#define TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_

#include <map>
#include <string>

#include "talk/base/network.h"
#include "talk/base/basicpacketsocketfactory.h"
#include "talk/base/basictypes.h"
#include "talk/base/messagehandler.h"
#include "talk/base/proxyinfo.h"
#include "talk/base/scoped_ptr.h"
#include "talk/base/sigslot.h"
#include "talk/base/socketaddress.h"
#include "talk/p2p/client/httpportallocator.h"

namespace talk_base {
class AsyncHttpRequest;
class AutoDetectProxy;
class BasicPacketSocketFactory;
class NetworkManager;
class PacketSocketFactory;
class SignalThread;
class TestHttpPortAllocatorSession;
class Thread;
}

namespace cricket {
class HttpPortAllocator;
class Port;
class PortAllocatorSession;
struct PortConfiguration;
class RelayPort;
class StunPort;

// Contains details about a discovered firewall that are of interest
// when debugging call failures.
struct FirewallInfo {
  std::string brand;
  std::string model;

  // TODO: List of current port mappings.
};

// Contains details about a specific connect attempt.
struct ConnectInfo {
  ConnectInfo()
      : rtt(-1), error(0) {}
  // Time when the connection was initiated. Needed for calculating
  // the round trip time.
  uint32 start_time_ms;
  // Round trip time in milliseconds or -1 for failed connection.
  int32 rtt;
  // Error code representing low level errors like socket errors.
  int error;
};

// Identifier for a network interface and proxy address pair.
struct NicId {
  NicId(const talk_base::IPAddress& ip,
        const talk_base::SocketAddress& proxy_address)
      : ip(ip),
        proxy_address(proxy_address) {
  }
  talk_base::IPAddress ip;
  talk_base::SocketAddress proxy_address;
};

// Comparator implementation identifying unique network interface and
// proxy address pairs.
class NicIdComparator {
 public:
  int compare(const NicId &first, const NicId &second) const {
    if (first.ip == second.ip) {
      // Compare proxy address.
      if (first.proxy_address == second.proxy_address) {
        return 0;
      } else {
        return first.proxy_address < second.proxy_address? -1 : 1;
      }
    }
    return first.ip < second.ip ? -1 : 1;
  }

  bool operator()(const NicId &first, const NicId &second) const {
    return (compare(first, second) < 0);
  }
};

// Contains information of a network interface and proxy address pair.
struct NicInfo {
  NicInfo() {}
  talk_base::IPAddress ip;
  talk_base::ProxyInfo proxy_info;
  talk_base::SocketAddress external_address;
  talk_base::SocketAddress stun_server_address;
  talk_base::SocketAddress media_server_address;
  ConnectInfo stun;
  ConnectInfo http;
  ConnectInfo https;
  ConnectInfo udp;
  ConnectInfo tcp;
  ConnectInfo ssltcp;
  FirewallInfo firewall;
};

// Holds the result of the connectivity check.
class NicMap : public std::map<NicId, NicInfo, NicIdComparator> {
};

class TestHttpPortAllocatorSession : public HttpPortAllocatorSession {
 public:
  TestHttpPortAllocatorSession(
      HttpPortAllocator* allocator, const std::string &name,
      const std::string& session_type,
      const std::vector<talk_base::SocketAddress>& stun_hosts,
      const std::vector<std::string>& relay_hosts,
      const std::string& relay_token,
      const std::string& user_agent)
      : HttpPortAllocatorSession(allocator, name, session_type, stun_hosts,
                                 relay_hosts, relay_token, user_agent) {
  }
  void set_proxy(const talk_base::ProxyInfo& proxy) {
    proxy_ = proxy;
  }

  void ConfigReady(PortConfiguration* config);

  void OnRequestDone(talk_base::SignalThread* data);

  sigslot::signal2<const PortConfiguration*,
                   const talk_base::ProxyInfo&> SignalConfigReady;
  sigslot::signal1<talk_base::AsyncHttpRequest*> SignalRequestDone;

 private:
  talk_base::ProxyInfo proxy_;
};

// Runs a request/response check on all network interface and proxy
// address combinations. The check is considered done either when all
// checks has been successful or when the check times out.
class ConnectivityChecker
    : public talk_base::MessageHandler, public sigslot::has_slots<> {
 public:
  ConnectivityChecker(talk_base::Thread* worker,
                      const std::string& jid,
                      const std::string& session_id,
                      const std::string& user_agent,
                      const std::string& relay_token,
                      const std::string& connection);
  virtual ~ConnectivityChecker();

  // Virtual for gMock.
  virtual bool Initialize();
  virtual void Start();
  virtual void Stop();

  // MessageHandler implementation.
  virtual void OnMessage(talk_base::Message *msg);

  const NicMap& GetResults() const {
    return nics_;
  }

  void set_timeout_ms(uint32 timeout) {
    timeout_ms_ = timeout;
  }

  void set_stun_address(const talk_base::SocketAddress& stun_address) {
    stun_address_ = stun_address;
  }

  const std::string& connection() const {
    return connection_;
  }

  talk_base::Thread* worker() {
    return worker_;
  }

  const std::string& jid() const {
    return jid_;
  }

  const std::string& session_id() const {
    return session_id_;
  }

  // Context: Main Thread. Signalled when the connectivity check is complete.
  sigslot::signal1<ConnectivityChecker*> SignalCheckDone;

 protected:
  // Can be overridden for test.
  virtual talk_base::NetworkManager* CreateNetworkManager() {
    return new talk_base::BasicNetworkManager();
  }
  virtual talk_base::BasicPacketSocketFactory* CreateSocketFactory(
      talk_base::Thread* thread) {
    return new talk_base::BasicPacketSocketFactory(thread);
  }
  virtual HttpPortAllocator* CreatePortAllocator(
      talk_base::NetworkManager* network_manager,
      const std::string& user_agent,
      const std::string& relay_token);
  virtual StunPort* CreateStunPort(const PortConfiguration* config,
                                   talk_base::Network* network);
  virtual RelayPort* CreateRelayPort(const PortConfiguration* config,
                                     talk_base::Network* network);
  virtual void InitiateProxyDetection();
  virtual void SetProxyInfo(const talk_base::ProxyInfo& info);
  virtual talk_base::ProxyInfo GetProxyInfo() const;

 private:
  bool AddNic(const talk_base::IPAddress& ip,
              const talk_base::SocketAddress& proxy_address);
  void AllocatePorts();
  void AllocateRelayPorts();
  void CheckNetworks();
  void CreateRelayPorts(const PortConfiguration* config,
                        const talk_base::ProxyInfo& proxy_info);

  // Must be called by the worker thread.
  void CleanUp();

  void OnCheckDone(bool signal_results);
  void OnRequestDone(talk_base::AsyncHttpRequest* request);
  void OnRelayAddressReady(Port* port);
  void OnStunAddressReady(Port* port);
  void OnRelayAddressError(Port* port);
  void OnStunAddressError(Port* port);
  void OnNetworksChanged();
  void OnProxyDetect(talk_base::SignalThread* thread);
  void OnConfigReady(const PortConfiguration*,
                     const talk_base::ProxyInfo& proxy);
  void OnConfigWithProxyReady(const PortConfiguration*);
  void RegisterHttpStart(int port);
  talk_base::Thread* worker_;
  std::string jid_;
  std::string session_id_;
  std::string user_agent_;
  std::string relay_token_;
  std::string connection_;
  talk_base::AutoDetectProxy* proxy_detect_;
  talk_base::scoped_ptr<talk_base::NetworkManager> network_manager_;
  talk_base::scoped_ptr<talk_base::BasicPacketSocketFactory> socket_factory_;
  talk_base::scoped_ptr<HttpPortAllocator> port_allocator_;
  NicMap nics_;
  std::vector<Port*> ports_;
  std::vector<PortAllocatorSession*> sessions_;
  uint32 timeout_ms_;
  talk_base::SocketAddress stun_address_;
  talk_base::Thread* main_;
};

}  // namespace cricket

#endif  // TALK_P2P_CLIENT_CONNECTIVITYCHECKER_H_
