# Copyright (c) 2018-2020, NVIDIA CORPORATION.
import re
from contextlib import ExitStack as does_not_raise
from sys import getsizeof

import cupy
import numpy as np
import pandas as pd
import pyarrow as pa
import pytest

import cudf
from cudf import concat
from cudf.core import DataFrame, Series
from cudf.core.column.string import StringColumn
from cudf.core.index import StringIndex, as_index
from cudf.tests.utils import (
    DATETIME_TYPES,
    NUMERIC_TYPES,
    assert_eq,
    assert_exceptions_equal,
)
from cudf.utils import dtypes as dtypeutils

data_list = [
    ["AbC", "de", "FGHI", "j", "kLm"],
    ["nOPq", None, "RsT", None, "uVw"],
    [None, None, None, None, None],
]

data_id_list = ["no_nulls", "some_nulls", "all_nulls"]

idx_list = [None, [10, 11, 12, 13, 14]]

idx_id_list = ["None_index", "Set_index"]


def raise_builder(flags, exceptions):
    if any(flags):
        return pytest.raises(exceptions)
    else:
        return does_not_raise()


@pytest.fixture(params=data_list, ids=data_id_list)
def data(request):
    return request.param


@pytest.fixture(params=idx_list, ids=idx_id_list)
def index(request):
    return request.param


@pytest.fixture
def ps_gs(data, index):
    ps = pd.Series(data, index=index, dtype="str", name="nice name")
    gs = Series(data, index=index, dtype="str", name="nice name")
    return (ps, gs)


@pytest.mark.parametrize("construct", [list, np.array, pd.Series, pa.array])
def test_string_ingest(construct):
    expect = ["a", "a", "b", "c", "a"]
    data = construct(expect)
    got = Series(data)
    assert got.dtype == np.dtype("object")
    assert len(got) == 5
    for idx, val in enumerate(expect):
        assert expect[idx] == got[idx]


def test_string_export(ps_gs):
    ps, gs = ps_gs

    expect = ps
    got = gs.to_pandas()
    assert_eq(expect, got)

    expect = np.array(ps)
    got = gs.to_array()
    assert_eq(expect, got)

    expect = pa.Array.from_pandas(ps)
    got = gs.to_arrow()

    assert pa.Array.equals(expect, got)


@pytest.mark.parametrize(
    "item",
    [
        0,
        2,
        4,
        slice(1, 3),
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [0, 1, 2, 3, 4, 4, 3, 2, 1, 0],
        np.array([0, 1, 2, 3, 4]),
        cupy.asarray(np.array([0, 1, 2, 3, 4])),
    ],
)
def test_string_get_item(ps_gs, item):
    ps, gs = ps_gs

    got = gs.iloc[item]
    if isinstance(got, Series):
        got = got.to_arrow()

    if isinstance(item, cupy.ndarray):
        item = cupy.asnumpy(item)

    expect = ps.iloc[item]
    if isinstance(expect, pd.Series):
        expect = pa.Array.from_pandas(expect)
        pa.Array.equals(expect, got)
    else:
        if got is cudf.NA and expect is None:
            return
        assert expect == got


@pytest.mark.parametrize(
    "item",
    [
        [True] * 5,
        [False] * 5,
        np.array([True] * 5),
        np.array([False] * 5),
        cupy.asarray(np.array([True] * 5)),
        cupy.asarray(np.array([False] * 5)),
        np.random.randint(0, 2, 5).astype("bool").tolist(),
        np.random.randint(0, 2, 5).astype("bool"),
        cupy.asarray(np.random.randint(0, 2, 5).astype("bool")),
    ],
)
def test_string_bool_mask(ps_gs, item):
    ps, gs = ps_gs

    got = gs.iloc[item]
    if isinstance(got, Series):
        got = got.to_arrow()

    if isinstance(item, cupy.ndarray):
        item = cupy.asnumpy(item)

    expect = ps[item]
    if isinstance(expect, pd.Series):
        expect = pa.Array.from_pandas(expect)
        pa.Array.equals(expect, got)
    else:
        assert expect == got


@pytest.mark.parametrize("item", [0, slice(1, 3), slice(5)])
def test_string_repr(ps_gs, item):
    ps, gs = ps_gs

    got_out = gs.iloc[item]
    expect_out = ps.iloc[item]

    expect = str(expect_out)
    got = str(got_out)

    if got_out is not cudf.NA and len(got_out) > 1:
        expect = expect.replace("None", "<NA>")

    assert expect == got or (expect == "None" and got == "<NA>")


@pytest.mark.parametrize(
    "dtype", NUMERIC_TYPES + DATETIME_TYPES + ["bool", "object", "str"]
)
def test_string_astype(dtype):
    if (
        dtype.startswith("int")
        or dtype.startswith("uint")
        or dtype.startswith("long")
    ):
        data = ["1", "2", "3", "4", "5"]
    elif dtype.startswith("float"):
        data = ["1.0", "2.0", "3.0", "4.0", "5.0"]
    elif dtype.startswith("bool"):
        data = ["True", "False", "True", "False", "False"]
    elif dtype.startswith("datetime64"):
        data = [
            "2019-06-04T00:00:00Z",
            "2019-06-04T12:12:12Z",
            "2019-06-03T00:00:00Z",
            "2019-05-04T00:00:00Z",
            "2018-06-04T00:00:00Z",
            "1922-07-21T01:02:03Z",
        ]
    elif dtype == "str" or dtype == "object":
        data = ["ab", "cd", "ef", "gh", "ij"]
    ps = pd.Series(data)
    gs = Series(data)

    # Pandas str --> bool typecasting always returns True if there's a string
    if dtype.startswith("bool"):
        expect = ps == "True"
    else:
        expect = ps.astype(dtype)
    got = gs.astype(dtype)

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "dtype", NUMERIC_TYPES + DATETIME_TYPES + ["bool", "object", "str"]
)
def test_string_empty_astype(dtype):
    data = []
    ps = pd.Series(data, dtype="str")
    gs = Series(data, dtype="str")

    expect = ps.astype(dtype)
    got = gs.astype(dtype)

    assert_eq(expect, got)


@pytest.mark.parametrize("dtype", NUMERIC_TYPES + DATETIME_TYPES + ["bool"])
def test_string_numeric_astype(dtype):
    if dtype.startswith("bool"):
        data = [1, 0, 1, 0, 1]
    elif (
        dtype.startswith("int")
        or dtype.startswith("uint")
        or dtype.startswith("long")
    ):
        data = [1, 2, 3, 4, 5]
    elif dtype.startswith("float"):
        data = [1.0, 2.0, 3.0, 4.0, 5.0]
    elif dtype.startswith("datetime64"):
        # pandas rounds the output format based on the data
        # Use numpy instead
        # but fix '2011-01-01T00:00:00' -> '2011-01-01 00:00:00'
        data = [1000000001, 2000000001, 3000000001, 4000000001, 5000000001]
        ps = np.asarray(data, dtype=dtype).astype(str)
        ps = np.array([i.replace("T", " ") for i in ps])

    if not dtype.startswith("datetime64"):
        ps = pd.Series(data, dtype=dtype)

    gs = Series(data, dtype=dtype)

    expect = pd.Series(ps.astype("str"))
    got = gs.astype("str")

    assert_eq(expect, got)


@pytest.mark.parametrize("dtype", NUMERIC_TYPES + DATETIME_TYPES + ["bool"])
def test_string_empty_numeric_astype(dtype):
    data = []

    if dtype.startswith("datetime64"):
        ps = pd.Series(data, dtype="datetime64[ns]")
    else:
        ps = pd.Series(data, dtype=dtype)
    gs = Series(data, dtype=dtype)

    expect = ps.astype("str")
    got = gs.astype("str")

    assert_eq(expect, got)


def test_string_concat():
    data1 = ["a", "b", "c", "d", "e"]
    data2 = ["f", "g", "h", "i", "j"]
    index = [1, 2, 3, 4, 5]

    ps1 = pd.Series(data1, index=index)
    ps2 = pd.Series(data2, index=index)
    gs1 = Series(data1, index=index)
    gs2 = Series(data2, index=index)

    expect = pd.concat([ps1, ps2])
    got = concat([gs1, gs2])

    assert_eq(expect, got)

    expect = ps1.str.cat(ps2)
    got = gs1.str.cat(gs2)

    assert_eq(expect, got)


@pytest.mark.parametrize("ascending", [True, False])
def test_string_sort(ps_gs, ascending):
    ps, gs = ps_gs

    expect = ps.sort_values(ascending=ascending)
    got = gs.sort_values(ascending=ascending)

    assert_eq(expect, got)


def test_string_len(ps_gs):
    ps, gs = ps_gs

    expect = ps.str.len()
    got = gs.str.len()

    # Can't handle nulls in Pandas so use PyArrow instead
    # Pandas will return as a float64 so need to typecast to int32
    expect = pa.array(expect, from_pandas=True).cast(pa.int32())
    got = got.to_arrow()
    assert pa.Array.equals(expect, got)


def _cat_convert_seq_to_cudf(others):
    pd_others = others
    if isinstance(pd_others, (pd.Series, pd.Index)):
        gd_others = cudf.from_pandas(pd_others)
    else:
        gd_others = pd_others
    if isinstance(gd_others, (list, tuple)):
        temp_tuple = [
            cudf.from_pandas(elem)
            if isinstance(elem, (pd.Series, pd.Index))
            else elem
            for elem in gd_others
        ]

        if isinstance(gd_others, tuple):
            gd_others = tuple(temp_tuple)
        else:
            gd_others = list(temp_tuple)
    return gd_others


