{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
-- | The 'Search' typeclass lets you build dictinaries and then query them to
--   find words close to a given one.
--
--   Right now two data types are provided: 'TST.TST' and 'BK.BK', monomorphic
--   functions are provided as well.  The difference is in performance:
--   'TST.TST' is faster for low distances (less than 3) but impractical for
--   larger ones, where 'BK.BK' is more suited.  See the specific modules for
--   more info.
module Language.Distance.Search
    ( Search (..)
    , TSTDist (..)
    , BKDist (..)
    ) where

import           Data.ListLike (ListLike)
import qualified Data.ListLike as ListLike

import           Data.TSTSet (TSTSet)
import qualified Data.TSTSet as TSTSet
import           Language.Distance
import           Language.Distance.Search.BK (BKTree)
import qualified Language.Distance.Search.BK as BK
import           Language.Distance.Search.Class
import qualified Language.Distance.Search.TST as TST

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
-- ~~ TST Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --

-- | We need to wrap 'TSTSet' in a newtype because we need the algorithm and the
--   container have to depend on the type.
newtype TSTDist full sym algo = TSTDist {getTST :: TSTSet sym}

instance (Ord sym, ListLike full sym, EditDistance sym Levenshtein)
         => Search (TSTDist full sym Levenshtein) full Levenshtein where
    empty        = TSTDist TST.empty
    insert ll    = TSTDist . TST.insert ll . getTST
    query maxd s = TST.levenshtein maxd s . getTST

instance (Ord sym, ListLike full sym, EditDistance sym DamerauLevenshtein)
         => Search (TSTDist full sym DamerauLevenshtein) full DamerauLevenshtein where
    empty     = TSTDist TST.empty
    insert ll = TSTDist . TSTSet.insert (ListLike.toList ll) . getTST
    query maxd s = TST.damerauLevenshtein maxd s . getTST

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
-- ~~ BK Search ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --

-- | Again, wrapping 'BKTree' to have the phantom types in place.
newtype BKDist full sym algo = BKDist {getBK :: BKTree full algo}

instance (Eq sym, ListLike full sym, EditDistance sym algo)
         => Search (BKDist full sym algo) full algo where
    empty        = BKDist BK.empty
    insert s     = BKDist . BK.insert s . getBK
    query maxd s = BK.query maxd s . getBK