```module Penny.Cabin.Allocate (
Allocation,
allocation,
unAllocation,
allocate) where

import qualified Data.Foldable as F
import qualified Data.Traversable as T
import qualified Data.Map as M

newtype Allocation = Allocation { unAllocation :: Int }
deriving (Show, Eq, Ord)

allocation :: Int -> Allocation
allocation i =
if i < 1
then error "Allocations must be at least 1"
else Allocation i

-- | Divide up a whole number proportionally into several parts. The
-- sum of the parts is guaranteed to add up to original number.
allocate ::
Ord k
=> M.Map k Allocation
-- ^ Maps arbitrary unique values to the allocations, so that you
-- can look up the corresponding allocations.

-> Int
-- ^ Allocated values will add up to this number.

-> M.Map k Int
-- ^ The given number, allocated into proportional parts.

allocate m t = let
tot = F.sum . fmap (toDouble . unAllocation) \$ m
ratios = fmap ((/tot) . toDouble . unAllocation) m
rounded = fmap (round . (* (toDouble t))) ratios
toDouble = fromIntegral :: Int -> Double

in if M.null m
then M.empty

Ord k
=> M.Map k Int
-> Int
-> M.Map k Int
wsInts = fmap fromIntegral ws
diff = (fromIntegral w) - F.sum wsInts in
if M.null ws
then M.empty
else if diff == 0
then ws
else let
ws' = St.evalState (T.mapM adjustMap ws) diff

-- | The state is the target number minus the current actual total.
adjustMap :: Int -> St.State Int Int