class TUI(GameObserver, CursesMenuControllerObserver, GameControllerObserver):
    def __init__(self, curses_module=curses, logger=logger):
        super().__init__()

        self.curses_module = curses_module
        self.logger = logger

        self.row_count = None
        self.column_count = None
        self.board = None
        self.minefield_identifier = None
        self.most_recent_turn = None
        self.most_recent_board_snapshot = None
        self.create_minefield = None
        self.reveal_cell = None
        self.toggle_flag = None
        self.cursor_row = 0
        self.cursor_column = 0
        self.presentation_count = 0
        self.FOREGROUND_COLOR_KEY = 1
        self.BACKGROUND_COLOR_KEY = 2

        self.screen_controller = None
        self.game_controller = None

        self.menu_options = None
        self.present_create_board_options = None

    ####
    #
    # GameObserver
    #
    def game_did_present_start_menu(self,
                                    menu_options: MenuOptions,
                                    present_create_board_options: Callable[
                                        [PresentCreateBoardOptionsUseCaseObserver], None]):
        self.logger.info("Presenting start menu…")
        self.menu_options = menu_options
        self.present_create_board_options = present_create_board_options
        self.curses_module.wrapper(self._start)

    def game_did_present_create_board_options(self,
                                              minimum_row_count: int,
                                              minimum_column_count: int,
                                              default_row_count: int,
                                              default_column_count: int,
                                              create_board: Callable[[int, int, CreateBoardUseCaseObserver], None]):
        row_count = minimum_row_count
        column_count = minimum_column_count

        message = "At some point we will show a board options menu here, for now, just choose size {} x {}…"
        self.logger.info(message.format(row_count, column_count))
        self.logging_controller.refresh()

        create_board(row_count=row_count, column_count=column_count)

    def game_did_create_board(self,
                              board: Board,
                              board_snapshot: [[CellType]],
                              create_minefield: Callable[[int, int, CreateMinefieldUseCaseObserver], None]):
        self.create_minefield = create_minefield
        self.game_controller.prepare_to_create_minefield(board=board, board_snapshot=board_snapshot, observer=self)

    def game_did_create_minefield(self,
                                  minefield_identifier: UUID,
                                  first_turn: Turn,
                                  board_snapshot: [[CellType]],
                                  reveal_cell: Callable[[int, int, TakeTurnUseCaseObserver], None],
                                  toggle_flag: Callable[[int, int, TakeTurnUseCaseObserver], None]):
        self.logger.info("First cell revealed, mines placed, game on…")
        self.logging_controller.refresh()

        self.minefield_identifier = minefield_identifier
        self.most_recent_turn = first_turn
        self.reveal_cell = reveal_cell
        self.toggle_flag = toggle_flag
        self.game_controller.update_and_refresh(board_snapshot=board_snapshot)

    def game_did_take_turn(self,
                           turn: Turn,
                           game_state: GameState,
                           board_snapshot: [[CellType]],
                           reveal_cell: Callable[[int, int, TakeTurnUseCaseObserver], None],
                           toggle_flag: Callable[[int, int, TakeTurnUseCaseObserver], None]):
        if game_state == GameState.Won:
            message = "You won!  Congratulations."
        elif game_state == GameState.Lost:
            message = "Ruh-roh, looks like you stepped on a mine"
        else:
            message = "Took turn to {} on x: {}, y: {}".format(turn.action.name,
                                                               turn.coordinate.row_index,
                                                               turn.coordinate.column_index)
        self.logger.info(message)
        self.logging_controller.refresh()

        self.most_recent_turn = turn
        self.reveal_cell = reveal_cell
        self.toggle_flag = toggle_flag
        self.game_controller.update_and_refresh(board_snapshot=board_snapshot)

    ####
    #
    #     GameControllerObserver
    #
    def did_signal_intent_to_toggle_flag(self, controller, row_index, column_index):
        if self.toggle_flag:
            self.toggle_flag(column_index=column_index, row_index=row_index)

    def did_signal_intent_to_reveal_cell(self, controller, row_index, column_index):
        if self.reveal_cell:
            self.reveal_cell(column_index=column_index, row_index=row_index)
        elif self.create_minefield:
            self.create_minefield(column_index=column_index, row_index=row_index)

    def did_signal_intent_to_pause_game(self, controller):
        self.screen_controller.refresh()

    def did_update_cursor_location(self, controller):
        controller.refresh()

    ####
    #
    #     CursesMenuControllerObserver
    #
    def did_signal_intent_to_close_menu(self, controller):
        pass

    def did_select_menu_option_at_index(self, controller, menu_option_index: int):
        menu_option = self.menu_options.options[menu_option_index]

        if menu_option == StartMenuOptionType.StartNewGame:
            self.logger.info("Starting new game…")
            self.present_create_board_options()
            return True
        elif menu_option == StartMenuOptionType.Quit:
            self.logger.info("Quiting…")
            exit(1)

    def will_redisplay_updated_menu_options(self, controller):
        self._prepare_for_doupdate()

    def did_redisplay_updated_menu_options(self, controller):
        self.curses_module.doupdate()

    ####
    #
    # Private
    #
    def _start(self, screen):
        self.curses_module.noecho()
        self.curses_module.cbreak()
        self.curses_module.curs_set(0)
        if self.curses_module.has_colors():
            self.curses_module.start_color()

        screen_height, screen_width = screen.getmaxyx()

        start_menu_height = 20
        start_menu_width = 25
        start_menu_origin_y = 1
        start_menu_origin_x = 0
        start_menu_window = self.curses_module.newwin(start_menu_height, start_menu_width, start_menu_origin_y,
                                                      start_menu_origin_x)
        start_menu_window.keypad(1)

        data_source = StartMenuDataSource(menu_options=self.menu_options)
        start_menu_controller = CursesMenuController(keypress_listener=start_menu_window.getch,
                                                     window=start_menu_window,
                                                     data_source=data_source,
                                                     box=True)

        game_height = start_menu_height
        game_width = screen_width - start_menu_width
        game_origin_y = start_menu_origin_y
        game_origin_x = start_menu_origin_x + start_menu_width
        game_window = self.curses_module.newwin(game_height,
                                                game_width,
                                                game_origin_y,
                                                game_origin_x)
        game_window.keypad(1)

        self.curses_module.init_pair(self.FOREGROUND_COLOR_KEY,
                                     self.curses_module.COLOR_BLACK,
                                     self.curses_module.COLOR_RED)

        self.curses_module.init_pair(self.BACKGROUND_COLOR_KEY,
                                     self.curses_module.COLOR_WHITE,
                                     self.curses_module.COLOR_BLACK)

        highlighted_color_pair = self.curses_module.color_pair(self.FOREGROUND_COLOR_KEY)
        nonhighlighted_color_pair = self.curses_module.color_pair(self.BACKGROUND_COLOR_KEY)

        self.game_controller = GameController(window=game_window,
                                              keypress_listener=game_window.getch,
                                              highlighted_color_pair=highlighted_color_pair,
                                              nonhighlighted_color_pair=nonhighlighted_color_pair,
                                              logger=self.logger,
                                              box=True)

        logging_window_height = 6
        logging_window_width = screen_width
        logging_window = self.curses_module.newwin(logging_window_height,
                                                   logging_window_width,
                                                   start_menu_height + 1,
                                                   0)

        self.logging_controller = LineListController(window=logging_window, lines="", box=True)
        curses_logging_handler = CursesLoggingHandler(controller=self.logging_controller)
        self.logger.addHandler(curses_logging_handler)

        screen_controller_children = [start_menu_controller, self.game_controller, self.logging_controller]
        self.screen_controller = TitledController(title="MINESWEEPER",
                                                  window=screen,
                                                  child_controllers=screen_controller_children)
        self._prepare_for_doupdate()
        self.curses_module.doupdate()

        start_menu_controller.wait_and_listen_for_keypress(observer=self)

    def _prepare_for_doupdate(self):
        self.screen_controller.refresh()
    def _start(self, screen):
        self.curses_module.noecho()
        self.curses_module.cbreak()
        self.curses_module.curs_set(0)
        if self.curses_module.has_colors():
            self.curses_module.start_color()

        screen_height, screen_width = screen.getmaxyx()

        start_menu_height = 20
        start_menu_width = 25
        start_menu_origin_y = 1
        start_menu_origin_x = 0
        start_menu_window = self.curses_module.newwin(start_menu_height, start_menu_width, start_menu_origin_y,
                                                      start_menu_origin_x)
        start_menu_window.keypad(1)

        data_source = StartMenuDataSource(menu_options=self.menu_options)
        start_menu_controller = CursesMenuController(keypress_listener=start_menu_window.getch,
                                                     window=start_menu_window,
                                                     data_source=data_source,
                                                     box=True)

        game_height = start_menu_height
        game_width = screen_width - start_menu_width
        game_origin_y = start_menu_origin_y
        game_origin_x = start_menu_origin_x + start_menu_width
        game_window = self.curses_module.newwin(game_height,
                                                game_width,
                                                game_origin_y,
                                                game_origin_x)
        game_window.keypad(1)

        self.curses_module.init_pair(self.FOREGROUND_COLOR_KEY,
                                     self.curses_module.COLOR_BLACK,
                                     self.curses_module.COLOR_RED)

        self.curses_module.init_pair(self.BACKGROUND_COLOR_KEY,
                                     self.curses_module.COLOR_WHITE,
                                     self.curses_module.COLOR_BLACK)

        highlighted_color_pair = self.curses_module.color_pair(self.FOREGROUND_COLOR_KEY)
        nonhighlighted_color_pair = self.curses_module.color_pair(self.BACKGROUND_COLOR_KEY)

        self.game_controller = GameController(window=game_window,
                                              keypress_listener=game_window.getch,
                                              highlighted_color_pair=highlighted_color_pair,
                                              nonhighlighted_color_pair=nonhighlighted_color_pair,
                                              logger=self.logger,
                                              box=True)

        logging_window_height = 6
        logging_window_width = screen_width
        logging_window = self.curses_module.newwin(logging_window_height,
                                                   logging_window_width,
                                                   start_menu_height + 1,
                                                   0)

        self.logging_controller = LineListController(window=logging_window, lines="", box=True)
        curses_logging_handler = CursesLoggingHandler(controller=self.logging_controller)
        self.logger.addHandler(curses_logging_handler)

        screen_controller_children = [start_menu_controller, self.game_controller, self.logging_controller]
        self.screen_controller = TitledController(title="MINESWEEPER",
                                                  window=screen,
                                                  child_controllers=screen_controller_children)
        self._prepare_for_doupdate()
        self.curses_module.doupdate()

        start_menu_controller.wait_and_listen_for_keypress(observer=self)