#!/usr/bin/env python3

# Copyright (C) 2011-2012  Jordi Colomer <jordikolomer@gmail.com>
# Copyright (C) 2019-2020  Gleb Fotengauer-Malinovskiy <glebfm@altlinux.org>

# A script to download a torrent file and output its contents to stdout.

# 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 <https://www.gnu.org/licenses/>.

# SPDX-License-Identifier: GPL-3.0-or-later

# http://www.rasterbar.com/products/libtorrent/manual.html
import libtorrent as lt
import time
import sys
import subprocess
import _thread
import socket
import os
import urllib.request
import tempfile


strout = None
played = 0


def debug_format_piece_status(pieces, first, last):
    line = ''
    for i, piece in enumerate(pieces):
        if i >= first and i <= last:
            if piece:
                line = line + '1'
            else:
                line = line + '0'
    return line


def printstatus():
    status = handle.status()

    # if status.state == 4:
    #   break
    sys.stdout.flush()

    completed = status.pieces[piecestart:pieceend+1].count(True)
    progress = completed/(pieceend - piecestart + 1)

    # print(debug_format_piece_status(status.pieces, piecestart, pieceend), file = sys.stderr)
    line = 'completed: %.2f%% down: %.1f kb/s up: %.1f kB/s peers: %d seeds: %d' % (
        progress * 100, status.download_rate / 1000, status.upload_rate / 1000, status.num_peers, status.num_seeds)
    print(line, file=sys.stderr)


def addnewpieces():
    prio = handle.piece_priorities()
    status = handle.status()
    downloading = 0
    if len(status.pieces) == 0:
        return
    for piece in range(piecestart, pieceend+1):
        if prio[piece] != 0 and status.pieces[piece] == False:
            die2()
            downloading = downloading+1
    for piece in range(piecestart, pieceend+1):
        if prio[piece] == 0 and downloading < piecesperite:
            die2()
            # if piece < piecestart+100:
            #   print('downloading piece ', piece, file = sys.stderr)
            handle.piece_priority(piece, 1)
            downloading = downloading+1
    for piece in range(piecestart, pieceend+1):
        if prio[piece] != 0 and status.pieces[piece] == False:
            die2()
            # print('high prio ', piece, file = sys.stderr)
            # handle.piece_priority(piece, 7)
            break


kill = False
kill2 = False


def die():
    global kill
    if kill:
        # print('exiting thread 1', file = sys.stderr)
        kill = False
        _thread.exit()


def die2():
    global kill2
    if kill2:
        # print('exiting thread 2', file = sys.stderr)
        kill2 = False
        _thread.exit()


cache = {}


def getpiece(i):
    global cache
    if i in cache:
        ret = cache[i]
        cache[i] = 0
        return ret
    while True:
        status = handle.status()
        if len(status.pieces) == 0:
            break
        if status.pieces[i] == True:
            break
        time.sleep(.1)
        die()
    handle.read_piece(i)

    while True:
        buf = None
        got_piece = False
        for piece in ses.pop_alerts():
            if isinstance(piece, lt.read_piece_alert):
                got_piece = True
                if piece.piece == i:
                    buf = piece.buffer
                else:
                    cache[piece.piece] = piece.buffer

        if got_piece:
            return buf
        else:
            time.sleep(.1)
            die()


def runmplayer():
    time.sleep(1)
    # os.system("ffplay http://127.0.0.1:50008")
    os.system("player.bat")
    # os.system("vlc http://127.0.0.1:50008")
    # os.system("vlc play.m3u")
    # os.system("mplayer -fs http://127.0.0.1:50008")
    # os.system("D:/Python26/mplayer -cache 100 -fs http://127.0.0.1:50008")


completed = False


def writethread():
    global completed, played, output_process, kill
    stream = 0
    conn = 0
    for piece in range(piecestart, pieceend+1):
        buf = getpiece(piece)
        played = piece-piecestart
        if piece == piecestart:
            buf = buf[offset1:]
        if piece == pieceend:
            buf = buf[:offset2]
        if outputcmd == '-':
            stream = sys.stdout.buffer
        elif outputcmd == 'http':
            if conn == 0:
                HOST = '127.0.0.1'                 # Symbolic name meaning all available interfaces
                PORT = 50008              # Arbitrary non-privileged port
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.bind((HOST, PORT))
                sock.listen(1)
                # print "mplayer http://127.0.0.1:"+str(PORT)
                # os.system("mplayer http://127.0.0.1:"+str(PORT))
                # os.spawnl(os.P_NOWAIT, "mplayer http://127.0.0.1:"+str(PORT))
                _thread.start_new_thread(runmplayer, ())
                conn, addr = sock.accept()
                print('Connected by', addr)
                data = conn.recv(1024)
                print(data)
                # conn.send('HTTP/1.1 206 Partial Content\r\nContent-Type: video/mp4\r\n\r\n')
                conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: video/mp4\r\n\r\n')
                # conn.send('HTTP/1.1 200 OK\r\nContent-Type: video/x-msvideo\r\n\r\n')
                # conn.send('HTTP/1.1 206 Partial Content\r\nDate: Sun, 18 Sep 2011 13:34:12 GMT\r\nServer: Apache/2.2.6 (Unix) mod_ssl/2.2.6 OpenSSL/0.9.7a mod_bwlimited/1.4 FrontPage/5.0.2.2635 mod_auth_passthrough/2.1 PHP/5.2.4\r\nLast-Modified: Thu, 03 Mar 2005 21:18:36 GMT\r\nETag: "5814d8-663c88-2c3d2300"\r\nAccept-Ranges: bytes\r\nContent-Length: 6011062\r\nContent-Range: bytes 689106-6700167/6700168\r\nContent-Type: video/x-msvideo\r\n\r\n')

        else:
            if stream == 0:
                output_process = subprocess.Popen(outputcmd.split(' '), stdin=subprocess.PIPE)
                stream = output_process.stdin
        try:
            # if piece == piecestart+1:
            #    time.sleep(100)
            if stream != 0:
                stream.write(buf)
            if conn != 0:
                # print('played', piece, file = sys.stderr)
                # print('output', piece, len(buf), file = sys.stderr)
                r = conn.sendall(buf)
                # print('ret', r, file = sys.stderr)
        except Exception as err:
            ses.remove_torrent(handle)
            completed = True
            # print('exiting thread 1 (exception)', file = sys.stderr)
            kill = False
            kill2 = True
            # log.log('')
            _thread.exit()
            # exit(0)
        time.sleep(.1)
        die()
    ses.remove_torrent(handle)
    completed = True


