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

###############################################################################
import logging
from json import dumps, loads
import requests
from os import path as os_path
from urllib import quote_plus, unquote
from threading import Thread
from Queue import Queue, Empty

from xbmc import executeJSONRPC, sleep, translatePath
from xbmcvfs import listdir, delete

from utils import window, settings, language as lang, kodiSQL, tryEncode, \
    tryDecode, IfExists, ThreadMethods, ThreadMethodsAdditionalStop, dialog

# Disable annoying requests warnings
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
###############################################################################

log = logging.getLogger("PLEX."+__name__)

###############################################################################

ARTWORK_QUEUE = Queue()


def setKodiWebServerDetails():
    """
    Get the Kodi webserver details - used to set the texture cache
    """
    xbmc_port = None
    xbmc_username = None
    xbmc_password = None
    web_query = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "Settings.GetSettingValue",
        "params": {
            "setting": "services.webserver"
        }
    }
    result = executeJSONRPC(dumps(web_query))
    result = loads(result)
    try:
        xbmc_webserver_enabled = result['result']['value']
    except (KeyError, TypeError):
        xbmc_webserver_enabled = False
    if not xbmc_webserver_enabled:
        # Enable the webserver, it is disabled
        web_port = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "Settings.SetSettingValue",
            "params": {
                "setting": "services.webserverport",
                "value": 8080
            }
        }
        result = executeJSONRPC(dumps(web_port))
        xbmc_port = 8080
        web_user = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "Settings.SetSettingValue",
            "params": {
                "setting": "services.webserver",
                "value": True
            }
        }
        result = executeJSONRPC(dumps(web_user))
        xbmc_username = "kodi"
    # Webserver already enabled
    web_port = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "Settings.GetSettingValue",
        "params": {
            "setting": "services.webserverport"
        }
    }
    result = executeJSONRPC(dumps(web_port))
    result = loads(result)
    try:
        xbmc_port = result['result']['value']
    except (TypeError, KeyError):
        pass
    web_user = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "Settings.GetSettingValue",
        "params": {
            "setting": "services.webserverusername"
        }
    }
    result = executeJSONRPC(dumps(web_user))
    result = loads(result)
    try:
        xbmc_username = result['result']['value']
    except (TypeError, KeyError):
        pass
    web_pass = {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "Settings.GetSettingValue",
        "params": {
            "setting": "services.webserverpassword"
        }
    }
    result = executeJSONRPC(dumps(web_pass))
    result = loads(result)
    try:
        xbmc_password = result['result']['value']
    except TypeError:
        pass
    return (xbmc_port, xbmc_username, xbmc_password)


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


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


def get_uncached_artwork():
    """
    Returns a list of URLs that haven't been cached yet
    """
    all_urls = []
    cached_urls = []
    result = []
    connection = kodiSQL('video')
    cursor = connection.cursor()
    # Get all artwork urls
    cursor.execute("SELECT url FROM art WHERE media_type != 'actor'")
    for url in cursor.fetchall():
        all_urls.append(url[0])
    connection.close()
    connection = kodiSQL('music')
    cursor = connection.cursor()
    cursor.execute("SELECT url FROM art")
    for url in cursor.fetchall():
        all_urls.append(url[0])
    connection.close()
    # Get the cached urls
    connection = kodiSQL('texture')
    cursor = connection.cursor()
    cursor.execute("SELECT url FROM texture")
    for url in cursor.fetchall():
        cached_urls.append(url[0])
    connection.close()
    for url in all_urls:
        if url not in cached_urls:
            result.append(url)
    log.info('%s artwork urls have not been cached yet' % len(result))
    return result


