module S03 (
  -- * Exercise 2
  rotate,
  -- * Exercise 3
  int2let, let2int, shift, encode,
  -- * Exercise 4
  freqs,
  -- * Exercise 5
  chisqr,
  -- * Exercise 6
  crack
  ) where
import Data.Char

{-| This is the approximate letter frequency list for 'a' to 'z'
in the English language. -}
tableEn :: [Double]
tableEn = [8.2,1.5,2.8,4.3,12.7,2.2,2.0,6.1,7.0,
           0.2, 0.8,4.0,2.4,6.7,7.5,1.9,0.1,6.0,
           6.3,9.1,2.8,1.0,2.4,0.2,2.0,0.1]

{-| Transform an integer into a lowercase letter (where 0 is 'a' and
25 is 'z'). -}
int2let :: Int -> Char
int2let i = chr (ord 'a' + i)

{-| Transform a lowercase letter into an integer. -}
let2int :: Char -> Int
let2int c = ord c - ord 'a'

{-| Shift a lowercase letter by a given number of positions in
the alphabet. -}
shift :: Int -> Char -> Char
shift n c | isLower c = int2let ((let2int c + n) `mod` 26)
          | otherwise = c

{-| Shift all lowercase letters in the input string by a given
number of positions in the alphabet, thereby applying Caesar's
cipher. -}
encode :: Int -> String -> String
encode k = map (shift k)

{-| Ratio of two integers after converting them to floats. -}
ratio :: Int -> Int -> Double
ratio m n = (fromIntegral m * 100) / fromIntegral n

count :: Char -> String -> Int
count x xs = length [x' | x' <- xs, x == x']

freqs :: String -> [Double]
freqs xs = [count x xs `ratio` n | x <- ['a'..'z']]
  where n = length (filter isLower xs)

chisqr :: [Double] -> [Double] -> Double
chisqr os es = sum [((o - e)^2)/e | (o,e) <- zip os es]

rotate :: Int -> [a] -> [a]
rotate n xs = drop n xs ++ take n xs

crack :: String -> String
crack xs = encode (-key) xs
  where
    key    = head [i | (chi,i) <- zip chitab [0..25], chi == minimum chitab]
    chitab = [chisqr (rotate n table') tableEn | n <- [0..25]]
    table' = freqs xs