# Orca
#
# Copyright 2005-2009 Sun Microsystems Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA  02110-1301 USA.

# pylint: disable=wrong-import-position

"""Produces speech presentation for accessible objects."""

# This has to be the first non-docstring line in the module to make linters happy.
from __future__ import annotations

__id__        = "$Id$"
__version__   = "$Revision$"
__date__      = "$Date$"
__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc."
__license__   = "LGPL"

from typing import TYPE_CHECKING, Any

import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi

from orca import debug
from orca import focus_manager
from orca import messages
from orca import settings_manager
from orca import speech_generator
from orca.ax_component import AXComponent
from orca.ax_object import AXObject
from orca.ax_table import AXTable
from orca.ax_text import AXText
from orca.ax_utilities import AXUtilities

if TYPE_CHECKING:
    from . import script


class SpeechGenerator(speech_generator.SpeechGenerator):
    """Produces speech presentation for accessible objects."""

    # Type annotation to override the base class script type
    _script: script.Script

    @staticmethod
    def log_generator_output(func):
        """Decorator for logging."""

        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            tokens = [f"SOFFICE SPEECH GENERATOR: {func.__name__}:", result]
            debug.print_tokens(debug.LEVEL_INFO, tokens, True)
            return result
        return wrapper

    @log_generator_output
    def _generate_accessible_name(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._script.utilities.is_spreadsheet_cell(obj):
            # Currently the coordinates of the cell are exposed as the name.
            return []
        return super()._generate_accessible_name(obj, **args)

    @log_generator_output
    def _generate_text_line(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if AXUtilities.is_combo_box(obj):
            if entry := AXObject.find_descendant(obj, AXUtilities.is_text_input):
                return super()._generate_text_line(entry)
            return []

        # TODO - JD: The SayLine, etc. code should be generated and not put
        # together in the scripts. In addition, the voice crap needs to go
        # here. Then it needs to be removed from the scripts.
        if AXObject.supports_text(obj):
            text = AXText.get_line_at_offset(obj)[0]
            if not text:
                result: list[Any] = [messages.BLANK]
                result.extend(self.voice(string=text, obj=obj, **args))
                return result

        return super()._generate_text_line(obj, **args)

    @log_generator_output
    def _generate_state_pressed(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """Treat toggle buttons in the toolbar specially. This is so we can
        have more natural sounding speech such as "bold on", "bold off", etc."""

        if not AXUtilities.is_toggle_button(obj, args.get("role")):
            return []

        if not AXUtilities.is_tool_bar(AXObject.get_parent(obj)):
            return super()._generate_state_pressed(obj, **args)

        if AXUtilities.is_checked(obj):
            result: list[Any] = [messages.ON]
        else:
            result = [messages.OFF]
        result.extend(self.voice(speech_generator.SYSTEM, obj=obj, **args))
        return result

    def _generate_too_long(self, obj: Atspi.Accessible, **args) -> list[Any]:
        """If there is text in this spread sheet cell, compare the size of
        the text within the table cell with the size of the actual table
        cell and report back to the user if it is larger.

        Returns an indication of how many characters are greater than the size
        of the spread sheet cell, or None if the message fits.
        """
        if settings_manager.get_manager().get_setting("onlySpeakDisplayedText"):
            return []

        # TODO - JD: Can this be moved to AXText?
        result: list[Any] = []
        length = AXText.get_character_count(obj)
        too_long_count = 0
        extents = AXComponent.get_rect(obj)
        for i in range(length):
            rect = AXText.get_character_rect(obj, i)
            if rect.x < extents.x:
                too_long_count += 1
            elif rect.x + rect.width > extents.x + extents.width:
                too_long_count += length - i
                break
        if too_long_count > 0:
            result = [messages.characters_too_long(too_long_count)]
        if result:
            result.extend(self.voice(speech_generator.SYSTEM, obj=obj, **args))
        return result

    def _generate_has_formula(self, obj: Atspi.Accessible, **args) -> list[Any]:
        formula = AXTable.get_cell_formula(obj)
        if not formula:
            return []

        if args.get("formatType") == "basicWhereAmI":
            result: list[Any] = [f"{messages.HAS_FORMULA}. {formula}"]
        else:
            result = [messages.HAS_FORMULA]

        result.extend(self.voice(speech_generator.SYSTEM, obj=obj, **args))
        return result

    @log_generator_output
    def _generate_real_table_cell(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if focus_manager.get_manager().in_say_all():
            return []

        result = super()._generate_real_table_cell(obj, **args)

        if not self._script.utilities.is_spreadsheet_cell(obj):
            if self._script.get_table_navigator().last_input_event_was_navigation_command():
                return result

            if settings_manager.get_manager().get_setting("speakCellCoordinates"):
                result.append(AXObject.get_name(obj))
            return result

        if settings_manager.get_manager().get_setting("speakSpreadsheetCoordinates") \
           or args.get("formatType") == "basicWhereAmI":
            label = AXTable.get_label_for_cell_coordinates(obj) \
                or self._script.utilities.spreadsheet_cell_name(obj)
            result.append(label)

        if self._script.utilities.should_read_full_row(obj, args.get("priorObj")):
            if self._script.utilities.cell_row_changed(obj):
                return result

        too_long = self._generate_too_long(obj, **args)
        if too_long:
            result.extend(self._generate_pause(obj, **args))
            result.extend(too_long)

        has_formula = self._generate_has_formula(obj, **args)
        if has_formula:
            result.extend(self._generate_pause(obj, **args))
            result.extend(has_formula)

        if result == speech_generator.PAUSE:
            result = [messages.BLANK]
            result.extend(self.voice(speech_generator.DEFAULT, obj=obj, **args))

        return result

    @log_generator_output
    def _generate_new_ancestors(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._script.utilities.is_spreadsheet_cell(obj) \
           and self._script.utilities.is_document_panel(AXObject.get_parent(args.get("priorObj"))):
            return []

        return super()._generate_new_ancestors(obj, **args)

    @log_generator_output
    def _generate_old_ancestors(self, obj: Atspi.Accessible, **args) -> list[Any]:
        if self._script.utilities.is_spreadsheet_cell(args.get("priorObj")):
            return []

        return super()._generate_old_ancestors(obj, **args)
