module Text.LDIF.Diff (
        diffLDIF,
        diffRecord,
        compareLDIF
)
where
import Text.LDIF.Types
import Text.LDIF.Utils
import Data.Maybe
import Data.List (foldl', sortBy)

-- | Calculate Change LDIF between two LDIF contents.
-- If there is not difference the empty change list is returned.
diffLDIF :: LDIF -> LDIF -> Either String LDIF
diffLDIF l1 l2 | getLDIFType l1 == LDIFContentType && getLDIFType l2 == LDIFContentType = Right (diffLDIF' l1 l2)
               | otherwise = Left ("Diff supported only on Content LDIFs but SRC="
                                         ++(show $ getLDIFType l1)++" and DST="
                                         ++(show $ getLDIFType l2))

diffLDIF' :: LDIF -> LDIF -> LDIF
diffLDIF' l1@(LDIF _ c1) l2@(LDIF v2 c2) = LDIF v2 (changes ++ deletes ++ adds)
   where 
      adds = map content2add $ filter (not . isEntryIn) c2
        where
          llc = createLookupTable l1
          isEntryIn ex = case findRecordByDN llc (reDN ex) of
            Nothing                   -> False
            Just (ContentRecord _ _)  -> True
            Just (ChangeRecord _ _)   -> error "Unexpected record type"
          content2add (ContentRecord dn vals) = ChangeRecord dn (ChangeAdd vals)
          content2add (ChangeRecord _ _)      = error "Unexpected record type"
      (changes,deletes) = (reverse $ fnu changes', fnu deletes')
        where
          fnu = filter (not . isDummyRecord)
          l2c = createLookupTable l2
          (changes',deletes') = foldl' processEntry ([],[]) c1
          processEntry (cx,dx) e1 = procEntry' $ findRecordByDN l2c (reDN e1)
            where
              procEntry' Nothing   = (cx, (ChangeRecord (reDN e1) ChangeDelete):dx)
              procEntry' (Just e2) = ((fromJust $ diffRecord e1 e2):cx, dx)

-- | Calculate difference between two LDIF Records
diffRecord :: LDIFRecord -> LDIFRecord -> Maybe LDIFRecord
diffRecord r1 r2 | (reDN r1) /= (reDN r2) = Nothing
                 | otherwise = Just (ChangeRecord (reDN r1) (ChangeModify mods))
   where
      mods = delMods ++ addMods
      addMods = map (\x -> ModAdd (fst x) [(snd x)]) addVals
      delMods = map (\x -> ModDelete (fst x) [(snd x)]) delVals
      addVals = filter (\x -> not $ elem x (coAttrVals r1)) (coAttrVals r2) :: [AttrValue]
      delVals = filter (\x -> not $ elem x (coAttrVals r2)) (coAttrVals r1) :: [AttrValue]

-- | Compare two LDIFs and provide list of the Records for each input LDIF,
-- which are different or not present in the other LDIF.
compareLDIF :: LDIF -> LDIF -> ([LDIFRecord], [LDIFRecord])
compareLDIF l1@(LDIF _ c1) l2@(LDIF _ c2) = (sortBy cmpByDN r1, sortBy cmpByDN $ r2 ++ adds)
   where 
      cmpByDN a b = (reDN a) `compare` (reDN b)
      adds = filter (not . isEntryIn) c2
        where
          llc = createLookupTable l1
          isEntryIn ex = case findRecordByDN llc (reDN ex) of
            Nothing                   -> False
            (Just _)                  -> True
      (r1,r2) = foldl' processEntry ([],[]) c1
        where
          l2c = createLookupTable l2
          processEntry (a1,a2) e1 = case findRecordByDN l2c (reDN e1) of
            Nothing -> (e1:a1,a2)
            Just e2 -> if e1 == e2 then (a1,a2) else (e1:a1,e2:a2)