# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals

FUNCTIONS = 'x y scroll_to scroll_into_view reset_globals __reset_transforms window_scroll_pos content_size'.split(' ')

from read_book.globals import get_boss, viewport_mode_changer
from utils import document_height, document_width, ios_major_version


class ScrollViewport:
    def __init__(self):
        self.set_mode('flow')
        self.window_width_from_parent = self.window_height_from_parent = None
        # In RTL mode, we hide the fact that we are scrolling to the left by negating the
        # current X position and the requested X scroll position, which fools the reader
        # code into thinking that it's always scrolling in positive X.
        self.rtl = False
        self.ltr = True
        self.vertical_writing_mode = False
        self.horizontal_writing_mode = True

    def set_mode(self, mode):
        prefix = ('flow' if mode is 'flow' else 'paged') + '_'
        for attr in FUNCTIONS:
            self[attr] = self[prefix + attr]

    def initialize_on_layout(self, body_style):
        self.horizontal_writing_mode = True
        self.vertical_writing_mode = False
        self.ltr = True
        self.rtl = False
        if body_style.direction is 'rtl':
            self.rtl = True
            self.ltr = False

        css_vertical_rl = body_style.getPropertyValue('writing-mode') is 'vertical-rl'
        if css_vertical_rl:
            self.vertical_writing_mode = True
            self.horizontal_writing_mode = False
            self.rtl = True
            self.ltr = False
        else:
            css_vertical_lr = body_style.getPropertyValue('writing-mode') is 'vertical-lr'
            if css_vertical_lr:
                self.vertical_writing_mode = True
                self.horizontal_writing_mode = False
                self.ltr = True
                self.rtl = False

    def flow_x(self):
        if self.rtl:
            return -window.pageXOffset
        return window.pageXOffset

    def flow_y(self):
        return window.pageYOffset

    def flow_window_scroll_pos(self):
        return window.pageXOffset, window.pageYOffset

    def inline_pos(self):
        if self.vertical_writing_mode:
            return self.y()
        return self.x()

    def block_pos(self):
        if self.horizontal_writing_mode:
            return self.y()
        return self.x()

    def flow_scroll_to(self, x, y):
        if self.rtl:
            window.scrollTo(-x, y)
        else:
            window.scrollTo(x, y)

    def scroll_to_in_inline_direction(self, pos, preserve_other):
        # Lines flow vertically, so inline is vertical.
        if self.vertical_writing_mode:
            self.scroll_to(self.x() if preserve_other else 0, pos)
        else:
            self.scroll_to(pos, self.y() if preserve_other else 0)

    def scroll_to_in_block_direction(self, pos, preserve_other):
        # In horizontal modes, the block direction is vertical.
        if self.horizontal_writing_mode:
            self.scroll_to(self.x() if preserve_other else 0, pos)
        # In vertical modes, the block direction is horizontal.
        else:
            self.scroll_to(pos, self.y() if preserve_other else 0)

    def flow_scroll_into_view(self, elem):
        elem.scrollIntoView()

    def scroll_by(self, x, y):
        if self.ltr:
            window.scrollBy(x, y)
        # Swap inline direction if in RTL mode.
        else:
            window.scrollBy(-x, y)

    def scroll_by_in_inline_direction(self, offset):
        # Same logic as scroll_to_in_inline_direction
        if self.vertical_writing_mode:
            self.scroll_by(0, offset)
        else:
            self.scroll_by(offset, 0)

    def scroll_by_in_block_direction(self, offset):
        # Same logic as scroll_to_in_block_direction
        if self.horizontal_writing_mode:
            self.scroll_by(0, offset)
        else:
            self.scroll_by(offset, 0)

    def flow_reset_globals(self):
        pass

    def flow___reset_transforms(self):
        pass

    def flow_content_size(self):
        return document.documentElement.scrollWidth, document.documentElement.scrollHeight

    def paged_content_inline_size(self):
        w, h = self.content_size()
        return w if self.horizontal_writing_mode else h

    def paged_content_block_size(self):
        w, h = self.content_size()
        return h if self.horizontal_writing_mode else w

    def inline_size(self):
        if self.horizontal_writing_mode:
            return self.width()
        return self.height()

    def block_size(self):
        if self.horizontal_writing_mode:
            return self.height()
        return self.width()

    def update_window_size(self, w, h):
        self.window_width_from_parent = w
        self.window_height_from_parent = h

    def width(self):
        return window.innerWidth

    def height(self):
        return window.innerHeight

    def document_inline_size(self):
        if self.horizontal_writing_mode:
            return document_width()
        return document_height()

    def document_block_size(self):
        if self.horizontal_writing_mode:
            return document_height()
        return document_width()

    # Assure that the viewport position returned is corrected for the RTL
    # mode of ScrollViewport.
    def viewport_to_document(self, x, y, doc):
        # Convert x, y from the viewport (window) coordinate system to the
        # document (body) coordinate system
        doc = doc or window.document
        topdoc = window.document
        while doc is not topdoc and doc.defaultView:
            # We are in a frame
            frame = doc.defaultView.frameElement
            rect = frame.getBoundingClientRect()
            x += rect.left
            y += rect.top
            doc = frame.ownerDocument
        wx, wy = self.window_scroll_pos()
        x += wx
        y += wy
        if self.rtl:
            x *= -1
        return x, y

    def rect_inline_start(self, rect):
        # Lines start on the left in LTR mode, right in RTL mode
        if self.horizontal_writing_mode:
            return rect.left if self.ltr else rect.right
        # Only top-to-bottom vertical writing is supported
        return rect.top

    def rect_inline_end(self, rect):
        # Lines end on the right in LTR mode, left in RTL mode
        if self.horizontal_writing_mode:
            return rect.right if self.ltr else rect.left
        # In top-to-bottom (the only vertical mode supported), bottom:
        return rect.bottom

    def rect_block_start(self, rect):
        # Block flows top to bottom in horizontal modes
        if self.horizontal_writing_mode:
            return rect.top
        # Block flows either left or right in vertical modes
        return rect.left if self.ltr else rect.right

    def rect_block_end(self, rect):
        # Block flows top to bottom in horizontal modes
        if self.horizontal_writing_mode:
            return rect.bottom
        # Block flows either left or right in vertical modes
        return rect.right if self.ltr else rect.left

    def rect_inline_size(self, rect):
        # Lines go horizontally in horizontal writing, so use width
        if self.horizontal_writing_mode:
            return rect.width
        return rect.height

    def rect_block_size(self, rect):
        # The block is vertical in horizontal writing, so use height
        if self.horizontal_writing_mode:
            return rect.height
        return rect.width

    # Returns document inline coordinate (1 value) at viewport inline coordinate
    def viewport_to_document_inline(self, pos, doc):
        # Lines flow horizontally in horizontal mode
        if self.horizontal_writing_mode:
            return self.viewport_to_document(pos, 0, doc)[0]
        # Inline is vertical in vertical mode
        return self.viewport_to_document(0, pos, doc)[1]

    # Returns document block coordinate (1 value) at viewport block coordinate
    def viewport_to_document_block(self, pos, doc):
        # Block is vertical in horizontal mode
        if self.horizontal_writing_mode:
            return self.viewport_to_document(0, pos, doc)[1]
        # Horizontal in vertical mode
        return self.viewport_to_document(pos, 0, doc)[0]

    def viewport_to_document_inline_block(self, inline, block, doc):
        if self.horizontal_writing_mode:
            return self.viewport_to_document(inline, block, doc)
        return self.viewport_to_document(block, inline, doc)


