{-# LANGUAGE DefaultSignatures      #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs                  #-}
module Kleene.Classes where

import Prelude ()
import Prelude.Compat

import Data.Char                          (ord)
import Data.Foldable                      (toList)
import Data.Function.Step.Discrete.Closed (SF)
import Data.Map                           (Map)
import Data.Maybe                         (mapMaybe)
import Data.RangeSet.Map                  (RSet)
import Data.Word                          (Word8)

import qualified Data.ByteString   as BS
import qualified Data.RangeSet.Map as RSet

import Kleene.Internal.Sets (dotRSet)

-- | Kleene algebra.
--
-- If 'k' is 'Monoid' it's expected that @'appends' = 'mappend'@;
-- if 'k' is 'Algebra.Lattice.Lattice' it's expected that @'unions' = 'Algebra.Lattice.joins'@.
--
-- [Wikipedia: Kleene Algebra](https://en.wikipedia.org/wiki/Kleene_algebra).
--
class Kleene k where
    -- | Empty regex. Doesn't accept anything.
    empty :: k

    -- | Empty string. /Note:/ different than 'empty'.
    eps :: k

    -- | Concatenation.
    appends :: [k] -> k

    -- | Union.
    unions :: [k] -> k

    -- | Kleene star.
    star :: k -> k

class Kleene k => CharKleene c k | k -> c where
    -- | Single character
    char :: c -> k

    string :: [c] -> k
    string = appends . map char

-- | One of the characters.
oneof :: (CharKleene c k, Foldable f) => f c -> k
oneof = unions . map char . toList

class CharKleene c k => FiniteKleene c k | k -> c where
    -- | Everything. \(\Sigma^\star\).
    everything :: k
    everything = star anyChar

    -- | @'charRange' 'a' 'z' = ^[a-z]$@.
    charRange :: c -> c -> k

    -- | Generalisation of 'charRange'.
    fromRSet :: RSet c -> k

    -- | @.@ Every character except new line @\\n@.
    dot :: c ~ Char => k
    dot = fromRSet dotRSet

    -- | Any character. /Note:/ different than 'dot'!
    anyChar :: k

    notChar :: c -> k
    default notChar :: (Ord c, Enum c, Bounded c) => c -> k
    notChar = fromRSet . RSet.complement . RSet.singleton

class Derivate c k | k -> c where
    -- | Does language contain an empty string?
    nullable :: k -> Bool

    -- | Derivative of a language.
    derivate :: c -> k -> k

-- | An @f@ can be used to match on the input.
class Match c k | k -> c where
    match :: k -> [c] -> Bool

    match8 :: c ~ Word8 => k -> BS.ByteString -> Bool
    match8 k = match k . BS.unpack

-- | Equivalence induced by 'Match'.
--
-- /Law:/
--
-- @
-- 'equivalent' re1 re2 <=> forall s. 'match' re1 s == 'match' re1 s
-- @
--
class Match c k => Equivalent c k | k -> c  where
    equivalent :: k -> k -> Bool

-- | Transition map.
class Derivate c k => TransitionMap c k | k -> c where
    transitionMap :: k -> Map k (SF c k)

-- | Complement of the language.
--
-- /Law:/
--
-- @
-- 'match' ('complement' f) xs = 'not' ('match' f) xs
-- @
class Complement c k | k -> c where
    complement :: k -> k

class ToLatin1 k where
    toLatin1 :: k Char -> k Word8

instance ToLatin1 RSet where
    toLatin1 = RSet.fromRangeList . mapMaybe f . RSet.toRangeList where
        f :: (Char, Char) -> Maybe (Word8, Word8)
        f (a, b)
            | ord a >= 256 = Nothing
            | otherwise    = Just (fromIntegral (ord a), fromIntegral (min 255 (ord b)))
