#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import os
import itertools

import consts
from qt5 import QtWidgets, QtCore, QtGui, QtMultimedia
from settings import s, qsettings, settings
from block import Block
from tetromino import Tetromino
from grids import Grid, HoldQueue, NextQueue
from matrix import Matrix
from stats import Stats




class AspectRatioWidget(QtWidgets.QWidget):
    """
    Keeps aspect ratio of child widget on resize
    https://stackoverflow.com/questions/48043469/how-to-lock-aspect-ratio-while-resizing-the-window
    """

    def __init__(self, widget, parent):
        super().__init__(parent)
        self.aspect_ratio = widget.size().width() / widget.size().height()
        self.setLayout(QtWidgets.QBoxLayout(QtWidgets.QBoxLayout.LeftToRight, self))
        #  add spacer, then widget, then spacer
        self.layout().addItem(QtWidgets.QSpacerItem(0, 0))
        self.layout().addWidget(widget)
        self.layout().addItem(QtWidgets.QSpacerItem(0, 0))

    def resizeEvent(self, e):
        w = e.size().width()
        h = e.size().height()

        if w / h > self.aspect_ratio:  # too wide
            self.layout().setDirection(QtWidgets.QBoxLayout.LeftToRight)
            widget_stretch = h * self.aspect_ratio
            outer_stretch = (w - widget_stretch) / 2 + 0.5
        else:  # too tall
            self.layout().setDirection(QtWidgets.QBoxLayout.TopToBottom)
            widget_stretch = w / self.aspect_ratio
            outer_stretch = (h - widget_stretch) / 2 + 0.5

        self.layout().setStretch(0, outer_stretch)
        self.layout().setStretch(1, widget_stretch)
        self.layout().setStretch(2, outer_stretch)
        
        