class IOSScrollViewport(ScrollViewport):
    def width(self):
        return self.window_width_from_parent or window.innerWidth

    def height(self):
        return self.window_height_from_parent or window.innerHeight

    def _scroll_implementation(self, x, y):
        if x is 0 and y is 0:
            document.documentElement.style.transform = 'none'
        else:
            x *= -1
            y *= -1
            document.documentElement.style.transform = f'translateX({x}px) translateY({y}px)'

    def paged_scroll_to(self, x, y):
        x = x or 0
        y = y or 0
        if self.ltr:
            self._scroll_implementation(x, y)
        else:
            self._scroll_implementation(-x, y)
        boss = get_boss()
        if boss:
            boss.onscroll()

    def transform_val(self, y):
        raw = document.documentElement.style.transform
        if not raw or raw is 'none':
            return 0
        idx = raw.lastIndexOf('(') if y else raw.indexOf('(')
        raw = raw[idx + 1:]
        ans = parseInt(raw)
        if isNaN(ans):
            ans = 0
        return ans

    def paged_x(self):
        ans = self.transform_val()
        if self.ltr:
            ans *= -1
        return ans

    def paged_y(self):
        return -1 * self.transform_val(True)

    def paged_scroll_into_view(self, elem):
        left = elem.offsetLeft
        if left is None:
            return  # element has display: none
        elem.scrollIntoView()
        window.scrollTo(0, 0)
        p = elem.offsetParent
        while p:
            left += p.offsetLeft
            p = p.offsetParent
        # left -= window_width() // 2
        self._scroll_implementation(max(0, left), 0)

    def paged_window_scroll_pos(self):
        return self.transform_val(), self.transform_val(True)

    def paged_content_size(self):
        w = document.documentElement.scrollWidth
        h = document.documentElement.scrollHeight
        return w - self.transform_val(), h - self.transform_val(True)

    def paged___reset_transforms(self):
        s = document.documentElement.style
        if s.transform is not 'none':
            s.transform = 'none'

    def paged_reset_globals(self):
        self.__reset_transforms()


