{--
  Last changed Time-stamp: <2001-12-11 20:15:43 xtof>
  simple echo client
  you need Meurig Sage Regular expression library for Haskell
  available from: http://www.dcs.gla.ac.uk/~meurig/regexp/
  compile and link with (ghc-5.02.1):
  ghc -syslib net -syslib data -syslib Regexp -c -o EchoClient.o EchoClient.hs
  ghc -syslib net -syslib data -syslib Regexp -o EchoClient EchoClient.o
--}
module Main (main) where

-- pull in useful modules (similar to #include in C)
import IO
import System
import SocketPrim
import BSD
{--
  Meurig Sage's regexp library is not part of the ghc-5.02.1 libraries
  get the library for Haskell 98 from http://www.dcs.gla.ac.uk/~meurig/regexp/
  follow the instructions how to build the library
  if you want to use the library with ghc's -syslib option,
  you have to add it to your ghc distribution using ghc-pkg
  otherwise use the ghc option
  -iPATH_TO_REGEXP_HI_FILES for compiling and
  -LPATH_TO_REGEXP_LIBRARY -lRegexp for linking
--}
import Regexp

-- declare datatype Consts (similar to a typedef struct in C)
data Consts
    = Consts{ address :: String,
              portnum :: Int }

-- initialize defaults an instance of Consts
defaults :: Consts
defaults
    = Consts { address = "localhost",
               portnum = 7 }

{--
  i found the following on the Programming Language Examples Alike Cookbook
  (PLEAC homepage) http://pleac.sourceforge.net/
--}
(=~) :: String -> String -> Bool
(=~) s re = let matchresult = searchS re [] s in
			      matchedAny matchresult

-- check with regex if argument is a number
isPortNumber :: String -> Bool
isPortNumber s
    | s =~ "^\\d+$"  = True
    | otherwise      = False

-- check with regex if arument is a hostname
isAddress :: String -> Bool
isAddress s
    | s =~ "^[a-z.]+$"  = True
    | otherwise         = False

{--
  if portnumber was given on command-line return it
  otherwise return portnumber from defaults
--}
extractPort :: [String] -> IO Int
extractPort args
    =
      case [ a | a <- args, isPortNumber a ] of
					     ([] ) -> return (portnum defaults)
					     (x:_) -> return ((read x)::Int)

{--
  if hostname was given on command-line return it
  otherwise return hostname from defaults
--}
extractAddr :: [String] -> IO String
extractAddr args
    =
      case [ a | a <- args, isAddress a ] of
					  ([] ) -> return (address defaults)
					  (x:_) -> return x     

-- process command-line arguments
processCmlArgs :: IO (String, Int)
processCmlArgs
    = do
      args <- getArgs
      port <- extractPort args
      addr <- extractAddr args
      return (addr, port)

-- translate symbolic hostname to packed IP address
gethostbyname:: String -> IO HostAddress
gethostbyname hostname
    = do
      hostentry <- getHostByName hostname
      return (hostAddress hostentry)

{--
  retrive the value of the TCP protocol number
  convert symbolic hostname into a packt IP address
  create a strem-style Internet-domain socket (local communication endpoint)
  connect to the remote host (remote communication endpoint)
  convert socket to a handle
--}
connectto :: String -> Int -> IO (Handle, Socket)
connectto hostname port
    = do
      protocolnum <- getProtocolNumber "tcp"
      ipaddress <- gethostbyname hostname
      sock <- socket AF_INET Stream protocolnum
      connect sock (SockAddrInet (fromIntegral port) ipaddress)
      handle <- socketToHandle sock ReadWriteMode
      return (handle, sock)

-- close handle and socket
disconnect :: Handle -> Socket -> IO ()
disconnect handle sock
    = do
      hClose handle
      sClose sock

-- loop until empty line is given
loop :: Handle -> IO ()
loop handle
    = do
      l <- hGetLine stdin
      sendLine l handle
      r <- reciveLine handle
      case r of
	     ([])  -> return ()
	     (_:_) -> do
		      hPutStrLn stdout r
		      loop handle

-- send a line to the echo server
sendLine :: String -> Handle -> IO ()
sendLine string handle
    = do
      hPutStrLn handle string

-- recive a line from the echo server
reciveLine :: Handle -> IO String
reciveLine handle
    = do
      l <- hGetLine handle
      return l

main :: IO ()
main
    = do
      (hostname, port) <- processCmlArgs
      (handle, sock) <- connectto hostname port
      hSetBuffering handle LineBuffering
      loop handle
      disconnect handle sock

-- End of file