#!/usr/bin/env python

import policy
def get_config_dir():
    return policy.get_policy().root_path

from BitTorrent import platform
platform.get_config_dir = get_config_dir
platform.install_translation()

#from BitTorrent import Rerequester
#from BitTornado import bencode as _bencode
#bencode.bencode = _bencode.bencode
#bencode.bdecode = _bencode.bdecode
#Rerequester.bencode = _bencode.bencode
#Rerequester.bdecode = _bencode.bdecode

from BitTorrent import RawServer
try:
    basestring
except NameError:
    class dummy: pass
    RawServer.basestring = dummy

########################################################################
# Rerequester.Rerequester
########################################################################
from BitTorrent import Rerequester
from BitTorrent.zurllib import quote
from binascii import b2a_hex
from random import random, randrange
from socket import error, gethostbyname, gethostname
from BitTorrent.platform import bttime
from BitTorrent.btformats import check_peers

BTRerequester = Rerequester.Rerequester
class _Rerequester(BTRerequester):
    def _makeurl(self, peerid, port):
        url = ('info_hash=%s&peer_id=%s&port=%s&key=%s' %
                (quote(self.infohash), quote(peerid), str(port),
                 b2a_hex(''.join([chr(randrange(256)) for i in xrange(4)]))))
        try:
            url += '&local_ip=%s' % quote(gethostbyname(gethostname()))
        except:
            pass
        if self.baseurl.find('?') >= 0:
            c = '&'
        else: 
            c = '?'
        return '%s%s%s' % (self.baseurl,c,url)

    def _rerequest(self, url, peerid):
        get_thread().add_message(self.infohash,'ann: %s' % url)
        return BTRerequester._rerequest(self, url, peerid)

    def _postrequest(self, data=None, errormsg=None, peerid=None):
        self.current_started = None
        self.last_time = bttime()
        if errormsg is not None:
            self.errorfunc(WARNING, errormsg)
            self._fail()
            return
        try:
            r = bdecode(data)
            check_peers(r)
        except BTFailure, e:
            if data != '':
                self.errorfunc(ERROR, _("bad data from tracker - ") + str(e))
            self._fail()
            return
        if type(r.get('complete')) in (int, long) and \
           type(r.get('incomplete')) in (int, long):
            self.tracker_num_seeds = r['complete']
            self.tracker_num_peers = r['incomplete']
        else:
            self.tracker_num_seeds = self.tracker_num_peers = None
        if r.has_key('failure reason'):
            if self.howmany() > 0:
                self.errorfunc(ERROR, _("rejected by tracker - ") +
                               r['failure reason'])
            else:
                # sched shouldn't be strictly necessary
                def die():
                    self.diefunc(CRITICAL,
                                 _("Aborting the torrent as it was rejected by "
                                   "the tracker while not connected to any peers. ") +
                                 _(" Message from the tracker: ") + r['failure reason'])
                self.sched(die, 0)
            self._fail()
        else:
            self.fail_wait = None
            if r.has_key('warning message'):
                self.errorfunc(ERROR, _("warning from tracker - ") +
                               r['warning message'])
            self.announce_interval = r.get('interval', self.announce_interval)
            self.config['rerequest_interval'] = r.get('min interval',
                                            self.config['rerequest_interval'])
            self.trackerid = r.get('tracker id', self.trackerid)
            self.last = r.get('last')
            p = r['peers']
            peers = []
            if type(p) == str:
                for x in xrange(0, len(p), 6):
                    ip = '.'.join([str(ord(i)) for i in p[x:x+4]])
                    port = (ord(p[x+4]) << 8) | ord(p[x+5])
                    peers.append((ip, port, None))
            else:
                for x in p:
                    peers.append((x['ip'], x['port'], x.get('peer id')))
            ps = len(peers) + self.howmany()
            if ps < self.config['max_initiate']:
                if self.doneflag.isSet():
                    if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2:            
                        self.last = None
                else:
                    if r.get('num peers', 1000) > ps * 1.2:
                        self.last = None    
            thread = get_thread()
            if thread:
                thread.got_peers(self.infohash,peers)
            for x in peers:
                self.connect((x[0], x[1]), x[2])
            if peerid == self.wanted_peerid:
                self.successfunc()
            self._check()

Rerequester.Rerequester = _Rerequester

