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 IGameTimerService timerService;
        private readonly TopPanelPresenter topPanelPresenter;
        private readonly IGameView view;
        private bool boardBuilt;

        public GamePresenter(IGameCommandDispatcher commandDispatcher, ICellViewFactory cellViewFactory, IGamePauseService pauseService, IGameReadModel readModel, IGameStateService gameStateService, IGameTimerService timerService, TopPanelPresenter topPanelPresenter, IGameView view = null)
        {
            this.commandDispatcher = commandDispatcher;
            this.cellViewFactory = cellViewFactory;
            this.pauseService = pauseService;
            this.readModel = readModel;
            this.gameStateService = gameStateService;
            this.timerService = timerService;
            this.topPanelPresenter = topPanelPresenter;
            this.view = view;
        }

        public void Initialize()
        {
            if (view != null)
            {
                view.RestartRequested += OnRestartRequested;
                view.GoToMenuRequested += OnGoToMenuRequested;
                view.PauseRequested += OnPauseRequested;
                view.ResumeRequested += OnResumeRequested;
                view.CellPressStarted += OnCellPressStarted;
                view.CellPressEnded += OnCellPressEnded;
                view.CellOpenRequested += OnCellOpenRequested;
                view.CellFlagRequested += OnCellFlagRequested;
                gameStateService.StateChanged += OnStateChanged;
                pauseService.PauseChanged += OnPauseChanged;
                timerService.TimeChanged += OnTimeChanged;
                OnStateChanged(gameStateService.Current);
                OnPauseChanged(pauseService.IsPaused);
                OnTimeChanged(timerService.ElapsedSeconds);
            }
        }

        public void Dispose()
        {
            if (view != null)
            {
                view.RestartRequested -= OnRestartRequested;
                view.GoToMenuRequested -= OnGoToMenuRequested;
                view.PauseRequested -= OnPauseRequested;
                view.ResumeRequested -= OnResumeRequested;
                view.CellPressStarted -= OnCellPressStarted;
                view.CellPressEnded -= OnCellPressEnded;
                view.CellOpenRequested -= OnCellOpenRequested;
                view.CellFlagRequested -= OnCellFlagRequested;
                gameStateService.StateChanged -= OnStateChanged;
                pauseService.PauseChanged -= OnPauseChanged;
                timerService.TimeChanged -= OnTimeChanged;
            }
        }

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

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

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

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

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

        private void OnCellFlagRequested(int x, int y)
        {
            commandDispatcher.Dispatch(new ToggleFlagCommand(x, y));
            RefreshBoard();
            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;
                view.HideGame();
                view.HidePause();
                view.HideResult();
                return;
            }

            view.ShowGame();

            if (state == GameState.Won || state == GameState.Lost)
            {
                view.HidePause();
                view.ShowResult(state);
            }
            else
            {
                view.HideResult();
            }

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

            UpdateBoardInput();
        }

        private void OnPauseChanged(bool isPaused)
        {
            if (isPaused && gameStateService.Current == GameState.Playing)
            {
                view.ShowPause();
            }
            else
            {
                view.HidePause();
            }

            UpdateBoardInput();
        }

        private void OnTimeChanged(float seconds)
        {
            view.SetTimer(seconds);
        }

        private void RebuildBoard()
        {
            var cells = readModel.GetCells();
            view.SetMineCount(readModel.MinesCount);
            view.RebuildBoard(cells, readModel.Width, readModel.Height, cellViewFactory);
            boardBuilt = true;
            topPanelPresenter.RefreshCounters();
            UpdateBoardInput();
        }

        private void RefreshBoard()
        {
            view.RefreshBoard(readModel.GetCells());
        }

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