# -*- coding: utf-8 -*-
"""
Provides the MythVideo class with convenient methods to access the MythTV
MythVideo database.
"""

from MythDB import *
from MythLog import *
from MythTV import MythTV, FileTransfer
from socket import gethostname
import os

log = MythLog(CRITICAL, '#%(levelname)s - %(message)s', 'MythVideo')

MVSCHEMA_VERSION = 1028

class MythVideo(object):
    """
    Provides convenient methods to access the MythTV MythVideo database.
    """
    def __init__(self,db = None):
        """
        Initialise the MythDB connection.
        """
        self.db = db
        if not self.db:
            self.db = MythDB()

        # check schema version
        sver = int(self.db.getSetting('mythvideo.DBSchemaVer'))
        if MVSCHEMA_VERSION != sver:
            log.Msg(CRITICAL, 
                    'DB speaks schema version %d, but we speak version %d', 
                    sver, MVSCHEMA_VERSION)
            raise MythError('Mismatched schema version')

    def _getSG(self, SG, host=None, fallback=True):
        if not host:
            host = gethostname()
        ret = []
        for i in self.db.getStorageGroup(SG,host):
            ret.append(i['dirname'])
        if ret:
            return tuple(ret)
        else:
            if (SG == 'Videos') or (not fallback):
                return None
            else:
                return self._getSG('Videos',host)

    def _getLocal(self, setting, fallback=True):
        host = gethostname()
        path = self.db.getSetting(setting, host)
        if path:
            return tuple(path.split(':'))
        else:
            if (setting == 'VideoStartupDir') or (not fallback):
                return None
            else:
                return self._getLocal(self,'VideoStartupDir')

    def getVideoSG(self, host=None, local=False):
        """
        Get a tuple of directories for the 'Videos' storage group.
        Defaults to local machine.
        If 'local' is true, grab local storage path instead.
        """
        if local: return self._getLocal('VideoStartupDir')
        return self._getSG('Videos',host)

    def getBannerSG(self, host=None, local=False, fallback=True):
        """
        Get a tuple of directories for the 'Banners' storage group.
        Defaults to local machine.
        If 'local' is true, grab local storage path instead.
        If 'fallback' is true, allow fallback to primary video storage.
        """
        if local: return self._getLocal('mythvideo.bannerDir',fallback)
        return self._getSG('Banners',host,fallback)

    def getCoverartSG(self, host=None, local=False, fallback=True):
        """
        Get a tuple of directories for the 'Coverart' storage group.
        Defaults to local machine.
        If 'local' is true, grab local storage path instead.
        If 'fallback' is true, allow fallback to primary video storage.
        """
        if local: return self._getLocal('VideoArtworkDir',fallback)
        return self._getSG('Coverart',host,fallback)

    def getFanartSG(self, host=None, local=False, fallback=True):
        """
        Get a tuple of directories for the 'Fanart' storage group.
        Defaults to local machine.
        If 'local' is true, grab local storage path instead.
        If 'fallback' is true, allow fallback to primary video storage.
        """
        if local: return self._getLocal('mythvideo.fanartDir',fallback)
        return self._getSG('Fanart',host,fallback)

    def getScreenshotSG(self, host=None, local=False, fallback=True):
        """
        Get a tuple of directories for the 'Screenshots' storage group.
        Defaults to local machine.
        If 'local' is true, grab local storage path instead.
        If 'fallback' is true, allow fallback to primary video storage.
        """
        if local: return self._getLocal('mythvideo.screenshotDir',fallback)
        return self._getSG('Screenshots',host,fallback)

    def getTrailerSG(self, host=None, local=False, fallback=True):
        """
        Get a tuple of directories for the 'Trailers' storage group.
        Defaults to local machine.
        If 'local' is true, grab local storage path instead.
        If 'fallback' is true, allow fallback to primary video storage.
        """
        if local: return self._getLocal('mythvideo.TrailersDir',fallback)
        return self._getSG('Trailers',host,fallback)

    def pruneMetadata(self):
        """
        Removes metadata from the database for files that no longer exist. 
        Respects 'Videos' storage groups and relative paths.
        """
        c = self.db.cursor()
        c.execute("""SELECT DISTINCT host FROM videometadata""")
        hosts = c.fetchall()

        for h in hosts:
            host = h[0]
            myth = None
            if host is None:
                if self.getVideoSG(local=True) is None:
                    continue    # no local video directories to process
            else:
                try:
                    myth = MythTV(host)
                except MythError:
                    continue    # cannot connect to remote backend for scan

            videos = self.searchVideos(host=host)
            bases = self.getVideoSG(host)
            paths = {}
            for vid in videos:
                if host is None:    # check for local videos
                    if not os.path.exists(vid.filename):
                        vid.delete()
                    continue
                
                # scan available storage group paths
                vpath = '/'+vid.filename
                vfile = vpath[vpath.rfind('/')+1:]
                vpath = vpath[:vpath.rfind('/')]

                if vpath not in paths:
                    files = []
                    for b in bases:
                        d,f,s = myth.getSGList(host,'Videos',b+vpath)
                        files.extend(f)
                    paths[vpath] = files
                if vfile not in paths[vpath]:
                    print u'deleting '+str(vid)
                    vid.delete()

        
    def searchVideos(self, title=None, subtitle=None, season=None, 
                        episode=None, host=None, file=None, exactfile=False,
                        cast=None, genre=None, country=None, category=None):
        """
        Tries to find videos matching the given information.
        Returns a tuple of Video objects
        """
        if host:
            if self.getVideoSG(host):
                host = host.lower()
            else:
                host = None

        where = []
        fields = []
        tables = ['videometadata']
        if title:
            where.append('videometadata.title=%s')
            fields.append(title)
        if subtitle:
            where.append('videometadata.subtitle=%s')
            fields.append(subtitle)
        if season:
            where.append('videometadata.season=%s')
            fields.append(int(season))
        if episode:
            where.append('videometadata.episode=%s')
            fields.append(int(episode))
        if host:
            where.append('videometadata.host=%s')
            fields.append(host)
        if file:
            if exactfile:
                where.append('videometadata.filename=%s')
                fields.append(file)
            else:
                where.append('videometadata.filename LIKE %s')
                fields.append('%'+file)
        if cast:
            tables.append('videometadatacast')
            where.append('videometadata.intid=videometadatacast.idvideo')
            fields.append(cast)
            if isinstance(cast,int):
                where.append('videometadatacast.idcast=%s')
            else:
                tables.append('videocast')
                where.append('videometadatacast.idcast=videocast.intid')
                where.append('videocast.cast=%s')
        if genre:
            tables.append('videometadatagenre')
            where.append('videometadata.intid=videometadatagenre.idvideo')
            fields.append(genre)
            if isinstance(genre,int):
                where.append('videometadatagenre.idgenre=%s')
            else:
                tables.append('videogenre')
                where.append('videometadatagenre.idgenre=videogenre.intid')
                where.append('videogenre.genre=%s')
        if country:
            tables.append('videometadatacountry')
            where.append('videometadata.intid=videometadatacountry.idvideo')
            fields.append(country)
            if isinstance(country,int):
                where.append('videometadatacountry.idcountry=%s')
            else:
                tables.append('videocontry')
                where.append('videometadatacountry.idcountry=videocountry.intid')
                where.append('videocast.country=%s')
        if category:
            fields.append(category)
            if isinstance(category,int):
                where.append('vidoemetadata.category=%s')
            else:
                tables.append('videocategory')
                where.append('videometadata.category=videocategory.intid')
                where.append('videocategory.category=%s')
                
        if len(where) == 0:
            return None
        where  = ' AND '.join(where)
        tables = ','.join(tables)

        c = self.db.cursor()
        c.execute("""   SELECT videometadata.* FROM %s
                WHERE %s""" % (tables,where), fields)
        rows = c.fetchall()
        c.close()

        if len(rows) == 0:
            return None
        videos = []
        for row in rows:
            videos.append(Video(data=row,db=self.db))
        if len(videos):
            return tuple(videos)
        else:
            return None


    def getVideo(self, title=None, subtitle=None, season=None,
                        episode=None, host=None, file=None, exactfile=False):
        """
        Tries to find a video matching the given information.
        Returns a Video object.
        """
        videos = self.searchVideos(title,subtitle,season,\
                                episode,host,file,exactfile)
        if videos:
            return videos[0]
        else:
            return None

    def getCategoryId(self, category_name):
        """
        Find the id of the given category from MythDB.

        If the category does not exist, insert it and return its id.
        """
        c = self.db.cursor()
        c.execute("""   SELECT intid
                        FROM videocategory
                        WHERE lower(category) = %s""",
                    (category_name,))
        row = c.fetchone()
        c.close()

        if row is not None:
            return row[0]

        # Insert a new genre.
        c = self.db.cursor()
        c.execute("""INSERT INTO videocategory(category)
                                VALUES (%s)""",
                        (category_name.capitalize(),))
        newid = c.lastrowid
        c.close()

        return newid

