#!/usr/local/bin/python3.12
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>


import json
import os
import shutil

from qt.core import QApplication, QHBoxLayout, QIcon, QLabel, QProgressBar, QPushButton, QSize, QUrl, QVBoxLayout, QWidget, pyqtSignal
from qt.webengine import QWebEngineDownloadRequest, QWebEnginePage, QWebEngineProfile, QWebEngineView

from calibre import random_user_agent, url_slash_cleaner
from calibre.constants import STORE_DIALOG_APP_UID, islinux, iswindows
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2 import Application, choose_save_file, error_dialog, gprefs, info_dialog, set_app_uid
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.listener import send_message_in_process
from calibre.gui2.main_window import MainWindow
from calibre.ptempfile import PersistentTemporaryDirectory, reset_base_dir
from calibre.startup import connect_lambda
from calibre.utils.webengine import setup_profile
from polyglot.binary import as_base64_bytes, from_base64_bytes
from polyglot.builtins import string_or_bytes


class DownloadItem(QWidget):

    def __init__(self, download_id, filename, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QHBoxLayout(self)
        self.la = la = QLabel(f'{filename}:\xa0')
        la.setMaximumWidth(400)
        l.addWidget(la)

        self.pb = pb = QProgressBar(self)
        pb.setRange(0, 0)
        l.addWidget(pb)

        self.download_id = download_id

    def __call__(self, done, total):
        self.pb.setRange(0, total)
        self.pb.setValue(done)


class DownloadProgress(QWidget):

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setVisible(False)
        self.l = QVBoxLayout(self)
        self.items = {}

    def add_item(self, download_id, filename):
        self.setVisible(True)
        item = DownloadItem(download_id, filename, self)
        self.l.addWidget(item)
        self.items[download_id] = item

    def update_item(self, download_id, done, total):
        item = self.items.get(download_id)
        if item is not None:
            item(done, total)

    def remove_item(self, download_id):
        item = self.items.pop(download_id, None)
        if item is not None:
            self.l.removeWidget(item)
            item.setVisible(False)
            item.setParent(None)
            item.deleteLater()
        if not self.items:
            self.setVisible(False)


def create_profile():
    ans = getattr(create_profile, 'ans', None)
    if ans is None:
        ans = create_profile.ans = setup_profile(QWebEngineProfile('web_store', QApplication.instance()))
        ans.setHttpUserAgent(random_user_agent(allow_ie=False))
    return ans


class Central(QWidget):

    home = pyqtSignal()

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout(self)
        self.view = v = QWebEngineView(self)
        self._page = QWebEnginePage(create_profile(), v)
        v.setPage(self._page)
        v.loadStarted.connect(self.load_started)
        v.loadProgress.connect(self.load_progress)
        v.loadFinished.connect(self.load_finished)
        l.addWidget(v)
        self.h = h = QHBoxLayout()
        l.addLayout(h)
        self.download_progress = d = DownloadProgress(self)
        h.addWidget(d)

        self.home_button = b = QPushButton(_('Home'))
        b.clicked.connect(self.home)
        h.addWidget(b)
        self.back_button = b = QPushButton(_('Back'))
        b.clicked.connect(v.back)
        h.addWidget(b)
        self.forward_button = b = QPushButton(_('Forward'))
        b.clicked.connect(v.forward)
        h.addWidget(b)

        self.progress_bar = b = QProgressBar(self)
        h.addWidget(b)

        self.reload_button = b = QPushButton(_('Reload'))
        b.clicked.connect(v.reload)
        h.addWidget(b)

    @property
    def profile(self):
        return self.view.page().profile()

    def load_started(self):
        self.progress_bar.setValue(0)

    def load_progress(self, amt):
        self.progress_bar.setValue(amt)

    def load_finished(self, ok):
        self.progress_bar.setValue(100)


class Main(MainWindow):

    def __init__(self, data):
        MainWindow.__init__(self, None)
        self.setWindowIcon(QIcon.ic('store.png'))
        self.setWindowTitle(data['window_title'])
        self.download_data = {}
        self.data = data
        self.central = c = Central(self)
        c.home.connect(self.go_home)
        c.profile.downloadRequested.connect(self.download_requested)
        self.setCentralWidget(c)
        self.restore_geometry(gprefs, 'store_dialog_main_window_geometry')
        self.go_to(data['detail_url'] or None)

    def sizeHint(self):
        return QSize(1024, 740)

    def closeEvent(self, e):
        self.save_geometry(gprefs, 'store_dialog_main_window_geometry')
        MainWindow.closeEvent(self, e)

    @property
    def view(self):
        return self.central.view

    def go_home(self):
        self.go_to()

    def go_to(self, url=None):
        url = url or self.data['base_url']
        url = url_slash_cleaner(url)
        self.view.load(QUrl(url))

    def download_requested(self, download_item):
        fname = download_item.downloadFileName()
        download_id = download_item.id()
        tdir = PersistentTemporaryDirectory()
        self.download_data[download_id] = download_item
        download_item.setDownloadDirectory(tdir)
        connect_lambda(download_item.receivedBytesChanged, self, lambda self: self.download_progress(download_id))
        connect_lambda(download_item.totalBytesChanged, self, lambda self: self.download_progress(download_id))
        connect_lambda(download_item.isFinishedChanged, self, lambda self: self.download_finished(download_id))
        download_item.accept()
        self.central.download_progress.add_item(download_id, fname)

    def download_progress(self, download_id):
        download_item = self.download_data.get(download_id)
        if download_item is not None:
            self.central.download_progress.update_item(download_id, download_item.receivedBytes(), download_item.totalBytes())

    def download_finished(self, download_id):
        self.central.download_progress.remove_item(download_id)
        download_item = self.download_data.pop(download_id)
        fname = download_item.downloadFileName()
        path = os.path.join(download_item.downloadDirectory(), fname)
        if download_item.state() == QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
            error_dialog(self, _('Download failed'), _(
                'Download of {0} failed with error: {1}').format(fname, download_item.interruptReasonString()), show=True)
            return
        ext = fname.rpartition('.')[-1].lower()
        if ext not in BOOK_EXTENSIONS:
            if ext == 'acsm':
                if not confirm('<p>' + _(
                    'This e-book is a DRMed EPUB file.  '
                    'You will be prompted to save this file to your '
                    'computer. Once it is saved, open it with '
                    '<a href="https://www.adobe.com/solutions/ebook/digital-editions.html">'
                    'Adobe Digital Editions</a> (ADE).<p>ADE, in turn '
                    'will download the actual e-book, which will be a '
                    '.epub file. You can add this book to calibre '
                    'using "Add Books" and selecting the file from '
                    'the ADE library folder.'),
                    'acsm_download', self):
                    return
            name = choose_save_file(self, 'web-store-download-unknown', _(
                'File is not a supported e-book type. Save to disk?'), initial_filename=fname)
            if name:
                shutil.copyfile(path, name)
                os.remove(path)
            return
        tags = self.data['tags']
        if isinstance(tags, string_or_bytes):
            tags = list(filter(None, [x.strip() for x in tags.split(',')]))
        data = json.dumps({'path': path, 'tags': tags})
        if not isinstance(data, bytes):
            data = data.encode('utf-8')

        try:
            send_message_in_process(b'web-store:' + data)
        except Exception as err:
            error_dialog(self, _('Could not contact calibre'), _(
                'No running calibre instance found. Please start calibre before trying to'
                ' download books.'), det_msg=str(err), show=True)
            return

        info_dialog(self, _('Download completed'), _(
            'Download of {0} has been completed, the book was added to'
            ' your calibre library').format(fname), show=True)


def main(args):
    # Ensure we can continue to function if GUI is closed
    os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
    reset_base_dir()
    if iswindows:
        # Ensure that all instances are grouped together in the task bar. This
        # prevents them from being grouped with viewer/editor process when
        # launched from within calibre, as both use calibre-parallel.exe
        set_app_uid(STORE_DIALOG_APP_UID)

    data = args[-1]
    data = json.loads(from_base64_bytes(data))
    override = 'calibre-gui' if islinux else None
    app = Application(args, override_program_name=override)
    m = Main(data)
    m.show(), m.raise_and_focus()
    app.exec()
    del m
    del app


if __name__ == '__main__':
    sample_data = as_base64_bytes(
        json.dumps({
            'window_title': 'MobileRead',
            'base_url': 'https://www.mobileread.com/',
            'detail_url': 'http://www.mobileread.com/forums/showthread.php?t=54477',
            'id':1,
            'tags': '',
        })
    )
    main(['store-dialog', sample_data])
