// +build linux

package service

import (
	"github.com/mame82/P4wnP1_aloa/common_web"
	"github.com/mame82/P4wnP1_aloa/netlink"
	pb "github.com/mame82/P4wnP1_aloa/proto"
	"sync"
	"os/exec"
	"github.com/mame82/P4wnP1_aloa/service/util"
	"errors"
	"fmt"
	"strings"
	"log"
	"time"
	"syscall"
	"net"
	"io/ioutil"
	"os"
	"regexp"
	"strconv"
)

const (
	wifi_if_name                   string = "wlan0"
	WPA_SUPPLICANT_CONNECT_TIMEOUT        = time.Second * 20
	HOSTAPD_WAIT_AP_UP_TIMEOUT        = time.Second * 8
)

func wifiCheckExternalBinaries() error {
	if !binaryAvailable("wpa_supplicant") {
		return errors.New("wpa_supplicant seems to be missing, please install it")
	}
	// to create wpa_supplicant.conf
	if !binaryAvailable("wpa_passphrase") {
		return errors.New("wpa_passphrase seems to be missing, please install it")
	}
	if !binaryAvailable("hostapd") {
		return errors.New("hostapd seems to be missing, please install it")
	}
	// for wifiScan
	if !binaryAvailable("iw") {
		return errors.New("The tool 'iw' seems to be missing, please install it")
	}

	return nil
}

type WiFiService struct {
	RootSvc *Service

	State *pb.WiFiState
	//Settings *pb.WiFi2Settings

	mutexSettings           *sync.Mutex              // Lock settings on change
	CmdWpaSupplicant        *exec.Cmd                //Manages wpa-supplicant process
	mutexWpaSupplicant      *sync.Mutex              //mutex for wpa-supplicant proc
	CmdHostapd              *exec.Cmd                //Manages hostapd process
	mutexHostapd            *sync.Mutex              //hostapd proc lock
	IfaceName               string                   //Name of WiFi interface
	PathWpaSupplicantConf   string                   // path to config file for wpa-supplicant
	PathHostapdConf         string                   // path to config file for hostapd
	LoggerHostapd           *util.TeeLogger          //logger for hostapd
	LoggerWpaSupplicant     *util.TeeLogger          //logger for WPA supplicant
	OutMonitorWpaSupplicant *wpaSupplicantOutMonitor //Monitors wpa_supplicant output and sets signals where needed
	OutMonitorHostapd       *hostapdOutMonitor //Monitors hostapd output and sets signals where needed
}

func (wSvc *WiFiService) StartHostapd(timeout time.Duration) (err error) {
	log.Printf("Starting hostapd for interface '%s'...\n", wSvc.IfaceName)

	wSvc.mutexHostapd.Lock()
	defer wSvc.mutexHostapd.Unlock()

	//stop hostapd if already running
	if wSvc.CmdHostapd != nil {
		// avoid deadlock
		wSvc.mutexHostapd.Unlock()
		wSvc.StopHostapd()
		wSvc.mutexHostapd.Lock()
	}

	wSvc.CmdHostapd = exec.Command("/usr/sbin/hostapd", wSvc.PathHostapdConf)
	// the logger is the signal generator for OutMonitorHostapd, so we reset the signal before applying the logger
	wSvc.OutMonitorHostapd.resultReceived.Reset()
	wSvc.CmdHostapd.Stdout = wSvc.LoggerHostapd.LogWriter
	wSvc.CmdHostapd.Stderr = wSvc.LoggerHostapd.LogWriter
	err = wSvc.CmdHostapd.Start()
	if err != nil {
		wSvc.CmdHostapd.Wait()
		return errors.New(fmt.Sprintf("Error starting hostapd '%v'", err))
	}

	//wait for result in output
	apUp, errcon := wSvc.OutMonitorHostapd.WaitConnectResultOnce(timeout)
	if errcon != nil {
		log.Printf("... hostapd reached timeout of '%v' without beeing able to bring up an Access Point\n", timeout)
		// avoid dead lock
		wSvc.mutexHostapd.Unlock()
		wSvc.StopHostapd()
		wSvc.mutexHostapd.Lock()
		return errors.New("TIMEOUT REACHED")
	}

	if apUp {
		//We could return success and keep wpa_supplicant running
		log.Printf("... hostapd AP for interface '%s' started\n", wSvc.IfaceName)
		return nil
	} else {
		log.Println("... hostapd failed to bring up an Access Point, stopping ...")
		//wifiStopWpaSupplicant(nameIface)
		// avoid dead lock
		wSvc.mutexHostapd.Unlock()
		wSvc.StopHostapd()
		wSvc.mutexHostapd.Lock()
		log.Println("... hostapd terminated")
		return errors.New("Hostapd failed to bring up Access Point")
	}

	return nil
}

