deduplicate :: (Throws DuplicatedRecord l,
                Throws InternalError l)
            => [DeduplicationRule]        -- ^ List of deduplication rules
            -> [Ext Record]               -- ^ Source list of records
            -> Ledger l [Ext Record]
deduplicate rules records = go $ reverse records
  where
    index = buildIndex records

    go [] = return []
    go (r:rs) =
      case findDRule r rules of
        Nothing -> $trace ("No rule for " ++ show (extID r)) $ (r:) <$> go rs
        Just (checks, oldAttrs, action) -> 
          case matchBy index checks oldAttrs r rs of
            (Nothing, _) -> (r:) <$> go rs
            (Just old, other) -> 
              case action of
                DError -> throw $ DuplicatedRecord
                                      (pPrint r ++ "\nOld was:\n" ++ pPrint old)
                                      (getLocation r)
                DWarning -> do
                    $warning $ "Duplicated records:\n" ++ pPrint r ++
                              "\nOld was:\n" ++ pPrint old
                    (r:) <$> go rs
                DDuplicate -> (r:) <$> go rs
                DIgnoreNew -> go rs
                DDeleteOld -> (r:) <$> go other
                DSetAttributes sets ->
                    (setAttributes sets r old:) <$> go other
deduplicate :: (Throws DuplicatedRecord l,
                Throws InternalError l)
            => [DeduplicationRule]        -- ^ List of deduplication rules
            -> [Ext Record]               -- ^ Source list of records
            -> Ledger l [Ext Record]
deduplicate rules records = go $ reverse records
  where
    index = buildIndex records

    go [] = return []
    go (r:rs) =
      case findDRule r rules of
        Nothing -> $trace ("No rule for " ++ show (extID r)) $ (r:) <$> go rs
        Just (checks, oldAttrs, action) -> 
          case matchBy index checks oldAttrs r rs of
            (Nothing, _) -> (r:) <$> go rs
            (Just old, other) -> 
              case action of
                DError -> throw $ DuplicatedRecord
                                      (pPrint r ++ "\nOld was:\n" ++ pPrint old)
                                      (getLocation r)
                DWarning -> do
                    $warning $ "Duplicated records:\n" ++ pPrint r ++
                              "\nOld was:\n" ++ pPrint old
                    (r:) <$> go rs
                DDuplicate -> (r:) <$> go rs
                DIgnoreNew -> go rs
                DDeleteOld -> (r:) <$> go other
                DSetAttributes sets ->
                    (setAttributes sets r old:) <$> go other
deduplicate rules records = go $ reverse records
  where
    index = buildIndex records

    go [] = return []
    go (r:rs) =
      case findDRule r rules of
        Nothing -> $trace ("No rule for " ++ show (extID r)) $ (r:) <$> go rs
        Just (checks, oldAttrs, action) -> 
          case matchBy index checks oldAttrs r rs of
            (Nothing, _) -> (r:) <$> go rs
            (Just old, other) -> 
              case action of
                DError -> throw $ DuplicatedRecord
                                      (pPrint r ++ "\nOld was:\n" ++ pPrint old)
                                      (getLocation r)
                DWarning -> do
                    $warning $ "Duplicated records:\n" ++ pPrint r ++
                              "\nOld was:\n" ++ pPrint old
                    (r:) <$> go rs
                DDuplicate -> (r:) <$> go rs
                DIgnoreNew -> go rs
                DDeleteOld -> (r:) <$> go other
                DSetAttributes sets ->
                    (setAttributes sets r old:) <$> go other