import wx
from wxPython.wx import *
from world import NORTH, EAST, SOUTH, WEST
import os
from utils import get_rootdir
# TODO: Figure out a way to make a child window for 
# part of the world that isn't the graph coordinates,
# so we don't have to clip everything manually.

def loadGvrBitmaps(useGuido):
    if useGuido: prpnd = "guido"
    else: prpnd = "gvr"
    gvrup = wxBitmap(os.path.join(get_rootdir(),'bitmaps','%s-up.bmp'%prpnd), wxBITMAP_TYPE_BMP)
    gvright = wxBitmap(os.path.join(get_rootdir(),'bitmaps','%s-right.bmp'%prpnd), wxBITMAP_TYPE_BMP)
    gvrleft = wxBitmap(os.path.join(get_rootdir(),'bitmaps','%s-left.bmp'%prpnd), wxBITMAP_TYPE_BMP)
    gvrdown = wxBitmap(os.path.join(get_rootdir(),'bitmaps','%s-down.bmp'%prpnd), wxBITMAP_TYPE_BMP)
    return { 
        NORTH: gvrup,
        WEST: gvrleft,
        SOUTH: gvrdown,
        EAST: gvright,
    }

def getFont(size):
    return wxFont(size, wxDEFAULT, wxNORMAL, wxNORMAL)
def getUsableDeskTopSize():
    desktopSize = wxGetDisplaySize()
    return desktopSize.GetWidth()-50, desktopSize.GetHeight()-175

class WorldDisplay:
    def __init__(self, frame):
        self.frame = frame
        self.xPad = 6
        self.yPad = 6
        self.squareSize = 25
        self.black_grid_dot_pen   = wxPen(wxColour(0,0,0), 5, wxSOLID)
        self.red_wall_pen   = wxPen(wxColour(255,0,0), 2, wxSOLID)
        self.thick_blue_pen = wxPen(wxColour(0,0,255), 4, wxSOLID)
        self.thin_blue_pen  = wxPen(wxColour(0,0,255), 1, wxSOLID)
        self.lowerLeftCorner = None

    def reset(self, world):
        self.gvr_bitmaps = loadGvrBitmaps(world.useGuido)
        self.offset = world.robot
        self.getSizes()
        scrolling, self.offset = world.newOffset(self.offset, (self.screenX, self.screenY))
        self.drawWorldOntoBitmap(world)

    def drawAxisWalls(self):
        self.dc.SetPen(self.red_wall_pen)
        offsetX, offsetY = self.offset
        if offsetX == 0:
            self.drawLine((.5, .5+offsetY), (.5, .5+offsetY+self.screenY))
        if offsetY == 0:
            self.drawLine((.5+offsetX, .5), (.5+offsetX+self.screenX, .5))

    def drawAxisLabels(self):
        # wxSWISS is the sans-serif font
        self.dc.SetFont(wxFont(8, wxSWISS, wxNORMAL, wxNORMAL))
        #turn it into integers, floats are deprecated for range
        offsetX, offsetY = map(int,self.offset)
        for x in range(offsetX+1, offsetX+1+self.screenX):
            label = '%d' % (x)
            self.centerText(label,(x, offsetY))
        for y in range(offsetY+1, offsetY+1+self.screenY):
            label = '%d' % (y)
            self.centerText(label,(offsetX, y))

    def drawSmallWalls(self,world):
        self.dc.SetPen(self.red_wall_pen)
        for (x,y) in world.walls[WEST].keys():
            self.drawWestWall(x,y)
        for (x,y) in world.walls[SOUTH].keys():
            self.drawSouthWall(x,y)

    def drawWestWall(self, x, y):
        self.drawLine((x-.5, y-.5),(x-.5, y+.5))

    def drawSouthWall(self, x, y):
        self.drawLine((x-.5, y-.5),(x+.5, y-.5))

    def drawStreetCorners(self):
        self.dc.SetPen(self.black_grid_dot_pen)
        offsetX, offsetY = map(int,self.offset)
        for y in range(offsetY+1,offsetY+1+self.screenY):
            for x in range(offsetX+1,offsetX+1+self.screenX):
                self.drawCircle((x+.5, y+.5),1)
        self.dc.SetFont(getFont(10))

    def drawBeepers(self, world):
        for coord in world.beepers.keys():
            self.drawBeeper(world, coord)

    def updateOneWall(self, dir, (x, y)):
        '''
        This method can be called by anybody who
        wants to redraw one wall on the canvas.  The
        most likely caller in the future will be some
        code involved in a GUI world builder.  You can
        also call this from powerMode.
        '''
        bmp = self.CachedWorldBitmap
        self.dc = wxMemoryDC()
        self.dc.SelectObject(bmp)
        self.dc.BeginDrawing()
        self.dc.SetPen(self.red_wall_pen)
        if dir == WEST:
            self.drawWestWall(x, y)
        elif dir == SOUTH:
            self.drawSouthWall(x,y)
        self.refreshJustOneSquare((x,y))

    def updateWorldBitmapAfterMove(self, world, oldCoords):
        scrolling, self.offset = world.newOffset(self.offset, (self.screenX, self.screenY))
        if scrolling:
            self.drawWorldOntoBitmap(world)
            self.frame.Refresh()
            return
        bmp = self.CachedWorldBitmap
        self.dc = wxMemoryDC()
        self.dc.SelectObject(bmp)
        self.dc.BeginDrawing()
        if oldCoords is not None:
            self.drawBeeper(world, oldCoords)
            self.refreshJustOneSquare(oldCoords)
        self.drawRobot(world)
        self.refreshJustOneSquare(world.robot)
       
    def drawBeeper(self, world, coords): 
        x, y = coords
        offsetX, offsetY = self.offset
        if x <= offsetX: return
        if y <= offsetY: return
        self.clearSquareInWorld(coords)
        numBeepers = world.beepers.get(coords,0)
        dc = self.dc
        if numBeepers > 0:
            dc.SetPen(self.thin_blue_pen)
            dc.SetBrush(wxWHITE_BRUSH)
            self.drawCircle(coords, 7)
            if numBeepers == 1:
                dc.SetPen(self.thick_blue_pen)
                dc.SetBrush(wxBLUE_BRUSH)
                self.drawCircle(coords, 3)
            else:
                font = getFont(8)
                dc.SetFont(font)
                self.centerText('%s' % world.beepers.get(coords,0), coords)

    def drawRobot(self, world):
        coords = world.robot
        dir = world.dir
        size_of_bitmap = (15, 15)
        bitmap = self.gvr_bitmaps[dir]
        (x,y) = self.reScale(coords)
        (width, height) = size_of_bitmap
        self.dc.DrawBitmap(bitmap, x - width/2, y - height/2, 1)

    def move(self):
        (w, h) = self.getSizes()
        (x, y) = self.frame.GetPosition()
        self.lowerLeftCorner = (x+w, y+h)

    def reSize(self, world):
        (w, h) = self.getSizes()
        (x, y) = self.frame.GetPosition()
        if self.lowerLeftCorner:
            # This code allows some on to pull on the bottom
            # of the window and see streets that are further
            # south.  Similar code could work for pulling on
            # the eastern edge of the window.
            (lx, ly) = self.lowerLeftCorner
            yDisplacement = (ly - (y+h)) / (1.0 * self.squareSize)
            offsetx, offsety = self.offset
            offsety += yDisplacement
            if offsety < 0.2: offsety = 0
            robotX, robotY = world.robot
            if offsety >= robotY - 1: 
                # what I really want in this case is to
                # disable resizing at this point, but I
                # am tired
                offsety = robotY - 1
            self.offset = offsetx, offsety
            # We also need to make this code work more cleanly
            # when you pull on the north edge.
        self.lowerLeftCorner = (x+w, y+h)
        self.drawWorldOntoBitmap(world)
        return True

    def drawWorldOntoBitmap(self, world):
        (x,y) = self.getSizes()
        bmp = wxEmptyBitmap(x, y, -1)
        self.dc = wxMemoryDC()
        self.dc.SelectObject(bmp)
        self.dc.Clear()
        self.dc.BeginDrawing()
        self.drawAxisWalls()
        self.drawAxisLabels()
        self.drawStreetCorners()
        self.drawSmallWalls(world)
        self.drawBeepers(world)
        self.drawRobot(world)
        self.CachedWorldBitmap = bmp

    ############ UTILITY METHODS RELATED TO SCREEN GEOMETRY

    def yAdj(self):
        adjx, adjy = self.frame.GetClientAreaOrigin()
        if os.name == 'posix' and os.uname()[0] == 'Darwin':
            return 0
        return adjy

    def getSizes(self):
        (x, y) = self.frame.GetClientSizeTuple()
        adjy = self.yAdj()
        self.screenX = int((x - self.xPad) / self.squareSize)
        self.screenY = int((y - adjy - self.yPad) / self.squareSize)
        self.screenHeight = y
        return (x,y)
    
    def getScreenSize(self, avenues, streets):
        width, height = [20+avenues*self.squareSize+int(self.squareSize/2),\
                        20+streets*self.squareSize+int(self.squareSize/2)]
        maxWidth, maxHeight = getUsableDeskTopSize()
        if width > maxWidth: width = maxWidth
        if height > maxHeight: height = maxHeight
        return width, height

    def getWorldSize(self):
        greatestX, greatestY = self.frame.myWorld.furthestCoordinate()
        if greatestX < 14: greatestX = 14
        if greatestY < 9: greatestY = 9
        return getScreenSize(greatestX, greatestY)
        
    def reScale(self, tuple_to_fix):
        x, y = tuple_to_fix
        offsetX, offsetY = self.offset
        return self.reScaleFromOffset(x-offsetX, y-offsetY)

    def reScaleFromOffset(self, x, y):
        x = (20 + (self.squareSize * x))
        y = self.screenHeight - (20 + (self.squareSize * y))
        return (int(x), int(y))

    def clearSquareInWorld(self, coords):
        dc = self.dc
        dc.SetPen(wxWHITE_PEN)
        dc.SetBrush(wxWHITE_BRUSH)
        (x,y) = self.reScale(coords)
        (width, height) = (15, 15)
        dc.DrawRectangle(x - width/2, y - height/2, width, height)

    def refreshJustOneSquare(self, coords):
        if os.name == 'nt': return
        (xx,yy) = self.reScale(coords)
        size = self.squareSize + 1
        rect = (xx-size/2-1, yy-size/2, size+1, size)
        self.frame.RefreshRect(rect)
        return rect

    def centerText(self, label, coords):
        (x,y) = self.reScale(coords)
        (w,h) = self.dc.GetTextExtent(label)
        x -= w/2
        y -= h/2
        self.dc.DrawText(label, x, y)

    def drawCircle(self, center, radius):
        (x,y) = self.reScale(center)
        self.dc.DrawEllipse(x-radius,y-radius, 2*radius, 2*radius)

    def drawLine(self, coords1, coords2):
        x1, y1 = coords1
        x2, y2 = coords2
        ox, oy = self.offset
        minx, miny = ox+0.5, oy+0.5
        if x1 < minx:
            x1=minx
            if x1 > x2: return
        if y1 < miny:
            y1=miny
            if y1 > y2: return 
        xx1, yy1 = self.reScale((x1, y1))
        xx2, yy2 = self.reScale((x2, y2))
        self.dc.DrawLine(xx1, yy1, xx2, yy2)

    def onPaint(self, dc):
        try:
            del self.dc
        except AttributeError:
            pass
        bitmapDC = wxMemoryDC() 
        bitmapDC.SelectObject(self.CachedWorldBitmap)
        corruptedRegions = wxRegionIterator(self.frame.GetUpdateRegion())
        x, y, w, h = corruptedRegions.GetRect()
        if corruptedRegions.HaveRects():
            # More than one region is corrupted, just redraw it all
            width, height = bitmapDC.GetSizeTuple()
            dc.Blit(0, self.yAdj(), width, height, bitmapDC, 0, 0) 
        else:
            # Draw on the affected area
            dc.Blit(x, y + self.yAdj(), w, h, bitmapDC, x, y)