BTDHTRerequester = Rerequester.DHTRerequester
class _DHTRerequester(_Rerequester):
    def __init__(self, config, sched, howmany, connect, externalsched,
            amount_left, up, down, port, myid, infohash, errorfunc, doneflag,
            upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc, dht):
        self.dht = dht
        _Rerequester.__init__(self, "http://localhost/announce", config, sched,  howmany, connect, externalsched,
                             amount_left, up, down, port, myid, infohash, errorfunc, doneflag,
                             upratefunc, downratefunc, ever_got_incoming, diefunc, sfunc)

    def _announce(self, event=None):
        self.current_started = bttime()
        self._rerequest("", self.peerid)
 
    def _rerequest(self, url, peerid):
        self.peers = ""
        try:
            self.dht.getPeersAndAnnounce(self.infohash, self.port, self._got_peers)
        except Exception, e:
            self._postrequest(errormsg=_("Trackerless lookup failed: ") + str(e), peerid=self.wanted_peerid)

    def _got_peers(self, peers):
        if not self.howmany:
            return
        if not peers:
            self._postrequest(bencode({'peers':''}), peerid=self.wanted_peerid)
        else:
            self._postrequest(bencode({'peers':''.join(peers)}), peerid=None)

    def _announced_peers(self, nodes):
        pass
 
    def announce_stop(self):
        # don't do anything
        pass

Rerequester.DHTRerequester = _DHTRerequester

########################################################################
# download.SingleTorrent
########################################################################
from BitTorrent import download

download.Rerequester = _Rerequester
download.DHTRerequester = _DHTRerequester

BTSingleTorrent = download._SingleTorrent
class _SingleTorrent(BTSingleTorrent):
    def __init__(self,*args,**kw):
        BTSingleTorrent.__init__(self,*args,**kw)
        self.policy = policy.get_policy()

    def _make_id(self):
        pid = self.policy(policy.PEER_ID).lower()
        if pid == 'btq':
            self._make_btq()
        elif pid == 'az':
            self._make_az()
        elif pid.startswith('bc'):
            self._make_bc(pid[2:])
        elif pid == 'ml':
            self._make_bt()
        else:
            self._make_btq()
        return self.myid

    def _make_btq(self):
        from BitTorrent import PeerID
        ins = PeerID.make_id()[-3:]
        from __init__ import createPeerID
        self.myid = createPeerID(ins)

    def _make_bc(self,version='0062'):
        import random
        version = version[:4] or '0062'
        self.myid = '-BC%s-' % version
        while len(self.myid) < 20:
            self.myid += random.choice('0123456789')

    def _make_az(self):
        import random
        self.myid = '-AZ2400-'
        while len(self.myid) < 20:
            self.myid += random.choice('0123456789')

    def _make_bt(self):
        from BitTorrent import PeerID
        self.myid = PeerID.make_id()

    def reannounce(self):
        if self._rerequest._announce:
            self._rerequest._announce()

download._SingleTorrent = _SingleTorrent

########################################################################
# ConvertedMetainfo.ConvertedMetainfo
########################################################################
from BitTorrent import ConvertedMetainfo

BTConvertedMetainfo = ConvertedMetainfo.ConvertedMetainfo
class _ConvertedMetainfo(BTConvertedMetainfo):
    def __init__(self,*args,**kw):
        self.policy = policy.get_policy()
        BTConvertedMetainfo.__init__(self,*args,**kw)

    def _enforce_utf8(self, s):
        encodings = ['utf-8']+self.policy(policy.DEFAULT_ENCODING).split(',')+['latin1']
        for encoding in encodings:
            try:
                s = s.decode(encoding)
                break
            except:
                pass
        if type(s) == type(u''):
            s = s.encode('utf-8')
        return BTConvertedMetainfo._enforce_utf8(self,s)

ConvertedMetainfo.ConvertedMetainfo = _ConvertedMetainfo

from BitTorrent.bencode import bencode,bdecode
from BitTorrent import BTFailure,status_dict
from BitTorrent import configfile
from BitTorrent.defaultargs import get_defaults
from BitTorrent.IPC import ipc_interface
from BitTorrent.prefs import Preferences
from BitTorrent import TorrentQueue
from BitTorrent.ConvertedMetainfo import ConvertedMetainfo, set_filesystem_encoding
from BitTorrent.download import Feedback, Multitorrent
from BitTorrent import INFO, WARNING, ERROR, CRITICAL, status_dict

import copy
from random import seed,randrange
from socket import error as socketerror
from threading import Event
from sys import argv, exit
import sys, os
from __init__ import createPeerID, mapbase64, version
from threading import Event,Thread
from cStringIO import StringIO
import time