func (wSvc *WiFiService) StopHostapd() (err error) {
	eSuccess := fmt.Sprintf("... hostapd for interface '%s' stopped", wSvc.IfaceName)
	eCantStop := fmt.Sprintf("... couldn't terminate hostapd for interface '%s'", wSvc.IfaceName)

	log.Println("... killing hostapd")
	wSvc.mutexHostapd.Lock()
	defer wSvc.mutexHostapd.Unlock()

	if wSvc.CmdHostapd == nil {
		log.Printf("... hostapd for interface '%s' isn't running, no need to stop it\n", wSvc.IfaceName)
		return nil
	}

	/*
	wSvc.CmdHostapd.Process.Signal(syscall.SIGTERM)
	time.Sleep(time.Millisecond * 500)
	if wSvc.CmdHostapd.ProcessState == nil || !wSvc.CmdHostapd.ProcessState.Exited() {
		wSvc.CmdHostapd.Process.Kill()
		wSvc.CmdHostapd.Wait()

		//wSvc.CmdHostapd.Process.Kill()
		if wSvc.CmdHostapd.ProcessState.Exited() {
			wSvc.CmdHostapd = nil
			log.Println(eSuccess)
			return nil
		} else {
			log.Println(eCantStop)
			return errors.New(eCantStop)
		}

	}
	*/
	err = ProcSoftKill(wSvc.CmdHostapd, time.Second)
	if err != nil { return errors.New(eCantStop) }


	wSvc.CmdHostapd = nil
	log.Println(eSuccess)
	return nil
}

func (wSvc *WiFiService) StopWpaSupplicant() (err error) {
	eSuccess := fmt.Sprintf("... wpa_supplicant for interface '%s' stopped", wSvc.IfaceName)
	eCantStop := fmt.Sprintf("... couldn't terminate wpa_supplicant for interface '%s'", wSvc.IfaceName)

	log.Printf("... stop running wpa_supplicant processes for interface '%s'\n", wSvc.IfaceName)

	wSvc.mutexWpaSupplicant.Lock()
	defer wSvc.mutexWpaSupplicant.Unlock()

	if wSvc.CmdWpaSupplicant == nil {
		log.Printf("... wpa_supplicant for interface '%s' wasn't running, no need to stop it\n", wSvc.IfaceName)
		return nil
	}

	/*
	log.Printf("... sending SIGTERM for wpa_supplicant on interface '%s' with PID\n", wSvc.IfaceName, wSvc.CmdWpaSupplicant.Process.Pid)
	wSvc.CmdWpaSupplicant.Process.Signal(syscall.SIGTERM)
	wSvc.CmdWpaSupplicant.Wait()
	if !wSvc.CmdWpaSupplicant.ProcessState.Exited() {
		log.Printf("... wpa_supplicant didn't react on SIGTERM for interface '%s', trying SIGKILL\n", wSvc.IfaceName)
		wSvc.CmdWpaSupplicant.Process.Kill()

		time.Sleep(500 * time.Millisecond)
		if wSvc.CmdWpaSupplicant.ProcessState.Exited() {
			wSvc.CmdWpaSupplicant = nil
			log.Println(eSuccess)
			return nil
		} else {
			log.Println(eCantStop)
			return errors.New(eCantStop)
		}
	}
	*/
	log.Printf("... stopping wpa_supplicant\n", wSvc.IfaceName, wSvc.CmdWpaSupplicant.Process.Pid)
	err = ProcSoftKill(wSvc.CmdWpaSupplicant, time.Second*2)
	if err != nil { return errors.New(eCantStop) }

	wSvc.CmdWpaSupplicant = nil
	log.Println(eSuccess)
	return nil
}

func (wSvc *WiFiService) StartWpaSupplicant(timeout time.Duration) (err error) {
	log.Printf("Starting wpa_supplicant for interface '%s'...\n", wSvc.IfaceName)

	wSvc.mutexWpaSupplicant.Lock()
	defer wSvc.mutexWpaSupplicant.Unlock()

	//stop wpa_supplicant if already running
	if wSvc.CmdWpaSupplicant != nil {
		// avoid dead lock
		wSvc.mutexWpaSupplicant.Unlock()
		wSvc.StopWpaSupplicant()
		wSvc.mutexWpaSupplicant.Lock()
	}

	//we monitor output of wpa_supplicant till we are connected, fail due to wrong PSK or timeout is reached
	//Note: PID file creation doesn't work when not started as daemon, so we do it manually, later on
	wSvc.CmdWpaSupplicant = exec.Command("/sbin/wpa_supplicant", "-c", wSvc.PathWpaSupplicantConf, "-i", wSvc.IfaceName)
	// the logger is the signal generator for OutMonitorWpaSupplicant, so we reset the signal before apllying the logger
	wSvc.OutMonitorWpaSupplicant.resultReceived.Reset()
	wSvc.CmdWpaSupplicant.Stdout = wSvc.LoggerWpaSupplicant.LogWriter

	err = wSvc.CmdWpaSupplicant.Start()
	if err != nil {
		return err
	}

	//wait for result in output
	connected, errcon := wSvc.OutMonitorWpaSupplicant.WaitConnectResultOnce(timeout)
	if errcon != nil {
		log.Printf("... wpa_supplicant reached timeout of '%v' without beeing able to connect to given network\n", timeout)
		log.Println("... killing wpa_supplicant")
		// avoid dead lock
		wSvc.mutexWpaSupplicant.Unlock()
		wSvc.StopWpaSupplicant()
		wSvc.mutexWpaSupplicant.Lock()
		return errors.New("TIMEOUT REACHED")
	}
	if connected {
		//We could return success and keep wpa_supplicant running
		log.Println("... connected to given WiFi network, wpa_supplicant running")
		return nil
	} else {
		//we stop wpa_supplicant and return err
		log.Println("... seems the wrong PSK was provided for the given WiFi network, stopping wpa_supplicant ...")
		//wifiStopWpaSupplicant(nameIface)
		log.Println("... killing wpa_supplicant")
		// avoid dead lock
		wSvc.mutexWpaSupplicant.Unlock()
		wSvc.StopWpaSupplicant()
		wSvc.mutexWpaSupplicant.Lock()
		return errors.New("Wrong PSK")
	}

	return nil
}


