matchBy :: DateIndex (Ext Record)
        -> [CheckAttribute]                 -- ^ Attributes to match
        -> Attributes                       -- ^ Attributes of old records
        -> Ext Record                       -- ^ New record
        -> [Ext Record]                     -- ^ All other records
        -> (Maybe (Ext Record), [Ext Record])
matchBy index checks oldAttrs newRecord oldRecords
    | (CDate dx:_) <- checks = $trace ("Matching " ++ show (extID newRecord) ++ " by " ++ show checks) $
                               case go (lookupDatePrev (getDate newRecord) dx index) of
                                 Nothing -> $trace "No match found" (Nothing, oldRecords)
                                 Just r -> $trace ("Match found: " ++ show r) (Just r,  filter (r /=) oldRecords)
    | otherwise = $trace ("Matching " ++ show (extID newRecord)) $
                  case go oldRecords of
                    Nothing -> (Nothing, oldRecords)
                    Just r -> (Just r,  filter (r /=) oldRecords)
  where
    go [] = Nothing
    go (r:rs) = $trace ("Comparing with " ++ show (extID r)) $
      if (newRecord /= r) && checkAttrs oldAttrs r && all (matches r) checks
        then Just r
        else go rs

    matches oldRecord (CDate d) =
      $traceS (show (extID newRecord) ++ " DATE: " ++ show (getDate newRecord) ++ ", " ++ show (getDate oldRecord) ++ ": ") $
      datesDifference (getDate newRecord) (getDate oldRecord) <= d
    matches oldRecord (CAmount x) =
      case (getRAmount newRecord, getRAmount oldRecord) of
        (Just (a1 :# c1), Just (a2 :# c2)) -> 
          if c1 /= c2
            then False
            else $traceS (show (extID newRecord) ++ " CAmount: " ++ show a1 ++ ", " ++ show a2) $
                 if x == 0
                   then a1 == a2
                   else (max a1 a2) *. (fromIntegral x / 100.0) > (abs $ a1 - a2)
        _ -> False
    matches oldRecord CCreditAccount =
      case (getCreditAccount newRecord, getCreditAccount oldRecord) of
        (Just aid1, Just aid2) -> $traceS (show (extID newRecord) ++ " CACC: " ++ show aid1 ++ ", " ++ show aid2 ++ ": ") $ aid1 == aid2
        (Nothing,Just _) -> $trace (show (extID newRecord) ++ " No credit account specified") False
        (Nothing, Nothing)     -> True
        _                      -> False
    matches oldRecord CDebitAccount =
      case (getDebitAccount newRecord, getDebitAccount oldRecord) of
        (Just aid1, Just aid2) -> $traceS (show (extID newRecord) ++ " DACC: " ++ show aid1 ++ ", " ++ show aid2 ++ ": ") $ aid1 == aid2
        (Nothing, Just _) -> $trace (show (extID newRecord) ++ " No debit account specified") False
        (Nothing, Nothing)     -> True
        _                      -> False
    matches oldRecord (CAttribute name) =
      case (getAttr name newRecord, getAttr name oldRecord) of
        (Just v1, Just v2) -> v1 == v2
        (Nothing, Nothing)     -> True
        _                  -> False

-- | Set attributes from new record to old record
matchBy :: DateIndex (Ext Record)
        -> [CheckAttribute]                 -- ^ Attributes to match
        -> Attributes                       -- ^ Attributes of old records
        -> Ext Record                       -- ^ New record
        -> [Ext Record]                     -- ^ All other records
        -> (Maybe (Ext Record), [Ext Record])
matchBy index checks oldAttrs newRecord oldRecords
    | (CDate dx:_) <- checks = $trace ("Matching " ++ show (extID newRecord) ++ " by " ++ show checks) $
                               case go (lookupDatePrev (getDate newRecord) dx index) of
                                 Nothing -> $trace "No match found" (Nothing, oldRecords)
                                 Just r -> $trace ("Match found: " ++ show r) (Just r,  filter (r /=) oldRecords)
    | otherwise = $trace ("Matching " ++ show (extID newRecord)) $
                  case go oldRecords of
                    Nothing -> (Nothing, oldRecords)
                    Just r -> (Just r,  filter (r /=) oldRecords)
  where
    go [] = Nothing
    go (r:rs) = $trace ("Comparing with " ++ show (extID r)) $
      if (newRecord /= r) && checkAttrs oldAttrs r && all (matches r) checks
        then Just r
        else go rs

    matches oldRecord (CDate d) =
      $traceS (show (extID newRecord) ++ " DATE: " ++ show (getDate newRecord) ++ ", " ++ show (getDate oldRecord) ++ ": ") $
      datesDifference (getDate newRecord) (getDate oldRecord) <= d
    matches oldRecord (CAmount x) =
      case (getRAmount newRecord, getRAmount oldRecord) of
        (Just (a1 :# c1), Just (a2 :# c2)) -> 
          if c1 /= c2
            then False
            else $traceS (show (extID newRecord) ++ " CAmount: " ++ show a1 ++ ", " ++ show a2) $
                 if x == 0
                   then a1 == a2
                   else (max a1 a2) *. (fromIntegral x / 100.0) > (abs $ a1 - a2)
        _ -> False
    matches oldRecord CCreditAccount =
      case (getCreditAccount newRecord, getCreditAccount oldRecord) of
        (Just aid1, Just aid2) -> $traceS (show (extID newRecord) ++ " CACC: " ++ show aid1 ++ ", " ++ show aid2 ++ ": ") $ aid1 == aid2
        (Nothing,Just _) -> $trace (show (extID newRecord) ++ " No credit account specified") False
        (Nothing, Nothing)     -> True
        _                      -> False
    matches oldRecord CDebitAccount =
      case (getDebitAccount newRecord, getDebitAccount oldRecord) of
        (Just aid1, Just aid2) -> $traceS (show (extID newRecord) ++ " DACC: " ++ show aid1 ++ ", " ++ show aid2 ++ ": ") $ aid1 == aid2
        (Nothing, Just _) -> $trace (show (extID newRecord) ++ " No debit account specified") False
        (Nothing, Nothing)     -> True
        _                      -> False
    matches oldRecord (CAttribute name) =
      case (getAttr name newRecord, getAttr name oldRecord) of
        (Just v1, Just v2) -> v1 == v2
        (Nothing, Nothing)     -> True
        _                  -> False

-- | Set attributes from new record to old record
matchBy index checks oldAttrs newRecord oldRecords
    | (CDate dx:_) <- checks = $trace ("Matching " ++ show (extID newRecord) ++ " by " ++ show checks) $
                               case go (lookupDatePrev (getDate newRecord) dx index) of
                                 Nothing -> $trace "No match found" (Nothing, oldRecords)
                                 Just r -> $trace ("Match found: " ++ show r) (Just r,  filter (r /=) oldRecords)
    | otherwise = $trace ("Matching " ++ show (extID newRecord)) $
                  case go oldRecords of
                    Nothing -> (Nothing, oldRecords)
                    Just r -> (Just r,  filter (r /=) oldRecords)
  where
    go [] = Nothing
    go (r:rs) = $trace ("Comparing with " ++ show (extID r)) $
      if (newRecord /= r) && checkAttrs oldAttrs r && all (matches r) checks
        then Just r
        else go rs

    matches oldRecord (CDate d) =
      $traceS (show (extID newRecord) ++ " DATE: " ++ show (getDate newRecord) ++ ", " ++ show (getDate oldRecord) ++ ": ") $
      datesDifference (getDate newRecord) (getDate oldRecord) <= d
    matches oldRecord (CAmount x) =
      case (getRAmount newRecord, getRAmount oldRecord) of
        (Just (a1 :# c1), Just (a2 :# c2)) -> 
          if c1 /= c2
            then False
            else $traceS (show (extID newRecord) ++ " CAmount: " ++ show a1 ++ ", " ++ show a2) $
                 if x == 0
                   then a1 == a2
                   else (max a1 a2) *. (fromIntegral x / 100.0) > (abs $ a1 - a2)
        _ -> False
    matches oldRecord CCreditAccount =
      case (getCreditAccount newRecord, getCreditAccount oldRecord) of
        (Just aid1, Just aid2) -> $traceS (show (extID newRecord) ++ " CACC: " ++ show aid1 ++ ", " ++ show aid2 ++ ": ") $ aid1 == aid2
        (Nothing,Just _) -> $trace (show (extID newRecord) ++ " No credit account specified") False
        (Nothing, Nothing)     -> True
        _                      -> False
    matches oldRecord CDebitAccount =
      case (getDebitAccount newRecord, getDebitAccount oldRecord) of
        (Just aid1, Just aid2) -> $traceS (show (extID newRecord) ++ " DACC: " ++ show aid1 ++ ", " ++ show aid2 ++ ": ") $ aid1 == aid2
        (Nothing, Just _) -> $trace (show (extID newRecord) ++ " No debit account specified") False
        (Nothing, Nothing)     -> True
        _                      -> False
    matches oldRecord (CAttribute name) =
      case (getAttr name newRecord, getAttr name oldRecord) of
        (Just v1, Just v2) -> v1 == v2
        (Nothing, Nothing)     -> True
        _                  -> False

-- | Set attributes from new record to old record