# fetcher.py
#
# Copyright 2020-2022 Fabio Comuni, et al.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from threading import Thread
import os
import logging
import tempfile
import urllib.request

from gi.repository import GLib  # type: ignore
from gi.repository import GObject

logger = logging.getLogger(__name__)


class DownloadCancelled(Exception):
    pass


class Fetcher(GObject.GObject):
    """ Fetch source to dest, asyncly.

    property
        running (bool, r) download is running
        fraction (floar, r) download progress 0.0 - 1.0

    signals:
        error (message:str)
        done (local_file_path:str)
    """

    @GObject.Signal(arg_types=(str,))
    def error(self, message):
        ...

    @GObject.Signal(arg_types=(str,))
    def done(self, local_file_path):
        ...

    @GObject.Property(type=bool, default=False)
    def running(self):
        return self._run

    @GObject.Property(type=float)
    def fraction(self):
        return self._fraction

    def __init__(self, source, dest=None, cbk=None):
        """ download `source` to `dest`
            `source`: uri
            `dest`: path. if None, then temp
            `cbk` : function called when done: `fn(local_file_path)`

            `cbk` is called before firing `done` signal. If any exception
            is raised by `cbk`, `error` signal is fired, instead.
        """
        super().__init__()

        self._run = True
        self._fraction = 0.0
        self._cbk = cbk

        if dest is None:
            _, dest = tempfile.mkstemp()
        logger.info(f"fetch {source=} to {dest=}")
        Thread(target=self._fetch, args=(source, dest)).start()

    def _fetch(self, source, dest):
        try:
            with urllib.request.urlopen(source, timeout=20) as fsrc, open(dest, "wb") as fdst:
                total_size = int(fsrc.info().get('Content-Length', 0))
                block_size = 8192
                dwl_size = 0
                # python in runtime is old :)
                # while chunk := fsrc.read(block_size):
                chunk = fsrc.read(block_size)
                while chunk:
                    if not self._run:
                        raise DownloadCancelled("Download cacelled")
                    fdst.write(chunk)
                    if total_size > 0:
                        dwl_size += block_size
                        self._fraction = min(dwl_size, total_size) / total_size
                        GLib.idle_add(self.notify, "fraction")
                    chunk = fsrc.read(block_size)
        except Exception as e:
            if os.path.exists(dest):
                os.unlink(dest)
            import traceback
            traceback.print_exc()

            GLib.idle_add(self.emit, "error", str(e))
        else:
            GLib.idle_add(self._done, dest)

    def _done(self, dest):
        if self._cbk is not None:
            try:
                self._cbk(self, dest)
            except Exception as e:
                import traceback
                traceback.print_exc()

                GLib.idle_add(self.emit, "error", str(e))
                return
        GLib.idle_add(self.emit, "done", dest)

    def cancel(self):
        self._run = False
        self.notify("running")