# old replaced functions... remain for compatibility

    def getTitleId(self, title, subtitle=None, season=None,
                            episode=None, array=False, host=None):
        """Legacy"""
        videos = self.searchVideos(title,subtitle,season,episode,host)
        res = None
        if videos:
            if array:
                res = []
                for vid in videos:
                    res.append(vid.intid)
                res = tuple(res)
            else:
                res = videos[0].intid
        return res

    def setMetadata(self, data, id=None):
        """Legacy"""
        if id is None: return Video().create(data)
        else: Video(id).update(data)

    def getMetadataId(self, videopath, host=None):
        """Legacy"""
        video = self.getVideo(file=videopath,host=host)
        if video is None: return None
        else: return video.intid

    def hasMetadata(self, videopath, host=None):
        """Legacy"""
        if self.getVideo(file=videopath,host=host,exactfile=True) is not None:
            return True
        else: return False

    def rmMetadata(self, video, host=None):
        """Legacy"""
        if isinstance(video,str):
            videos = self.searchVideos(file=video, host=host, exactfile=True)
            if videos is None:
                return
            video = videos[0]
        elif isinstance(video, int) or isinstance(video, long):
            video = Video(video, db=self.db)
        else:
            raise MythError('Invalid input type to rmMetadata()')
        video.delete()

    def rtnVideoStorageGroup(self, host=None):
        """Legacy"""
        return self._getSG('Videos',host)
    def getTableFieldNames(self, tablename):
        """Legacy"""
        return self.db.getTableFieldNames(tablename)
    def getMetadata(self, id):
        """Legacy"""
        return list(Video(id,db=self.db))
    def getGenreId(self, genre_name):
        """Legacy"""
        return Video(db=self.db)._getSubFieldID('genre',
                                    genre_name.capitalize())
    def getCastId(self, cast_name):
        """Legacy"""
        return Video(db=self.db)._getSubFieldID('cast',
                                    cast_name.capitalize())
    def setCast(self, cast_name, idvideo):
        """Legacy"""
        return Video(idvideo,db=self.db).addCast(cast_name)
    def getMetadataDictionary(self, id):
        """Legacy"""
        return Video(id,db=self.db).data
    def getFieldId(self, field, name):
        """Legacy"""
        return Video(db=self.db)._getSubFieldID(field,name)
    def setField(self, field, name, idvideo):
        """Legacy"""
        return Video(idvideo,db=self.db)._setSubField(field,name)
    def getGenresId(self, genre_name):
        """Legacy"""
        return Video(db=self.db)._getSubFieldID('genre',
                                    genre_name.capitalize())
    def setGenres(self, genre_name, idvideo):
        """Legacy"""
        return Video(idvideo,db=self.db).addGenre(genre_name)
    def getCountryId(self, country_name):
        """Legacy"""
        return Video(db=self.db)._getSubFieldID('country',
                                    country_name.capitalize())
    def setCountry(self, country_name, idvideo):
        """Legacy"""
        return Video(idvideo,db=self.db).addCountry(country_name)
    def cleanGenres(self, idvideo):
        """Legacy"""
        Video(idvideo,db=self.db).cleanGenre()
    def cleanCountry(self, idvideo):
        """Legacy"""
        Video(idvideo,db=self.db).cleanCountry()
    def cleanCast(self, idvideo):
        """Legacy"""
        Video(idvideo,db=self.db).cleanCast()

