#!/usr/bin/env python

# mcpipy.com retrieved from URL below, written by Davie Wales
# https://bitbucket.org/dwales/minesweeper-for-minecraft-pi-edition/src


import sys
import random
import threading
import mcpi.minecraft as minecraft
import server


defaultDifficulty = 0.1
setDifficulty = defaultDifficulty

class board:
    """ The cartesian grid can be done as follows:
    board = [["?", "*", "?", "?", "*", "?", "?", "?", "?", "*",], 
            ["?", "?", "*", "?", "?", "?", "?", "?", "?", "?",], 
            ["?", "*", "*", "?", "*", "?", "?", "*", "*", "?",], 
            ["?", "?", "?", "?", "?", "?", "*", "?", "*", "?",],
            ["*", "?", "?", "?", "?", "*", "?", "?", "?", "?",], 
            ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?",], 
            ["?", "*", "?", "*", "?", "?", "?", "*", "?", "*",], 
            ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?",],
            ["?", "*", "?", "?", "*", "*", "?", "?", "*", "?",], 
            ["?", "?", "*", "?", "?", "?", "?", "*", "?", "?",]]

     Obviously the grid will be randomly generated on run to fit 
     within the bounds of the terminal window.
     Notice that you can access the state of any tile on the grid with
     the simple command "board[x][y]"
     i.e. a = board[0][0] makes a == "?", a = board[0][9] makes a == "*"
     NOTE: I subsequently changed the code to use nested dictionaries,
     rather than lists to represent the board. The general idea is
     still the same...
     If we use a dictionary rather than a list in the following
     function, we will get a KeyError if we try to access a 
     negative index, assuming we construct the dictionary such
     that it has identical indexes to the list equivalent.
     i.e. dictionary = {0:{0:" ", 1:" ", 2"*"}, 1:{0:"*", 1:" ", 2:" "}}
     This will be helpful, as it will negate the need to explicitly
     check whether a particular coordinate is legitimate. i.e. a 
     dictionary won't match negative values for x, y, but a list will..."""

    """ At the moment we are getting the window size before curses is
     initialised, which means that we have to use "stty size". If 
     we can move this code into the curses section, we can use the
     built in window.getmaxyx(). This will make it easier to use
     windows smaller than the size of the terminal for the game, 
     which will in turn allow us to add timers and minecounts."""

    def __init__(self):
        global width, height
        width = 10
        height = 10

    def options(self):
        totalTiles = width * height

        #possible choices of tile: either "*" or " "
        self.mineNumber = int(setDifficulty * totalTiles)
        choices = list(self.mineNumber * "*")
        choices.extend(list((totalTiles-len(choices))*" "))
        return choices

    # For every x and y, check all the squares around to see if there is a mine,
    # add together the number of mines touching the original square and replace
    # the original square with the final count.
    def numberise(self, board):
        for x in range(width):
            for y in range(height):
                count = 0
                if board[x][y] != "*":
                        for i in range(-1, 2):
                            for n in range(-1, 2):
                                try:
                                    if board[x+i][y+n] == "*":
                                        count += 1
                                except KeyError:
                                    pass
                        if count != 0:
                            board[x][y] = str(count)

    def create(self):
        self.mineCoords = []
        choices = self.options()
        board = {}
        for i in range(0, width):
            board[i] = {}
            for n in range(0, height):
                board[i][n] = choices.pop(choices.index(random.choice(choices)))
                if board[i][n] == "*":
                    self.mineCoords.append([i,n])

        self.numberise(board)
        for i in range(width):
            for n in range(height):
                minecraftAddBlock(i, n, 1, board[i][n])

        return board

    def visibleScreen(self):
        board = {}
        for i in range(0, width):
            board[i] = {}
            for n in range(0, height):
                board[i][n] = " "
        return board

def minecraftAddBlock(X, Y, Z, mineAttribute):
    # This equates values passed through mineAttribute with the actual
    # block IDs from Minecraft.
    # 0 is Air, 5 is Wood Planks, 4 is cobblestone, coal is 16
    # Iron is 15, Gold is 14, Diamond is 56, Gold Block is 41, 
    # Diamond Block is 57
    mineDict = {"dirt":3, "*":46, " ":20, 0:0, "1":5, "2":4, "3":16, "4":15, "5":14, "6":56, "7":41, "8":57}
    mc.setBlock(X, Y, Z, mineDict[mineAttribute])

