# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause

"""Implements operations on bytes and str (unicode) array items."""

import operator
import numpy as np

from numba_cuda_mlir.numba_cuda import types
from numba_cuda_mlir.numba_cuda import cgutils
from numba_cuda_mlir.numba_cuda.extending import (
    overload,
    overload_method,
    register_jitable,
    intrinsic,
)
from numba_cuda_mlir.numba_cuda.core.imputils import Registry
from numba_cuda_mlir.numba_cuda.cgutils import is_nonelike
from numba_cuda_mlir.numba_cuda.cpython import unicode

registry = Registry("charseq")
lower = registry.lower
lower_cast = registry.lower_cast


# The deref intrinsics and _unicode_to_bytes below keep their typing (the
# returned signatures) but their codegen built llvmlite IR and is filtered out
# on the MLIR path, so it is a shared tombstone. The bytes/charseq/unicode cast
# @lower_cast functions are likewise dead and have been removed.
def _dead_codegen(context, builder, signature, args):
    raise NotImplementedError(
        "this charseq codegen built llvmlite IR and is not used on the MLIR path"
    )


# bytes and str arrays items are of type CharSeq and UnicodeCharSeq,
# respectively.  See numpy/types/npytypes.py for CharSeq,
# UnicodeCharSeq definitions.  The corresponding data models are
# defined in numpy/datamodel/models.py. Boxing/unboxing of item types
# are defined in numpy/targets/boxing.py, see box_unicodecharseq,
# unbox_unicodecharseq, box_charseq, unbox_charseq.

s1_dtype = np.dtype("S1")
assert s1_dtype.itemsize == 1
bytes_type = types.Bytes(types.uint8, 1, "C", readonly=True)

# Currently, NumPy supports only UTF-32 arrays but this may change in
# future and the approach used here for supporting str arrays may need
# a revision depending on how NumPy will support UTF-8 and UTF-16
# arrays.
u1_dtype = np.dtype("U1")
unicode_byte_width = u1_dtype.itemsize
unicode_uint = {1: np.uint8, 2: np.uint16, 4: np.uint32}[unicode_byte_width]
# unicode_kind = {
#     1: unicode.PY_UNICODE_1BYTE_KIND,
#     2: unicode.PY_UNICODE_2BYTE_KIND,
#     4: unicode.PY_UNICODE_4BYTE_KIND,
# }[unicode_byte_width]


# this is modified version of numba.unicode.make_deref_codegen
def make_deref_codegen(bitsize):
    return _dead_codegen


@intrinsic
def deref_uint8(typingctx, data, offset):
    sig = types.uint32(data, types.intp)
    return sig, make_deref_codegen(8)


@intrinsic
def deref_uint16(typingctx, data, offset):
    sig = types.uint32(data, types.intp)
    return sig, make_deref_codegen(16)


@intrinsic
def deref_uint32(typingctx, data, offset):
    sig = types.uint32(data, types.intp)
    return sig, make_deref_codegen(32)


@register_jitable(_nrt=False)
def charseq_get_code(a, i):
    """Access i-th item of CharSeq object via code value"""
    return deref_uint8(a, i)


@register_jitable
def charseq_get_value(a, i):
    """Access i-th item of CharSeq object via code value.

    null code is interpreted as IndexError
    """
    code = charseq_get_code(a, i)
    if code == 0:
        raise IndexError("index out of range")
    return code


@register_jitable(_nrt=False)
def unicode_charseq_get_code(a, i):
    """Access i-th item of UnicodeCharSeq object via code value"""
    if unicode_byte_width == 4:
        return deref_uint32(a, i)
    elif unicode_byte_width == 2:
        return deref_uint16(a, i)
    elif unicode_byte_width == 1:
        return deref_uint8(a, i)
    else:
        raise NotImplementedError("unicode_charseq_get_code: unicode_byte_width not in [1, 2, 4]")


@register_jitable
def unicode_get_code(a, i):
    """Access i-th item of UnicodeType object."""
    return unicode._get_code_point(a, i)


@register_jitable
def bytes_get_code(a, i):
    """Access i-th item of Bytes object."""
    return a[i]


