"""ci-dessous: tache 2"""
def solveMissingPuzzle(data):
    '''
    un 0 dans une cellule de data indique une valeur manquante. La fonction
    doit trouver la valeur correcte correspondante et la remplacer par celle-ci.
    '''

    #ECRIVEZ VOTRE CODE ICI

"""ci-dessus: tache 2"""




























########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
########Il n'est pas necessaire de lire ou de modifier le code sous cette ligne.##################
import sys
import math
import random
import os
import numpy as np
import OpenGL.GL as gl
from PyQt5 import (QtGui, QtCore, QtWidgets, QtTest)


def solveErrorPuzzle(data, n = 3): 
    print("Empty function.")


def verifyPuzzle(data, n = 3):
    for i in range(0, n*n):
        _tmp = [0] * (n*n + 1) 
        for j in range(0, n*n):
            _tmp[ data[i][j] ] += 1

        for k in range(1, n*n+1):
            if _tmp [ k ] == 0:
                print("Incorrect!")
                return

    for j in range(0, n*n):
        _tmp = [0] * (n*n + 1) 
        for i in range(0, n*n):
            _tmp[ data[i][j] ] += 1

        for k in range(1, n*n+1):
            if _tmp [ k ] == 0:
                print("Incorrect!")
                return

    for i in range(0, n):
        for j in range(0, n):
            _tmp = [0] * (n*n + 1) 
            for l in range(0, n):
                for m in range(0, n):
                    _tmp[ data[n * i + l][n * j + m] ] += 1

            for k in range(1, n*n+1):
                if _tmp [ k ] == 0:
                    print("Incorrect!")
                    return

    print("Correct!")


def printBoard(data, n = 3):
    print("Printing the rows:")
    for i in range(0, n*n):
        print(f"\tdata[{i}]:  ", data[i])

    print("Printing the individual entries:")
    for i in range(0, n*n):
        print("\t", end="")
        for j in range(0, n*n):
            print(f"data[{i}][{j}]:", data[i][j], end="  ")
        print()