class Frames(QtWidgets.QWidget):
    """
    Display Hold queue, Matrix, Next piece, Next queue and Stats.
    Manage interactions between them.
    """

    def __init__(self, parent):
        super().__init__(parent)
        self.setSizePolicy(
            QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
        )

        self.playing = False
        self.paused = False
        self.load_music()

        self.hold_queue = HoldQueue(self)
        self.matrix = Matrix(self)
        self.next_piece = Grid(self)
        self.stats = Stats(self)
        self.next_queue = NextQueue(self)

        self.matrices = (self.hold_queue, self.matrix, self.next_piece)
        self.columns = sum(matrix.COLUMNS + 2 for matrix in self.matrices)
        self.rows = self.matrix.ROWS + consts.GRID_INVISIBLE_ROWS

        w = QtWidgets.QWidget(self)
        w.setStyleSheet("background-color: transparent")
        grid = QtWidgets.QGridLayout()
        for x in range(self.rows):
            grid.setRowStretch(x, 1)
        for y in range(self.columns):
            grid.setColumnStretch(y, 1)
        grid.setSpacing(0)
        x, y = 0, 0
        grid.addWidget(
            self.hold_queue, y, x, self.hold_queue.ROWS + 1, self.hold_queue.COLUMNS + 2
        )
        x += self.hold_queue.COLUMNS + 2
        grid.addWidget(self.matrix, y, x, self.matrix.ROWS + consts.GRID_INVISIBLE_ROWS, self.matrix.COLUMNS + 2)
        x += self.matrix.COLUMNS + 3
        grid.addWidget(
            self.next_piece, y, x, self.next_piece.ROWS + 1, self.next_piece.COLUMNS + 2
        )
        x, y = 0, self.hold_queue.ROWS + 2
        grid.addWidget(self.stats, y, x, self.stats.ROWS, self.stats.COLUMNS + 1)
        x += self.stats.COLUMNS + self.matrix.COLUMNS + 5
        grid.addWidget(
            self.next_queue, y, x, self.next_queue.ROWS, self.next_queue.COLUMNS + 2
        )
        w.setLayout(grid)
        w.resize(self.columns, self.rows)
        asw = AspectRatioWidget(w, self)
        layout = QtWidgets.QGridLayout()
        layout.addWidget(asw)
        self.setLayout(layout)

        self.stats.temporary_text.connect(self.matrix.show_temporary_text)
        self.matrix.drop_signal.connect(self.stats.update_drop_score)
        self.matrix.lock_signal.connect(self.stats.update_score)
        
        self.set_background(os.path.join(consts.BG_IMAGE_DIR, consts.START_BG_IMAGE_NAME))
        
        self.apply_settings()

    def load_music(self):
        playlist = QtMultimedia.QMediaPlaylist(self)
        for entry in os.scandir(consts.MUSICS_DIR):
            path = os.path.join(consts.MUSICS_DIR, entry.name)
            url = QtCore.QUrl.fromLocalFile(path)
            music = QtMultimedia.QMediaContent(url)
            playlist.addMedia(music)
        playlist.setPlaybackMode(QtMultimedia.QMediaPlaylist.Loop)
        self.music = QtMultimedia.QMediaPlayer(self)
        self.music.setAudioRole(QtMultimedia.QAudio.GameRole)
        self.music.setPlaylist(playlist)
        self.music.setVolume(settings[s.SOUND][s.MUSIC_VOLUME])
        
    def apply_settings(self):
        if self.music.volume() > 5 and self.playing:
            self.music.play()
        else:
            self.music.pause()
            
        if self.playing:
            self.hold_enabled = settings[s.OTHER][s.HOLD_ENABLED]
            self.pause(False)
            
        self.matrix.keys = {
            getattr(QtCore.Qt, "Key_" + name): action
            for action, name in settings[s.KEYBOARD].items()
        }
        self.matrix.auto_repeat_timer.start(settings[s.DELAYS][s.AUTO_REPEAT_RATE])
        self.matrix.spotlight = Matrix.SPOTLIGHT
        
        for sfx in (
            self.matrix.rotate_sfx, self.matrix.wall_sfx,
            self.stats.line_clear_sfx, self.stats.tetris_sfx
        ):
            sfx.setVolume(settings[s.SOUND][s.SFX_VOLUME])

    def resizeEvent(self, event):
        Block.side = 0.9 * min(self.width() // self.columns, self.height() // self.rows)
        self.resize_bg_image()

    def reset_backgrounds(self):
        backgrounds_paths = (
            os.path.join(consts.BG_IMAGE_DIR, entry.name)
            for entry in os.scandir(consts.BG_IMAGE_DIR)
        )
        self.backgrounds_cycle = itertools.cycle(backgrounds_paths)

    def set_background(self, path):
        self.bg_image = QtGui.QImage(path)
        self.resize_bg_image()

    def resize_bg_image(self):
        self.resized_bg_image = QtGui.QPixmap.fromImage(self.bg_image)
        self.resized_bg_image = self.resized_bg_image.scaled(
            self.size(),
            QtCore.Qt.KeepAspectRatioByExpanding,
            QtCore.Qt.SmoothTransformation
        )
        self.resized_bg_image = self.resized_bg_image.copy(
            (self.resized_bg_image.width() - self.width()) // 2,
            (self.resized_bg_image.height() - self.height()) // 2,
            self.width(),
            self.height()
        )
        self.update()

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.drawPixmap(
            self.rect(),
            self.resized_bg_image)
        
    def new_game(self):
        if self.playing:
            answer = QtWidgets.QMessageBox.question(
                self,
                self.tr("New game"),
                self.tr("A game is in progress.\n" "Do you want to abord it?"),
                QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
                QtWidgets.QMessageBox.Cancel,
            )
            if answer == QtWidgets.QMessageBox.Cancel:
                self.pause(False)
                return
            self.music.stop()

        self.reset_backgrounds()
        self.stats.level, ok = QtWidgets.QInputDialog.getInt(
            self,
            self.tr("New game"),
            self.tr("Start level:"),
            1,
            1,
            15,
            flags=QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint,
        )
        if not ok:
            return

        self.playing = True
        self.load_music()
        self.music.play()
        self.hold_queue.piece = None
        self.stats.new_game()
        self.matrix.new_game()
        self.next_queue.pieces = [Tetromino() for _ in range(5)]
        self.next_queue.insert_pieces()
        self.next_piece.insert(Tetromino())
        self.pause(False)
        self.new_level()
        self.new_piece()

    def new_level(self):
        self.set_background(next(self.backgrounds_cycle))
        level = self.stats.new_level()
        self.matrix.new_level(level)

    def new_piece(self):
        if self.stats.goal <= 0:
            self.new_level()
        self.matrix.insert(self.next_piece.piece)
        self.matrix.lock_wait()
        self.next_piece.insert(self.next_queue.pieces[0])
        self.next_queue.new_piece()
        self.hold_enabled = settings[s.OTHER][s.HOLD_ENABLED]
        self.update()

        if not self.matrix.piece.move(0, 0):
            self.game_over()
            return

        self.matrix.fall_timer.start(self.matrix.speed)

    def pause(self, paused):
        if not self.playing:
            return

        if paused:
            self.paused = True
            self.update()
            self.matrix.fall_timer.stop()
            self.stats.clock.stop()
            self.matrix.auto_repeat_timer.stop()
            self.music.pause()
        else:
            self.matrix.text = ""
            self.update()
            QtCore.QTimer.singleShot(1000, self.resume)

    def resume(self):
        self.paused = False
        self.update()
        self.matrix.fall_timer.start(self.matrix.speed)
        self.stats.clock.start(1000)
        self.matrix.auto_repeat_timer.start(settings[s.DELAYS][s.AUTO_REPEAT_RATE])
        self.music.play()

    def hold(self):
        """
        Using the Hold command places the Tetrimino in play into the Hold Queue.
        The previously held Tetrimino (if one exists) will then start falling
        from the top of the Matrix,
        beginning from its generation position and North Facing orientation.
        Only one Tetrimino may be held at a time.
        A Lock Down must take place between Holds.
        For example, at the beginning, the first Tetrimino is generated and begins to fall.
        The player decides to hold this Tetrimino.
        Immediately the Next Tetrimino is generated from the Next Queue and begins to fall.
        The player must first Lock Down this Tetrimino before holding another Tetrimino.
        In other words, you may not Hold the same Tetrimino more than once.
        """

        if not self.hold_enabled:
            return

        piece = self.hold_queue.piece
        if piece:
            self.hold_queue.insert(self.matrix.piece)
            self.matrix.insert(piece)
            self.update()
        else:
            self.hold_queue.insert(self.matrix.piece)
            self.new_piece()
        self.hold_enabled = False

    def game_over(self):
        self.matrix.fall_timer.stop()
        self.stats.clock.stop()
        self.matrix.auto_repeat_timer.stop()
        self.music.stop()
        self.playing = False
        self.matrix.game_over = True
        msgbox = QtWidgets.QMessageBox(self)
        msgbox.setWindowTitle(self.tr("Game over"))
        msgbox.setIcon(QtWidgets.QMessageBox.Information)
        if self.stats.score_total == self.stats.high_score:
            msgbox.setText(
                self.tr(
                    "Congratulations!\nYou have the high score: {}"
                ).format(
                    
                    locale.format("%i", self.stats.high_score, grouping=True, monetary=True)
                )
            )
            qsettings.setValue(self.tr("High score"), self.stats.high_score)
        else:
            msgbox.setText(
                self.tr(
                    "Score: {}\nHigh score: {}"
                ).format(
                    locale.format("%i", self.stats.score_total, grouping=True, monetary=True),
                    locale.format("%i", self.stats.high_score, grouping=True, monetary=True)
                )
            )
        msgbox.setDetailedText(self.stats.text(full_stats=True))
        msgbox.exec_()