{-# LANGUAGE OverloadedStrings #-}
module Waldo.Story (
    selectStory
  , knapsackSizer
  , Story(..)
  , StoryGuard, StoryOption
  , isIn, browserIs, osIs, netSpeedIs, orgIs, orgMatch, ispIs
  , refererDomainIs
  , pdTestJustIs
  , giveThem, allocate
  ) where

import Data.Maybe
import Data.List
import Control.Monad.Reader
import Data.ByteString.Char8 (ByteString)
import qualified Data.Text as T
import Network.URI
import Text.Regex.TDFA ((=~))

import Waldo.Stalk
import Waldo.Script

type StoryGuard = ReaderT PersonalData Maybe ()
type StoryOption = ReaderT PersonalData Maybe Story

data Story = 
    StoryGoto {
        storyGoto :: T.Text
      }
  | Story {
         storyAltText   :: T.Text
       , storyPanelSets :: [PanelSizes]
       , storyPadX :: Int
       , storyPadY :: Int
       , storyName :: T.Text
       }
  deriving (Show)

selectStory :: ((Int, Int) -> Story -> Maybe Script) -> Script -> [StoryOption] -> PersonalData -> IO Script
selectStory sizer d storyGens pd = do
  --print storyGens
  -- generate stories
  let stories = catMaybes $ map (flip runReaderT pd) storyGens
  --print stories
  -- size the selected scripts
  let scripts = mapMaybe doSize stories
  --print scripts
  -- Get our script, either the default or a selected one.
  return $ fromMaybe d $ listToMaybe scripts
  where
    doSize (s@Story {}) = sizer (pdScreen pd) s
    doSize (StoryGoto t) = Just (ScriptTo t)

knapsackSizer :: Int -> (Int, Int) -> Story -> Maybe Script
knapsackSizer sitePad (w, h) s =
    -- Get the first entry if there is one, the smallest if none of them fit.
    listToMaybe $ (sortCorrectDir sizeLimited) ++ (take 1 areaSortedSized)
  where
    -- selected sort dir by what we know about the screen
    sortCorrectDir =
      if (h > 0) && (w > 0)
      then reverse
      else id
    -- The fitting scripts
    sizeLimited = fitWidth $ fitHeight $ areaSortedSized
    -- sort by area
    areaSortedSized = areaSort allScripts
    -- Of all scripts
    allScripts = do
      combo <- mapM id $ storyPanelSets s
      return $ mkScript (storyName s) (storyAltText s) combo
    areaSort = sortBy (\a b -> compare (scriptArea a) (scriptArea b))
    scriptArea scr = (sHeight scr) * (sWidth scr)
    fitHeight scripts =
      if h > 0
      then filter (\scr -> h > (sHeight scr+storyPadY s+sitePad)) scripts
      else scripts
    fitWidth scripts =
      if w > 0
      then filter (\scr -> w > (sWidth scr+storyPadX s+sitePad)) scripts
      else scripts

refererDomainIs :: String -> StoryGuard
refererDomainIs d =
  asks pdRefURI >>= guard . fromMaybe False . fmap ((isSuffixOf d) . uriRegName) . join . fmap uriAuthority

--refererMatches :: 

pdTestJustIs :: Eq a => (PersonalData -> Maybe a) -> a -> StoryGuard
pdTestJustIs g v = asks g >>= guard . maybe False (v==)

isIn :: ByteString -> StoryGuard
isIn locName = asks pdLocal >>= guard . (not . null . (filter (locName==)))

browserIs :: Browser -> StoryGuard
browserIs b = asks pdBrowser >>= guard . (maybe False (b==))

osIs :: OS -> StoryGuard
osIs os = asks pdOS >>= guard . (maybe False (os==))

netSpeedIs :: NetSpeed -> StoryGuard
netSpeedIs ns = asks pdNetSpeed >>= guard . (maybe False (ns==))

orgIs :: ByteString -> StoryGuard
orgIs o = asks pdOrg >>= guard . (maybe False (o==))

orgMatch :: ByteString -> StoryGuard
orgMatch o = asks pdOrg >>= guard . (maybe False (flip (=~) o))

ispIs :: ByteString -> StoryGuard
ispIs i = asks pdISP >>= guard . (maybe False (i==))

allocate :: MonadPlus m => m () -> a -> m a
allocate cnd r = cnd >> return r

giveThem :: MonadPlus m => m () -> m a -> m a
giveThem = (>>)