class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        ##################################
        self.canvas = Canvas()
        ##################################

        ##################################
        global_panel = QtWidgets.QHBoxLayout()
        self.print_Button = QtWidgets.QPushButton("Print Board")
        self.print_Button.clicked.connect(lambda: printBoard (self.canvas.Sudoku.data, self.canvas.Sudoku.n))
        self.verify_Button = QtWidgets.QPushButton("Verify")
        self.verify_Button.clicked.connect(lambda: verifyPuzzle (self.canvas.Sudoku.data, self.canvas.Sudoku.n))
        self.clear_Button = QtWidgets.QPushButton("Clear")
        self.clear_Button.clicked.connect(self.canvas.drawSudoku)
        global_panel.addWidget(self.print_Button, 33)
        global_panel.addWidget(self.verify_Button, 33)
        global_panel.addWidget(self.clear_Button, 33)
        ##################################

        ##################################
        puzzles_panel = QtWidgets.QHBoxLayout()
        self.missingPuzzle_groupbox = QtWidgets.QGroupBox("Puzzle: Find missing numbers", checkable = True)
        self.missingPuzzle_groupbox.setChecked(False)
        self.missingPuzzle_groupbox.toggled.connect(self.missingPuzzle_groupbox_toggle)
        self.missingPuzzle_groupbox.setLayout(QtWidgets.QHBoxLayout())
        if self.missingPuzzle_groupbox:
            self.missingPuzzle_no_box = QtWidgets.QComboBox()
            self.missingPuzzle_no_box.addItems( [ str(i) for i in range(0, self.canvas.Sudoku.n * self.canvas.Sudoku.n * self.canvas.Sudoku.n ) ] )
            self.missingPuzzle_no_box.setCurrentIndex( 1 )
            self.missingPuzzle_no_box.installEventFilter(self)
            self.missingPuzzle_groupbox.layout().addWidget(self.missingPuzzle_no_box)

            self.missingPuzzle_generate_Button = QtWidgets.QPushButton("Generate")
            self.missingPuzzle_generate_Button.clicked.connect(self.generateMissingPuzzle)
            self.missingPuzzle_groupbox.layout().addWidget(self.missingPuzzle_generate_Button)

            self.missingPuzzle_solve_Button = QtWidgets.QPushButton("Solve!")
            self.missingPuzzle_solve_Button.clicked.connect(self.solveMissingPuzzle)
            self.missingPuzzle_groupbox.layout().addWidget(self.missingPuzzle_solve_Button)

        self.errorPuzzle_groupbox = QtWidgets.QGroupBox("Puzzle: Correct errors", checkable = True)
        self.errorPuzzle_groupbox.setChecked(False)
        self.errorPuzzle_groupbox.toggled.connect(self.errorPuzzle_groupbox_toggle)
        self.errorPuzzle_groupbox.setLayout(QtWidgets.QHBoxLayout())
        if self.errorPuzzle_groupbox:
            self.errorPuzzle_no_box = QtWidgets.QComboBox()
            self.errorPuzzle_no_box.addItems( [ str(i) for i in range(1, self.canvas.Sudoku.n * self.canvas.Sudoku.n ) ] )
            self.errorPuzzle_no_box.setCurrentIndex( 0 )
            self.errorPuzzle_no_box.installEventFilter(self)
            self.errorPuzzle_groupbox.layout().addWidget(self.errorPuzzle_no_box)

            self.errorPuzzle_generate_Button = QtWidgets.QPushButton("Generate")
            self.errorPuzzle_generate_Button.clicked.connect(self.generateErrorPuzzle)
            self.errorPuzzle_groupbox.layout().addWidget(self.errorPuzzle_generate_Button)
    
            self.errorPuzzle_solve_Button = QtWidgets.QPushButton("Solve!")
            self.errorPuzzle_solve_Button.clicked.connect(self.solveErrorPuzzle)
            self.errorPuzzle_groupbox.layout().addWidget(self.errorPuzzle_solve_Button)
        puzzles_panel.addWidget(self.missingPuzzle_groupbox, 50)
        puzzles_panel.addWidget(self.errorPuzzle_groupbox, 50)
        ##################################

        main = QtWidgets.QWidget()
        main.setLayout(QtWidgets.QVBoxLayout())
        main.setFocusPolicy(QtCore.Qt.StrongFocus)
        main.layout().addWidget(self.canvas)
        main.layout().addLayout(global_panel)
        main.layout().addLayout(puzzles_panel)

        QtWidgets.QShortcut(QtGui.QKeySequence("Escape"), self, activated=self.on_Escape)

        self.resize(400, 400)
        self.setCentralWidget(main)
        self.show()


    def eventFilter(self, widget, event):
        if (event.type() == QtCore.QEvent.KeyPress):
            self.event(event)
            return True
        return super(MainWindow, self).eventFilter(widget, event)


    def keyPressEvent(self, event):
        print("Key: ", QtGui.QKeySequence(event.key()).toString())
        if event.key() == QtCore.Qt.Key_Escape or event.key() == QtCore.Qt.Key_Q:
            quit()
            
        event.accept()
        self.update()


    def generateMissingPuzzle(self):
        self.canvas.Sudoku.generateMissingPuzzle( int( self.missingPuzzle_no_box.currentText() ) )
        self.canvas.drawSudoku()


    def solveMissingPuzzle(self):
        print("Solving missing puzzle...", end="")
        solveMissingPuzzle( self.canvas.Sudoku.data )
        print("done!")
        self.canvas.drawSudoku( )


    def missingPuzzle_groupbox_toggle(self, state):
        if state: 
            self.errorPuzzle_groupbox.setChecked(False)
            self.generateMissingPuzzle()


    def generateErrorPuzzle(self):
        self.canvas.Sudoku.generateErrorPuzzle( int( self.errorPuzzle_no_box.currentText() ) )
        self.canvas.drawSudoku()


    def solveErrorPuzzle(self):
        print("Solving error puzzle...", end="")
        solveErrorPuzzle( self.canvas.Sudoku.data, self.canvas.Sudoku.n )
        print("done!")
        self.canvas.drawSudoku( )


    def errorPuzzle_groupbox_toggle(self, state):
        if state: 
            self.missingPuzzle_groupbox.setChecked(False)
            self.generateErrorPuzzle()

    @QtCore.pyqtSlot()
    def on_Escape(self):
        quit()


