from socket import *
import os
import sys
import struct
import time
import select
import binascii
# Should use stdev
from statistics import stdev

ICMP_ECHO_REQUEST = 8


def checksum(string):
    csum = 0
    countTo = (len(string) // 2) * 2
    count = 0

    while count < countTo:
        thisVal = (string[count + 1]) * 256 + (string[count])
        csum += thisVal
        csum &= 0xffffffff
        count += 2

    if countTo < len(string):
        csum += (string[len(string) - 1])
        csum &= 0xffffffff

    csum = (csum >> 16) + (csum & 0xffff)
    csum = csum + (csum >> 16)
    answer = ~csum
    answer = answer & 0xffff
    answer = answer >> 8 | (answer << 8 & 0xff00)
    return answer



def receiveOnePing(mySocket, ID, timeout, destAddr):
    timeLeft = timeout

    while 1:
        startedSelect = time.time()
        whatReady = select.select([mySocket], [], [], timeLeft)
        howLongInSelect = (time.time() - startedSelect)
        if whatReady[0] == []:  # Timeout
            return "Request timed out."

        timeReceived = time.time()
        recPacket, addr = mySocket.recvfrom(1024)
        #print("received reply: ", timeReceived)

        # Fill in start
        #print("in receive- packetlength: ", len(recPacket))
        header = recPacket[0:8]
        #print ("in receive - header length: ", len(header))

        # Fetch the ICMP header from the IP packet
        headerStruct = struct.unpack("bbHHh", header)
        # print("type: ", headerStruct[0])
        # print("code: ", headerStruct[1])
        # print("checksum: ", headerStruct[2])
        # print("identifier: ", headerStruct[3])
        # print("seq num: ", headerStruct[4])

        # Fill in end
        timeLeft = timeLeft - howLongInSelect
        if timeLeft <= 0:
            return "Request timed out."
        return howLongInSelect * 1000


def sendOnePing(mySocket, destAddr, ID):
    # Header is type (8), code (8), checksum (16), id (16), sequence (16)

    myChecksum = 0
    # Make a dummy header with a 0 checksum
    # struct -- Interpret strings as packed binary data
    header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, myChecksum, ID, 1)
    data = struct.pack("d", time.time())
    # Calculate the checksum on the data and the dummy header.
    myChecksum = checksum(header + data)

    # Get the right checksum, and put in the header

    if sys.platform == 'darwin':
        # Convert 16-bit integers from host to network  byte order
        myChecksum = htons(myChecksum) & 0xffff
    else:
        myChecksum = htons(myChecksum)


    header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, myChecksum, ID, 1)
    packet = header + data

    # print("type: ", header[0])
    # print("code: ", header[1])
    # print("checksum: ", header[2])
    # print("identifier: ", header[3])
    # print("seq num: ", header[4])
    # print("in send - header length: ", len(header))
    # print("in send - data length: ", len(data))
    # print("in send - packet length: ", len(packet))

    mySocket.sendto(packet, (destAddr, 1))  # AF_INET address must be tuple, not str


    # Both LISTS and TUPLES consist of a number of objects
    # which can be referenced by their position number within the object.

def doOnePing(destAddr, timeout):
    icmp = getprotobyname("icmp")


    # SOCK_RAW is a powerful socket type. For more details:   http://sockraw.org/papers/sock_raw
    mySocket = socket(AF_INET, SOCK_RAW, icmp)

    myID = os.getpid() & 0xFFFF  # Return the current process i
    sendOnePing(mySocket, destAddr, myID)
    delay = receiveOnePing(mySocket, myID, timeout, destAddr)
    mySocket.close()
    return delay


def ping(host, timeout=1, numPings=4):
    # timeout=1 means: If one second goes by without a reply from the server,  	# the client assumes that either the client's ping or the server's pong is lost
    dest = gethostbyname(host)
    print("Pinging " + dest + " using Python:")
    print("")
    # Calculate vars values and return them
    # vars = [str(round(packet_min, 2)), str(round(packet_avg, 2)), str(round(packet_max, 2)),str(round(stdev(stdev_var), 2))]
    # Send ping requests to a server separated by approximately one second
    #for i in range(0,4):
    packet_min = -1
    packet_max = -1
    packet_total = 0
    stdev_var = []
    for i in range(0,numPings):
        delay = doOnePing(dest, timeout)
        print(delay)

        if (packet_min == -1) or (packet_min > delay):
            packet_min = delay
        
        if (packet_max < delay):
            packet_max = delay
        
        packet_total = packet_total + delay
        stdev_var.append(delay)

        time.sleep(1)  # one second

    packet_avg = packet_total / numPings
    vars = [str(round(packet_min, 2)), str(round(packet_avg, 2)), str(round(packet_max, 2)),str(round(stdev(stdev_var), 2))]

    return vars

if __name__ == '__main__':
    #vars = ping("127.0.0.1")
    vars = ping("google.co.il")

    print("min: ", vars[0])
    print("avg: ", vars[1])
    print("max: ", vars[2])
    print("std_dev: ", vars[3])