def _get_code_impl(a):
    if isinstance(a, types.CharSeq):
        return charseq_get_code
    elif isinstance(a, types.Bytes):
        return bytes_get_code
    elif isinstance(a, types.UnicodeCharSeq):
        return unicode_charseq_get_code
    elif isinstance(a, types.UnicodeType):
        return unicode_get_code


def _same_kind(a, b):
    for t in [
        (types.CharSeq, types.Bytes),
        (types.UnicodeCharSeq, types.UnicodeType),
    ]:
        if isinstance(a, t) and isinstance(b, t):
            return True
    return False


def _is_bytes(a):
    return isinstance(a, (types.CharSeq, types.Bytes))


def is_default(x, default):
    return x == default or isinstance(x, types.Omitted)


@register_jitable
def unicode_charseq_get_value(a, i):
    """Access i-th item of UnicodeCharSeq object via unicode value

    null code is interpreted as IndexError
    """
    code = unicode_charseq_get_code(a, i)
    if code == 0:
        raise IndexError("index out of range")
    # Return numpy equivalent of `chr(code)`
    return np.array(code, unicode_uint).view(u1_dtype)[()]


#
# CAST
#
# Currently, the following casting operations are supported:
#   Bytes -> CharSeq                 (ex: a=np.array(b'abc'); a[()] = b'123')
#   UnicodeType -> UnicodeCharSeq    (ex: a=np.array('abc'); a[()] = '123')
#   CharSeq -> Bytes                 (ex: a=np.array(b'abc'); b = bytes(a[()]))
#   UnicodeType -> Bytes             (ex: str('123')._to_bytes())
#
# The following casting operations can be implemented when required:
#   Bytes -> UnicodeCharSeq   (ex: a=np.array('abc'); a[()] = b'123')
#   UnicodeType -> CharSeq    (ex: a=np.array(b'abc'); a[()] = '123')
#   UnicodeType -> Bytes      (ex: bytes('123', 'utf8'))
#


@intrinsic
def _unicode_to_bytes(typingctx, s):
    # used in _to_bytes method
    assert s == types.unicode_type
    sig = bytes_type(s)
    return sig, _dead_codegen


#
#   Operations on bytes/str array items
#
# Implementation note: while some operations need
# CharSeq/UnicodeCharSeq specific implementations (getitem, len, str,
# etc), many operations can be supported by casting
# CharSeq/UnicodeCharSeq objects to Bytes/UnicodeType objects and
# re-use existing operations.
#
# However, in numba more operations are implemented for UnicodeType
# than for Bytes objects, hence the support for operations with bytes
# array items will be less complete than for str arrays. Although, in
# some cases (hash, contains, etc) the UnicodeType implementations can
# be reused for Bytes objects via using `_to_str` method.
#


@overload(operator.getitem)
def charseq_getitem(s, i):
    get_value = None
    if isinstance(i, types.Integer):
        if isinstance(s, types.CharSeq):
            get_value = charseq_get_value
        if isinstance(s, types.UnicodeCharSeq):
            get_value = unicode_charseq_get_value
    if get_value is not None:
        max_i = s.count
        msg = "index out of range [0, %s]" % (max_i - 1)

        def getitem_impl(s, i):
            if i < max_i and i >= 0:
                return get_value(s, i)
            raise IndexError(msg)

        return getitem_impl


@overload(len)
def charseq_len(s):
    if isinstance(s, (types.CharSeq, types.UnicodeCharSeq)):
        get_code = _get_code_impl(s)
        n = s.count
        if n == 0:

            def len_impl(s):
                return 0

            return len_impl
        else:

            def len_impl(s):
                # return the index of the last non-null value (numpy
                # behavior)
                i = n
                code = 0
                while code == 0:
                    i = i - 1
                    if i < 0:
                        break
                    code = get_code(s, i)
                return i + 1

            return len_impl