class Canvas(QtWidgets.QLabel):
    def __init__(self):
        super().__init__()
        self.clear()

        self.Sudoku = mySudoku(3)
        self.drawSudoku()
        self.last_x, self.last_y = None, None


    def getGlobalCanvasRectangle(self):
        rect = self.contentsRect()
        pmRect = self.pixmap().rect()
        if rect != pmRect:
            align = self.alignment()
            if align & QtCore.Qt.AlignHCenter:
                pmRect.moveLeft( int((rect.width() - pmRect.width()) / 2))
            elif align & QtCore.Qt.AlignRight:
                pmRect.moveRight(rect.right())
            if align & QtCore.Qt.AlignVCenter:
                pmRect.moveTop( int((rect.height() - pmRect.height()) / 2))
            elif align &  QtCore.Qt.AlignBottom:
                pmRect.moveBottom(rect.bottom())
        #absolute bounding rectangle coordinates are stored in pmRect as topleft and bottomright points: pmRect.topLeft()  and pmRect.bottomRight()  
        return pmRect


    def resizeEvent(self, event):
        self.drawSudoku()


    def mousePressEvent(self, e):
        pmRect = self.getGlobalCanvasRectangle()

        _bigwidth = int ( (pmRect.topRight().x() - pmRect.topLeft().x())  / self.Sudoku.n ) 
        _bigheight = int ( (pmRect.bottomLeft().y() - pmRect.topLeft().y()) / self.Sudoku.n )
        _smallwidth = int( _bigwidth / self.Sudoku.n )
        _smallheight = int( _bigheight / self.Sudoku.n ) 

        _i = int( (e.x() - pmRect.topLeft().x()) / _smallwidth )
        _j = int( (e.y() - pmRect.topLeft().y()) / _smallheight )

        print("data[", _j, "][", _i, "] = ", self.Sudoku.data[_j][_i], sep='')


    def mouseMoveEvent(self, e):
        pmRect = self.getGlobalCanvasRectangle()

        if self.last_x is None:
            self.last_x = e.x()
            self.last_y = e.y()
            return
        
        painter = QtGui.QPainter(self.pixmap())
        painter.translate(-pmRect.topLeft())
        painter.setRenderHints(QtGui.QPainter.Antialiasing)

        p = QtGui.QPen(QtGui.QColor(0,100,0), 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
        p.setWidth(10)
        painter.setPen(p)
        painter.drawLine(self.last_x, self.last_y, e.x(), e.y())

        painter.end()

        self.last_x = e.x()
        self.last_y = e.y()

        self.update()


    def mouseReleaseEvent(self, e):
        self.last_x = None
        self.last_y = None


    def clear(self):
        pixmap = QtGui.QPixmap(self.width(), self.height())
        pixmap.fill(QtGui.QColor(225, 255, 255))
        self.setPixmap(pixmap)


    def drawSudoku(self):
        self.clear()
        pmRect = self.getGlobalCanvasRectangle()

        painter = QtGui.QPainter(self.pixmap())
        painter.translate(-pmRect.topLeft())
        painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
        painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
        painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)

        _pen = QtGui.QPen(QtGui.QColor(0,0,0), 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)
        painter.setPen(_pen)

        _bigwidth = int ( (pmRect.topRight().x() - pmRect.topLeft().x())  / self.Sudoku.n ) 
        _bigheight = int ( (pmRect.bottomLeft().y() - pmRect.topLeft().y()) / self.Sudoku.n )
        _smallwidth = int( _bigwidth / self.Sudoku.n )
        _smallheight = int( _bigheight / self.Sudoku.n ) 

        for i in range(0, self.Sudoku.n):
            for j in range(0, self.Sudoku.n):
                _startx = pmRect.topLeft().x() + i * _bigwidth
                _starty = pmRect.topLeft().y() + j * _bigheight

                _pen.setWidth(8)
                painter.setPen(_pen)
                painter.drawRect( _startx, _starty, _bigwidth, _bigheight )

                _pen.setWidth(2)
                painter.setPen(_pen)
                for k in range(0, self.Sudoku.n):
                    for l in range(0, self.Sudoku.n):
                        if (j*self.Sudoku.n+l, i*self.Sudoku.n+k) in self.Sudoku.missing:
                            painter.setBrush(QtGui.QColor(255, 200, 200, 100))
                        else:
                            painter.setBrush(QtGui.QColor(225, 255, 255))

                        painter.drawRect( _startx + k * _smallwidth, _starty + l * _smallheight, _smallwidth, _smallheight)


        _pen = QtGui.QPen(QtGui.QColor(0,0,100), 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)

        painter.setFont( QtGui.QFont('OldEnglish', pointSize = min ( int(_smallwidth/2), int(_smallheight/2) ), weight = QtGui.QFont.Light ) )
        painter.setFont( QtGui.QFont('SanSerif', pointSize = min ( int(_smallwidth/2), int(_smallheight/2) ), weight = QtGui.QFont.Light ) )
        painter.setFont( QtGui.QFont('Courier', pointSize = min ( int(_smallwidth/2), int(_smallheight/2) ), weight = QtGui.QFont.Light ) )

        _movex = int(_smallwidth/(3.5))
        _movey = -int(_smallheight/(4.0))

        for i in range(0, self.Sudoku.n):
            for j in range(0, self.Sudoku.n):
                _startx = pmRect.topLeft().x() + i * _bigwidth
                _starty = pmRect.topLeft().y() + j * _bigheight

                for k in range(0, self.Sudoku.n):
                    for l in range(0, self.Sudoku.n):
                        _pen.setColor(QtGui.QColor(0,0,200))
                        painter.setPen(_pen)

                        _str = str( self.Sudoku.data[j * self.Sudoku.n + l][i * self.Sudoku.n + k] )

                        if (j * self.Sudoku.n + l, i * self.Sudoku.n + k) in self.Sudoku.missing:
                            _str = "?"
                            if self.Sudoku.data[j * self.Sudoku.n + l][i * self.Sudoku.n + k] != 0: 
                                _str = _str + str( self.Sudoku.data[j * self.Sudoku.n + l][i * self.Sudoku.n + k] )
                            _pen.setColor(QtGui.QColor(200,0,0))
                            painter.setPen(_pen)

                        painter.drawText( _startx + k * _smallwidth + _movex, _starty + (l+1) * _smallheight + _movey, _str )

        painter.end()
        self.update()



