using Minesweeper.Commands;
using Minesweeper.Core;
using Minesweeper.Presentation.Factories;
using Minesweeper.Presentation.ReadModels;
using Minesweeper.Presentation.Views;

namespace Minesweeper.Presentation.Presenters
{
    public sealed class GamePresenter : IPresenter
    {
        private readonly IGameCommandDispatcher commandDispatcher;
        private readonly ICellViewFactory cellViewFactory;
        private readonly IGamePauseService pauseService;
        private readonly IGameReadModel readModel;
        private readonly IGameStateService gameStateService;
        private readonly TopPanelPresenter topPanelPresenter;
        private readonly IBoardView boardView;
        private readonly IPauseView pauseView;
        private readonly IResultView resultView;
        private bool boardBuilt;

        public GamePresenter(IGameCommandDispatcher commandDispatcher, ICellViewFactory cellViewFactory, IGamePauseService pauseService, IGameReadModel readModel, IGameStateService gameStateService, TopPanelPresenter topPanelPresenter, IBoardView boardView, IPauseView pauseView, IResultView resultView)
        {
            this.commandDispatcher = commandDispatcher;
            this.cellViewFactory = cellViewFactory;
            this.pauseService = pauseService;
            this.readModel = readModel;
            this.gameStateService = gameStateService;
            this.topPanelPresenter = topPanelPresenter;
            this.boardView = boardView;
            this.pauseView = pauseView;
            this.resultView = resultView;
        }

        public void Initialize()
        {
            boardView.CellPressStarted += OnCellPressStarted;
            boardView.CellPressEnded += OnCellPressEnded;
            boardView.PauseRequested += OnPauseRequested;
            boardView.CellOpenRequested += OnCellOpenRequested;
            boardView.CellFlagRequested += OnCellFlagRequested;
            pauseView.RestartRequested += OnRestartRequested;
            pauseView.ResumeRequested += OnResumeRequested;
            pauseView.GoToMenuRequested += OnGoToMenuRequested;
            resultView.RestartRequested += OnRestartRequested;
            resultView.GoToMenuRequested += OnGoToMenuRequested;
            gameStateService.StateChanged += OnStateChanged;
            pauseService.PauseChanged += OnPauseChanged;
            OnStateChanged(gameStateService.Current);
            OnPauseChanged(pauseService.IsPaused);
        }

        public void Dispose()
        {
            boardView.CellPressStarted -= OnCellPressStarted;
            boardView.CellPressEnded -= OnCellPressEnded;
            boardView.PauseRequested -= OnPauseRequested;
            boardView.CellOpenRequested -= OnCellOpenRequested;
            boardView.CellFlagRequested -= OnCellFlagRequested;
            pauseView.RestartRequested -= OnRestartRequested;
            pauseView.ResumeRequested -= OnResumeRequested;
            pauseView.GoToMenuRequested -= OnGoToMenuRequested;
            resultView.RestartRequested -= OnRestartRequested;
            resultView.GoToMenuRequested -= OnGoToMenuRequested;
            gameStateService.StateChanged -= OnStateChanged;
            pauseService.PauseChanged -= OnPauseChanged;
        }

        private void OnCellOpenRequested(int x, int y)
        {
            topPanelPresenter.SetCellPressActive(false);
            commandDispatcher.Dispatch(new OpenCellCommand(x, y));
            RefreshChangedCellsOrBoard();
            topPanelPresenter.RefreshCounters();
            UpdateBoardInput();
        }

        private void OnCellFlagRequested(int x, int y)
        {
            commandDispatcher.Dispatch(new ToggleFlagCommand(x, y));
            RefreshChangedCellsOrBoard();
            topPanelPresenter.RefreshCounters();
            UpdateBoardInput();
        }

        private void OnCellPressStarted()
        {
            topPanelPresenter.SetCellPressActive(true);
        }

        private void OnCellPressEnded()
        {
            topPanelPresenter.SetCellPressActive(false);
        }

        private void OnStateChanged(GameState state)
        {
            if (state == GameState.FieldSelection)
            {
                boardBuilt = false;
                boardView.Hide();
                pauseView.Hide();
                resultView.Hide();
                return;
            }

            boardView.Show();

            if (state == GameState.Won || state == GameState.Lost)
            {
                pauseView.Hide();
                resultView.Show(state);
            }
            else
            {
                resultView.Hide();
            }

            if (!boardBuilt || state == GameState.Preparing)
            {
                RebuildBoard();
            }
            else
            {
                RefreshBoard();
            }

            UpdateBoardInput();
        }

        private void OnPauseChanged(bool isPaused)
        {
            if (isPaused && CanPause(gameStateService.Current))
            {
                pauseView.Show();
            }
            else
            {
                pauseView.Hide();
            }

            UpdateBoardInput();
        }

        private void RebuildBoard()
        {
            var cells = readModel.GetCells();
            boardView.Rebuild(cells, readModel.Width, readModel.Height, cellViewFactory, IsFinalState());
            boardBuilt = true;
            topPanelPresenter.RefreshCounters();
            UpdateBoardInput();
        }

        private void RefreshBoard()
        {
            boardView.Refresh(readModel.GetCells(), IsFinalState());
        }

        private void RefreshChangedCellsOrBoard()
        {
            if (IsFinalState())
            {
                RefreshBoard();
                return;
            }

            boardView.RefreshCells(readModel.GetChangedCells(), false);
        }

        private bool IsFinalState()
        {
            var state = gameStateService.Current;
            return state == GameState.Lost || state == GameState.Won;
        }

        private void UpdateBoardInput()
        {
            var state = gameStateService.Current;
            boardView.SetInputEnabled(!pauseService.IsPaused && (state == GameState.Preparing || state == GameState.Playing));
        }

        private void OnRestartRequested()
        {
            commandDispatcher.Dispatch(new RestartCommand());
            RebuildBoard();
            topPanelPresenter.RefreshCounters();
        }

        private void OnGoToMenuRequested()
        {
            commandDispatcher.Dispatch(new GoToMenuCommand());
        }

        private void OnResumeRequested()
        {
            commandDispatcher.Dispatch(new ResumeCommand());
        }

        private void OnPauseRequested()
        {
            commandDispatcher.Dispatch(new PauseCommand());
        }

        private static bool CanPause(GameState state)
        {
            return state == GameState.Preparing || state == GameState.Playing;
        }
    }
}
