# -*- coding: UTF-8 -*-

__revision__ = '$Id$'

# Copyright (c) 2005-2009 Vasco Nunes, Piotr Ożarowski
#
# 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 2 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 Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

# You may use and distribute this software under the terms of the
# GNU General Public License, version 2 or later

import logging
import os
import tempfile
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
from gettext import gettext as _
from locale import getlocale
from typing import IO
from urllib.parse import quote
from urllib.request import urlcleanup

import gi
import requests

gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
import gutils

log = logging.getLogger("Griffith")
_UAURLOPENER = None
TEMP_FILE_CLEANUP = None


class Movie:
    cast = ''
    classification = ''
    country = ''
    director = ''
    genre = ''
    image = ''
    notes = ''
    number = None
    o_site = ''
    o_title = ''
    plot = ''
    rating = ''
    runtime = ''
    site = ''
    studio = ''
    title = ''
    trailer = ''
    year = ''
    screenplay = ''
    cameraman = ''
    resolution = ''
    barcode = ''

    movie_id = ''
    debug = False
    locations = ''
    engine_author = None
    engine_description = None
    engine_language = None
    engine_name = None
    engine_version = None
    page = None
    url = ''
    image_url = ''
    encode = 'utf-8'
    fields_to_fetch = []
    progress = None
    parent_window = None
    poster_md5 = None
    medium_id = None

    # functions that plugin should implement: {{{
    def initialize(self):
        pass

    def get_barcode(self):
        pass

    def get_cameraman(self):
        pass

    def get_cast(self):
        pass

    def get_classification(self):
        pass

    def get_country(self):
        pass

    def get_director(self):
        pass

    def get_genre(self):
        pass

    def get_image(self):
        pass

    def get_notes(self):
        pass

    def get_o_site(self):
        pass

    def get_o_title(self):
        pass

    def get_plot(self):
        pass

    def get_rating(self):
        pass

    def get_resolution(self):
        pass

    def get_runtime(self):
        pass

    def get_screenplay(self):
        pass

    def get_site(self):
        pass

    def get_studio(self):
        pass

    def get_title(self):
        pass

    def get_trailer(self):
        pass

    def get_year(self):
        pass

    # }}}

    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)

    def get_movie(self, parent_window=None):
        try:
            # initialize the progress dialog once for the following loading process
            if self.progress is None:
                self.progress = Progress(parent_window)
            self.progress.set_data(
                parent_window, _("Checking connection"), _("Wait a moment"),
                True)
            # check for internet connection
            urllib.request.urlopen("https://www.google.com")
            self.progress.set_data(
                parent_window, _("Fetching data"), _("Wait a moment"), True)
            # get the page
            if not self.open_page(parent_window):
                return None
            return True

        except Exception as exc:
            log.error(f"[E017]Caught too broad try-except, {type(exc).__name__}:"
                      f" {exc}")
            self.progress.hide()
            gutils.error(_("Connection failed."))
            return False

    def open_page(self, parent_window=None, url=None):
        log.debug("[Movie>open_page]")
        if url is None:
            url_to_fetch = self.url
        else:
            url_to_fetch = url
        if parent_window is not None:
            self.parent_window = parent_window
        self.progress.set_data(
            parent_window, _("Fetching data"), _("Wait a moment"), False)
        retriever = Retriever(url_to_fetch, self.parent_window, self.progress)
        retriever.start()
        log.debug(f"self.progress.status: retriever.start(), url: {self.url}")
        while retriever.is_alive():
            self.progress.pulse()
            if self.progress.status:
                retriever.join()
            while Gtk.events_pending():
                Gtk.main_iteration()
        data = None
        try:
            if retriever.exception is None:
                if retriever.html:
                    log.debug(f"[Movie>open_page]: retriever.html: "
                              f"{retriever.html}")
                    with open(retriever.html[0], "r", encoding=self.encode,
                              errors='replace') as ifile:
                        data = ifile.read()
                    # check for gzip compressed pages before decoding to Unicode
                    if len(data) > 2 and data[0:2] == '\037\213':
                        log.debug(f"data is gzipped: {retriever.html[0]}")
                        data = gutils.decompress(data)
            else:
                log.warning(f"Unable to retrieve: {retriever.exception}")
                gutils.urllib_error(_("Connection error"), self.parent_window)
        except IOError as exc:
            log.exception(f"IOError: {exc}")
        log.debug(f"Retrieved data size: {len(data)}")
        if url is None:
            self.page = data
        urlcleanup()
        return data

    def fetch_picture(self):
        if self.image_url:
            tmp_dest = tempfile.NamedTemporaryFile(delete_on_close=False,
                                                   delete=False, suffix='.jpg')
            self.image = tmp_dest.name
            log.debug(f"[Movie>fetch_picture]: self.image: {self.image}")
            try:
                self.progress.set_data(
                    self.parent_window,
                    _("Fetching poster"),
                    _("Wait a moment"),
                    False)
                retriever = Retriever(self.image_url, self.parent_window,
                                      self.progress, self.image)
                retriever.start()
                while retriever.is_alive():
                    self.progress.pulse()
                    if self.progress.status:
                        retriever.join()
                    while Gtk.events_pending():
                        Gtk.main_iteration()
                urlcleanup()
            except Exception as exc:
                log.exception(f"Caught too broad try-except, {exc}")
                self.image = ""
                try:
                    os.remove(tmp_dest.name)
                except Exception as exc:
                    log.info(f"Can't remove {tmp_dest} file, {exc}")
            log.debug(f"[Movie>fetch_picture]: exists: "
                      f"{os.path.isfile(self.image)} self.image: {self.image}")
        else:
            self.image = ""

    def parse_movie(self):
        log.debug("[parse_movie]")
        try:
            fields = list(self.fields_to_fetch)  # make a copy

            self.initialize()

            if 'year' in fields:
                self.get_year()
                self.year = gutils.digits_only(self.year, 2100)
                fields.pop(fields.index('year'))
            if 'runtime' in fields:
                self.get_runtime()
                self.runtime = gutils.digits_only(self.runtime)
                fields.pop(fields.index('runtime'))
            if 'rating' in fields:
                self.get_rating()
                self.rating = gutils.digits_only(self.rating, 10)
                fields.pop(fields.index('rating'))
            if 'cast' in fields:
                self.get_cast()
                self.cast = gutils.clean(self.cast)
                if not isinstance(self.cast, str):
                    self.cast = gutils.gdecode(self.cast, self.encode)
                fields.pop(fields.index('cast'))
            if 'plot' in fields:
                self.get_plot()
                log.debug(f"self.plot: {self.plot}")
                self.plot = gutils.clean(self.plot)
                if not isinstance(self.plot, str):
                    self.plot = gutils.gdecode(self.plot, self.encode)
                fields.pop(fields.index('plot'))
            if 'notes' in fields:
                self.get_notes()
                self.notes = gutils.clean(self.notes)
                if not isinstance(self.notes, str):
                    self.notes = gutils.gdecode(self.notes, self.encode)
                fields.pop(fields.index('notes'))
            if 'image' in fields:
                log.debug(f"image in fields: {fields}")
                self.get_image()
                self.fetch_picture()
                log.debug(f"[Movie>parse_move]: self.image_url: '{self.image_url}' self.image: '{self.image}' "
                          f"Exists: {os.path.isfile(self.image)}")
                fields.pop(fields.index('image'))

            for i in fields:
                getattr(self, "get_%s" % i)()
                self[i] = gutils.clean(self[i])
                if not isinstance(self[i], str):
                    self[i] = gutils.gdecode(self[i], self.encode)

            if 'o_title' in self.fields_to_fetch and self.o_title is not None:
                if self.o_title[:4] == 'The ':
                    self.o_title = self.o_title[4:] + ', The'
            if 'title' in self.fields_to_fetch and self.title is not None:
                if self.title[:4] == 'The ':
                    self.title = self.title[4:] + ', The'
        except Exception as exc:
            log.exception(f"Caught too broad try-except, {type(exc).__name__}:"
                          f" {exc}")
        finally:
            # close the progress dialog which was opened in get_movie
            self.progress.hide()


