{-# LANGUAGE ExplicitForAll    #-}
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}


{-|
  The memo function remembers the last argument passed and the last result.
  When a new value is passed, it compares it with the previous value and if they match,
  the previous result is returned from memory. This is useful when computing Html
  is expensive and benefits from memoization.
-}


module Shpadoinkle.Html.Memo (
  -- * Variadic Class
  Memo (..)
  -- * Uniadic
  , memo1,  memo2,  memo3,  memo4,  memo5,  memo6,  memo7,  memo8,  memo9
  -- * Custom Equality
  , memo1', memo2', memo3', memo4', memo5', memo6', memo7', memo8', memo9'
  ) where


import           Data.IORef
import           System.IO.Unsafe


{-|
   Variadic ditzy memoizer that only recalls at most one thing.

   prop> memo = id
-}
class Memo f where memo :: f -> f
instance                   Eq a                                                  => Memo (a -> b)                                         where memo = memo1
instance {-# OVERLAPS #-} (Eq a, Eq b)                                           => Memo (a -> b -> c)                                    where memo = memo2
instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c)                                     => Memo (a -> b -> c -> d)                               where memo = memo3
instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d)                               => Memo (a -> b -> c -> d -> e)                          where memo = memo4
instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e)                         => Memo (a -> b -> c -> d -> e -> f)                     where memo = memo5
instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f)                   => Memo (a -> b -> c -> d -> e -> f -> g)                where memo = memo6
instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g)             => Memo (a -> b -> c -> d -> e -> f -> g -> h)           where memo = memo7
instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h)       => Memo (a -> b -> c -> d -> e -> f -> g -> h -> i)      where memo = memo8
instance {-# OVERLAPS #-} (Eq a, Eq b, Eq c, Eq d, Eq e, Eq f, Eq g, Eq h, Eq i) => Memo (a -> b -> c -> d -> e -> f -> g -> h -> i -> j) where memo = memo9

memo1' e f a = unsafePerformIO $ do
  r <- newIORef (a, f a)
  return $ applyEq e f r a
memo2' e f a b               = memo1' e (uncurry  f) (a,b)
memo3' e f a b c             = memo1' e (uncurry2 f) (a,b,c)
memo4' e f a b c d           = memo1' e (uncurry3 f) (a,b,c,d)
memo5' e f a b c d g         = memo1' e (uncurry4 f) (a,b,c,d,g)
memo6' e f a b c d g h       = memo1' e (uncurry5 f) (a,b,c,d,g,h)
memo7' e f a b c d g h i     = memo1' e (uncurry6 f) (a,b,c,d,g,h,i)
memo8' e f a b c d g h i j   = memo1' e (uncurry7 f) (a,b,c,d,g,h,i,j)
memo9' e f a b c d g h i j k = memo1' e (uncurry8 f) (a,b,c,d,g,h,i,j,k)

memo1 :: Eq a =>                                                                 (a -> b)                                         -> a -> b
memo2 :: Eq a => Eq b =>                                                         (a -> b -> c)                                    -> a -> b -> c
memo3 :: Eq a => Eq b => Eq c =>                                                 (a -> b -> c -> d)                               -> a -> b -> c -> d
memo4 :: Eq a => Eq b => Eq c => Eq d =>                                         (a -> b -> c -> d -> e)                          -> a -> b -> c -> d -> e
memo5 :: Eq a => Eq b => Eq c => Eq d => Eq e =>                                 (a -> b -> c -> d -> e -> f)                     -> a -> b -> c -> d -> e -> f
memo6 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f =>                         (a -> b -> c -> d -> e -> f -> g)                -> a -> b -> c -> d -> e -> f -> g
memo7 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f => Eq g =>                 (a -> b -> c -> d -> e -> f -> g -> h)           -> a -> b -> c -> d -> e -> f -> g -> h
memo8 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f => Eq g => Eq h =>         (a -> b -> c -> d -> e -> f -> g -> h -> i)      -> a -> b -> c -> d -> e -> f -> g -> h -> i
memo9 :: Eq a => Eq b => Eq c => Eq d => Eq e => Eq f => Eq g => Eq h => Eq i => (a -> b -> c -> d -> e -> f -> g -> h -> i -> j) -> a -> b -> c -> d -> e -> f -> g -> h -> i -> j

memo1 = memo1' (/=)
memo2 = memo2' (/=)
memo3 = memo3' (/=)
memo4 = memo4' (/=)
memo5 = memo5' (/=)
memo6 = memo6' (/=)
memo7 = memo7' (/=)
memo8 = memo8' (/=)
memo9 = memo9' (/=)

uncurry2 f (a,b,c)             = f a b c
uncurry3 f (a,b,c,d)           = f a b c d
uncurry4 f (a,b,c,d,e)         = f a b c d e
uncurry5 f (a,b,c,d,e,g)       = f a b c d e g
uncurry6 f (a,b,c,d,e,g,h)     = f a b c d e g h
uncurry7 f (a,b,c,d,e,g,h,i)   = f a b c d e g h i
uncurry8 f (a,b,c,d,e,g,h,i,j) = f a b c d e g h i j

applyEq :: (a -> a -> Bool) -> (a -> b) -> IORef (a, b) -> a -> b
applyEq e f r a = unsafePerformIO $ do
  (a', b) <- readIORef r
  if not $ e a' a then return b else do
    let b' = f a
    writeIORef r (a', b')
    return b'