{-# LANGUAGE TupleSections       #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Network.Metaverse.Login where

import Control.Concurrent

import Control.Monad
import Control.Monad.Trans

import Data.Char
import Data.Digest.MD5
import Data.Word
import Data.Int
import Data.Bits
import Data.List

import Data.Binary
import Data.Binary.Put
import Data.Binary.Get
import Data.Binary.IEEE754

import Data.Time.Clock

import Network.XmlRpc.Client
import Network.XmlRpc.Internals

import Network.Socket hiding (send, sendTo, recv, recvFrom)
import Network.Socket.ByteString

import Network.Metaverse.Utils
import Network.Metaverse.Packets

import System.Random
import System.Info.MAC

import System.IO.Unsafe -- for logging

import qualified Data.ByteString      as B
import qualified Data.ByteString.Lazy as L

{-
    Login implementation.  This implements the pure XML-RPC login sequence
    for Second Life (but not the avatar presence piece, which is left until
    later.)
-}

hashPassword :: String -> String
hashPassword = ("$1$" ++) . toHexString . hash . map (fromIntegral . ord)

loginPacket :: String -> String -> String -> String -> Value
loginPacket firstName lastName password ethmac = ValueStruct [
    ("first",           ValueString firstName),
    ("last",            ValueString lastName),
    ("passwd",          ValueString (hashPassword password)),
    ("start",           ValueString "last"),
    ("channel",         ValueString "WillowdaleBot"),
    ("version",         ValueString "0.9"),
    ("platform",        ValueString "Lin"),
    ("mac",             ValueString ethmac),
    ("agree_to_tos",    ValueBool   True),
    ("read_critical",   ValueBool   True),
    ("id0",             ValueString ethmac),
    ("last_exec_event", ValueInt 0),
    ("options",         ValueArray [
                          ValueString "inventory-root",
                          ValueString "adult_compliant"
                        ])
    ]

data MVToken = MVToken {
    tokenAgentID     :: UUID,
    tokenSimIP       :: String,
    tokenSimPort     :: Word16,
    tokenSessionID   :: UUID,
    tokenCircuitCode :: Word32,
    tokenInvHost     :: String,
    tokenInvRoot     :: UUID
    }
    deriving Show

loginXml :: String -> String -> String -> IO MVToken
loginXml firstName lastName password = do
    maybemac <- mac
    case maybemac of
        Nothing     -> error "Unable to fetch Ethernet MAC"
        Just ethmac -> do
            let msg = loginPacket firstName lastName password (show ethmac)
            ValueStruct rsp <- handleError error
                               (call loginURL loginMethod [ msg ])
            let Just (ValueString loginAns)    = lookup "login"               rsp
            when (loginAns /= "true") $ error $ "Login did not succeed: " ++ loginAns
            let Just (ValueString agentID)     = lookup "agent_id"            rsp
            let Just (ValueString simIP)       = lookup "sim_ip"              rsp
            let Just (ValueInt    simPort)     = lookup "sim_port"            rsp
            let Just (ValueString sessionID)   = lookup "session_id"          rsp
            let Just (ValueInt    circuitCode) = lookup "circuit_code"        rsp
            let Just (ValueString invHost)     = lookup "inventory_host"      rsp
            let Just (ValueArray [ ValueStruct [ (_, ValueString invRoot) ] ])
                                               = lookup "inventory-root"      rsp
            return $ MVToken { tokenAgentID     = read (agentID),
                               tokenSimIP       = simIP,
                               tokenSimPort     = fromIntegral simPort,
                               tokenSessionID   = read (sessionID),
                               tokenCircuitCode = fromIntegral circuitCode,
                               tokenInvHost     = invHost,
                               tokenInvRoot     = read (invRoot)
                             }

  where loginURL    = "http://localhost:8001/cgi-bin/login.cgi" -- stunnel endpoint
        loginMethod = "login_to_simulator"