@overload(operator.add)
@overload(operator.iadd)
def charseq_concat(a, b):
    if not _same_kind(a, b):
        return
    if isinstance(a, types.UnicodeCharSeq) and isinstance(b, types.UnicodeType):

        def impl(a, b):
            return str(a) + b

        return impl
    if isinstance(b, types.UnicodeCharSeq) and isinstance(a, types.UnicodeType):

        def impl(a, b):
            return a + str(b)

        return impl
    if isinstance(a, types.UnicodeCharSeq) and isinstance(b, types.UnicodeCharSeq):

        def impl(a, b):
            return str(a) + str(b)

        return impl
    if isinstance(a, (types.CharSeq, types.Bytes)) and isinstance(b, (types.CharSeq, types.Bytes)):

        def impl(a, b):
            return (a._to_str() + b._to_str())._to_bytes()

        return impl


@overload(operator.mul)
def charseq_repeat(a, b):
    if isinstance(a, types.UnicodeCharSeq):

        def wrap(a, b):
            return str(a) * b

        return wrap
    if isinstance(b, types.UnicodeCharSeq):

        def wrap(a, b):
            return a * str(b)

        return wrap
    if isinstance(a, (types.CharSeq, types.Bytes)):

        def wrap(a, b):
            return (a._to_str() * b)._to_bytes()

        return wrap
    if isinstance(b, (types.CharSeq, types.Bytes)):

        def wrap(a, b):
            return (a * b._to_str())._to_bytes()

        return wrap


@overload(operator.not_)
def charseq_not(a):
    if isinstance(a, (types.UnicodeCharSeq, types.CharSeq, types.Bytes)):

        def impl(a):
            return len(a) == 0

        return impl


@overload(operator.eq)
def charseq_eq(a, b):
    if not _same_kind(a, b):
        return
    left_code = _get_code_impl(a)
    right_code = _get_code_impl(b)
    if left_code is not None and right_code is not None:

        def eq_impl(a, b):
            n = len(a)
            if n != len(b):
                return False
            for i in range(n):
                if left_code(a, i) != right_code(b, i):
                    return False
            return True

        return eq_impl


@overload(operator.ne)
def charseq_ne(a, b):
    if not _same_kind(a, b):
        return
    left_code = _get_code_impl(a)
    right_code = _get_code_impl(b)
    if left_code is not None and right_code is not None:

        def ne_impl(a, b):
            return not (a == b)

        return ne_impl


@overload(operator.lt)
def charseq_lt(a, b):
    if not _same_kind(a, b):
        return
    left_code = _get_code_impl(a)
    right_code = _get_code_impl(b)
    if left_code is not None and right_code is not None:

        def lt_impl(a, b):
            na = len(a)
            nb = len(b)
            n = min(na, nb)
            for i in range(n):
                ca, cb = left_code(a, i), right_code(b, i)
                if ca != cb:
                    return ca < cb
            return na < nb

        return lt_impl


@overload(operator.gt)
def charseq_gt(a, b):
    if not _same_kind(a, b):
        return
    left_code = _get_code_impl(a)
    right_code = _get_code_impl(b)
    if left_code is not None and right_code is not None:

        def gt_impl(a, b):
            return b < a

        return gt_impl


@overload(operator.le)
def charseq_le(a, b):
    if not _same_kind(a, b):
        return
    left_code = _get_code_impl(a)
    right_code = _get_code_impl(b)
    if left_code is not None and right_code is not None:

        def le_impl(a, b):
            return not (a > b)

        return le_impl


@overload(operator.ge)
def charseq_ge(a, b):
    if not _same_kind(a, b):
        return
    left_code = _get_code_impl(a)
    right_code = _get_code_impl(b)
    if left_code is not None and right_code is not None:

        def ge_impl(a, b):
            return not (a < b)

        return ge_impl


@overload(operator.contains)
def charseq_contains(a, b):
    if not _same_kind(a, b):
        return
    left_code = _get_code_impl(a)
    right_code = _get_code_impl(b)
    if left_code is not None and right_code is not None:
        if _is_bytes(a):

            def contains_impl(a, b):
                # Ideally, `return bytes(b) in bytes(a)` would be used
                # here, but numba Bytes does not implement
                # contains. So, using `unicode_type` implementation
                # here:
                return b._to_str() in a._to_str()

        else:

            def contains_impl(a, b):
                return str(b) in str(a)

        return contains_impl