def cancel():
    global kill, kill2
    # ses.abort()
    # ses.remove_torrent(handle)
    kill = True
    kill2 = True
    if output_process is not None:
        output_process.terminate()
    handle.pause()


ses = None


def start_session():
    global ses

    if ses is not None:
        return

    ses = lt.session()

    ses.start_dht()
    ses.add_dht_router("router.bittorrent.com", 6881)
    ses.add_dht_router("router.utorrent.com", 6881)
    ses.add_dht_router("router.bitcomet.com", 6881)
    ses.listen_on(6881, 6891)


def start(torrent, fileid, outdir, _outputcmd):
    global ses, handle, piecestart, pieceend, offset1, offset2, piecesperite, outputcmd, kill, kill2, output_process, completed, played
    played = 0
    kill = False
    kill2 = False
    output_process = None
    completed = False
    outputcmd = _outputcmd
    if torrent.startswith('magnet:?xt=urn:btih:'):
        start_session()
        handle = lt.add_magnet_uri(ses, torrent, {'save_path': outdir})
        while (not handle.has_metadata()):
            time.sleep(.1)
        info = handle.torrent_file()
    else:
        info = lt.torrent_info(torrent)
    piecesperite = 40*1024*1024//info.piece_length()  # 40 MB
    # print('piecesperite', piecesperite, file = sys.stderr)
    # print('info.piece_length()', info.piece_length(), file = sys.stderr)
    biggest_size, biggest_id = 0, 0
    for i, file_entry in enumerate(info.files()):
        piecestart = file_entry.offset // info.piece_length()
        pieceend = (file_entry.offset+file_entry.size) // info.piece_length()
        if file_entry.size > biggest_size:
            biggest_size, biggest_id = file_entry.size, i
        print(i, file_entry.path, file_entry.size, file_entry.offset,
              piecestart, pieceend, file=sys.stderr)
    if fileid == 'list':
        return
    if fileid == 'max':
        fileid = biggest_id
    else:
        fileid = int(fileid)

    file_entry = info.files().at(fileid)
    # print(file_entry.path, file = sys.stderr)
    piecestart = file_entry.offset // info.piece_length()
    pieceend = (file_entry.offset + file_entry.size) // info.piece_length()
    # how many bytes need to be removed from the 1st piece
    offset1 = file_entry.offset % info.piece_length()
    # how many bytes need we keep from the last piece
    offset2 = ((file_entry.offset + file_entry.size) % info.piece_length())
    # print(piecestart, pieceend, offset1, offset2, info.piece_length(), file = sys.stderr)
    # print((pieceend-piecestart+1)*info.piece_length()-(offset1+offset2), file_entry.size, file = sys.stderr)
    start_session()

    ses.set_alert_mask(lt.alert.category_t.storage_notification)
    handle = ses.add_torrent({'ti': info, 'save_path': outdir})
    for i in range(info.num_pieces()):
        handle.piece_priority(i, 0)
    # print('starting', handle.name(), file = sys.stderr)
    for i in range(piecestart, piecestart+piecesperite):
        if i <= pieceend:
            handle.piece_priority(i, 7)
            # print('downloading piece '+str(i), file = sys.stderr)
    handle.set_sequential_download(True)
    _thread.start_new_thread(writethread, ())
    while not completed:
        printstatus()
        addnewpieces()
        time.sleep(1)
        die2()
    # print('exiting thread 2 (end)', file = sys.stderr)


def main():
    if len(sys.argv) == 1:
        print('Usage: btcat TORRENTFILE|TORRENTURL [FILEID]')
        exit(0)
    torrent = sys.argv[1]
    fileid = 'list'
    # if len(sys.argv) > 2:
    #   fileid = int(sys.arSgv[2])
    outdir = '/tmp'
    # outdir = '/media/data/btcat'
    outputcmd = 'http'
    outputcmd = 'vlc -'
    outputcmd = 'mplayer -fs -'
    outputcmd = 'mplayer -aspect 16:9 -fs -'
    outputcmd = '-'
    # fileid = 'max'
    if len(sys.argv) > 2:
        fileid = sys.argv[2]
    if len(sys.argv) > 3:
        outdir = sys.argv[3]
    if len(sys.argv) > 4:
        outputcmd = sys.argv[4]
    if len(torrent) > 4 and torrent.startswith('http'):
        dest = tempfile.NamedTemporaryFile(prefix=outdir + '/')
        dest.write(urllib.request.urlopen(torrent).read())
        torrent = dest.name
    start(torrent, fileid, outdir, outputcmd)


if __name__ == "__main__":
    main()
