text:
using ::remoting::protocol::MockClientStub;
using ::remoting::protocol::MockConnectionToClient;
using ::remoting::protocol::MockConnectionToClientEventHandler;
using ::remoting::protocol::MockHostStub;
using ::remoting::protocol::MockSession;
using ::remoting::protocol::MockVideoStub;
using ::remoting::protocol::Session;
using ::remoting::protocol::SessionConfig;

using testing::_;
using testing::AnyNumber;
using testing::AtLeast;
using testing::AtMost;
using testing::CreateFunctor;
using testing::DeleteArg;
using testing::DoAll;
using testing::Expectation;
using testing::InSequence;
using testing::Invoke;
using testing::InvokeArgument;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::ReturnRef;
using testing::SaveArg;
using testing::Sequence;

namespace remoting {

namespace {

void PostQuitTask(base::MessageLoop* message_loop) {
  message_loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
}

// Run the task and delete it afterwards. This action is used to deal with
// done callbacks.
ACTION(RunDoneTask) {
  arg1.Run();
}

}  // namespace

class ChromotingHostTest : public testing::Test {
 public:
  ChromotingHostTest() {
  }

  void SetUp() override {
    task_runner_ = new AutoThreadTaskRunner(
        message_loop_.task_runner(),
        base::Bind(&ChromotingHostTest::QuitMainMessageLoop,
                   base::Unretained(this)));

    desktop_environment_factory_.reset(new MockDesktopEnvironmentFactory());
    EXPECT_CALL(*desktop_environment_factory_, CreatePtr())
        .Times(AnyNumber())
        .WillRepeatedly(Invoke(this,
                               &ChromotingHostTest::CreateDesktopEnvironment));
    EXPECT_CALL(*desktop_environment_factory_, SupportsAudioCapture())
        .Times(AnyNumber())
        .WillRepeatedly(Return(false));

    session_manager_ = new protocol::MockSessionManager();

    host_.reset(new ChromotingHost(
        &signal_strategy_,
        desktop_environment_factory_.get(),
        make_scoped_ptr(session_manager_),
        task_runner_,   // Audio
        task_runner_,   // Input
        task_runner_,   // Video capture
        task_runner_,   // Video encode
        task_runner_,   // Network
        task_runner_)); // UI
    host_->AddStatusObserver(&host_status_observer_);

    xmpp_login_ = "host@domain";
    session1_ = new MockSession();
    session2_ = new MockSession();
    session_unowned1_.reset(new MockSession());
    session_unowned2_.reset(new MockSession());
    session_config1_ = SessionConfig::ForTest();
    session_jid1_ = "user@domain/rest-of-jid";
    session_config2_ = SessionConfig::ForTest();
    session_jid2_ = "user2@domain/rest-of-jid";
    session_unowned_jid1_ = "user3@doman/rest-of-jid";
    session_unowned_jid2_ = "user4@doman/rest-of-jid";

    EXPECT_CALL(*session1_, jid())
        .WillRepeatedly(ReturnRef(session_jid1_));
    EXPECT_CALL(*session2_, jid())
        .WillRepeatedly(ReturnRef(session_jid2_));
    EXPECT_CALL(*session_unowned1_, jid())
        .WillRepeatedly(ReturnRef(session_unowned_jid1_));
    EXPECT_CALL(*session_unowned2_, jid())
        .WillRepeatedly(ReturnRef(session_unowned_jid2_));
    EXPECT_CALL(*session1_, SetEventHandler(_))
        .Times(AnyNumber());
    EXPECT_CALL(*session2_, SetEventHandler(_))
        .Times(AnyNumber());
    EXPECT_CALL(*session_unowned1_, SetEventHandler(_))
        .Times(AnyNumber())
        .WillRepeatedly(SaveArg<0>(&session_unowned1_event_handler_));
    EXPECT_CALL(*session_unowned2_, SetEventHandler(_))
        .Times(AnyNumber())
        .WillRepeatedly(SaveArg<0>(&session_unowned2_event_handler_));
    EXPECT_CALL(*session1_, config())
        .WillRepeatedly(ReturnRef(*session_config1_));
    EXPECT_CALL(*session2_, config())
        .WillRepeatedly(ReturnRef(*session_config2_));

    owned_connection1_.reset(new MockConnectionToClient(session1_,
                                                        &host_stub1_));
    connection1_ = owned_connection1_.get();
    owned_connection2_.reset(new MockConnectionToClient(session2_,
                                                        &host_stub2_));
    connection2_ = owned_connection2_.get();

    ON_CALL(video_stub1_, ProcessVideoPacketPtr(_, _))
        .WillByDefault(DeleteArg<0>());
    ON_CALL(video_stub2_, ProcessVideoPacketPtr(_, _))
        .WillByDefault(DeleteArg<0>());
    ON_CALL(*connection1_, video_stub())
        .WillByDefault(Return(&video_stub1_));
    ON_CALL(*connection1_, client_stub())
        .WillByDefault(Return(&client_stub1_));
    ON_CALL(*connection1_, session())
        .WillByDefault(Return(session1_));
    ON_CALL(*connection2_, video_stub())
        .WillByDefault(Return(&video_stub2_));
    ON_CALL(*connection2_, client_stub())
        .WillByDefault(Return(&client_stub2_));
    ON_CALL(*connection2_, session())
        .WillByDefault(Return(session2_));
    EXPECT_CALL(*connection1_, video_stub())
        .Times(AnyNumber());
    EXPECT_CALL(*connection1_, client_stub())
        .Times(AnyNumber());
    EXPECT_CALL(*connection1_, session())
        .Times(AnyNumber());
    EXPECT_CALL(*connection2_, video_stub())
        .Times(AnyNumber());
    EXPECT_CALL(*connection2_, client_stub())
        .Times(AnyNumber());
    EXPECT_CALL(*connection2_, session())
        .Times(AnyNumber());

    empty_candidate_config_ =
        protocol::CandidateSessionConfig::CreateEmpty();
    default_candidate_config_ =
        protocol::CandidateSessionConfig::CreateDefault();
  }

  // Helper method to pretend a client is connected to ChromotingHost.
  void SimulateClientConnection(int connection_index, bool authenticate,
                                bool reject) {
    scoped_ptr<protocol::ConnectionToClient> connection =
        ((connection_index == 0) ? owned_connection1_ : owned_connection2_)
            .Pass();
    protocol::ConnectionToClient* connection_ptr = connection.get();
    scoped_ptr<ClientSession> client(new ClientSession(
        host_.get(),
        task_runner_, // Audio
        task_runner_, // Input
        task_runner_, // Video capture
        task_runner_, // Video encode
        task_runner_, // Network
        task_runner_, // UI
        connection.Pass(),
        desktop_environment_factory_.get(),
        base::TimeDelta(),
        nullptr,
        std::vector<HostExtension*>()));

    connection_ptr->set_host_stub(client.get());

    if (authenticate) {
      task_runner_->PostTask(
          FROM_HERE,
          base::Bind(&ClientSession::OnConnectionAuthenticated,
                     base::Unretained(client.get()), connection_ptr));
      if (!reject) {
        task_runner_->PostTask(
            FROM_HERE,
            base::Bind(&ClientSession::OnConnectionChannelsConnected,
                       base::Unretained(client.get()), connection_ptr));
      }
    } else {
      task_runner_->PostTask(
          FROM_HERE, base::Bind(&ClientSession::OnConnectionClosed,
                                base::Unretained(client.get()), connection_ptr,
                                protocol::AUTHENTICATION_FAILED));
    }

    get_client(connection_index) = client.get();

    // |host| is responsible for deleting |client| from now on.
    host_->clients_.push_back(client.release());
  }

  void TearDown() override {
    // Make sure that the host has been properly deleted.
    DCHECK(host_.get() == nullptr);
  }

  // Change the session route for |client1_|.
  void ChangeSessionRoute(const std::string& channel_name,
                          const protocol::TransportRoute& route) {
    host_->OnSessionRouteChange(get_client(0), channel_name, route);
  }

  // Creates a DesktopEnvironment with a fake webrtc::DesktopCapturer, to mock
  // DesktopEnvironmentFactory::Create().
  DesktopEnvironment* CreateDesktopEnvironment() {
    MockDesktopEnvironment* desktop_environment = new MockDesktopEnvironment();
    EXPECT_CALL(*desktop_environment, CreateAudioCapturerPtr())
        .Times(0);
    EXPECT_CALL(*desktop_environment, CreateInputInjectorPtr())
        .Times(AtMost(1))
        .WillOnce(Invoke(this, &ChromotingHostTest::CreateInputInjector));
    EXPECT_CALL(*desktop_environment, CreateScreenControlsPtr())
        .Times(AtMost(1));
    EXPECT_CALL(*desktop_environment, CreateVideoCapturerPtr())
        .Times(AtMost(1))
        .WillOnce(Invoke(this, &ChromotingHostTest::CreateVideoCapturer));
    EXPECT_CALL(*desktop_environment, CreateMouseCursorMonitorPtr())
        .Times(AtMost(1))
        .WillOnce(Invoke(this, &ChromotingHostTest::CreateMouseCursorMonitor));
    EXPECT_CALL(*desktop_environment, GetCapabilities())
        .Times(AtMost(1));
    EXPECT_CALL(*desktop_environment, SetCapabilities(_))
        .Times(AtMost(1));

    return desktop_environment;
  }

