import sys
import wx
import os

from cStringIO import StringIO
from threading import Thread,currentThread
from time import sleep
from traceback import print_exc, print_stack
from webbrowser import open_new

from Dialogs.dupfiledialog import DupFileDialog

from Utility.constants import * #IGNORE:W0611
from Tribler.unicode import bin2unicode

################################################################
#
# Class: TorrentFiles
#
# Keep track of the files associated with a torrent
#
################################################################
class TorrentFiles:
    def __init__(self, torrent):
        self.torrent = torrent
        self.utility = torrent.utility

        namekey = self.torrent.namekey
        
        self.filename = self.torrent.info[namekey]
                      
        # Array to store file priorities
        # Just using a placeholder of 1 (Normal) for now
        if self.isFile():
            numfiles = 1
        else:
            numfiles = len(self.torrent.info['files'])
            # Change all files to unicode
            if self.torrent.info['files'][0].has_key('path.utf-8'):
                pathkey = 'path.utf-8'
            else:
                pathkey = 'path'
            for i in range(numfiles):
                for j in range(len(self.torrent.info['files'][i]['path'])):
                    self.torrent.info['files'][i]['path'][j] = bin2unicode(self.torrent.info['files'][i][pathkey][j])
                        
        self.filepriorities = [1] * numfiles

        self.floattotalsize = float(self.getSize())
        self.realsize = self.floattotalsize
        
        # This one is to store the download progress ; if it's not stored, the progress
        # of an inactive torrent would stay only in the display of the list, and so it would
        # be lost if the GUI wouldn't display the column "progress". In this case it couldn't
        # be saved in the torrent.lst file.
        self.progress = 0.0
        self.downsize = 0.0
        self.upsize = 0.0
        
        # Progress of individual files within torrent
        self.fileprogress = [""] * numfiles
        
        self.dest = None
        
