-- "really simple distributed dns" prototype
-- pesco, 2010

import Network.Socket hiding (send)
import Control.Monad (unless, when)
import Data.Word (Word16)
import Control.Concurrent (forkIO)
import System.Posix (sleep)
import System.Time (getClockTime, toCalendarTime, formatCalendarTime)
import System.Locale (defaultTimeLocale)
import Data.List (sortBy, groupBy)
import Data.IORef


-- utility: print a timestamped message to stdout
trace :: String -> IO ()
trace str = do
    clkt <- getClockTime
    calt <- toCalendarTime clkt
    let ts = formatCalendarTime defaultTimeLocale "[%T] " calt
    putStrLn (ts ++ str)
    sleep 1
    return ()


type Port = Word16
type Host = String

type NodeID  = String
data Message = Mtun NodeID Message
             | Mstr String
    deriving (Read, Show, Eq)

data Node = Link Host Port              -- send over UDP
          | Mesh NodeID
          | Print                       -- print to local console
    deriving (Read, Show, Eq)

type CtxTbl   = [(NodeID,[Node])]       -- mesh context table

send :: IORef [Message] -> Socket -> CtxTbl -> Message -> Node -> IO ()
send rseen sock ct msg (Link h p)  = do trace (">> " ++ h ++ ":" ++ show p ++ ": " ++ show msg)
                                        hostaddr <- inet_addr h
                                        sendTo sock (show msg) (SockAddrInet (fromIntegral p) hostaddr)
                                        modifyIORef rseen (\seen -> msg:seen)
                                        return ()
send rseen sock ct msg (Mesh node) = do let ctx = maybe [] id (lookup node ct)
                                        mapM_ (send rseen sock ct (Mtun node msg)) ctx
send rseen sock ct msg Print       = trace ("** " ++ show msg)

receive :: IORef [Message] -> Socket -> CtxTbl -> Node -> Message -> IO ()
receive rseen sock ct from (Mtun to msg) = do
    let node = Mesh to
    receive rseen sock ct node msg      -- attempt to transfer
    send rseen sock ct msg node         -- broadcast within node
receive _     _    _  from msg = return ()


mainloop :: Port -> CtxTbl -> IO ()
mainloop port ct  = do
    sock <- socket AF_INET Datagram defaultProtocol -- i.e. UDP
    setSocketOption sock ReuseAddr 1
    bindSocket sock (SockAddrInet (fromIntegral port) iNADDR_ANY)
    rseen <- newIORef []
    iter sock rseen
    where
    iter sock rseen = do
        (str, n, SockAddrInet pnum addr) <- recvFrom sock 1024
        host <- inet_ntoa addr
        let msg  = read str
            port = fromIntegral pnum
            from = Link host port
        seen <- readIORef rseen
        unless (msg `elem` seen) $ do
            trace ("<< " ++ host ++ ":" ++ show port ++ ": " ++ show msg)
            receive rseen sock ct from msg
            modifyIORef rseen (\seen -> msg:seen)
        iter sock rseen

testsock srcport = do
    sock <- socket AF_INET Datagram defaultProtocol -- i.e. UDP
    setSocketOption sock ReuseAddr 1
    bindSocket sock (SockAddrInet (fromIntegral srcport) iNADDR_ANY)
    return sock

testsend sock dsthost dstport msg = do
    hostaddr <- inet_addr dsthost
    trace (">> " ++ dsthost ++ ":" ++ show dstport ++ ": " ++ show msg)
    sendTo sock (show msg) (SockAddrInet (fromIntegral dstport) hostaddr)



-- port 2001
laptop_ct = [("pesco.hh.ccc.hack", [Link "127.0.0.1" 2002   -- daddel
                                   ,Link "127.0.0.1" 2003   -- router
                                   ])
            ,("laptop.pesco.hh.ccc.hack", [Print])
            ]

-- port 2002
daddel_ct = [("pesco.hh.ccc.hack", [Link "127.0.0.1" 2001   -- laptop
                                   ,Link "127.0.0.1" 2003   -- router
                                   ])
            ,("daddel.pesco.hh.ccc.hack", [Print])
            ]

-- port 2003
router_ct = [("pesco.hh.ccc.hack", [Link "127.0.0.1" 2001   -- laptop
                                   ,Link "127.0.0.1" 2002]) -- daddel
            ,("hh.ccc.hack",       [Link "127.0.0.1" 2004]) -- turing
            ,("router.pesco.hh.ccc.hack", [Print])
            ]

-- port 2004
turing_ct = [("hh.ccc.hack", [Link "127.0.0.1" 2003]) -- pesco
            ,("ccc.hack",    [Link "127.0.0.1" 2005]) -- b1
            ,("turing.hh.ccc.hack", [Print])
            ]

-- port 2005
b1_ct = [("berlin.ccc.hack", [Link "127.0.0.1" 2006]) -- b2
        ,("ccc.hack",        [Link "127.0.0.1" 2004   -- hamburg
                             ,Mesh "berlin.ccc.hack"]) -- tunnel through berlin
        ,("b1.berlin.ccc.hack", [Print])
        ]

-- port 2006
b2_ct = [("berlin.ccc.hack", [Link "127.0.0.1" 2005]) -- b1
        ,("ccc.hack",        [Link "127.0.0.1" 2007   -- koeln
                             ,Mesh "berlin.ccc.hack"]) -- tunnel through berlin
        ,("b2.berlin.ccc.hack", [Print])
        ]

-- port 2007
k1_ct = [("ccc.hack",        [Link "127.0.0.1" 2006]) -- berlin
        ,("k1.koeln.ccc.hack", [Print])
        ]