@pytest.mark.parametrize(
    "others",
    [
        None,
        ["f", "g", "h", "i", "j"],
        ("f", "g", "h", "i", "j"),
        pd.Series(["f", "g", "h", "i", "j"]),
        pd.Series(["AbC", "de", "FGHI", "j", "kLm"]),
        pytest.param(
            pd.Index(["f", "g", "h", "i", "j"]),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        pytest.param(
            pd.Index(["AbC", "de", "FGHI", "j", "kLm"]),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        (
            np.array(["f", "g", "h", "i", "j"]),
            np.array(["f", "g", "h", "i", "j"]),
        ),
        [
            np.array(["f", "g", "h", "i", "j"]),
            np.array(["f", "g", "h", "i", "j"]),
        ],
        [
            pd.Series(["f", "g", "h", "i", "j"]),
            pd.Series(["f", "g", "h", "i", "j"]),
        ],
        (
            pd.Series(["f", "g", "h", "i", "j"]),
            pd.Series(["f", "g", "h", "i", "j"]),
        ),
        [
            pd.Series(["f", "g", "h", "i", "j"]),
            np.array(["f", "g", "h", "i", "j"]),
        ],
        (
            pd.Series(["f", "g", "h", "i", "j"]),
            np.array(["f", "g", "h", "i", "j"]),
        ),
        pytest.param(
            (
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["1", "2", "3", "4", "5"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["f", "g", "h", "i", "j"]),
            ),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        pytest.param(
            [
                pd.Index(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["f", "g", "h", "i", "j"]),
            ],
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        [
            pd.Series(["hello", "world", "abc", "xyz", "pqr"]),
            pd.Series(["abc", "xyz", "hello", "pqr", "world"]),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=[10, 11, 12, 13, 14],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=[10, 15, 11, 13, 14],
            ),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=["10", "11", "12", "13", "14"],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=["10", "11", "12", "13", "14"],
            ),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=["10", "11", "12", "13", "14"],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=["10", "15", "11", "13", "14"],
            ),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=["1", "2", "3", "4", "5"],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=["10", "11", "12", "13", "14"],
            ),
        ],
    ],
)
@pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"])
@pytest.mark.parametrize("na_rep", [None, "", "null", "a"])
@pytest.mark.parametrize(
    "index",
    [
        ["1", "2", "3", "4", "5"],
        pd.Series(["1", "2", "3", "4", "5"]),
        pd.Index(["1", "2", "3", "4", "5"]),
    ],
)
def test_string_cat(ps_gs, others, sep, na_rep, index):
    ps, gs = ps_gs

    pd_others = others
    gd_others = _cat_convert_seq_to_cudf(others)

    expect = ps.str.cat(others=pd_others, sep=sep, na_rep=na_rep)
    got = gs.str.cat(others=gd_others, sep=sep, na_rep=na_rep)
    assert_eq(expect, got)

    ps.index = index
    gs.index = index

    expect = ps.str.cat(others=ps.index, sep=sep, na_rep=na_rep)
    got = gs.str.cat(others=gs.index, sep=sep, na_rep=na_rep)

    assert_eq(expect, got)

    expect = ps.str.cat(others=[ps.index] + [ps.index], sep=sep, na_rep=na_rep)
    got = gs.str.cat(others=[gs.index] + [gs.index], sep=sep, na_rep=na_rep)

    assert_eq(expect, got)

    expect = ps.str.cat(others=(ps.index, ps.index), sep=sep, na_rep=na_rep)
    got = gs.str.cat(others=(gs.index, gs.index), sep=sep, na_rep=na_rep)

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "data",
    [
        ["1", "2", "3", "4", "5"],
        ["a", "b", "c", "d", "e"],
        ["a", "b", "c", None, "e"],
    ],
)
@pytest.mark.parametrize(
    "others",
    [
        None,
        ["f", "g", "h", "i", "j"],
        ("f", "g", "h", "i", "j"),
        pd.Series(["f", "g", "h", "i", "j"]),
        pd.Series(["AbC", "de", "FGHI", "j", "kLm"]),
        pytest.param(
            pd.Index(["f", "g", "h", "i", "j"]),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        pytest.param(
            pd.Index(["AbC", "de", "FGHI", "j", "kLm"]),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        (
            np.array(["f", "g", "h", "i", "j"]),
            np.array(["f", "g", "h", "i", "j"]),
        ),
        [
            np.array(["f", "g", "h", "i", "j"]),
            np.array(["f", "g", "h", "i", "j"]),
        ],
        [
            pd.Series(["f", "g", "h", "i", "j"]),
            pd.Series(["f", "g", "h", "i", "j"]),
        ],
        pytest.param(
            (
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["1", "2", "3", "4", "5"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["f", "g", "h", "i", "j"]),
            ),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        pytest.param(
            [
                pd.Index(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["f", "g", "h", "i", "j"]),
            ],
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=["a", "b", "c", "d", "e"],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=["a", "b", "c", "d", "e"],
            ),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=[10, 11, 12, 13, 14],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=[10, 15, 11, 13, 14],
            ),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=["1", "2", "3", "4", "5"],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=["1", "2", "3", "4", "5"],
            ),
        ],
    ],
)
@pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"])
@pytest.mark.parametrize("na_rep", [None, "", "null", "a"])
@pytest.mark.parametrize("name", [None, "This is the name"])
def test_string_index_str_cat(data, others, sep, na_rep, name):
    pi, gi = pd.Index(data, name=name), cudf.Index(data, name=name)

    pd_others = others
    gd_others = _cat_convert_seq_to_cudf(others)

    expect = pi.str.cat(others=pd_others, sep=sep, na_rep=na_rep)
    got = gi.str.cat(others=gd_others, sep=sep, na_rep=na_rep)

    assert_eq(
        expect, got, exact=False,
    )


@pytest.mark.parametrize(
    "data",
    [
        pytest.param(
            ["a", None, "c", None, "e"],
            marks=pytest.mark.xfail(
                reason="https://github.com/rapidsai/cudf/issues/5862"
            ),
        ),
        ["a", "b", "c", "d", "a"],
    ],
)
@pytest.mark.parametrize(
    "others",
    [
        None,
        ["f", "g", "h", "i", "j"],
        pd.Series(["AbC", "de", "FGHI", "j", "kLm"]),
        pytest.param(
            pd.Index(["f", "g", "h", "i", "j"]),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        pytest.param(
            pd.Index(["AbC", "de", "FGHI", "j", "kLm"]),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        [
            np.array(["f", "g", "h", "i", "j"]),
            np.array(["f", "g", "h", "i", "j"]),
        ],
        [
            pd.Series(["f", "g", "h", "i", "j"]),
            pd.Series(["f", "g", "h", "i", "j"]),
        ],
        pytest.param(
            [
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "g", "h", "i", "j"]),
            ],
            marks=pytest.mark.xfail(
                reason="https://github.com/rapidsai/cudf/issues/5862"
            ),
        ),
        pytest.param(
            (
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Series(["f", "g", "h", "i", "j"]),
                np.array(["f", "a", "b", "f", "a"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["1", "2", "3", "4", "5"]),
                np.array(["f", "a", "b", "f", "a"]),
                pd.Index(["f", "g", "h", "i", "j"]),
            ),
            marks=pytest.mark.xfail(
                reason="https://github.com/pandas-dev/pandas/issues/33436"
            ),
        ),
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=["a", "b", "c", "d", "e"],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=["a", "b", "c", "d", "e"],
            ),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=[10, 11, 12, 13, 14],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=[10, 15, 11, 13, 14],
            ),
        ],
        [
            pd.Series(
                ["hello", "world", "abc", "xyz", "pqr"],
                index=["1", "2", "3", "4", "5"],
            ),
            pd.Series(
                ["abc", "xyz", "hello", "pqr", "world"],
                index=["1", "2", "3", "4", "5"],
            ),
        ],
    ],
)
@pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"])
@pytest.mark.parametrize("na_rep", [None, "", "null", "a"])
@pytest.mark.parametrize("name", [None, "This is the name"])
def test_string_index_duplicate_str_cat(data, others, sep, na_rep, name):
    pi, gi = pd.Index(data, name=name), cudf.Index(data, name=name)

    pd_others = others
    gd_others = _cat_convert_seq_to_cudf(others)

    got = gi.str.cat(others=gd_others, sep=sep, na_rep=na_rep)
    expect = pi.str.cat(others=pd_others, sep=sep, na_rep=na_rep)

    # TODO: Remove got.sort_values call once we have `join` param support
    # in `.str.cat`
    # https://github.com/rapidsai/cudf/issues/5862

    # TODO: Replace ``pd.Index(expect.to_series().sort_values())`` with
    # ``expect.sort_values()`` once the below issue is fixed
    # https://github.com/pandas-dev/pandas/issues/35584
    assert_eq(
        pd.Index(expect.to_series().sort_values())
        if not isinstance(expect, str)
        else expect,
        got.sort_values() if not isinstance(got, str) else got,
        exact=False,
    )


@pytest.mark.xfail(raises=ValueError)
@pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"])
@pytest.mark.parametrize("na_rep", [None, "", "null", "a"])
def test_string_cat_str(ps_gs, sep, na_rep):
    ps, gs = ps_gs

    got = gs.str.cat(gs.str, sep=sep, na_rep=na_rep)
    expect = ps.str.cat(ps.str, sep=sep, na_rep=na_rep)

    assert_eq(expect, got)


@pytest.mark.xfail(raises=(NotImplementedError, AttributeError))
@pytest.mark.parametrize("sep", [None, "", " ", "|", ",", "|||"])
def test_string_join(ps_gs, sep):
    ps, gs = ps_gs

    expect = ps.str.join(sep)
    got = gs.str.join(sep)

    assert_eq(expect, got)


@pytest.mark.parametrize("pat", [r"(a)", r"(f)", r"([a-z])", r"([A-Z])"])
@pytest.mark.parametrize("expand", [True, False])
@pytest.mark.parametrize("flags,flags_raise", [(0, 0), (1, 1)])
def test_string_extract(ps_gs, pat, expand, flags, flags_raise):
    ps, gs = ps_gs
    expectation = raise_builder([flags_raise], NotImplementedError)

    with expectation:
        expect = ps.str.extract(pat, flags=flags, expand=expand)
        got = gs.str.extract(pat, flags=flags, expand=expand)

        assert_eq(expect, got)


