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

from elementmaker import E
from gettext import gettext as _

from ajax import absolute_path
from dom import add_extra_css, svgicon, unique_id
from popups import MODAL_Z_INDEX
from read_book.flow_mode import FlickAnimator
from read_book.touch import TouchHandler, install_handlers
from utils import debounce

add_extra_css(def ():
    # Modern, minimal "glass" image viewer styling
    css = ''
    css += '.calibre-image-popup { background: rgba(10, 10, 12, 0.92); color: rgba(255,255,255,0.92); }\n'
    css += '.calibre-image-popup canvas { outline: none; }\n'
    css += '.calibre-image-popup .cs-image-toolbar {'
    css += ' position: fixed; top: 0; left: 0; width: 100%;'
    css += ' box-sizing: border-box; display: flex; justify-content: space-between; align-items: center;'
    css += ' padding: calc(env(safe-area-inset-top, 0px) + 10px) 12px 10px 12px;'
    css += ' background: rgba(20,20,24,0.55);'
    css += ' border-bottom: var(--calibre-border-style-subtle);'
    css += ' backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);'
    css += ' }\n'
    css += '.calibre-image-popup a {'
    css += ' display: inline-flex; align-items: center; justify-content: center;'
    css += ' gap: 0.4rem;'
    css += ' padding: 0.55rem 0.75rem;'
    css += ' border-radius: 999px;'
    css += ' cursor: pointer; user-select: none;'
    css += ' background: rgba(255,255,255,0.10);'
    css += ' border: var(--calibre-border-style-subtle);'
    css += ' color: inherit;'
    css += ' }\n'
    css += '.calibre-image-popup a:hover { background: rgba(255,255,255,0.16); }\n'
    css += '.calibre-image-popup a:active { transform: scale(0.98); }\n'
    css += '.calibre-image-popup a:focus { outline: none; box-shadow: 0 0 0 3px rgba(108,180,238,0.35); }\n'
    css += '.calibre-image-popup svg { width: 2.1ex; height: 2.1ex; }\n'
    css += '.calibre-image-popup.fit-to-window a.fit-to-window {'
    css += ' background: rgba(108,180,238,0.28);'
    css += ' border-color: rgba(108,180,238,0.38);'
    css += ' }\n'
    css += '@media (max-width: 720px) {'
    css += ' .calibre-image-popup .cs-image-toolbar { padding-left: 10px; padding-right: 10px; }'
    css += ' .calibre-image-popup a { padding: 0.65rem 0.9rem; }'
    css += ' }\n'
    return css
)


def fit_image(width, height, pwidth, pheight):
    '''
    Fit image in box of width pwidth and height pheight.
    @param width: Width of image
    @param height: Height of image
    @param pwidth: Width of box
    @param pheight: Height of box
    @return: scaled, new_width, new_height. scaled is True iff new_width and/or new_height is different from width or height.
    '''
    scaled = height > pheight or width > pwidth
    if height > pheight:
        corrf = pheight / float(height)
        width, height = Math.floor(corrf * width), pheight
    if width > pwidth:
        corrf = pwidth / float(width)
        width, height = pwidth, Math.floor(corrf * height)
    if height > pheight:
        corrf = pheight / float(height)
        width, height = Math.floor(corrf * width), pheight

    return scaled, int(width), int(height)