  // Creates a dummy InputInjector, to mock
  // DesktopEnvironment::CreateInputInjector().
  InputInjector* CreateInputInjector() {
    MockInputInjector* input_injector = new MockInputInjector();
    EXPECT_CALL(*input_injector, StartPtr(_));
    return input_injector;
  }

  // Creates a fake webrtc::DesktopCapturer, to mock
  // DesktopEnvironment::CreateVideoCapturer().
  webrtc::DesktopCapturer* CreateVideoCapturer() {
    return new FakeDesktopCapturer();
  }

  // Creates a MockMouseCursorMonitor, to mock
  // DesktopEnvironment::CreateMouseCursorMonitor().
  webrtc::MouseCursorMonitor* CreateMouseCursorMonitor() {
    return new FakeMouseCursorMonitor();
  }

  void DisconnectAllClients() {
    host_->DisconnectAllClients();
  }

  // Helper method to disconnect client 1 from the host.
  void DisconnectClient1() {
    NotifyClientSessionClosed(0);
  }

  // Notify |host_| that the authenticating client has been rejected.
  void RejectAuthenticatingClient() {
    host_->RejectAuthenticatingClient();
  }

  // Notify |host_| that a client session has closed.
  void NotifyClientSessionClosed(int connection_index) {
    get_client(connection_index)->OnConnectionClosed(
        get_connection(connection_index), protocol::OK);
  }

  void NotifyConnectionClosed1() {
    if (session_unowned1_event_handler_) {
      session_unowned1_event_handler_->OnSessionStateChange(Session::CLOSED);
    }
  }

  void NotifyConnectionClosed2() {
    if (session_unowned2_event_handler_) {
      session_unowned2_event_handler_->OnSessionStateChange(Session::CLOSED);
    }
  }

  void ShutdownHost() {
    task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&ChromotingHostTest::StopAndReleaseTaskRunner,
                   base::Unretained(this)));
  }

  void StopAndReleaseTaskRunner() {
    host_.reset();
    task_runner_ = nullptr;
    desktop_environment_factory_.reset();
  }

  void QuitMainMessageLoop() {
    PostQuitTask(&message_loop_);
  }

  // Expect the host and session manager to start, and return the expectation
  // that the session manager has started.
  Expectation ExpectHostAndSessionManagerStart() {
    EXPECT_CALL(host_status_observer_, OnStart(xmpp_login_));
    return EXPECT_CALL(*session_manager_, Init(_, host_.get()));
  }

  // Expect a client to connect.
  // Return an expectation that a session has started, and that the first
  // video packet has been sent to the client.
  // Do |action| when that happens.
  template <class A>
  Expectation ExpectClientConnected(int connection_index, A action) {
    const std::string& session_jid = get_session_jid(connection_index);
    MockVideoStub& video_stub = get_video_stub(connection_index);

    Expectation client_authenticated =
        EXPECT_CALL(host_status_observer_, OnClientAuthenticated(session_jid));
    EXPECT_CALL(host_status_observer_, OnClientConnected(session_jid))
        .After(client_authenticated);
    Expectation video_packet_sent =
        EXPECT_CALL(video_stub, ProcessVideoPacketPtr(_, _))
        .After(client_authenticated)
        .WillOnce(DoAll(
            action,
            RunDoneTask()))
        .RetiresOnSaturation();
    EXPECT_CALL(video_stub, ProcessVideoPacketPtr(_, _))
        .Times(AnyNumber())
        .After(video_packet_sent)
        .WillRepeatedly(RunDoneTask());
    return video_packet_sent;
  }

  // Return an expectation that a client will disconnect after a given
  // expectation. The given action will be done after the event executor is
  // notified that the session has finished.
  template <class A>
  Expectation ExpectClientDisconnected(int connection_index,
                                       bool expect_host_status_change,
                                       Expectation after,
                                       A action) {
    MockConnectionToClient* connection = get_connection(connection_index);

    Expectation client_disconnected =
        EXPECT_CALL(*connection, Disconnect())
            .After(after)
            .WillOnce(InvokeWithoutArgs(CreateFunctor(
                this, &ChromotingHostTest::NotifyClientSessionClosed,
                connection_index)))
            .RetiresOnSaturation();
    ExpectClientDisconnectEffects(connection_index,
                                  expect_host_status_change,
                                  after,
                                  action);
    return client_disconnected;
  }

  // Expect the side-effects of a client disconnection, after a given
  // expectation. The given action will be done after the event executor is
  // notifed that the session has finished.
  template <class A>
  void ExpectClientDisconnectEffects(int connection_index,
                                     bool expect_host_status_change,
                                     Expectation after,
                                     A action) {
    const std::string& session_jid = get_session_jid(connection_index);

    if (expect_host_status_change) {
      EXPECT_CALL(host_status_observer_, OnClientDisconnected(session_jid))
          .After(after)
          .WillOnce(action)
          .RetiresOnSaturation();
    }
  }

 protected:
  base::MessageLoop message_loop_;
  scoped_refptr<AutoThreadTaskRunner> task_runner_;
  MockConnectionToClientEventHandler handler_;
  MockSignalStrategy signal_strategy_;
  scoped_ptr<MockDesktopEnvironmentFactory> desktop_environment_factory_;
  scoped_ptr<ChromotingHost> host_;
  MockHostStatusObserver host_status_observer_;
  protocol::MockSessionManager* session_manager_;
  std::string xmpp_login_;
  MockConnectionToClient* connection1_;
  scoped_ptr<MockConnectionToClient> owned_connection1_;
  ClientSession* client1_;
  std::string session_jid1_;
  MockSession* session1_;  // Owned by |connection_|.
  scoped_ptr<SessionConfig> session_config1_;
  MockVideoStub video_stub1_;
  MockClientStub client_stub1_;
  MockHostStub host_stub1_;
  MockConnectionToClient* connection2_;
  scoped_ptr<MockConnectionToClient> owned_connection2_;
  ClientSession* client2_;
  std::string session_jid2_;
  MockSession* session2_;  // Owned by |connection2_|.
  scoped_ptr<SessionConfig> session_config2_;
  MockVideoStub video_stub2_;
  MockClientStub client_stub2_;
  MockHostStub host_stub2_;
  scoped_ptr<MockSession> session_unowned1_;  // Not owned by a connection.
  std::string session_unowned_jid1_;
  scoped_ptr<MockSession> session_unowned2_;  // Not owned by a connection.
  std::string session_unowned_jid2_;
  protocol::Session::EventHandler* session_unowned1_event_handler_;
  protocol::Session::EventHandler* session_unowned2_event_handler_;
  scoped_ptr<protocol::CandidateSessionConfig> empty_candidate_config_;
  scoped_ptr<protocol::CandidateSessionConfig> default_candidate_config_;

  MockConnectionToClient*& get_connection(int connection_index) {
    return (connection_index == 0) ? connection1_ : connection2_;
  }

  // Returns the cached client pointers client1_ or client2_.
  ClientSession*& get_client(int connection_index) {
    return (connection_index == 0) ? client1_ : client2_;
  }

  // Returns the list of clients of the host_.
  std::list<ClientSession*>& get_clients_from_host() {
    return host_->clients_;
  }

  const std::string& get_session_jid(int connection_index) {
    return (connection_index == 0) ? session_jid1_ : session_jid2_;
  }

  MockVideoStub& get_video_stub(int connection_index) {
    return (connection_index == 0) ? video_stub1_ : video_stub2_;
  }
};

TEST_F(ChromotingHostTest, StartAndShutdown) {
  Expectation start = ExpectHostAndSessionManagerStart();
  EXPECT_CALL(host_status_observer_, OnShutdown()).After(start);

  host_->Start(xmpp_login_);
  ShutdownHost();
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, Connect) {
  ExpectHostAndSessionManagerStart();

  // Shut down the host when the first video packet is received.
  Expectation video_packet_sent = ExpectClientConnected(
      0, InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
  Expectation client_disconnected = ExpectClientDisconnected(
      0, true, video_packet_sent, InvokeWithoutArgs(base::DoNothing));
  EXPECT_CALL(host_status_observer_, OnShutdown()).After(client_disconnected);

  host_->Start(xmpp_login_);
  SimulateClientConnection(0, true, false);
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, RejectAuthenticatingClient) {
  Expectation start = ExpectHostAndSessionManagerStart();
  EXPECT_CALL(host_status_observer_, OnClientAuthenticated(session_jid1_))
      .WillOnce(InvokeWithoutArgs(
      this, &ChromotingHostTest::RejectAuthenticatingClient));
  ExpectClientDisconnected(
      0, true, start,
      InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);
  SimulateClientConnection(0, true, true);
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, AuthenticationFailed) {
  ExpectHostAndSessionManagerStart();
  EXPECT_CALL(host_status_observer_, OnAccessDenied(session_jid1_))
      .WillOnce(InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);
  SimulateClientConnection(0, false, false);
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, Reconnect) {
  ExpectHostAndSessionManagerStart();

  // When a video packet is received on the first connection, disconnect it,
  // then quit the message loop.
  Expectation video_packet_sent1 = ExpectClientConnected(0, DoAll(
      InvokeWithoutArgs(this, &ChromotingHostTest::DisconnectClient1),
      InvokeWithoutArgs(this, &ChromotingHostTest::QuitMainMessageLoop)));
  ExpectClientDisconnectEffects(
      0, true, video_packet_sent1, InvokeWithoutArgs(base::DoNothing));

  // When a video packet is received on the second connection, shut down the
  // host.
  Expectation video_packet_sent2 = ExpectClientConnected(
      1, InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
  Expectation client_disconnected2 = ExpectClientDisconnected(
      1, true, video_packet_sent2, InvokeWithoutArgs(base::DoNothing));
  EXPECT_CALL(host_status_observer_, OnShutdown()).After(client_disconnected2);

  host_->Start(xmpp_login_);
  SimulateClientConnection(0, true, false);
  message_loop_.Run();
  SimulateClientConnection(1, true, false);
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, ConnectWhenAnotherClientIsConnected) {
  ExpectHostAndSessionManagerStart();

  // When a video packet is received, connect the second connection.
  // This should disconnect the first connection.
  Expectation video_packet_sent1 = ExpectClientConnected(
      0,
      InvokeWithoutArgs(
          CreateFunctor(
              this,
              &ChromotingHostTest::SimulateClientConnection, 1, true, false)));
  ExpectClientDisconnected(
      0, true, video_packet_sent1, InvokeWithoutArgs(base::DoNothing));
  Expectation video_packet_sent2 = ExpectClientConnected(
      1, InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
  Expectation client_disconnected2 = ExpectClientDisconnected(
      1, true, video_packet_sent2, InvokeWithoutArgs(base::DoNothing));
  EXPECT_CALL(host_status_observer_, OnShutdown()).After(client_disconnected2);

  host_->Start(xmpp_login_);
  SimulateClientConnection(0, true, false);
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, IncomingSessionDeclined) {
  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::ACCEPT;
  host_->OnIncomingSession(session1_, &response);
  EXPECT_EQ(protocol::SessionManager::DECLINE, response);

  ShutdownHost();
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, IncomingSessionIncompatible) {
  ExpectHostAndSessionManagerStart();
  EXPECT_CALL(*session_unowned1_, candidate_config()).WillOnce(Return(
      empty_candidate_config_.get()));
  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);

  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::ACCEPT;
  host_->OnIncomingSession(session_unowned1_.get(), &response);
  EXPECT_EQ(protocol::SessionManager::INCOMPATIBLE, response);

  ShutdownHost();
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, IncomingSessionAccepted) {
  ExpectHostAndSessionManagerStart();
  EXPECT_CALL(*session_unowned1_, candidate_config()).WillOnce(Return(
      default_candidate_config_.get()));
  EXPECT_CALL(*session_unowned1_, set_config_ptr(_));
  EXPECT_CALL(*session_unowned1_, Close()).WillOnce(InvokeWithoutArgs(
    this, &ChromotingHostTest::NotifyConnectionClosed1));
  EXPECT_CALL(host_status_observer_, OnAccessDenied(_));
  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);

  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::DECLINE;
  host_->OnIncomingSession(session_unowned1_.release(), &response);
  EXPECT_EQ(protocol::SessionManager::ACCEPT, response);

  ShutdownHost();
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, LoginBackOffUponConnection) {
  ExpectHostAndSessionManagerStart();
  EXPECT_CALL(*session_unowned1_, candidate_config()).WillOnce(
    Return(default_candidate_config_.get()));
  EXPECT_CALL(*session_unowned1_, set_config_ptr(_));
  EXPECT_CALL(*session_unowned1_, Close()).WillOnce(
    InvokeWithoutArgs(this, &ChromotingHostTest::NotifyConnectionClosed1));
  EXPECT_CALL(host_status_observer_, OnAccessDenied(_));
  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);

  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::DECLINE;

  host_->OnIncomingSession(session_unowned1_.release(), &response);
  EXPECT_EQ(protocol::SessionManager::ACCEPT, response);

  host_->OnSessionAuthenticating(get_clients_from_host().front());
  host_->OnIncomingSession(session_unowned2_.get(), &response);
  EXPECT_EQ(protocol::SessionManager::OVERLOAD, response);

  ShutdownHost();
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, LoginBackOffUponAuthenticating) {
  Expectation start = ExpectHostAndSessionManagerStart();
  EXPECT_CALL(*session_unowned1_, candidate_config()).WillOnce(
    Return(default_candidate_config_.get()));
  EXPECT_CALL(*session_unowned1_, set_config_ptr(_));
  EXPECT_CALL(*session_unowned1_, Close()).WillOnce(
    InvokeWithoutArgs(this, &ChromotingHostTest::NotifyConnectionClosed1));

  EXPECT_CALL(*session_unowned2_, candidate_config()).WillOnce(
    Return(default_candidate_config_.get()));
  EXPECT_CALL(*session_unowned2_, set_config_ptr(_));
  EXPECT_CALL(*session_unowned2_, Close()).WillOnce(
    InvokeWithoutArgs(this, &ChromotingHostTest::NotifyConnectionClosed2));

  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);

  protocol::SessionManager::IncomingSessionResponse response =
      protocol::SessionManager::DECLINE;

  host_->OnIncomingSession(session_unowned1_.release(), &response);
  EXPECT_EQ(protocol::SessionManager::ACCEPT, response);

  host_->OnIncomingSession(session_unowned2_.release(), &response);
  EXPECT_EQ(protocol::SessionManager::ACCEPT, response);

  // This will set the backoff.
  host_->OnSessionAuthenticating(get_clients_from_host().front());

  // This should disconnect client2.
  host_->OnSessionAuthenticating(get_clients_from_host().back());

  // Verify that the host only has 1 client at this point.
  EXPECT_EQ(get_clients_from_host().size(), 1U);

  ShutdownHost();
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, OnSessionRouteChange) {
  std::string channel_name("ChannelName");
  protocol::TransportRoute route;

  ExpectHostAndSessionManagerStart();
  Expectation video_packet_sent = ExpectClientConnected(
      0, InvokeWithoutArgs(CreateFunctor(
          this, &ChromotingHostTest::ChangeSessionRoute, channel_name, route)));
  Expectation route_change =
      EXPECT_CALL(host_status_observer_, OnClientRouteChange(
          session_jid1_, channel_name, _))
      .After(video_packet_sent)
      .WillOnce(InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
  ExpectClientDisconnected(0, true, route_change,
                           InvokeWithoutArgs(base::DoNothing));
  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);
  SimulateClientConnection(0, true, false);
  message_loop_.Run();
}

TEST_F(ChromotingHostTest, DisconnectAllClients) {
  ExpectHostAndSessionManagerStart();
  Expectation video_packet_sent = ExpectClientConnected(
      0, InvokeWithoutArgs(this, &ChromotingHostTest::DisconnectAllClients));
  ExpectClientDisconnected(0, true, video_packet_sent,
      InvokeWithoutArgs(this, &ChromotingHostTest::ShutdownHost));
  EXPECT_CALL(host_status_observer_, OnShutdown());

  host_->Start(xmpp_login_);
  SimulateClientConnection(0, true, false);
  message_loop_.Run();
}

}  // namespace remoting


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ArtifitialInteligence
{
    public enum Player
    {
        White,
        Black,
        Neutral
    }
}

redpajama_set_name: RedPajamaGithub


text:
Ext.require( [ 'ASPIREdb.store.PhenotypeStore', 'ASPIREdb.ActiveProjectSettings', 'Ext.grid.column.Column',
              'ASPIREdb.view.NeurocartaGeneWindow', 'ASPIREdb.view.SubjectPhenotypeHeatmapWindow',
              'ASPIREdb.view.PhenotypesContigencyTableWindow' ] );

/**
 * Phenotype pane which displays all the phenotypes in the current project. Phenotype values are grouped together and
 * displayed as barcharts.
 */