@overload_method(types.UnicodeCharSeq, "isascii")
@overload_method(types.CharSeq, "isascii")
@overload_method(types.Bytes, "isascii")
def charseq_isascii(s):
    get_code = _get_code_impl(s)

    def impl(s):
        for i in range(len(s)):
            if get_code(s, i) > 127:
                return False
        return True

    return impl


@overload_method(types.UnicodeCharSeq, "_get_kind")
@overload_method(types.CharSeq, "_get_kind")
def charseq_get_kind(s):
    get_code = _get_code_impl(s)

    def impl(s):
        max_code = 0
        for i in range(len(s)):
            code = get_code(s, i)
            if code > max_code:
                max_code = code
        if max_code > 0xFFFF:
            return unicode.PY_UNICODE_4BYTE_KIND
        if max_code > 0xFF:
            return unicode.PY_UNICODE_2BYTE_KIND
        return unicode.PY_UNICODE_1BYTE_KIND

    return impl


@overload_method(types.UnicodeType, "_to_bytes")
def unicode_to_bytes_mth(s):
    """Convert unicode_type object to Bytes object.

    Note: The usage of _to_bytes method can be eliminated once all
    Python bytes operations are implemented for numba Bytes objects.

    """

    def impl(s):
        return _unicode_to_bytes(s)

    return impl


@overload_method(types.CharSeq, "_to_str")
@overload_method(types.Bytes, "_to_str")
def charseq_to_str_mth(s):
    """Convert bytes array item or bytes instance to UTF-8 str.

    Note: The usage of _to_str method can be eliminated once all
    Python bytes operations are implemented for numba Bytes objects.
    """
    get_code = _get_code_impl(s)

    def tostr_impl(s):
        n = len(s)
        is_ascii = s.isascii()
        result = unicode._empty_string(unicode.PY_UNICODE_1BYTE_KIND, n, is_ascii)
        for i in range(n):
            code = get_code(s, i)
            unicode._set_code_point(result, i, code)
        return result

    return tostr_impl


@overload_method(types.UnicodeCharSeq, "__str__")
def charseq_str(s):
    get_code = _get_code_impl(s)

    def str_impl(s):
        n = len(s)
        kind = s._get_kind()
        is_ascii = kind == 1 and s.isascii()
        result = unicode._empty_string(kind, n, is_ascii)
        for i in range(n):
            code = get_code(s, i)
            unicode._set_code_point(result, i, code)
        return result

    return str_impl


@overload(bytes)
def charseq_bytes(s):
    if isinstance(s, types.CharSeq):
        return lambda s: s


@overload_method(types.UnicodeCharSeq, "__hash__")
def unicode_charseq_hash(s):
    def impl(s):
        return hash(str(s))

    return impl


@overload_method(types.CharSeq, "__hash__")
def charseq_hash(s):
    def impl(s):
        # Ideally, `return hash(bytes(s))` would be used here but
        # numba Bytes does not implement hash (yet). However, for a
        # UTF-8 string `s`, we have hash(bytes(s)) == hash(s), hence,
        # we can convert CharSeq object to unicode_type and reuse its
        # hash implementation:
        return hash(s._to_str())

    return impl


@overload_method(types.UnicodeCharSeq, "isupper")
def unicode_charseq_isupper(s):
    def impl(s):
        # workaround unicode_type.isupper bug: it returns int value
        return not not str(s).isupper()

    return impl


@overload_method(types.CharSeq, "isupper")
def charseq_isupper(s):
    def impl(s):
        # return bytes(s).isupper()  # TODO: implement isupper for Bytes
        return not not s._to_str().isupper()

    return impl


@overload_method(types.UnicodeCharSeq, "upper")
def unicode_charseq_upper(s):
    def impl(s):
        return str(s).upper()

    return impl


@overload_method(types.CharSeq, "upper")
def charseq_upper(s):
    def impl(s):
        # return bytes(s).upper()  # TODO: implement upper for Bytes
        return s._to_str().upper()._to_bytes()

    return impl