@ThreadMethodsAdditionalStop('plex_shouldStop')
@ThreadMethods
class Image_Cache_Thread(Thread):
    xbmc_host = 'localhost'
    xbmc_port, xbmc_username, xbmc_password = setKodiWebServerDetails()
    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 threadSuspended(self):
        # Overwrite method to add TWO additional suspends
        return (self._threadSuspended or
                window('suspend_LibraryThread') or
                window('plex_dbScan'))

    def run(self):
        threadStopped = self.threadStopped
        threadSuspended = self.threadSuspended
        queue = self.queue
        sleep_between = self.sleep_between
        while not threadStopped():
            # In the event the server goes offline
            while threadSuspended():
                # Set in service.py
                if threadStopped():
                    # 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"
                            % (self.xbmc_host, self.xbmc_port, url),
                        auth=(self.xbmc_username, self.xbmc_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 threadStopped():
                        # 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()
            log.debug('Cached art: %s' % double_urldecode(url))
            # 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 = tryDecode(translatePath("special://thumbnails/"))
            if IfExists(path):
                allDirs, allFiles = listdir(path)
                for dir in allDirs:
                    allDirs, allFiles = listdir(path+dir)
                    for file in allFiles:
                        if os_path.supports_unicode_filenames:
                            delete(os_path.join(
                                path + tryDecode(dir),
                                tryDecode(file)))
                        else:
                            delete(os_path.join(
                                tryEncode(path) + dir,
                                file))

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

        # Cache all entries in video DB
        connection = kodiSQL('video')
        cursor = connection.cursor()
        # dont include actors
        cursor.execute("SELECT url FROM art WHERE media_type != '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.cacheTexture(url[0])
        # Cache all entries in music DB
        connection = kodiSQL('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.cacheTexture(url[0])

    def cacheTexture(self, url):
        # Cache a single image url to the texture cache
        if url and self.enableTextureCache:
            self.queue.put(double_urlencode(tryEncode(url)))

    def addArtwork(self, artwork, kodiId, mediaType, cursor):
        # Kodi conversion table
        kodiart = {

            'Primary': ["thumb", "poster"],
            'Banner': "banner",
            'Logo': "clearlogo",
            'Art': "clearart",
            'Thumb': "landscape",
            'Disc': "discart",
            'Backdrop': "fanart",
            'BoxRear': "poster"
        }

        # Artwork is a dictionary
        for art in artwork:
            if art == "Backdrop":
                # Backdrop entry is a list
                # Process extra fanart for artwork downloader (fanart, fanart1,
                # fanart2...)
                backdrops = artwork[art]
                backdropsNumber = len(backdrops)

                query = ' '.join((

                    "SELECT url",
                    "FROM art",
                    "WHERE media_id = ?",
                    "AND media_type = ?",
                    "AND type LIKE ?"
                ))
                cursor.execute(query, (kodiId, mediaType, "fanart%",))
                rows = cursor.fetchall()

                if len(rows) > backdropsNumber:
                    # More backdrops in database. Delete extra fanart.
                    query = ' '.join((

                        "DELETE FROM art",
                        "WHERE media_id = ?",
                        "AND media_type = ?",
                        "AND type LIKE ?"
                    ))
                    cursor.execute(query, (kodiId, mediaType, "fanart_",))

                # Process backdrops and extra fanart
                index = ""
                for backdrop in backdrops:
                    self.addOrUpdateArt(
                        imageUrl=backdrop,
                        kodiId=kodiId,
                        mediaType=mediaType,
                        imageType="%s%s" % ("fanart", index),
                        cursor=cursor)

                    if backdropsNumber > 1:
                        try: # Will only fail on the first try, str to int.
                            index += 1
                        except TypeError:
                            index = 1

            elif art == "Primary":
                # Primary art is processed as thumb and poster for Kodi.
                for artType in kodiart[art]:
                    self.addOrUpdateArt(
                        imageUrl=artwork[art],
                        kodiId=kodiId,
                        mediaType=mediaType,
                        imageType=artType,
                        cursor=cursor)

            elif kodiart.get(art):
                # Process the rest artwork type that Kodi can use
                self.addOrUpdateArt(
                    imageUrl=artwork[art],
                    kodiId=kodiId,
                    mediaType=mediaType,
                    imageType=kodiart[art],
                    cursor=cursor)

    def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor):
        if not imageUrl:
            # Possible that the imageurl is an empty string
            return

        query = ' '.join((
            "SELECT url",
            "FROM art",
            "WHERE media_id = ?",
            "AND media_type = ?",
            "AND type = ?"
        ))
        cursor.execute(query, (kodiId, mediaType, imageType,))
        try:
            # Update the artwork
            url = cursor.fetchone()[0]
        except TypeError:
            # Add the artwork
            log.debug("Adding Art Link for kodiId: %s (%s)"
                      % (kodiId, imageUrl))
            query = (
                '''
                INSERT INTO art(media_id, media_type, type, url)
                VALUES (?, ?, ?, ?)
                '''
            )
            cursor.execute(query, (kodiId, mediaType, imageType, imageUrl))
        else:
            if url == imageUrl:
                # Only cache artwork if it changed
                return
            # Only for the main backdrop, poster
            if (window('plex_initialScan') != "true" and
                    imageType in ("fanart", "poster")):
                # Delete current entry before updating with the new one
                self.deleteCachedArtwork(url)
            log.debug("Updating Art url for %s kodiId %s %s -> (%s)"
                      % (imageType, kodiId, url, imageUrl))
            query = ' '.join((
                "UPDATE art",
                "SET url = ?",
                "WHERE media_id = ?",
                "AND media_type = ?",
                "AND type = ?"
            ))
            cursor.execute(query, (imageUrl, kodiId, mediaType, imageType))

        # Cache fanart and poster in Kodi texture cache
        if mediaType != 'actor':
            self.cacheTexture(imageUrl)

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

    def deleteCachedArtwork(self, url):
        # Only necessary to remove and apply a new backdrop or poster
        connection = kodiSQL('texture')
        cursor = connection.cursor()
        try:
            cursor.execute("SELECT cachedurl FROM texture WHERE url = ?",
                           (url,))
            cachedurl = cursor.fetchone()[0]
        except TypeError:
            log.info("Could not find cached url.")
        else:
            # Delete thumbnail as well as the entry
            thumbnails = tryDecode(
                translatePath("special://thumbnails/%s" % cachedurl))
            log.debug("Deleting cached thumbnail: %s" % thumbnails)
            try:
                delete(thumbnails)
            except Exception as e:
                log.error('Could not delete cached artwork %s. Error: %s'
                          % (thumbnails, e))
            cursor.execute("DELETE FROM texture WHERE url = ?", (url,))
            connection.commit()
        finally:
            connection.close()