Ext.define( 'ASPIREdb.view.PhenotypeGrid', {
   extend : 'Ext.grid.Panel',
   alias : 'widget.phenotypeGrid',
   title : 'Phenotype',
   id : 'phenotypeGrid',
   multiSelect : true,
   header: {
      items: [{
          xtype: 'image',
          style:'right: auto; left: 0px; top: 6px;',
          src: 'scripts/ASPIREdb/resources/images/qmark.png',
          height: '14px',
          width: '15px',
          listeners: {
             afterrender: function(c) {
                 Ext.create('Ext.tip.ToolTip', {
                     target: c.getEl(),
                     html: 'Displays phenotypes associated with the subjects shown in the Subject panel. Selecting a subject (by clicking on it) shows the phenotype values for that subject (middle column).'
                 });
             }
         }
      }],
      layout: 'fit'
  },

   config : {

      // member variables

      // column id of selectedValuesColumn
      SELECTED_VALUES_COL_IDX : 1,

      // PhenotypeSummary styles
      STYLE_DEFAULT : "style='color: black'",
      STYLE_HPO_PRESENT : "style='color: red'",
      STYLE_HPO_ABSENT : "style='color: green'",
      DB_VAL_HPO_PRESENT : 1,
      DB_VAL_HPO_ABSENT : 0,

      // collection of all the PhenotypeSummaryValueObject loaded
      phenotypeSummaryValueObjects : [],
      selPhenotypes : [],

   },
   constructor : function(cfg) {
      this.initConfig( cfg );
      this.callParent( arguments );
   },
   // plugins : {
   // ptype : 'bufferedrenderer',
   // trailingBufferZone : 20, // Keep 20 rows rendered in the table behind scroll
   // leadingBufferZone : 50
   // // Keep 50 rows rendered in the table ahead of scroll
   // },
   // pageSize : 100,
   dockedItems : [ {
      xtype : 'toolbar',
      itemId : 'phenotypeGridToolbar',
      dock : 'top',
      items : [ {
         xtype : 'button',
         text : 'Heatmap',
         disabled : 'true',
         itemId : 'heatmapButton',
         tooltip : 'Show subject and phenotype clusters',

      }, {
         xtype : 'button',
         text : 'Contingency table',
         disabled : 'true',
         itemId : 'contingencyTableButton',
         tooltip : 'Examine the relationship among up to three phenotypes.',
      }, {
         xtype : 'tbfill',
      }, {
         itemId : 'saveButton',
         xtype : 'button',
         text : '',
         tooltip : 'Download table contents as text',
         icon : 'scripts/ASPIREdb/resources/images/icons/disk.png'

      } ]

   } ],

   bbar : [ {
      xtype : 'label',
      itemId : 'statusbar',
      html : ''
   } ], // bbar

   columns : [
              {
                 text : 'Name',
                 dataIndex : 'name',
                 renderer : function(value, meta, rec, rowIndex, colIndex, store) {
                    meta.style = "white-space: normal;"; // wrap-text
                    var image = "";
                    if ( value.neurocartaPhenotype ) {
                       var src = 'scripts/ASPIREdb/resources/images/icons/neurocarta.png';
                       var tooltip = "View genes associated in Neurocarta";

                       var ahrefurl = '';

                       image = Ext.String.format( ahrefurl + " ", src, tooltip );
                    }
                    var ret = value.name + " " + image;
                    return ret;
                 },
                 // width : 350,
                 flex : 1
              },

              {
                 // populated dynamically when a Subject is selected
                 text : '',
                 dataIndex : 'selectedPhenotype',
                 hidden : true,
                 // width : 80,
                 renderer : function(value) {

                    var phenSummary = value.selectedPhenotype;
                    if ( phenSummary == null )
                       return ret;
                    var style = this.STYLE_DEFAULT;
                    var displayVal = phenSummary.dbValue;
                    /**
                      * Used the Color Brewer 2.0 system for coloring the chart Thanks for Cynthia Brewer, Mark Harrower
                      * and The Pennsylvania State University
                      */
                    var colors = [ "#b35806", "#31a354", "#636363", "#d8b365", "#2c7fb8", "#addd8e", "#7570b3",
                                  "#a6bddb" ];
                    var colorIdx = 5;
                    // TODO : This is not a good workaround to display the color, have to figure out a way to do
                    // this
                    // better
                    if ( phenSummary.valueType == "HPONTOLOGY" ) {
                       if ( phenSummary.dbValue == this.DB_VAL_HPO_ABSENT ) {
                          style = colors[1];
                          displayVal = "Absent";
                       } else if ( phenSummary.dbValue == this.DB_VAL_HPO_PRESENT ) {
                          style = colors[0];
                          displayVal = "Present";
                       } else if ( phenSummary.dbValue == "Unknown" ) {
                          style = colors[2];
                          displayVal = "Unknown";
                       }
                    } else if ( phenSummary.dbValue == "Unknown" ) {
                       style = colors[2];
                       displayVal = "Unknown";
                    } else if ( phenSummary.dbValue == "N" ) {
                       style = colors[3];
                       displayVal = "N";
                    } else if ( phenSummary.dbValue == "Y" ) {
                       style = colors[4];
                       displayVal = "Y";
                    } else if ( phenSummary.dbValue == "F" ) {
                       style = colors[3];
                       displayVal = "F";
                    } else if ( phenSummary.dbValue == "M" ) {
                       style = colors[4];
                       displayVal = "M";
                    } else {
                       style = colors[colorIdx];
                       displayVal = phenSummary.dbValue;
                    }

                    var ret = "" + displayVal + "";
                    return ret;
                 },
              },

              {
                 text : 'Select Subject values',
                 hidden : true,
                 dataIndex : 'phenoSummaryMap',
                 width : 60,
                 renderer : function(value, metadata, record) {

                    var phenSummary = value.phenoSummaryMapSelectedSubjects;
                    var displaySummary = value.displaySummarySelectedSubjects
                    if ( phenSummary != null ) {
                       var ret = "<canvas width='50' height='20' id=multi" + value.name.replace( / /g, '' ) + ">"
                          + "</canvas>";
                       metadata.tdAttr = 'data-qtip="' + displaySummary + ret + '"';
                       return ret;
                    } else
                       return "";
                 },

              },

              {
                 text : 'Value',
                 tooltip : 'Number of subjects for each phenotype value',
                 dataIndex : 'allPhenoSummaryMap',
                 renderer : function(value, metadata, record) {
                    var phenSummary = value.phenoSummaryMap;

                    if ( phenSummary != null ) {
                       var ret = "<canvas width='50' height='20' id=all" + value.name.replace( / /g, '' ) + ">"
                          + "</canvas>";
                       metadata.tdAttr = 'data-qtip="' + value.displaySummary + ret + '"';
                       return ret;
                    } else
                       return "";
                 },
                 width : 60,
              // flex : 1
              } ],
   listeners : {
      sortchange : function(phenotypeGrid, sortinfo) {

         ASPIREdb.EVENT_BUS.fireEvent( 'allPhenoSummary_sorted' );
      }
   },

   store : Ext.create( 'ASPIREdb.store.PhenotypeStore' ),

   initComponent : function() {
      this.callParent();

      this.getDockedComponent( 'phenotypeGridToolbar' ).getComponent( 'heatmapButton' ).on( 'click', this.viewHeatmap,
         this );

      this.getDockedComponent( 'phenotypeGridToolbar' ).getComponent( 'contingencyTableButton' ).on( 'click',
         this.viewSubjectLabel, this );

      var ref = this;

      ASPIREdb.EVENT_BUS.on( 'filter_submit', function() {

         // ref.setLoading( true );
         ref.getStore().removeAll();

      } );

      // when phenotypes selected
      this.on( 'selectionchange', this.selectionChangeHandler, this );

      ASPIREdb.EVENT_BUS.on( 'subjects_loaded', function(subjectIds) {

         ref.currentSubjectIds = subjectIds;

         SubjectService.getPhenotypeSummaries( subjectIds, ASPIREdb.ActiveProjectSettings.getActiveProjectIds(), {
            callback : function(vos) {// vos is a list of phenotypeSummaryValueobjects (converted to a javascript
               // Array)

               var data = [];
               ref.phenotypeSummaryValueObjects = [];

               if ( vos.length > 0 ) {
                  ref.getDockedComponent( 'phenotypeGridToolbar' ).getComponent( 'heatmapButton' ).enable();
               } else {
                  ref.getDockedComponent( 'phenotypeGridToolbar' ).getComponent( 'heatmapButton' ).disable();
               }

               for (var i = 0; i < vos.length; i++) {
                  var phenSummary = vos[i];

                  ref.phenotypeSummaryValueObjects[i] = phenSummary;

                  var row = [ phenSummary, phenSummary, phenSummary, phenSummary ];
                  data.push( row );
               }

               ref.down( '#statusbar' ).update( vos.length + " phenotypes loaded" );

               ref.store.loadData( data );
               ref.updatePhenotypeSummaryCanvasesAllSubjects();
               ref.setLoading( false );

            }
         } );

      } );

      var saveButton = ref.getDockedComponent( 'phenotypeGridToolbar' ).getComponent( 'saveButton' );

      saveButton.on( 'click', function() {
         ASPIREdb.TextDataDownloadWindow.show();
         ASPIREdb.TextDataDownloadWindow.setLoading( true );

         SubjectService.getPhenotypeTextDownloadBySubjectIds( ref.currentSubjectIds, ref.saveButtonHandler );
      } );

      ASPIREdb.EVENT_BUS.on( 'select_subject_from_variant_grid', this.subjectSelectHandler, this );
      ASPIREdb.EVENT_BUS.on( 'select_subject_from_ideogram', this.subjectSelectHandler, this );
      ASPIREdb.EVENT_BUS.on( 'subject_selected', this.subjectSelectHandler, this );
      ASPIREdb.EVENT_BUS.on( 'allPhenoSummary_sorted', this.sortAllPhenoSummary, this );
      ASPIREdb.EVENT_BUS.on( 'selectedPhenoSummary_sorted', this.sortSelectedPhenoSummary, this );

   },

   /**
    * when all phenotype summary canvas sorted
    */
   sortAllPhenoSummary : function() {

      this.updatePhenotypeSummaryCanvasesAllSubjects();
      this.updatePhenotypeSummaryCanvasesSelectedSubjects();
      this.setLoading( false );

   },

   /**
    * This method called when phenotypes are selected in the phenotype grid
    */
   selectionChangeHandler : function() {
      this.selPhenotypes = this.getSelectionModel().getSelection();

      if ( this.selPhenotypes.length > 0 && this.selPhenotypes.length < 4 ) {
         var names = [];

         for (var i = 0; i < this.selPhenotypes.length; i++) {
            names.push( this.selPhenotypes[i].data.name );
         }
         ASPIREdb.EVENT_BUS.fireEvent( 'phenotype_selected', names );
         this.getDockedComponent( 'phenotypeGridToolbar' ).getComponent( 'contingencyTableButton' ).enable();

      } else {
         ASPIREdb.EVENT_BUS.fireEvent( 'phenotype_selected', null );
         this.getDockedComponent( 'phenotypeGridToolbar' ).getComponent( 'contingencyTableButton' ).disable();
      }

   },
   /**
    * when all phenotype summary canvas sorted
    */
   sortSelectedPhenoSummary : function() {
      this.updatePhenotypeSummaryCanvasesAllSubjects();
      this.updatePhenotypeSummaryCanvasesSelectedSubjects();
      this.setLoading( false );

   },
   /**
    * Loads selected Subject's phenotypes
    *
    * @param subjectId
    */
   subjectSelectHandler : function(subjectIds) {// todo

      if ( !subjectIds || subjectIds.length == 0 ) {
         var col = this.columns[this.SELECTED_VALUES_COL_IDX];

         col.setText( "" );
         col.setVisible( false );

         return;
      }

      var activeProjectId = ASPIREdb.ActiveProjectSettings.getActiveProjectIds()[0];

      var ref = this;
      // console.log( "on subject select handler in phenotype grid ..........." );

      if ( subjectIds.length == 1 ) {
         SubjectService.getSubject( activeProjectId, subjectIds[0], {
            callback : function(svo) {
               if ( svo != null ) {
                  ref.setLoading( true );

                  PhenotypeService.getPhenotypes( svo.id, {
                     callback : function(voMap) {// these vos a

                        ref.columns[2].setVisible( false );

                        var col = ref.columns[ref.SELECTED_VALUES_COL_IDX];
                        col.setText( svo.patientId );
                        col.setVisible( true );

                        for (var i = 0; i < ref.phenotypeSummaryValueObjects.length; i++) {
                           var phenSummary = ref.phenotypeSummaryValueObjects[i];
                           var subjectPhenotype = voMap[phenSummary.name];
                           phenSummary.selectedPhenotype = subjectPhenotype;
                        }

                        ref.getView().refresh( true );
                        ref.updatePhenotypeSummaryCanvasesAllSubjects();
                        ref.setLoading( false );
                     }
                  } );
               }
            }
         } );
      } else {

         SubjectService.getPhenotypeSummaryValueObjects( subjectIds, ASPIREdb.ActiveProjectSettings
            .getActiveProjectIds(), {
            callback : function(voMap) {// voMap is a <String, PhenotypeSummaryValueObject>Map

               if ( voMap != null ) {
                  ref.setLoading( true );

                  // validate that single case column is hidden
                  ref.columns[1].setVisible( false );

                  var col = ref.columns[2];
                  col.setVisible( true );

                  var data = [];

                  for (var i = 0; i < ref.phenotypeSummaryValueObjects.length; i++) {
                     var phenSummary = ref.phenotypeSummaryValueObjects[i];
                     var phenoSummaryValueObject = voMap[phenSummary.name];
                     // we are attaching a new property to phenSummary here, calling it
                     // phenoSummaryMapSelectedSubjects

                     if ( phenoSummaryValueObject ) {
                        phenSummary.phenoSummaryMapSelectedSubjects = phenoSummaryValueObject.phenoSummaryMap;
                        phenSummary.displaySummarySelectedSubjects = phenoSummaryValueObject.displaySummary;
                     } else {// if phenoSummaryValueObject is null or undefined
                        console.log( "null or undefined phenoSummaryValueObject: " + phenSummary.name );
                     }

                  }
                  ref.getView().refresh( true );
                  ref.updatePhenotypeSummaryCanvasesAllSubjects();
                  ref.updatePhenotypeSummaryCanvasesSelectedSubjects();
                  ref.setLoading( false );
               }

            }
         } );

      }

   },

   updatePhenotypeSummaryCanvasesSelectedSubjects : function() {

      for (var i = 0; i < this.phenotypeSummaryValueObjects.length; i++) {
         var phenSummary = this.phenotypeSummaryValueObjects[i];

         // phenSummary.phenoSummaryMapSelectedSubjects is the new parameter we added in the javascript
         var phenMap = phenSummary.phenoSummaryMapSelectedSubjects;

         var canvas = document.getElementById( "multi" + phenSummary.name.replace( / /g, '' ) );

         this.drawCanvas( canvas, phenSummary, phenMap );

      }

   },

   updatePhenotypeSummaryCanvasesAllSubjects : function() {

      for (var i = 0; i < this.phenotypeSummaryValueObjects.length; i++) {
         var phenSummary = this.phenotypeSummaryValueObjects[i];

         // phenSummary.phenoSummaryMapSelectedSubjects is the new parameter we added in the javascript
         var phenMap = phenSummary.phenoSummaryMap;

         var canvas = document.getElementById( "all" + phenSummary.name.replace( / /g, '' ) );

         this.drawCanvas( canvas, phenSummary, phenMap );

      }

   },

   drawCanvas : function(canvas, phenSummary, phenMap) {

      if ( canvas === null || phenMap === undefined ) {
         // console.log( "phenSummary name " + phenSummary.name + " phenMap is undefined" );
         return;
      }

      var keyArray = phenSummary.phenoSet;

      var total = 0;
      for (var j = 0; j < keyArray.length; j++) {
         total = total + phenMap[keyArray[j]];
      }

      var ctx = canvas.getContext( "2d" );
      ctx.font = "bold 8px sans-serif";
      ctx.textAlign = "center";

      var width = 50;
      var height = 20;
      var yValue = 0;
      var xValue = 0;
      var colorIndex = 3;
      /**
       * Used the Color Brewer 2.0 system for coloring the chart Thanks for Cynthia Brewer, Mark Harrower and The
       * Pennsylvania State University
       */
      var colors = [ "#b35806", "#31a354", "#636363", "#d8b365", "#2c7fb8", "#addd8e", "#7570b3", "#a6bddb" ];
      var displayVal = '';

      // draw Y axis
      /**
       * ctx.beginPath(); ctx.moveTo(0,50); ctx.lineTo(0,0); ctx.closePath(); ctx.stroke();
       *
       * //draw Y axis ctx.beginPath(); ctx.moveTo(0,50); ctx.lineTo(50,50); ctx.closePath(); ctx.stroke();
       */

      for (var k = 0; k < keyArray.length; k++) {
         if ( keyArray[k] != "Unknown" ) {

            if ( phenSummary.valueType == "HPONTOLOGY" ) {
               // horizontal = -(phenMap["Present"]*width)/total,5)
               if ( keyArray[k] == "Present" ) {

                  ctx.fillStyle = colors[0];
                  ctx.fillRect( xValue, yValue, (phenMap["Present"] * width) / total, 5 );
                  yValue = yValue + 5;
                  displayVal = displayVal + "Present(" + phenMap["Present"] + ")";

               } else if ( keyArray[k] == "Absent" ) {

                  ctx.fillStyle = colors[1];
                  ctx.fillRect( xValue, yValue, (phenMap["Absent"] * width) / total, 5 );
                  yValue = yValue + 5;
                  displayVal = displayVal + "Absent(" + phenMap["Absent"] + ")";

               } else {

                  ctx.fillStyle = colors[colorIndex];
                  ctx.fillRect( xValue, yValue, (phenMap[keyArray[k]] * width) / total, 5 );
                  colorIndex++;
                  yValue = yValue + 5;
                  displayVal = displayVal + keyArray[k] + "(" + phenMap[keyArray[k]] + ")";

               }
            } else {

               ctx.fillStyle = colors[colorIndex];
               ctx.fillRect( xValue, yValue, (phenMap[keyArray[k]] * width) / total, 5 );
               colorIndex++;
               yValue = yValue + 5;
               displayVal = displayVal + keyArray[k] + "(" + phenMap[keyArray[k]] + ")";

            }
            ;
         } else {
            var unknown = phenMap["Unknown"];
         }

      }
      if ( unknown != null ) {
         ctx.fillStyle = colors[2];
         ctx.fillRect( xValue, yValue, (phenMap["Unknown"] * width) / total, 5 );
         yValue = yValue + 5;
         displayVal = displayVal + "Unknown(" + phenMap["Unknown"] + ")";

      }

   },

   saveButtonHandler : function(text) {
      ASPIREdb.TextDataDownloadWindow.setLoading( false );
      ASPIREdb.TextDataDownloadWindow.showPhenotypesDownload( text );
   },

   getSubjectValue : function(phenSummary) {

      var valueToSubjectSet = phenSummary.dbValueToSubjectSet;

      var subjectValue = '';

      var keyArray = [];

      // if this is a special(large) project, this will not be populated
      if ( !valueToSubjectSet.Unknown ) {
         return subjectValue;
      }

      for ( var key in valueToSubjectSet) {
         if ( key !== "Unknown" ) {
            keyArray.push( key );
         }

      }

      keyArray.sort();
      // ensure "Unknown is at the end
      keyArray.push( "Unknown" );

      for (var i = 0; i < keyArray.length; i++) {

         var key = keyArray[i];

         if ( phenSummary.valueType == "HPONTOLOGY" ) {
            if ( key == this.DB_VAL_HPO_PRESENT ) {
               subjectValue = subjectValue + ' Present(' + valueToSubjectSet[key].length + ')';
               subjectValue = "" + subjectValue + "";
            } else if ( key == this.DB_VAL_HPO_ABSENT ) {
               subjectValue = subjectValue + ' Absent(' + valueToSubjectSet[key].length + ')';
               subjectValue = "" + subjectValue + "";
            } else {
               subjectValue = subjectValue + ' ' + key + ' (' + valueToSubjectSet[key].length + ')';
               subjectValue = "" + subjectValue + "";
            }
         } else {
            subjectValue = subjectValue + ' ' + key + ' (' + valueToSubjectSet[key].length + ')';
            subjectValue = "" + subjectValue + "";
         }
      }

      return subjectValue;

   },

   viewHeatmap : function() {
      var ref = this;
      var removeEmpty = true;

      SubjectService.getPhenotypeBySubjectIds( ref.currentSubjectIds, removeEmpty, {
         callback : function(matrix) {
            ASPIREdb.view.SubjectPhenotypeHeatmapWindow.show();
            ASPIREdb.view.SubjectPhenotypeHeatmapWindow.draw( matrix );
         }
      } );
   },

   viewSubjectLabel : function() {
      console.log( "view subject labels" );
      ASPIREdb.view.PhenotypesContigencyTableWindow.initGridAndShow( this.phenotypeSummaryValueObjects,
         this.selPhenotypes );

   },

   viewNeurocartaGenes : function(value, name) {
      ASPIREdb.view.NeurocartaGeneWindow.initGridAndShow( value, name );
   }

} );