from queue import hours as fmttime
from i18n import *

try:
    True
except:
    True = 1
    False = 0


class Struct:
    pass


class global_logger:
    def __init__(self,logger=None):
        self.logger = logger

    def __call__(self, severity,msg):
        if self.logger:
            self.logger(severity,msg)
        else:
            sys.stderr.write("%s: %s\n" % (status_dict[severity],msg))


global_log_func = global_logger()

_global_thread = None
def set_thread(o):
    global _global_thread
    _global_thread = o

def get_thread():
    return _global_thread

class LaunchManyThread(Thread,Feedback):
    def __init__(self,output,args=[],banfunc=None,msgfunc=None,startflag=None):
        Thread.__init__(self)

        set_thread(self)

        self.startflag = startflag or Event()
        self.output = output
        self.output.set_controller(self)

        defaults = get_defaults('bittorrent')
        config,args = configfile.parse_configuration_and_args(defaults,'btqueue.py',args,0,None)
        self.config = Preferences().initWithDict(config)
        set_filesystem_encoding(self.config['filesystem_encoding'],self.global_error)
#        ui_options = []
#        ipc = ipc_interface(self.config,global_log_func)
#        ipc.create()

#        self.torrentqueue = TorrentQueue.TorrentQueue(self.config,ui_options,ipc)
#        self.torrentqueue_finished = self.torrentqueue.finished
#        self.torrentqueue.finished = self.hook_finished
#        self.wrappedqueue = TorrentQueue.ThreadWrappedQueue(self.torrentqueue)

        self.doneflag = Event()

        self.multitorrent = Multitorrent(self.config,self.doneflag,self.global_error,listen_fail_ok=True)

        self.banfunc = banfunc
        self.msgfunc = msgfunc
        self.policy = policy.get_policy()

        self.stats_period = self.config['display_interval']
        self.torrent_list = []
        self.downloads = {}
        self.counter = 0
        self.scheduler = None

        self.startqueue = []
        self.busyflag = Event()
        self.emptyflag = Event()
        self.removing_list = []
        self.removingflag = Event()
        self.starting_item = None

    def initialized(self):
        return 1

    def set_scheduler(self,scheduler):
        self.scheduler = scheduler

    def remove(self, item):
        if item in self.startqueue:
            self.startqueue.remove(item)
            self.torrent_list.remove(item.infohash_bin)
            try:
                del self.downloads[item]
            except TypeError:
                pass
            return

        try:
            self.removing_list.append(item)
            self.removingflag.set()
            self.multitorrent.rawserver.external_add_task(item.dow.shutdown,0)
            self.multitorrent.rawserver.external_add_task(self.terminated,0,args=(item.dow,))
        except (KeyError,AttributeError):
            pass

    def request_status(self, item, spew=1, file=1):
        try:
            status = item.dow.get_status(spew,file)
            self.run_task(self.update_status,item.infohash_bin,status)
        except AttributeError:
            pass
#        hash = item.infohash_bin
#        self.torrentqueue.request_status(hash,1,1)

    def add(self, item):
        hash,data = item.infohash_bin,item.get_metadata()
        pol = item.get_policy()

        try:
            item.metainfo = ConvertedMetainfo(item.get_meta())
        except BTFailure,why:
            self.output.exception('FAILURE: %s %s' % (item.id,item.title))
            self.output.exception(str(why))
            raise why

        self.torrent_list.append(hash)
        self.downloads[hash] = item

        name = item.metainfo.name_fs
        item.config = copy.copy(self.config)

        item.saveas = os.path.join(item.dest_path,item.handle_name(name))

        item.config['save_in'] = item.dest_path
        item.config['min_peers'] = pol(policy.MIN_PEER)
        item.config['max_initiate'] = pol(policy.MAX_INITIATE)
        item.config['rerequest_interval'] = pol(policy.REREQUEST_INTERVAL)
        item.config['snub_time'] = pol(policy.SNUB_TIME)
        item.config['socket_timeout'] = pol(policy.IDLE_TIMEOUT)
        item.config['timeout_check_interval'] = pol(policy.IDLE_TIMEOUT_CHECK_INTERVAL)
        item.config['start_trackerless_client'] = self.auto_dht(item)
        item.config['filesystem_encoding'] = pol(policy.FILESYSTEM_ENCODING)
        item.config['tracker_proxy'] = pol(policy.TRACKER_PROXY)

        self.start_check(item)