class SearchMovie(object):
    page = None
    number_results = None
    titles = []
    ids = []
    url = None
    encode = 'utf-8'
    original_url_search = None
    translated_url_search = None
    elements = None
    title = None
    remove_accents = True
    progress = None
    usepostrequest = False

    def __init__(self):
        log.debug("[SearchMovie>__init__]")
        pass

    def search_movies(self, parent_window):
        try:
            # initialize the progress dialog once for the following search process
            if self.progress is None:
                self.progress = Progress(parent_window)
            self.progress.set_data(parent_window, _("Searching"), _("Wait a moment"), True)
            # call the plugin specific search method
            return self.search(parent_window)
        finally:
            # don't forget to hide the progress dialog
            self.progress.hide()

    def open_search(self, parent_window, destination=None):
        log.debug("[SearchMovie>open_search]")
        self.titles = []
        self.ids = []

        self.title = quote(self.title)
        if self.url.find('%s') > 0:
            self.url %= self.title
            self.url = self.url.replace(' ', '%20')
        else:
            if not self.usepostrequest:
                temp_str = self.url + self.title
                self.url = temp_str.replace(' ', '%20')
        url = self.url
        self.progress.set_data(parent_window, _("Searching"), _("Wait a moment"), True)
        if self.usepostrequest:
            postdata = self.get_postdata()
            retriever = Retriever(url, parent_window, self.progress,
                                  destination, postdata=postdata)
        else:
            retriever = Retriever(url, parent_window, self.progress,
                                  destination)
        retriever.start()
        while retriever.is_alive():
            self.progress.pulse()
            if self.progress.status:
                retriever.join()
            while Gtk.events_pending():
                Gtk.main_iteration()
        try:
            if retriever.exception is None:
                if destination:
                    # caller gave an explicit destination file
                    # don't care about the content
                    return True
                if retriever.html:
                    with open(retriever.html[0], 'rb') as ifile:
                        self.page = ifile.read()
                    # check for gzip compressed pages before decoding to Unicode
                    if len(self.page) > 2 and self.page[0:2] == '\037\213':
                        self.page = gutils.decompress(self.page)
                    self.page = self.page.decode(self.encode, 'replace')
                else:
                    return False
            else:
                self.progress.hide()
                gutils.urllib_error(_("Connection error"), parent_window)
                return False
        except IOError as exc:
            log.exception(f"IOError: {exc}")
        finally:
            urlcleanup()
        return True

    def get_postdata(self):
        # sample, depends on target site
        # return {'title' : self.Title, 'category' : 'movieTitel' }
        return {}


