{-# LANGUAGE BangPatterns, OverloadedStrings #-}

-- | LDIF related types
module Text.LDIF.Types (
 	LDIF(..),   
        LDIFRecord(..),
        Change(..),
        Modify(..), 
        DN(..), 
        LDIFType(..),
        Attribute(..), Value(..), AttrValue,
        isContentRecord,
        isChangeRecord,
        getLDIFType
)
where
import qualified Data.ByteString.Char8 as BC
import Data.Char

-- | Attribute name is case-insensitive string
data Attribute = Attribute { aName :: BC.ByteString } deriving Show

instance Eq Attribute where
    (Attribute xs) == (Attribute ys)  = (BC.map toUpper xs) == (BC.map toUpper ys)

instance Ord Attribute where
    (Attribute xs) `compare` (Attribute ys)  = (BC.map toUpper xs) `compare` (BC.map toUpper ys)

-- | Attribute value is either case sensitive or insensitive string
data Value = Value  { aVal :: BC.ByteString }
           | ValueI { aVal :: BC.ByteString } deriving Show
             
instance Eq Value where
    (Value xs) == (Value ys)  = xs == ys
    xs == ys  = (BC.map toUpper $ aVal xs) == (BC.map toUpper $ aVal ys)

instance Ord Value where
    (Value xs) `compare` (Value ys)  = xs `compare` ys
    xs `compare` ys = (BC.map toUpper $ aVal xs) `compare` (BC.map toUpper $ aVal ys)

-- | Pair of Atribute and Value
type AttrValue = (Attribute, Value)

-- | Enumeration LDIF Types
data LDIFType = LDIFContentType -- ^ LDIF with Content Records
              | LDIFChangesType -- ^ LDIF with Changes Records
              | LDIFMixedType   -- ^ LDIF with both Content and Changes Records
              deriving Eq

instance Show LDIFType where
    show LDIFChangesType = "Delta"
    show LDIFContentType = "Content"
    show LDIFMixedType   = "Mixed"

-- | Represents LDIF structure, it can be either simply LDIF data dump or
-- changes LDIF with LDAP operations 
data LDIF = LDIF { lcVersion :: Maybe BC.ByteString, lcEntries :: ![LDIFRecord] } deriving (Show, Eq)

data LDIFRecord
  -- | Represents one data record within LDIF file with DN and content
  = ContentRecord { reDN :: !DN, coAttrVals :: ![AttrValue] } 
  -- | Represents one change record within LDIF file with DN and content
  | ChangeRecord  { reDN :: !DN, chOp :: !Change } deriving (Show, Eq)

-- | Represents one LDAP operation within changes LDIF
data Change = ChangeAdd     { chAttrVals :: ![AttrValue] }
            | ChangeDelete 
            | ChangeModify  { chMods :: ![Modify] }
            | ChangeModDN  deriving (Show, Eq)

-- | Represents ChangeModify operations upon one entry within given DN
data Modify = ModAdd     { modAttr :: !Attribute, modAttrVals :: ![Value] }
            | ModDelete  { modAttr :: !Attribute, modAttrVals :: ![Value] }
            | ModReplace { modAttr :: !Attribute, modAttrVals :: ![Value] } deriving (Show, Eq)

-- | Represents Distinguished Name (DN)
data DN = DN { dnAttrVals :: ![AttrValue] } deriving (Eq, Show)

-- | Ord Instance for DN
instance Ord DN where
  (DN xs1) `compare` (DN xs2)  = let cmpAV ((a1,v1),(a2,v2)) = let ca = a1 `compare` a2
                                                                   cv = v1 `compare` v2
                                                               in if ca == EQ then cv else ca
                                     dx = map cmpAV $ zip (reverse $ xs1) (reverse $ xs2)
                                     lx | length xs1 > length xs2 = GT
                                        | length xs1 < length xs2 = LT
                                        | otherwise = EQ
                                 in case filter (EQ /=) dx of
                                   []    -> lx
                                   (x:_) -> x

-- | Check if LDIFRecord is Content Record
isContentRecord :: LDIFRecord -> Bool
isContentRecord (ContentRecord _ _) = True
isContentRecord _ = False

-- | Check if LDIFRecord is Change Record
isChangeRecord :: LDIFRecord -> Bool
isChangeRecord (ChangeRecord _ _) = True
isChangeRecord _ = False

-- | Dettect from LDIF content the Type (Content, Changes, Mixed)
getLDIFType :: LDIF -> LDIFType
getLDIFType (LDIF _ []) = LDIFContentType
getLDIFType (LDIF _ xs) = getLDIFType' con chg
    where
      con = filter (isContentRecord) xs
      chg = filter (not . isContentRecord) xs
      getLDIFType' [] [] = LDIFContentType -- Fallback Empty LDIF as an Content LDIF
      getLDIFType' [] _  = LDIFChangesType
      getLDIFType' _  [] = LDIFContentType
      getLDIFType' _  _  = LDIFMixedType