class ImagePopup(TouchHandler):
    def __init__(self):
        TouchHandler.__init__(self)
        self.flick_animator = fa = FlickAnimator()
        fa.scroll_x = self.scroll_x
        fa.scroll_y = self.scroll_y
        fa.cancel_current_drag_scroll = def (): pass
        self._container = None
        self.container_id = unique_id('image-popup')
        self.img = None
        self.img_loading = False
        self.img_ok = False

    @property
    def container(self):
        if self._container is None:
            self._container = E.div(
                style=(
                    f'display:none; position:absolute; left:0; top: 0; z-index: {MODAL_Z_INDEX};'
                    + ' width: 100vw; height: 100vh; padding: 0; margin: 0; border-width: 0; box-sizing: border-box'
                ),
                id=self.container_id,
                tabindex='0',
                class_='calibre-image-popup',
                onkeydown=self.onkeydown,
                onwheel=self.onwheel,
                E.div(
                    class_='cs-image-toolbar',
                    E.a(svgicon('fit-to-screen'), title=_('Fit image to screen'), class_='fit-to-window', onclick=self.toggle_fit_to_window,),
                    E.a(svgicon('close'), title=_('Close'), onclick=self.hide_container),
                ),
                E.canvas(
                    width=window.innerWidth + '', height=window.innerHeight + '',
                    style='width: 100%; height: 100%; margin: 0; padding: 0; border-width: 0; box-sizing: border-box; display: block',
                    aria_label='Popup view of image',
                )
            )
            document.body.appendChild(self._container)
            install_handlers(self.canvas, self)
            window.addEventListener('resize', debounce(self.resize_canvas, 250))
        return self._container

    def onwheel(self, ev):
        ev.stopPropagation(), ev.preventDefault()
        dy = ev.deltaY
        dx = ev.deltaX
        if ev.deltaMode is window.WheelEvent.DOM_DELTA_LINE:
            dy *= 20
            dx *= 20
        self.scroll_y(dy)
        self.scroll_x(dx)

    def onkeydown(self, ev):
        ev.preventDefault(), ev.stopPropagation()
        if ev.key is ' ':
            return self.toggle_fit_to_window()
        if ev.key is 'Escape':
            return self.hide_container()
        if ev.key is 'ArrowDown':
            return self.scroll_down((window.innerHeight - 10) if ev.getModifierState('Control') else 10)
        if ev.key is 'PageDown':
            return self.scroll_down(window.innerHeight - 10)
        if ev.key is 'ArrowUp':
            return self.scroll_up((window.innerHeight - 10) if ev.getModifierState('Control') else 10)
        if ev.key is 'PageUp':
            return self.scroll_up(window.innerHeight - 10)
        if ev.key is 'ArrowLeft':
            return self.scroll_left((window.innerWidth - 10) if ev.getModifierState('Control') else 10)
        if ev.key is 'ArrowRight':
            return self.scroll_right((window.innerWidth - 10) if ev.getModifierState('Control') else 10)

    def handle_gesture(self, gesture):
        self.flick_animator.stop()
        if gesture.type is 'swipe':
            if not self.img_ok or self.fit_to_window:
                return
            if gesture.points.length > 1 and not gesture.is_held:
                delta = gesture.points[-2] - gesture.points[-1]
                if Math.abs(delta) >= 1:
                    if gesture.axis is 'vertical':
                        self.scroll_y(delta)
                    else:
                        self.scroll_x(delta)
            if not gesture.active and not gesture.is_held:
                self.flick_animator.start(gesture)
        if gesture.type is 'pinch' and self.img_ok:
            if gesture.direction is 'in':
                self.fit_to_window = True
            else:
                self.fit_to_window = False
            self.update_canvas()

    @property
    def vertical_max_bounce_distance(self):
        return Math.min(60, window.innerHeight / 2)

    @property
    def horizontal_max_bounce_distance(self):
        return Math.min(60, window.innerWidth / 2)

    def scroll_down(self, amt):
        if self.img_ok and not self.fit_to_window:
            canvas_height = self.canvas_height
            if self.img.height <= canvas_height:
                return
            miny = canvas_height - self.img.height - self.vertical_max_bounce_distance
            y = Math.max(self.y - amt, miny)
            if y is not self.y:
                self.y = y
                self.update_canvas()

    def scroll_up(self, amt):
        if self.img_ok and not self.fit_to_window:
            canvas_height = self.canvas_height
            if self.img.height <= canvas_height:
                return
            y = Math.min(self.y + amt, self.vertical_max_bounce_distance)
            if y is not self.y:
                self.y = y
                self.update_canvas()

    def scroll_right(self, amt):
        if self.img_ok and not self.fit_to_window:
            canvas_width = self.canvas_width
            if self.img.width <= canvas_width:
                return
            minx = canvas_width - self.img.width - self.horizontal_max_bounce_distance
            x = Math.max(self.x - amt, minx)
            if x is not self.x:
                self.x = x
                self.update_canvas()

    def scroll_left(self, amt):
        if self.img_ok and not self.fit_to_window:
            canvas_width = self.canvas_width
            if self.img.width <= canvas_width:
                return
            x = Math.min(self.x + amt, self.horizontal_max_bounce_distance)
            if x is not self.x:
                self.x = x
                self.update_canvas()

    def scroll_y(self, amt):
        if amt < 0:
            self.scroll_up(-amt)
        elif amt > 0:
            self.scroll_down(amt)

    def scroll_x(self, amt):
        if amt < 0:
            self.scroll_left(-amt)
        elif amt > 0:
            self.scroll_right(amt)

    @property
    def canvas(self):
        return self.container.getElementsByTagName('canvas')[0]

    @property
    def fit_to_window(self):
        return self.container.classList.contains('fit-to-window')

    @fit_to_window.setter
    def fit_to_window(self, val):
        c = self.container
        if val:
            c.classList.add('fit-to-window')
            c.querySelector('a.fit-to-window').title = _('Do not fit image to window')
        else:
            c.classList.remove('fit-to-window')
            c.querySelector('a.fit-to-window').title = _('Fit image to window')

    def resize_canvas(self):
        c = self.canvas
        dpr = Math.max(1, window.devicePixelRatio)
        c.width = Math.ceil(window.innerWidth * dpr)
        c.height = Math.ceil(window.innerHeight * dpr)
        c.style.width = (c.width / dpr) + 'px'
        c.style.height = (c.height / dpr) + 'px'
        ctx = c.getContext('2d')
        ctx.setTransform(1, 0, 0, 1, 0, 0)
        ctx.scale(dpr, dpr)
        ctx.fillStyle = ctx.strokeStyle = '#eee'
        ctx.font = '16px sans-serif'
        self.update_canvas()

    def show_container(self):
        self.flick_animator.stop()
        self.container.style.display = 'block'

    def hide_container(self):
        self.flick_animator.stop()
        self.container.style.display = 'none'

    def show_url(self, url):
        self.img = Image()
        self.img.addEventListener('load', self.image_loaded)
        self.img.addEventListener('error', self.image_failed)
        self.img_loading = True
        self.x = self.y = 0
        self.img_ok = False
        self.fit_to_window = True
        self.img.src = url
        self.show_container()
        self.resize_canvas()

    @property
    def canvas_width(self):
        return self.canvas.width / Math.max(1, window.devicePixelRatio)

    @property
    def canvas_height(self):
        return self.canvas.height / Math.max(1, window.devicePixelRatio)

    def update_canvas(self):
        canvas = self.canvas
        ctx = canvas.getContext('2d')
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        canvas_width, canvas_height = self.canvas_width, self.canvas_height

        def draw_centered_text(text):
            tm = ctx.measureText(text)
            x = Math.max(0, (canvas_width - tm.width) / 2)
            y = (canvas_height - 16) / 2
            ctx.fillText(text, x, y)

        if self.img_loading:
            draw_centered_text(_('Loading image, please wait…'))
            return
        if not self.img_ok:
            draw_centered_text(_('Loading the image failed'))
            return

        def draw_full_image_fit_to_canvas():
            scaled, width, height = fit_image(self.img.width, self.img.height, canvas_width, canvas_height)
            scaled
            x = (canvas_width - width) / 2
            y = (canvas_height - height) / 2
            ctx.drawImage(self.img, x, y, width, height)

        if self.fit_to_window:
            draw_full_image_fit_to_canvas()
            return

        ctx.drawImage(self.img, self.x, self.y)

    def toggle_fit_to_window(self):
        self.fit_to_window ^= True
        self.x = self.y = 0
        self.update_canvas()

    def image_loaded(self):
        self.img_loading = False
        self.img_ok = True
        self.update_canvas()

    def image_failed(self):
        self.img_loading = False
        self.img_ok = False
        self.update_canvas()


popup = None


def show_image(url):
    nonlocal popup
    if popup is None:
        popup = ImagePopup()
    popup.show_url(url)


def develop(container):
    show_image(absolute_path('get/cover/1698'))
