from tdl.runner.challenge_server_client import ChallengeServerClient, ClientErrorException, OtherCommunicationException, \
    ServerErrorException
from tdl.runner.recording_system import RecordingEvent
from tdl.runner.recording_system import RecordingSystem
from tdl.runner.round_management import RoundManagement

class ChallengeSession:

    @staticmethod
    def for_runner(runner):
        return ChallengeSession(runner)

    def __init__(self, runner):
        self._runner = runner
        self._config = None
        self._audit_stream = None
        self._user_input_callback = None
        self._recording_system = None
        self._challenge_server_client = None

    def with_config(self, config):
        self._config = config
        self._audit_stream = config.get_audit_stream()
        return self

    def with_action_provider(self, callback):
        self._user_input_callback = callback
        return self

    def start(self):
        self._recording_system = RecordingSystem(self._config.get_recording_system_should_be_on())

        if not self._recording_system.is_recording_system_ok():
            self._audit_stream.log('Please run `record_screen_and_upload` before continuing.')
            return

        self._audit_stream.log('Connecting to {}'.format(self._config.get_hostname()))
        self.run_app()

    def run_app(self):
        self._challenge_server_client = ChallengeServerClient(
            self._config.get_hostname(),
            self._config.get_port(),
            self._config.get_journey_id(),
            self._config.get_use_colours())

        try:
            journey_progress = self._challenge_server_client.get_journey_progress()
            self._audit_stream.log(journey_progress)

            available_actions = self._challenge_server_client.get_available_actions()
            self._audit_stream.log(available_actions)

            no_actions_available = 'No actions available.' in available_actions
            if no_actions_available:
                self._recording_system.tell_to_stop()
                return

            user_input = self._user_input_callback()
            self._audit_stream.log('Selected action is: {}'.format(user_input))
            if user_input == 'deploy':
                self._runner.run()
                last_fetched_round = RoundManagement.get_last_fetched_round(self._config.get_working_directory())
                self._recording_system.notify_event(last_fetched_round, RecordingEvent.ROUND_SOLUTION_DEPLOY)

            action_feedback = self._challenge_server_client.send_action(user_input)
            if 'Round time for' in str(action_feedback):
                last_fetched_round = RoundManagement.get_last_fetched_round(self._config.get_working_directory())
                self._recording_system.notify_event(last_fetched_round, RecordingEvent.ROUND_COMPLETED)

            if 'All challenges have been completed' in action_feedback:
                self._recording_system.tell_to_stop()

            self._audit_stream.log(action_feedback)
            round_description = self._challenge_server_client.get_round_description()
            round_description = round_description
            RoundManagement.save_description(
                self._recording_system,
                round_description,
                self._audit_stream,
                self._config.get_working_directory())

        except ClientErrorException as e:
            self._audit_stream.log(e)
        except ServerErrorException:
            self._audit_stream.log('Server experienced an error. Try again in a few minutes.')
        except OtherCommunicationException:
            self._audit_stream.log('Client threw an unexpected error. Try again.')

