#!/usr/bin/env python
# -*- coding: utf-8 -*-

###############################################################################
#   Copyright(c) 2008-2012 pyLoad Team
#   http://www.pyload.org
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as
#   published by the Free Software Foundation, either version 3 of the
#   License, or (at your option) any later version.
#
#   Subjected to the terms and conditions in LICENSE
#
#   @author: RaNaN
###############################################################################

from new_collections import OrderedDict

from module.Api import DownloadInfo, FileInfo, PackageInfo, PackageStats
from module.database import DatabaseMethods, queue, async, inner

zero_stats = PackageStats(0, 0, 0, 0)

class FileMethods(DatabaseMethods):
    @queue
    def filecount(self, user=None):
        """returns number of files"""
        self.c.execute("SELECT COUNT(*) FROM files")
        return self.c.fetchone()[0]

    @queue
    def queuecount(self, user=None):
        """ number of files in queue not finished yet"""
        # status not in NA, finished, skipped
        self.c.execute("SELECT COUNT(*) FROM files WHERE dlstatus NOT IN (0,5,6)")
        return self.c.fetchone()[0]

    @queue
    def processcount(self, fid, user=None):
        """ number of files which have to be processed """
        # status in online, queued, starting, waiting, downloading
        self.c.execute("SELECT COUNT(*) FROM files WHERE dlstatus IN (2,3,8,9,10) AND fid != ?", (fid, ))
        return self.c.fetchone()[0]

    # TODO: think about multiuser side effects on *count methods

    @queue
    def addLink(self, url, name, plugin, package, owner):
        # mark file status initially as missing, dlstatus - queued
        self.c.execute('INSERT INTO files(url, name, plugin, status, dlstatus, package, owner) VALUES(?,?,?,1,3,?,?)',
            (url, name, plugin, package, owner))
        return self.c.lastrowid

    @async
    def addLinks(self, links, package, owner):
        """ links is a list of tuples (url, plugin)"""
        links = [(x[0], x[0], x[1], package, owner) for x in links]
        self.c.executemany('INSERT INTO files(url, name, plugin, status, dlstatus, package, owner) VALUES(?,?,?,1,3,?,?)',
            links)

    @queue
    def addFile(self, name, size, media, package, owner):
        # file status - ok, dl status NA
        self.c.execute('INSERT INTO files(name, size, media, package, owner) VALUES(?,?,?,?,?)',
            (name, size, media, package, owner))
        return self.c.lastrowid

    @queue
    def addPackage(self, name, folder, root, password, site, comment, status, owner):
        self.c.execute(
            'INSERT INTO packages(name, folder, root, password, site, comment, status, owner) VALUES(?,?,?,?,?,?,?,?)'
            , (name, folder, root, password, site, comment, status, owner))
        return self.c.lastrowid

    @async
    def deletePackage(self, pid, owner=None):
        # order updated by trigger, as well as links deleted
        if owner is None:
            self.c.execute('DELETE FROM packages WHERE pid=?', (pid,))
        else:
            self.c.execute('DELETE FROM packages WHERE pid=? AND owner=?', (pid, owner))

    @async
    def deleteFile(self, fid, order, package, owner=None):
        """ To delete a file order and package of it is needed """
        if owner is None:
            self.c.execute('DELETE FROM files WHERE fid=?', (fid,))
            self.c.execute('UPDATE files SET fileorder=fileorder-1 WHERE fileorder > ? AND package=?',
                (order, package))
        else:
            self.c.execute('DELETE FROM files WHERE fid=? AND owner=?', (fid, owner))
            self.c.execute('UPDATE files SET fileorder=fileorder-1 WHERE fileorder > ? AND package=? AND owner=?',
                (order, package, owner))

    @async
    def saveCollector(self, owner, data):
        """ simply save the json string to database """
        self.c.execute("INSERT INTO collector(owner, data) VALUES (?,?)", (owner, data))

    @queue
    def retrieveCollector(self, owner):
        """ retrieve the saved string """
        self.c.execute('SELECT data FROM collector WHERE owner=?', (owner,))
        r = self.c.fetchone()
        if not r: return None
        return r[0]

    @async
    def deleteCollector(self, owner):
        """ drop saved user collector """
        self.c.execute('DELETE FROM collector WHERE owner=?', (owner,))

    @queue
    def getAllFiles(self, package=None, search=None, unfinished=False, owner=None):
        """ Return dict with file information

        :param package: optional package to filter out
        :param search: or search string for file name
        :param unfinished: filter by dlstatus not finished
        :param owner: only specific owner
        """
        qry = ('SELECT fid, name, owner, size, status, media, added, fileorder, '
               'url, plugin, hash, dlstatus, error, package FROM files WHERE ')

        arg = []

        if unfinished:
            qry += 'dlstatus NOT IN (0, 5, 6) AND '
        if owner is not None:
            qry += 'owner=? AND '
            arg.append(owner)

        if package is not None:
            arg.append(package)
            qry += 'package=? AND '
        if search is not None:
            search = "%%%s%%" % search.strip("%")
            arg.append(search)
            qry += "name LIKE ? "

        # make qry valid
        if qry.endswith("WHERE "): qry = qry[:-6]
        if qry.endswith("AND "): qry = qry[:-4]

        self.c.execute(qry + "ORDER BY package, fileorder", arg)

        data = OrderedDict()
        for r in self.c:
            f = FileInfo(r[0], r[1], r[13], r[2], r[3], r[4], r[5], r[6], r[7])
            if r[11] > 0: # dl status != NA
                f.download = DownloadInfo(r[8], r[9], r[10], r[11], self.manager.statusMsg[r[11]], r[12])

            data[r[0]] = f

        return data

    @queue
    def getAllPackages(self, root=None, owner=None):
        """ Return dict with package information

        :param root: optional root to filter
        """
        qry = ('SELECT pid, name, folder, root, owner, site, comment, password, added, status, packageorder '
               'FROM packages%s ORDER BY root, packageorder')

        if root is None:
            stats = self.getPackageStats(owner=owner)
            if owner is None:
                self.c.execute(qry % "")
            else:
                self.c.execute(qry % " WHERE owner=?", (owner,))
        else:
            stats = self.getPackageStats(root=root, owner=owner)
            if owner is None:
                self.c.execute(qry % ' WHERE root=? OR pid=?', (root, root))
            else:
                self.c.execute(qry % ' WHERE (root=? OR pid=?) AND owner=?', (root, root, owner))

        data = OrderedDict()
        for r in self.c:
            data[r[0]] = PackageInfo(
                r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], stats.get(r[0], zero_stats)
            )

        return data

    @inner
    def getPackageStats(self, pid=None, root=None, owner=None):
        qry = ("SELECT p.pid, SUM(f.size) AS sizetotal, COUNT(f.fid) AS linkstotal, sizedone, linksdone "
               "FROM packages p JOIN files f ON p.pid = f.package AND f.dlstatus > 0 %(sub)s LEFT OUTER JOIN "
               "(SELECT p.pid AS pid, SUM(f.size) AS sizedone, COUNT(f.fid) AS linksdone "
               "FROM packages p JOIN files f ON p.pid = f.package %(sub)s AND f.dlstatus in (5,6) GROUP BY p.pid) s ON s.pid = p.pid "
               "GROUP BY p.pid")

        # status in (finished, skipped, processing)

        if root is not None:
            self.c.execute(qry % {"sub": "AND (p.root=:root OR p.pid=:root)"}, locals())
        elif pid is not None:
            self.c.execute(qry % {"sub": "AND p.pid=:pid"}, locals())
        elif owner is not None:
            self.c.execute(qry % {"sub": "AND p.owner=:owner"}, locals())
        else:
            self.c.execute(qry % {"sub": ""})

        data = {}
        for r in self.c:
            data[r[0]] = PackageStats(
                r[2] if r[2] else 0,
                r[4] if r[4] else 0,
                int(r[1]) if r[1] else 0,
                int(r[3]) if r[3] else 0,
            )

        return data

    @queue
    def getStatsForPackage(self, pid):
        return self.getPackageStats(pid=pid)[pid]

    @queue
    def getFileInfo(self, fid, force=False):
        """get data for specific file, when force is true download info will be appended"""
        self.c.execute('SELECT fid, name, owner, size, status, media, added, fileorder, '
                       'url, plugin, hash, dlstatus, error, package FROM files '
                       'WHERE fid=?', (fid,))
        r = self.c.fetchone()
        if not r:
            return None
        else:
            f = FileInfo(r[0], r[1], r[13], r[2], r[3], r[4], r[5], r[6], r[7])
            if r[11] > 0 or force:
                f.download = DownloadInfo(r[8], r[9], r[10], r[11], self.manager.statusMsg[r[11]], r[12])

            return f

    @queue
    def getPackageInfo(self, pid, stats=True):
        """get data for a specific package, optionally with package stats"""
        if stats:
            stats = self.getPackageStats(pid=pid)

        self.c.execute('SELECT pid, name, folder, root, owner, site, comment, password, added, status, packageorder '
                       'FROM packages WHERE pid=?', (pid,))

        r = self.c.fetchone()
        if not r:
            return None
        else:
            return PackageInfo(
                r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], stats.get(r[0], zero_stats) if stats else None
            )

    @async
    def updateLinkInfo(self, data, owner):
        """ data is list of tuples (name, size, status,[ hash,] url)"""
        if data and len(data[0]) == 4:
            self.c.executemany('UPDATE files SET name=?, size=?, dlstatus=? WHERE url=? AND dlstatus IN (0,1,2,3,14)',
                data)
        else:
            self.c.executemany(
                'UPDATE files SET name=?, size=?, dlstatus=?, hash=? WHERE url=? AND dlstatus IN (0,1,2,3,14)', data)

    @async
    def updateFile(self, f):
        self.c.execute('UPDATE files SET name=?, size=?, status=?,'
                       'media=?, url=?, hash=?, dlstatus=?, error=? WHERE fid=?',
            (f.name, f.size, f.filestatus, f.media, f.url,
             f.hash, f.status, f.error, f.fid))

    @async
    def updatePackage(self, p):
        self.c.execute('UPDATE packages SET name=?, folder=?, site=?, comment=?, password=?, status=? WHERE pid=?',
            (p.name, p.folder, p.site, p.comment, p.password, p.status, p.pid))

    # TODO: most modifying methods needs owner argument to avoid checking beforehand
    @async
    def orderPackage(self, pid, root, oldorder, order):
        if oldorder > order: # package moved upwards
            self.c.execute(
                'UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND root=? AND packageorder >= 0'
                , (order, oldorder, root))
        elif oldorder < order: # moved downwards
            self.c.execute(
                'UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND root=? AND packageorder >= 0'
                , (order, oldorder, root))

        self.c.execute('UPDATE packages SET packageorder=? WHERE pid=?', (order, pid))

    @async
    def orderFiles(self, pid, fids, oldorder, order):
        diff = len(fids)
        data = []

        if oldorder > order: # moved upwards
            self.c.execute('UPDATE files SET fileorder=fileorder+? WHERE fileorder >= ? AND fileorder < ? AND package=?'
                , (diff, order, oldorder, pid))
            data = [(order + i, fid) for i, fid in enumerate(fids)]
        elif oldorder < order:
            self.c.execute(
                'UPDATE files SET fileorder=fileorder-? WHERE fileorder <= ? AND fileorder >= ? AND package=?'
                , (diff, order, oldorder + diff, pid))
            data = [(order - diff + i + 1, fid) for i, fid in enumerate(fids)]

        self.c.executemany('UPDATE files SET fileorder=? WHERE fid=?', data)

    @async
    def moveFiles(self, pid, fids, package):
        self.c.execute('SELECT max(fileorder) FROM files WHERE package=?', (package,))
        r = self.c.fetchone()
        order = (r[0] if r[0] else 0) + 1

        self.c.execute('UPDATE files SET fileorder=fileorder-? WHERE fileorder > ? AND package=?',
            (len(fids), order, pid))

        data = [(package, order + i, fid) for i, fid in enumerate(fids)]
        self.c.executemany('UPDATE files SET package=?, fileorder=? WHERE fid=?', data)

    @async
    def movePackage(self, root, order, pid, dpid):
        self.c.execute('SELECT max(packageorder) FROM packages WHERE root=?', (dpid,))
        r = self.c.fetchone()
        max = (r[0] if r[0] else 0) + 1

        self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND root=?',
            (order, root))

        self.c.execute('UPDATE packages SET root=?, packageorder=? WHERE pid=?', (dpid, max, pid))

    @async
    def restartFile(self, fid):
        # status -> queued
        self.c.execute('UPDATE files SET dlstatus=3, error="" WHERE fid=?', (fid,))

    @async
    def restartPackage(self, pid):
        # status -> queued
        self.c.execute('UPDATE files SET status=3 WHERE package=?', (pid,))


    # TODO: multi user approach
    @queue
    def getJob(self, occ):
        """return pyfile ids, which are suitable for download and dont use a occupied plugin"""
        cmd = "(%s)" % ", ".join(["'%s'" % x for x in occ])
        #TODO

        # dlstatus in online, queued | package status = ok
        cmd = ("SELECT f.fid FROM files as f INNER JOIN packages as p ON f.package=p.pid "
               "WHERE f.plugin NOT IN %s AND f.dlstatus IN (2,3) AND p.status=0 "
               "ORDER BY p.packageorder ASC, f.fileorder ASC LIMIT 5") % cmd

        self.c.execute(cmd) # very bad!

        return [x[0] for x in self.c]

    @queue
    def getUnfinished(self, pid):
        """return list of max length 3 ids with pyfiles in package not finished or processed"""

        # status in finished, skipped, processing
        self.c.execute("SELECT fid FROM files WHERE package=? AND dlstatus NOT IN (5, 6, 14) LIMIT 3", (pid,))
        return [r[0] for r in self.c]

    @queue
    def restartFailed(self, owner):
        # status=queued, where status in failed, aborted, temp offline
        self.c.execute("UPDATE files SET dlstatus=3, error='' WHERE dlstatus IN (7, 11, 12)")

    @queue
    def findDuplicates(self, id, folder, filename):
        """ checks if filename exists with different id and same package """
        # TODO
        self.c.execute(
            "SELECT l.plugin FROM files f INNER JOIN packages as p ON f.package=p.pid AND p.folder=? WHERE f.fid!=? AND l.status=0 AND l.name=?"
            , (folder, id, filename))
        return self.c.fetchone()

    @queue
    def purgeLinks(self):
        # fstatus = missing
        self.c.execute("DELETE FROM files WHERE status == 1")

    @queue
    def purgeAll(self): # only used for debugging
        self.c.execute("DELETE FROM packages")
        self.c.execute("DELETE FROM files")
        self.c.execute("DELETE FROM collector")

FileMethods.register()