@overload_method(types.UnicodeCharSeq, "find")
@overload_method(types.CharSeq, "find")
@overload_method(types.Bytes, "find")
def unicode_charseq_find(a, b):
    if isinstance(a, types.UnicodeCharSeq):
        if isinstance(b, types.UnicodeCharSeq):

            def impl(a, b):
                return str(a).find(str(b))

            return impl
        if isinstance(b, types.UnicodeType):

            def impl(a, b):
                return str(a).find(b)

            return impl
    if isinstance(a, types.CharSeq):
        if isinstance(b, (types.CharSeq, types.Bytes)):

            def impl(a, b):
                return a._to_str().find(b._to_str())

            return impl
    if isinstance(a, types.UnicodeType):
        if isinstance(b, types.UnicodeCharSeq):

            def impl(a, b):
                return a.find(str(b))

            return impl
    if isinstance(a, types.Bytes):
        if isinstance(b, types.CharSeq):

            def impl(a, b):
                return a._to_str().find(b._to_str())

            return impl


@overload_method(types.UnicodeCharSeq, "rfind")
@overload_method(types.CharSeq, "rfind")
@overload_method(types.Bytes, "rfind")
def unicode_charseq_rfind(a, b):
    if isinstance(a, types.UnicodeCharSeq):
        if isinstance(b, types.UnicodeCharSeq):

            def impl(a, b):
                return str(a).rfind(str(b))

            return impl
        if isinstance(b, types.UnicodeType):

            def impl(a, b):
                return str(a).rfind(b)

            return impl
    if isinstance(a, types.CharSeq):
        if isinstance(b, (types.CharSeq, types.Bytes)):

            def impl(a, b):
                return a._to_str().rfind(b._to_str())

            return impl
    if isinstance(a, types.UnicodeType):
        if isinstance(b, types.UnicodeCharSeq):

            def impl(a, b):
                return a.rfind(str(b))

            return impl
    if isinstance(a, types.Bytes):
        if isinstance(b, types.CharSeq):

            def impl(a, b):
                return a._to_str().rfind(b._to_str())

            return impl


@overload_method(types.UnicodeCharSeq, "startswith")
@overload_method(types.CharSeq, "startswith")
@overload_method(types.Bytes, "startswith")
def unicode_charseq_startswith(a, b):
    if isinstance(a, types.UnicodeCharSeq):
        if isinstance(b, types.UnicodeCharSeq):

            def impl(a, b):
                return str(a).startswith(str(b))

            return impl
        if isinstance(b, types.UnicodeType):

            def impl(a, b):
                return str(a).startswith(b)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if isinstance(b, (types.CharSeq, types.Bytes)):

            def impl(a, b):
                return a._to_str().startswith(b._to_str())

            return impl


@overload_method(types.UnicodeCharSeq, "endswith")
@overload_method(types.CharSeq, "endswith")
@overload_method(types.Bytes, "endswith")
def unicode_charseq_endswith(a, b):
    if isinstance(a, types.UnicodeCharSeq):
        if isinstance(b, types.UnicodeCharSeq):

            def impl(a, b):
                return str(a).endswith(str(b))

            return impl
        if isinstance(b, types.UnicodeType):

            def impl(a, b):
                return str(a).endswith(b)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if isinstance(b, (types.CharSeq, types.Bytes)):

            def impl(a, b):
                return a._to_str().endswith(b._to_str())

            return impl


@register_jitable
def _map_bytes(seq):
    return [s._to_bytes() for s in seq]


@overload_method(types.UnicodeCharSeq, "split")
@overload_method(types.CharSeq, "split")
@overload_method(types.Bytes, "split")
def unicode_charseq_split(a, sep=None, maxsplit=-1):
    if not (
        maxsplit == -1 or isinstance(maxsplit, (types.Omitted, types.Integer, types.IntegerLiteral))
    ):
        return None
    if isinstance(a, types.UnicodeCharSeq):
        if isinstance(sep, types.UnicodeCharSeq):

            def impl(a, sep=None, maxsplit=-1):
                return str(a).split(sep=str(sep), maxsplit=maxsplit)

            return impl
        if isinstance(sep, types.UnicodeType):

            def impl(a, sep=None, maxsplit=-1):
                return str(a).split(sep=sep, maxsplit=maxsplit)

            return impl
        if is_nonelike(sep):
            if is_default(maxsplit, -1):

                def impl(a, sep=None, maxsplit=-1):
                    return str(a).split()

            else:

                def impl(a, sep=None, maxsplit=-1):
                    return str(a).split(maxsplit=maxsplit)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if isinstance(sep, (types.CharSeq, types.Bytes)):

            def impl(a, sep=None, maxsplit=-1):
                return _map_bytes(a._to_str().split(sep._to_str(), maxsplit=maxsplit))

            return impl
        if is_nonelike(sep):
            if is_default(maxsplit, -1):

                def impl(a, sep=None, maxsplit=-1):
                    return _map_bytes(a._to_str().split())

            else:

                def impl(a, sep=None, maxsplit=-1):
                    return _map_bytes(a._to_str().split(maxsplit=maxsplit))

            return impl


