# -*- coding: utf-8 -*-

###############################################################################
from logging import getLogger
from Queue import Queue, Empty
from shutil import rmtree
from urllib import quote_plus, unquote
from threading import Thread
from os import makedirs
import requests

from xbmc import sleep, translatePath
from xbmcvfs import exists

from utils import window, settings, language as lang, kodi_sql, try_encode, \
    thread_methods, dialog, exists_dir, try_decode
import state

###############################################################################
LOG = getLogger("PLEX." + __name__)

# Disable annoying requests warnings
requests.packages.urllib3.disable_warnings()
ARTWORK_QUEUE = Queue()
###############################################################################


def double_urlencode(text):
    return quote_plus(quote_plus(text))


def double_urldecode(text):
    return unquote(unquote(text))


@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
                              'DB_SCAN',
                              'STOP_SYNC'])
class Image_Cache_Thread(Thread):
    sleep_between = 50
    # Potentially issues with limited number of threads
    # Hence let Kodi wait till download is successful
    timeout = (35.1, 35.1)

    def __init__(self):
        self.queue = ARTWORK_QUEUE
        Thread.__init__(self)

    def run(self):
        stopped = self.stopped
        suspended = self.suspended
        queue = self.queue
        sleep_between = self.sleep_between
        while not stopped():
            # In the event the server goes offline
            while suspended():
                # Set in service.py
                if stopped():
                    # Abort was requested while waiting. We should exit
                    LOG.info("---===### Stopped Image_Cache_Thread ###===---")
                    return
                sleep(1000)
            try:
                url = queue.get(block=False)
            except Empty:
                sleep(1000)
                continue
            sleeptime = 0
            while True:
                try:
                    requests.head(
                        url="http://%s:%s/image/image://%s"
                            % (state.WEBSERVER_HOST,
                               state.WEBSERVER_PORT,
                               url),
                        auth=(state.WEBSERVER_USERNAME,
                              state.WEBSERVER_PASSWORD),
                        timeout=self.timeout)
                except requests.Timeout:
                    # We don't need the result, only trigger Kodi to start the
                    # download. All is well
                    break
                except requests.ConnectionError:
                    if stopped():
                        # Kodi terminated
                        break
                    # Server thinks its a DOS attack, ('error 10053')
                    # Wait before trying again
                    if sleeptime > 5:
                        LOG.error('Repeatedly got ConnectionError for url %s',
                                  double_urldecode(url))
                        break
                    LOG.debug('Were trying too hard to download art, server '
                              'over-loaded. Sleep %s seconds before trying '
                              'again to download %s',
                              2**sleeptime, double_urldecode(url))
                    sleep((2**sleeptime)*1000)
                    sleeptime += 1
                    continue
                except Exception as e:
                    LOG.error('Unknown exception for url %s: %s'.
                              double_urldecode(url), e)
                    import traceback
                    LOG.error("Traceback:\n%s", traceback.format_exc())
                    break
                # We did not even get a timeout
                break
            queue.task_done()
            # Sleep for a bit to reduce CPU strain
            sleep(sleep_between)
        LOG.info("---===### Stopped Image_Cache_Thread ###===---")