def explore(x, y, Z): # Z is capitalised because it doesn't
                              # need to be changed by the function.
    """ This is the bit that happens when you click on a blank square
     First it checks the squares directly around the clicked square
     If the square it checks is a number, it will display it, and
     if the square it checks is blank, it will add the blank square's
     coordinates to a list or dictionary, then it will keep doing the
     same process to all the coordinates in the list, deleting squares
     that have been checked, and adding new squares, until the list is
     empty. At that point, the area around the original square will be
     revealed, as you would expect to happen in minesweeper."""

    checked = [[x,y]]       # Has been checked and contains either a number or ' '
    toBeCentre = [[x, y]]   # Each point in this list will be checked on all sides for the above conditions
    centred = []            # These points have already been checked on all sides
    global cleared
    cleared = []

    while len(toBeCentre) > 0:
        X, Y = toBeCentre.pop(0)
        centred.append([X,Y])
        minecraftAddBlock(X, Y, Z, 0)
        if [X,Y] not in cleared:
            cleared.append([X,Y])
        for i in range(-1, 2):
            for n in range(-1, 2):

        # When I was writing this section, it wouldn't work, and wouldn't work
        # and then after changing it around a million times, suddenly it started working...
        # The only problem is that I don't actually know what I did to make it work... =P
                try:
                    if ((newBoard[X+i][Y+n] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])and ([X+i, Y+n] not in checked)):
                        minecraftAddBlock(X+i, Y+n, Z, 0) #newBoard[X+i][Y+n])
                        checked.append([X+i, Y+n])
                        if [X+i,Y+n] not in cleared:
                            cleared.append([X+i,Y+n])

                    elif newBoard[X+i][Y+n] == " ":
                        if (([X+i, Y+n] not in checked) and ([X+i, Y+n] not in toBeCentre)):
                            toBeCentre.append([X+i, Y+n])
                            checked.append([X+i, Y+n])
                except KeyError:
                    pass

class WinningCheckThread (threading.Thread):

    def __init__(self,  mineCoords, mineNumber, z):
        self.mineCoords = mineCoords
        self.mineNumber = mineNumber
        self.z = z
        threading.Thread.__init__(self)

    def run(self):
        global running
        running = True
        mc = minecraft.Minecraft.create()
        while running:
            ### This is the winning condition... ###
            flagCount = 0
            correctFlagCount = 0

            for x in range(width):
                for y in range(height):
                    if mc.getBlock(x, y, 0-1) == 50:
                        flagCount += 1
                        if [x,y] in self.mineCoords:
                            correctFlagCount += 1

            if  (self.mineNumber == correctFlagCount) and (self.mineNumber == flagCount):
                for x in range(width):
                     for y in range(height):
                         mc.setBlock(x, y, self.z, 20)

                mc.postToChat("You Win!!!")
                running = False
                sys.exit()

class BlockCheckThread(threading.Thread):

    def __init__(self):

        threading.Thread.__init__(self)
    def run(self):
        global running
        running = True
        mc = minecraft.Minecraft.create()
        while running:

            event = mc.events.pollBlockHits()

            if event:
#                mc.postToChat("Hit detected")
                eventSplit = str(event[0]).split()
                eventSplit = [eventSplit[1][0], eventSplit[2][0], eventSplit[3][0]]
                cursorX, cursorY, cursorZ = eventSplit
                cursorX = int(cursorX)
                cursorY = int(cursorY)
                cursorZ = int(cursorZ)
                if newBoard[cursorX][cursorY] == "*":
                    for y in range(height):
                        for x in range(width):
                            # This bit of code's dodgy, because it relies on the 
                            # creation of "newBoard" external to the function...
                            mc.setBlock(x, y, z, 0) # (If you hit a mine it clears the board.)
                    mc.postToChat("You Lose!")
                    running = False
                    sys.exit()

                if newBoard[cursorX][cursorY] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
                    #visibleScreen[x][y] = newBoard[x][y]
                    mc.setBlock(cursorX, cursorY, cursorZ, 0) # We just remove the top layer.

                if newBoard[cursorX][cursorY] == " ":
                    explore(cursorX, cursorY, cursorZ)

#def main():
global running
running = True
mc = minecraft.Minecraft.create(server.address)
board = board()
newBoard = board.create()
visibleScreen = board.visibleScreen()

for x in range(width):
    for y in range(height):
        mc.setBlock(x,y,-1,0)

z = 0 # For now... We can make this dynamic later.
for y in range(height):
   for x in range(width):
       # This bit of code's dodgy, because it relies on the 
       # creation of "visibleScreen" external to the function...
       minecraftAddBlock(x, y, z, "dirt")

WinningCheck = WinningCheckThread(board.mineCoords, board.mineNumber, z)
BlockCheck = BlockCheckThread()
BlockCheck.daemon
WinningCheck.start()
BlockCheck.start()

#main()