# NOT IMPLEMENTED: rsplit


@overload_method(types.UnicodeCharSeq, "ljust")
@overload_method(types.CharSeq, "ljust")
@overload_method(types.Bytes, "ljust")
def unicode_charseq_ljust(a, width, fillchar=" "):
    if isinstance(a, types.UnicodeCharSeq):
        if is_default(fillchar, " "):

            def impl(a, width, fillchar=" "):
                return str(a).ljust(width)

            return impl
        elif isinstance(fillchar, types.UnicodeCharSeq):

            def impl(a, width, fillchar=" "):
                return str(a).ljust(width, str(fillchar))

            return impl
        elif isinstance(fillchar, types.UnicodeType):

            def impl(a, width, fillchar=" "):
                return str(a).ljust(width, fillchar)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if is_default(fillchar, " ") or is_default(fillchar, b" "):

            def impl(a, width, fillchar=" "):
                return a._to_str().ljust(width)._to_bytes()

            return impl
        elif isinstance(fillchar, (types.CharSeq, types.Bytes)):

            def impl(a, width, fillchar=" "):
                return a._to_str().ljust(width, fillchar._to_str())._to_bytes()

            return impl


@overload_method(types.UnicodeCharSeq, "rjust")
@overload_method(types.CharSeq, "rjust")
@overload_method(types.Bytes, "rjust")
def unicode_charseq_rjust(a, width, fillchar=" "):
    if isinstance(a, types.UnicodeCharSeq):
        if is_default(fillchar, " "):

            def impl(a, width, fillchar=" "):
                return str(a).rjust(width)

            return impl
        elif isinstance(fillchar, types.UnicodeCharSeq):

            def impl(a, width, fillchar=" "):
                return str(a).rjust(width, str(fillchar))

            return impl
        elif isinstance(fillchar, types.UnicodeType):

            def impl(a, width, fillchar=" "):
                return str(a).rjust(width, fillchar)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if is_default(fillchar, " ") or is_default(fillchar, b" "):

            def impl(a, width, fillchar=" "):
                return a._to_str().rjust(width)._to_bytes()

            return impl
        elif isinstance(fillchar, (types.CharSeq, types.Bytes)):

            def impl(a, width, fillchar=" "):
                return a._to_str().rjust(width, fillchar._to_str())._to_bytes()

            return impl


@overload_method(types.UnicodeCharSeq, "center")
@overload_method(types.CharSeq, "center")
@overload_method(types.Bytes, "center")
def unicode_charseq_center(a, width, fillchar=" "):
    if isinstance(a, types.UnicodeCharSeq):
        if is_default(fillchar, " "):

            def impl(a, width, fillchar=" "):
                return str(a).center(width)

            return impl
        elif isinstance(fillchar, types.UnicodeCharSeq):

            def impl(a, width, fillchar=" "):
                return str(a).center(width, str(fillchar))

            return impl
        elif isinstance(fillchar, types.UnicodeType):

            def impl(a, width, fillchar=" "):
                return str(a).center(width, fillchar)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if is_default(fillchar, " ") or is_default(fillchar, b" "):

            def impl(a, width, fillchar=" "):
                return a._to_str().center(width)._to_bytes()

            return impl
        elif isinstance(fillchar, (types.CharSeq, types.Bytes)):

            def impl(a, width, fillchar=" "):
                return a._to_str().center(width, fillchar._to_str())._to_bytes()

            return impl