text:
layout: post
title: "Using strace"
---

In my [last post][1], I discussed what a file descriptor is and ran some commands to interact with the file descriptors associated with a shell process.  In this post, I'll look at how to use the `strace` tool to inspect the file activity of a process.

# History

When using a new tool, I find it worthwhile to read about how the tool came about.  `strace` has a [wikipedia page][2] that tells us it is a "system call tracer" (hence the name s-trace) originally written for SunOS by Paul Kranenburg (1991).  It was ported to Linux by Branko Lankester (1992) and then refactored by Richard Sladkey (1994).  The page also mentions a kernel feature called [ptrace][3].  `ptrace` is a system call (I'll have a post about the ins and outs of system calls soon) which allows one process to control another.  In the case of `strace`, we're not actively controlling the other process, but inspecting it's system calls.  Debuggers like `gdb` use `ptrace` in order to control the flow of a program.  It can also be used as a sandbox like in the [sydbox][4] project.  This project intercepts system calls from the process and decides whether to accept or reject them.

# Using `strace`

One way to use `strace` is to start a process with it.  For instance:

```
$ strace ls
```

This will dump a lot of output to the screen. If we want to inspect it for later, we can save it to an output file like this:

```
$ strace -o ls.txt ls
```

In a later post, I'll dig through what all the lines in that output are doing. For now, I'll just appreciate how much goes into a simple call to `ls`.

We can also look at a running process's system call with the "-p" option.  For instance, when I run `strace` from one terminal with the PID of a separate shell process and then type "ab" into that process, I get the following:

```
$ strace -p 3227
Process 3227 attached
read(10, "a", 1)                        = 1
rt_sigprocmask(SIG_BLOCK, [WINCH], [], 8) = 0
write(10, "a", 1)                       = 1
rt_sigprocmask(SIG_UNBLOCK, [WINCH], [WINCH], 8) = 0
read(10, "b", 1)                        = 1
rt_sigprocmask(SIG_BLOCK, [WINCH], [], 8) = 0
write(10, "\10ab", 3)                   = 3
rt_sigprocmask(SIG_UNBLOCK, [WINCH], [WINCH], 8) = 0
read(10,
```

A couple interesting things come out here.  First, the process is separately reading in the input and then writing it back out.  Also, it writes out both "ab" rather than just "b" the second time around.  However, if we type "c" after, it just writes "c".  This seems to be true of the first two characters.  Finally, it is both reading and writing from [file descriptor][1] 10.  I would expect it to be reading from STDIN (fd 0) and writing to STDOUT (fd 1), however we can see from inspecting the process's fd directory, these all point to the same place: `/dev/pts/2`

```
ls -la /proc/3227/fd
total 0
dr-x------ 2 paul paul  0 Nov  3 07:26 .
dr-xr-xr-x 9 paul paul  0 Nov  3 07:26 ..
lrwx------ 1 paul paul 64 Nov  3 07:26 0 -> /dev/pts/2
lrwx------ 1 paul paul 64 Nov  3 07:27 1 -> /dev/pts/2
lrwx------ 1 paul paul 64 Nov  3 07:35 10 -> /dev/pts/2
lrwx------ 1 paul paul 64 Nov  3 07:26 2 -> /dev/pts/2
```
They all point to "pseudo terminal" #2.  (For a brief explanation of pseudo terminals see [this stack exchange post][5]).

One last thing on this I would like to point out are those calls to `rt_sigprocmask`.  Looking at the [man page][7] for `rt_sigprocmask`, we can see it is used for blocking or unblocking the delivery of signals to a thread.  This makes sense based on the arguments `SIG_BLOCK` and `SIG_UNBLOCK`.  The next question then is what is the `WINCH` signal?  Here is a list of [signals][6] which says that the `WINCH` signal is the " Window resize signal" (short for Window Change).  This means that in between reading and writing, the shell process ignores any resizes of the window.  This likely because a resize would change where we would want to write the character out, so we want to wait until we're finished righting before re-writing everything for the new window size.  We can observe a `WINCH` signal in action by resizing the window while strace is running.

# Conclusion

Hopefully that was a useful brief dive into `strace`.  For a more in depth dive, checkout [this walkthrough][7] of determining if a command line tool accepts wildcards.

[1]:{% post_url 2016-10-26-file-system-1 %}
[2]:https://en.wikipedia.org/wiki/Strace
[3]:https://en.wikipedia.org/wiki/Ptrace#cite_note-PRoot-2
[4]:http://freecode.com/projects/sydbox
[5]:https://linux.die.net/man/2/rt_sigprocmask
[6]:http://man7.org/linux/man-pages/man7/signal.7.html
[7]:http://events.linuxfoundation.org/sites/events/files/slides/lce-2015-strace-bash-en.pdf


text:
[![Build Status](http://img.shields.io/travis/chef/bento.svg)][travis]

Bento is a project that encapsulates [Packer](https://www.packer.io/) templates for building [Vagrant](https://www.vagrantup.com/) base boxes. A subset of templates are built and published to the [bento org](https://app.vagrantup.com/bento) on Vagrant Cloud. These published boxes serve as the default boxes for [kitchen-vagrant](https://github.com/test-kitchen/kitchen-vagrant/).

### Using Public Boxes

Adding a bento box to Vagrant

```
$ vagrant box add bento/ubuntu-16.04
```

Using a bento box in a Vagrantfile

```
Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-16.04"
end
```

### Building Boxes

#### Requirements

- [Packer](https://www.packer.io/)
- At least one of the following virtualization providers:
  - [VirtualBox](https://www.virtualbox.org)
  - [VMware Fusion](https://www.vmware.com/products/fusion.html)
  - [VMware Workstation](https://www.vmware.com/products/workstation.html)
  - [Parallels Desktop](http://www.parallels.com/products/desktop)
  - [KVM](https://www.linux-kvm.org/page/Main_Page) *
  - [Hyper-V](https://technet.microsoft.com/en-us/library/hh831531(v=ws.11).aspx) *

\***NOTE:** support for these providers is considered experimental and corresponding Vagrant Cloud images may or may not exist.

#### Using `packer`

To build an Ubuntu 16.04 box for only the VirtualBox provider

```
$ cd ubuntu
$ packer build -only=virtualbox-iso ubuntu-16.04-amd64.json
```

To build Debian 9.1 32bit boxes for all possible providers (simultaneously)

```
$ cd debian
$ packer build debian-9.1-i386.json
```

To build CentOS 7.3 boxes for all providers except VMware and Parallels

```
$ cd centos
$ packer build -except=parallels-iso,vmware-iso centos-7.3-x86_64.json
```

To use an alternate mirror

```
$ cd fedora
$ packer build -var 'mirror=http://mirror.utexas.edu/fedora/linux' fedora-26-x86_64.json
```

If the build is successful, ready to import box files will be in the `builds` directory at the root of the repository.

\***NOTE:** box_basename can be overridden like other Packer vars with `-var 'box_basename=ubuntu-16.04'`

### Proprietary Templates

Templates for operating systems only available via license or subscription are also available in the repository, these include but are not limited to: Mac OS X, Red Hat Enterprise Linux, and SUSE Linux Enterprise. As the ISOs are not publicly available the URL values will need to be overridden as appropriate. We rely on the efforts of those with access to licensed versions of the operating systems to keep these up-to-date.

### Networking/Firewalls

Most of the providers expect unrestricted access to networking in order to build as expected. We can't enumerate all possible firewall configurations but include some snippets below that might be useful to users.

#### Windows

```
$VS = "Standardswitch"
$IF_ALIAS = (Get-NetAdapter -Name "vEthernet ($VS)").ifAlias
New-NetFirewallRule -Displayname "Allow incomming from $VS" -Direction Inbound -InterfaceAlias $IF_ALIAS -Action Allow
```

#### macOS / OSX

See this [wiki page](https://github.com/chef/bento/wiki/macOS)

## Bugs and Issues

Please use GitHub issues to report bugs, features, or other problems.

## Related projects

* https://github.com/boxcutter
* https://github.com/mcandre/packer-templates
* https://github.com/timsutton/osx-vm-templates
* https://github.com/ferventcoder/vagrant-windows-puppet/tree/master/baseboxes

## License & Authors

These basebox templates were converted from [veewee](https://github.com/jedi4ever/veewee) definitions originally based on [work done by Tim Dysinger](https://github.com/dysinger/basebox) to make "Don't Repeat Yourself" (DRY) modular baseboxes. Thanks Tim!

Mac OS X templates were adopted wholesale from [Fletcher Nichol's packer templates](https://github.com/fnichol/packer-templates).

- Author: Seth Chisamore ([schisamo@chef.io](mailto:schisamo@chef.io))
- Author: Stephen Delano ([stephen@chef.io](mailto:stephen@chef.io))
- Author: Joshua Timberman ([joshua@chef.io](mailto:joshua@chef.io))
- Author: Tim Dysinger ([tim@dysinger.net](mailto:tim@dysinger.net))
- Author: Chris McClimans ([chris@hippiehacker.org](mailto:chris@hippiehacker.org))
- Author: Julian Dunn ([jdunn@chef.io](mailto:jdunn@chef.io))
- Author: Tom Duffield ([tom@chef.io](mailto:tom@chef.io))
- Author: Ross Timson ([ross@rosstimson.com](mailto:ross@rosstimson.com))
- Author: Fletcher Nichol ([fnichol@nichol.ca](mailto:fnichol@nichol.ca))

```text
Copyright 2012-2017, Chef Software, Inc. (<legal@chef.io>)
Copyright 2011-2012, Tim Dysinger (<tim@dysinger.net>)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```

[travis]: https://travis-ci.org/chef/bento


text:
'use strict';

// MODULES //

var tape = require( 'tape' );
var unaryBy = require( '@stdlib/strided/base/unary-by' );
var abs = require( '@stdlib/math/base/special/abs' );
var isFunction = require( '@stdlib/assert/is-function' );
var dispatchBy = require( './../lib' );

// FIXTURES //

var fill = require( './fixtures/fill.js' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.strictEqual( typeof dispatchBy, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if not provided either a function or an array of functions as a first argument', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		3.14,
		NaN,
		true,
		false,
		null,
		void 0,
		[],
		{}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var types = [ 'float64', 'float64' ];
			var data = [ abs ];
			dispatchBy( value, types, data, 8, 1, 1 );
		};
	}
});

tape( 'the function throws an error if not provided an array-like object as a second argument', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		3.14,
		NaN,
		true,
		false,
		null,
		void 0,
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var data = [ abs ];
			dispatchBy( unaryBy, value, data, 8, 1, 1 );
		};
	}
});

tape( 'the function throws an error if not provided either an array-like object or `null` as a third argument', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		3.14,
		NaN,
		true,
		false,
		void 0,
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var types = [ 'float64', 'float64' ];
			dispatchBy( unaryBy, types, value, 8, 1, 1 );
		};
	}
});

tape( 'the function throws an error if not provided a positive integer for a fourth argument', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		0,
		-1,
		3.14,
		NaN,
		true,
		false,
		null,
		void 0,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var types = [ 'float64', 'float64' ];
			var data = [ abs ];
			dispatchBy( unaryBy, types, data, value, 1, 1 );
		};
	}
});

tape( 'the function throws an error if not provided a nonnegative integer for a fifth argument', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		-1,
		3.14,
		NaN,
		true,
		false,
		null,
		void 0,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var types = [ 'float64', 'float64' ];
			var data = [ abs ];
			dispatchBy( unaryBy, types, data, 8, value, 1 );
		};
	}
});