#        self.skipcheck = False
        
            
    def setupDest(self, dest, forceasklocation, caller):
        self.dest = dest
        
        # Try reading the config file
        if self.dest is None:
            self.dest = self.torrent.torrentconfig.Read("dest")

        # Treat an empty string for dest the same as
        # not having one defined
        if not self.dest:
            self.dest = None

        # For new torrents, get the destination where to save the torrent
        if self.dest is None or forceasklocation:
            self.getDestination(forceasklocation, caller)

        # Treat an empty string for dest the same as
        # not having one defined
        if not self.dest:
            self.dest = None

    def onOpenDest(self, event = None, index = 0):
        return self.onOpenFileDest(index, pathonly = True)

    def onOpenFileDest(self, event = None, index = 0, pathonly = False):
        dest = self.getSingleFileDest(index, pathonly, checkexists = False)
        
        # Check to make sure that what we're trying to get exists
        if dest is None or not os.access(dest, os.R_OK):
            if dest is None:
                dest = "None"
            # Error : file not found
            dialog = wx.MessageDialog(None, 
                                      dest + '\n\n' + self.utility.lang.get('filenotfound'), 
                                      self.utility.lang.get('error'), 
                                      wx.ICON_ERROR)
            dialog.ShowModal()
            dialog.Destroy()
            return False
            
        # A file is completed if it either is a single file flagged as completed,
        # or is a file within a multi-file torrent flagged as "Done"
        # (i.e.: file is in-place)
        if self.isFile():
            completed = self.torrent.status.completed
        else:
            completed = (self.fileprogress[index] == self.utility.lang.get('done'))
            
        # Don't need to check if the torrent is complete if we're only
        # opening the path
        if not pathonly and not completed:
            #Display Warning file is not complete yet
            dialog = wx.MessageDialog(None, 
                                      self.torrent.getColumnText(COL_TITLE) + '\n\n'+ self.utility.lang.get('warningopenfile'), 
                                      self.utility.lang.get('warning'), 
                                      wx.YES_NO|wx.ICON_EXCLAMATION)

            result = dialog.ShowModal()
            dialog.Destroy()
            if result != wx.ID_YES:
                return False

        try:
            if sys.platform == 'darwin':
                dest = 'file://%s' % dest
            Thread(target = open_new(str(dest))).start()
        except:
            pass
            
        return True
        
    def changeProcDest(self, dest, rentorrent = False):

        self.dest = dest
        self.torrent.updateColumns([COL_DEST])
        self.torrent.torrentconfig.writeBasicInfo()

        if rentorrent:
            # Update torrent name
            self.torrent.changeTitle(os.path.split(dest)[1])
        
        details = self.torrent.dialogs.details
        if details is not None:
            try:
                oldlabel = details.fileInfoPanel.opendirbtn.GetLabel()
                newlabel = self.torrent.files.getProcDest(pathonly = True, checkexists = False)
                if oldlabel != newlabel:
                    details.fileInfoPanel.opendirbtn.SetLabel(newlabel)
                    # Need to call "Layout" since the new text
                    # may require a larger button to fit
                    details.fileInfoPanel.Layout()
                
                details.updateTorrentName()
            except wx.PyDeadObjectError:
                pass
            
    def move(self, dest = None):
        if dest is None:
            dest = self.utility.config.Read('defaultmovedir')
        
        if not os.access(dest, os.F_OK):
            try:
                os.makedirs(dest)
            except:
                return False
       
        #Wait thread a little bit for returning resource
        ##################################################
        sleep(0.5)

        if self.isFile():
            self.moveSingleFile(dest)
        else:
            self.moveDir(dest)

        self.changeProcDest(os.path.join(dest, self.filename))
        
        return True
                               
    def moveSingleFile(self, dest):
        if not self.isFile():
            self.moveDir(dest)
            return
        
        filename = os.path.split(self.dest)[1]
        source = os.path.split(self.dest)[0]
        size = int(self.torrent.info['length'])
        
        self.moveFiles({filename: size}, source, dest)
            
    def moveFiles(self, filearray, source, dest):
        dummyname = os.path.join(os.path.split(self.dest)[0], 'dummy')
        try:
            file(dummyname, 'w').close()
        except:
            pass
       
        overwrite = "ask"
       
        for filename in filearray:
            oldloc = os.path.join(source, filename)
            newloc = os.path.join(dest, filename)
            size = filearray[filename]

            done = False
            firsttime = True
                
            while not done:
                try:
                    # File exists
                    if os.access(oldloc, os.R_OK):
                        copyfile = True
                        
                        # Something already exists where we're trying to copy:
                        if os.access(newloc, os.F_OK):
                            # Default to "No"
                            result = -1
                            
                            if overwrite == "ask":
                                single = len(filearray) > 1
                                dialog = DupFileDialog(self.torrent, filename, single)
                                result = dialog.ShowModal()
                                dialog.Destroy()
                                if result == 2:
                                    overwrite = "yes"
                                elif result == -2:
                                    overwrite = "no"
                                    
                            if overwrite == "yes" or result > 0:
                                os.remove(newloc)
                            elif overwrite == "no" or result < 0:
                                copyfile = False
                                
                        if copyfile:
                            os.renames(oldloc, newloc)
                    done = True
                except:
                    # There's a very special case for a file with a null size referenced in the torrent
                    # but not retrieved just because of this null size : It can't be renamed so we
                    # just skip it.
                    if size == 0:
                        done = True
                    else:
                        #retry >_<;
                        if firsttime:
                            firsttime = False
                            sleep(0.1)
                        else:
                            done = True
                            
                            data = StringIO()
                            print_exc(file = data)
                            
                            dialog = wx.MessageDialog(None, self.utility.lang.get('errormovefile') + "\n" + data.getvalue(), self.utility.lang.get('error'), wx.ICON_ERROR)
                            dialog.ShowModal()
                            dialog.Destroy()
                            
        try:
            os.remove(dummyname)
        except:
            pass
                   
    def moveDir(self, dest):
        if self.isFile():
            self.moveSingleFile(dest)
            return
           
        destname = self.getProcDest()
       
        if destname is None:
            return
        
        filearray = {}
        
        movename = os.path.join(dest, self.filename)
        for f in self.torrent.info['files']:
            for item in f['path']:
                size = int(f['length'])
                filearray[item] = size

        self.moveFiles(filearray, destname, movename)

        self.utility.RemoveEmptyDir(destname, True)
        
            
    def removeFiles(self):
        destination = self.getProcDest()
        
        if destination is None:
            return
      
        # Remove File
        ##################################################        
        done = False
        firsttime = True
        while not done:
            #Wait thread a little bit for returning resource
            ##################################################
            sleep(0.5)
            try:
                if self.isFile():
                    #remove file
                    if os.access(destination, os.F_OK):
                        os.remove(destination)
                else:                  
                    # Only delete files from this torrent
                    # (should be safer this way)
                    subdirs = 0
                    for x in self.torrent.info['files']:
                        filename = destination
                        subdirs = max(subdirs, len(x['path']) - 1)
                        for i in x['path']:
                            filename = os.path.join(filename, i)
                        if os.access(filename, os.F_OK):
                            os.remove(filename)
                    
                    self.utility.RemoveEmptyDir(destination, (subdirs > 0))
                done = True
            except:
                #retry >_<;
                if firsttime:
                    firsttime = False
                    sleep(0.1)
                else:
                    done = True
                    
                    data = StringIO()
                    print_exc(file = data)
                    
                    dialog = wx.MessageDialog(None, self.utility.lang.get('errordeletefile') + "\n" + data.getvalue(), self.utility.lang.get('error'), wx.ICON_ERROR)
                    dialog.ShowModal()
                    dialog.Destroy()
                    
        #TODO: change db
                    
    def getDest(self):
        return self.dest
        
    # Specify where to save the torrent
    def getDestination(self, forceasklocation = False, caller = ""):
        # Set destination location that will be used in next set destination dialog

        # No default directory (or default directory can't be found)
        defaultfolder = self.utility.config.Read('defaultfolder')
        if not os.access(defaultfolder, os.F_OK):
            try:
                os.makedirs(defaultfolder)
            except:
                forceasklocation = True
                
        if ((not self.utility.config.Read('setdefaultfolder', "boolean") or forceasklocation)
            and (caller != "web")):
            success, dest = self.torrent.dialogs.setDestination()
            if not success:
                try:
                    os.remove(dest)
                except:
                    pass
            else:
                if not 'length' in self.torrent.info:     #multi file torrent
                    self.dest = os.path.join(dest, self.filename)
                else:   #1 file for this torrent
                    self.dest = dest
        else:
            self.dest = os.path.join(self.utility.config.Read('defaultfolder'), self.filename)
            
    def getProcDest(self, pathonly = False, checkexists = True):
        # Set it to self.dest (should be fine for files)
        dest = self.dest
        
        # In the case of a multifile torrent, see where we're saving
        if not self.isFile():
            ## see if we're saving to a subdirectory or not
            existing = 0
            try:
              if os.path.exists(dest):
                if not os.path.isdir(dest):
                    dest = None
                if os.listdir(dest):  # if it's not empty
                    for x in self.torrent.info['files']:
                        if os.path.exists(os.path.join(dest, x['path'][0])):
                            existing = 1
                    if not existing:
                        dest = os.path.join(dest, self.filename)
            except UnicodeEncodeError:
                return dest
        elif pathonly:
            # Strip out just the path for a regular torrent
            dest = os.path.dirname(self.dest)
                        
        if checkexists and dest is not None and not os.access(dest, os.F_OK):
            return None
                        
        return dest
    
    # Used for getting the path for a file in a multi-file torrent
    def getSingleFileDest(self, index = 0, pathonly = False, checkexists = True):
        if self.isFile():
            return self.getProcDest(pathonly, checkexists)
        
        # This isn't a valid file
        if index > len(self.torrent.info['files']):
            return None
            
        fileinfo = self.torrent.info['files'][index]
        dest = self.getProcDest(pathonly = True, checkexists = False)
        for item in fileinfo['path']:
            dest = os.path.join(dest, item)
                        
        if pathonly:
            dest = os.path.dirname(dest)
                   
        if checkexists and dest is not None and not os.access(dest, os.F_OK):
            return None
            
        return dest
        
    def isFile(self):
        return 'length' in self.torrent.info
        
    #
    # Get the total size of all files in the torrent
    #
    # If realsize is True, only return the total size
    # of files that aren't set to "download never"
    #
    def getSize(self, realsize = False):
        if self.isFile():   #1 file for this torrent
            file_length = self.torrent.info['length']
        else:   # Directory torrent
            file_length = 0
            count = 0
            for x in self.torrent.info['files']:
                # If returning the real size, don't include files
                # set to "download never"
                if not realsize or self.filepriorities[count] != -1:
                    file_length += x['length']
                count += 1
        
        return file_length        
        
    def updateRealSize(self):
        self.realsize = self.getSize(realsize = True)
        
        self.torrent.updateColumns([COL_SIZE])
    
    # Set the priorities for all of the files in a multi-file torrent    
    def setFilePriorities(self, priority_array = None):
        if priority_array is not None:
            self.filepriorities = priority_array
            self.torrent.torrentconfig.writeFilePriorities()
            self.updateRealSize()
            self.updateFileProgress()
        
        engine = self.torrent.connection.engine
        if len(self.filepriorities) > 1 and engine is not None and engine.dow is not None:
            engine.dow.fileselector.set_priorities(self.filepriorities)
            
    def getFilePrioritiesAsString(self):
        notdefault = False
        text = ""
        if len(self.filepriorities) > 1:
            for entry in self.filepriorities:
                if entry != 1:
                    notdefault = True
                text += ('%d,' % entry)
            # Remove the trailing ","
            text = text[:-1]

        return notdefault, text
        
    def updateProgress(self):

        if currentThread().getName() != "MainThread":
            print "TorrentFiles: updateProgress thread",currentThread()
            print "NOT MAIN THREAD"
            print_stack()
        

        # update the download progress
        if self.torrent.status.isActive():
            engine = self.torrent.connection.engine
            self.downsize = engine.downsize['old'] + engine.downsize['new']
            self.upsize = engine.upsize['old'] + engine.upsize['new']
            
            if self.torrent.status.isActive(checking = False, pause = False):
                self.progress = engine.progress
                
                if self.isFile():
                    details = self.torrent.dialogs.details
                    if details is not None:
                        details.fileInfoPanel.updateColumns([FILEINFO_PROGRESS])
                    
                
    def updateFileProgress(self, statistics = None):
        if self.isFile():
            return
        
        if currentThread().getName() != "MainThread":
            print "TorrentFiles: updateFileProgress thread",currentThread()
            print "NOT MAIN THREAD"
            print_stack()


        # Clear progress for all files that are set to never download
        for i in range(len(self.filepriorities)):
            priority = self.filepriorities[i]
            if priority == -1:
                self.fileprogress[i] = ''
                
        if statistics is not None and statistics.filelistupdated.isSet():
            for i in range(len(statistics.filecomplete)):
                progress = None
                
                if self.filepriorities[i] == -1:
                    # Not download this file
                    progress = ''
                elif statistics.fileinplace[i]:
                    # File is done
                    progress = self.utility.lang.get('done')
                elif statistics.filecomplete[i]:
                    # File is at complete, but not done
                    progress = "100%"
                else:
                    # File isn't complete yet
                    frac = statistics.fileamtdone[i]
                    if frac:
                        progress = '%d%%' % (frac*100)
                    else:
                        progress = ''
                if progress is None:
                    progress = ''
                    
                self.fileprogress[i] = progress
                
            statistics.filelistupdated.clear()
        
        details = self.torrent.dialogs.details
        if details is not None:
            details.fileInfoPanel.updateColumns([FILEINFO_SIZE, FILEINFO_PROGRESS])

    #
    # See how much more space is allocated to this torrent
    #
    def getSpaceAllocated(self):
        allocated = 0L
        try:
          if self.isFile():
            if os.path.exists(self.dest):
                allocated = os.path.getsize(self.dest)
          else:
            count = 0
            for f in self.torrent.info['files']:
                # Don't include space taken by disabled files
                if self.filepriorities[count] != -1:
                    filename = self.getProcDest()
                    for item in f['path']:
                        filename = os.path.join(filename, item)
                    if os.path.exists(filename):
                        allocated += os.path.getsize(filename)
                count += 1
        except UnicodeEncodeError:
            pass
        return allocated
    
    #
    # See how much space is needed by this torrent
    #
    def getSpaceNeeded(self, realsize = True):
        # Shouldn't need any more space if the file is complete
        if self.torrent.status.completed:
            return 0L
        
        # See how much space the torrent needs vs. how much is already allocated
        space = self.getSize(realsize = realsize) - self.getSpaceAllocated() 
        if space < 0:
            space = 0L
        return space
        
        
