# Path: beancount_import/journal_editor.py
# META_IGNORE = get_meta_ignore()
# 
# Path: beancount_import/sorted_list.py
# class SortedList(Generic[K, V]):
#     def __init__(self, items: Iterable[Tuple[K, V]]) -> None:
#         entries = sorted(items, key=lambda x: x[0])
#         self.keys = [x[0] for x in entries]
#         self.values = [x[1] for x in entries]
# 
#     def __repr__(self) -> str:
#         return repr(list(zip(self.keys, self.values)))
# 
#     def find(self, lower_bound: K, upper_bound: K) -> Iterable[V]:
#         keys = self.keys
#         begin_pos = bisect.bisect_left(keys, lower_bound)
#         end_pos = bisect.bisect_right(keys, upper_bound)
#         if begin_pos == end_pos:
#             return ()
#         return itertools.islice(self.values, begin_pos, end_pos)
# 
# Path: beancount_import/posting_date.py
# def get_posting_date(entry, posting):
#     """Returns the date associated with a posting."""
#     return ((posting.meta and (posting.meta.get(POSTING_DATE_KEY) or
#                                posting.meta.get(POSTING_TRANSACTION_DATE_KEY))) or entry.date)
# 
# POSTING_DATE_KEY = 'date'
# 
# POSTING_TRANSACTION_DATE_KEY = 'transaction_date'


import datetime
import collections
import itertools
import beancount.parser.printer
from typing import Sequence, Tuple, List, NamedTuple, Dict, Callable, Optional, Iterable, Set, cast, FrozenSet, Union, Any
from beancount.core.number import MISSING, ZERO, Decimal
from beancount.core.data import Transaction, Posting, Meta, Cost, CostSpec
from beancount.core.amount import mul as amount_mul
from beancount.core.amount import Amount
from beancount.parser import booking_full
from .journal_editor import META_IGNORE
from .sorted_list import SortedList
from .posting_date import get_posting_date, POSTING_DATE_KEY, POSTING_TRANSACTION_DATE_KEY

def compute_single_sign_match_groups(
        matchable_postings: SingleSignMatchablePostings,
        unweighted_postings: Tuple[List[Posting],List[Posting]],
        is_cleared: IsClearedFunction,
        max_residual: Decimal) -> SingleSignMatchGroups:
    """Given a list of single-sign matchable postings for each of two transactions,
    computes the list of valid PostingMatchSet objects.  A PostingMatchSet is a
    set of non-conflicting valid matches between matchable postings in the first
    transaction and matchable postings in the second transaction as well as a
    list of postings to remove.  The computed PostingMatchSet objects are
    organized into SingleSignMatchGroup objects according to whether 0 or 1
    postings from each transaction are removed (more than 1 removal from each of
    the two transactions is not permitted).

    A match between two MatchablePosting objects is valid if, and only if, their
    weights differ by at most `max_residual` and they satisfy the
    `are_postings_mergeable` predicate.

    Each MatchablePosting may correspond either to a single underlying Posting
    or a subset of underlying Posting objects of one of the two transactions.  A
    set of matches is non-conflicting if no two matches contained in the set use
    the same underlying posting of either of the two transactions.

    A PostingMatchSet is permitted to contain a removal only under certain
    constraints: the MatchablePosting must satisfy `is_removal_candidate`, and
    it must not be possible to replace the removal with a regualr match that
    does not conflict with any existing matches in the PostingMatchSet.
    """


# Path: beancount_import/journal_editor.py
# META_IGNORE = get_meta_ignore()
# 
# Path: beancount_import/sorted_list.py
# class SortedList(Generic[K, V]):
#     def __init__(self, items: Iterable[Tuple[K, V]]) -> None:
#         entries = sorted(items, key=lambda x: x[0])
#         self.keys = [x[0] for x in entries]
#         self.values = [x[1] for x in entries]
# 
#     def __repr__(self) -> str:
#         return repr(list(zip(self.keys, self.values)))
# 
#     def find(self, lower_bound: K, upper_bound: K) -> Iterable[V]:
#         keys = self.keys
#         begin_pos = bisect.bisect_left(keys, lower_bound)
#         end_pos = bisect.bisect_right(keys, upper_bound)
#         if begin_pos == end_pos:
#             return ()
#         return itertools.islice(self.values, begin_pos, end_pos)
# 
# Path: beancount_import/posting_date.py
# def get_posting_date(entry, posting):
#     """Returns the date associated with a posting."""
#     return ((posting.meta and (posting.meta.get(POSTING_DATE_KEY) or
#                                posting.meta.get(POSTING_TRANSACTION_DATE_KEY))) or entry.date)
# 
# POSTING_DATE_KEY = 'date'
# 
# POSTING_TRANSACTION_DATE_KEY = 'transaction_date'


# Path: beancount_import/matching.py
import datetime
import collections
import itertools
import beancount.parser.printer
from typing import Sequence, Tuple, List, NamedTuple, Dict, Callable, Optional, Iterable, Set, cast, FrozenSet, Union, Any
from beancount.core.number import MISSING, ZERO, Decimal
from beancount.core.data import Transaction, Posting, Meta, Cost, CostSpec
from beancount.core.amount import mul as amount_mul
from beancount.core.amount import Amount
from beancount.parser import booking_full
from .journal_editor import META_IGNORE
from .sorted_list import SortedList
from .posting_date import get_posting_date, POSTING_DATE_KEY, POSTING_TRANSACTION_DATE_KEY


def compute_single_sign_match_groups(
        matchable_postings: SingleSignMatchablePostings,
        unweighted_postings: Tuple[List[Posting],List[Posting]],
        is_cleared: IsClearedFunction,
        max_residual: Decimal) -> SingleSignMatchGroups:
    """Given a list of single-sign matchable postings for each of two transactions,
    computes the list of valid PostingMatchSet objects.  A PostingMatchSet is a
    set of non-conflicting valid matches between matchable postings in the first
    transaction and matchable postings in the second transaction as well as a
    list of postings to remove.  The computed PostingMatchSet objects are
    organized into SingleSignMatchGroup objects according to whether 0 or 1
    postings from each transaction are removed (more than 1 removal from each of
    the two transactions is not permitted).

    A match between two MatchablePosting objects is valid if, and only if, their
    weights differ by at most `max_residual` and they satisfy the
    `are_postings_mergeable` predicate.

    Each MatchablePosting may correspond either to a single underlying Posting
    or a subset of underlying Posting objects of one of the two transactions.  A
    set of matches is non-conflicting if no two matches contained in the set use
    the same underlying posting of either of the two transactions.

    A PostingMatchSet is permitted to contain a removal only under certain
    constraints: the MatchablePosting must satisfy `is_removal_candidate`, and
    it must not be possible to replace the removal with a regualr match that
    does not conflict with any existing matches in the PostingMatchSet.
    """


    b_lookup_table = SortedList(