#!/usr/bin/python3

import os, sys
import time
import threading

from subprocess import run, SubprocessError, DEVNULL
from ipaddress import IPv4Network


class PingSweep:
    def __init__(self, subnet):
        self._subnet = subnet
        self._reachable_hosts = []

        self._ip_count        = 0
        self._completed_count = 0
        self._counter_lock    = threading.Lock()
        self._sweep_complete  = threading.Event()

    def __enter__(self):
        try:
            self._ip_network = list(IPv4Network(self._subnet).hosts())
        except Exception:
            self._ip_network = None
        else:
            self._ip_count = len(self._ip_network)

        return self

    def __exit__(self, exc_type, exc_val, traceback):
        return True

    def start(self):
        if (self._ip_network is None):
            raise TypeError('invalid network subnet. ex. 192.168.100.0/24')

        threading.Thread(target=self._main).start()
        # blocking until all threads finished and completion event is set
        self._timer(time.time())
        self._complete()

    def _icmp(self, ip_address):
        try:
            run(f'ping -c 1 -W 0.51 {ip_address}',
                shell=True, check=True, stdout=DEVNULL, stderr=DEVNULL)
        except SubprocessError:
            pass
        else:
            self._reachable_hosts.append(ip_address)

        with self._counter_lock:
            self._completed_count += 1
            self._progress()

    def _main(self):
        threads = []
        for ip_address in self._ip_network:
            threads.append(threading.Thread(target=self._icmp, args=(ip_address,)))

        # starting all threads. 1 for each host.
        for thread in threads:
            thread.start()
        # waiting for all threads to complete
        for thread in threads:
            thread.join()

        self._sweep_complete.set()

    def _complete(self):
        print('\n' + '-' * 26)
        print(f'completed in {self._time_to_sweep} seconds')
        print('-' * 26)
        if (not self._reachable_hosts):
            print('NO HOSTS RESPONDED :(.')
        else:
            for ip in self._reachable_hosts:
                print(str(ip))

    def _timer(self, start):
        self._sweep_complete.wait()

        self._time_to_sweep = round(time.time() - start, 2)

    def _progress(self):
	    bar_len = 38
	    filled_len = int(round(bar_len * self._completed_count / float(self._ip_count)))
	    percents = round(100.0 * self._completed_count / float(self._ip_count), 1)
	    bar = '#' * filled_len + '=' * (bar_len - filled_len)
	    sys.stdout.write(f'{self._completed_count}/{self._ip_count} || [{bar}] {percents}%s\r')
	    sys.stdout.flush()

def scan_again():
    while True:
        user_input = input('Do another scan? [Y/n]: ').lower()
        if (user_input in ['y', '']):
            break
        elif (user_input == 'n'):
            print('exiting...')
            os._exit(0)
        else:
            print('invalid entry. try again.')

def main():
    while True:
        ip_range = input('IP range to scan: ')
        with PingSweep(ip_range) as ping_sweep:
            ping_sweep.start()

        scan_again()

if __name__ == '__main__':
    print('Ping sweep| by DOWRIGHT. ^_^')
    print('-' * 26)
    main()