{-|

Copyright:
  This file is part of the package zxcvbn-hs. It is subject to the
  license terms in the LICENSE file found in the top-level directory
  of this distribution and at:

    https://code.devalot.com/sthenauth/zxcvbn-hs

  No part of this package, including this file, may be copied,
  modified, propagated, or distributed except according to the terms
  contained in the LICENSE file.

License: MIT

-}
module Text.Password.Strength.Internal.Math
  ( variations
  , variations'
  , bruteForce
  , caps
  ) where

--------------------------------------------------------------------------------
-- Library Imports:
import Control.Lens ((^.))
import Data.Char (isUpper)
import qualified Data.Text as Text
import Numeric.SpecFunctions (choose)

--------------------------------------------------------------------------------
-- Project Imports:
import Text.Password.Strength.Internal.Token

--------------------------------------------------------------------------------
-- | Equation 2, section 4, page 163 (8/18)
--
-- NOTE: The other implementations don't seem to divide the sum by two
--       but the equation in the paper clearly does.
variations :: Int -> Int -> Integer
variations :: Int -> Int -> Integer
variations Int
0 Int
_ = Integer
1
variations Int
_ Int
0 = Integer
1
variations Int
u Int
l = Integer -> Integer -> Integer
forall a. Ord a => a -> a -> a
max Integer
1 ([Integer] -> Integer
forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum (Double -> Integer
forall a b. (RealFrac a, Integral b) => a -> b
floor (Double -> Integer) -> (Int -> Double) -> Int -> Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Int -> Double
choose (Int
uInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
l) (Int -> Integer) -> [Int] -> [Integer]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Int
1 .. Int -> Int -> Int
forall a. Ord a => a -> a -> a
min Int
u Int
l]) Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` Integer
2)

--------------------------------------------------------------------------------
-- | Like equation 2, but modified for l33t and keyboard variations.
--
-- This equation does not appear in the 2016 paper although it is
-- mentioned.  Therefore it was adapted from the CoffeeScript and
-- Python implementations.
variations' :: Int -> Int -> Integer
variations' :: Int -> Int -> Integer
variations' Int
0 Int
_ = Integer
2
variations' Int
_ Int
0 = Integer
2
variations' Int
u Int
l = Int -> Int -> Integer
variations Int
u Int
l

--------------------------------------------------------------------------------
-- | Calculate the brute force score for text with the given length.
bruteForce :: Int -> Integer
bruteForce :: Int -> Integer
bruteForce = (Integer
10 Integer -> Int -> Integer
forall a b. (Num a, Integral b) => a -> b -> a
^)

--------------------------------------------------------------------------------
-- | Score the use of uppercase letters according to the paper.  This
-- is specified in the paragraph preceding equation 2.
caps :: Token -> Integer -> Integer
caps :: Token -> Integer -> Integer
caps Token
token Integer
n =
  let text :: Text
text       = Token
token Token -> Getting Text Token Text -> Text
forall s a. s -> Getting a s a -> a
^. Getting Text Token Text
Lens' Token Text
tokenChars
      upper :: Int
upper      = Text -> Int
Text.length ((Char -> Bool) -> Text -> Text
Text.filter Char -> Bool
isUpper Text
text)
      lower :: Int
lower      = Text -> Int
Text.length Text
text Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
upper
      allLower :: Bool
allLower   = Int
lower Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Text -> Int
Text.length Text
text
      allUpper :: Bool
allUpper   = Int
lower Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0
      firstUpper :: Bool
firstUpper = Int
upper Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 Bool -> Bool -> Bool
&& (Char -> Bool) -> Text -> Bool
Text.all Char -> Bool
isUpper (Int -> Text -> Text
Text.take Int
1 Text
text)
      lastUpper :: Bool
lastUpper  = Int
upper Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 Bool -> Bool -> Bool
&& (Char -> Bool) -> Text -> Bool
Text.all Char -> Bool
isUpper (Int -> Text -> Text
Text.takeEnd Int
1 Text
text)
  in case () of
    () | Bool
allLower   -> Integer
n
       | Bool
firstUpper -> Integer
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
2
       | Bool
lastUpper  -> Integer
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
2
       | Bool
allUpper   -> Integer
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Integer
2
       | Bool
otherwise  -> Integer
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
* Int -> Int -> Integer
variations Int
upper Int
lower

--------------------------------------------------------------------------------
-- NOTE: Equation 3 is in Keybaord.hs.