tape( 'the function throws an error if not provided a nonnegative integer for a sixth argument', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		-1,
		3.14,
		NaN,
		true,
		false,
		null,
		void 0,
		[],
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), TypeError, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var types = [ 'float64', 'float64' ];
			var data = [ abs ];
			dispatchBy( unaryBy, types, data, 8, 1, value );
		};
	}
});

tape( 'the function throws an error if the number of types is incompatible with the number of types indicated by other function parameters (function array)', function test( t ) {
	var values;
	var i;

	values = [
		[ 'float64' ],
		[ 'float64', 'float64', 'float64' ],
		[ 'float64', 'float32', 'float32' ]
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var fcns = [ unaryBy, unaryBy, unaryBy ];
			var data = [ abs, abs, abs ];
			dispatchBy( fcns, value, data, 8, 1, 1 );
		};
	}
});

tape( 'the function throws an error if the number of types is incompatible with the number of types indicated by other function parameters (function argument)', function test( t ) {
	var values;
	var i;

	values = [
		[ 'float64' ],
		[ 'float64', 'float64', 'float64' ],
		[ 'float64', 'float32', 'float32' ]
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var data = [ abs, abs, abs ];
			dispatchBy( unaryBy, value, data, 8, 1, 1 );
		};
	}
});

tape( 'the function throws an error if the length of `data` argument does not match the number of strided array functions', function test( t ) {
	var values;
	var i;

	values = [
		[],
		[ abs, abs ],
		[ abs, abs, abs ]
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var types = [ 'float64', 'float64' ];
			var fcns = [ unaryBy ];
			dispatchBy( fcns, types, value, 8, 1, 1 );
		};
	}
});

