//
// ping.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2008 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <istream>
#include <iostream>
#include <ostream>

#include "icmp_header.hpp"
#include "ipv4_header.hpp"

using boost::asio::ip::icmp;
using boost::asio::deadline_timer;
namespace posix_time = boost::posix_time;

class pinger
{
public:
    pinger(boost::asio::io_service& io_service, const char* destination)
        : resolver_(io_service), socket_(io_service, icmp::v4()),
          timer_(io_service), sequence_number_(0), num_replies_(0)
    {
        icmp::resolver::query query(icmp::v4(), destination, "");
        destination_ = *resolver_.resolve(query);

        start_send();
        start_receive();
    }

    double delay;

private:
    void start_send()
    {
        std::string body("\"Hello!\" from Asio ping.");

        // Create an ICMP header for an echo request.
        icmp_header echo_request;
        echo_request.type(icmp_header::echo_request);
        echo_request.code(0);
        echo_request.identifier(get_identifier());
        echo_request.sequence_number(++sequence_number_);
        compute_checksum(echo_request, body.begin(), body.end());

        // Encode the request packet.
        boost::asio::streambuf request_buffer;
        std::ostream os(&request_buffer);
        os << echo_request << body;

        // Send the request.
        time_sent_ = posix_time::microsec_clock::universal_time();
        socket_.send_to(request_buffer.data(), destination_);

        // Wait up to five seconds for a reply.
        num_replies_ = 0;
        timer_.expires_at(time_sent_ + posix_time::seconds(5));
        timer_.async_wait(boost::bind(&pinger::handle_timeout, this));
    }

    void handle_timeout()
    {
        if (num_replies_ == 0) {
	  std::cout << "Request timed out" << std::endl;
	  delay = -1;
	}

        // Requests must be sent no less than one second apart.
        timer_.expires_at(time_sent_ + posix_time::seconds(1));
        timer_.async_wait(boost::bind(&pinger::start_send, this));

    }

    void start_receive()
    {
        // Discard any data already in the buffer.
        reply_buffer_.consume(reply_buffer_.size());

        // Wait for a reply. We prepare the buffer to receive up to 64KB.
        socket_.async_receive(reply_buffer_.prepare(65536),
                              boost::bind(&pinger::handle_receive, this, _2));
    }

    void handle_receive(std::size_t length)
    {
        // The actual number of bytes received is committed to the buffer so that we
        // can extract it using a std::istream object.
        reply_buffer_.commit(length);

        // Decode the reply packet.
        std::istream is(&reply_buffer_);
        ipv4_header ipv4_hdr;
        icmp_header icmp_hdr;
        is >> ipv4_hdr >> icmp_hdr;

        // We can receive all ICMP packets received by the host, so we need to
        // filter out only the echo replies that match the our identifier and
        // expected sequence number.
        if (is && icmp_hdr.type() == icmp_header::echo_reply
            && icmp_hdr.identifier() == get_identifier()
            && icmp_hdr.sequence_number() == sequence_number_)
            {
                // If this is the first reply, interrupt the five second timeout.
                if (num_replies_++ == 0)
                    timer_.cancel();

                // Print out some information about the reply packet.
                posix_time::ptime now = posix_time::microsec_clock::universal_time();
                delay = (now - time_sent_).total_nanoseconds()/(1000.0*1000); // added
                std::cout << length - ipv4_hdr.header_length()
                          << " bytes from " << ipv4_hdr.source_address()
                          << ": icmp_seq=" << icmp_hdr.sequence_number()
                          << ", ttl=" << ipv4_hdr.time_to_live()
                          //<< ", time=" << (now - time_sent_).total_milliseconds() << " ms"
                          << ", time=" << delay << " ms"
                          << std::endl;
            }

        start_receive();
    }

    static unsigned short get_identifier()
    {
#if defined(BOOST_WINDOWS)
        return static_cast<unsigned short>(::GetCurrentProcessId());
#else
        return static_cast<unsigned short>(::getpid());
#endif
    }

    icmp::resolver resolver_;
    icmp::endpoint destination_;
    icmp::socket socket_;
    deadline_timer timer_;
    unsigned short sequence_number_;
    posix_time::ptime time_sent_;
    boost::asio::streambuf reply_buffer_;
    std::size_t num_replies_;
};

#if 0
int main(int argc, char* argv[])
{
  try
      {
          if (argc != 2)
              {
                  std::cerr << "Usage: ping <host>" << std::endl;
#if !defined(BOOST_WINDOWS)
                  std::cerr << "(You may need to run this program as root.)" << std::endl;
#endif
                  return 1;
              }

          boost::asio::io_service io_service;
          pinger p(io_service, argv[1]);
          io_service.run();
      }
  catch (std::exception& e)
      {
          std::cerr << "Exception: " << e.what() << std::endl;
      }
}
#endif
