import contextlib
from sublime import Region


def append(view, text):
    """Appends text to view."""
    with in_one_edit(view) as edit:
        view.insert(edit, view.size(), text)


@contextlib.contextmanager
def in_one_edit(view):
    """Context manager to group edits in a view.

        Example:
            ...
            with in_one_edit(view):
                ...
            ...
    """
    try:
        edit = view.begin_edit()
        yield edit
    finally:
        view.end_edit(edit)


def has_sels(view):
    """Returns ``True`` if ``view`` has one selection or more.``
    """
    return len(view.sel()) > 0


def has_file_ext(view, ext):
    """Returns ``True`` if view has file extension ``ext``.
    ``ext`` may be specified with or without leading ``.``.
    """
    if not view.file_name() or not ext.strip().replace('.', ''):
        return False

    if not ext.startswith('.'):
        ext = '.' + ext

    return view.file_name().endswith(ext)


def rowcount(view):
    """Returns the number of rows in ``view``.
    """
    return view.rowcol(view.size())[0] + 1


def rowwidth(view, row):
    """Returns the number of characters of ``row`` in ``view``.
    """
    return view.rowcol(view.line(view.text_point(row, 0)).end())[1]


def relative_point(view, x=0, y=0, p=None):
    """Returns a point (int) to the given coordinates.

    Supports relative (negative) parameters and checks if they are in the
    bounds (other than ``View.text_point()``).

    If p (indexable -> ``p[0]``, ``len(p) == 2``; preferrably a tuple) is
    specified, x and y parameters are overridden.
    """
    if p is not None:
        if len(p) != 2:
            raise TypeError("Coordinates have 2 dimensions, not %d" % len(p))
        (x, y) = p
    row, col = x, y

    # shortcut
    if x == -1 and y == -1:
        return view.size()

    # calc absolute coords and check if coords are in the bounds
    rowc = rowcount(view)
    if x < 0:
        row = max(rowc + x, 0)
    else:
        row = min(row, rowc)

    roww = rowwidth(view, row)
    if y < 0:
        col = max(roww + y, 0)
    else:
        col = min(col, roww)

    return view.text_point(row, col)


def coorded_region(view, reg1=None, reg2=None):
    """Returns a region of two coordinate pairs parsed by ``relative_point(view, p=reg1)``.

    The pairs are supporsed to be indexable and have a length of 2.
    Tuples are preferred.

    Defaults to the whole buffer (``reg1=(0, 0), reg2=(-1, -1)``).
    """
    reg1 = reg1 or (0, 0)
    reg2 = reg2 or (-1, -1)

    p1 = relative_point(view, p=reg1)
    p2 = relative_point(view, p=reg2)
    return Region(p1, p2)


def coorded_substr(view, reg1=None, reg2=None):
    """Returns the string of two coordinate pairs parsed by ``relative_point(view, p=reg1)``.

    The pairs are supporsed to be indexable and have a length of 2.
    Tuples are preferred.

    Defaults to the whole buffer.
    """
    return view.substr(coorded_region(view, reg1, reg2))


def get_text(view):
    """Returns the whole string of a buffer.

    Alias for ``coorded_substr(view)``.
    """
    return coorded_substr(view)
