import logging
import json
import inspect

from lib.config import Config
from lib.videomix import CompositeModes
from lib.response import NotifyResponse, OkResponse


def decodeName(items, name_or_id):
    try:
        name_or_id = int(name_or_id)
        if name_or_id < 0 or name_or_id >= len(items):
            raise IndexError("unknown index %d" % name_or_id)

        return name_or_id

    except ValueError as e:
        try:
            return items.index(name_or_id)

        except ValueError as e:
            raise IndexError("unknown name %s" % name_or_id)


def decodeEnumName(enum, name_or_id):
    try:
        name_or_id = int(name_or_id)
        if name_or_id < 0 or name_or_id >= len(enum):
            raise IndexError("unknown index %d" % name_or_id)

        return name_or_id

    except ValueError as e:
        try:
            return enum[name_or_id]

        except KeyError as e:
            raise IndexError("unknown name %s" % name_or_id)


def encodeName(items, id):
    try:
        return items[id]
    except IndexError as e:
        raise IndexError("unknown index %d" % id)


def encodeEnumName(enum, id):
    try:
        return enum(id).name
    except ValueError as e:
        raise IndexError("unknown index %d" % id)


class ControlServerCommands(object):

    def __init__(self, pipeline):
        self.log = logging.getLogger('ControlServerCommands')

        self.pipeline = pipeline

        self.sources = Config.getlist('mix', 'sources')
        self.blankerSources = Config.getlist('stream-blanker', 'sources')

    # Commands are defined below. Errors are sent to the clients by throwing
    # exceptions, they will be turned into messages outside.

    def message(self, *args):
        """sends a message through the control-server, which can be received by
        user-defined scripts. does not change the state of the voctocore."""
        return NotifyResponse('message', *args)

    def help(self):
        helplines = []

        helplines.append("Commands:")
        for name, func in ControlServerCommands.__dict__.items():
            if name[0] == '_':
                continue

            if not func.__code__:
                continue

            params = inspect.signature(func).parameters
            params = [str(info) for name, info in params.items()]
            params = ', '.join(params[1:])

            command_sig = '\t' + name

            if params:
                command_sig += ': ' + params

            if func.__doc__:
                command_sig += '\n\t\t{}\n'.format('\n\t\t'.join(
                    [line.strip() for line in func.__doc__.splitlines()]
                ))

            helplines.append(command_sig)

        helplines.append('\t' + 'quit / exit')

        helplines.append("\n")
        helplines.append("Source-Names:")
        for source in self.sources:
            helplines.append("\t" + source)

        helplines.append("\n")
        helplines.append("Stream-Blanker Sources-Names:")
        for source in self.blankerSources:
            helplines.append("\t" + source)

        helplines.append("\n")
        helplines.append("Composition-Modes:")
        for mode in CompositeModes:
            helplines.append("\t" + mode.name)

        return OkResponse("\n".join(helplines))

    def _get_video_status(self):
        a = encodeName(self.sources, self.pipeline.vmix.getVideoSourceA())
        b = encodeName(self.sources, self.pipeline.vmix.getVideoSourceB())
        return [a, b]

    def get_video(self):
        """gets the current video-status, consisting of the name of
           video-source A and video-source B"""
        status = self._get_video_status()
        return OkResponse('video_status', *status)

    def set_video_a(self, src_name_or_id):
        """sets the video-source A to the supplied source-name or source-id,
           swapping A and B if the supplied source is currently used as
           video-source B"""
        src_id = decodeName(self.sources, src_name_or_id)
        self.pipeline.vmix.setVideoSourceA(src_id)

        status = self._get_video_status()
        return NotifyResponse('video_status', *status)

    def set_video_b(self, src_name_or_id):
        """sets the video-source B to the supplied source-name or source-id,
           swapping A and B if the supplied source is currently used as
           video-source A"""
        src_id = decodeName(self.sources, src_name_or_id)
        self.pipeline.vmix.setVideoSourceB(src_id)

        status = self._get_video_status()
        return NotifyResponse('video_status', *status)

    def _get_audio_status(self):
        src_id = self.pipeline.amix.getAudioSource()
        return encodeName(self.sources, src_id)

    def get_audio(self):
        """gets the name of the current audio-source"""
        status = self._get_audio_status()
        return OkResponse('audio_status', status)

    def set_audio(self, src_name_or_id):
        """sets the audio-source to the supplied source-name or source-id"""
        src_id = decodeName(self.sources, src_name_or_id)
        self.pipeline.amix.setAudioSource(src_id)

        status = self._get_audio_status()
        return NotifyResponse('audio_status', status)

    def _get_composite_status(self):
        mode = self.pipeline.vmix.getCompositeMode()
        return encodeEnumName(CompositeModes, mode)

    def get_composite_mode(self):
        """gets the name of the current composite-mode"""
        status = self._get_composite_status()
        return OkResponse('composite_mode', status)

    def set_composite_mode(self, mode_name_or_id):
        """sets the name of the id of the composite-mode"""
        mode = decodeEnumName(CompositeModes, mode_name_or_id)
        self.pipeline.vmix.setCompositeMode(mode)

        composite_status = self._get_composite_status()
        video_status = self._get_video_status()
        return [
            NotifyResponse('composite_mode', composite_status),
            NotifyResponse('video_status', *video_status)
        ]

    def set_videos_and_composite(self, src_a_name_or_id, src_b_name_or_id,
                                 mode_name_or_id):
        """sets the A- and the B-source synchronously with the composition-mode
           all parametets can be set to "*" which will leave them unchanged."""
        if src_a_name_or_id != '*':
            src_a_id = decodeName(self.sources, src_a_name_or_id)
            self.pipeline.vmix.setVideoSourceA(src_a_id)

        if src_b_name_or_id != '*':
            src_b_id = decodeName(self.sources, src_b_name_or_id)
            self.pipeline.vmix.setVideoSourceB(src_b_id)

        if mode_name_or_id != '*':
            mode = decodeEnumName(CompositeModes, mode_name_or_id)
            self.pipeline.vmix.setCompositeMode(mode)

        composite_status = self._get_composite_status()
        video_status = self._get_video_status()

        return [
            NotifyResponse('composite_mode', composite_status),
            NotifyResponse('video_status', *video_status)
        ]

    def _get_stream_status(self):
        blankSource = self.pipeline.streamblanker.blankSource
        if blankSource is None:
            return ('live',)

        return 'blank', encodeName(self.blankerSources, blankSource)

    def get_stream_status(self):
        """gets the current streamblanker-status"""
        status = self._get_stream_status()
        return OkResponse('stream_status', *status)

    def set_stream_blank(self, source_name_or_id):
        """sets the streamblanker-status to blank with the specified
           blanker-source-name or -id"""
        src_id = decodeName(self.blankerSources, source_name_or_id)
        self.pipeline.streamblanker.setBlankSource(src_id)

        status = self._get_stream_status()
        return NotifyResponse('stream_status', *status)

    def set_stream_live(self):
        """sets the streamblanker-status to live"""
        self.pipeline.streamblanker.setBlankSource(None)

        status = self._get_stream_status()
        return NotifyResponse('stream_status', *status)

    def get_config(self):
        """returns the parsed server-config"""
        confdict = {header: dict(section)
                    for header, section in dict(Config).items()}
        return OkResponse('server_config', json.dumps(confdict))