@pytest.mark.parametrize(
    "pat,regex",
    [
        ("a", False),
        ("a", True),
        ("f", False),
        (r"[a-z]", True),
        (r"[A-Z]", True),
        ("hello", False),
        ("FGHI", False),
    ],
)
@pytest.mark.parametrize("flags,flags_raise", [(0, 0), (1, 1)])
@pytest.mark.parametrize("na,na_raise", [(np.nan, 0), (None, 1), ("", 1)])
def test_string_contains(ps_gs, pat, regex, flags, flags_raise, na, na_raise):
    ps, gs = ps_gs

    expectation = raise_builder([flags_raise, na_raise], NotImplementedError)

    with expectation:
        expect = ps.str.contains(pat, flags=flags, na=na, regex=regex)
        got = gs.str.contains(pat, flags=flags, na=na, regex=regex)
        assert_eq(expect, got)


# Pandas isn't respect the `n` parameter so ignoring it in test parameters
@pytest.mark.parametrize(
    "pat,regex",
    [("a", False), ("f", False), (r"[a-z]", True), (r"[A-Z]", True)],
)
@pytest.mark.parametrize("repl", ["qwerty", "", " "])
@pytest.mark.parametrize("case,case_raise", [(None, 0), (True, 1), (False, 1)])
@pytest.mark.parametrize("flags,flags_raise", [(0, 0), (1, 1)])
def test_string_replace(
    ps_gs, pat, repl, case, case_raise, flags, flags_raise, regex
):
    ps, gs = ps_gs

    expectation = raise_builder([case_raise, flags_raise], NotImplementedError)

    with expectation:
        expect = ps.str.replace(pat, repl, case=case, flags=flags, regex=regex)
        got = gs.str.replace(pat, repl, case=case, flags=flags, regex=regex)

        assert_eq(expect, got)


def test_string_lower(ps_gs):
    ps, gs = ps_gs

    expect = ps.str.lower()
    got = gs.str.lower()

    assert_eq(expect, got)


def test_string_upper(ps_gs):
    ps, gs = ps_gs

    expect = ps.str.upper()
    got = gs.str.upper()

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "data",
    [
        ["a b", " c ", "   d", "e   ", "f"],
        ["a-b", "-c-", "---d", "e---", "f"],
        ["ab", "c", "d", "e", "f"],
        [None, None, None, None, None],
    ],
)
@pytest.mark.parametrize("pat", [None, " ", "-"])
@pytest.mark.parametrize("n", [-1, 0, 1, 3, 10])
@pytest.mark.parametrize("expand", [True, False, None])
def test_string_split(data, pat, n, expand):

    if data in (["a b", " c ", "   d", "e   ", "f"],) and pat is None:
        pytest.xfail("None pattern split algorithm not implemented yet")

    ps = pd.Series(data, dtype="str")
    gs = Series(data, dtype="str")

    expect = ps.str.split(pat=pat, n=n, expand=expand)
    got = gs.str.split(pat=pat, n=n, expand=expand)

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "str_data,str_data_raise",
    [
        ([], 0),
        (["a", "b", "c", "d", "e"], 0),
        ([None, None, None, None, None], 1),
    ],
)
@pytest.mark.parametrize("num_keys", [1, 2, 3])
@pytest.mark.parametrize("how", ["left", "right", "inner", "outer"])
def test_string_join_key(str_data, str_data_raise, num_keys, how):
    other_data = [1, 2, 3, 4, 5][: len(str_data)]

    pdf = pd.DataFrame()
    gdf = DataFrame()
    for i in range(num_keys):
        pdf[i] = pd.Series(str_data, dtype="str")
        gdf[i] = Series(str_data, dtype="str")
    pdf["a"] = other_data
    gdf["a"] = other_data

    pdf2 = pdf.copy()
    gdf2 = gdf.copy()

    expectation = raise_builder(
        [0 if how == "right" else str_data_raise], (AssertionError)
    )

    with expectation:
        expect = pdf.merge(pdf2, on=list(range(num_keys)), how=how)
        got = gdf.merge(gdf2, on=list(range(num_keys)), how=how)

        if len(expect) == 0 and len(got) == 0:
            expect = expect.reset_index(drop=True)
            got = got[expect.columns]

        assert_eq(expect, got)


@pytest.mark.parametrize(
    "str_data_nulls",
    [
        ["a", "b", "c"],
        ["a", "b", "f", "g"],
        ["f", "g", "h", "i", "j"],
        ["f", "g", "h"],
        [None, None, None, None, None],
        [],
    ],
)
def test_string_join_key_nulls(str_data_nulls):
    str_data = ["a", "b", "c", "d", "e"]
    other_data = [1, 2, 3, 4, 5]

    other_data_nulls = [6, 7, 8, 9, 10][: len(str_data_nulls)]

    pdf = pd.DataFrame()
    gdf = DataFrame()
    pdf["key"] = pd.Series(str_data, dtype="str")
    gdf["key"] = Series(str_data, dtype="str")
    pdf["vals"] = other_data
    gdf["vals"] = other_data

    pdf2 = pd.DataFrame()
    gdf2 = DataFrame()
    pdf2["key"] = pd.Series(str_data_nulls, dtype="str")
    gdf2["key"] = Series(str_data_nulls, dtype="str")
    pdf2["vals"] = pd.Series(other_data_nulls, dtype="int64")
    gdf2["vals"] = Series(other_data_nulls, dtype="int64")

    expect = pdf.merge(pdf2, on="key", how="left")
    got = gdf.merge(gdf2, on="key", how="left")
    got["vals_y"] = got["vals_y"].fillna(-1)

    if len(expect) == 0 and len(got) == 0:
        expect = expect.reset_index(drop=True)
        got = got[expect.columns]

    expect["vals_y"] = expect["vals_y"].fillna(-1).astype("int64")

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "str_data", [[], ["a", "b", "c", "d", "e"], [None, None, None, None, None]]
)
@pytest.mark.parametrize("num_cols", [1, 2, 3])
@pytest.mark.parametrize("how", ["left", "right", "inner", "outer"])
def test_string_join_non_key(str_data, num_cols, how):
    other_data = [1, 2, 3, 4, 5][: len(str_data)]

    pdf = pd.DataFrame()
    gdf = DataFrame()
    for i in range(num_cols):
        pdf[i] = pd.Series(str_data, dtype="str")
        gdf[i] = Series(str_data, dtype="str")
    pdf["a"] = other_data
    gdf["a"] = other_data

    pdf2 = pdf.copy()
    gdf2 = gdf.copy()

    expect = pdf.merge(pdf2, on=["a"], how=how)
    got = gdf.merge(gdf2, on=["a"], how=how)

    if len(expect) == 0 and len(got) == 0:
        expect = expect.reset_index(drop=True)
        got = got[expect.columns]

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "str_data_nulls",
    [
        ["a", "b", "c"],
        ["a", "b", "f", "g"],
        ["f", "g", "h", "i", "j"],
        ["f", "g", "h"],
        [None, None, None, None, None],
        [],
    ],
)
def test_string_join_non_key_nulls(str_data_nulls):
    str_data = ["a", "b", "c", "d", "e"]
    other_data = [1, 2, 3, 4, 5]

    other_data_nulls = [6, 7, 8, 9, 10][: len(str_data_nulls)]

    pdf = pd.DataFrame()
    gdf = DataFrame()
    pdf["vals"] = pd.Series(str_data, dtype="str")
    gdf["vals"] = Series(str_data, dtype="str")
    pdf["key"] = other_data
    gdf["key"] = other_data

    pdf2 = pd.DataFrame()
    gdf2 = DataFrame()
    pdf2["vals"] = pd.Series(str_data_nulls, dtype="str")
    gdf2["vals"] = Series(str_data_nulls, dtype="str")
    pdf2["key"] = pd.Series(other_data_nulls, dtype="int64")
    gdf2["key"] = Series(other_data_nulls, dtype="int64")

    expect = pdf.merge(pdf2, on="key", how="left")
    got = gdf.merge(gdf2, on="key", how="left")

    if len(expect) == 0 and len(got) == 0:
        expect = expect.reset_index(drop=True)
        got = got[expect.columns]

    assert_eq(expect, got)


def test_string_join_values_nulls():
    left_dict = [
        {"b": "MATCH 1", "a": 1.0},
        {"b": "MATCH 1", "a": 1.0},
        {"b": "LEFT NO MATCH 1", "a": -1.0},
        {"b": "MATCH 2", "a": 2.0},
        {"b": "MATCH 2", "a": 2.0},
        {"b": "MATCH 1", "a": 1.0},
        {"b": "MATCH 1", "a": 1.0},
        {"b": "MATCH 2", "a": 2.0},
        {"b": "MATCH 2", "a": 2.0},
        {"b": "LEFT NO MATCH 2", "a": -2.0},
        {"b": "MATCH 3", "a": 3.0},
        {"b": "MATCH 3", "a": 3.0},
    ]

    right_dict = [
        {"b": "RIGHT NO MATCH 1", "c": -1.0},
        {"b": "MATCH 3", "c": 3.0},
        {"b": "MATCH 2", "c": 2.0},
        {"b": "RIGHT NO MATCH 2", "c": -2.0},
        {"b": "RIGHT NO MATCH 3", "c": -3.0},
        {"b": "MATCH 1", "c": 1.0},
    ]

    left_pdf = pd.DataFrame(left_dict)
    right_pdf = pd.DataFrame(right_dict)

    left_gdf = DataFrame.from_pandas(left_pdf)
    right_gdf = DataFrame.from_pandas(right_pdf)

    expect = left_pdf.merge(right_pdf, how="left", on="b")
    got = left_gdf.merge(right_gdf, how="left", on="b")

    expect = expect.sort_values(by=["a", "b", "c"]).reset_index(drop=True)
    got = got.sort_values(by=["a", "b", "c"]).reset_index(drop=True)

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "str_data", [[], ["a", "b", "c", "d", "e"], [None, None, None, None, None]]
)
@pytest.mark.parametrize("num_keys", [1, 2, 3])
def test_string_groupby_key(str_data, num_keys):
    other_data = [1, 2, 3, 4, 5][: len(str_data)]

    pdf = pd.DataFrame()
    gdf = DataFrame()
    for i in range(num_keys):
        pdf[i] = pd.Series(str_data, dtype="str")
        gdf[i] = Series(str_data, dtype="str")
    pdf["a"] = other_data
    gdf["a"] = other_data

    expect = pdf.groupby(list(range(num_keys)), as_index=False).count()
    got = gdf.groupby(list(range(num_keys)), as_index=False).count()

    expect = expect.sort_values([0]).reset_index(drop=True)
    got = got.sort_values([0]).reset_index(drop=True)

    assert_eq(expect, got, check_dtype=False)


@pytest.mark.parametrize(
    "str_data", [[], ["a", "b", "c", "d", "e"], [None, None, None, None, None]]
)
@pytest.mark.parametrize("num_cols", [1, 2, 3])
@pytest.mark.parametrize("agg", ["count", "max", "min"])
def test_string_groupby_non_key(str_data, num_cols, agg):
    other_data = [1, 2, 3, 4, 5][: len(str_data)]

    pdf = pd.DataFrame()
    gdf = DataFrame()
    for i in range(num_cols):
        pdf[i] = pd.Series(str_data, dtype="str")
        gdf[i] = Series(str_data, dtype="str")
    pdf["a"] = other_data
    gdf["a"] = other_data

    expect = getattr(pdf.groupby("a", as_index=False), agg)()
    got = getattr(gdf.groupby("a", as_index=False), agg)()

    expect = expect.sort_values(["a"]).reset_index(drop=True)
    got = got.sort_values(["a"]).reset_index(drop=True)

    if agg in ["min", "max"] and len(expect) == 0 and len(got) == 0:
        for i in range(num_cols):
            expect[i] = expect[i].astype("str")

    assert_eq(expect, got, check_dtype=False)


def test_string_groupby_key_index():
    str_data = ["a", "b", "c", "d", "e"]
    other_data = [1, 2, 3, 4, 5]

    pdf = pd.DataFrame()
    gdf = DataFrame()
    pdf["a"] = pd.Series(str_data, dtype="str")
    gdf["a"] = Series(str_data, dtype="str")
    pdf["b"] = other_data
    gdf["b"] = other_data

    expect = pdf.groupby("a").count()
    got = gdf.groupby("a").count()

    assert_eq(expect, got, check_dtype=False)


@pytest.mark.parametrize("scalar", ["a", None])
def test_string_set_scalar(scalar):
    pdf = pd.DataFrame()
    pdf["a"] = [1, 2, 3, 4, 5]
    gdf = DataFrame.from_pandas(pdf)

    pdf["b"] = "a"
    gdf["b"] = "a"

    assert_eq(pdf["b"], gdf["b"])
    assert_eq(pdf, gdf)


def test_string_index():
    from cudf.core.column import as_column

    pdf = pd.DataFrame(np.random.rand(5, 5))
    gdf = DataFrame.from_pandas(pdf)
    stringIndex = ["a", "b", "c", "d", "e"]
    pdf.index = stringIndex
    gdf.index = stringIndex
    assert_eq(pdf, gdf)
    stringIndex = np.array(["a", "b", "c", "d", "e"])
    pdf.index = stringIndex
    gdf.index = stringIndex
    assert_eq(pdf, gdf)
    stringIndex = StringIndex(["a", "b", "c", "d", "e"], name="name")
    pdf.index = stringIndex.to_pandas()
    gdf.index = stringIndex
    assert_eq(pdf, gdf)
    stringIndex = as_index(as_column(["a", "b", "c", "d", "e"]), name="name")
    pdf.index = stringIndex.to_pandas()
    gdf.index = stringIndex
    assert_eq(pdf, gdf)


@pytest.mark.parametrize(
    "item",
    [
        ["Cbe", "cbe", "CbeD", "Cb", "ghi", "Cb"],
        ["a", "a", "a", "a", "A"],
        ["A"],
        ["abc", "xyz", None, "ab", "123"],
        [None, None, "abc", None, "abc"],
    ],
)
def test_string_unique(item):
    ps = pd.Series(item)
    gs = Series(item)
    # Pandas `unique` returns a numpy array
    pres = pd.Series(ps.unique())
    # cudf returns sorted unique with `None` placed before other strings
    pres = pres.sort_values(na_position="first").reset_index(drop=True)
    gres = gs.unique()
    assert_eq(pres, gres)


def test_string_slice():
    df = DataFrame({"a": ["hello", "world"]})
    pdf = pd.DataFrame({"a": ["hello", "world"]})
    a_slice_got = df.a.str.slice(0, 2)
    a_slice_expected = pdf.a.str.slice(0, 2)

    assert isinstance(a_slice_got, Series)
    assert_eq(a_slice_expected, a_slice_got)


def test_string_equality():
    data1 = ["b", "c", "d", "a", "c"]
    data2 = ["a", None, "c", "a", "c"]

    ps1 = pd.Series(data1)
    ps2 = pd.Series(data2)
    gs1 = Series(data1)
    gs2 = Series(data2)

    expect = ps1 == ps2
    got = gs1 == gs2

    assert_eq(expect, got.fillna(False))

    expect = ps1 == "m"
    got = gs1 == "m"

    assert_eq(expect, got.fillna(False))

    ps1 = pd.Series(["a"])
    gs1 = Series(["a"])

    expect = ps1 == "m"
    got = gs1 == "m"

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "lhs",
    [
        ["Cbe", "cbe", "CbeD", "Cb", "ghi", "Cb"],
        ["abc", "xyz", "a", "ab", "123", "097"],
    ],
)
@pytest.mark.parametrize(
    "rhs",
    [
        ["Cbe", "cbe", "CbeD", "Cb", "ghi", "Cb"],
        ["a", "a", "a", "a", "A", "z"],
    ],
)
def test_string_binary_op_add(lhs, rhs):
    pds = pd.Series(lhs) + pd.Series(rhs)
    gds = Series(lhs) + Series(rhs)

    assert_eq(pds, gds)


@pytest.mark.parametrize("name", [None, "new name", 123])
def test_string_misc_name(ps_gs, name):
    ps, gs = ps_gs
    ps.name = name
    gs.name = name

    expect = ps.str.slice(0, 1)
    got = gs.str.slice(0, 1)

    assert_eq(expect, got)
    assert_eq(ps + ps, gs + gs)
    assert_eq(ps + "RAPIDS", gs + "RAPIDS")
    assert_eq("RAPIDS" + ps, "RAPIDS" + gs)


def test_string_no_children_properties():
    empty_col = StringColumn(children=())
    assert empty_col.base_children == ()
    assert empty_col.base_size == 0

    assert empty_col.children == ()
    assert empty_col.size == 0

    assert empty_col._nbytes == 0
    assert getsizeof(empty_col) >= 0  # Accounts for Python GC overhead


@pytest.mark.parametrize(
    "string",
    [
        ["Cbe", "cbe", "CbeD", "Cb", "ghi", "Cb"],
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["abcdefghij", "0123456789", "9876543210", None, "accénted", ""],
    ],
)
@pytest.mark.parametrize(
    "index", [-100, -5, -2, -6, -1, 0, 1, 2, 3, 9, 10, 100]
)
def test_string_get(string, index):
    pds = pd.Series(string)
    gds = Series(string)

    assert_eq(
        pds.str.get(index).fillna(""), gds.str.get(index).fillna(""),
    )


@pytest.mark.parametrize(
    "string",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["abcdefghij", "0123456789", "9876543210", None, "accénted", ""],
        ["koala", "fox", "chameleon"],
    ],
)
@pytest.mark.parametrize(
    "number", [-10, 0, 1, 3, 10],
)
@pytest.mark.parametrize(
    "diff", [0, 2, 5, 9],
)
def test_string_slice_str(string, number, diff):
    pds = pd.Series(string)
    gds = Series(string)

    assert_eq(pds.str.slice(start=number), gds.str.slice(start=number))
    assert_eq(pds.str.slice(stop=number), gds.str.slice(stop=number))
    assert_eq(pds.str.slice(), gds.str.slice())
    assert_eq(
        pds.str.slice(start=number, stop=number + diff),
        gds.str.slice(start=number, stop=number + diff),
    )
    if diff != 0:
        assert_eq(pds.str.slice(step=diff), gds.str.slice(step=diff))
        assert_eq(
            pds.str.slice(start=number, stop=number + diff, step=diff),
            gds.str.slice(start=number, stop=number + diff, step=diff),
        )


def test_string_slice_from():
    gs = Series(["hello world", "holy accéntéd", "batman", None, ""])
    d_starts = Series([2, 3, 0, -1, -1], dtype=np.int32)
    d_stops = Series([-1, -1, 0, -1, -1], dtype=np.int32)
    got = gs.str.slice_from(starts=d_starts._column, stops=d_stops._column)
    expected = Series(["llo world", "y accéntéd", "", None, ""])
    assert_eq(got, expected)


@pytest.mark.parametrize(
    "string",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["abcdefghij", "0123456789", "9876543210", None, "accénted", ""],
        ["koala", "fox", "chameleon"],
    ],
)
@pytest.mark.parametrize("number", [0, 1, 10])
@pytest.mark.parametrize("diff", [0, 2, 9])
@pytest.mark.parametrize("repr", ["2", "!!"])
def test_string_slice_replace(string, number, diff, repr):
    pds = pd.Series(string)
    gds = Series(string)

    assert_eq(
        pds.str.slice_replace(start=number, repl=repr),
        gds.str.slice_replace(start=number, repl=repr),
        check_dtype=False,
    )
    assert_eq(
        pds.str.slice_replace(stop=number, repl=repr),
        gds.str.slice_replace(stop=number, repl=repr),
    )
    assert_eq(pds.str.slice_replace(), gds.str.slice_replace())
    assert_eq(
        pds.str.slice_replace(start=number, stop=number + diff),
        gds.str.slice_replace(start=number, stop=number + diff),
    )
    assert_eq(
        pds.str.slice_replace(start=number, stop=number + diff, repl=repr),
        gds.str.slice_replace(start=number, stop=number + diff, repl=repr),
        check_dtype=False,
    )


def test_string_insert():
    gs = Series(["hello world", "holy accéntéd", "batman", None, ""])

    ps = pd.Series(["hello world", "holy accéntéd", "batman", None, ""])

    assert_eq(gs.str.insert(0, ""), gs)
    assert_eq(gs.str.insert(0, "+"), "+" + ps)
    assert_eq(gs.str.insert(-1, "---"), ps + "---")
    assert_eq(
        gs.str.insert(5, "---"),
        ps.str.slice(stop=5) + "---" + ps.str.slice(start=5),
    )


