# The commands that control the player.

import time
import traceback

import player_presentation_errors
import player_protocol_definition
import signage_exceptions
import player_presentation_utils


class Command:
    # The base class for all commands.
    #
    # :ivar connection: A PlayerProtocolSlave that is used to send messages to
    #                   the server.
    # :ivar action_id: An arbitrary string that is used to identify this
    #                  command to the server.
    # :ivar command_params: A list of parameters to be passed to the command
    #                       when run.
    # :ivar runner: The QueueRunner that is running this command.
    # :ivar expected_params: Either an integer, if a specific number of
    #                        arguments is expected, or the string 'many' if a
    #                        variable number of arguments, greater than 1, is
    #                        expected.

    def __init__(self, connection, action_id, command_params):
        # Initialise a Command.
        #
        # :param connection: A PlayerProtocolSlave that can be used to send
        #                    messages to the server.
        # :param action_id: An arbitrary string that should be used to identify
        #                   this command to the sever.
        # :param command_params: A list of parameters to pass to the command
        #                        when run.
        self.connection = connection
        self.action_id = action_id
        self.command_params = command_params
        self.runner = None
        self.log = None

    def set_runner(self, runner):
        # Set the QueueRunner of the Command.
        self.runner = runner
        self.log = self.runner.log

    @player_presentation_utils.log_method
    def check_params(self):
        # Check that the correct number of parameters has been passed.
        num_params = len(self.command_params)
        if self.expected_params == 'many':
            if num_params > 1:
                return
        elif self.expected_params == num_params:
            return
        raise signage_exceptions.InvalidCommandParameters(
                '%d != %d' % (self.expected_params, num_params))

    @player_presentation_utils.log_method
    def run_cmd(self, *args):
        # Run the command, with the given arguments.
        raise player_presentation_errors.NotImplementedException()

    @player_presentation_utils.log_method
    def run(self):
        # A wrapper around the functionality of the command.
        try:
            self.check_params()
            self.run_cmd(*self.command_params)
        except signage_exceptions.InvalidCommandParameters:
            for line in traceback.format_exc().splitlines():
                self.log.debug(line)
            self.send_fatal_response('format', 'InvalidCommandParameters')
        except player_presentation_errors.IllegalArgumentException, err:
            for line in traceback.format_exc().splitlines():
                self.log.debug(line)
            self.send_failed_response('nomedia', str(err))
        except player_presentation_errors.VersionError, err:
            for line in traceback.format_exc().splitlines():
                self.log.debug(line)
            self.send_fatal_response('version', str(err))
        except player_presentation_errors.Version2Error, err:
            for line in traceback.format_exc().splitlines():
                self.log.debug(line)
            self.send_fatal_response('version2', str(err))
        except Exception, err:
            for line in traceback.format_exc().splitlines():
                self.log.debug(line)
            self.send_fatal_response('', str(err))

    @player_presentation_utils.log_method
    def send_failed_response(self, *args):
        # Send a 'failed' response to the server.
        self.connection.sendResponse(
            player_protocol_definition.MSG_EncodeFailedResponse(self.action_id,
                                                                args))

    @player_presentation_utils.log_method
    def send_fatal_response(self, *args):
        # Send a 'fatal' response to the server.
        self.connection.sendResponse(
            player_protocol_definition.MSG_EncodeFatalResponse(self.action_id,
                                                               args))
    @player_presentation_utils.log_method
    def send_flushed_response(self, *args):
        # Send a 'flushed' response to the server.
        self.connection.sendResponse(
            player_protocol_definition.MSG_EncodeFlushedResponse(self.action_id,
                                                                 args))

    @player_presentation_utils.log_method
    def send_started_response(self, *args):
        # Send a 'started' response to the server.
        self.connection.sendResponse(
            player_protocol_definition.MSG_EncodeStartedResponse(self.action_id,
                                                                 args))

    @player_presentation_utils.log_method
    def send_successful_response(self, *args):
        # Send a 'successful' response to the server.
        self.connection.sendResponse(
            player_protocol_definition.MSG_EncodeSucceededResponse(self.action_id,
                                                                   args))


class ImmediateCommand(Command):
    # A subclass of Command for commands that should be run immediately.

    pass


class Caps(Command):
    # Sends a list of capabilities to the server.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Sends a list of capabilities to the server.
        for cap in self.runner.caps:
            self.send_started_response(cap, *self.runner.caps[cap])
        for prop in self.runner.properties.get_supported_properties():
            self.send_started_response('property', prop)
        self.send_successful_response()


class Delay(Command):
    # Holds up the queue for a given amount of time.

    expected_params = 1

    @player_presentation_utils.log_method
    def run_cmd(self, delay_time):
        # Holds up the queue for a given amount of time.
        #
        # :param delay_time: The time (in seconds) that the queue should be held
        #                    up for.
        try:
            counter = int(delay_time)
        except ValueError:
            raise signage_exceptions.InvalidCommandParameters('not an integer')
        while (counter > 0 and self.runner.running_media is not None):
            time.sleep(1)
            counter -= 1
        self.send_successful_response()


class Flush(ImmediateCommand):
    # Flushes the queue and returns the player to a 'startable' state.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Flushes the queue and returns the player to a 'startable' state.
        while not self.runner.queue.empty():
            command = self.runner.queue.get()
            command.send_flushed_response()
        if self.runner.running_media is not None:
            self.runner.running_media.stop(flushed=True)
        self.send_successful_response()


