"""
Original Author: sanmusen214(https://github.com/sanmusen214)
Adapted from https://github.com/sanmusen214/BAAH/blob/1.2/modules/AllTask/SubTask/ScrollSelect.py
"""

from module.base.timer import Timer
from module.logger import logger


class ScrollSelect:
    """
    Scroll and select the corresponding level by clicking on the right-side window.
    
    Parameters
    ----------
    targetind : int
        Index of the target level
    window_starty: 
        Y-coordinate of the upper edge of the window
    first_item_endy: 
        Y-coordinate of the lower edge of the first item
    window_endy:
        Y-coordinate of the lower edge of the window
    clickx: int
        Base X-coordinate for sliding and clicking the button
    hasexpectimage: function
        Function to determine the appearance of the expected image after clicking, returns a boolean
    swipeoffsetx: int
        X offset of the base X-coordinate during sliding to prevent accidental button clicks
    finalclick: bool
        Whether to click on clickx and the last row after the sliding ends
    """

    def __init__(self, window_button, first_item_button, expected_button, clickx, swipeoffsetx=-100,
                 finalclick=True) -> None:
        # TODO: Actually, only concerned about the height of one element, completely displaying the Y of the first button, completely displaying the Y of the bottom button, the number of complete elements that the window can contain, the height of the last element in the window, and the left offset and response distance.
        self.window_starty = window_button.area[1]
        self.window_endy = window_button.area[3]
        self.first_item_endy = first_item_button.area[3]
        self.windowheight = window_button.height
        self.itemheight = first_item_button.height
        self.clickx = clickx
        self.expected_button = expected_button
        self.swipeoffsetx = swipeoffsetx
        self.responsey = 40
        self.finalclick = finalclick

    def compute_swipe(self, main, x1, y1, distance, responsey):
        """
        Swipe vertically from bottom to top, actual swipe distance calculated based on the distance between two target points, considering inertia.
        """
        distance = abs(distance)
        logger.info(f"Swipe distance: {distance}")
        # 0-50
        if distance < 50:
            main.device.swipe((x1, y1), (x1, y1 - (distance + responsey)), duration=2)
        else:
            # Effective swipe distance for the Chinese server is 60
            main.device.swipe((x1, y1), (x1, int(y1 - (distance + responsey - 4 * (1 + distance / 100)))),
                              duration=1 + distance / 100)

    def select_location(self, main, target_index) -> None:
        click_coords = main.device.click_methods.get(main.config.Emulator_ControlMethod, main.device.click_adb)
        logger.info("Scroll and select the {}-th level".format(target_index + 1))
        self.scroll_right_up(main, scrollx=self.clickx + self.swipeoffsetx)
        # Calculate how many complete elements are on one page
        itemcount = self.windowheight // self.itemheight
        # Calculate how much height the last incomplete element on this page occupies
        lastitemheight = self.windowheight % self.itemheight
        # Height below the incomplete element
        hiddenlastitemheight = self.itemheight - lastitemheight
        # Center point of the height of the first element
        start_center_y = self.window_starty + self.itemheight // 2
        # Center point of the last complete element on this page
        end_center_y = start_center_y + (itemcount - 1) * self.itemheight
        # If the target element is on the current page
        if target_index < itemcount:
            # Center point of the target element
            target_center_y = start_center_y + self.itemheight * target_index
            self.run_until(main,
                           lambda: click_coords(self.clickx, target_center_y),
                           lambda: main.appear(self.expected_button),
                           )
        else:
            # Start scrolling from the gap in the middle of the levels
            scroll_start_from_y = self.window_endy - self.itemheight // 2
            # The target element is on subsequent pages
            # Calculate how much the page should be scrolled
            scrolltotal_distance = (target_index - itemcount) * self.itemheight + hiddenlastitemheight
            logger.info("Height hidden by the last element: %d" % hiddenlastitemheight)
            # First, slide up the hidden part, add a little distance to let the system recognize it as a swipe event
            self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, hiddenlastitemheight,
                               self.responsey)
            logger.info(f"Swipe distance: {hiddenlastitemheight}")
            # Update scrolltotal_distance
            scrolltotal_distance -= hiddenlastitemheight
            # Still need to scroll up (target_index - itemcount) * self.itemheight
            # Important: slide the height of (itemcount - 1) elements each time
            if itemcount == 1:
                scroll_distance = itemcount * self.itemheight
            else:
                scroll_distance = (itemcount - 1) * self.itemheight
            while scroll_distance <= scrolltotal_distance:
                self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, scroll_distance,
                                   self.responsey)
                scrolltotal_distance -= scroll_distance
            if scrolltotal_distance > 5:
                # Last slide
                self.compute_swipe(main, self.clickx + self.swipeoffsetx, scroll_start_from_y, scrolltotal_distance,
                                   self.responsey)
            if self.finalclick:
                # Click on the last row
                self.run_until(main,
                               lambda: click_coords(self.clickx, self.window_endy - self.itemheight // 2),
                               lambda: main.appear(self.expected_button)
                               )

    def run_until(self, main, func1, func2, times=6, sleeptime=1.5) -> bool:
        """
        Repeat the execution of func1 up to a maximum of times or until func2 evaluates to True.
        
        func1 should perform a single valid operation or internally call a screenshot function.
        A screenshot is triggered before evaluating func2.

        After each execution of func1, wait for sleeptime seconds.

        If func2 evaluates to True, exit and return True. Otherwise, return False.

        Note: The comment assumes that func1 produces a meaningful operation or internally calls a screenshot function,
        and func2 is evaluated after each execution of func1.
        """
        for i in range(times):
            main.device.screenshot()
            if func2():
                return True
            func1()
            timer = Timer(sleeptime).start()
            while not timer.reached_and_reset():
                pass
        main.device.screenshot()
        if func2():
            return True
        logger.warning("run_until exceeded max times")
        return False

    def scroll_right_up(self, main, scrollx=928, times=3):
        """
        scroll to top
        """
        for i in range(times):
            main.device.swipe((scrollx, 226), (scrollx, 561), duration=0.2)
        timer = Timer(0.5).start()
        while not timer.reached_and_reset():
            pass
