# Copyright (C) 2007 daelstorm. All rights reserved.
# -*- 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/>.
#
# Original copyright below
# Copyright (c) 2003-2004 Hyriand. All rights reserved.

from __future__ import division

import gtk
import gobject
from types import StringType
import string
from time import time
from math import ceil

from utils import InitialiseColumns, int_sort_func, float_sort_func
from uglytree import UglyTree

from pynicotine.utils import _
from pynicotine.logfacility import log

class TransferList:
	MINIMUM_GUI_DELAY = 0.3 # in seconds
	MINIMUM_GUI_DELAY_SLEEP = int(ceil(MINIMUM_GUI_DELAY * 2000)) # in ms
	def __init__(self, frame, widget):
		self.frame = frame
		self.widget = widget
		self.transfers = []
		self.list = None
		self.selected_transfers = []
		self.selected_users = []
		self.users = {}
		self.lastupdate = 0
		self.finalupdatetimerid = None
		widget.get_selection().set_mode(gtk.SELECTION_MULTIPLE)

		columntypes = [gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_INT , gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING,  gobject.TYPE_INT, gobject.TYPE_INT, gobject.TYPE_INT, gobject.TYPE_BOOLEAN]

		self.transfersmodel = gtk.TreeStore(*columntypes)
		#self.transfersmodel = UglyTree(columntypes)
		self.cols = cols = InitialiseColumns(widget,
			[_("User"), 100, "text", self.CellDataFunc],
			[_("Filename"), 250, "text", self.CellDataFunc],
			[_("Status"), 140, "text", self.CellDataFunc],
			[_("Queue Position"), 50, "number", self.CellDataFunc],
			[_("Percent"), 70, "progress"],
			[_("Size"), 170, "text", self.CellDataFunc],
			[_("Speed"), 50, "number", self.CellDataFunc],
			[_("Time elapsed"), 70, "text", self.CellDataFunc],
			[_("Time left"), 70, "text", self.CellDataFunc],
			[_("Path"), 1000, "text", self.CellDataFunc],
		)
		self.col_user, self.col_filename , self.col_status, self.col_position, self.col_percent, self.col_human_size, self.col_human_speed, self.col_time_elapsed, self.col_time_left, self.col_path = cols
		#, self.col_fullpath,  self.col_int_status, self.col_int_speed, self.col_current_size, self.col_visible 
		self.col_user.set_sort_column_id(0)
		self.col_filename.set_sort_column_id(1)
		self.col_status.set_sort_column_id(2)
		
		# Only view progress renderer on transfers, not user tree parents
		self.transfersmodel.set_sort_func(2, self.status_sort_func, 2)
		self.col_position.set_sort_column_id(3)
		self.transfersmodel.set_sort_func(3, int_sort_func, 3)
		self.col_percent.set_sort_column_id(11)
		
		self.col_percent.set_attributes(self.col_percent.get_cell_renderers()[0], value=4, visible=14)
		#self.col_position.set_attributes(self.col_position.get_cell_renderers()[0], visible=14)
		
		self.col_human_size.set_sort_column_id(12)
		self.col_human_speed.set_sort_column_id(6)
		self.col_time_elapsed.set_sort_column_id(7)
		self.col_time_left.set_sort_column_id(8)
		self.col_path.set_sort_column_id(9)
		
		self.transfersmodel.set_sort_func(11, self.progress_sort_func, 4)
		#self.transfersmodel.set_sort_func(11, self.progress_sort_func, 11)
		#self.transfersmodel.set_sort_func(12, self.progress_sort_func, 12)
		#self.transfersmodel.set_sort_func(13, self.progress_sort_func, 13)
		self.transfersmodel.set_sort_func(6, float_sort_func, 6)
		#self.frame.CreateIconButton(gtk.STOCK_INDENT, "stock", self.OnToggleTree, "Group by Users")
		#self.hbox1.pack_end(self.ToggleTree, False, False)
		
		
		widget.set_model(self.transfersmodel)
		self.UpdateColours()
		
	def UpdateColours(self):
		self.frame.SetTextBG(self.widget)
		self.frame.ChangeListFont(self.widget, self.frame.np.config.sections["ui"]["transfersfont"])
		
	status_tab = [
		_("Getting status"),
		_("Waiting for download"),
		_("Waiting for upload"),
		_("Getting address"),
		_("Connecting"),
		_("Waiting for peer to connect"),
		_("Cannot connect"),
		_("User logged off"),
		_("Requesting file"),
		_("Initializing transfer"),
		_('Filtered'),
		_("Download directory error"),
		_("Local file error"),
		_("File not shared"),
		_("Aborted"),
		_('Paused'),
		_("Queued"),
		_("Transferring"),
		_("Finished"),

	]
	
	def CellDataFunc(self, column, cellrenderer, model, iter):
		colour = self.frame.np.config.sections["ui"]["search"]
		if colour == "":
			colour = None
		cellrenderer.set_property("foreground", colour)
		
	def get_status_index(self, val):
		try:
			return int(val)
		except:
			if val in self.status_tab:
				return self.status_tab.index(val)
			else:
				return -len(self.status_tab)
	
	def status_sort_func(self, model, iter1, iter2, column):
		val1 = self.get_status_index(model.get_value(iter1, column))
		val2 = self.get_status_index(model.get_value(iter2, column))
		return cmp(val1, val2)
	def progress_sort_func(self, model, iter1, iter2, column):
		# We want 0% to be always below anything else,
		# so we have to look up whether we are ascending or descending
		ascending = True
		if model.get_sort_column_id()[1] == gtk.SORT_DESCENDING:
			ascending = False
		val1 = self.get_status_index(model.get_value(iter1, column))
		val2 = self.get_status_index(model.get_value(iter2, column))
		if val1 == 0 and val2 == 0:
			return 0
		if val1 == 0:
			return -1 + (ascending * 2)
		if val2 == 0:
			return 1 - (ascending * 2)
		return cmp(val1, val2)

	def InitInterface(self, list):
		self.list = list
		self.update()
		self.widget.set_sensitive(True)
		
	def ConnClose(self):
		self.widget.set_sensitive(False)
		self.list = None
		self.Clear()
		self.transfersmodel.clear()
		self.transfers = []
		self.users.clear()
		self.selected_transfers = []
		self.selected_users = []
		
	def SelectedTransfersCallback(self, model, path, iter):
		user = model.get_value(iter, 0)
		file = model.get_value(iter, 10)
		for i in self.list:
			if i.user == user and i.filename == file:
				self.selected_transfers.append(i)
				
				break
		if user not in self.selected_users:
			self.selected_users.append(user)
		
	def SelectCurrentRow(self, event, kind):
		# If nothing is selected (first click was right-click?) try to select the 
		# current row
		if self.selected_transfers == [] and self.selected_users == [] and kind == "mouse":
			d = self.widget.get_path_at_pos(int(event.x), int(event.y))
			if d:
				path, column, x, y = d
				iter = self.transfersmodel.get_iter(path)
				user = self.transfersmodel.get_value(iter, 0)
				file = self.transfersmodel.get_value(iter, 10)
				if path is not None:
					sel = self.widget.get_selection()
					sel.unselect_all()
					sel.select_path(path)
				for i in self.list:
					if i.user == user and i.filename == file:
						self.selected_transfers.append(i)
						break
				if user not in self.selected_users:
					self.selected_users.append(user)
	def Humanize(self, size, modifier):
		if size is None:
			return None

		if modifier == None:
			modifier = ""
		else: modifier = " " + modifier

		try:
			s = int(size)
			if s >= 1000*1024*1024:
				r = _("%.2f GB") % ((float(s) / (1024.0*1024.0*1024.0)))
			elif s >= 1000*1024:
				r = _("%.2f MB") % ((float(s) / (1024.0*1024.0)))
			elif s >= 1000:
				r = _("%.2f KB") % ((float(s) / 1024.0))
			else:
				r = str(size)
			return r + modifier
		except:
			return size + modifier
			
	def TranslateStatus(self, status):
		if status == "Waiting for download":
			newstatus = _("Waiting for download")
		elif status == "Waiting for upload":
			newstatus = _("Waiting for upload")
		elif status == "Requesting file":
			newstatus = _("Requesting file")
		elif status == "Initializing transfer":
			newstatus = _("Initializing transfer")
		elif status == "Cannot connect":
			newstatus = _("Cannot connect")
		elif status == "Waiting for peer to connect":
			newstatus = _("Waiting for peer to connect")
		elif status == "Connecting":
			newstatus = _("Connecting")
		elif status == "Getting address":
			newstatus = _("Getting address")
		elif status == "Getting status":
			newstatus = _("Getting status")
		elif status == "Queued":
			newstatus = _("Queued")
		elif status == "User logged off":
			newstatus = _("User logged off")
		elif status == "Aborted":
			newstatus =  _("Aborted")
		elif status == "Finished":
			newstatus = _("Finished")
		elif status == 'Paused':
			newstatus = _("Paused")
		elif status == 'Transferring':
			newstatus = _("Transferring")
		elif status == 'Filtered':
			newstatus = _('Filtered')
		elif status == 'Connection closed by peer':
			newstatus = _('Connection closed by peer')
		elif status == "File not shared":
			newstatus = _("File not shared")
		elif status == "Establishing connection":
			newstatus = _("Establishing connection")
		elif status == "Download directory error":
			newstatus = _("Download directory error")
		elif status == "Local file error":
			newstatus = _("Local file error")
		else:
			newstatus = status
		return newstatus
	
	
	def finalupdate(self, func):
		now = time()
		# I had a logical explanation about why it has to be 3*delay, but I
		# forgot. Something to do with the timeout being 2*delay
		if now - self.lastupdate < 3*self.MINIMUM_GUI_DELAY:
			# The list has been updated recently,
			# trying again later.
			#print "sleeping..."
			return True 
		#print "final update!"
		self.update(forced=True) # delayed updates can never trigger a new timer
		self.finalupdatetimerid = None
		return False # Stopping timeout
	def update(self, transfer = None, forced = False):
		now = time()
		if transfer is not None:
			self.update_specific(transfer)
		elif self.list is not None:
			# This seems to me to be O(n^2), perhaps constructing a temp. dict
			# from self.list would be better?
			for i in self.transfers[:]:
				for j in self.list:
					if [j.user, j.filename] == i[0]:
						break
				else:
					# Remove transfers from treeview that aren't in the transfer list
					self.transfersmodel.remove(i[1])
					self.transfers.remove(i)
			for i in self.list:
				self.update_specific(i)
		# The rest is just summarizing so it's not too important.
		# It's fairly CPU intensive though, so we only do it if we haven't updated it recently
		if not forced and now - self.lastupdate < self.MINIMUM_GUI_DELAY:
			#print "recently updated, not doing it again"
			if not self.finalupdatetimerid:
				self.finalupdatetimerid = True # I'm not sure if gobject returns fast enough
				#print "there's no timeout active, adding one"
				self.finalupdatetimerid = gobject.timeout_add(self.MINIMUM_GUI_DELAY_SLEEP, self.finalupdate, self.update)
			return
		# Remove empty parent rows
		self.lastupdate = now
		for user in self.users.keys()[:]:
			if not self.transfersmodel.iter_has_child(self.users[user]):
				self.transfersmodel.remove(self.users[user])
				del self.users[user]
			else:
				files = self.transfersmodel.iter_n_children(self.users[user])
				ispeed = 0.0
				percent = totalsize = position = 0
				elapsed = left = ""
				elap = 0
				salientstatus = ""
				extensions = {}
				for f in range(files):
					iter = self.transfersmodel.iter_nth_child(self.users[user], f)
					filename = self.transfersmodel.get_value(iter, 10)
					parts = filename.rsplit('.', 1)
					if len(parts) == 2:
						ext = parts[1]
						try:
							extensions[ext.lower()] += 1
						except KeyError:
							extensions[ext.lower()] = 1
					for transfer in self.list:
						if [transfer.user, transfer.filename] == [user, filename] and transfer.timeelapsed is not None:
							elap += transfer.timeelapsed
							break
					totalsize += self.transfersmodel.get_value(iter, 12)
					position += self.transfersmodel.get_value(iter, 13)
					status = self.transfersmodel.get_value(iter, 2)
						
					if status == _("Transferring"):
						str_speed = self.transfersmodel.get_value(iter, 6)
						if str_speed != "":
							ispeed += float(str_speed)
						
						left = self.transfersmodel.get_value(iter, 8)
					if salientstatus in ('',_("Finished")): # we prefer anything over ''/finished
						salientstatus = status
					if status in (_("Transferring"), _("Banned"), _("Getting address"), _("Establishing connection")):
						salientstatus = status
				try:
					speed = "%.1f" % ispeed
				except TypeError:
					speed = str(ispeed)
				if totalsize > 0:
					percent = ((100 * position)/ totalsize)
					
				if ispeed <= 0.0:
					left = "∞"
				else:
					left = self.frame.np.transfers.getTime((totalsize - position)/ispeed/1024)
				elapsed = self.frame.np.transfers.getTime(elap)
				
				if len(extensions) == 0:
					extensions = "Unknown"
				elif len(extensions) == 1:
					extensions = _("All %(ext)s") % {'ext':extensions.keys()[0]}
				else:
					extensionlst = [(extensions[key], key) for key in extensions]
					extensionlst.sort(reverse=True)
					extensions = ", ".join([str(count) + " " + ext for (count, ext) in extensionlst])
				self.transfersmodel.set(self.users[user],
						1, _("%(number)2s files ") % {'number':files} + " (" + extensions + ")",
						2, salientstatus,
						4, percent,
						5, "%s / %s" % (self.Humanize(position, None), self.Humanize(totalsize, None )),
						6, speed,
						7, elapsed,
						8, left,
						12, ispeed,
						14, True)
		self.frame.UpdateBandwidth()
	def update_specific(self, transfer = None):
		if not transfer in self.list:
			return
		fn = transfer.filename
		user = transfer.user
		shortfn = self.frame.np.transfers.encode(fn.split("\\")[-1], user)
		currentbytes = transfer.currentbytes
		place = transfer.place
		if currentbytes == None:
			currentbytes = 0
		
		key = [user, fn]
		
		status = self.Humanize(self.TranslateStatus(transfer.status), None)
		istatus = self.get_status_index(transfer.status)
		try:
			size = int(transfer.size)
		except TypeError:
			size = 0
		hsize = "%s / %s" % (self.Humanize(currentbytes, None), self.Humanize(size, transfer.modifier ))
		#self.Humanize(transfer.size, transfer.modifier)
		try:
			speed = "%.1f" % transfer.speed
		except TypeError:
			speed = str(transfer.speed)

		elap = transfer.timeelapsed
		left = str(transfer.timeleft)
		
		if speed == "None":
			speed = ""
		if elap == None:
			elap = 0
		elap = self.frame.np.transfers.getTime(elap)
		if left == "None":
			left = ""
		try:
			#print currentbytes
			icurrentbytes = int(currentbytes)
			if  icurrentbytes == int(transfer.size):
				percent = 100
			else:
				percent = ((100 * icurrentbytes)/ int(size))
		except Exception, e:
			#print e
			icurrentbytes = 0
			percent = 0

		# Modify old transfer
		for i in self.transfers:
			if i[0] != key:
				continue
			if i[2] != transfer:
				continue
			self.transfersmodel.set(i[1], 1, shortfn, 2, status, 3, place, 4, percent, 5, hsize, 6, speed, 7, elap, 8, left, 9, self.frame.np.decode(transfer.path), 11, istatus, 12, size, 13, currentbytes)
			break
		else:
			newparent = False
			if self.TreeUsers:
				if user not in self.users:
					# Create Parent if it doesn't exist
					# ProgressRender not visible (last column sets 4th column)
					self.users[user] = self.transfersmodel.append(None, [user, "", "", "", 0,  "", "", "", "", "", "", 0, 0, 0,  False])
					newparent = True
					#self.col_position.set_attributes(self.col_position.get_cell_renderers()[0], visible=14)
					
				parent = self.users[user]
			else:
				parent = None
			# Add a new transfer
			
			path = self.frame.np.decode(transfer.path)
			iter = self.transfersmodel.append(parent, [user, shortfn, status, place, percent,  hsize, speed, elap, left, path, fn, istatus, size, icurrentbytes, True])
			
			# Expand path
			path = self.transfersmodel.get_path(iter)
			self.transfers.append([key, iter, transfer])
			if newparent:
				self.expandcollapse(self.transfersmodel.get_path(parent))

	def Clear(self):
		self.users.clear()
		self.transfers = []
		self.selected_transfers = []
		self.selected_users = []
		self.transfersmodel.clear()

	def OnCopyURL(self, widget):
		i = self.selected_transfers[0]
		self.frame.SetClipboardURL(i.user, i.filename)
	
	def OnCopyDirURL(self, widget):
		i = self.selected_transfers[0]
		path = string.join(i.filename.split("\\")[:-1], "\\") + "\\"
		if path[:-1] != "/":
			path += "/"
		self.frame.SetClipboardURL(i.user, path)
		
	def OnAbortTransfer(self, widget, remove = False, clear = False):
		transfers = self.selected_transfers
		for i in transfers:
			self.frame.np.transfers.AbortTransfer(i, remove)
			i.status = "Aborted"
			i.req = None
			if clear:
				for t in self.list[:]:
					if i.user == t.user and i.filename == t.filename:
						self.list.remove(t)
		self.update()

	def OnClearTransfer(self, widget):
		self.OnAbortTransfer(widget, False, True)

	def ClearTransfers(self, status):
		for i in self.list[:]:
			if i.status in status:
				if i.transfertimer is not None:
					i.transfertimer.cancel()
				self.list.remove(i)
		self.update()
		
	def OnClearFinished(self, widget):
		self.ClearTransfers(["Finished"])
	def OnClearAborted(self, widget):
		statuslist = ["Aborted","Cancelled"]
		self.ClearTransfers(statuslist)
	def OnClearFiltered(self, widget):
		statuslist = ["Filtered"]
		self.ClearTransfers(statuslist)
	def OnClearFailed(self, widget):
		statuslist = ["Cannot connect", 'Connection closed by peer', "Local file error", "Getting address", "Waiting for peer to connect", "Initializing transfer"]
		self.ClearTransfers(statuslist)
	def OnClearPaused(self, widget):
		statuslist = ["Paused"]
		self.ClearTransfers(statuslist)
	def OnClearFinishedAborted(self, widget):
		statuslist = ["Aborted","Cancelled", "Finished", "Filtered"]
		self.ClearTransfers(statuslist)
	def OnClearFinishedErred(self, widget):
		statuslist = ["Aborted","Cancelled", "Finished", "Filtered", "Cannot connect", 'Connection closed by peer', "Local file error"]
		self.ClearTransfers(statuslist)
	def OnClearQueued(self, widget):
		self.ClearTransfers(["Queued"])