tape( 'the function throws an error if the fourth argument is incompatible with the number of strided input and output arrays', function test( t ) {
	var values;
	var i;

	values = [
		0,
		1,
		2,
		3,
		4,
		5,
		6,
		7,
		9,
		11
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[i] ), Error, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			var types = [ 'float64', 'float64' ];
			var data = [ abs ];
			dispatchBy( unaryBy, types, data, value, 1, 1 );
		};
	}
});

tape( 'the function throws an error if the number of specified strided input and output arrays is zero', function test( t ) {
	t.throws( badValue, Error, 'throws an error' );
	t.end();

	function badValue() {
		var types = [ 'float64' ];
		dispatchBy( fill( 3 ), types, null, 2, 0, 0 );
	}
});

tape( 'the function returns a strided array function interface', function test( t ) {
	var types;
	var data;
	var f;

	types = [ 'float64', 'float64' ];
	data = [ abs ];

	f = dispatchBy( unaryBy, types, data, 8, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	f = dispatchBy( unaryBy, types, data, 10, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	f = dispatchBy( [ unaryBy ], types, data, 8, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	f = dispatchBy( [ unaryBy ], types, data, 10, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	f = dispatchBy( fill( 3 ), types, null, 8, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	f = dispatchBy( fill( 3 ), types, null, 10, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	f = dispatchBy( [ fill( 3 ) ], types, null, 8, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	f = dispatchBy( [ fill( 3 ) ], types, null, 10, 1, 1 );
	t.strictEqual( isFunction( f ), true, 'returns expected value' );

	t.end();
});


text:
=begin

  nomlib - C++11 cross-platform game engine

Copyright (c) 2013, 2014 Jeffrey Carpenter <i8degrees@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

=end

=begin

# TODO #

- [ ] Stub

# References #

* Stub

=end

NOMDEV_SRC_DIR = File.expand_path( File.dirname(__FILE__) ) + "/src/nomdev"
$LOAD_PATH.unshift( NOMDEV_SRC_DIR ) unless $LOAD_PATH.include?( NOMDEV_SRC_DIR )

require 'yaml'

require "#{NOMDEV_SRC_DIR}/config.rb"
require "#{NOMDEV_SRC_DIR}/helpers.rb"
require "#{NOMDEV_SRC_DIR}/NomDevOptions.rb"
require "#{NOMDEV_SRC_DIR}/CMakeGenerator.rb"
require "#{NOMDEV_SRC_DIR}/BuildSystem.rb"
require "#{NOMDEV_SRC_DIR}/ExternalDeps.rb"

NOMDEV_WORKING_DIRECTORY = FileUtils.pwd # Your current working directory

options = NomDevOptions.parse( ARGV )

if NOMDEV_DEBUG
  p "options: #{options}"
  p "ARGV: #{ARGV}"
end

command = ARGV[0]

case command
  when "gen", "generate"
    generator = CMakeGenerator.new( options )
    generator.generate
  when "build"
    build_system = BuildSystem.new( options )
    build_system.build
  when "clean"
    build_system = BuildSystem.new( options )
    build_system.clean
  when "rebuild"
    if platform["windows"]
      puts "Error: feature currently disabled; broken on Windows platform."
      quit
    else
      build_system = BuildSystem.new( options )
      build_system.clean
      build_system.build
    end
  when "install"
    build_system = BuildSystem.new( options )
    build_system.install
  when "uninstall", "remove"
    build_system = BuildSystem.new( options )
    build_system.uninstall
  when "test", "tests"
    build_system = BuildSystem.new( options )
    build_system.test
  when "wipe"
    # TODO: make me safer
    pwd = NOMDEV_WORKING_DIRECTORY
    if options.dry_run
      FileUtils.rmtree "#{pwd}/#{options.build_dir}", :verbose => true, :noop => true
    else
      FileUtils.rmtree "#{pwd}/#{options.build_dir}", :verbose => false, :noop => false
    end
    exit 0
  when "docs", "documentation" # experimental
    build_system = BuildSystem.new( options )
    build_system.docs
  when "analyze"
    build_system = BuildSystem.new( options )
    build_system.analyze
  when "get" # experimental
    deps = ExternalDeps.new( options )
    deps.get
  when "archive" # experimental
    deps = ExternalDeps.new( options )
    deps.archive
  when "LOC", "loc" # Uses CLOC: http://cloc.sourceforge.net/
    loc_filename = "#{NOMDEV_WORKING_DIRECTORY}/.loc.yml"

    args = Array.new
    args << '--quiet'
    args << "--ignored=#{NOMDEV_WORKING_DIRECTORY}/.cloc_ignored.log" if NOMDEV_DEBUG
    args << "--out=#{loc_filename}"
    args << '--yaml'
    args << '--exclude-dir=scale2x,hqx,jsoncpp,build,extra,third-party,tmp,wiki,Resources'
    args << "#{NOMDEV_WORKING_DIRECTORY}"

    if ! run( 'cloc', *args, options )
      puts "Error: Could not save #{loc_filename} using cloc."
      quit
    end

    if File.exists? loc_filename
      buffer = YAML.load_file( loc_filename )
    else
      puts "Error: Could not open #{loc_filename}."
      quit
    end

    config = buffer["SUM"]["code"]

    if config
      puts "\n" # Clear the buffer that CLOC seems to leave behind
      puts config
    else
      puts "Error: Could not parse #{loc_filename}."
      quit
    end
  else
    puts "Error: Unknown command #{ARGV[0]}"
    quit
end

exit 0


text:
module.exports = {
  type: 'georeference-long-lat',
  label: _t('editor.layers.analysis-form.georeference.long-lat'),
  parametersDataFields: 'latitude,longitude',
  fieldAttrs: {
    latitude: {
      showSearch: true
    },
    longitude: {
      showSearch: true
    }
  },

  validatorFor: {
    latitudde: ['required'],
    longitude: ['required']
  },

  parse: function (nodeAttrs) {
    return {
      type: 'georeference-long-lat',
      latitude: nodeAttrs.latitude,
      longitude: nodeAttrs.longitude
    };
  },

  getParameters: function () {
    var params = this.parametersDataFields;

    return params;
  },

  formatAttrs: function (formAttrs) {
    return formAttrs;
  }
};


text:
// Classes for creating the files for a delta glb given the files for the
// original glb and the latest glb.

#ifndef GEO_EARTH_ENTERPRISE_SRC_FUSION_PORTABLEGLOBE_DELTABUILDER_H_
#define GEO_EARTH_ENTERPRISE_SRC_FUSION_PORTABLEGLOBE_DELTABUILDER_H_

#include <iostream>  // NOLINT(readability/streams)
#include <string>
#include <cstdint>
#include "common/khTypes.h"
#include "fusion/portableglobe/shared/packetbundle.h"

namespace fusion_portableglobe {

class PacketBundleSource : public PacketBundle {
 public:
  explicit PacketBundleSource(const std::string &directory) {
    directory_ = directory + "/data";
  }
  /**
   * Returns packetbundle file's name for the given file id.
   */
  std::string SourcePacketBundleFileName(std::uint16_t file_id) const {
    return PacketBundleFileName(file_id);
  }

  /**
   * Returns index file's name.
   */
  std::string SourceIndexFileName() const {
    return IndexFileName();
  }

  /**
   * Returns number of packets referenced in index.
   */
  std::uint64_t SourceIndexSize() const {
    std::ifstream fp;

    fp.open(SourceIndexFileName().c_str(), std::ios::binary);
    fp.seekg(0, std::ios::end);
    std::int64_t file_size = fp.tellg();
    if (file_size < 0) {
      return 0;
    }
    return file_size / sizeof(IndexItem);
  }
};

/**
 * Class for constructing a portable globe.
 * Traverses quadtree packets and prunes out the ones that
 * are not required. Writes all of packet bundles of the nodes
 * that are kept.
 * TODO: Always use gepartialglobebuilder.
 * TODO: I.e. Remove geportableglobebuilder.
 */
class PortableDeltaBuilder {
 public:
  /**
   * Production constructor.
   */
  PortableDeltaBuilder(const std::string& original_directory,
                       const std::string& latest_directory,
                       const std::string& delta_directory);

  ~PortableDeltaBuilder();

  /**
   * Display nodes that were added to the delta index. This is mostly
   * for debugging.
   */
  void DisplayDeltaQtNodes();

  /**
   * Build packet bundle for all data types between the given starting
   * and ending quadtree addresses. Add data based on
   * whether corresponding quadtree node is to be kept in the globe.
   */
  void BuildDeltaGlobe();

 private:
  void LoadPacket(
      const char* file_name, const IndexItem& index_item, std::string* buffer);

  bool IsLatestPacketDifferent(
      const IndexItem& original_index_item,
      const std::ifstream& original_index,
      std::string* original_buffer,
      const IndexItem& latest_index_item,
      const std::ifstream& latest_index,
      std::string* latest_buffer,
      std::string* qtpath,
      bool* read_original_data);

  // Directory where delta files should be created.
  std::string delta_directory_;

  // Source of files from the original cut for the glb
  // that is out in the field.
  PacketBundleSource original_;
  // Source of files from the latest cut for the glb.
  PacketBundleSource latest_;
};

}  // namespace fusion_portableglobe

#endif  // GEO_EARTH_ENTERPRISE_SRC_FUSION_PORTABLEGLOBE_DELTABUILDER_H_