class mySudoku:

    def __init__(self, size):
        self.n = size
        self.data = [ [0]*(self.n*self.n) for i in range(self.n*self.n) ]
        self.missing = []
        self.setRandom()


    def setRandom(self):
        self.missing = []
        n = self.n;
        rows = [g*n + r for g in random.sample(range(n), n) for r in random.sample(range(n),n) ]
        cols = [g*n + c for g in random.sample(range(n), n) for c in random.sample(range(n),n) ]
        nums = random.sample(range(1, n*n+1), n*n)
        self.data = [ [nums[(n*(r%n)+r//n+c)%(n*n)] for c in cols ] for r in rows ]


    def generateMissingPuzzle(self, _missno):
        self.setRandom()

        _indexset = [ (i, j) for i in range(self.n*self.n) for j in range(self.n*self.n) ]
        self.missing = random.sample(_indexset, _missno)
        for index in self.missing:
            self.data[index[0]][index[1]] = 0


    def generateErrorPuzzle(self, _missno):
        self.setRandom()

        _indexset = [ (i, j) for i in range(self.n*self.n) for j in range(self.n*self.n) ]
        A = random.sample(_indexset, _missno)
        for _pos in A:
            self.data[_pos[0]][_pos[1]] = random.randint(1, 9)



if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