class Video(DBDataWrite):
    """
    Class for managing and creating metadata for a single video file 
    """
    field_type = 'Pass'
    data = None
    table = 'videometadata'
    where = 'intid=%s'
    defaults = {'subtitle':u'',             'director':u'Unknown',
                'rating':u'NR',             'inetref':u'00000000',
                'year':1895,                'userrating':0.0,
                'length':0,                 'showlevel':1,
                'coverfile':u'No Cover',    'host':u'',
                'intid':None}
    def __init__(self, id=None, data=None, db=None):
        """
        Creates a new video object.
        Should be supplied with existing database object if possible.
        Can be initialized in three manners, or left uninitialized.
            id = intid, data = None:
                    initialize object to an existing video entry
            id = intid, data = dictionary:
                    initialize object to an existing video entry
                    and update with info in dictionary
            id = none, data = list:
                    initialize object to an existing video entry
                    given by a row returned from 'select * from videometadata'
            id = none, data = dictionary:
                    initialize object to a new video entry
                    using info from dictionary
            id = none, data = none:
                leave object empty
        """
        self.db = db
        if self.db is None:
            self.db = MythDB()
        self.mvid = MythVideo(self.db)
        self.field_order = self.db.getTableFieldNames(self.table)

        if id:
            self.wheredat = (id,)
            self._getData()
            if self.data is None:
                raise MythError(\
                        'Invalid intid used during initialization of Video()')
            if isinstance(data,dict):
                self._readDict(data)
                self.update()
        elif isinstance(data,list) or isinstance(data,tuple):
            self._getData(data)
            self.wheredat = (self.intid,)
        elif isinstance(data,dict):
            self.wheredat = (self.create(data),)
            self._getData()

    def __repr__(self):
        return self._str__().encode('utf-8')

    def __str__(self):
        res = self.title
        if self.season and self.episode:
            res += u' - %dx%02d' % (self.season, self.episode)
        if self.subtitle:
            res += u' - '+self.subtitle
        return res

    def _getSubFieldID(self, field, name):
        """
        Add name to video<field> if it does not exist.
        """
        c = self.db.cursor()
        c.execute("SELECT intid FROM video%s WHERE lower(%s) = %%s"\
                        % (field, field), (name,))
        row = c.fetchone()
        c.close()

        if row is not None:
            return row[0]

        c = self.db.cursor()
        c.execute("INSERT INTO video%s(%s) VALUES (%%s)"\
                        % (field, field), (name,))
        c.close()

        return self._getSubFieldID(field,name)

    def _getSubField(self, field):
        """
        Get list of subfield entries crossreferenced to the current video entry
        """
        if self.data is None:
            return None

        c = self.db.cursor()
        c.execute("""   SELECT %s FROM video%s,videometadata%s
                        WHERE video%s.intid=videometadata%s.id%s
                        AND idvideo=%%s"""\
                    % (field,field,field,field,field,field), (self.intid,))
        rows = c.fetchall()
        c.close()

        if len(rows) == 0:
            return None
        else:
            res = []
            for row in rows:
                res.append(row[0])
            return tuple(res)

    def _setSubField(self, field, name):
        """
        Add crossreference to videometadata<field>.
        Add name to video<field> if it does not exist.
        """
        if self.data is None:
            return None

        id = self._getSubFieldId(field, name)

        c = self.db.cursor()
        c.execute("""   SELECT *
                        FROM videometadata%s
                        WHERE idvideo = %%s
                        AND id%s = %%s"""\
                    % (field,field), (self.intid, id))
        row = c.fetchone()
        c.close()

        if row is None:
            c = self.db.cursor()
            c.execute("""   INSERT INTO videometadata%s
                            VALUES (%%s,%%s)"""\
                        % field, (self.intid, id))
            c.close()
        
        return id

    def _delSubField(self, field, name=None):
        """
        Remove crossreference to videometadata<field>.
        """
        if self.data is None:
            return None

        c = self.db.cursor()
        if name is None:
            c.execute("""   DELETE FROM videometadata%s
                            WHERE idvideo = %%s"""\
                        % field, (self.intid,))
        else:
            id = self.getFieldId(field,name)
            c.execute("""   DELETE FROM videometadata%s
                            WHERE idvideo = %%s
                            AND id%s = %%s"""\
                        % (field, field), (self.intid, id))
        c.close()

    def _open(self, type, mode='r'):
        """
        Open data socket
        """
        sgroup = {  'filename':'Videos',        'banner':'Banners',
                    'coverfile':'Coverart',     'fanart':'Fanart',
                    'screenshot':'Screenshots', 'trailer':'Trailers'}
        local = {   'filename':'VideoStartupDir',
                    'banner':'mythvideo.bannerDir',
                    'coverfile':'VideoArtworkDir',
                    'fanart':'mythvideo.fanartDir',
                    'screenshot':'mythvideo.screenshotDir',
                    'trailer':'mythvideo.TrailersDir'}
        if self.data is None:
            return None
        if type not in sgroup.keys():
            raise MythError('Video._open() - '+\
                            'Trying to open invalid file type: '+str(type))
        if mode not in ('r','w'):
            raise MythError('Video._open() - '+\
                            'Using invalid access mode: '+str(mode))
        if self.host:  # open file transfer connection
            SG = sgroup[type]
            if self.mvid._getSG(SG, self.host, False) is None:
                SG = 'Videos'
            fp = FileTransfer((self.host,self.data[type],SG),mode)
        else:   # open local file
            path = self.mvid._getLocal(local[type])
            fp = open(path+'/'+file,mode)
        return fp

    def delete(self):
        """
        Delete video entry from database
        """
        if self.data is None:
            return
        self.cleanCast()
        self.cleanGenre()
        self.cleanCountry()
        DBDataWrite.delete(self)

    def open(self,mode='r'):
        """Open file pointer to video"""
        return self._open('filename',mode)

    def openBanner(self,mode='r'):
        """Open file pointer to banner image"""
        return self._open('banner',mode)

    def openCoverart(self,mode='r'):
        """Open file pointer to coverart image"""
        return self._open('coverfile',mode)

    def openFanart(self,mode='r'):
        """Open file pointer to fanart image"""
        return self._open('fanart',mode)

    def openScreenshot(self,mode='r'):
        """Open file pointer to screenshot image"""
        return self._open('screenshot',mode)

    def openTrailer(self,mode='r'):
        """Open file pointer to trailer"""
        return self._open('trailer',mode)

    def getCast(self):
        """Return a tuple of cast members for video entry"""
        return self._getSubField('cast')

    def addCast(self,member):
        """Add cast member to video entry"""
        return self._setSubField('cast',member.capitalize())

    def delCast(self,member):
        """Remove cast member from video entry"""
        self._delSubField('cast',member.capitalize())

    def cleanCast(self):
        """Remove all cast members from video entry"""
        self._delSubField('cast')

    def getGenre(self):
        """Return a tuple of genres for video entry"""
        return self._getSubField('genre')

    def addGenre(self,genre):
        """Add genre to video entry"""
        return self._setSubField('genre',genre.capitalize())

    def delGenre(self,genre):
        """Remove genre from video entry"""
        self._delSubField('genre',genre.capitalize())

    def cleanGenre(self):
        """Remove all genres from video entry"""
        self._delSubField('genre')

    def getCountry(self):
        """Return a tuple of countries for video entry"""
        return self._getSubField('country')

    def addCountry(self,country):
        """Add country to video entry"""
        return self._setSubField('country',country.capitalize())

    def delCountry(self,country):
        """Remove country from video entry"""
        self._delSubField('country',country.capitalize())

    def cleanCountry(self):
        """Remove all countries from video entry"""
        self._delSubField('country')

        
# vim: tabstop=4 shiftwidth=4 expandtab:
