import wx
from random import random


bombsID_NEWGAME = wx.ID_HIGHEST
bombsID_EASY = wx.ID_HIGHEST + 1
bombsID_MEDIUM = wx.ID_HIGHEST + 2
bombsID_HARD = wx.ID_HIGHEST + 3

BG_HIDDEN   = 0x100
BG_BOMB     = 0x200
BG_MARKED   = 0x400
BG_EXPLODED = 0x800
BG_MASK     = 0x0FF
PROB        = 0.2
X_UNIT      = 4
Y_UNIT      = 4

class BombsGame:
    def __init__ (self):
        self.m_width = 0
        self.m_height = 0
        self.m_field = 0

    def Init (self, aWidth, aHeight):
        self.m_gridFocusX = -1
        self.m_gridFocusY = -1

        try:
            if self.m_field:
                self.m_field = []
        except:
            pass

        self.m_field = aWidth * aHeight*[0]

        self.m_width = aWidth
        self.m_height = aHeight

        for x in range (0, self.m_width):
            for y in range (0, self.m_height):
                if random() < PROB:
                    self.m_field [x + y * self.m_width] = BG_HIDDEN | BG_BOMB
                else:
                    self.m_field [x + y * self.m_width] = BG_HIDDEN

        self.m_numBombCells = 0
        for x in range (0, self.m_width):
            for y in range (0, self.m_height):
                if self.m_field [x + y * self.m_width] & BG_BOMB:
                    self.m_numBombCells += 1

                    for xx in range (x-1, x + 2):
                        if xx >= 0 and xx < self.m_width:
                            for yy in range (y - 1, y + 2):
                                if yy >= 0 and yy < self.m_height and (yy != y or xx != x):
                                    self.m_field [xx + yy * self.m_width] += 1

        self.m_numRemainingCells = self.m_height * self.m_width - self.m_numBombCells
        return True

    def GetWidth (self):
        return self.m_width

    def GetHeight (self):
        return self.m_height

    def Get (self, x, y):
        return self.m_field [x + y * self.m_width]

    def IsFocussed (self, x, y):
        return self.m_gridFocusX == x and self.m_gridFocusY == y

    def IsHidden (self, x, y):
        return self.Get (x, y) & BG_HIDDEN

    def IsMarked (self, x, y):
        return self.Get (x, y) & BG_MARKED

    def IsBomb (self, x, y):
        return self.Get (x, y) & BG_BOMB

    def IsExploded (self, x, y):
        return self.Get (x, y) & BG_EXPLODED

    def GetNumBombs(self):
        return self.m_numBombCells

    def GetNumRemainingCells(self):
        return self.m_numRemainingCells

    # Marks/unmarks a cell
    def Mark(self, x, y):
        self.m_field [x + y * self.m_width] ^= BG_MARKED

    # Unhides a cell
    def Unhide(self, x, y):
        if not self.IsHidden(x,y):
            return

        self.m_field [x + y * self.m_width] &= ~BG_HIDDEN

        if not self.IsBomb(x,y):
            self.m_numRemainingCells -= 1

    # Makes a cell exploded
    def Explode (self, x, y):
        self.m_field [x + y * self.m_width] |= BG_EXPLODED


class BombsCanvas (wx.Panel):
    def __init__(self, parent, game):
        wx.Panel.__init__(self, parent, wx.ID_ANY)
        # Cell size in pixels
        self.m_game = game
        dc = wx.ClientDC (self)

        m_game = game
        dc = wx.ClientDC (self)
        font = wx.Font(14, wx.ROMAN, wx.NORMAL, wx.NORMAL)

        dc.SetFont(font)

        buf = "M"

        chw, chh = dc.GetTextExtent(buf)
        dc.SetFont(wx.NullFont)

        dc.SetMapMode(wx.MM_METRIC)

        xcm = dc.LogicalToDeviceX(10)
        ycm = dc.LogicalToDeviceY(10)
        # To have a square cell, there must be :
        #    sx*ycm == sy*xcm
        if chw * ycm < chh * xcm:
            sy = chh
            sx = chh * xcm / ycm
        else:
            sx = chw
            sy = chw * ycm / xcm

        self.m_cellWidth = (sx + 3 + X_UNIT) / X_UNIT
        self.m_cellHeight = (sy + 3 + Y_UNIT) / Y_UNIT
        dc.SetMapMode(wx.MM_TEXT)
        self.m_bmp = 0

        self.Bind (wx.EVT_PAINT, self.OnPaint)
        self.Bind (wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
        self.Bind (wx.EVT_CHAR, self.OnChar)

    def UpdateGridSize(self):
        if self.m_bmp:
            self.m_bmp = None
        self.Refresh()

    def GetGridSizeInPixels(self):
        return wx.Size(self.m_cellWidth * X_UNIT * self.m_game.GetWidth(),  self.m_cellHeight * Y_UNIT * self.m_game.GetHeight())

    def OnPaint(self, event):
        dc = wx.PaintDC (self)

        numHorzCells = self.m_game.GetWidth()
        numVertCells = self.m_game.GetHeight()
        #// Insert your drawing code here.
        if not self.m_bmp:
            size = dc.GetSize()
            self.m_bmp = wx.EmptyBitmap (size.GetWidth(), size.GetHeight())
            if self.m_bmp:
                memDC = wx.MemoryDC()
                memDC.SelectObject(self.m_bmp)
                self.DrawField(memDC, 0, 0, numHorzCells - 1, numVertCells - 1)
                memDC.SelectObject(wx.NullBitmap)
        if self.m_bmp:
            memDC = wx.MemoryDC()
            memDC.SelectObject(self.m_bmp)
            size = dc.GetSize()
            dc.Blit(0, 0, size.GetWidth(), size.GetHeight(), memDC, 0, 0, wx.COPY)
            memDC.SelectObject(wx.NullBitmap)
        else:
            self.DrawField(dc, 0, 0, numHorzCells - 1, numVertCells - 1)

    def DrawField(self, dc, xc1, yc1, xc2, yc2):
        buf = ""

        wx.Black = wx.TheColourDatabase.Find("BLACK")
        wx.White = wx.TheColourDatabase.Find("WHITE")
        wx.Red = wx.TheColourDatabase.Find("RED")
        wx.Blue = wx.TheColourDatabase.Find("BLUE")
        wx.Grey = wx.TheColourDatabase.Find("LIGHT GREY")
        wx.Focused = wx.TheColourDatabase.Find("GREY")
        wx.Green = wx.TheColourDatabase.Find("GREEN")

        blackPen = wx.ThePenList.FindOrCreatePen(wx.Black, 1, wx.SOLID)
        redPen = wx.ThePenList.FindOrCreatePen(wx.Red, 1, wx.SOLID)
        bluePen = wx.ThePenList.FindOrCreatePen(wx.Blue, 1, wx.SOLID)
        whiteBrush = wx.TheBrushList.FindOrCreateBrush(wx.White, wx.SOLID)
        greyBrush = wx.TheBrushList.FindOrCreateBrush(wx.Grey, wx.SOLID)
        focusedBrush = wx.TheBrushList.FindOrCreateBrush(wx.Focused, wx.SOLID)
        redBrush = wx.TheBrushList.FindOrCreateBrush(wx.Red, wx.SOLID)

        dc.SetPen (blackPen)

        xMax = self.GetGridSizeInPixels().GetWidth()
        yMax = self.GetGridSizeInPixels().GetHeight()
        for x in range (xc1, xc2 + 1):
            dc.DrawLine(x * self.m_cellWidth * X_UNIT, 0, x * self.m_cellWidth * X_UNIT, yMax)
        for y in range (yc1, yc2 + 1):
            dc.DrawLine(0, y * self.m_cellHeight * Y_UNIT, xMax, y * self.m_cellHeight *Y_UNIT)


        font = wx.Font(14, wx.ROMAN, wx.NORMAL, wx.NORMAL)
        dc.SetFont(font)

        for x in range(xc1, xc2 + 1):
            for y in range(yc1, yc2 + 1):
                if self.m_game.IsMarked(x,y):
                    dc.SetPen (blackPen)

                    if self.m_game.IsFocussed(x, y):
                        dc.SetBrush (focusedBrush)
                    else:
                        dc.SetBrush (greyBrush)

                    dc.DrawRectangle (x * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT, self.m_cellWidth * X_UNIT + 1, self.m_cellHeight * Y_UNIT + 1)
                    buf = "M"
                    if not self.m_game.IsHidden (x, y) and self.m_game.IsBomb(x,y):
                        dc.SetTextForeground(wx.Blue)
                    else:
                        dc.SetTextForeground(wx.Red)

                    dc.SetTextBackground(wx.Grey)
                    chw, chh = dc.GetTextExtent(buf)
                    dc.DrawText (buf, x * self.m_cellWidth * X_UNIT + (self.m_cellWidth * X_UNIT-chw) / 2, y * self.m_cellHeight * Y_UNIT + (self.m_cellHeight * Y_UNIT - chh) / 2 )

                    if not self.m_game.IsHidden(x,y) and self.m_game.IsBomb(x,y):
                        dc.SetPen (redPen)
                        dc.DrawLine (x * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT, (x + 1) * self.m_cellWidth * X_UNIT, (y + 1) * self.m_cellHeight * Y_UNIT)
                        dc.DrawLine (x * self.m_cellWidth * X_UNIT, (y + 1) * self.m_cellHeight * Y_UNIT,   (x + 1) * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT)
                elif self.m_game.IsHidden (x, y):
                    dc.SetPen (blackPen)
                    if self.m_game.IsFocussed (x, y):
                        dc.SetBrush (focusedBrush)
                    else:
                        dc.SetBrush (greyBrush)

                    dc.DrawRectangle (x * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT, self.m_cellWidth * X_UNIT+1, self.m_cellHeight * Y_UNIT + 1)
                elif self.m_game.IsBomb(x,y):
                    dc.SetPen (blackPen)
                    dc.SetBrush (redBrush)
                    dc.DrawRectangle (x * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT, self.m_cellWidth * X_UNIT + 1, self.m_cellHeight * Y_UNIT + 1)
                    buf = "B"
                    dc.SetTextForeground (wx.Black)
                    dc.SetTextBackground (wx.Red)
                    chw, chh = dc.GetTextExtent (buf)
                    dc.DrawText (buf, x * self.m_cellWidth * X_UNIT + (self.m_cellWidth * X_UNIT - chw) / 2, y * self.m_cellHeight * Y_UNIT + (self.m_cellHeight * Y_UNIT - chh) / 2)
                    if self.m_game.IsExploded (x, y):
                        dc.SetPen (bluePen)
                        dc.DrawLine (x * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT, (x + 1) * self.m_cellWidth * X_UNIT, (y + 1) * self.m_cellHeight * Y_UNIT)
                        dc.DrawLine (x * self.m_cellWidth * X_UNIT, (y + 1) * self.m_cellHeight * Y_UNIT, (x + 1) * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT)
                else:   # Display a digit
                    dc.SetPen (blackPen)
                    if self.m_game.IsFocussed (x, y):
                        dc.SetBrush (focusedBrush)
                    else:
                        dc.SetBrush (whiteBrush)
                    dc.DrawRectangle (x * self.m_cellWidth * X_UNIT, y * self.m_cellHeight * Y_UNIT, self.m_cellWidth * X_UNIT + 1, self.m_cellHeight * Y_UNIT + 1)

                    digit_value = self.m_game.Get (x, y) & BG_MASK
                    if digit_value == 0:
                        buf = "0"
                        dc.SetTextForeground (wx.Green)
                    elif digit_value == 1:
                        buf = "1"
                        dc.SetTextForeground (wx.Blue)
                    else:
                        buf = "%d" % digit_value
                        dc.SetTextForeground (wx.Black)
                chw, chh = dc.GetTextExtent (buf)
                dc.SetTextBackground(wx.White)
                dc.DrawText (buf, x * self.m_cellWidth * X_UNIT + (self.m_cellWidth * X_UNIT-chw) / 2, y * self.m_cellHeight * Y_UNIT + (self.m_cellHeight * Y_UNIT-chh) / 2)
        dc.SetFont (wx.NullFont)

        wx.LogStatus ("%d bombs  %d remaining cells" % (self.m_game.GetNumBombs(), self.m_game.GetNumRemainingCells()))

    def RefreshField(self, xc1, yc1, xc2, yc2):
        dc = wx.ClientDC (self)
        self.DrawField (dc, xc1, yc1, xc2, yc2)
        if self.m_bmp:
            memDC = wx.MemoryDC()
            memDC.SelectObject (self.m_bmp)
            self.DrawField (memDC, xc1, yc1, xc2, yc2)
            memDC.SelectObject(wx.NullBitmap)

    def Uncover(self, x, y):
        self.m_game.Unhide (x, y)
        self.RefreshField (x, y, x, y)

        gridWidth = self.m_game.GetWidth()
        gridHeight = self.m_game.GetHeight()

        hasWon = self.m_game.GetNumRemainingCells() == 0
        if self.m_game.IsBomb (x,y) or hasWon:
            wx.Bell()
            if hasWon:
                wx.MessageBox("Nice! You found all the bombs!", "wxWin Bombs", wx.OK | wx.CENTRE)
            else: # x,y is a bomb
                self.m_game.Explode(x, y)

            for x in range (0, gridWidth):
                for y in range (0, gridHeight):
                    self.m_game.Unhide (x, y)
            self.RefreshField (0, 0, gridWidth - 1, gridHeight - 1)
        elif not self.m_game.Get (x, y):
            left = 0
            if x > 0:
                left = x-1

            right = gridWidth - 1
            if x < gridWidth - 1:
                right = x + 1
            top = 0
            if y > 0:
                top = y - 1
            bottom = gridHeight - 1
            if y < gridHeight - 1:
                bottom = y + 1

            for j in range (top, bottom + 1):
                for i in range (left, right + 1):
                    if (i != x or j != y) and self.m_game.IsHidden (i, j) and not self.m_game.IsMarked(i, j):
                        self.Uncover (i, j)

    def OnMouseEvent(self, event):
        gridWidth = self.m_game.GetWidth()
        gridHeight = self.m_game.GetHeight()

        #wx.Coord fx, fy
        fx, fy = event.GetPosition()
        x = fx / (self.m_cellWidth * X_UNIT)
        y = fy / (self.m_cellHeight * Y_UNIT)
        if x < gridWidth and y < gridHeight:
            if ((event.RightDown() or (event.LeftDown() and event.ShiftDown())) or (self.m_game.IsHidden(x,y) and not self.m_game.GetNumRemainingCells())):
                #// store previous and current field
                prevFocusX = self.m_game.m_gridFocusX
                prevFocusY = self.m_game.m_gridFocusY
                self.m_game.m_gridFocusX = x
                self.m_game.m_gridFocusY = y
                self.RefreshField (prevFocusX, prevFocusY, prevFocusX, prevFocusY)
                self.m_game.Mark (x, y)
                self.RefreshField (x, y, x, y)
                return
            elif event.LeftDown() and self.m_game.IsHidden(x,y) and not self.m_game.IsMarked(x,y):
                #// store previous and current field
                prevGridFocusX = self.m_game.m_gridFocusX
                prevGridFocusY = self.m_game.m_gridFocusY
                self.m_game.m_gridFocusX = x
                self.m_game.m_gridFocusY = y
                self.RefreshField (prevGridFocusX, prevGridFocusY, prevGridFocusX, prevGridFocusY)
                self.Uncover(x, y)
                return

    def OnChar(self, event):
        keyCode = event.GetKeyCode()
        prevGridFocusX = self.m_game.m_gridFocusX
        prevGridFocusY = self.m_game.m_gridFocusY

        gridWidth = self.m_game.GetWidth()
        gridHeight = self.m_game.GetHeight()

        if keyCode == wx.WXK_RIGHT:
            self.m_game.m_gridFocusX += 1
            if self.m_game.m_gridFocusX >= gridWidth:
                self.m_game.m_gridFocusX = 0

        elif keyCode == wx.WXK_LEFT:
            self.m_game.m_gridFocusX -= 1
            if self.m_game.m_gridFocusX < 0:
                self.m_game.m_gridFocusX = gridWidth - 1

        elif keyCode == wx.WXK_DOWN:
            self.m_game.m_gridFocusY += 1
            if self.m_game.m_gridFocusY >= gridHeight:
                m_game.m_gridFocusY = 0

        elif keyCode == wx.WXK_UP:
            self.m_game.m_gridFocusY -= 1
            if self.m_game.m_gridFocusY < 0:
                self.m_game.m_gridFocusY = gridHeight - 1

        elif keyCode == wx.WXK_RETURN:
            if ((prevGridFocusX == self.m_game.m_gridFocusX) and (prevGridFocusY == self.m_game.m_gridFocusY) and (self.m_game.IsHidden (self.m_game.m_gridFocusX, self.m_game.m_gridFocusY))):
                self.m_game.Mark (self.m_game.m_gridFocusX, self.m_game.m_gridFocusY)
                if not self.m_game.IsMarked (self.m_game.m_gridFocusX, self.m_game.m_gridFocusY):
                    self.Uncover (self.m_game.m_gridFocusX, self.m_game.m_gridFocusY)
                self.RefreshField (self.m_game.m_gridFocusX, self.m_game.m_gridFocusY, self.m_game.m_gridFocusX, self.m_game.m_gridFocusY)
        else:
            event.Skip()

        if (prevGridFocusX != self.m_game.m_gridFocusX) or (prevGridFocusY != self.m_game.m_gridFocusY):
            # cause focused field to be visible after first key hit after launching new game
            if self.m_game.m_gridFocusX < 0:
                self.m_game.m_gridFocusX = 0
            if self.m_game.m_gridFocusY < 0:
                self.m_game.m_gridFocusY = 0

            # refresh previous field and focused field
            self.RefreshField (prevGridFocusX, prevGridFocusY, prevGridFocusX, prevGridFocusY)
            self.RefreshField (self.m_game.m_gridFocusX, self.m_game.m_gridFocusY, self.m_game.m_gridFocusX, self.m_game.m_gridFocusY)


class BombsFrame (wx.Frame):
    def __init__(self, game, parent = None):
        wx.Frame.__init__(self, None, wx.ID_ANY, "wxBombs", wx.DefaultPosition, wx.Size(300, 300), wx.DEFAULT_DIALOG_STYLE | wx.MINIMIZE_BOX)

        self.m_game = game
        if parent:
            try:
                self.SetIcon(wx.Icon(parent.prefs.pluginsdirectory + "/bitmaps/bombs.ico", wx.BITMAP_TYPE_ICO))
            except:
                pass
        else:
            try:
                self.SetIcon(wx.Icon('bombs.ico', wx.BITMAP_TYPE_ICO))
            except:
                pass


        self.CreateStatusBar()

        # Create a menu bar for the frame
        self.menuBar = wx.MenuBar()
        self.menuFile = wx.Menu()
        self.menuLevel = wx.Menu()
        self.menuLevel.AppendRadioItem(bombsID_EASY, "&Easy (10x10)\tCtrl-1")
        self.menuLevel.AppendRadioItem(bombsID_MEDIUM, "&Medium (15x15)\tCtrl-2")
        self.menuLevel.AppendRadioItem(bombsID_HARD, "&Hard (25x20)\tCtrl-3")

        self.menuFile.AppendMenu(bombsID_NEWGAME, "&New Game",  self.menuLevel, "Starts a new game")

        self.menuFile.AppendSeparator()
        self.menuFile.Append(wx.ID_EXIT, "E&xit", "Quits the application")

        self.menuBar.Append(self.menuFile, "&File")


        self.menuHelp = wx.Menu()
        self.menuHelp.Append (wx.ID_ABOUT, "&About", "Displays the program information")

        self.menuBar.Append(self.menuHelp, "&Help")

        self.SetMenuBar(self.menuBar)

        # Create child subwindows.
        self.m_canvas = BombsCanvas(self, self.m_game)

        # Ensure the subwindows get resized o.k.
        #  OnSize(width, height)

        # Centre frame on the screen.
        self.Centre(wx.BOTH)

        # Show the frame.
        self.Show()

        self.Bind(wx.EVT_MENU, self.OnNewEasyGame, id = bombsID_EASY)
        self.Bind(wx.EVT_MENU, self.OnNewMediumGame, id = bombsID_MEDIUM)
        self.Bind(wx.EVT_MENU, self.OnNewHardGame, id = bombsID_HARD)
        self.Bind(wx.EVT_MENU, self.OnExit, id = wx.ID_EXIT)
        self.Bind(wx.EVT_MENU, self.OnAbout, id = wx.ID_ABOUT)

    def NewGame(self, level, query):
        if query:
            ok = wx.MessageBox ("Start new game regardless previous board?", "Confirm", wx.YES_NO | wx.ICON_QUESTION, self)
            if ok != wx.YES:
                return

        numHorzCells = 20
        numVertCells = 20

        if level == bombsID_EASY:
            numHorzCells = 10
            numVertCells = 10
        elif level == bombsID_MEDIUM:
            numHorzCells = 15
            numVertCells = 15
        elif level == bombsID_HARD:
            numHorzCells = 25
            numVertCells = 20
        else:
            wx.FAIL_MSG("Invalid level")

        self.m_game.Init (numHorzCells, numVertCells)

        self.GetMenuBar().Check(level, True)

        self.m_canvas.UpdateGridSize()
        self.SetClientSize(self.m_canvas.GetGridSizeInPixels())

    def OnNewEasyGame(self, event):
        self.NewGame(bombsID_EASY, True)

    def OnNewMediumGame(self, event):
        self.NewGame(bombsID_MEDIUM, True)

    def OnNewHardGame(self, event):
        self.NewGame(bombsID_HARD, True)

    def OnExit(self, event):
        self.Close()

    def OnAbout(self, event):
        wx.MessageBox("wxBombs (c) 1996 by P. Foggia\n<foggia@amalfi.dis.unina.it>\nTransmitted from wxWidgets to wxPython by Franz Steinhaeusler 2005", "About wxBombs")


class BombsApp (wx.App):

    def OnInit(self, parent = None):
        self.m_game = BombsGame()
        self.m_frame = BombsFrame(self.m_game, parent)
        self.m_frame.NewGame(bombsID_EASY, False)
        return True

def Plugin(DrFrame):

    def OnBombs (event):
        m_game = BombsGame()
        m_frame = BombsFrame(m_game, DrFrame)
        m_frame.NewGame(bombsID_EASY, False)

    try:
        DrFrame.miscmenu
    except:
        #else create
        DrFrame.miscmenu = wx.Menu()
        if DrFrame.PLATFORM_IS_WIN:
            menuBarName = "&Misc"
        else:
            menuBarName = "Misc"
    
        DrFrame.menuBar.Insert(8, DrFrame.miscmenu, menuBarName)

    ID_BOMBS = DrFrame.GetNewId()

    DrFrame.Bind(wx.EVT_MENU, OnBombs, id = ID_BOMBS)

    DrFrame.AddPluginShortcutFunction("Bombs", "Bombs", OnBombs)
    DrFrame.AddPluginPopUpMenuFunction("Bombs", "Bombs", OnBombs)
    DrFrame.LoadPluginShortcuts('Bombs')

    DrFrame.miscmenu.Append(ID_BOMBS, DrFrame.GetPluginMenuLabel('Bombs', 'Bombs', 'Bombs'))

if __name__ == '__main__':
    app = BombsApp(0)
    app.MainLoop()
