"""
A log destination for use by Twisted applications.

Runs in a thread, so that we don't do blocking I/O in the event loop thread.
"""

from __future__ import unicode_literals

import threading
import select

from twisted.application.service import Service
from twisted.internet.threads import deferToThreadPool
if getattr(select, "poll", None):
    from twisted.internet.pollreactor import PollReactor as Reactor
else:
    from twisted.internet.selectreactor import SelectReactor as Reactor

from eliot import addDestination, removeDestination


class ThreadedFileWriter(Service):
    """
    An Eliot log destination that writes log messages as lines to a file, using
    a managed thread.

    Unfortunately Python's Queue is not reentrant
    (http://bugs.python.org/issue14976) and neither is RLock
    (http://bugs.python.org/issue13697). In order to queue items in a thread we
    therefore rely on the self-pipe trick, and the easiest way to do that is by
    running another reactor in the thread.

    @ivar _reactor: A private reactor running in a thread which will do the log
        writes.

    @ivar _thread: C{None}, or a L{threading.Thread} running the private
        reactor.
    """
    name = u"Eliot Log Writer"


    def __init__(self, logFile, reactor):
        """
        @param logFile: A C{file}-like object that is at the end of its existing
           contents (e.g. opened with append mode) and accepts bytes.
        @type logFile: C{file}, or any file-like object with C{write}, C{flush}
            and C{close} methods e.g. a L{twisted.python.logfile.LogFile} if you
            want log rotation.

        @param reactor: The main reactor.
        """
        self._logFile = logFile
        self._reactor = Reactor()
        # Ick. See https://twistedmatrix.com/trac/ticket/6982 for real solution.
        self._reactor._registerAsIOThread = False
        self._mainReactor = reactor
        self._thread = None


    def startService(self):
        """
        Start the writer thread.
        """
        Service.startService(self)
        self._thread = threading.Thread(target=self._writer)
        self._thread.start()
        addDestination(self)


    def stopService(self):
        """
        Stop the writer thread, wait for it to finish.
        """
        Service.stopService(self)
        removeDestination(self)
        self._reactor.callFromThread(self._reactor.stop)
        return deferToThreadPool(
            self._mainReactor, self._mainReactor.getThreadPool(),
            self._thread.join)


    def __call__(self, data):
        """
        Write given bytes to the queue, to be written by the writer thread with a
        newline added.

        @param data: C{bytes} to write to disk.
        """
        self._reactor.callFromThread(self._logFile.write, data + b'\n')


    def _writer(self):
        """
        The function run by the writer thread.
        """
        self._reactor.run(installSignalHandlers=False)
        self._logFile.close()
