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

"""
    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 3 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, see <http://www.gnu.org/licenses/>.
    
    @author: mkaay
    @author: RaNaN
    @version: v0.3.2
    @list-version: v4
"""

LIST_VERSION = 4

from operator import attrgetter
from operator import concat
from os.path import join
from threading import RLock
from time import sleep

import cPickle
from module.DownloadThread import Status
from module.PullEvents import InsertEvent
from module.PullEvents import RemoveEvent
from module.PullEvents import UpdateEvent

class NoSuchElementException(Exception):
    pass

class FileList(object):
    def __init__(self, core):
        self.core = core
        self.lock = RLock()
        self.download_folder = self.core.config["general"]["download_folder"]
        self.collector = self.pyLoadCollector(self)
        self.packager = self.pyLoadPackager(self)
        
        self.data = {
            "version": LIST_VERSION,
            "queue": [],
            "packages": [],
            "collector": []
        }
        self.load()
        
    def load(self):
        self.lock.acquire()
        try:
            pkl_file = open(join(self.core.configdir, "links.pkl"), "rb")
            obj = cPickle.load(pkl_file)
        except:
            obj = False
        if obj != False and obj["version"] == LIST_VERSION:
            packages = []
            queue = []
            collector = []
            for n, pd in enumerate(obj["packages"]):
                p = PyLoadPackage()
                pd.get(p, self)
                packages.append(p)
            for pd in obj["queue"]:
                p = PyLoadPackage()
                pd.get(p, self)
                queue.append(p)
            for fd in obj["collector"]:
                f = PyLoadFile("", self)
                fd.get(f)
                collector.append(f)
            obj["packages"] = packages
            obj["queue"] = queue
            obj["collector"] = collector
            self.data = obj
        self.lock.release()
        
        if len(self.data["collector"]) > 0:
            self.core.logger.info(_("Found %s links in linkcollector") % len(self.data["collector"]))
        if len(self.data["packages"]) > 0:
            self.core.logger.info(_("Found %s unqueued packages") % len(self.data["packages"]))
        if len(self.data["queue"]) > 0:
            self.core.logger.info(_("Added %s packages to queue") % len(self.data["queue"]))
    
    def save(self):
        self.lock.acquire()
        
        pdata = {
            "version": LIST_VERSION,
            "queue": [],
            "packages": [],
            "collector": []
        }

        pdata["packages"] = [PyLoadPackageData().set(x) for x in self.data["packages"]]
        pdata["queue"] = [PyLoadPackageData().set(x) for x in self.data["queue"]]
        pdata["collector"] = [PyLoadFileData().set(x) for x in self.data["collector"]]
        
        output = open(join(self.core.configdir, "links.pkl"), "wb")
        cPickle.dump(pdata, output, -1)
        
        self.lock.release()
    
    def queueEmpty(self):
        return (self.data["queue"] == [])
    
    def getDownloadList(self, occ):
        """
            for thread_list only, returns all elements that are suitable for downloadthread
        """
        files = []
        files += [[x for x in p.files if x.status.type == None and x.plugin.__type__ == "container" and not x.active] for p in self.data["queue"] + self.data["packages"]]
        files += [[x for x in p.files if (x.status.type == None or x.status.type == "reconnected") and not x.active and not x.plugin.__name__ in occ] for p in self.data["queue"]]

        return reduce(concat, files, [])
    
    def getAllFiles(self):
        
        return map(attrgetter("files"), self.data["queue"] + self.data["packages"])
    
    def countDownloads(self):
        """ simply return len of all files in all packages(which have no type) in queue and collector"""
        return len(reduce(concat, [[x for x in p.files if x.status.type == None] for p in self.data["queue"] + self.data["packages"]], []))
    
    def getFileInfo(self, id):
        try:
            n, pyfile = self.collector._getFileFromID(id)
        except NoSuchElementException:
            key, n, pyfile, pypack, pid = self.packager._getFileFromID(id)
        info = {}
        info["id"] = pyfile.id
        info["url"] = pyfile.url
        info["folder"] = pyfile.folder
        info["filename"] = pyfile.status.filename
        info["status_type"] = pyfile.status.type
        info["status_url"] = pyfile.status.url
        info["status_filename"] = pyfile.status.filename
        info["status_error"] = pyfile.status.error
        info["size"] = pyfile.status.size()
        info["active"] = pyfile.active
        info["plugin"] = pyfile.plugin.__name__
        try:
            info["package"] = pypack.data["id"]
        except:
            pass
        return info

    def continueAborted(self):
        [[self.packager.resetFileStatus(x.id) for x in p.files if x.status.type == "aborted"] for p in self.data["queue"]]
    
    class pyLoadCollector():
        def __init__(collector, file_list):
            collector.file_list = file_list
        
        def _getFileFromID(collector, id):
            """
                returns PyLoadFile instance and position in collector with given id
            """
            for n, pyfile in enumerate(collector.file_list.data["collector"]):
                if pyfile.id == id:
                    return (n, pyfile)
            raise NoSuchElementException()
        
        def _getFreeID(collector):
            """
                returns a free id
            """
            ids = []
            for pypack in (collector.file_list.data["packages"] + collector.file_list.data["queue"]):
                for pyf in pypack.files:
                    ids.append(pyf.id)
            ids += map(attrgetter("id"), collector.file_list.data["collector"])
            id = 1
            while id in ids:
                id += 1
            return id
        
        def getFile(collector, id):
            """
                returns PyLoadFile instance from given id
            """
            return collector._getFileFromID(id)[1]
            
        def popFile(collector, id):
            """
                returns PyLoadFile instance given id and remove it from the collector
            """
            collector.file_list.lock.acquire()
            try:
                n, pyfile = collector._getFileFromID(id)
                del collector.file_list.data["collector"][n]
                collector.file_list.core.pullManager.addEvent(RemoveEvent("file", id, "collector"))
            except Exception, e:
                raise Exception, e
            else:
                return pyfile
            finally:
                collector.file_list.lock.release()
        
        def addLink(collector, url):
            """
                appends a new PyLoadFile instance to the end of the collector
            """
            pyfile = PyLoadFile(url, collector.file_list)
            pyfile.id = collector._getFreeID()
            pyfile.folder = collector.file_list.download_folder
            collector.file_list.lock.acquire()
            collector.file_list.data["collector"].append(pyfile)
            collector.file_list.lock.release()
            collector.file_list.core.pullManager.addEvent(InsertEvent("file", pyfile.id, -2, "collector"))
            return pyfile.id
        
        def removeFile(collector, id):
            """
                removes PyLoadFile instance with the given id from collector
            """
            collector.popFile(id)
            collector.file_list.core.pullManager.addEvent(RemoveEvent("file", id, "collector"))
        
        def replaceFile(collector, newpyfile):
            """
                replaces PyLoadFile instance with the given PyLoadFile instance at the given id
            """
            collector.file_list.lock.acquire()
            try:
                n, pyfile = collector._getFileFromID(newpyfile.id)
                collector.file_list.data["collector"][n] = newpyfile
                collector.file_list.core.pullManager.addEvent(UpdateEvent("file", newpyfile.id, "collector"))
            finally:
                collector.file_list.lock.release()
        
    class pyLoadPackager():
        def __init__(packager, file_list):
            packager.file_list = file_list
        
        def _getFreeID(packager):
            """
                returns a free id
            """
            ids = [pypack.data["id"] for pypack in packager.file_list.data["packages"] + packager.file_list.data["queue"]]
            
            id = 1
            while id in ids:
                id += 1
            return id
        
        def _getPackageFromID(packager, id):
            """
                returns PyLoadPackage instance and position with given id
            """
            for n, pypack in enumerate(packager.file_list.data["packages"]):
                if pypack.data["id"] == id:
                    return ("packages", n, pypack)
            for n, pypack in enumerate(packager.file_list.data["queue"]):
                if pypack.data["id"] == id:
                    return ("queue", n, pypack)
            raise NoSuchElementException()
        
        def _getFileFromID(packager, id):
            """
                returns PyLoadFile instance and position with given id
            """
            for n, pypack in enumerate(packager.file_list.data["packages"]):
                for pyfile in pypack.files:
                    if pyfile.id == id:
                        return ("packages", n, pyfile, pypack, pypack.data["id"])
            for n, pypack in enumerate(packager.file_list.data["queue"]):
                for pyfile in pypack.files:
                    if pyfile.id == id:
                        return ("queue", n, pyfile, pypack, pypack.data["id"])
            raise NoSuchElementException()
        
        def addNewPackage(packager, package_name=None):
            pypack = PyLoadPackage()
            pypack.data["id"] = packager._getFreeID()
            if package_name is not None:
                pypack.data["package_name"] = package_name
            packager.file_list.data["packages"].append(pypack)
            packager.file_list.core.pullManager.addEvent(InsertEvent("pack", pypack.data["id"], -2, "packages"))
            return pypack.data["id"]
        
        def removePackage(packager, id):
            packager.file_list.lock.acquire()
            try:
                key, n, pypack = packager._getPackageFromID(id)
                for pyfile in pypack.files:
                    pyfile.plugin.req.abort = True
                sleep(0.1)
                del packager.file_list.data[key][n]
                packager.file_list.core.pullManager.addEvent(RemoveEvent("pack", id, key))
            finally:
                packager.file_list.lock.release()
        
        def removeFile(packager, id):
            """
                removes PyLoadFile instance with the given id from package
            """
            packager.file_list.lock.acquire()
            try:
                key, n, pyfile, pypack, pid = packager._getFileFromID(id)
                pyfile.plugin.req.abort = True
                sleep(0.1)
                packager.removeFileFromPackage(id, pid)
                if not pypack.files:
                    packager.removePackage(pid)
            finally:
                packager.file_list.lock.release()
        
        def pushPackage2Queue(packager, id):
            packager.file_list.lock.acquire()
            try:
                key, n, pypack = packager._getPackageFromID(id)
                if key == "packages":
                    del packager.file_list.data["packages"][n]
                    packager.file_list.data["queue"].append(pypack)
                    packager.file_list.core.pullManager.addEvent(RemoveEvent("pack", id, "packages"))
                    packager.file_list.core.pullManager.addEvent(InsertEvent("pack", id, -2, "queue"))
            finally:
                packager.file_list.lock.release()
        
        def pullOutPackage(packager, id):
            packager.file_list.lock.acquire()
            try:
                key, n, pypack = packager._getPackageFromID(id)
                if key == "queue":
                    del packager.file_list.data["queue"][n]
                    packager.file_list.data["packages"].append(pypack)
                    packager.file_list.core.pullManager.addEvent(RemoveEvent("pack", id, "queue"))
                    packager.file_list.core.pullManager.addEvent(InsertEvent("pack", id, -2, "packages"))
            finally:
                packager.file_list.lock.release()
        
        def setPackageData(packager, id, package_name=None, folder=None):
            packager.file_list.lock.acquire()
            try:
                key, n, pypack = packager._getPackageFromID(id)
                if package_name is not None:
                    pypack.data["package_name"] = package_name
                if folder is not None:
                    pypack.data["folder"] = folder
                packager.file_list.data[key][n] = pypack
                packager.file_list.core.pullManager.addEvent(UpdateEvent("pack", id, key))
            finally:
                packager.file_list.lock.release()
        
        def getPackageData(packager, id):
            key, n, pypack = packager._getPackageFromID(id)
            return pypack.data
        
        def getPackageFiles(packager, id):
            key, n, pypack = packager._getPackageFromID(id)
            ids = map(attrgetter("id"), pypack.files)
            
            return ids
        
        def addFileToPackage(packager, id, pyfile):
            key, n, pypack = packager._getPackageFromID(id)
            pyfile.package = pypack
            pypack.files.append(pyfile)
            packager.file_list.data[key][n] = pypack
            packager.file_list.core.pullManager.addEvent(InsertEvent("file", pyfile.id, -2, key))
        
        def resetFileStatus(packager, fileid):
            packager.file_list.lock.acquire()
            try:
                key, n, pyfile, pypack, pid = packager._getFileFromID(fileid)
                pyfile.init()
                pyfile.status.type = None
                packager.file_list.core.pullManager.addEvent(UpdateEvent("file", fileid, key))
            finally:
                packager.file_list.lock.release()
        
        def abortFile(packager, fileid):
            packager.file_list.lock.acquire()
            try:
                key, n, pyfile, pypack, pid = packager._getFileFromID(fileid)
                pyfile.plugin.req.abort = True
                packager.file_list.core.pullManager.addEvent(UpdateEvent("file", fileid, key))
            finally:
                packager.file_list.lock.release()
       
        def removeFileFromPackage(packager, id, pid):
            key, n, pypack = packager._getPackageFromID(pid)
            for k, pyfile in enumerate(pypack.files):
                if id == pyfile.id:
                    del pypack.files[k]
                    packager.file_list.core.pullManager.addEvent(RemoveEvent("file", pyfile.id, key))
                    if not pypack.files:
                        packager.removePackage(pid)
                    return True
            raise NoSuchElementException()