_string_char_types_data = [
    ["abc", "xyz", "a", "ab", "123", "097"],
    ["abcdefghij", "0123456789", "9876543210", None, "accénted", ""],
    ["koala", "fox", "chameleon"],
    [
        "1234567890",
        "de",
        "1.75",
        "-34",
        "+9.8",
        "7¼",
        "x³",
        "2³",
        "12⅝",
        "",
        "\t\r\n ",
    ],
    ["one", "one1", "1", ""],
    ["A B", "1.5", "3,000"],
    ["23", "³", "⅕", ""],
    [" ", "\t\r\n ", ""],
    ["leopard", "Golden Eagle", "SNAKE", ""],
    [r"¯\_(ツ)_/¯", "(╯°□°)╯︵ ┻━┻", "┬─┬ノ( º _ ºノ)"],
    ["a1", "A1", "a!", "A!", "!1", "aA"],
]


@pytest.mark.parametrize(
    "type_op",
    [
        "isdecimal",
        "isalnum",
        "isalpha",
        "isdigit",
        "isnumeric",
        "isupper",
        "islower",
    ],
)
@pytest.mark.parametrize("data", _string_char_types_data)
def test_string_char_types(type_op, data):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(getattr(gs.str, type_op)(), getattr(ps.str, type_op)())


def test_string_filter_alphanum():
    data = ["1234567890", "!@#$%^&*()", ",./<>?;:[]}{|+=", "abc DEF"]
    expected = []
    for st in data:
        rs = ""
        for c in st:
            if str.isalnum(c):
                rs = rs + c
        expected.append(rs)

    gs = Series(data)
    assert_eq(gs.str.filter_alphanum(), Series(expected))

    expected = []
    for st in data:
        rs = ""
        for c in st:
            if not str.isalnum(c):
                rs = rs + c
        expected.append(rs)
    assert_eq(gs.str.filter_alphanum(keep=False), Series(expected))

    expected = []
    for st in data:
        rs = ""
        for c in st:
            if str.isalnum(c):
                rs = rs + c
            else:
                rs = rs + "*"
        expected.append(rs)
    assert_eq(gs.str.filter_alphanum("*"), Series(expected))

    expected = []
    for st in data:
        rs = ""
        for c in st:
            if not str.isalnum(c):
                rs = rs + c
            else:
                rs = rs + "*"
        expected.append(rs)
    assert_eq(gs.str.filter_alphanum("*", keep=False), Series(expected))


@pytest.mark.parametrize(
    "case_op", ["title", "capitalize", "lower", "upper", "swapcase"]
)
@pytest.mark.parametrize(
    "data",
    [
        *_string_char_types_data,
        [
            None,
            "The quick bRoWn fox juMps over the laze DOG",
            '123nr98nv9rev!$#INF4390v03n1243<>?}{:-"',
            "accénted",
        ],
    ],
)
def test_string_char_case(case_op, data):
    gs = Series(data)
    ps = pd.Series(data)

    s = gs.str
    a = getattr(s, case_op)

    assert_eq(a(), getattr(ps.str, case_op)())

    assert_eq(gs.str.capitalize(), ps.str.capitalize())
    assert_eq(gs.str.isdecimal(), ps.str.isdecimal())
    assert_eq(gs.str.isalnum(), ps.str.isalnum())
    assert_eq(gs.str.isalpha(), ps.str.isalpha())
    assert_eq(gs.str.isdigit(), ps.str.isdigit())
    assert_eq(gs.str.isnumeric(), ps.str.isnumeric())
    assert_eq(gs.str.isspace(), ps.str.isspace())

    assert_eq(gs.str.isempty(), ps == "")


@pytest.mark.parametrize(
    "data",
    [
        ["koala", "fox", "chameleon"],
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
    ],
)
def test_strings_rpartition(data):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(ps.str.rpartition(), gs.str.rpartition())
    assert_eq(ps.str.rpartition("-"), gs.str.rpartition("-"))
    assert_eq(ps.str.rpartition(","), gs.str.rpartition(","))


@pytest.mark.parametrize(
    "data",
    [
        ["koala", "fox", "chameleon"],
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
    ],
)
def test_strings_partition(data):
    gs = Series(data, name="str_name")
    ps = pd.Series(data, name="str_name")

    assert_eq(ps.str.partition(), gs.str.partition())
    assert_eq(ps.str.partition(","), gs.str.partition(","))
    assert_eq(ps.str.partition("-"), gs.str.partition("-"))

    gi = as_index(data, name="new name")
    pi = pd.Index(data, name="new name")
    assert_eq(pi.str.partition(), gi.str.partition())
    assert_eq(pi.str.partition(","), gi.str.partition(","))
    assert_eq(pi.str.partition("-"), gi.str.partition("-"))


@pytest.mark.parametrize(
    "data",
    [
        ["koala", "fox", "chameleon"],
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        [
            "this is a regular sentence",
            "https://docs.python.org/3/tutorial/index.html",
            None,
        ],
    ],
)
@pytest.mark.parametrize("n", [-1, 2, 1, 9])
@pytest.mark.parametrize("expand", [True, False, None])
def test_strings_rsplit(data, n, expand):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(
        ps.str.rsplit(n=n, expand=expand).reset_index(),
        gs.str.rsplit(n=n, expand=expand).reset_index(),
        check_index_type=False,
    )
    assert_eq(
        ps.str.rsplit(",", n=n, expand=expand),
        gs.str.rsplit(",", n=n, expand=expand),
    )
    assert_eq(
        ps.str.rsplit("-", n=n, expand=expand),
        gs.str.rsplit("-", n=n, expand=expand),
    )


@pytest.mark.parametrize(
    "data",
    [
        ["koala", "fox", "chameleon"],
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        [
            "this is a regular sentence",
            "https://docs.python.org/3/tutorial/index.html",
            None,
        ],
    ],
)
@pytest.mark.parametrize("n", [-1, 2, 1, 9])
@pytest.mark.parametrize("expand", [True, False, None])
def test_strings_split(data, n, expand):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(
        ps.str.split(n=n, expand=expand).reset_index(),
        gs.str.split(n=n, expand=expand).reset_index(),
        check_index_type=False,
    )

    assert_eq(
        ps.str.split(",", n=n, expand=expand),
        gs.str.split(",", n=n, expand=expand),
    )
    assert_eq(
        ps.str.split("-", n=n, expand=expand),
        gs.str.split("-", n=n, expand=expand),
    )