func MatchGivenBBSToScanResult(scanRes []BSS, targets []*pb.WiFiBSSCfg) (matches []*pb.WiFiBSSCfg) {
	for _, bssCfgTarget := range targets {
		for _, bssCfgScan := range scanRes {
			if bssCfgScan.SSID == bssCfgTarget.SSID {
				log.Printf("Found SSID '%s'\n", bssCfgScan.SSID)
				if len(bssCfgTarget.PSK) == 0 && bssCfgScan.AuthMode != WiFiAuthMode_OPEN {
					log.Printf("No PSK provided for '%s', but authentication mode isn't OPEN. Ignoring this network ...\n", bssCfgScan.SSID)
				} else {
					// SSID match, possible candidate
					matches = append(matches, bssCfgTarget)
				}

			}
		}
	}
	return
}

func (wSvc *WiFiService) runStaMode(newWifiSettings *pb.WiFiSettings) (err error) {
	if len(newWifiSettings.Client_BSSList) == 0 {
		return errors.New("Error: WiFi mode set to station (STA) but no BSS configurations provided")
	}

	//scan for provided wifi
	scanres, err := WifiScan(wSvc.IfaceName)
	if err != nil {
		return errors.New(fmt.Sprintf("Scanning for existing WiFi networks failed: %v", err))
	}

	matchingBssList := MatchGivenBBSToScanResult(scanres, newWifiSettings.Client_BSSList)
	if len(matchingBssList) == 0 {
		return errors.New(fmt.Sprintf("Non of the given SSIDs found during scan\n"))
	}

	// Create config for the remaining networks
	confstr, err := wifiCreateWpaSupplicantConfStringList(matchingBssList)
	if err != nil {
		return err
	}
	// store config to file
	log.Printf("Creating wpa_supplicant configuration file at '%s'\n", wSvc.PathWpaSupplicantConf)
	err = ioutil.WriteFile(wSvc.PathWpaSupplicantConf, []byte(confstr), os.ModePerm)
	if err != nil {
		return err
	}

	//ToDo: proper error handling, in case connection not possible
	err = wSvc.StartWpaSupplicant(WPA_SUPPLICANT_CONNECT_TIMEOUT)
	if err != nil {
		return err
	}


	return nil
}

// ToDo: Output monitor for AP-ENABLED (same approach as for wpa_supplicant)
func (wSvc *WiFiService) runAPMode(newWifiSettings *pb.WiFiSettings) (err error) {
	//generate hostapd.conf (overwrite old one)
	hostapdCreateConfigFile2(newWifiSettings, wSvc.PathHostapdConf)

	//start hostapd
	err = wSvc.StartHostapd(HOSTAPD_WAIT_AP_UP_TIMEOUT)
	if err != nil {
		fmt.Println("Wait 2 seconds and retry to to start hostapd once...")
		time.Sleep(2 * time.Second)
		err = wSvc.StartHostapd(HOSTAPD_WAIT_AP_UP_TIMEOUT)
		if err != nil {
			return err
		}
	}

	return nil
}

