{-# OPTIONS -fglasgow-exts #-}

-- | WLAN Commands
module HNM.WLAN where

import IO
import Data.List
import System.IO
import System.Exit
import System.Process
import System.IO.Unsafe
import Text.Regex.Posix
import System.Posix.User
import System.Environment
import Control.Concurrent
import Control.Monad.State

ignore :: Monad m  m a   m ()
ignore _ = return ()

assu :: IO a  IO a  IO a
assu a b = do
  root  userIsRoot
  case root of
    True   a
    False  b

userIsRoot :: IO Bool
userIsRoot = return . (0 ==) =<< getRealUserID

run :: String  IO String
run cmd = do
  (i,o,e,h)  runInteractiveCommand cmd
  hGetContents o >>= \s  waitForProcess h >> return s

type Key       = String
type SSID      = String
type MAC       = String
type Interface = String
type Cell      = String
type IP        = String
type AP        = String
type Driver    = String

getDefaultInterface :: IO Interface
getDefaultInterface = return . head =<< getInterfaces

getInterfaces :: IO [Interface]
getInterfaces = do
  cat  run "cat /proc/net/wireless"
  return $ filter (/= "") $ map (matching " *(.*[0-9]+):") $ lines cat

scan :: Interface  IO [Cell]
scan interface = return . drop 1 . getCells =<< run ("iwlist " ++ interface ++ " scan")

getCells :: String  [Cell]
getCells block = map unwords $ groupBy (const $ (/=) "Cell") $ words block


-- | WLAN encryption values
data Encryption = None        -- ^ no  encryption
                | WEP         -- ^ WEP encryption
                | WPA Version -- ^ WPA encryption
                deriving (Show, Read, Eq, Ord)

-- | WPA version
data Version = One
             | Two
             deriving (Show, Read, Eq, Ord)

type Quality = Int
type Unit = (MAC,Quality)

data CWLAN = CWLAN
             {
               cessid   :: SSID,
               cencrypt :: Encryption,
               ccell    :: [Unit]
             }
             deriving (Show, Read, Eq, Ord)

meanQuality :: [Quality]  Quality
meanQuality = round . mean . (map (fromInteger . toInteger))

mean :: Fractional a  [a]  a
mean [] = 0
mean (x:xs) = mean' (x:xs) 0 0
  where
    mean' []     s c = s / c
    mean' (x:xs) s c = mean' xs (s+x) (c+1)

data WLAN = WLAN
            {
              essid      :: SSID,
              quality    :: Quality,
              encryption :: Encryption, 
              mac        :: MAC
            }
            deriving (Show, Read, Eq, Ord)

compact :: [WLAN]  [CWLAN]
compact []     = []
compact (w:ws) = CWLAN cid cen ceq_us : compact cne
  where
    cid = essid w
    cen = encryption w
    (ceq,cne) = partition ((cid ==) . essid) ws
    ceq_us = macqual w : map macqual ceq
    macqual = \ew  (mac ew, quality ew)

cellToWLAN :: Cell  WLAN
cellToWLAN c = WLAN (getEssid c) (getQuality c) (getEncrypt c) (getMac c)
  where
    getEssid   = matching "ESSID:\"(.*)\""
    getMac     = matching "Address: (.{17})"
    getQuality = \c  case matching "Quality=(.*)/100" c of { ""  0; q  read q :: Int }
    getEncrypt = \c  case matching "Encryption key:(on|off)" c of
                        "on"   case matching "(WPA.)" c of
                                  "WPA "  WPA One
                                  "WPA2"  WPA Two
                                  _       WEP
                        _      None

-- | given an interface, returns a list of wireless lans 
getWLANs :: Interface  IO [WLAN]
getWLANs interface = return . {-debug . -}map cellToWLAN =<< scan interface

getLocalIP :: Interface  IO IP
getLocalIP interface = return . matching "inet addr:(.+)  ." =<< run ("ifconfig " ++ interface)

getESSID :: Interface  IO SSID
getESSID interface = return . matching "ESSID:\"(.+)\"" =<< run ("iwconfig " ++ interface)

getAP :: Interface  IO AP
getAP interface = return . matching "Access Point: (.+)\n" =<< run ("iwconfig " ++ interface)

data ConnectionStatus = NotConnected | Connected IP SSID
                        deriving (Show, Read, Eq, Ord)

getConnectionStatus :: Interface  IO ConnectionStatus
getConnectionStatus interface = do
  ip  getLocalIP interface
  case ip of
    []  return NotConnected
    ip  do
    ap  getAP interface
    case (debug ap) of
      "Not-Associated   "  return NotConnected
      ap                   return . Connected ip =<< getESSID interface

matching :: String  String  String
matching = \pattern info  case info =~ pattern of { [[a,b]]  b; [[a,b],[c,d]]  d; _  "" }

debug :: Show a  a  a
debug a = unsafePerformIO (print a >> return a)

exec :: String  IO ()
exec cmd = runCommand cmd >>= waitForProcess >> return ()

pcom :: String  [String]  IO ()
pcom c = exec . unwords . (c:)

modprobe :: [String]  IO ()
modprobe = pcom "modprobe"

iwconfig :: [String]  IO ()
iwconfig = pcom "iwconfig"

ifconfig :: [String]  IO ()
ifconfig = pcom "ifconfig"

dhclient :: [String]  IO ()
dhclient = pcom "dhclient"

wpa_supplicant :: [String]  IO ()
wpa_supplicant = pcom "wpa_supplicant"

initHardware :: Driver  Interface  IO ()
initHardware driver interface = do
  disconnect interface
  deactivate driver interface
  activate driver interface
  threadDelay 2000000
  exec $ "iwlist " ++ interface ++ " scan"
  return ()

deactivate :: Driver  Interface  IO ()
deactivate driver interface = do
  ifconfig [interface, "down"]
  modprobe ["-r", driver]

activate :: Driver  Interface  IO ()
activate driver interface = do
  modprobe [driver]
  ifconfig [interface, "up"]

disconnect :: Interface  IO ()
disconnect interface = do
  exec $ "killall dhclient"
  exec $ "killall wpa_supplicant"
  ifconfig [interface, "down"]

connectFree :: Interface  SSID  IO ()
connectFree interface ssid = do
  connect interface (Wireless ssid Nothing)

connect :: Interface  ConnectionSetting  IO ()
connect interface (Wireless ssid enc) = do
  disconnect interface
  ifconfig [interface, "up"]
  case enc of
    Nothing         iwconfig [interface, "essid", ssid]
    Just (WEP,   key)  iwconfig [interface, "essid", ssid, "key", "s:" ++ key]
    Just (WPA _, key)  wpaconfig [interface, ssid, key]
  dhclient [interface]

wpaconfig :: [String]  IO ()
wpaconfig [interface, ssid, key] = do
  h  openFile wpa_temp WriteMode
  l h $ "network={"
  l h $ ""
  l h $ "  ssid=\"" ++ ssid ++ "\""
  l h $ "  key_mgmt=WPA-PSK"
  l h $ "  psk=\"" ++ key ++ "\""
  l h $ ""
  l h $ "}"
  hClose h
  wpa_supplicant ["-B", "-c"++wpa_temp, "-i"++interface]
  threadDelay 2000000
  where
    l = hPutStrLn 

wpa_temp :: FilePath
wpa_temp = "/tmp/wpatemp.conf"

data ConnectionSetting = Wireless SSID (Maybe (Encryption,Key))
                       deriving (Show, Read, Eq, Ord)