"""
Tests for the video server.

Author(s): Arno Bakker
"""
import binascii
import os

from Tribler.Test.tools import trial_timeout
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks, Deferred
from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol
from twisted.internet.protocol import Protocol, connectionDone

from Tribler.Core.DownloadConfig import DownloadStartupConfig
from Tribler.Core.TorrentDef import TorrentDef
from Tribler.Core.Utilities.network_utils import get_random_port
from Tribler.Core.Video.VideoServer import VideoServer
from Tribler.Test.Core.base_test import MockObject, TriblerCoreTest
from Tribler.Test.common import TESTS_DATA_DIR
from Tribler.Test.test_as_server import TestAsServer


class VideoServerProtocol(Protocol):

    def __init__(self, finished, content_size, expected_content, setset, exp_byte_range):
        self.finished = finished
        self.content_size = content_size
        self.seen_empty_line = False
        self.has_header = False
        self.expected_content = expected_content
        self.setset = setset
        self.exp_byte_range = exp_byte_range

    def sendMessage(self, msg):
        self.transport.write("%s" % msg)

    def dataReceived(self, data):
        if not self.has_header:
            for line in data.split('\r\n'):
                if len(line) == 0 and self.seen_empty_line:
                    self.has_header = True
                elif len(line) == 0:
                    self.seen_empty_line = True
                else:
                    self.seen_empty_line = False
                    self.check_header(line)
        else:
            assert self.expected_content == data
            self.transport.loseConnection()

    def connectionLost(self, reason=connectionDone):
        self.finished.callback(None)

    def check_header(self, line):
        if line.startswith("HTTP"):
            if not self.setset:
                # Python returns "HTTP/1.0 206 Partial Content\r\n" HTTP 1.0???
                assert line.startswith("HTTP/1.")
                assert line.find("206") != -1  # Partial content
            else:
                assert line.startswith("HTTP/1.")
                assert line.find("416") != -1  # Requested Range Not Satisfiable
                self.transport.loseConnection()

        elif line.startswith("Content-Range:"):
            expline = "Content-Range: bytes " + TestVideoServerSession.create_range_str(
                self.exp_byte_range[0], self.exp_byte_range[1]) + "/" + str(self.content_size)
            assert expline == line

        elif line.startswith("Content-Type:") and not self.setset:
            # We do not check for an exact content-type since that might differ between platforms.
            assert line.startswith("Content-Type: video")

        elif line.startswith("Content-Length:"):
            assert line == "Content-Length: " + str(len(self.expected_content))


class TestVideoServer(TriblerCoreTest):

    def setUp(self):
        TriblerCoreTest.setUp(self)
        self.mock_session = MockObject()
        self.video_server = VideoServer(get_random_port(), self.mock_session)

    def test_get_vod_dest_dir(self):
        """
        Testing whether the right destination of a VOD download is returned
        """
        mock_download = MockObject()
        mock_download.get_content_dest = lambda: "abc"
        mock_download.get_selected_files = lambda: ["def"]
        mock_def = MockObject()
        mock_def.is_multifile_torrent = lambda: True
        mock_download.get_def = lambda: mock_def

        self.assertEqual(self.video_server.get_vod_destination(mock_download), os.path.join("abc", "def"))

    def test_get_vod_stream(self):
        """
        Testing whether the right VOD stream is returned
        """
        self.mock_session.get_download = lambda _: None
        self.assertEqual(self.video_server.get_vod_stream("abcd"), (None, None))


class TestVideoServerSession(TestAsServer):

    """
    Class for testing HTTP-based video server in a session.

    Mainly HTTP range queries.
    """
    @inlineCallbacks
    def setUp(self):
        """ unittest test setup code """
        yield super(TestVideoServerSession, self).setUp()
        self.port = self.session.config.get_video_server_port()
        self.sourcefn = os.path.join(TESTS_DATA_DIR, "video.avi")
        self.sourcesize = os.path.getsize(self.sourcefn)
        self.tdef = None
        self.expsize = 0
        yield self.start_vod_download()

    def setUpPreSession(self):
        TestAsServer.setUpPreSession(self)
        self.config.set_libtorrent_enabled(True)
        self.config.set_video_server_enabled(True)

    @trial_timeout(10)
    def test_specific_range(self):
        return self.range_check(115, 214)

    @trial_timeout(10)
    def test_last_100(self):
        return self.range_check(self.sourcesize - 100, None)

    @trial_timeout(10)
    def test_first_100(self):
        return self.range_check(None, 100)

    @trial_timeout(10)
    def test_combined(self):
        return self.range_check(115, 214, setset=True)

    def start_vod_download(self):
        self.tdef = TorrentDef()
        self.tdef.add_content(self.sourcefn)
        self.tdef.set_tracker("http://127.0.0.1:12/announce")
        self.tdef.finalize()

        dscfg = DownloadStartupConfig()
        dscfg.set_dest_dir(os.path.dirname(self.sourcefn))

        download = self.session.start_download_from_tdef(self.tdef, dscfg)
        return download.get_handle()

    def get_std_header(self):
        msg = "GET /%s/0 HTTP/1.1\r\n" % binascii.hexlify(self.tdef.get_infohash())
        msg += "Host: 127.0.0.1:" + str(self.port) + "\r\n"
        return msg

    @staticmethod
    def create_range_str(firstbyte, lastbyte):
        head = ""
        if firstbyte is not None:
            head += str(firstbyte)
        head += "-"
        if lastbyte is not None:
            head += str(lastbyte)

        return head

    def get_header(self, firstbyte, lastbyte, setset=False):
        head = self.get_std_header()

        head += "Range: bytes="
        head += self.create_range_str(firstbyte, lastbyte)
        if setset:
            # Make into set of byte ranges, VideoHTTPServer should refuse.
            head += ",0-99"
        head += "\r\n"

        head += "Connection: close\r\n"

        return head + "\r\n"

    def range_check(self, firstbyte, lastbyte, setset=False):
        test_deferred = Deferred()
        self._logger.debug("range_test: %s %s %s setset %s", firstbyte, lastbyte, self.sourcesize, setset)

        if firstbyte is not None and lastbyte is None:
            exp_byte_range = (firstbyte, self.sourcesize - 1)
        elif firstbyte is None and lastbyte is not None:
            exp_byte_range = (self.sourcesize - lastbyte, self.sourcesize - 1)
        else:
            exp_byte_range = (firstbyte, lastbyte)

        # the amount of bytes actually requested. (Content-length)
        self.expsize = exp_byte_range[1] - exp_byte_range[0] + 1
        f = open(self.sourcefn, "rb")
        f.seek(exp_byte_range[0])

        expdata = f.read(self.expsize)
        f.close()

        def on_connected(p):
            p.sendMessage(self.get_header(firstbyte, lastbyte, setset))

        endpoint = TCP4ClientEndpoint(reactor, "localhost", self.port)
        connectProtocol(endpoint, VideoServerProtocol(test_deferred, self.sourcesize, expdata, setset, exp_byte_range))\
            .addCallback(on_connected)
        return test_deferred