@pytest.mark.parametrize(
    "data",
    [
        ["koala", "fox", "chameleon"],
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        [
            "this is a regular sentence",
            "https://docs.python.org/3/tutorial/index.html",
            None,
        ],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
@pytest.mark.parametrize(
    "to_strip", ["⅕", None, "123.", ".!? \n\t", "123.!? \n\t", " ", ".", ","]
)
def test_strings_strip_tests(data, to_strip):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(ps.str.strip(to_strip=to_strip), gs.str.strip(to_strip=to_strip))
    assert_eq(
        ps.str.rstrip(to_strip=to_strip), gs.str.rstrip(to_strip=to_strip)
    )
    assert_eq(
        ps.str.lstrip(to_strip=to_strip), gs.str.lstrip(to_strip=to_strip)
    )

    gi = as_index(data)
    pi = pd.Index(data)

    assert_eq(pi.str.strip(to_strip=to_strip), gi.str.strip(to_strip=to_strip))
    assert_eq(
        pi.str.rstrip(to_strip=to_strip), gi.str.rstrip(to_strip=to_strip)
    )
    assert_eq(
        pi.str.lstrip(to_strip=to_strip), gi.str.lstrip(to_strip=to_strip)
    )


@pytest.mark.parametrize(
    "data",
    [
        ["koala", "fox", "chameleon"],
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        [
            "this is a regular sentence",
            "https://docs.python.org/3/tutorial/index.html",
            None,
        ],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
@pytest.mark.parametrize("width", [0, 1, 4, 9, 100])
@pytest.mark.parametrize("fillchar", ["⅕", "1", ".", "t", " ", ","])
def test_strings_filling_tests(data, width, fillchar):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(
        ps.str.center(width=width, fillchar=fillchar),
        gs.str.center(width=width, fillchar=fillchar),
    )
    assert_eq(
        ps.str.ljust(width=width, fillchar=fillchar),
        gs.str.ljust(width=width, fillchar=fillchar),
    )
    assert_eq(
        ps.str.rjust(width=width, fillchar=fillchar),
        gs.str.rjust(width=width, fillchar=fillchar),
    )

    gi = as_index(data)
    pi = pd.Index(data)

    assert_eq(
        pi.str.center(width=width, fillchar=fillchar),
        gi.str.center(width=width, fillchar=fillchar),
    )
    assert_eq(
        pi.str.ljust(width=width, fillchar=fillchar),
        gi.str.ljust(width=width, fillchar=fillchar),
    )
    assert_eq(
        pi.str.rjust(width=width, fillchar=fillchar),
        gi.str.rjust(width=width, fillchar=fillchar),
    )


@pytest.mark.parametrize(
    "data",
    [
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["+23", "³", "⅕", ""],
        ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
        [" ", "\t\r\n ", ""],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
@pytest.mark.parametrize("width", [0, 1, 4, 6, 9, 100])
def test_strings_zfill_tests(data, width):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(ps.str.zfill(width=width), gs.str.zfill(width=width))

    gi = as_index(data)
    pi = pd.Index(data)

    assert_eq(pi.str.zfill(width=width), gi.str.zfill(width=width))


@pytest.mark.parametrize(
    "data",
    [
        ["A,,B", "1,,5", "3,00,0"],
        ["Linda van der Berg", "George Pitt-Rivers"],
        ["+23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
@pytest.mark.parametrize("width", [0, 1, 4, 9, 100])
@pytest.mark.parametrize(
    "side", ["left", "right", "both"],
)
@pytest.mark.parametrize("fillchar", [" ", ".", "\n", "+", "\t"])
def test_strings_pad_tests(data, width, side, fillchar):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(
        ps.str.pad(width=width, side=side, fillchar=fillchar),
        gs.str.pad(width=width, side=side, fillchar=fillchar),
    )

    gi = as_index(data)
    pi = pd.Index(data)

    assert_eq(
        pi.str.pad(width=width, side=side, fillchar=fillchar),
        gi.str.pad(width=width, side=side, fillchar=fillchar),
    )


@pytest.mark.parametrize(
    "data",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["A B", "1.5", "3,000"],
        ["23", "³", "⅕", ""],
        # [" ", "\t\r\n ", ""],
        ["leopard", "Golden Eagle", "SNAKE", ""],
        ["line to be wrapped", "another line to be wrapped"],
    ],
)
@pytest.mark.parametrize("width", [1, 4, 8, 12, 100])
def test_string_wrap(data, width):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(
        gs.str.wrap(width=width),
        ps.str.wrap(
            width=width,
            break_long_words=False,
            expand_tabs=False,
            replace_whitespace=True,
            drop_whitespace=True,
            break_on_hyphens=False,
        ),
    )

    gi = as_index(data)
    pi = pd.Index(data)

    assert_eq(
        gi.str.wrap(width=width),
        pi.str.wrap(
            width=width,
            break_long_words=False,
            expand_tabs=False,
            replace_whitespace=True,
            drop_whitespace=True,
            break_on_hyphens=False,
        ),
    )


@pytest.mark.parametrize(
    "data",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["A B", "1.5", "3,000"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
        ["line to be wrapped", "another line to be wrapped"],
    ],
)
@pytest.mark.parametrize("pat", ["a", " ", "\t", "another", "0", r"\$"])
def test_string_count(data, pat):
    gs = Series(data)
    ps = pd.Series(data)

    assert_eq(gs.str.count(pat=pat), ps.str.count(pat=pat), check_dtype=False)
    assert_eq(as_index(gs).str.count(pat=pat), pd.Index(ps).str.count(pat=pat))


def test_string_findall():
    ps = pd.Series(["Lion", "Monkey", "Rabbit"])
    gs = Series(["Lion", "Monkey", "Rabbit"])

    assert_eq(ps.str.findall("Monkey")[1][0], gs.str.findall("Monkey")[0][1])
    assert_eq(ps.str.findall("on")[0][0], gs.str.findall("on")[0][0])
    assert_eq(ps.str.findall("on")[1][0], gs.str.findall("on")[0][1])
    assert_eq(ps.str.findall("on$")[0][0], gs.str.findall("on$")[0][0])
    assert_eq(ps.str.findall("b")[2][1], gs.str.findall("b")[1][2])


def test_string_replace_multi():
    ps = pd.Series(["hello", "goodbye"])
    gs = Series(["hello", "goodbye"])
    expect = ps.str.replace("e", "E").str.replace("o", "O")
    got = gs.str.replace(["e", "o"], ["E", "O"])

    assert_eq(expect, got)

    ps = pd.Series(["foo", "fuz", np.nan])
    gs = Series.from_pandas(ps)

    expect = ps.str.replace("f.", "ba", regex=True)
    got = gs.str.replace(["f."], ["ba"], regex=True)
    assert_eq(expect, got)

    ps = pd.Series(["f.o", "fuz", np.nan])
    gs = Series.from_pandas(ps)

    expect = ps.str.replace("f.", "ba", regex=False)
    got = gs.str.replace(["f."], ["ba"], regex=False)
    assert_eq(expect, got)


@pytest.mark.parametrize(
    "find",
    [
        "(\\d)(\\d)",
        "(\\d)(\\d)",
        "(\\d)(\\d)",
        "(\\d)(\\d)",
        "([a-z])-([a-z])",
        "([a-z])-([a-zé])",
        "([a-z])-([a-z])",
        "([a-z])-([a-zé])",
    ],
)
@pytest.mark.parametrize(
    "replace",
    ["\\1-\\2", "V\\2-\\1", "\\1 \\2", "\\2 \\1", "X\\1+\\2Z", "X\\1+\\2Z"],
)
def test_string_replace_with_backrefs(find, replace):
    s = [
        "A543",
        "Z756",
        "",
        None,
        "tést-string",
        "two-thréé four-fivé",
        "abcd-éfgh",
        "tést-string-again",
    ]
    ps = pd.Series(s)
    gs = Series(s)
    got = gs.str.replace_with_backrefs(find, replace)
    expected = ps.str.replace(find, replace, regex=True)
    assert_eq(got, expected)

    got = as_index(gs).str.replace_with_backrefs(find, replace)
    expected = pd.Index(ps).str.replace(find, replace, regex=True)
    assert_eq(got, expected)


def test_string_table_view_creation():
    data = ["hi"] * 25 + [None] * 2027
    psr = pd.Series(data)
    gsr = Series.from_pandas(psr)

    expect = psr[:1]
    got = gsr[:1]

    assert_eq(expect, got)


@pytest.mark.parametrize(
    "data",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["A B", "1.5", "3,000"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
        ["line to be wrapped", "another line to be wrapped"],
        ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
@pytest.mark.parametrize(
    "pat", ["", None, " ", "a", "abc", "cat", "$", "\n"],
)
def test_string_starts_ends(data, pat):
    ps = pd.Series(data)
    gs = Series(data)

    assert_eq(
        ps.str.startswith(pat), gs.str.startswith(pat), check_dtype=False
    )
    assert_eq(ps.str.endswith(pat), gs.str.endswith(pat), check_dtype=False)


@pytest.mark.parametrize(
    "data,pat",
    [
        (
            ["abc", "xyz", "a", "ab", "123", "097"],
            ["abc", "x", "a", "b", "3", "7"],
        ),
        (["A B", "1.5", "3,000"], ["A ", ".", ","]),
        (["23", "³", "⅕", ""], ["23", "³", "⅕", ""]),
        ([" ", "\t\r\n ", ""], ["d", "\n ", ""]),
        (
            ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
            ["$", "$", "a", "<", "(", "#"],
        ),
        (
            ["line to be wrapped", "another line to be wrapped"],
            ["another", "wrapped"],
        ),
        (
            ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
            ["hsdjfk", None, "ll", "+", "-", "w", "-", "én"],
        ),
        (
            ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
            ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
        ),
    ],
)
def test_string_starts_ends_list_like_pat(data, pat):
    gs = Series(data)

    starts_expected = []
    ends_expected = []
    for i in range(len(pat)):
        if data[i] is None:
            starts_expected.append(None)
            ends_expected.append(None)
        else:
            if pat[i] is None:
                starts_expected.append(False)
                ends_expected.append(False)
            else:
                starts_expected.append(data[i].startswith(pat[i]))
                ends_expected.append(data[i].endswith(pat[i]))
    starts_expected = pd.Series(starts_expected)
    ends_expected = pd.Series(ends_expected)
    assert_eq(starts_expected, gs.str.startswith(pat), check_dtype=False)
    assert_eq(ends_expected, gs.str.endswith(pat), check_dtype=False)


@pytest.mark.parametrize(
    "data",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["A B", "1.5", "3,000"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
        ["line to be wrapped", "another line to be wrapped"],
        ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
@pytest.mark.parametrize(
    "sub", ["", " ", "a", "abc", "cat", "$", "\n"],
)
def test_string_find(data, sub):
    ps = pd.Series(data)
    gs = Series(data)

    got = gs.str.find(sub)
    expect = ps.str.find(sub)
    assert_eq(
        expect, got, check_dtype=False,
    )

    got = gs.str.find(sub, start=1)
    expect = ps.str.find(sub, start=1)
    assert_eq(
        expect, got, check_dtype=False,
    )

    got = gs.str.find(sub, end=10)
    expect = ps.str.find(sub, end=10)
    assert_eq(
        expect, got, check_dtype=False,
    )

    got = gs.str.find(sub, start=2, end=10)
    expect = ps.str.find(sub, start=2, end=10)
    assert_eq(
        expect, got, check_dtype=False,
    )

    got = gs.str.rfind(sub)
    expect = ps.str.rfind(sub)
    assert_eq(
        expect, got, check_dtype=False,
    )

    got = gs.str.rfind(sub, start=1)
    expect = ps.str.rfind(sub, start=1)
    assert_eq(
        expect, got, check_dtype=False,
    )

    got = gs.str.rfind(sub, end=10)
    expect = ps.str.rfind(sub, end=10)
    assert_eq(
        expect, got, check_dtype=False,
    )

    got = gs.str.rfind(sub, start=2, end=10)
    expect = ps.str.rfind(sub, start=2, end=10)
    assert_eq(
        expect, got, check_dtype=False,
    )


@pytest.mark.parametrize(
    "data,sub,er",
    [
        (["abc", "xyz", "a", "ab", "123", "097"], "a", ValueError),
        (["A B", "1.5", "3,000"], "abc", ValueError),
        (["23", "³", "⅕", ""], "⅕", ValueError),
        ([" ", "\t\r\n ", ""], "\n", ValueError),
        (["$", "B", "Aab$", "$$ca", "C$B$", "cat"], "$", ValueError),
        (["line to be wrapped", "another line to be wrapped"], " ", None),
        (
            ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
            "+",
            ValueError,
        ),
        (["line to be wrapped", "another line to be wrapped"], "", None),
    ],
)
def test_string_str_index(data, sub, er):
    ps = pd.Series(data)
    gs = Series(data)

    if er is None:
        assert_eq(ps.str.index(sub), gs.str.index(sub), check_dtype=False)

    try:
        ps.str.index(sub)
    except er:
        pass
    else:
        assert not er

    try:
        gs.str.index(sub)
    except er:
        pass
    else:
        assert not er


@pytest.mark.parametrize(
    "data,sub,er",
    [
        (["abc", "xyz", "a", "ab", "123", "097"], "a", ValueError),
        (["A B", "1.5", "3,000"], "abc", ValueError),
        (["23", "³", "⅕", ""], "⅕", ValueError),
        ([" ", "\t\r\n ", ""], "\n", ValueError),
        (["$", "B", "Aab$", "$$ca", "C$B$", "cat"], "$", ValueError),
        (["line to be wrapped", "another line to be wrapped"], " ", None),
        (
            ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
            "+",
            ValueError,
        ),
        (["line to be wrapped", "another line to be wrapped"], "", None),
    ],
)
def test_string_str_rindex(data, sub, er):
    ps = pd.Series(data)
    gs = Series(data)

    if er is None:
        assert_eq(ps.str.rindex(sub), gs.str.rindex(sub), check_dtype=False)
        assert_eq(pd.Index(ps).str.rindex(sub), as_index(gs).str.rindex(sub))

    try:
        ps.str.rindex(sub)
    except er:
        pass
    else:
        assert not er

    try:
        gs.str.rindex(sub)
    except er:
        pass
    else:
        assert not er


@pytest.mark.parametrize(
    "data,sub,expect",
    [
        (
            ["abc", "xyz", "a", "ab", "123", "097"],
            ["b", "y", "a", "c", "4", "8"],
            [True, True, True, False, False, False],
        ),
        (
            ["A B", "1.5", "3,000", "23", "³", "⅕"],
            ["A B", ".", ",", "1", " ", " "],
            [True, True, True, False, False, False],
        ),
        (
            [" ", "\t", "\r", "\f ", "\n", ""],
            ["", "\t", "\r", "xx", "yy", "zz"],
            [True, True, True, False, False, False],
        ),
        (
            ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
            ["$", "B", "ab", "*", "@", "dog"],
            [True, True, True, False, False, False],
        ),
        (
            ["hello", "there", "world", "-1234", None, "accént"],
            ["lo", "e", "o", "+1234", " ", "e"],
            [True, True, True, False, None, False],
        ),
        (
            ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", "", "x", None],
            ["A", "B", "C", " ", "y", "e"],
            [True, True, True, False, False, None],
        ),
    ],
)
def test_string_contains_multi(data, sub, expect):
    gs = Series(data)
    sub = Series(sub)
    got = gs.str.contains(sub)
    expect = Series(expect)
    assert_eq(expect, got, check_dtype=False)


@pytest.mark.parametrize(
    "data",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["A B", "1.5", "3,000"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
        ["line to be wrapped", "another line to be wrapped"],
        ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
@pytest.mark.parametrize("pat", ["", " ", "a", "abc", "cat", "$", "\n"])
def test_string_str_match(data, pat):
    ps = pd.Series(data)
    gs = Series(data)

    assert_eq(ps.str.match(pat), gs.str.match(pat))
    assert_eq(
        pd.Index(pd.Index(ps).str.match(pat)), as_index(gs).str.match(pat)
    )


@pytest.mark.parametrize(
    "data",
    [
        ["abc", "xyz", "a", "ab", "123", "097"],
        ["A B", "1.5", "3,000"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
        ["line to be wrapped", "another line to be wrapped"],
        ["hello", "there", "world", "+1234", "-1234", None, "accént", ""],
        ["1. Ant.  ", "2. Bee!\n", "3. Cat?\t", None],
    ],
)
def test_string_str_translate(data):
    ps = pd.Series(data)
    gs = Series(data)

    assert_eq(
        ps.str.translate(str.maketrans({"a": "z"})),
        gs.str.translate(str.maketrans({"a": "z"})),
    )
    assert_eq(
        pd.Index(ps).str.translate(str.maketrans({"a": "z"})),
        as_index(gs).str.translate(str.maketrans({"a": "z"})),
    )
    assert_eq(
        ps.str.translate(str.maketrans({"a": "z", "i": "$", "z": "1"})),
        gs.str.translate(str.maketrans({"a": "z", "i": "$", "z": "1"})),
    )
    assert_eq(
        pd.Index(ps).str.translate(
            str.maketrans({"a": "z", "i": "$", "z": "1"})
        ),
        as_index(gs).str.translate(
            str.maketrans({"a": "z", "i": "$", "z": "1"})
        ),
    )
    assert_eq(
        ps.str.translate(
            str.maketrans({"+": "-", "-": "$", "?": "!", "B": "."})
        ),
        gs.str.translate(
            str.maketrans({"+": "-", "-": "$", "?": "!", "B": "."})
        ),
    )
    assert_eq(
        pd.Index(ps).str.translate(
            str.maketrans({"+": "-", "-": "$", "?": "!", "B": "."})
        ),
        as_index(gs).str.translate(
            str.maketrans({"+": "-", "-": "$", "?": "!", "B": "."})
        ),
    )
    assert_eq(
        ps.str.translate(str.maketrans({"é": "É"})),
        gs.str.translate(str.maketrans({"é": "É"})),
    )


def test_string_str_filter_characters():

    data = [
        "hello world",
        "A+B+C+D",
        "?!@#$%^&*()",
        "accént",
        None,
        "$1.50",
        "",
    ]
    gs = Series(data)
    expected = Series(["helloworld", "ABCD", "", "accnt", None, "150", ""])
    filter = {"a": "z", "A": "Z", "0": "9"}
    assert_eq(expected, gs.str.filter_characters(filter))

    expected = Series([" ", "+++", "?!@#$%^&*()", "é", None, "$.", ""])
    assert_eq(expected, gs.str.filter_characters(filter, False))

    expected = Series(
        ["hello world", "A B C D", "           ", "acc nt", None, " 1 50", ""]
    )
    assert_eq(expected, gs.str.filter_characters(filter, True, " "))


def test_string_str_code_points():

    data = [
        "abc",
        "Def",
        None,
        "jLl",
        "dog and cat",
        "accénted",
        "",
        " 1234 ",
        "XYZ",
    ]
    gs = Series(data)
    expected = [
        97,
        98,
        99,
        68,
        101,
        102,
        106,
        76,
        108,
        100,
        111,
        103,
        32,
        97,
        110,
        100,
        32,
        99,
        97,
        116,
        97,
        99,
        99,
        50089,
        110,
        116,
        101,
        100,
        32,
        49,
        50,
        51,
        52,
        32,
        88,
        89,
        90,
    ]
    expected = Series(expected)

    assert_eq(expected, gs.str.code_points(), check_dtype=False)


@pytest.mark.parametrize(
    "data",
    [
        ["http://www.hellow.com", "/home/nvidia/nfs", "123.45 ~ABCDEF"],
        ["23", "³", "⅕", ""],
        [" ", "\t\r\n ", ""],
        ["$", "B", "Aab$", "$$ca", "C$B$", "cat"],
    ],
)
def test_string_str_url_encode(data):
    import urllib.parse

    gs = Series(data)

    got = gs.str.url_encode()
    expected = pd.Series([urllib.parse.quote(url, safe="~") for url in data])
    assert_eq(expected, got)


@pytest.mark.parametrize(
    "data",
    [
        [
            "http://www.hellow.com?k1=acc%C3%A9nted&k2=a%2F/b.c",
            "%2Fhome%2fnfs",
            "987%20ZYX",
        ]
    ],
)
def test_string_str_decode_url(data):
    import urllib.parse

    gs = Series(data)

    got = gs.str.url_decode()
    expected = pd.Series([urllib.parse.unquote(url) for url in data])
    assert_eq(expected, got)


@pytest.mark.parametrize(
    "data,dtype",
    [
        (["0.1", "10.2", "10.876"], "float"),
        (["-0.1", "10.2", "+10.876"], "float"),
        (["1", "10.2", "10.876"], "float32"),
        (["+123", "6344556789", "0"], "int"),
        (["+123", "6344556789", "0"], "uint64"),
        (["+123", "6344556789", "0"], "float"),
        (["0.1", "-10.2", "10.876", None], "float"),
    ],
)
@pytest.mark.parametrize("obj_type", [None, "str", "category"])
def test_string_typecast(data, obj_type, dtype):
    psr = pd.Series(data, dtype=obj_type)
    gsr = Series(data, dtype=obj_type)

    expect = psr.astype(dtype=dtype)
    actual = gsr.astype(dtype=dtype)
    assert_eq(expect, actual)


@pytest.mark.parametrize(
    "data,dtype",
    [
        (["0.1", "10.2", "10.876"], "int"),
        (["1", "10.2", "+10.876"], "int"),
        (["abc", "1", "2", " "], "int"),
        (["0.1", "10.2", "10.876"], "uint64"),
        (["1", "10.2", "+10.876"], "uint64"),
        (["abc", "1", "2", " "], "uint64"),
        ([" ", "0.1", "2"], "float"),
        ([""], "int"),
        ([""], "uint64"),
        ([" "], "float"),
        (["\n"], "int"),
        (["\n"], "uint64"),
        (["0.1", "-10.2", "10.876", None], "int"),
        (["0.1", "-10.2", "10.876", None], "uint64"),
        (["0.1", "-10.2", "10.876", None, "ab"], "float"),
        (["+", "-"], "float"),
        (["+", "-"], "int"),
        (["+", "-"], "uint64"),
        (["1++++", "--2"], "float"),
        (["1++++", "--2"], "int"),
        (["1++++", "--2"], "uint64"),
        (["++++1", "--2"], "float"),
        (["++++1", "--2"], "int"),
        (["++++1", "--2"], "uint64"),
    ],
)
@pytest.mark.parametrize("obj_type", [None, "str", "category"])
def test_string_typecast_error(data, obj_type, dtype):
    psr = pd.Series(data, dtype=obj_type)
    gsr = Series(data, dtype=obj_type)

    assert_exceptions_equal(
        lfunc=psr.astype,
        rfunc=gsr.astype,
        lfunc_args_and_kwargs=([dtype],),
        rfunc_args_and_kwargs=([dtype],),
        compare_error_message=False,
    )


@pytest.mark.parametrize(
    "data",
    [
        ["f0:18:98:22:c2:e4", "00:00:00:00:00:00", "ff:ff:ff:ff:ff:ff"],
        ["f0189822c2e4", "000000000000", "ffffffffffff"],
        ["0xf0189822c2e4", "0x000000000000", "0xffffffffffff"],
        ["0Xf0189822c2e4", "0X000000000000", "0Xffffffffffff"],
    ],
)
def test_string_hex_to_int(data):

    gsr = Series(data)

    got = gsr.str.htoi()
    expected = Series([263988422296292, 0, 281474976710655])

    assert_eq(expected, got)


def test_string_ishex():
    gsr = Series(["", None, "0x01a2b3c4d5e6f", "0789", "ABCDEF0"])
    got = gsr.str.ishex()
    expected = Series([False, None, True, True, True])
    assert_eq(expected, got)


def test_string_istimestamp():
    gsr = Series(
        [
            "",
            None,
            "20201009 123456.987654AM+0100",
            "1920111 012345.000001",
            "18201235 012345.1",
            "20201009 250001.2",
            "20201009 129901.3",
            "20201009 123499.4",
            "20201009 000000.500000PM-0130",
            "20201009:000000.600000",
            "20201009 010203.700000PM-2500",
            "20201009 010203.800000AM+0590",
            "20201009 010203.900000AP-0000",
        ]
    )
    got = gsr.str.istimestamp(r"%Y%m%d %H%M%S.%f%p%z")
    expected = Series(
        [
            False,
            None,
            True,
            False,
            False,
            False,
            False,
            False,
            True,
            False,
            False,
            False,
            False,
        ]
    )
    assert_eq(expected, got)


def test_string_ip4_to_int():
    gsr = Series(["", None, "hello", "41.168.0.1", "127.0.0.1", "41.197.0.1"])
    expected = Series([0, None, 0, 698875905, 2130706433, 700776449])

    got = gsr.str.ip2int()

    assert_eq(expected, got)


def test_string_int_to_ipv4():
    gsr = Series([0, None, 0, 698875905, 2130706433, 700776449])
    expected = Series(
        ["0.0.0.0", None, "0.0.0.0", "41.168.0.1", "127.0.0.1", "41.197.0.1"]
    )

    got = Series(gsr._column.int2ip())

    assert_eq(expected, got)


def test_string_isipv4():
    gsr = Series(
        [
            "",
            None,
            "1...1",
            "141.168.0.1",
            "127.0.0.1",
            "1.255.0.1",
            "256.27.28.26",
            "25.257.28.26",
            "25.27.258.26",
            "25.27.28.256",
            "-1.0.0.0",
        ]
    )
    got = gsr.str.isipv4()
    expected = Series(
        [
            False,
            None,
            False,
            True,
            True,
            True,
            False,
            False,
            False,
            False,
            False,
        ]
    )
    assert_eq(expected, got)


@pytest.mark.parametrize(
    "dtype", sorted(list(dtypeutils.NUMERIC_TYPES - {"int64", "uint64"}))
)
def test_string_int_to_ipv4_dtype_fail(dtype):
    gsr = Series([1, 2, 3, 4, 5]).astype(dtype)
    with pytest.raises(TypeError):
        gsr._column.int2ip()


@pytest.mark.parametrize(
    "data",
    [
        ["abc", "xyz", "pqr", "tuv"],
        ["aaaaaaaaaaaa"],
        ["aaaaaaaaaaaa", "bdfeqwert", "poiuytre"],
    ],
)
@pytest.mark.parametrize(
    "index",
    [
        0,
        1,
        2,
        slice(0, 1, 2),
        slice(0, 5, 2),
        slice(-1, -2, 1),
        slice(-1, -2, -1),
        slice(-2, -1, -1),
        slice(-2, -1, 1),
        slice(0),
        slice(None),
    ],
)
def test_string_str_subscriptable(data, index):
    psr = pd.Series(data)
    gsr = Series(data)

    assert_eq(psr.str[index], gsr.str[index])

    psi = pd.Index(data)
    gsi = StringIndex(data)

    assert_eq(psi.str[index], gsi.str[index])


@pytest.mark.parametrize(
    "data,expected",
    [
        (["abc", "xyz", "pqr", "tuv"], [3, 3, 3, 3]),
        (["aaaaaaaaaaaa"], [12]),
        (["aaaaaaaaaaaa", "bdfeqwert", "poiuytre"], [12, 9, 8]),
        (["abc", "d", "ef"], [3, 1, 2]),
        (["Hello", "Bye", "Thanks 😊"], [5, 3, 11]),
        (["\n\t", "Bye", "Thanks 😊"], [2, 3, 11]),
    ],
)
def test_string_str_byte_count(data, expected):
    sr = Series(data)
    expected = Series(expected, dtype="int32")
    actual = sr.str.byte_count()
    assert_eq(expected, actual)

    si = as_index(data)
    expected = as_index(expected, dtype="int32")
    actual = si.str.byte_count()
    assert_eq(expected, actual)


@pytest.mark.parametrize(
    "data,expected",
    [
        (["1", "2", "3", "4", "5"], [True, True, True, True, True]),
        (
            ["1.1", "2.0", "3.2", "4.3", "5."],
            [False, False, False, False, False],
        ),
        (
            [".12312", "213123.", ".3223.", "323423.."],
            [False, False, False, False],
        ),
        ([""], [False]),
        (
            ["1..1", "+2", "++3", "4++", "-5"],
            [False, True, False, False, True],
        ),
        (
            [
                "24313345435345 ",
                "+2632726478",
                "++367293674326",
                "4382493264392746.237649274692++",
                "-578239479238469264",
            ],
            [False, True, False, False, True],
        ),
        (
            ["2a2b", "a+b", "++a", "a.b++", "-b"],
            [False, False, False, False, False],
        ),
        (
            ["2a2b", "1+3", "9.0++a", "+", "-"],
            [False, False, False, False, False],
        ),
    ],
)
def test_str_isinteger(data, expected):
    sr = Series(data, dtype="str")
    expected = Series(expected)
    actual = sr.str.isinteger()
    assert_eq(expected, actual)

    sr = as_index(data)
    expected = as_index(expected)
    actual = sr.str.isinteger()
    assert_eq(expected, actual)


@pytest.mark.parametrize(
    "data,expected",
    [
        (["1", "2", "3", "4", "5"], [True, True, True, True, True]),
        (["1.1", "2.0", "3.2", "4.3", "5."], [True, True, True, True, True]),
        ([""], [False]),
        (
            [".12312", "213123.", ".3223.", "323423.."],
            [True, True, False, False],
        ),
        (
            ["1.00.323.1", "+2.1", "++3.30", "4.9991++", "-5.3"],
            [False, True, False, False, True],
        ),
        (
            [
                "24313345435345 ",
                "+2632726478",
                "++367293674326",
                "4382493264392746.237649274692++",
                "-578239479238469264",
            ],
            [False, True, False, False, True],
        ),
        (
            [
                "24313345435345.32732 ",
                "+2632726478.3627638276",
                "++0.326294632367293674326",
                "4382493264392746.237649274692++",
                "-57823947923.8469264",
            ],
            [False, True, False, False, True],
        ),
        (
            ["2a2b", "a+b", "++a", "a.b++", "-b"],
            [False, False, False, False, False],
        ),
        (
            ["2a2b", "1+3", "9.0++a", "+", "-"],
            [False, False, False, False, False],
        ),
    ],
)
def test_str_isfloat(data, expected):
    sr = Series(data, dtype="str")
    expected = Series(expected)
    actual = sr.str.isfloat()
    assert_eq(expected, actual)

    sr = as_index(data)
    expected = as_index(expected)
    actual = sr.str.isfloat()
    assert_eq(expected, actual)


@pytest.mark.parametrize(
    "data",
    [
        ["a", "b", "c", "d", "e"],
        ["a", "z", ".", '"', "aa", "zz"],
        ["aa", "zz"],
        ["z", "a", "zz", "aa"],
        ["1", "2", "3", "4", "5"],
        [""],
        ["a"],
        ["hello"],
        ["small text", "this is a larger text......"],
        ["👋🏻", "🔥", "🥇"],
        ["This is 💯", "here is a calendar", "📅"],
        ["", ".", ";", "[", "]"],
        ["\t", ".", "\n", "\n\t", "\t\n"],
    ],
)
def test_str_min(data):
    psr = pd.Series(data)
    sr = Series(data)

    assert_eq(psr.min(), sr.min())


@pytest.mark.parametrize(
    "data",
    [
        ["a", "b", "c", "d", "e"],
        ["a", "z", ".", '"', "aa", "zz"],
        ["aa", "zz"],
        ["z", "a", "zz", "aa"],
        ["1", "2", "3", "4", "5"],
        [""],
        ["a"],
        ["hello"],
        ["small text", "this is a larger text......"],
        ["👋🏻", "🔥", "🥇"],
        ["This is 💯", "here is a calendar", "📅"],
        ["", ".", ";", "[", "]"],
        ["\t", ".", "\n", "\n\t", "\t\n"],
    ],
)
def test_str_max(data):
    psr = pd.Series(data)
    sr = Series(data)

    assert_eq(psr.max(), sr.max())


@pytest.mark.parametrize(
    "data",
    [
        ["a", "b", "c", "d", "e"],
        ["a", "z", ".", '"', "aa", "zz"],
        ["aa", "zz"],
        ["z", "a", "zz", "aa"],
        ["1", "2", "3", "4", "5"],
        [""],
        ["a"],
        ["hello"],
        ["small text", "this is a larger text......"],
        ["👋🏻", "🔥", "🥇"],
        ["This is 💯", "here is a calendar", "📅"],
        ["", ".", ";", "[", "]"],
        ["\t", ".", "\n", "\n\t", "\t\n"],
    ],
)
def test_str_sum(data):
    psr = pd.Series(data)
    sr = Series(data)

    assert_eq(psr.sum(), sr.sum())


def test_str_mean():
    sr = Series(["a", "b", "c", "d", "e"])

    with pytest.raises(TypeError):
        sr.mean()


def test_string_product():
    psr = pd.Series(["1", "2", "3", "4", "5"])
    sr = Series(["1", "2", "3", "4", "5"])

    assert_exceptions_equal(
        lfunc=psr.product,
        rfunc=sr.product,
        expected_error_message=re.escape(
            f"cannot perform prod with type {sr.dtype}"
        ),
    )


def test_string_var():
    psr = pd.Series(["1", "2", "3", "4", "5"])
    sr = Series(["1", "2", "3", "4", "5"])

    assert_exceptions_equal(
        lfunc=psr.var, rfunc=sr.var, compare_error_message=False
    )


def test_string_std():
    psr = pd.Series(["1", "2", "3", "4", "5"])
    sr = Series(["1", "2", "3", "4", "5"])

    assert_exceptions_equal(
        lfunc=psr.std, rfunc=sr.std, compare_error_message=False
    )