class Artwork():
    enableTextureCache = settings('enableTextureCache') == "true"
    if enableTextureCache:
        queue = ARTWORK_QUEUE

    def fullTextureCacheSync(self):
        """
        This method will sync all Kodi artwork to textures13.db
        and cache them locally. This takes diskspace!
        """
        if not dialog('yesno', "Image Texture Cache", lang(39250)):
            return

        LOG.info("Doing Image Cache Sync")

        # ask to rest all existing or not
        if dialog('yesno', "Image Texture Cache", lang(39251)):
            LOG.info("Resetting all cache data first")
            # Remove all existing textures first
            path = try_decode(translatePath("special://thumbnails/"))
            if exists_dir(path):
                rmtree(path, ignore_errors=True)
                self.restore_cache_directories()

            # remove all existing data from texture DB
            connection = kodi_sql('texture')
            cursor = connection.cursor()
            query = 'SELECT tbl_name FROM sqlite_master WHERE type=?'
            cursor.execute(query, ('table', ))
            rows = cursor.fetchall()
            for row in rows:
                tableName = row[0]
                if tableName != "version":
                    cursor.execute("DELETE FROM %s" % tableName)
            connection.commit()
            connection.close()

        # Cache all entries in video DB
        connection = kodi_sql('video')
        cursor = connection.cursor()
        # dont include actors
        query = "SELECT url FROM art WHERE media_type != ?"
        cursor.execute(query, ('actor', ))
        result = cursor.fetchall()
        total = len(result)
        LOG.info("Image cache sync about to process %s video images", total)
        connection.close()

        for url in result:
            self.cache_texture(url[0])
        # Cache all entries in music DB
        connection = kodi_sql('music')
        cursor = connection.cursor()
        cursor.execute("SELECT url FROM art")
        result = cursor.fetchall()
        total = len(result)
        LOG.info("Image cache sync about to process %s music images", total)
        connection.close()
        for url in result:
            self.cache_texture(url[0])

    def cache_texture(self, url):
        '''
        Cache a single image url to the texture cache
        '''
        if url and self.enableTextureCache:
            self.queue.put(double_urlencode(try_encode(url)))

    def modify_artwork(self, artworks, kodi_id, kodi_type, cursor):
        """
        Pass in an artworks dict (see PlexAPI) to set an items artwork.
        """
        for kodi_art, url in artworks.iteritems():
            self.modify_art(url, kodi_id, kodi_type, kodi_art, cursor)

    def modify_art(self, url, kodi_id, kodi_type, kodi_art, cursor):
        """
        Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
        Kodi art table for item kodi_id/kodi_type. Will also cache everything
        except actor portraits.
        """
        query = '''
            SELECT url FROM art
            WHERE media_id = ? AND media_type = ? AND type = ?
            LIMIT 1
        '''
        cursor.execute(query, (kodi_id, kodi_type, kodi_art,))
        try:
            # Update the artwork
            old_url = cursor.fetchone()[0]
        except TypeError:
            # Add the artwork
            LOG.debug('Adding Art Link for %s kodi_id %s, kodi_type %s: %s',
                      kodi_art, kodi_id, kodi_type, url)
            query = '''
                INSERT INTO art(media_id, media_type, type, url)
                VALUES (?, ?, ?, ?)
            '''
            cursor.execute(query, (kodi_id, kodi_type, kodi_art, url))
        else:
            if url == old_url:
                # Only cache artwork if it changed
                return
            self.delete_cached_artwork(old_url)
            LOG.debug("Updating Art url for %s kodi_id %s, kodi_type %s to %s",
                      kodi_art, kodi_id, kodi_type, url)
            query = '''
                UPDATE art SET url = ?
                WHERE media_id = ? AND media_type = ? AND type = ?
            '''
            cursor.execute(query, (url, kodi_id, kodi_type, kodi_art))
        # Cache fanart and poster in Kodi texture cache
        if kodi_type != 'actor':
            self.cache_texture(url)

    def delete_artwork(self, kodiId, mediaType, cursor):
        query = 'SELECT url FROM art WHERE media_id = ? AND media_type = ?'
        cursor.execute(query, (kodiId, mediaType,))
        for row in cursor.fetchall():
            self.delete_cached_artwork(row[0])

    @staticmethod
    def delete_cached_artwork(url):
        """
        Deleted the cached artwork with path url (if it exists)
        """
        connection = kodi_sql('texture')
        cursor = connection.cursor()
        try:
            cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1",
                           (url,))
            cachedurl = cursor.fetchone()[0]
        except TypeError:
            # Could not find cached url
            pass
        else:
            # Delete thumbnail as well as the entry
            path = translatePath("special://thumbnails/%s" % cachedurl)
            LOG.debug("Deleting cached thumbnail: %s", path)
            if exists(path):
                rmtree(try_decode(path), ignore_errors=True)
            cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
            connection.commit()
        finally:
            connection.close()

    @staticmethod
    def restore_cache_directories():
        LOG.info("Restoring cache directories...")
        paths = ("", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                 "a", "b", "c", "d", "e", "f",
                 "Video", "plex")
        for path in paths:
            makedirs(try_decode(translatePath("special://thumbnails/%s"
                                              % path)))