module Enigma
  ( Wiring,
    Plugboard,
    Rotor (..),
    OrientedRotor (..),
    EnigmaConfig (..),
    cipher,
    normalize,
    getWiring,
    calculateIC,
    combinations,
    topLetters,
    ringSettings,
    rotorIDs,
  )
where

import Data.Char (isAlpha, toUpper)
import Data.List (foldl', subsequences)
import Enigma.Internal
    ( cipherChar,
      freqs,
      ic,
      step,
      EnigmaConfig(..),
      OrientedRotor(..),
      Plugboard,
      Rotor(..),
      Wiring )
import Enigma.Static (getWiring)
import GHC.Unicode (isAscii)

-- | Extract top letters from given EnigmaConfig
topLetters :: EnigmaConfig -> String
topLetters cfg = [topLetter rtr | rtr <- rotors cfg]

ringSettings :: EnigmaConfig -> String
ringSettings cfg = [ringSetting rts | rts <- rotors cfg]

rotorIDs :: EnigmaConfig -> [String]
rotorIDs cfg = [rotId $ rotor rts | rts <- rotors cfg]

-- |
--   Encipher a message using cfg, the starting configuration of
--   the Enigma machine
cipher :: EnigmaConfig -> String -> String
cipher _ [] = []
cipher cfg [last] = [cipherChar (step cfg) last]
cipher cfg (hd : tl) =
  let stepped = step cfg
   in cipherChar stepped hd : cipher stepped tl

-- | Uppercase and drop alpha characters from a string
--   Must be run on the input string before calling Enigma.Cipher
normalize :: String -> String
normalize = map toUpper . filter (\c -> isAscii c && isAlpha c)

-- |
--   Calculate the Index of Coincidence (IC) of a sequence of text
--   The IC of a natural language is typically much higher than
--   the IC of a uniformly random string.
calculateIC :: (Foldable t, Ord k) => t k -> Double
calculateIC = ic . freqs

combinations :: Int -> [a] -> [[a]]
combinations k ns = filter ((k ==) . length) $ subsequences ns
