{-# LANGUAGE TupleSections
  #-}
{-| System specific routines for determing the MAC address and macros to help
    sort things out at compile time.
 -}


module System.Info.MAC.Fetch where

import Data.MAC

import Control.Monad
import Control.Applicative ((<$>))
import Data.List
import Data.Maybe
import System.Process
import System.Info
import System.IO
import Text.ParserCombinators.Parsec


{-| Obtain a list containing the name and MAC of all NICs.
 -}
fetchNICs                   ::  IO [(String, MAC)]
fetchNICs                    =  parser <$> i_config


{-| Run @ifconfig@ or @ipconfig@, as appropriate, capturing its output.
 -}
i_config                    ::  IO String
i_config                     =  do
  (_, o, _, h)              <-  runInteractiveCommand cmd
  outputs                   <-  hGetContents o
  seq (length outputs) (return ())
  waitForProcess h
  return outputs
 where
  cmd | os == "mingw32"      =  "ipconfig /all"
      | otherwise            =  "LANG=C ifconfig"



parser | os == "mingw32"     =  parse' "ipconfig" ipconfig
       | otherwise           =  parse' "ifconfig" ifconfig . ("\n\n" ++)


{-| Parses the output of Linux or BSD @ifconfig@.
 -}
ifconfig                    ::  Parser [(String, MAC)]
ifconfig                     =  parseNICs parseNIC_ifconfig


{-| Parses the output of Windows @ipconfig@.
 -}
ipconfig                    ::  Parser [(String, MAC)]
ipconfig                     =  parseNICs parseNIC_ipconfig


parseNIC_ifconfig           ::  Parser (Maybe (String, MAC))
parseNIC_ifconfig            =  do
  name                      <-  many1 alphaNum
  skipManyTill (satisfy (/= '\n')) markers
  char ' '
  ((name,) <$>) <$> parseMAC ':'
 where
  markers = choice $ map (try . string) [ "ether", "HWaddr" ]


parseNIC_ipconfig           ::  Parser (Maybe (String, MAC))
parseNIC_ipconfig            =  do
  name                      <-  do string "Ethernet adapter "
                                   manyTill (satisfy (/= '\n')) (char ':')
  (skipManyAnyTill . choice) [ try (nl >> nl) >> unexpected "\\r\\n\\r\\n"
                             , (try . string) "Physical Address" ]
  manyTill (satisfy (/= '\n')) (char ':')
  char ' '
  ((name,) <$>) <$> parseMAC '-'


parseNICs :: Parser (Maybe (String, MAC)) -> Parser [(String, MAC)]
parseNICs p                  =  catMaybes <$> parseNICs'
 where
  parseNICs'                 =  (skipManyAnyTill . choice)
                                          [ eof >> return []
                                          , do try (nl >> nl)
                                               nic <- p
                                               (nic:) <$> parseNICs' ]


parseMAC sepChar = maybeMAC . intercalate ":" <$> sepHex (char sepChar)


parse'                      ::  String -> Parser [t] -> String -> [t]
parse' source parser         =  either (const []) id . parse parser source


maybeMAC                    ::  String -> Maybe MAC
maybeMAC s =
  case reads s of
    [(mac, _)]              ->  Just mac
    _                       ->  Nothing


sepHex                       =  sepBy (sequence [hexDigit, hexDigit])


manyAnyTill                 ::  Parser Char -> Parser String
manyAnyTill                  =  manyTill anyChar


skipManyTill                ::  Parser a -> Parser b -> Parser b
skipManyTill p end           =  choice [try end, p >> skipManyTill p end]


skipManyAnyTill             ::  Parser a -> Parser a
skipManyAnyTill              =  skipManyTill anyChar


nl                           =  many (char '\r') >> char '\n'