class Retriever(threading.Thread):

    def __init__(self, url: str, parent_window, progress, destination=None,
                 postdata=None):
        self.url = url
        self.html = None
        self.exception = None
        self.destination = destination
        self.progress = progress
        self._stopevent = threading.Event()
        self._sleepperiod = 1.0
        self.postdata = postdata
        threading.Thread.__init__(self, name="Retriever")
        log.debug(f"[Retriever>__init__]: url='{url}', postdata=[{postdata}]")

    def run(self):
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:147.0) Gecko/20100101 Firefox/147.0',
            'Cache-Control': 'no-cache',
            'Pragma': 'no-cache',
            'Accept-Encoding': 'gzip',
            'Accept-Language': f"en-US,en;q=0.9,{getlocale()[0]};q=0.7"
            }

        try:
            log.debug(f"Retriever>run: url='{self.url}'")
            request = requests.get(self.url, headers=headers)
            log.debug(f"request.status_code={request.status_code}")
            if request.status_code == 200:
                # mimic the signature of the former urlretrieve(2) functions which
                # use the deprecated urllib.request.FancyURLopener. A filename of
                # the content is from the request is expected by later functions.
                if self.destination:
                    filename = self.destination
                else:
                    fd, filename = create_tempfile()
                with open(filename, 'wb') as fp:
                    fp.write(request.content)
                    log.debug(f"[Retriever>run]: filename={filename}")
                self.html = (filename, None)
            else:
                log.debug(f"self.progress.status={self.progress.status}")
                self.html = None
        except Exception as exc:
            log.exception(f"[E019]Caught too broad try-except,"
                          f" {type(exc).__name__}: {exc}")
            self.exception = exc

    def hook(self, count, block_size, total_size):
        """Previous implementation suggested calculation of the download
        percentage, but it was meaningless as it was implemented."""
        pass


def create_tempfile() -> tuple[IO[bytes], str]:
    global TEMP_FILE_CLEANUP
    tmp_file = tempfile.NamedTemporaryFile(delete_on_close=False)
    filename = tmp_file.name
    fd = tmp_file.file
    if not TEMP_FILE_CLEANUP:
        TEMP_FILE_CLEANUP = TempFileCleanup()
    TEMP_FILE_CLEANUP.remember_tempfile(filename)
    return fd, filename


class TempFileCleanup:
    _tempfiles = []

    def __init__(self):
        self.__unlink = os.unlink

    def __del__(self):
        self.cleanup()

    def remember_tempfile(self, filename):
        self._tempfiles.append(filename)
        log.debug(f"remember_tempfile: {filename} {self._tempfiles}")

    def cleanup(self):
        if self._tempfiles:
            for file in self._tempfiles:
                try:
                    self.__unlink(file)
                except OSError:
                    pass
            del self._tempfiles[:]


class Progress:

    def __init__(self, window, title='', message=''):
        self.status = False
        self.dialog = Gtk.Dialog(title, window, Gtk.DialogFlags.MODAL, ())
        self.dialog.set_urgency_hint(False)
        self.label = Gtk.Label()
        self.label.set_markup(message)
        self.dialog.vbox.pack_start(self.label, True, True, 0)
        self.progress = Gtk.ProgressBar()
        self.progress.set_pulse_step(0.01)
        self.dialog.vbox.pack_start(self.progress, False, False, 0)
        self.button = Gtk.Button(_("Cancel"), Gtk.STOCK_CANCEL)
        self.button.connect("clicked", self.callback)
        self.dialog.vbox.pack_start(self.button, False, False, 0)
        self.dialog.show_all()

    def callback(self, widget):
        self.dialog.hide()
        self.status = True

    def pulse(self):
        self.progress.pulse()
        time.sleep(0.01)

    def close(self):
        self.dialog.destroy()

    def hide(self):
        self.dialog.hide()

    def set_data(self, parent_window, title, message, showit):
        # self.dialog.set_parent(parent_window)
        self.dialog.set_title(title)
        self.label.set_markup(message)
        log.debug(f"Progress>set_data: title={title} message={message}")
        if showit is True:
            self.dialog.show()
# vim: fdm=marker
