{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts           #-}

module Analytics 
  ( runAnalytics 
  , analyzeBoard
  ) where

import Data.Aeson (eitherDecode, FromJSON) 
import Data.Either (rights, lefts)
import Data.Typeable
import Data.List
import Data.Maybe
import Text.Format

import Control.Monad.IO.Class (liftIO, MonadIO)
import Control.Monad.Catch
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Applicative
import Control.Concurrent.ParallelIO
import Control.Concurrent (threadDelay)

import qualified Data.Text as T 
import qualified Data.Text.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as C (pack)

import GHC.Generics
import Debug.Trace
import Network.HTTP (simpleHTTP, getRequest, getResponseBody)
import Hakyll.Web.Html (stripTags)

import qualified Post as P
import qualified Logging as Log
import Types

newtype Analytics a = Analytics { unAnalytics :: ReaderT Log.Config (LoggingT IO) a }
  deriving (Functor, Applicative, Monad, MonadIO, MonadLogger, MonadReader Log.Config)


runAnalytics :: Analytics a -> IO a
runAnalytics a = runStdoutLoggingT $ runReaderT (unAnalytics a) (Log.mkConfig "Analytics")

bmemeCount :: Meme -> Board -> Int
bmemeCount meme board = sum $ map (memeCount meme) board

postCount :: Thread -> Int
postCount x = length $ posts x 

memeCount :: Meme -> Thread -> Int
memeCount meme thread = sum $ (map wordCount (alias meme)) <*>  (posts thread)
  where wordCount meme post = T.count (T.toLower meme) (T.toLower (fromMaybe "" (P.com post)))

catToThreadNo :: Catalog -> [ThreadID]
catToThreadNo cat = map no (concatMap threads cat)

dubs :: Board -> [Int]
dubs b = filter isDubs $ map P.no (concatMap posts b)
  where 
    isDubs :: Int -> Bool
    isDubs x = case digs x of 
      x:y:xs -> x == y
      x      -> error $ "Chan.dubs malformed post number" ++ (show x )
 
    -- convert number to list of digits 
    digs :: Integral x => x -> [x]
    digs 0 = []
    digs x = x `mod` 10 : digs (x `div` 10)

containsMeme :: T.Text -> P.Post -> Bool
containsMeme meme post 
 = case (P.com post) of 
   Nothing -> False
   Just com -> T.isInfixOf (T.toLower meme) (T.toLower com)

isAnon :: P.Post -> Bool 
isAnon p 
  = case (P.name p) of 
    Nothing -> False
    Just n  -> n == "Anonymous"

isTripcode :: P.Post -> Bool
isTripcode p 
 = case (P.name p) of 
   Nothing -> False 
   Just n  -> T.isInfixOf " !!" n

displayPost :: P.Post -> (ThreadID, String)
displayPost post = (P.no post, stripTags $ T.unpack (fromMaybe "" (P.com post)))

analyzeBoard :: (MonadReader Log.Config m, MonadLogger m) => Board -> m () 
analyzeBoard board = do 
    recordMemes board 
    recordCount board
    recordPosts board
    recordDubs board
    recordTripcodes board

recordMemes :: (MonadReader Log.Config m, MonadLogger m) => Board -> m () 
recordMemes board = do
    Log.info "Top Memes:"
    Log.info . show $ map (\x -> (x, bmemeCount x board)) memes

recordCount :: (MonadReader Log.Config m, MonadLogger m) => Board -> m ()
recordCount board = do
    Log.info "Post Counts"
    let pcs = map postCount board 
    Log.info $ show  pcs
    Log.info $ format "Average post count: {0}." [show (realToFrac (sum pcs) / (genericLength pcs))]

recordPosts :: (MonadReader Log.Config m, MonadLogger m) => Board -> m ()
recordPosts board = do 
    let haskellPosts = filter (containsMeme "haskell") (concatMap posts board) 
    Log.info $ format "Found {0} haskell posts:\n{1} " [show (length haskellPosts), concatMap (show . displayPost) haskellPosts]

recordDubs :: (MonadReader Log.Config m, MonadLogger m) => Board -> m ()
recordDubs board = do 
    let ds = map show (dubs board)
    Log.info $ format "Found {0} dubs in {1} posts: " [(show (length ds)), show $ length (concatMap posts board)]
    Log.info $ unwords ds

recordTripcodes :: (MonadReader Log.Config m, MonadLogger m) => Board -> m ()
recordTripcodes b = do
  let ps   = concatMap posts b 
  let anons    = filter isAnon ps
  let tripcodes = filter isTripcode ps
  let tripcodePercent = ((fromIntegral $ length tripcodes) / (fromIntegral $length ps))
  Log.info $ format "Found {0} Anonymous posts and {1} tripcode posts out of {2} total posts." [show $ length anons, show $ length tripcodes, show $ length ps]
  Log.info $ format "That's {0}" [status tripcodePercent]
     where 
      status :: (Real a, Fractional a)=> a -> String 
      status tripcodePercent 
        | tripcodePercent == 0 = "The Best Day Ever!"
        | tripcodePercent > 0.1 = "Terrible."
        | otherwise = "Same old, same old.."
    

showT :: Show a => a -> T.Text
showT = T.pack . show  