if 1 < ios_major_version < 15:
    scroll_viewport = IOSScrollViewport()
else:
    scroll_viewport = ScrollViewport()
for attr in FUNCTIONS:
    if not scroll_viewport['paged_' + attr]:
        scroll_viewport['paged_' + attr] = scroll_viewport[attr]
viewport_mode_changer(scroll_viewport.set_mode)


def get_unit_size_in_pixels(unit):
    d = document.createElement('span')
    d.style.position = 'absolute'
    d.style.visibility = 'hidden'
    d.style.width = f'1{unit}'
    d.style.fontSize = f'1{unit}'
    d.style.paddingTop = d.style.paddingBottom = d.style.paddingLeft = d.style.paddingRight = '0'
    d.style.marginTop = d.style.marginBottom = d.style.marginLeft = d.style.marginRight = '0'
    d.style.borderStyle = 'none'
    document.body.appendChild(d)
    ans = d.clientWidth
    document.body.removeChild(d)
    return ans


def rem_size(reset):
    if reset:
        rem_size.ans = None
        return
    if not rem_size.ans:
        d = document.createElement('span')
        d.style.position = 'absolute'
        d.style.visibility = 'hidden'
        d.style.width = '1rem'
        d.style.fontSize = '1rem'
        d.style.paddingTop = d.style.paddingBottom = d.style.paddingLeft = d.style.paddingRight = '0'
        d.style.marginTop = d.style.marginBottom = d.style.marginLeft = d.style.marginRight = '0'
        d.style.borderStyle = 'none'
        document.body.appendChild(d)
        rem_size.ans = max(2, d.clientWidth)
        document.body.removeChild(d)
    return rem_size.ans


def line_height(reset):
    if reset:
        line_height.ans = None
        return
    if not line_height.ans:
        ds = window.getComputedStyle(document.body)
        try:
            # will fail if line-height = "normal"
            lh = float(ds.lineHeight)
        except:
            try:
                lh = 1.2 * float(ds.fontSize)
            except:
                lh = 15
        line_height.ans = max(5, lh)
    return line_height.ans
