import Data.List (groupBy, sortBy, intercalate)

type Item = (Int, String)

type ItemList = [Item]

type ItemGroups = [ItemList]

type RankItem a = (a, Int, String)

type RankItemList a = [RankItem a]


prepare :: ItemList -> ItemGroups
prepare = groupBy gf . sortBy (flip compare)
  where
    gf (a, _) (b, _) = a == b


rank
  :: Num a
  => a -> Item -> RankItem a
rank n (a, b) = (n, a, b)


standard, modified, dense, ordinal :: ItemGroups -> RankItemList Int
standard = ms 1
  where
    ms _ [] = []
    ms n (x:xs) = (rank n <$> x) ++ ms (n + length x) xs

modified = md 1
  where
    md _ [] = []
    md n (x:xs) =
      let l = length x
          nl = n + l
          nl1 = nl - 1
      in (rank nl1 <$> x) ++ md (n + l) xs

dense = md 1
  where
    md _ [] = []
    md n (x:xs) = map (rank n) x ++ md (n + 1) xs

ordinal = zipWith rank [1 ..] . concat

fractional :: ItemGroups -> RankItemList Double
fractional = mf 1.0
  where
    mf _ [] = []
    mf n (x:xs) =
      let l = length x
          o = take l [n ..]
          ld = fromIntegral l
          a = sum o / ld
      in map (rank a) x ++ mf (n + ld) xs


test :: ItemGroups
test =
  prepare
    [ (44, "Solomon")
    , (42, "Jason")
    , (42, "Errol")
    , (41, "Garry")
    , (41, "Bernard")
    , (41, "Barry")
    , (39, "Stephen")
    ]


nicePrint
  :: Show a
  => String -> RankItemList a -> IO ()
nicePrint xs items = do
  putStrLn xs
  mapM_ np items
  putStr "\n"
  where
    np (a, b, c) = putStrLn $ intercalate "\t" [show a, show b, c]

main :: IO ()
main = do
  nicePrint "Standard:" $ standard test
  nicePrint "Modified:" $ modified test
  nicePrint "Dense:" $ dense test
  nicePrint "Ordinal:" $ ordinal test
  nicePrint "Fractional:" $ fractional test