@overload_method(types.UnicodeCharSeq, "zfill")
@overload_method(types.CharSeq, "zfill")
@overload_method(types.Bytes, "zfill")
def unicode_charseq_zfill(a, width):
    if isinstance(a, types.UnicodeCharSeq):

        def impl(a, width):
            return str(a).zfill(width)

        return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):

        def impl(a, width):
            return a._to_str().zfill(width)._to_bytes()

        return impl


@overload_method(types.UnicodeCharSeq, "lstrip")
@overload_method(types.CharSeq, "lstrip")
@overload_method(types.Bytes, "lstrip")
def unicode_charseq_lstrip(a, chars=None):
    if isinstance(a, types.UnicodeCharSeq):
        if is_nonelike(chars):

            def impl(a, chars=None):
                return str(a).lstrip()

            return impl
        elif isinstance(chars, types.UnicodeCharSeq):

            def impl(a, chars=None):
                return str(a).lstrip(str(chars))

            return impl
        elif isinstance(chars, types.UnicodeType):

            def impl(a, chars=None):
                return str(a).lstrip(chars)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if is_nonelike(chars):

            def impl(a, chars=None):
                return a._to_str().lstrip()._to_bytes()

            return impl
        elif isinstance(chars, (types.CharSeq, types.Bytes)):

            def impl(a, chars=None):
                return a._to_str().lstrip(chars._to_str())._to_bytes()

            return impl


@overload_method(types.UnicodeCharSeq, "rstrip")
@overload_method(types.CharSeq, "rstrip")
@overload_method(types.Bytes, "rstrip")
def unicode_charseq_rstrip(a, chars=None):
    if isinstance(a, types.UnicodeCharSeq):
        if is_nonelike(chars):

            def impl(a, chars=None):
                return str(a).rstrip()

            return impl
        elif isinstance(chars, types.UnicodeCharSeq):

            def impl(a, chars=None):
                return str(a).rstrip(str(chars))

            return impl
        elif isinstance(chars, types.UnicodeType):

            def impl(a, chars=None):
                return str(a).rstrip(chars)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if is_nonelike(chars):

            def impl(a, chars=None):
                return a._to_str().rstrip()._to_bytes()

            return impl
        elif isinstance(chars, (types.CharSeq, types.Bytes)):

            def impl(a, chars=None):
                return a._to_str().rstrip(chars._to_str())._to_bytes()

            return impl


@overload_method(types.UnicodeCharSeq, "strip")
@overload_method(types.CharSeq, "strip")
@overload_method(types.Bytes, "strip")
def unicode_charseq_strip(a, chars=None):
    if isinstance(a, types.UnicodeCharSeq):
        if is_nonelike(chars):

            def impl(a, chars=None):
                return str(a).strip()

            return impl
        elif isinstance(chars, types.UnicodeCharSeq):

            def impl(a, chars=None):
                return str(a).strip(str(chars))

            return impl
        elif isinstance(chars, types.UnicodeType):

            def impl(a, chars=None):
                return str(a).strip(chars)

            return impl
    if isinstance(a, (types.CharSeq, types.Bytes)):
        if is_nonelike(chars):

            def impl(a, chars=None):
                return a._to_str().strip()._to_bytes()

            return impl
        elif isinstance(chars, (types.CharSeq, types.Bytes)):

            def impl(a, chars=None):
                return a._to_str().strip(chars._to_str())._to_bytes()

            return impl


@overload_method(types.UnicodeCharSeq, "join")
@overload_method(types.CharSeq, "join")
@overload_method(types.Bytes, "join")
def unicode_charseq_join(a, parts):
    if isinstance(a, types.UnicodeCharSeq):
        # assuming parts contains UnicodeCharSeq or UnicodeType objects
        def impl(a, parts):
            _parts = [str(p) for p in parts]
            return str(a).join(_parts)

        return impl

    if isinstance(a, (types.CharSeq, types.Bytes)):
        # assuming parts contains CharSeq or Bytes objects
        def impl(a, parts):
            _parts = [p._to_str() for p in parts]
            return a._to_str().join(_parts)._to_bytes()

        return impl