#        self.torrentqueue.start_new_torrent(data)
#        metainfo = self.torrentqueue.torrents[hash]
#        self.save_location(hash)

    def auto_dht(self, item):
        mode = item.get_policy().get(policy.ENABLE_DHT)
        if mode == 0:
            return 0
        elif mode == 2:
            if not item.metainfo.is_trackerless:
                item.metainfo.is_trackerless = 1
                item.metainfo.nodes = []
            return 1

        enable = 1
        meta = item.get_meta()

        if meta.has_key('nodes'):
            enable = 1
        if meta.has_key('azureus_properties'):
            azp = meta['azureus_properties']
            enable = azp.get('dht_backup_enable',1)
        if meta.has_key('info'):
            info = meta['info']
            enable = enable and not info.get('private',0)
        if item.announce.startswith('udp://'):
            enable = 2

        return enable

    def start_check(self, item):
        if self.busyflag.isSet():
            self.startqueue.append(item)
        else:
            self.busyflag.set()
            self._start(item)

    def _start(self, item):
        self.emptyflag.clear()
        self.starting_item = item
        item.dow = self.multitorrent.start_torrent(item.metainfo,item.config,self,item.saveas)

    def stop(self):
        for hash in self.torrent_list:
            item = self.downloads[hash]
            self.remove(item)
        if self.removingflag.isSet():
            self.emptyflag.wait()
        self.doneflag.set()

#    def save_location(self, hash):
#        metainfo = self.torrentqueue.torrents[hash].metainfo
#        name = metainfo.name_fs
#        path = self.config['save_in']
#        fullname = os.path.join(path,name)
#        self.got_location(hash,fullname)

#    def got_location(self, hash, fullpath):
#        self.torrentqueue.torrents[hash].dlpath = fullpath
#        self.torrentqueue.set_save_location(hash,fullpath)

#    def saveAs(self, hash, name, saveas, isdir):
#        item = self.downloads[hash]
##        if saveas:
##            saveas = os.path.join(saveas,x['file'][:-1-len(x['type'])])
##        else:
##            saveas = x['path'][:-1-len(x['type'])]
#        if item.saveas:
#            saveas = os.path.join(saveas,item.saveas)
#        if isdir or not item.saveas:
#            saveas = os.path.join(saveas,name)
#        if isdir and not os.path.isdir(saveas):
#            try:
#                os.makedirs(saveas)
#            except:
#                raise OSError("couldn't create directory for "+item.title)
#        return saveas

#    def died(self, hash):
#        try:
#            item = self.downloads[hash]
#            self.output.message('DIED: '+item.id)
#        except KeyError:
#            self.failed('%s has not been started properly' % hash)

    def run(self):
#        self.torrentqueue.run(self,self.run_task,self.startflag)
        self.multitorrent.rawserver.listen_forever()
        if self.doneflag.isSet():
            self.run_task(self.quit)
        self.multitorrent.close_listening_socket()

    def run_task(self,function,*args):
        return function(*args)

    def process_queue(self):
        if self.startqueue:
            item = self.startqueue.pop(0)
            self._start(item)
        else:
            self.busyflag.clear()

    def started(self,torrent):
        item = self.downloads.get(torrent.infohash,None)
        if not item:
            self.output.message('FAILURE: not found '+torrent.infohash)
            return
        self.output.message('started %s' % item.id)
        self.starting_item = None
        self.process_queue()

    def finished(self,torrent):
        item = self.downloads.get(torrent.infohash)
        if hasattr(item,'cb_finished'):
            item.cb_finished(item)
        item.state = STATE_SEEDING
        item.activity = None
        item.clear_stat()

    def terminated(self,torrent):
        item = self.downloads.get(torrent.infohash,None)
        try:
            self.torrent_list.remove(torrent.infohash)
        except ValueError:
            pass
        try:
            del self.downloads[torrent.infohash]
        except KeyError:
            pass
        if not self.torrent_list:
            self.emptyflag.set()
        if item:
            self.output.message('terminated %s' % item.id)
            self.removing_list.remove(item)
            if not self.removing_list:
                self.removingflag.clear()

    def failed(self,torrent,is_external=1):
        item = self.downloads.get(torrent.infohash)
        if not item:
            self.output.message('FAILURE: invalid item')
            return
        self.output.message('FAILURE: %s %s' % (item.id,item.title))

        if torrent.errors:
            def format_error(t,l,msg):
                return '%s %s %s' % (time.strftime('%Y/%m/%d %H:%M:%S'),status_dict[l],msg)
            self.output.message(' %s' % format_error(*torrent.errors[-1]))

        self.scheduler.pause(item)
        if item == self.starting_item:
            self.starting_item = None
            self.process_queue()

    def got_peers(self,infohash,peers):
        item = self.downloads.get(infohash,None)
        if item:
            peer_list = []
            for ip,port,peer_id in peers:
                peer_list.append({'ip':ip,'port':port,'id':peer_id})
            item.update_peers(peer_list)

    def add_message(self,infohash,msg):
        item = self.downloads.get(infohash,None)
        if item:
            self.msgfunc(item,msg)

    def exception(self,torrent,s):
        self.output.exception(str(s))

    def error(self,torrent,severity,text):
        item = self.downloads.get(torrent.infohash,None)
        if item:
            item.error = text

    def set_config(self,option,value):
        print 'set_config',option,value

    def global_error(self,severity,text):
        self.output.message('FAILURE: '+text)

