package Plugins

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
	"os"
	"os/exec"
	"os/user"
	"runtime"
	"strings"
	"sync"
	"time"
)

var icmp ICMP

var AliveHosts []string

type ICMP struct {
	Type        uint8
	Code        uint8
	Checksum    uint16
	Identifier  uint16
	SequenceNum uint16
}

type SystemInfo struct {
	OS 			  string
	ARCH          string
	HostName      string
	Groupid  	  string
	Userid		  string
	Username	  string
	UserHomeDir	  string
}

func GetSys() SystemInfo {
	var sysinfo SystemInfo

	sysinfo.OS = runtime.GOOS
	sysinfo.ARCH = runtime.GOARCH
	name, err := os.Hostname()
	if err == nil {
		sysinfo.HostName = name
	}

	u, err := user.Current()
	sysinfo.Groupid = u.Gid
	sysinfo.Userid = u.Uid
	sysinfo.Username = u.Username
	sysinfo.UserHomeDir = u.HomeDir

	return sysinfo
}

func isping(ip string) bool {
	icmp.Type = 8
	icmp.Code = 0
	icmp.Checksum = 0
	icmp.Identifier = 0
	icmp.SequenceNum = 0

	recvBuf := make([]byte, 32)
	var buffer bytes.Buffer

	binary.Write(&buffer, binary.BigEndian, icmp)
	icmp.Checksum = CheckSum(buffer.Bytes())

	buffer.Reset()
	binary.Write(&buffer, binary.BigEndian, icmp)

	Time, _ := time.ParseDuration("2s")
	conn, err := net.DialTimeout("ip4:icmp", ip, Time)
	if err != nil {
		return false
	}
	_, err = conn.Write(buffer.Bytes())
	if err != nil {
		return false
	}
	conn.SetReadDeadline(time.Now().Add(time.Second * 2))
	num, err := conn.Read(recvBuf)
	if err != nil {
		return false
	}

	conn.SetReadDeadline(time.Time{})

	if string(recvBuf[0:num]) != "" {
		fmt.Printf("(ICMP) Target '%s' is alive\n",ip)
		return true
	}
	return false

}

func CheckSum(data []byte) uint16 {
	var (
		sum    uint32
		length int = len(data)
		index  int
	)
	for length > 1 {
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		index += 2
		length -= 2
	}
	if length > 0 {
		sum += uint32(data[index])
	}
	sum += (sum >> 16)

	return uint16(^sum)
}

func IcmpCheck(hostslist []string) {
	var wg sync.WaitGroup
	mutex := &sync.Mutex{}
	for _,host :=range hostslist{
		wg.Add(1)
		go func(host string) {
			defer wg.Done()
			if isping(host){
				mutex.Lock()
				AliveHosts = append(AliveHosts, host)
				mutex.Unlock()
			}
		}(host)
	}
	wg.Wait()
}

func ExecCommandPing(ip string,bsenv string) bool {
	command := exec.Command(bsenv, "-c", "ping -c 1 -w 1 "+ip+" >/dev/null && echo true || echo false")  //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
	outinfo := bytes.Buffer{}
	command.Stdout = &outinfo
	err := command.Start()
	if err != nil{
		return false
	}

	if err = command.Wait();err!=nil{
		return false
	}else{
		if(strings.Contains(outinfo.String(), "true")) {
			return true
		}else {
			return false
		}
	}
}

func PingCMDcheck(hostslist []string,bsenv string) {
	var wg sync.WaitGroup
	mutex := &sync.Mutex{}
	limiter := make(chan struct{}, 40)
	//aliveHost := make(chan string, 20)
	//go func() {
	//	for s := range aliveHost {
	//		fmt.Println(s)
	//	}
	//}()
	for _,host :=range hostslist{
		wg.Add(1)
		limiter <- struct{}{}
		go func(host string) {
			defer wg.Done()
			if ExecCommandPing(host,bsenv){
				mutex.Lock()
				fmt.Printf("(Ping) Target '%s' is alive\n",host)
				AliveHosts = append(AliveHosts, host)
				mutex.Unlock()
			}
			<-limiter
		}(host)
	}
	wg.Wait()
	//close(aliveHost)
}

func ICMPRun(hostslist []string)  []string{
	var sysinfo SystemInfo
	sysinfo = GetSys()

	if sysinfo.OS == "windows" {
		IcmpCheck(hostslist)
	}else if sysinfo.OS == "linux" {
		if (sysinfo.Groupid == "0" || sysinfo.Userid == "0" || sysinfo.Username == "root") {
			IcmpCheck(hostslist)
		}else {
			PingCMDcheck(hostslist,"/bin/bash")
		}
	}else if sysinfo.OS == "darwin" {
		if (sysinfo.Groupid == "0" || sysinfo.Userid == "0" || sysinfo.Username == "root") {
			IcmpCheck(hostslist)
		}else {
			PingCMDcheck(hostslist,"/usr/local/bin/bash")
		}
	}
	return AliveHosts
}