func (wSvc *WiFiService) DeploySettings(newWifiSettings *pb.WiFiSettings) (wstate *pb.WiFiState, err error) {
	log.Println("Deploying new WiFi settings...")
	log.Printf("Settings: %+v\n", newWifiSettings)

	wSvc.mutexSettings.Lock()
	defer wSvc.mutexSettings.Unlock()


	//ToDo: Dis/Enable nexmon if needed

	//stop wpa_supplicant if needed
	err = wSvc.StopWpaSupplicant()
	if err != nil {
		return wSvc.State, err
	}
	//kill hostapd in case it is still running
	err = wSvc.StopHostapd()
	if err != nil {
		return wSvc.State, err
	}

	// Note: When hostapd is killed, the WiFi interface is shut down. If the firmware is reloade (toggeling
	// nexmon) the whole interface gets destroyed. Both have influence on processes depending on the network
	// configuration of the WiFi interface (e.g. DHCP server / client). In order to avoid errors, we reconfigure the
	// WiFi network interface after changing the WiFi state.
	// We do this after setting the respective WiFi mode (STA / AP / FAILOVER), but in order to make this modes
	// work, we enable the interface first - regardless of its 'enabled' state.
	iface, tmperr := net.InterfaceByName(wSvc.IfaceName)
	if tmperr == nil {
		netlink.NetworkLinkUp(iface)
	}

	// Set proper regulatory domain
	errReg := wifiSetReg(newWifiSettings.Regulatory)
	if errReg != nil {
		log.Printf("Error setting WiFi regulatory domain '%s': %v\n", newWifiSettings.Regulatory	, err) //we don't abort on error here
	}

	var triggerEvent *pb.Event = nil
	if !newWifiSettings.Disabled {
		switch newWifiSettings.WorkingMode {
		case pb.WiFiWorkingMode_AP:
			err = wSvc.runAPMode(newWifiSettings)
			// emit Trigger event if AP is Up
			if err == nil {
				triggerEvent = ConstructEventTrigger(common_web.TRIGGER_EVT_TYPE_WIFI_AP_STARTED)
			}
		case pb.WiFiWorkingMode_STA, pb.WiFiWorkingMode_STA_FAILOVER_AP:
			errSta := wSvc.runStaMode(newWifiSettings)
			if errSta == nil {
				triggerEvent = ConstructEventTrigger(common_web.TRIGGER_EVT_TYPE_WIFI_CONNECTED_AS_STA)
			} else {
				//in failover mode, we try to enable AP first
				if newWifiSettings.WorkingMode == pb.WiFiWorkingMode_STA_FAILOVER_AP {
					log.Println(errSta)
					log.Printf("Trying to fail over to Access Point Mode...")
					err = wSvc.runAPMode(newWifiSettings)
					if err == nil {
						triggerEvent = ConstructEventTrigger(common_web.TRIGGER_EVT_TYPE_WIFI_AP_STARTED)
					}
				} else {
					err = errSta
				}
			}
		default:
			// None allowed working mode, so we leave wSvc as UNKNOWN
			err = errors.New("Unknown working mode")
		}
	}
	//fmt.Println("HOSTAPD ERR CHECK\n==============\n-->", err)
	if err == nil {
		log.Printf("... WiFi settings deployed successfully\n")
	} else {
		log.Printf("... deploying WiFi settings failed: %s\n", err.Error())
		return wSvc.State, err
	}

	// At this point, we reestablish the interface settings
	//ReInitNetworkInterface(wSvc.IfaceName)
	if nim,err := wSvc.RootSvc.SubSysNetwork.GetManagedInterface(wSvc.IfaceName); err == nil {
		nim.ReDeploy()
	}



	// update settings (wSvc is updated by runAPMode/runStaMode)
	wSvc.State.CurrentSettings = newWifiSettings

	// update rest of state
	if serr := wSvc.UpdateStateFromIw(); serr != nil {
		log.Println("Couldn't update internal WiFi state:", serr)
	}

	// Fire the event after everything is done, especially after redeployment of the network interface settings
	// to allow an ActionTrigger which deploys another ethernet settings template (without changing the settings
	// in parallel)
	// ToDo: check if it makes sense to lock the methods responsible for deploying ethernet interface settings and WifI settings (both long-running)
	if triggerEvent != nil {
		wSvc.RootSvc.SubSysEvent.Emit(triggerEvent)
	}


	return wSvc.State, nil
}

func NewWifiService(rootSvc *Service) (res *WiFiService) {
	ifName := wifi_if_name
	err := wifiCheckExternalBinaries()
	if err != nil {
		panic(err)
	}

	//Check interface existence
	if exists := CheckInterfaceExistence(ifName); !exists {
		panic(errors.New(fmt.Sprintf("WiFi interface '%s' not present")))
	}

	res = &WiFiService{
		RootSvc: rootSvc,
		mutexSettings:         &sync.Mutex{},
		CmdWpaSupplicant:      nil,
		mutexWpaSupplicant:    &sync.Mutex{},
		CmdHostapd:            nil,
		mutexHostapd:          &sync.Mutex{},
		IfaceName:             ifName,
		PathWpaSupplicantConf: fmt.Sprintf("/tmp/wpa_supplicant_%s.conf", ifName),
		PathHostapdConf:       fmt.Sprintf("/tmp/hostapd_%s.conf", ifName),
	}

	res.OutMonitorWpaSupplicant = NewWpaSupplicantOutMonitor()
	res.OutMonitorHostapd = NewHostapdOutMonitor()

	res.LoggerHostapd = util.NewTeeLogger(true)
	res.LoggerHostapd.SetPrefix("hostapd: ")
	res.LoggerHostapd.AddOutput(res.OutMonitorHostapd)

	res.LoggerWpaSupplicant = util.NewTeeLogger(true)
	res.LoggerWpaSupplicant.SetPrefix("wpa_supplicant: ")
	res.LoggerWpaSupplicant.AddOutput(res.OutMonitorWpaSupplicant) // add watcher too tee'ed output writers

	// Initial settings and state on service start

	res.State = &pb.WiFiState{
		Mode: pb.WiFiStateMode_STA_NOT_CONNECTED,
		Channel: 0,
		Ssid: "",
	}

	res.State.CurrentSettings = &pb.WiFiSettings{
		Disabled:       false,
		WorkingMode:    pb.WiFiWorkingMode_AP,
		Client_BSSList: []*pb.WiFiBSSCfg{&pb.WiFiBSSCfg{SSID:"", PSK:""}},
		Ap_BSS:         &pb.WiFiBSSCfg{},
	}
	return res
}

