{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}

{-|

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.Config (
  -- * Configuration
  Config,
  HasConfig(..),
  Dictionary,
  en_US,
  dictionaries,
  addCustomFrequencyList
  ) where

--------------------------------------------------------------------------------
-- Library Imports:
import Control.Lens ((&), (^.), (.~), (%~))
import Control.Lens.TH (makeClassy)
import Control.Monad (join)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.Text (Text)
import Data.Vector (Vector)
import qualified Data.Vector as Vector

--------------------------------------------------------------------------------
-- Project Imports:
import qualified Text.Password.Strength.Generated.Adjacency as Adjc
import qualified Text.Password.Strength.Generated.Frequency as Freq
import Text.Password.Strength.Internal.Adjacency (AdjacencyTable)

--------------------------------------------------------------------------------
-- | Type alias for a frequency database.
type Dictionary = HashMap Text Int

--------------------------------------------------------------------------------
-- | A type to control which dictionaries, keyboard layouts, etc. will
-- be used when estimating guesses.
data Config = Config
  { _passwordLists :: [Dictionary]
  , _wordFrequencyLists :: [Dictionary]
  , _customFrequencyLists :: [Dictionary]
  , _keyboardGraphs :: [AdjacencyTable]
  , _obviousSequenceStart :: Char -> Bool
  }

makeClassy ''Config

--------------------------------------------------------------------------------
instance Semigroup Config where
  (<>) x y =
      x & passwordLists        %~ (++ (y ^. passwordLists))
        & wordFrequencyLists   %~ (++ (y ^. wordFrequencyLists))
        & customFrequencyLists %~ (++ (y ^. customFrequencyLists))
        & keyboardGraphs       %~ (++ (y ^. keyboardGraphs))
        & obviousSequenceStart .~ oss
      where
        -- Laws:
        --
        -- >>> x <> y
        --
        -- * Left identity:  (\c -> const False c || y c) == y c
        -- * Right identity: (\c -> x c || const False c) == x c
        --
        -- * Associativity:
        --
        --   (\c -> (x c || y c) || z c) == (\c -> (x c || (y c || z c)))
        oss :: Char -> Bool
        oss c = (x ^. obviousSequenceStart) c
             || (y ^. obviousSequenceStart) c

--------------------------------------------------------------------------------
instance Monoid Config where
  mempty = Config [] [] [] [] (const False)

--------------------------------------------------------------------------------
-- | Default configuration for US English.
en_US :: Config
en_US = Config{..}
  where
    _customFrequencyLists = []
    _passwordLists        = [ Freq.xato ]
    _wordFrequencyLists   = [ Freq.english_wikipedia
                            , Freq.female_names
                            , Freq.male_names
                            , Freq.surnames
                            , Freq.us_tv_and_film
                            ]
    _keyboardGraphs       = [ Adjc.qwerty
                            , Adjc.numpad
                            ]
    _obviousSequenceStart c =
      c == 'a' || c == 'A' ||
      c == 'z' || c == 'Z' ||
      c == '0' || c == '1' || c == '9'

--------------------------------------------------------------------------------
-- | Access all configured dictionaries.
dictionaries :: Config -> [Dictionary]
dictionaries c = join [ c ^. passwordLists
                      , c ^. wordFrequencyLists
                      , c ^. customFrequencyLists
                      ]

--------------------------------------------------------------------------------
-- | Add a custom list of words for frequency lookup.  The words
-- should be ordered from most frequent to least frequent.
addCustomFrequencyList :: Vector Text -> Config -> Config
addCustomFrequencyList v = addDict (mkDict v)
  where
    mkDict :: Vector Text -> Dictionary
    mkDict = Vector.ifoldr (\i x -> HashMap.insert x (i+1)) HashMap.empty

    addDict :: Dictionary -> Config -> Config
    addDict d = customFrequencyLists %~ (d:)