class PyLoadPackage():
    def __init__(self):
        self.files = []
        self.data = {
            "id": None,
            "package_name": "new_package",
            "folder": ""
        }

class PyLoadFile():
    def __init__(self, url, file_list):
        self.id = None
        self.url = url
        self.folder = ""
        self.file_list = file_list
        self.core = file_list.core
        self.package = None
        self.filename = "n/a"
        self.init()
    
    def init(self):
        self.active = False
        pluginClass = self.core.pluginManager.getPluginFromPattern(self.url)
        self.plugin = pluginClass(self)
        self.status = Status(self)
        self.status.filename = self.url

    def init_download(self):
        if self.core.config['proxy']['activated']:
            self.plugin.req.add_proxy(self.core.config['proxy']['protocol'], self.core.config['proxy']['adress'])

class PyLoadFileData():
    def __init__(self):
        self.id = None
        self.url = None
        self.folder = None
        self.pack_id = None
        self.filename = None
        self.status_type = None
        self.status_url = None

    def set(self, pyfile):
        self.id = pyfile.id
        self.url = pyfile.url
        self.folder = pyfile.folder
        self.parsePackage(pyfile.package)
        self.filename = pyfile.filename
        self.status_type = pyfile.status.type
        self.status_url = pyfile.status.url
        self.status_filename = pyfile.status.filename
        self.status_error = pyfile.status.error

        return self

    def get(self, pyfile):
        pyfile.id = self.id
        pyfile.url = self.url
        pyfile.folder = self.folder
        pyfile.filename = self.filename
        pyfile.status.type = self.status_type
        pyfile.status.url = self.status_url
        pyfile.status.filename = self.status_filename
        pyfile.status.error = self.status_error
    
    def parsePackage(self, pack):
        if pack:
            self.pack_id = pack.data["id"]

class PyLoadPackageData():
    def __init__(self):
        self.data = None
        self.files = []

    def set(self, pypack):
        self.data = pypack.data
        self.files = [PyLoadFileData().set(x) for x in pypack.files]
        return self
        
    def get(self, pypack, fl):
        pypack.data = self.data
        for fdata in self.files:
            pyfile = PyLoadFile(fdata.url, fl)
            fdata.get(pyfile)
            pyfile.package = pypack
            pypack.files.append(pyfile)