// io.Writer firing a signal if predefined output arrives
type hostapdOutMonitor struct {
	resultReceived *util.Signal
	result         bool
	*sync.Mutex
}

func (m *hostapdOutMonitor) Write(p []byte) (n int, err error) {
	// if result already received, the write could exit (early out)
	if m.resultReceived.IsSet() {
		// fix endless loop of writing 'CTRL-EVENT-TERMINATING'
		return len(p), nil
	}

	// check if buffer contains relevant strings (assume write is called line wise by the hosted process
	// otherwise we'd need to utilize an io.Reader
	line := string(p)

	switch {
	case strings.Contains(line, "AP-DISABLED"):
		log.Printf("Starting Access Point failed\n")
		m.Lock()
		defer m.Unlock()
		m.result = false
		m.resultReceived.Set()
	case strings.Contains(line, "AP-ENABLED"):
		log.Printf("Access point is up\n")
		m.Lock()
		defer m.Unlock()
		m.result = true
		m.resultReceived.Set()
	}
	return len(p), nil
}

func (m *hostapdOutMonitor) WaitConnectResultOnce(timeout time.Duration) (connected bool, err error) {
	err = m.resultReceived.WaitTimeout(timeout)
	if err != nil {
		return false, errors.New("Couldn't retrieve hostapd connection state before timeout")
	}

	m.Lock()
	defer m.Unlock()
	connected = m.result
	m.resultReceived.Reset() //Disable result received, for next use
	return
}

func NewHostapdOutMonitor() *hostapdOutMonitor {
	return &hostapdOutMonitor{
		resultReceived: util.NewSignal(false, false),
		Mutex:          &sync.Mutex{},
		result: false,
	}
}

type wpaSupplicantOutMonitor struct {
	resultReceived *util.Signal
	result         bool
	*sync.Mutex
}

func (m *wpaSupplicantOutMonitor) Write(p []byte) (n int, err error) {
	// if result already received, the write could exit (early out)
	if m.resultReceived.IsSet() {
		return len(p), nil
	}

	// check if buffer contains relevant strings (assume write is called line wise by the hosted process
	// otherwise we'd need to utilize an io.Reader
	line := string(p)

	switch {
	case strings.Contains(line, "WRONG_KEY"):
		log.Printf("Seems the provided PSK doesn't match\n")
		m.Lock()
		defer m.Unlock()
		m.result = false
		m.resultReceived.Set()
	case strings.Contains(line, "CTRL-EVENT-CONNECTED"):
		// CTRL-EVENT-CONNECTED - Connection to 4e:66:41:a0:5b:35 completed
		log.Printf("Connected to target network\n")
		m.Lock()
		defer m.Unlock()
		m.result = true
		m.resultReceived.Set()
	}
	return len(p), nil
}

func (m *wpaSupplicantOutMonitor) WaitConnectResultOnce(timeout time.Duration) (connected bool, err error) {
	err = m.resultReceived.WaitTimeout(timeout)
	if err != nil {
		return false, errors.New("Couldn't retrieve wpa_supplicant connection state before timeout")
	}

	m.Lock()
	defer m.Unlock()
	connected = m.result
	m.resultReceived.Reset() //Disable result received, for next use
	return
}

func NewWpaSupplicantOutMonitor() *wpaSupplicantOutMonitor {
	return &wpaSupplicantOutMonitor{
		resultReceived: util.NewSignal(false, false),
		Mutex:          &sync.Mutex{},
	}
}

type WiFiAuthMode int

const (
	WiFiAuthMode_OPEN WiFiAuthMode = iota
	//WiFiAuthMode_WEP
	WiFiAuthMode_WPA_PSK
	WiFiAuthMode_WPA2_PSK
	WiFiAuthMode_UNSUPPORTED
)

type BSS struct {
	SSID           string
	BSSID          net.HardwareAddr
	Frequency      int
	BeaconInterval time.Duration //carefull, on IE level beacon interval isn't measured in milliseconds
	AuthMode       WiFiAuthMode
	Signal         float32 //Signal strength in dBm
}

func WifiScan(ifName string) (result []BSS, err error) {
	proc := exec.Command("/sbin/iw", ifName, "scan")
	res, err := proc.CombinedOutput()
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Error running scan: '%s'\niw output: %s", err, res))
	}

	result, err = ParseIwScan(string(res))

	return
}

func wifiCreateWpaSupplicantConfStringList(bsslist []*pb.WiFiBSSCfg) (config string, err error) {
	// if a PSK is provided, we assume it is needed, otherwise we assume OPEN AUTHENTICATION
	for _, bss := range bsslist {
		ssid := bss.SSID
		psk := bss.PSK
		if len(psk) > 0 {
			fmt.Println("Connecting WiFi with PSK")
			proc := exec.Command("/usr/bin/wpa_passphrase", ssid, psk)
			cres, err := proc.CombinedOutput()

			if err != nil {
				return "", errors.New(fmt.Sprintf("Error craeting wpa_supplicant.conf for SSID '%s' with PSK '%s': %s", ssid, psk, string(cres)))
			}
			config += string(cres)
		} else {
			fmt.Println("Connecting WiFi with OPEN AUTH")
			config += fmt.Sprintf(
				`network={
			ssid="%s"
			key_mgmt=NONE
		}
`, ssid)

		}

	}

	config = "ctrl_interface=/run/wpa_supplicant\n" + config

	return
}

func wifiCreateHostapdConfString(ws *pb.WiFiSettings) (config string, err error) {
	if ws.WorkingMode != pb.WiFiWorkingMode_AP && ws.WorkingMode != pb.WiFiWorkingMode_STA_FAILOVER_AP {
		return "", errors.New("Couldn't create hostapd configuration, the settings don't include an AP")
	}

	if ws.Ap_BSS == nil {
		return "", errors.New("WiFiSettings don't contain a BSS configuration for an AP")
	}

	config = fmt.Sprintf("interface=%s\n", wifi_if_name)

	config += fmt.Sprintf("driver=nl80211\n")                            //netlink capable driver
	config += fmt.Sprintf("hw_mode=g\n")                                 //Use 2.4GHz band
	config += fmt.Sprintf("ieee80211n=1\n")                              //Enable 802.111n
	config += fmt.Sprintf("wmm_enabled=1\n")                             //Enable WMM
	config += fmt.Sprintf("ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]\n") // 40MHz channels with 20ns guard interval
	config += fmt.Sprintf("macaddr_acl=0\n")                             //Accept all MAC addresses

	config += fmt.Sprintf("ssid=%s\n", ws.Ap_BSS.SSID)
	config += fmt.Sprintf("channel=%d\n", ws.Channel)

	if ws.AuthMode == pb.WiFiAuthMode_WPA2_PSK {
		config += fmt.Sprintf("auth_algs=1\n") //Use WPA authentication
		config += fmt.Sprintf("wpa=2\n")       //Use WPA2
		//ToDo: check if PSK could be provided encrypted
		config += fmt.Sprintf("wpa_key_mgmt=WPA-PSK\n") //Use a pre-shared key

		config += fmt.Sprintf("wpa_passphrase=%s\n", ws.Ap_BSS.PSK) //Set PSK
		config += fmt.Sprintf("rsn_pairwise=CCMP\n")                //Use Use AES, instead of TKIP
	} else {
		config += fmt.Sprintf("auth_algs=3\n") //Both, open and shared auth
	}

	if ws.HideSsid {
		config += fmt.Sprintf("ignore_broadcast_ssid=2\n") //Require clients to know the SSID
	} else {
		config += fmt.Sprintf("ignore_broadcast_ssid=0\n") //Send beacons + probes
	}

	return
}

func hostapdCreateConfigFile2(s *pb.WiFiSettings, filename string) (err error) {
	log.Printf("Creating hostapd configuration file at '%s'\n", filename)
	fileContent, err := wifiCreateHostapdConfString(s)
	if err != nil {
		return
	}
	err = ioutil.WriteFile(filename, []byte(fileContent), os.ModePerm)
	return
}

//ToDo: Create netlink based implementation (not relying on 'iw'): low priority
func ParseIwScan(scanresult string) (bsslist []BSS, err error) {
	//fmt.Printf("Parsing:\n%s\n", scanresult)

	//split into BSS sections
	rp := regexp.MustCompile("(?msU)^BSS.*")
	strBSSList := rp.Split(scanresult, -1)
	if len(strBSSList) < 1 {
		return nil, errors.New("Error parsing iw scan result") //splitting should always result in one element at least
	}

	bsslist = []BSS{}

	rp_bssid := regexp.MustCompile("[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}")
	rp_freq := regexp.MustCompile("(?m)freq:\\s*([0-9]{4})")
	rp_ssid := regexp.MustCompile("(?m)SSID:\\s*(.*)\n")
	rp_beacon_intv := regexp.MustCompile("(?m)beacon interval:\\s*([0-9]*)TU")

	rp_WEP := regexp.MustCompile("(?m)WEP:")
	rp_WPA := regexp.MustCompile("(?m)WPA:")
	rp_WPA2 := regexp.MustCompile("(?m)RSN:")
	rp_PSK := regexp.MustCompile("(?m)Authentication suites: PSK") //ToDo: check if PSK occurs under respective IE (RSN or WPA, when either is chosen)

	//signal: -75.00 dBm
	rp_signal := regexp.MustCompile("(?m)signal:\\s*(-?[0-9]*\\.[0-9]*)")
	for idx, strBSS := range strBSSList[1:] {
		currentBSS := BSS{}
		//fmt.Printf("BSS %d\n================\n%s\n", idx, strBSS)
		fmt.Printf("BSS %d\n================\n", idx)

		//BSSID (should be in first line)
		strBSSID := rp_bssid.FindString(strBSS)
		fmt.Printf("BSSID: %s\n", strBSSID)
		currentBSS.BSSID, err = net.ParseMAC(strBSSID)
		if err != nil {
			return nil, err
		}

		//freq
		strFreq_sub := rp_freq.FindStringSubmatch(strBSS)
		strFreq := "0"
		if len(strFreq_sub) > 1 {
			strFreq = strFreq_sub[1]
		}
		fmt.Printf("Freq: %s\n", strFreq)
		tmpI64, err := strconv.ParseInt(strFreq, 10, 32)
		if err != nil {
			return nil, err
		}
		currentBSS.Frequency = int(tmpI64)

		//ssid
		strSsid_sub := rp_ssid.FindStringSubmatch(strBSS)
		strSSID := ""
		if len(strSsid_sub) > 1 {
			strSSID = strSsid_sub[1]
		}
		fmt.Printf("SSID: '%s'\n", strSSID)
		currentBSS.SSID = strSSID

		//beacon interval
		strBI_sub := rp_beacon_intv.FindStringSubmatch(strBSS)
		strBI := "100"
		if len(strBI_sub) > 1 {
			strBI = strBI_sub[1]
		}
		fmt.Printf("Beacon Interval: %s\n", strBI)
		tmpI64, err = strconv.ParseInt(strBI, 10, 32)
		if err != nil {
			return nil, err
		}
		currentBSS.BeaconInterval = time.Microsecond * time.Duration(tmpI64*1024) //1TU = 1024 microseconds (not 1000)

		//auth type
		//assume OPEN
		//if "WEP: is present assume UNSUPPORTED
		//if "WPA:" is present assume WPA (overwrite WEP/UNSUPPORTED)
		//if "RSN:" is present assume WPA2 (overwrite WPA/UNSUPPORTED)
		//in case of WPA/WPA2 check for presence of "Authentication suites: PSK" to assure PSK support, otherwise assume unsupported (no EAP/CHAP support for now)
		currentBSS.AuthMode = WiFiAuthMode_OPEN
		if rp_WEP.MatchString(strBSS) {
			currentBSS.AuthMode = WiFiAuthMode_UNSUPPORTED
		}
		if rp_WPA.MatchString(strBSS) {
			currentBSS.AuthMode = WiFiAuthMode_WPA_PSK
		}
		if rp_WPA2.MatchString(strBSS) {
			currentBSS.AuthMode = WiFiAuthMode_WPA2_PSK
		}
		if currentBSS.AuthMode == WiFiAuthMode_WPA_PSK || currentBSS.AuthMode == WiFiAuthMode_WPA2_PSK {
			if !rp_PSK.MatchString(strBSS) {
				currentBSS.AuthMode = WiFiAuthMode_UNSUPPORTED
			}
		}
		switch currentBSS.AuthMode {
		case WiFiAuthMode_UNSUPPORTED:
			fmt.Println("AuthMode: UNSUPPORTED")
		case WiFiAuthMode_OPEN:
			fmt.Println("AuthMode: OPEN")
		case WiFiAuthMode_WPA_PSK:
			fmt.Println("AuthMode: WPA PSK")
		case WiFiAuthMode_WPA2_PSK:
			fmt.Println("AuthMode: WPA2 PSK")
		}

		//signal
		strSignal_sub := rp_signal.FindStringSubmatch(strBSS)
		strSignal := "0.0"
		if len(strSignal_sub) > 1 {
			strSignal = strSignal_sub[1]
		}
		tmpFloat, err := strconv.ParseFloat(strSignal, 32)
		if err != nil {
			return nil, err
		}
		currentBSS.Signal = float32(tmpFloat)
		fmt.Printf("Signal: %s dBm\n", strSignal)

		bsslist = append(bsslist, currentBSS)
	}

	return bsslist, nil
}//ToDo: Create netlink based implementation (not relying on 'iw'): low priority

func (wsvc WiFiService) UpdateStateFromIw() (err error) {
	proc := exec.Command("/sbin/iw", "dev", wsvc.IfaceName, "info")
	res, err := proc.CombinedOutput()
	if err != nil {
		return errors.New(fmt.Sprintf("Error fetching wifi info: '%s'\niw output: %s", err, res))
	}


	/*
	AP
	--
	Interface wlan0
	ifindex 2
	wdev 0x1
	addr b8:27:eb:71:bb:bc
	ssid \xf0\x9f\x92\xa5\xf0\x9f\x96\xa5\xf0\x9f\x92\xa5 \xe2\x93\x85\xe2\x9e\x83\xe2\x93\x8c\xe2\x93\x83\xf0\x9f\x85\x9f\xe2\x9d\xb6
	type AP
	wiphy 0
	channel 2 (2417 MHz), width: 20 MHz, center1: 2417 MHz
	txpower 31.00 dBm

	NOT CONNECTED
	-------------
	Interface wlan0
	ifindex 2
	wdev 0x1
	addr b8:27:eb:71:bb:bc
	type managed
	wiphy 0
	channel 2 (2417 MHz), width: 20 MHz, center1: 2417 MHz
	txpower 31.00 dBm

	CONNECTED
	-----------
	Interface wlan0
	ifindex 2
	wdev 0x1
	addr b8:27:eb:71:bb:bc
	ssid WLAN-579086
	type managed
	wiphy 0
	channel 6 (2437 MHz), width: 20 MHz, center1: 2437 MHz
	txpower 31.00 dBm


	 */

	output := string(res)

	//split into BSS sections
	reSsid := regexp.MustCompile("(?m)ssid (.*)\n")
	reMode := regexp.MustCompile("(?m)type (.*)\n")
	reChannel := regexp.MustCompile("(?m)channel ([0-9]+) .*\n")

	strSsid_sub := reSsid.FindStringSubmatch(output)
	strSsid := ""
	if len(strSsid_sub) > 1 {
		unSsid,uerr := strconv.Unquote(fmt.Sprintf("\"%s\"", strSsid_sub[1]))
		if uerr == nil {
			strSsid = unSsid
		} else {
			fmt.Println("Unquote error", uerr)
		}

	}
//	fmt.Printf("SSID: %s\n", strSsid)

	strChannel_sub := reChannel.FindStringSubmatch(output)
	strChannel := "0"
	if len(strChannel_sub) > 1 {
		strChannel = strChannel_sub[1]
	}
//	fmt.Printf("Channel: %s\n", strChannel)

	strMode_sub := reMode.FindStringSubmatch(output)
	strMode := "0"
	if len(strMode_sub) > 1 {
		strMode = strMode_sub[1]
	}

	switch strings.ToLower(strMode) {
	case "ap":
		wsvc.State.Mode = pb.WiFiStateMode_AP_UP
	default:
		wsvc.State.Mode = pb.WiFiStateMode_STA_NOT_CONNECTED
	}

	if len(strSsid) > 0 {
		wsvc.State.Ssid = strSsid
		if wsvc.State.Mode == pb.WiFiStateMode_STA_NOT_CONNECTED {
			wsvc.State.Mode = pb.WiFiStateMode_STA_CONNECTED // when a SSID is present, the wifi interface is connected to an AP
		}
	}

	intCh := 0
	intCh,_ = strconv.Atoi(strChannel)
	wsvc.State.Channel = uint32(intCh)

	return nil
}

func wifiSetReg(reg string) (err error) {
	if len(reg) == 0 {
		reg = "US" //default
		log.Printf("No ISO/IEC 3166-1 alpha2 regulatory domain provided, defaulting to '%s'\n", reg)
	}

	reg = strings.ToUpper(reg)

	proc := exec.Command("/sbin/iw", "reg", "set", reg)
	err = proc.Run()
	if err != nil {
		return err
	}

	log.Printf("Notified kernel to use ISO/IEC 3166-1 alpha2 regulatory domain '%s'\n", reg)
	return nil
}

func ProcSoftKill(cmd *exec.Cmd, timeToKill time.Duration) (err error) {
	if cmd.Process == nil {
		// process already dead
		return nil
	}

	//send SIGTERM for softkill
	cmd.Process.Signal(syscall.SIGTERM)

	//we wait for process to exit or issue SIGKILL after timeout
	hasExitted := make(chan interface{},0)
	go func() {
		cmd.Process.Wait() // even if waite ends with error, the process should have died
		//fmt.Println("WAIT RES ENDED")
		close(hasExitted)
	}()

	select {
	case <- hasExitted:
		//fmt.Println("HAS EXITED")
		return nil
	case <-time.After(timeToKill):
		//timeout exceeded, send SIGKILL
		//fmt.Println("TIMEOUT, SENDING SIGKILL")
		cmd.Process.Kill()
		return nil
	}
}