class Hide(Command):
    # Hide the player.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Hide the player.
        self.runner.oo.set_visible(False)
        self.send_successful_response()


class Init(Command):
    # Start up OpenOffice.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Start up OpenOffice.
        self.runner.start_oo()
        self.send_successful_response()


class NoOp(Command):
    # Do nothing.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Do nothing.
        pass


class Poll(ImmediateCommand):
    # Let the server know that we're still here.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Let the server know that we're still here.
        self.send_successful_response()


class Property(Command):
    # Sets properties on the player.

    expected_params = 'many'

    @player_presentation_utils.log_method
    def run_cmd(self, prop, *args):
        # Sets properties on the player.
        #
        # :param prop: The name of the property to set.
        # :param args: The value(s) to set the property to.
        try:
            func = self.runner.properties.set_methods[prop]
        except KeyError:
            raise signage_exceptions.InvalidCommandParameters('')
        try:
            func(self.runner.properties, *args)
        except TypeError, err:
            raise signage_exceptions.InvalidCommandParameters(str(err))
        self.send_successful_response()


class Quit(Command):
    # Gracefully shut the player down.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Gracefully shut the player down.
        self.runner.stop()
        self.send_successful_response()


class Start(Command):
    # Starts playing media.
    #
    # :ivar presentation: The presentation currently playing, or None if one is
    #                     not playing.
    # :ivar stopped: A boolean indicating whether the command has been stopped.

    expected_params = 1

    def __init__(self, *args):
        Command.__init__(self, *args)
        self.presentation = None
        self.stopped = False

    @player_presentation_utils.log_method
    def run_cmd(self, target):
        # Starts playing media.
        #
        # :param target: The location of the media to be played.

        # Ensure we're allowed to be running
        if self.runner.running_media is not None:
            self.runner.running_media.stop()
        self.runner.running_media = self

        if not target.startswith('file:'):
            raise player_presentation_errors.IllegalArgumentException(target)

        url = 'file:///excised/tests/%s' % (target[5:],)
        print url

        # Try to get the presentation
        self.presentation = self.runner.oo.get_presentation(url)

        self.presentation.setPropertyValue('IsFullScreen', False)
        self.runner.oo.set_visible()
        # Start the presentation and return control to the QueueRunner
        self.presentation.start()

        # Ensure the presentation has started
        while not self.stopped and not self.presentation.isRunning():
            time.sleep(2)

        # Start moving through the slides
        controller = self.presentation.getController()

        # Set timings
        slide_time, effect_time = self.runner.properties.get_timing()
        slide_advance = self.runner.oo.makePropertyValue(
                            'AutomaticAdvancement',
                            slide_time)
        #effect_advance = self.runner.oo.makePropertyValue(
        #                    'AutomaticEffectAdvancement',
        #                    effect_time)
        controller.getSlideShow().setProperty(slide_advance)
        #controller.getSlideShow().setProperty(effect_advance)

        self.send_started_response()

    @player_presentation_utils.log_method
    def stop(self, flushed=False):
        # Stop playing the media.
        #
        # :param flushed: Indicates whether the media was stopped or flushed,
        #                so the correct response is sent.
        self.stopped = True
        if self.presentation is not None:
            self.presentation.end()

        # Let the server and QueueRunner know that we're done
        if flushed:
            self.send_flushed_response()
        else:
            self.send_successful_response()
        self.runner.running_media = None


class Stop(Command):
    # Stops the currently playing media.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Stops the currently playing media.
        if self.runner.running_media is not None:
            self.runner.running_media.stop()
        self.send_successful_response()


class Version(Command):
    # Set the protocol version to be used.

    expected_params = 1

    @player_presentation_utils.log_method
    def run_cmd(self, version):
        # Set the protocol version to be used.
        #
        # :param version: The version to be set.
        if self.runner.version is not None:
            if self.runner.version != version:
                raise player_presentation_errors.Version2Error('')
        else:
            if version in self.runner.caps['version']:
                self.runner.version = version
            else:
                raise player_presentation_errors.VersionError(
                          '%s not supported' % version)
        self.send_successful_response()


class Wait(Command):
    # Blocks the queue until the current presentation has finished.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Blocks the queue until the current presentation has finished.
        if self.runner.running_media is None:
            self.send_fatal_response(['No media currently playing'])
            return
        while self.runner.media_is_running():
            time.sleep(2)
        self.log.debug(self.runner.running_media.presentation)
        if self.runner.running_media is not None:
            self.runner.running_media.stop()
        self.send_successful_response()


class ZToFront(Command):
    # Place the player in front of all other windows.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Place the player in front of all other windows.
        self.runner.oo.to_front()
        self.send_successful_response()


class ZToBack(Command):
    # Place the player behind all other windows.

    expected_params = 0

    @player_presentation_utils.log_method
    def run_cmd(self):
        # Place the player behind all other windows.
        self.runner.oo.to_back()
        self.send_successful_response()


command_registry = {
       'caps': Caps,
       'delay': Delay,
       'flush': Flush,
       'hide': Hide,
       'init': Init,
       'poll': Poll,
       'property': Property,
       'quit': Quit,
       'start': Start,
       'stop': Stop,
       'version': Version,
       'wait': Wait,
       'z_to_back': ZToBack,
       'z_to_front': ZToFront,
}

