    -- | Karma
    module Lambdabot.Plugin.Social.Karma (karmaPlugin) where

    import Lambdabot.Compat.FreenodeNick
    import Lambdabot.Plugin
    import qualified Lambdabot.Util.NickEq as E

    import Data.Char
    import Data.List
    import qualified Data.Map as M
    import Data.Maybe
    import Text.Printf

    type KarmaState = M.Map Nick Integer
    type Karma = ModuleT KarmaState LB

    karmaPlugin :: Module KarmaState
    karmaPlugin = newModule
        { moduleCmds = return
            [ (command "karma")
                { help = say "karma <polynick>. Return a person's karma value"
                , process = \rest -> withMsg $ \msg -> do
                    sender <- getSender
                    tellKarma sender $ case words rest of
                        []       -> E.mononickToPolynick sender
                        (nick:_) -> E.readPolynick msg nick

                }
            , (command "karma+")
                { help = say "karma+ <nick>. Increment someone's karma"
                , process = doCmd 1
                }
            , (command "karma-")
                { help = say "karma- <nick>. Decrement someone's karma"
                , process = doCmd (-1)
                }
            , (command "karma-all")
                { help = say "karma-all. List all karma"
                , process = const listKarma
                }
            ]

        , moduleDefState  = return $ M.empty
        , moduleSerialize = Just freenodeNickMapSerial

        -- nick++($| )
        , contextual = \text -> withMsg $ \_ -> do
            sender <- getSender

            let ws          = words text
                decs        = match "--"
                incs        = match "++"
                match m     = mapM readNick . filter okay . map (reverse . drop 2)
                            . filter (isPrefixOf m) . map reverse $ ws
                okay x      = not (elem x badNicks || any (`isPrefixOf` x) badPrefixes)
                -- Special cases.  Ignore the null nick.  C must also be ignored
                -- because C++ and C-- are languages.
                badNicks    = ["", "C", "c", "notepad"]
                -- More special cases, to ignore Perl code.
                badPrefixes = ["$", "@", "%"]

            mapM_ (changeKarma (-1) sender) =<< decs
            mapM_ (changeKarma   1  sender) =<< incs
        }

    doCmd :: Integer -> String -> Cmd Karma ()
    doCmd dk rest = do
        sender <- getSender
        case words rest of
          []       -> say "usage @karma(+|-) nick"
          (nick:_) -> do
              nick' <- readNick nick
              changeKarma dk sender nick' >>= say

    ------------------------------------------------------------------------

    tellKarma :: Nick -> E.Polynick -> Cmd Karma ()
    tellKarma sender nick = do
        lookup' <- lb E.lookupMononickMap
        karma <- (sum . map snd . lookup' nick) `fmap` readMS
        nickStr <- withMsg (return . flip E.showPolynick nick)
        say $ concat [if E.mononickToPolynick sender == nick then "You have" else nickStr ++ " has"
                       ," a karma of "
                       ,show karma]

    listKarma :: Cmd Karma ()
    listKarma = do
        ks <- M.toList `fmap` readMS
        let ks' = sortBy (\(_,e) (_,e') -> e' `compare` e) ks
        flip mapM_ ks' $ \(k,e) -> do
            k' <- showNick k
            say (printf " %-20s %4d" k' e)

    changeKarma :: Integer -> Nick -> Nick -> Cmd Karma String
    changeKarma km sender nick
        | map toLower (nName nick) == "java" && km > 0 = do
            me <- getLambdabotName
            changeKarma (-km) me sender
        | sender == nick = return "You can't change your own karma, silly."
        | otherwise      = do
            nickStr <- showNick nick
            withMS $ \fm write -> do
                let fm' = M.insertWith (+) nick km fm
                let karma = fromMaybe 0 $ M.lookup nick fm'
                write fm'
                return (fmt nickStr km (show karma))
            where
                fmt n v k | v < 0     = n ++ "'s karma lowered to "    ++ k ++ "."
                          | v == 0    = n ++ "'s karma unchanged at "  ++ k ++ "."
                          | otherwise = n ++ "'s karma raised to "     ++ k ++ "."