'''
This is the main file providing the application, the main window and a few
widget.

Author: Philippe Fremy
License: Gnu GPL (see fname LICENSE)
'''

from typing import Optional, Dict, List, Tuple
import sys, pathlib

from PyQt5.QtWidgets import (
    QMainWindow,
    QVBoxLayout,
    QLabel,
    QLCDNumber,
    QApplication,
    QMessageBox,
    QMenu,
    QHBoxLayout,
    QWidget,
    QSizePolicy,
)
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import Qt

from .kl_enum import *
from .kl_map import load_maps, KLMap
from .kl_board import KLBoard
from .kl_board_chooser import KLBoardChooser, KlMinimapProvider


def reverse_move(d: Tuple[int, int]) -> Tuple[int, int]:
    return (-d[0], -d[1])


class Klotski(QMainWindow):
    def __init__(self, maps: Dict[int, KLMap], firstBoard: Optional[int] = None) -> None:
        QMainWindow.__init__(self)
        self.klmap = None  # type: Optional[KLMap]
        self.moves = 0
        self.move_list = []  # type: List[ Tuple[str, Tuple[int, int]]]
        self.move_index = -1
        self.mini_maps_dict = {}  # type: Dict[int, QPixmap]
        self.levels_by_id = maps

        self.init_misc_gui()

        self.board.load_tiles()
        self.board.sig_move.connect(self.move_tile)

        self.generate_mini_maps()

        self.board_chooser = KLBoardChooser(KlMinimapProvider(self.levels_by_id, self.mini_maps_dict), self)
        self.board_chooser.hide()

        # needs self.board
        self.init_menu()

        self.setWindowTitle("Klotski")
        self.title_label.setText("Klotski")
        if firstBoard:
            self.new_level(self.levels_by_id[int(firstBoard)])
            self.move_enabled = True
        else:
            self.new_level(self.levels_by_id[ID_SPLASH_SCREEN])
            self.move_enabled = False

    def init_misc_gui(self) -> None:
        w = QWidget(self)
        self.setCentralWidget(w)
        ly = QVBoxLayout()
        w.setLayout(ly)
        ly.setSpacing(10)

        self.title_label = QLabel("Klotski", w)
        ft = self.title_label.font()
        ft.setPointSize(20)
        self.title_label.setFont(ft)
        self.title_label.setAlignment(Qt.AlignCenter)
        self.title_label.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        ly.addWidget(self.title_label)

        hb = QHBoxLayout()
        self.board = KLBoard(w)
        hb.addWidget(self.board)
        hb.setStretchFactor(self.board, 100)
        ly.addLayout(hb)

        # horizontal bottom hbox [ QLabel | QLcdNumber ]
        hb = QHBoxLayout()
        # hb.setSpacing( 5 )
        # hb.setMargin( 5 )
        ml = QLabel("Moves : ", w)
        ml.setFont(ft)
        ml.setAlignment(Qt.AlignRight)
        hb.addWidget(ml)

        self.move_lcd_nb = QLCDNumber(4, w)
        self.move_lcd_nb.adjustSize()
        self.move_lcd_nb.setFixedSize(self.move_lcd_nb.width(), 30)
        hb.addWidget(self.move_lcd_nb)
        # hb.setFixedHeight( hb.height() )
        ly.addLayout(hb)

        self.adjustSize()

    def generate_mini_maps(self) -> None:
        self.mini_maps_dict = {}
        for i in self.levels_by_id.keys():
            self.mini_maps_dict[i] = self.board.generate_mini_map(self.levels_by_id[i])

    def new_level(self, m: KLMap) -> None:
        '''Display a new map into the main window'''
        if m.name != NAME_SPLASH_SCREEN:
            # display the name of the board
            self.setWindowTitle("Klotski - " + m.name)
            self.title_label.setText(m.name)
        else:
            # display the splash screen
            self.setWindowTitle("Klotski")
            self.title_label.setText('')

        self.klmap = m
        w = max(m.w * TILE_SIZE + 10 + 40, self.title_label.sizeHint().width() + 20)
        h = (
            m.h * TILE_SIZE
            + 10
            + 80
            + self.title_label.sizeHint().height()
            + max(self.move_lcd_nb.sizeHint().height(), self.title_label.sizeHint().height())
        )

        center = self.frameGeometry().center()
        self.resize(w, h)

        r = self.frameGeometry()
        r.moveCenter(center)

        desktop = QApplication.desktop()
        if r.x() + w > desktop.width() or r.x() < 0 or r.y() + h > desktop.height() or r.y() < 0:

            r.moveCenter(desktop.rect().center())

        if r.x() < 0:
            r.setX(0)
        if r.y() < 0:
            r.setY(0)
        if r.x() + w > desktop.width():
            r.setX(0)
            w = desktop.width() - 20 - r.x()
        if r.y() + h > desktop.height():
            r.setY(0)
            h = desktop.height() - 20 - r.y()

        self.move(r.topLeft())
        self.resize(w, h)

        self.reset()

    def choose_board(self) -> None:
        self.board_chooser.exec()
        board_id = self.board_chooser.result()

        if board_id in self.levels_by_id:
            self.new_level(self.levels_by_id[board_id])

    def init_menu(self) -> None:
        file_menu = QMenu('Game', self)
        file_menu.addAction("Boards", self.choose_board, Qt.CTRL + Qt.Key_B)
        file_menu.addAction("Quit", self.close, Qt.CTRL + Qt.Key_X)  # type: ignore
        file_menu.addAction("About Klotski", self.about)

        move_menu = QMenu('Moves', self)
        move_menu.addAction("Reset", self.reset)
        move_menu.addAction("Undo", self.undo, Qt.CTRL + Qt.Key_U)
        move_menu.addAction("Redo", self.redo, Qt.CTRL + Qt.Key_R)

        main_menu = self.menuBar()
        main_menu.addMenu(file_menu)
        main_menu.addMenu(move_menu)

    def set_move_nb(self, m: int) -> None:
        self.move_lcd_nb.display(m)
        self.moves = m

    def move_tile(self, move_info: Tuple[str, Tuple[int, int]]) -> None:
        pid, delta = move_info
        if not self.move_enabled:
            return

        assert self.klmap
        self.klmap.move_piece(pid, delta)
        self.board.move_piece(pid, delta)

        if self.move_index < 0:
            self.move_list = [(pid, (0, 0))]
            self.move_index = 0
            self.set_move_nb(self.moves + 1)

        last_move_pid, last_move = self.move_list[self.move_index]

        if pid != last_move_pid:
            self.move_index = self.move_index + 1
            self.move_list[self.move_index:] = [(pid, delta)]
            self.set_move_nb(self.moves + 1)
        else:
            self.move_list[self.move_index:] = [(pid, (last_move[0] + delta[0], last_move[1] + delta[1]))]

        if self.klmap.is_game_won():
            QMessageBox.information(
                self, "Congratulation", "Congratulations!!!\nYou completed this level in %d moves" % self.moves
            )
            self.move_enabled = False
            self.klmap.reset()

    def reset(self) -> None:
        assert self.klmap
        self.klmap.reset()
        self.board.set_map(self.klmap)
        self.set_move_nb(0)
        self.move_enabled = True
        self.move_list = []
        self.move_index = -1

    def undo(self) -> None:
        if self.move_index < 0:
            return
        if not self.move_enabled:
            return

        pid = self.move_list[self.move_index][0]
        d = reverse_move(self.move_list[self.move_index][1])
        assert self.klmap
        assert self.board
        self.klmap.move_piece(pid, d)
        self.board.move_piece(pid, d)
        self.set_move_nb(self.moves - 1)

        self.move_index = self.move_index - 1

    def redo(self) -> None:
        if self.move_index + 1 >= len(self.move_list):
            return
        if not self.move_enabled:
            return

        self.move_index = self.move_index + 1
        pid, d = self.move_list[self.move_index]
        assert self.klmap
        assert self.board
        self.klmap.move_piece(pid, d)
        self.board.move_piece(pid, d)
        self.set_move_nb(self.moves + 1)

    def about(self) -> None:
        QMessageBox.about(self, 'About Klotski', MSG_ABOUT)

    def print_state(self) -> None:
        print(self.klmap)
        print("Move list : ", self.move_list)
        print("move list index : ", self.move_index)


def main() -> None:
    a = QApplication(sys.argv)
    klotski_icon = QIcon(QPixmap(str(pathlib.Path(__file__).parent / "klotski-icon.ico")))
    QApplication.setWindowIcon(klotski_icon)
    maps = load_maps(str(pathlib.Path(__file__).parent / "boards.kts"))

    firstBoard = None
    if len(sys.argv) > 1:
        firstBoardName = sys.argv[1]
        for m in maps.keys():
            if maps[m].name == firstBoardName:
                firstBoard = m
                break
        else:
            print("No such map: ", firstBoardName)
            print('Map List:')
            for m in maps:
                print(maps[m].name)

    klotski = Klotski(maps, firstBoard)
    klotski.setWindowIcon(klotski_icon)
    klotski.show()
    a.exec()
    del klotski


if __name__ == '__main__':
    main()
