"""
pyexcel.internal.sheets.column
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Generic table column

:copyright: (c) 2015-2025 by Onni Software Ltd.
:license: New BSD License
"""

import copy
import types

from pyexcel import _compact as compact

from . import _shared as utils


class Column(utils.CommonPropertyAmongRowNColumn):
    """Represent columns of a matrix

    .. table:: "example.csv"

        = = =
        1 2 3
        4 5 6
        7 8 9
        = = =

    Let us manipulate the data columns on the above data matrix::

        >>> import pyexcel as pe
        >>> data = [[1,2,3], [4,5,6], [7,8,9]]
        >>> m = pe.internal.sheets.Matrix(data)
        >>> m.column[0]
        [1, 4, 7]
        >>> m.column[2] = [0, 0, 0]
        >>> m.column[2]
        [0, 0, 0]
        >>> del m.column[1]
        >>> m.column[1]
        [0, 0, 0]
        >>> m.column[2]
        Traceback (most recent call last):
            ...
        IndexError

    """

    def select(self, indices):
        """
        Examples:

            >>> import pyexcel as pe
            >>> data = [[1,2,3,4,5,6,7,9]]
            >>> sheet = pe.Sheet(data)
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+---+---+---+---+
            | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 |
            +---+---+---+---+---+---+---+---+
            >>> sheet.column.select([1,2,3,5])
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+
            | 2 | 3 | 4 | 6 |
            +---+---+---+---+
            >>> data = [[1,2,3,4,5,6,7,9]]
            >>> sheet = pe.Sheet(data)
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+---+---+---+---+
            | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 |
            +---+---+---+---+---+---+---+---+
            >>> sheet.column.select([1,2,3,5])
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+
            | 2 | 3 | 4 | 6 |
            +---+---+---+---+
            >>> data = [
            ...     ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
            ...     [1,2,3,4,5,6,7,9],
            ... ]
            >>> sheet = pe.Sheet(data, name_columns_by_row=0)
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+---+---+---+---+
            | a | b | c | d | e | f | g | h |
            +===+===+===+===+===+===+===+===+
            | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 |
            +---+---+---+---+---+---+---+---+
            >>> del sheet.column['a', 'b', 'i', 'f'] # doctest:+ELLIPSIS
            Traceback (most recent call last):
                ...
            ValueError: ...
            >>> sheet.column.select(['a', 'c', 'e', 'h'])
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+
            | a | c | e | h |
            +===+===+===+===+
            | 1 | 3 | 5 | 9 |
            +---+---+---+---+
        """
        new_indices = []
        if compact.is_array_type(indices, str):
            new_indices = utils.names_to_indices(indices, self._ref.colnames)
        else:
            new_indices = indices
        to_remove = []
        for index in self._ref.column_range():
            if index not in new_indices:
                to_remove.append(index)
        self._ref.filter(column_indices=to_remove)

    def __delitem__(self, aslice):
        """Override the operator to delete items

        Examples:

            >>> import pyexcel as pe
            >>> data = [[1,2,3,4,5,6,7,9]]
            >>> sheet = pe.Sheet(data)
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+---+---+---+---+
            | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 |
            +---+---+---+---+---+---+---+---+
            >>> del sheet.column[1,2,3,5]
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+
            | 1 | 5 | 7 | 9 |
            +---+---+---+---+
            >>> data = [
            ...     ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
            ...     [1,2,3,4,5,6,7,9],
            ... ]
            >>> sheet = pe.Sheet(data, name_columns_by_row=0)
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+---+---+---+---+
            | a | b | c | d | e | f | g | h |
            +===+===+===+===+===+===+===+===+
            | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 |
            +---+---+---+---+---+---+---+---+
            >>> del sheet.column['a', 'b', 'i', 'f'] # doctest:+ELLIPSIS
            Traceback (most recent call last):
                ...
            ValueError: ...
            >>> del sheet.column['a', 'c', 'e', 'h']
            >>> sheet
            pyexcel sheet:
            +---+---+---+---+
            | b | d | f | g |
            +===+===+===+===+
            | 2 | 4 | 6 | 7 |
            +---+---+---+---+

        """
        is_sheet = compact.is_string(type(aslice)) and hasattr(
            self._ref,
            "delete_named_column_at",
        )
        if is_sheet:
            self._ref.delete_named_column_at(aslice)
        elif compact.is_tuple_consists_of_strings(aslice):
            indices = utils.names_to_indices(list(aslice), self._ref.colnames)
            self._ref.delete_columns(indices)
        elif isinstance(aslice, slice):
            my_range = utils.analyse_slice(
                aslice,
                self._ref.number_of_columns(),
            )
            self._ref.delete_columns(my_range)
        elif isinstance(aslice, str):
            index = utils.excel_column_index(aslice)
            self._ref.delete_columns([index])
        elif isinstance(aslice, tuple):
            indices = list(aslice)
            self._ref.filter(column_indices=indices)
        elif isinstance(aslice, list):
            indices = aslice
            self._ref.filter(column_indices=indices)
        elif isinstance(aslice, int):
            self._ref.delete_columns([aslice])
        elif isinstance(aslice, types.LambdaType):
            self._delete_columns_by_content(aslice)
        elif isinstance(aslice, types.FunctionType):
            self._delete_columns_by_content(aslice)
        else:
            raise IndexError

    def _delete_columns_by_content(self, locator):
        to_remove = []
        for index, column in enumerate(self._ref.columns()):
            if locator(index, column):
                to_remove.append(index)
        if len(to_remove) > 0:
            self._ref.delete_columns(to_remove)

    def __setitem__(self, aslice, a_column):
        """Override the operator to set items"""
        is_sheet = compact.is_string(type(aslice)) and hasattr(
            self._ref,
            "set_named_column_at",
        )
        if is_sheet:
            self._ref.set_named_column_at(aslice, a_column)
        elif isinstance(aslice, slice):
            my_range = utils.analyse_slice(
                aslice,
                self._ref.number_of_columns(),
            )
            for i in my_range:
                self._ref.set_column_at(i, a_column)
        elif isinstance(aslice, str):
            index = utils.excel_column_index(aslice)
            self._ref.set_column_at(index, a_column)
        elif isinstance(aslice, int):
            self._ref.set_column_at(aslice, a_column)
        else:
            raise IndexError

    def __getitem__(self, aslice):
        """By default, this class recognize from top to bottom
        from left to right"""
        index = aslice
        is_sheet = compact.is_string(type(aslice)) and hasattr(
            self._ref,
            "named_column_at",
        )
        if is_sheet:
            return self._ref.named_column_at(aslice)

        if isinstance(aslice, slice):
            my_range = utils.analyse_slice(
                aslice,
                self._ref.number_of_columns(),
            )
            results = []
            for i in my_range:
                results.append(self._ref.column_at(i))
            return results

        if isinstance(aslice, str):
            index = utils.excel_column_index(aslice)
        if utils.abs(index) in self._ref.column_range():
            return self._ref.column_at(index)

        raise IndexError

    def __iadd__(self, other):
        """Overload += sign

        :return: self
        """
        if isinstance(other, compact.OrderedDict):
            self._ref.extend_columns(copy.deepcopy(other))
        elif isinstance(other, list):
            self._ref.extend_columns(copy.deepcopy(other))
        elif hasattr(other, "get_internal_array"):
            self._ref.extend_columns_with_rows(
                copy.deepcopy(other.get_internal_array()),
            )
        else:
            raise TypeError

        return self

    def __add__(self, other):
        """Overload + sign
        :return: new instance
        """
        new_instance = self._ref.clone()
        if isinstance(other, compact.OrderedDict):
            new_instance.extend_columns(copy.deepcopy(other))
        elif isinstance(other, list):
            new_instance.extend_columns(copy.deepcopy(other))
        elif hasattr(other, "get_internal_array"):
            new_instance.extend_columns_with_rows(
                copy.deepcopy(other.get_internal_array()),
            )
        else:
            raise TypeError

        return new_instance

    def __getattr__(self, attr):
        """
        Refer to sheet.column.name
        """
        the_attr = attr
        if attr not in self._ref.colnames:
            the_attr = the_attr.replace("_", " ")
            if the_attr not in self._ref.colnames:
                raise AttributeError(f"{attr} is not found")

        return self._ref.named_column_at(the_attr)

    def format(self, column_index=None, formatter=None, format_specs=None):
        """Format a column"""
        if column_index is not None:
            self._handle_one_formatter(column_index, formatter)
        elif format_specs:
            for spec in format_specs:
                self._handle_one_formatter(spec[0], spec[1])

    def _handle_one_formatter(self, columns, theformatter):
        new_indices = columns
        if len(self._ref.colnames) > 0:
            new_indices = utils.names_to_indices(columns, self._ref.colnames)
        converter = utils.CommonPropertyAmongRowNColumn.get_converter(
            theformatter,
        )

        if isinstance(new_indices, list):
            for rcolumn in self._ref.column_range():
                if rcolumn in new_indices:
                    for row in self._ref.row_range():
                        value = self._ref.cell_value(row, rcolumn)
                        value = converter(value)
                        self._ref.cell_value(row, rcolumn, value)
        else:
            for row in self._ref.row_range():
                value = self._ref.cell_value(row, new_indices)
                value = converter(value)
                self._ref.cell_value(row, new_indices, value)