#    def check_version(self):
#        print 'check_version'

#    def stop_queue(self):
#        print 'stop_queue'


#    def restart_queue(self):
#        print 'restart_queue'

    def update_status(self,torrent,statistics):
        item = self.downloads.get(torrent)
#        metainfo = self.torrentqueue.torrents.get(torrent)
        metainfo = item.metainfo
        if item:
            activity = statistics['activity']
            status = ''
            progress = statistics['fractionDone'] or 0.0

            t = 0
            if not item.dow.started:
                if item.dow._hashcheck_thread:
                    status = 'checking hash'
                else:
                    status = 'waiting for hash check'
            elif item.dow.is_seed:
                status = ''
            elif statistics.get('numSeeds',0)+statistics.get('numPeers',0) > 0:
                t = statistics.get('timeEst',None)
                if t is None:
                    t = -1 
                status = ''
            else:
                t = -1
                status = 'connecting to peers'
            dnrate = statistics.get('downRate',0.0)
            uprate = statistics.get('upRate',0.0)

            s = Struct()
            s.downTotal = statistics.get('downTotal',0)
            s.upTotal = statistics.get('upTotal',0)
            s.numSeeds = statistics.get('numSeeds',0)
            s.numPeers = statistics.get('numPeers',0)
            s.numCopies = statistics.get('numCopies',0)
            s.percentDone = statistics.get('fractionDone',0.0)
            s.torrentRate = reduce(lambda a,b:a+b,[i.get('speed',0) for i in statistics.get('spew',[])],0)

            spew = statistics.get('spew',None)
            if spew:
                for i in spew:
                    i['direction'] = i['initiation']
                    i['utotal'],i['uprate'],i['uinterested'],i['uchoked'] = i['upload']
                    i['dtotal'],i['downrate'],i['dinterested'],i['dchoked'],i['snubbed'] = i['download']

            item.update_info(fractionDone=progress,
                             timeEst=t,
                             downRate=dnrate,
                             upRate=uprate,
                             activity=status,
                             statistics=s,
                             spew=spew)

#    def new_displayed_torrent(self,infohash,metainfo,dlpath,state,config,
#                              completion=None,uptotal=0,downtotal=0):
#        print 'new_displayed_torrent',state,completion,uptotal,downtotal

#    def torrent_state_changed(self,infohash,dlpath,state,completion,
#                              uptotal,downtotal,queuepos=None):
#        print 'torrent_state_changed',state,completion,uptotal,downtotal

#    def reorder_torrent(self,infohash,queuepos):
#        print 'reorder_torrent'

#    def update_completion(self,infohash,completion,
#                          files_left=None,files_allocated=None):
#        print 'update_completion'

#    def removed_torrent(self,infohash):
#        print 'removed_torrent'

#    def change_torrent_state(self,infohash,newstate,index=None,replaced=None,force_running=False):
#        print 'change_torrent_state'

    def quit(self):
        self.output.message('internal queue stopped')

    def set_upload_rate(self,rate,item=None):
        if not item:
            pass
            #self.ratelimiter.set_upload_rate(rate)
        else:
            item.dow.setUploadRate(rate)

    def set_download_rate(self,rate,item=None):
        if not item:
            pass
            #for hash in self.torrent_list:
            #    item = self.downloads[hash]
            #    item.dow.setDownloadRate(rate)
        else:
            item.dow.setDownloadRate(rate)
