"""Copyright (c) 2010-2012 David Rio Vierra

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE."""

from toolbasics import *
from fill import FillTool, BlockFillOperation

SelectSettings = config.Settings("Selection")
SelectSettings.showPreviousSelection = SelectSettings("Show Previous Selection", True)
SelectSettings.color = SelectSettings("Color", "teal")

ColorSettings = config.Settings("Selection Colors")
ColorSettings.defaultColors = {}


class ColorSetting(config.Setting):
    def __init__(self, section, name, dtype, default):
        super(ColorSetting, self).__init__(section, name, dtype, default)
        ColorSettings.defaultColors[name] = self

    def set(self, val):
        values = str(tuple(val))[1:-1]
        super(ColorSetting, self).set(values)

    def get(self):
        colorValues = super(ColorSetting, self).get()
        return parseValues(colorValues)
ColorSettings.Setting = ColorSetting


def parseValues(colorValues):
    if colorValues is None:
        return 1., 1., 1.

    try:
        values = colorValues.split(",")
        values = [(min(max(float(x), 0.0), 1.0)) for x in values]
    except:
        values = (1.0, 1.0, 1.0)

    return tuple(values)

ColorSettings("white", (1.0, 1.0, 1.0))

ColorSettings("blue", (0.75, 0.75, 1.0))
ColorSettings("green", (0.75, 1.0, 0.75))
ColorSettings("red", (1.0, 0.75, 0.75))

ColorSettings("teal", (0.75, 1.0, 1.0))
ColorSettings("pink", (1.0, 0.75, 1.0))
ColorSettings("yellow", (1.0, 1.0, 0.75))

ColorSettings("grey", (0.6, 0.6, 0.6))
ColorSettings("black", (0.0, 0.0, 0.0))


def GetSelectionColor(colorWord=None):
    if colorWord is None:
        colorWord = SelectSettings.color.get()

    colorValues = config.config.get("Selection Colors", colorWord)
    return parseValues(colorValues)


class SelectionToolOptions(ToolOptions):
    def updateColors(self):
        names = [name.lower() for (name, value) in config.config.items("Selection Colors")]
        self.colorPopupButton.choices = [name.capitalize() for name in names]

        color = SelectSettings.color.get()

        self.colorPopupButton.choiceIndex = names.index(color.lower())

    def __init__(self, tool):
        Panel.__init__(self)
        self.tool = tool

        self.colorPopupButton = ChoiceButton([], choose=self.colorChanged)
        self.updateColors()

        colorRow = Row((Label("Color: ", align="r"), self.colorPopupButton))
        okButton = Button("OK", action=self.dismiss)
        showPreviousRow = CheckBoxLabel("Show Previous Selection", ref=AttrRef(tool, 'showPreviousSelection'))

        def set_colorvalue(ch):
            i = "RGB".index(ch)

            def _set(val):
                choice = self.colorPopupButton.selectedChoice
                values = GetSelectionColor(choice)
                values = values[:i] + (val / 255.0,) + values[i + 1:]
                setting = str(values)[1:-1]
                config.config.set("Selection Colors", choice, setting)
                self.colorChanged()

            return _set

        def get_colorvalue(ch):
            i = "RGB".index(ch)

            def _get():
                return int(GetSelectionColor()[i] * 255)
            return _get

        colorValuesInputs = [IntInputRow(ch + ":", get_value=get_colorvalue(ch),
                                              set_value=set_colorvalue(ch),
                                              min=0, max=255)
                             for ch in "RGB"]

        colorValuesRow = Row(colorValuesInputs)
        col = Column((Label("Selection Options"), colorRow, colorValuesRow, showPreviousRow, okButton))

        self.add(col)
        self.shrink_wrap()

    def colorChanged(self):
        SelectSettings.color.set(self.colorPopupButton.selectedChoice)
        self.tool.updateSelectionColor()


class SelectionToolPanel(Panel):
    def __init__(self, tool):
        Panel.__init__(self)
        self.tool = tool

        nudgeBlocksButton = NudgeButton()
        nudgeBlocksButton.nudge = tool.nudgeBlocks
        nudgeBlocksButton.bg_color = (0.3, 1.0, 0.3, 0.35)
        self.nudgeBlocksButton = nudgeBlocksButton

        nudgeSelectionButton = NudgeButton()
        nudgeSelectionButton.nudge = tool.nudgeSelection
        nudgeSelectionButton.bg_color = tool.selectionColor + (0.7,)

        deleteBlocksButton = Button("Delete Blocks", action=self.tool.deleteBlocks)
        deleteBlocksButton.tooltipText = "Fill the selection with Air. Shortcut: DELETE"
        deleteEntitiesButton = Button("Delete Entities", action=self.tool.deleteEntities)
        deleteEntitiesButton.tooltipText = "Remove all entities within the selection"
        # deleteTileEntitiesButton = Button("Delete TileEntities", action=self.tool.deleteTileEntities)
        analyzeButton = Button("Analyze", action=self.tool.analyzeSelection)
        analyzeButton.tooltipText = "Count the different blocks and entities in the selection and display the totals."
        cutButton = Button("Cut", action=self.tool.cutSelection)
        cutButton.tooltipText = "Take a copy of all blocks and entities within the selection, then delete everything within the selection. Shortcut: {0}-X".format(mcplatform.cmd_name)
        copyButton = Button("Copy", action=self.tool.copySelection)
        copyButton.tooltipText = "Take a copy of all blocks and entities within the selection. Shortcut: {0}-C".format(mcplatform.cmd_name)
        pasteButton = Button("Paste", action=self.tool.editor.pasteSelection)
        pasteButton.tooltipText = "Import the last item taken by Cut or Copy. Shortcut: {0}-V".format(mcplatform.cmd_name)
        exportButton = Button("Export", action=self.tool.exportSelection)
        exportButton.tooltipText = "Export the selection to a .schematic file. Shortcut: {0}-E".format(mcplatform.cmd_name)

        selectButton = Button("Select Chunks")
        selectButton.tooltipText = "Expand the selection to the edges of the chunks within"
        selectButton.action = tool.selectChunks
        selectButton.highlight_color = (0, 255, 0)

        deselectButton = Button("Deselect")
        deselectButton.tooltipText = "Remove the selection. Shortcut: {0}-D".format(mcplatform.cmd_name)
        deselectButton.action = tool.deselect
        deselectButton.highlight_color = (0, 255, 0)

        nudgeRow = Row((nudgeBlocksButton, nudgeSelectionButton))
        buttonsColumn = (
            nudgeRow,
            deselectButton,
            selectButton,
            deleteBlocksButton,
            deleteEntitiesButton,
            analyzeButton,
            cutButton,
            copyButton,
            pasteButton,
            exportButton,
        )

        buttonsColumn = Column(buttonsColumn)

        self.add(buttonsColumn)
        self.shrink_wrap()


class NudgeBlocksOperation (Operation):
    def __init__(self, editor, sourceBox, direction):
        self.editor = editor
        self.sourceBox = sourceBox
        self.destBox = BoundingBox(sourceBox.origin, sourceBox.size)
        self.destBox.origin = map(operator.add, self.destBox.origin, direction)
        self.nudgeSelection = NudgeSelectionOperation(editor.selectionTool, direction)

    def dirtyBox(self):
        return self.sourceBox.union(self.destBox)

    def perform(self, recordUndo=True):
        level = self.editor.level
        tempSchematic = level.extractSchematic(self.sourceBox)
        if tempSchematic:
            dirtyBox = self.dirtyBox()
            if recordUndo:
                self.undoSchematic = self.extractUndoSchematicFrom(level, dirtyBox)

            level.fillBlocks(self.sourceBox, level.materials.Air)
            level.removeTileEntitiesInBox(self.sourceBox)
            level.removeTileEntitiesInBox(self.destBox)

            level.removeEntitiesInBox(self.sourceBox)
            level.removeEntitiesInBox(self.destBox)
            level.copyBlocksFrom(tempSchematic, tempSchematic.bounds, self.destBox.origin)
            self.editor.invalidateBox(dirtyBox)

            self.nudgeSelection.perform(recordUndo)

    def undo(self):
        if self.undoSchematic:
            self.editor.level.removeTileEntitiesInBox(self.destBox)
            self.editor.level.removeEntitiesInBox(self.destBox)
            self.editor.level.copyBlocksFrom(self.undoSchematic, self.undoSchematic.bounds, self.dirtyBox().origin)
            self.editor.invalidateBox(self.dirtyBox())
            self.nudgeSelection.undo()


class SelectionTool(EditorTool):
    # selectionColor = (1.0, .9, .9)
    color = (0.7, 0., 0.7)
    surfaceBuild = False
    toolIconName = "selection"
    tooltipText = "Select\nRight-click for options"

    bottomLeftPoint = topRightPoint = None

    bottomLeftColor = (0., 0., 1.)
    bottomLeftSelectionColor = (0.75, 0.62, 1.0)

    topRightColor = (0.89, 0.89, 0.35)
    topRightSelectionColor = (1, 0.99, 0.65)

    nudgePanel = None

    def __init__(self, editor):
        self.editor = editor
        editor.selectionTool = self
        self.selectionPoint = None

        self.optionsPanel = SelectionToolOptions(self)

        self.updateSelectionColor()

    # --- Tooltips ---

    def describeBlockAt(self, pos):
        blockID = self.editor.level.blockAt(*pos)
        blockdata = self.editor.level.blockDataAt(*pos)
        text = "X: {pos[0]}\nY: {pos[1]}\nZ: {pos[2]}\n".format(pos=pos)
        text += "L: {0} S: {1}\n".format(self.editor.level.blockLightAt(*pos), self.editor.level.skylightAt(*pos))
        text += "{name} ({bid}:{bdata})\n".format(name=self.editor.level.materials.names[blockID][blockdata], bid=blockID, pos=pos, bdata=blockdata)
        t = self.editor.level.tileEntityAt(*pos)
        if t:
            text += "TileEntity:\n"
            try:
                text += "{id}: {pos}\n".format(id=t["id"].value, pos=[t[a].value for a in "xyz"])
            except Exception, e:
                text += repr(e)
            if "Items" not in t:
                text += str(t)
        return text

    @property
    def worldTooltipText(self):
        pos, face = self.editor.blockFaceUnderCursor
        if pos is None:
            return
        try:

            size = None
            box = self.selectionBoxInProgress()
            if box:
                size = "{s[0]} W x {s[2]} L x {s[1]} H".format(s=box.size)
                text = size
            if key.get_mods() & KMOD_ALT:
                if size:
                    return size
                elif self.dragResizeFace is not None:
                    return None
                else:

                    return self.describeBlockAt(pos)

                return text.strip()

            else:

                return self.worldTooltipForBlock(pos) or size

        except Exception, e:
            return repr(e)

    def worldTooltipForBlock(self, pos):

        x, y, z = pos
        cx, cz = x / 16, z / 16
        if isinstance(self.editor.level, MCInfdevOldLevel):

            if y == 0:
                try:
                    chunk = self.editor.level.getChunk(cx, cz)
                except ChunkNotPresent:
                    return "Chunk not present."
                if not any(chunk.HeightMap):
                    if self.editor.level.blockAt(x, y, z):
                        return "Chunk HeightMap is incorrect! Please relight this chunk as soon as possible!"
                    else:
                        return "Chunk is present and filled with air."

        block = self.editor.level.blockAt(*pos)
        if block in (alphaMaterials.Chest.ID,
                     alphaMaterials.Furnace.ID,
                     alphaMaterials.LitFurnace.ID,
                     alphaMaterials.Dispenser.ID):
            t = self.editor.level.tileEntityAt(*pos)
            if t:
                containerID = t["id"].value
                if "Items" in t:
                    items = t["Items"]
                    d = defaultdict(int)
                    for item in items:
                        if "id" in item and "Count" in item:
                            d[item["id"].value] += item["Count"].value

                    if len(d):
                        items = sorted((v, k) for (k, v) in d.iteritems())
                        try:
                            top = pymclevel.items.items.findItem(items[0][1]).name
                        except Exception, e:
                            top = repr(e)
                        return "{0} contains {len} items. (Mostly {top}) \n\nDouble-click to edit {0}.".format(containerID, len=len(d), top=top)
                    else:
                        return "Empty {0}. \n\nDouble-click to edit {0}.".format(containerID)
            else:
                return "Double-click to initialize the {0}.".format(alphaMaterials.names[block][0])
        if block == alphaMaterials.MonsterSpawner.ID:

            t = self.editor.level.tileEntityAt(*pos)
            if t:
                id = t["EntityId"].value
            else:
                id = "[Undefined]"

            return "{id} spawner. \n\nDouble-click to edit spawner.".format(id=id)

        if block in (alphaMaterials.Sign.ID,
                     alphaMaterials.WallSign.ID):
            t = self.editor.level.tileEntityAt(*pos)
            if t:
                signtext = u"\n".join(t["Text" + str(x)].value for x in range(1, 5))
            else:
                signtext = "Undefined"
            return "Sign text: \n" + signtext + "\n\n" + "Double-click to edit sign."

        absentTexture = (self.editor.level.materials.blockTextures[block, 0, 0] == materials.NOTEX).all()
        if absentTexture:
            return self.describeBlockAt(pos)

    @alertException
    def selectChunks(self):
        box = self.selectionBox()
        newBox = BoundingBox((box.mincx << 4, 0, box.mincz << 4), (box.maxcx - box.mincx << 4, self.editor.level.Height, box.maxcz - box.mincz << 4))
        self.editor.selectionTool.setSelection(newBox)

    def updateSelectionColor(self):

        self.selectionColor = GetSelectionColor()
        from albow import theme
        theme.root.sel_color = tuple(int(x * 112) for x in self.selectionColor)

    # --- Nudge functions ---

    @alertException
    def nudgeBlocks(self, dir):
        if key.get_mods() & KMOD_SHIFT:
            dir = map(operator.mul, dir, (16, 16, 16))
        op = NudgeBlocksOperation(self.editor, self.selectionBox(), dir)

        self.performWithRetry(op)
        self.editor.addOperation(op)
        self.editor.addUnsavedEdit()

    def nudgeSelection(self, dir):
        if key.get_mods() & KMOD_SHIFT:
            dir = map(operator.mul, dir, (16, 16, 16))

        points = self.getSelectionPoints()
        bounds = self.editor.level.bounds

        inbounds = [map(operator.add, dir, p) in bounds for p in points]
        if not all(inbounds):
            return

        op = NudgeSelectionOperation(self, dir)
        self.performWithRetry(op)
        # self.editor.addOperation(op)

    def nudgePoint(self, p, n):
        if self.selectionBox() is None:
            return
        if key.get_mods() & KMOD_SHIFT:
            n = map(operator.mul, n, (16, 16, 16))
        self.setSelectionPoint(p, map(operator.add, self.getSelectionPoint(p), n))

    def nudgeBottomLeft(self, n):
        return self.nudgePoint(0, n)

    def nudgeTopRight(self, n):
        return self.nudgePoint(1, n)

    # --- Panel functions ---
    def sizeLabelText(self):
        size = self.selectionSize()
        if self.dragResizeFace is not None:
            size = self.draggingSelectionBox().size

        return "{0}W x {2}L x {1}H".format(*size)

    def showPanel(self):
        if self.selectionBox() is None:
            return

        if self.nudgePanel is None:
            self.nudgePanel = Panel()

            self.nudgePanel.bg_color = map(lambda x: x * 0.5, self.selectionColor) + [0.5, ]

            self.bottomLeftNudge = bottomLeftNudge = NudgeButton()
            bottomLeftNudge.nudge = self.nudgeBottomLeft
            bottomLeftNudge.anchor = "brwh"

            bottomLeftNudge.bg_color = self.bottomLeftColor + (0.33,)

            self.topRightNudge = topRightNudge = NudgeButton()
            topRightNudge.nudge = self.nudgeTopRight
            topRightNudge.anchor = "blwh"

            topRightNudge.bg_color = self.topRightColor + (0.33,)

            self.nudgeRow = Row((bottomLeftNudge, topRightNudge))
            self.nudgeRow.anchor = "blrh"
            self.nudgePanel.add(self.nudgeRow)

            self.editor.add(self.nudgePanel)

        if hasattr(self, 'sizeLabel'):
            self.nudgePanel.remove(self.sizeLabel)
        self.sizeLabel = Label(self.sizeLabelText())
        self.sizeLabel.anchor = "twh"
        self.sizeLabel.tooltipText = "{0:n} blocks".format(self.selectionBox().volume)

        # self.nudgePanelColumn = Column( (self.sizeLabel, self.nudgeRow) )
        self.nudgePanel.top = self.nudgePanel.left = 0

        self.nudgePanel.add(self.sizeLabel)
        self.nudgeRow.top = self.sizeLabel.bottom

        self.nudgePanel.shrink_wrap()
        self.sizeLabel.centerx = self.nudgePanel.centerx
        self.nudgeRow.centerx = self.nudgePanel.centerx

        self.nudgePanel.bottom = self.editor.toolbar.top
        self.nudgePanel.centerx = self.editor.centerx

        self.nudgePanel.anchor = "bwh"

        if self.panel is None and self.editor.currentTool in (self, None):
            if self.bottomLeftPoint is not None and self.topRightPoint is not None:
                self.panel = SelectionToolPanel(self)
                self.panel.left = self.editor.left
                self.panel.centery = self.editor.centery
                self.editor.add(self.panel)

    def hidePanel(self):
        self.editor.remove(self.panel)
        self.panel = None

    def hideNudgePanel(self):
        self.editor.remove(self.nudgePanel)
        self.nudgePanel = None

    selectionInProgress = False
    dragStartPoint = None

    # --- Event handlers ---

    def toolReselected(self):
        self.selectOtherCorner()

    def toolSelected(self):
        # if self.clearSelectionImmediately:
        #    self.setSelectionPoints(None)
        self.showPanel()

    def clampPos(self, pos):
        x, y, z = pos
        w, h, l = self.editor.level.Width, self.editor.level.Height, self.editor.level.Length

        if w > 0:
            if x >= w:
                x = w - 1
            if x < 0:
                x = 0
        if l > 0:
            if z >= l:
                z = l - 1
            if z < 0:
                z = 0

        if y >= h:
            y = h - 1
        if y < 0:
            y = 0

        pos = [x, y, z]
        return pos

    @property
    def currentCornerName(self):
        return ("Blue", "Yellow")[self.currentCorner]

    @property
    def statusText(self):
        # return "selectionInProgress {0} clickSelectionInProgress {1}".format(self.selectionInProgress, self.clickSelectionInProgress)
        if self.selectionInProgress:
            pd = self.editor.blockFaceUnderCursor
            if pd:
                p, d = pd
                if self.dragStartPoint == p:
                    if self.clickSelectionInProgress:

                        return "Click the mouse button again to place the {0} selection corner. Press {1} to switch corners.".format(self.currentCornerName, self.hotkey)
                    else:
                        return "Release the mouse button here to place the {0} selection corner. Press {1} to switch corners.".format(self.currentCornerName, self.hotkey)

            if self.clickSelectionInProgress:
                return "Click the mouse button again to place the other selection corner."

            return "Release the mouse button to finish the selection"

        return "Click or drag to make a selection. Drag the selection walls to resize. Click near the edge to drag the opposite wall.".format(self.currentCornerName, self.hotkey)

    clickSelectionInProgress = False

    def endSelection(self):
        self.selectionInProgress = False
        self.clickSelectionInProgress = False
        self.dragResizeFace = None
        self.dragStartPoint = None

    def cancel(self):
        self.endSelection()
        EditorTool.cancel(self)

    dragResizeFace = None
    dragResizeDimension = None
    dragResizePosition = None

    def mouseDown(self, evt, pos, direction):
        # self.selectNone()

        pos = self.clampPos(pos)
        if self.selectionBox() and not self.selectionInProgress:
            face, point = self.boxFaceUnderCursor(self.selectionBox())

            if face is not None:
                self.dragResizeFace = face
                self.dragResizeDimension = self.findBestTrackingPlane(face)

                # point = map(int, point)
                self.dragResizePosition = point[self.dragResizeDimension]

                return

        if self.selectionInProgress is False:
            self.dragStartPoint = pos
        self.selectionInProgress = True

    def mouseUp(self, evt, pos, direction):
        pos = self.clampPos(pos)
        if self.dragResizeFace is not None:
            box = self.selectionBox()
            if box is not None:
                o, m = self.selectionPointsFromDragResize()

                op = SelectionOperation(self, (o, m))
                self.performWithRetry(op)
                self.editor.addOperation(op)

            self.dragResizeFace = None
            return

        if self.editor.viewMode == "Chunk":
            self.clickSelectionInProgress = True

        if self.dragStartPoint is None and not self.clickSelectionInProgress:
            return

        if self.dragStartPoint != pos or self.clickSelectionInProgress:
            op = SelectionOperation(self, (self.dragStartPoint, pos))
            self.performWithRetry(op)
            self.editor.addOperation(op)
            self.selectionInProgress = False
            self.currentCorner = 1
            self.clickSelectionInProgress = False

        else:
            points = self.getSelectionPoints()
            if not all(points):
                points = (pos, pos)  # set both points on the first click
            else:
                points[self.currentCorner] = pos

            if not self.clickSelectionInProgress:
                self.clickSelectionInProgress = True
            else:
                op = SelectionOperation(self, points)
                self.performWithRetry(op)
                self.editor.addOperation(op)

                self.selectOtherCorner()
                self.selectionInProgress = False
                self.clickSelectionInProgress = False

        if self.chunkMode:
            self.editor.selectionToChunks(remove=evt.alt, add=evt.shift)
            self.editor.toolbar.selectTool(8)

    @property
    def chunkMode(self):
        return self.editor.viewMode == "Chunk" or self.editor.currentTool is self.editor.toolbar.tools[8]

    def selectionBoxInProgress(self):
        if self.editor.blockFaceUnderCursor is None:
            return
        pos = self.editor.blockFaceUnderCursor[0]
        if self.selectionInProgress or self.clickSelectionInProgress:
            return self.selectionBoxForCorners(pos, self.dragStartPoint)

# requires a selection
    def dragResizePoint(self):
        # returns a point representing the intersection between the mouse ray
        # and an imaginary plane perpendicular to the dragged face

        pos = self.editor.mainViewport.cameraPosition
        dim = self.dragResizeDimension
        distance = self.dragResizePosition - pos[dim]

        mouseVector = self.editor.mainViewport.mouseVector
        scale = distance / (mouseVector[dim] or 0.0001)
        point = map(lambda a, b: a * scale + b, mouseVector, pos)
        return point

    def draggingSelectionBox(self):
        p1, p2 = self.selectionPointsFromDragResize()
        box = self.selectionBoxForCorners(p1, p2)
        return box

    def selectionPointsFromDragResize(self):
        point = self.dragResizePoint()
#        glColor(1.0, 1.0, 0.0, 1.0)
#        glPointSize(9.0)
#        glBegin(GL_POINTS)
#        glVertex3f(*point)
#        glEnd()
#

#        facebox = BoundingBox(box.origin, box.size)
#        facebox.origin[dim] = self.dragResizePosition
#        facebox.size[dim] = 0
#        glEnable(GL_BLEND)
#
#        drawFace(facebox, dim * 2)
#
#        glDisable(GL_BLEND)
#
        side = self.dragResizeFace & 1
        dragdim = self.dragResizeFace >> 1
        box = self.selectionBox()

        o, m = box.origin, box.maximum
        (m, o)[side][dragdim] = int(floor(point[dragdim] + 0.5))
        m = map(lambda a: a - 1, m)
        return o, m

    def option1(self):
        self.selectOtherCorner()

    _currentCorner = 0

    @property
    def currentCorner(self):
        return self._currentCorner

    @currentCorner.setter
    def currentCorner(self, value):
        self._currentCorner = value & 1
        self.toolIconName = ("selection", "selection2")[self._currentCorner]
        self.editor.toolbar.toolTextureChanged()

    def selectOtherCorner(self):
        self.currentCorner = 1 - self.currentCorner

    showPreviousSelection = SelectSettings.showPreviousSelection.configProperty()
    alpha = 0.25

    def drawToolMarkers(self):

        selectionBox = self.selectionBox()
        if(selectionBox):
            widg = self.editor.find_widget(mouse.get_pos())

            # these corners stay even while using the chunk tool.
            glPolygonOffset(DepthOffset.SelectionCorners, DepthOffset.SelectionCorners)
            lineWidth = 3
            for t, c, n in ((self.bottomLeftPoint, self.bottomLeftColor, self.bottomLeftNudge), (self.topRightPoint, self.topRightColor, self.topRightNudge)):
                if t != None:
                    (sx, sy, sz) = t
                    if self.selectionInProgress:
                        if t == self.getSelectionPoint(self.currentCorner):
                            blockFace = self.editor.blockFaceUnderCursor
                            if blockFace:
                                p, d = blockFace
                                (sx, sy, sz) = p
                        else:
                            sx, sy, sz = self.dragStartPoint

                    # draw a blue or yellow wireframe box at the selection corner
                    r, g, b = c
                    alpha = 0.4
                    try:
                        bt = self.editor.level.blockAt(sx, sy, sz)
                        if(bt):
                            alpha = 0.2
                    except ChunkNotPresent:
                        pass

                    glLineWidth(lineWidth)
                    lineWidth += 1

                    # draw highlighted block faces when nudging
                    if (widg.parent == n or widg == n):
                        glEnable(GL_BLEND)
                        # drawCube(BoundingBox((sx, sy, sz), (1,1,1)))
                        nudgefaces = array([
                               selectionBox.minx, selectionBox.miny, selectionBox.minz,
                               selectionBox.minx, selectionBox.maxy, selectionBox.minz,
                               selectionBox.minx, selectionBox.maxy, selectionBox.maxz,
                               selectionBox.minx, selectionBox.miny, selectionBox.maxz,
                               selectionBox.minx, selectionBox.miny, selectionBox.minz,
                               selectionBox.maxx, selectionBox.miny, selectionBox.minz,
                               selectionBox.maxx, selectionBox.miny, selectionBox.maxz,
                               selectionBox.minx, selectionBox.miny, selectionBox.maxz,
                               selectionBox.minx, selectionBox.miny, selectionBox.minz,
                               selectionBox.minx, selectionBox.maxy, selectionBox.minz,
                               selectionBox.maxx, selectionBox.maxy, selectionBox.minz,
                               selectionBox.maxx, selectionBox.miny, selectionBox.minz,
                               ], dtype=float32)

                        if sx != selectionBox.minx:
                            nudgefaces[0:12:3] = selectionBox.maxx
                        if sy != selectionBox.miny:
                            nudgefaces[13:24:3] = selectionBox.maxy
                        if sz != selectionBox.minz:
                            nudgefaces[26:36:3] = selectionBox.maxz

                        glColor(r, g, b, 0.3)
                        glVertexPointer(3, GL_FLOAT, 0, nudgefaces)
                        glEnable(GL_DEPTH_TEST)
                        glDrawArrays(GL_QUADS, 0, 12)
                        glDisable(GL_DEPTH_TEST)

                        glDisable(GL_BLEND)

                    glColor(r, g, b, alpha)
                    drawCube(BoundingBox((sx, sy, sz), (1, 1, 1)), GL_LINE_STRIP)

            if not (not self.showPreviousSelection and self.selectionInProgress):
                # draw the current selection as a white box.  hangs around when you use other tools.
                glPolygonOffset(DepthOffset.Selection, DepthOffset.Selection)
                color = self.selectionColor + (self.alpha,)
                if self.dragResizeFace is not None:
                    box = self.draggingSelectionBox()
                else:
                    box = selectionBox

                if self.panel and (widg is self.panel.nudgeBlocksButton or widg.parent is self.panel.nudgeBlocksButton):
                    color = (0.3, 1.0, 0.3, self.alpha)
                self.editor.drawConstructionCube(box, color)

                # highlight the face under the cursor, or the face being dragged
                if self.dragResizeFace is None:
                    if self.selectionInProgress or self.clickSelectionInProgress:
                        pass
                    else:
                        face, point = self.boxFaceUnderCursor(box)

                        if face is not None:
                            glEnable(GL_BLEND)
                            glColor(*color)

                            # Shrink the highlighted face to show the click-through edges

                            offs = [s * self.edge_factor for s in box.size]
                            offs[face >> 1] = 0
                            origin = [o + off for o, off in zip(box.origin, offs)]
                            size = [s - off * 2 for s, off in zip(box.size, offs)]

                            cv = self.editor.mainViewport.cameraVector
                            for i in range(3):
                                if cv[i] > 0:
                                    origin[i] -= offs[i]
                                    size[i] += offs[i]
                                else:
                                    size[i] += offs[i]

                            smallbox = FloatBox(origin, size)

                            drawFace(smallbox, face)

                            glColor(0.9, 0.6, 0.2, 0.8)
                            glLineWidth(2.0)
                            drawFace(box, face, type=GL.GL_LINE_STRIP)
                            glDisable(GL_BLEND)
                else:
                    face = self.dragResizeFace
                    point = self.dragResizePoint()
                    dim = face >> 1
                    pos = point[dim]

                    side = face & 1
                    o, m = selectionBox.origin, selectionBox.maximum
                    otherFacePos = (m, o)[side ^ 1][dim]  # ugly
                    direction = (-1, 1)[side]
                    # print "pos", pos, "otherFace", otherFacePos, "dir", direction
                    # print "m", (pos - otherFacePos) * direction
                    if (pos - otherFacePos) * direction > 0:
                        face ^= 1

                    glColor(0.9, 0.6, 0.2, 0.5)
                    drawFace(box, face, type=GL_LINE_STRIP)
                    glEnable(GL_BLEND)
                    glEnable(GL_DEPTH_TEST)

                    drawFace(box, face)
                    glDisable(GL_BLEND)
                    glDisable(GL_DEPTH_TEST)

        pos, direction = self.editor.blockFaceUnderCursor
        x, y, z = pos
        selectionColor = map(lambda a: a * a * a * a, self.selectionColor)

        # draw a colored box representing the possible selection
        otherCorner = self.dragStartPoint
        if self.dragResizeFace is not None:
            self.showPanel()  # xxx do this every frame while dragging because our UI kit is bad

        if ((self.selectionInProgress or self.clickSelectionInProgress) and otherCorner != None):
            glPolygonOffset(DepthOffset.PotentialSelection, DepthOffset.PotentialSelection)

            box = self.selectionBoxForCorners(otherCorner, pos)
            if self.chunkMode:
                box = box.chunkBox(self.editor.level)
                if key.get_mods() & KMOD_ALT:
                    selectionColor = [1., 0., 0.]
            self.editor.drawConstructionCube(box, selectionColor + [self.alpha, ])
        else:
            # don't draw anything at the mouse cursor if we're resizing the box
            if self.dragResizeFace is None:
                box = self.selectionBox()
                if box:
                    face, point = self.boxFaceUnderCursor(box)
                    if face is not None:
                        return
            else:
                return

    def drawToolReticle(self):
        glPolygonOffset(DepthOffset.SelectionReticle, DepthOffset.SelectionReticle)
        pos, direction = self.editor.blockFaceUnderCursor

        # draw a selection-colored box for the cursor reticle
        selectionColor = map(lambda a: a * a * a * a, self.selectionColor)
        r, g, b = selectionColor
        alpha = 0.3

        try:
            bt = self.editor.level.blockAt(*pos)
            if(bt):
##                textureCoords = materials[bt][0]
                alpha = 0.12
        except ChunkNotPresent:
            pass

        # cube sides
        glColor(r, g, b, alpha)
        glDepthMask(False)
        glEnable(GL_BLEND)
        drawCube(BoundingBox(pos, (1, 1, 1)))
        glDepthMask(True)

        drawTerrainCuttingWire(BoundingBox(pos, (1, 1, 1)),
                               (r, g, b, 0.4),
                               (1., 1., 1., 1.0)
                               )

        glDisable(GL_BLEND)

    def setSelection(self, box):
        if box is None:
            self.selectNone()
        else:
            self.setSelectionPoints(self.selectionPointsFromBox(box))

    def selectionPointsFromBox(self, box):
        return (box.origin, map(lambda x: x - 1, box.maximum))

    def selectNone(self):
        self.setSelectionPoints(None)

    def selectAll(self):
        box = self.editor.level.bounds
        op = SelectionOperation(self, self.selectionPointsFromBox(box))
        self.performWithRetry(op)
        self.editor.addOperation(op)

    def deselect(self):
        op = SelectionOperation(self, None)
        self.performWithRetry(op)
        self.editor.addOperation(op)

    def setSelectionPoint(self, pointNumber, newPoint):
        points = self.getSelectionPoints()
        points[pointNumber] = newPoint
        self.setSelectionPoints(points)

    def setSelectionPoints(self, points):
        if points:
            self.bottomLeftPoint, self.topRightPoint = points
        else:
            self.bottomLeftPoint = self.topRightPoint = None

        self._selectionChanged()
        self.editor.selectionChanged()

    def _selectionChanged(self):
        if self.selectionBox():
            self.showPanel()
        else:
            self.hidePanel()
            self.hideNudgePanel()

    def getSelectionPoint(self, pointNumber):
        return (self.bottomLeftPoint, self.topRightPoint)[pointNumber]  # lisp programmers think this doesn't evaluate 'self.topRightPoint' - lol!

    def getSelectionPoints(self):
        return [self.bottomLeftPoint, self.topRightPoint]

    @alertException
    def deleteBlocks(self):
        box = self.selectionBox()
        if None is box:
            return
        if box == box.chunkBox(self.editor.level):
            resp = ask("You are deleting a chunk-shaped selection. Fill the selection with Air, or delete the chunks themselves?", responses=["Fill with Air", "Delete Chunks", "Cancel"])
            if resp == "Delete Chunks":
                self.editor.toolbar.tools[8].destroyChunks(box.chunkPositions)
            elif resp == "Fill with Air":
                self._deleteBlocks()
        else:
            self._deleteBlocks()

    def _deleteBlocks(self, recordUndo=True):
        box = self.selectionBox()
        if None is box:
            return
        op = BlockFillOperation(self.editor, self.editor.level, box, self.editor.level.materials.Air, [])
        with setWindowCaption("DELETING - "):
            self.editor.freezeStatus("Deleting {0} blocks".format(box.volume))
            self.performWithRetry(op, recordUndo)

            self.editor.addOperation(op)
            self.editor.invalidateBox(box)
            self.editor.addUnsavedEdit()

    @alertException
    def deleteEntities(self, recordUndo=True):
        box = self.selectionBox()

        with setWindowCaption("WORKING - "):
            self.editor.freezeStatus("Removing entities...")
            level = self.editor.level
            editor = self.editor

            class DeleteEntitiesOperation(Operation):
                def perform(self, recordUndo=True):
                    self.undoEntities = level.getEntitiesInBox(box)
                    level.removeEntitiesInBox(box)
                    editor.renderer.invalidateEntitiesInBox(box)

                def undo(self):
                    level.removeEntitiesInBox(box)
                    level.addEntities(self.undoEntities)
                    editor.renderer.invalidateEntitiesInBox(box)

            op = DeleteEntitiesOperation()
            self.performWithRetry(op, recordUndo)
            if recordUndo:
                self.editor.addOperation(op)
            self.editor.addUnsavedEdit()

    @alertException
    def analyzeSelection(self):
        box = self.selectionBox()
        self.editor.analyzeBox(self.editor.level, box)

    @alertException
    def cutSelection(self):
        self.copySelection()
        self.deleteBlocks()
        self.deleteEntities(False)

    @alertException
    def copySelection(self):
        schematic = self._copySelection()
        if schematic:
            self.editor.addCopiedSchematic(schematic)

    def _copySelection(self):
        box = self.selectionBox()
        if not box:
            return

        shape = box.size

        self.editor.mouseLookOff()

        print "Clipping: ", shape

        fileFormat = "schematic"
        if box.volume > self.maxBlocks:
            fileFormat = "schematic.zip"

        if fileFormat == "schematic.zip":
            missingChunks = filter(lambda x: not self.editor.level.containsChunk(*x), box.chunkPositions)
            if len(missingChunks):
                if not ((box.origin[0] & 0xf == 0) and (box.origin[2] & 0xf == 0)):
                    if ask("This is an uneven selection with missing chunks. Expand the selection to chunk edges, or copy air within the missing chunks?", ["Expand Selection", "Copy Air"]) == "Expand Selection":
                        self.selectChunks()
                        box = self.selectionBox()

        with setWindowCaption("Copying - "):
            filename = tempfile.mkdtemp(".zip", "mceditcopy")
            os.rmdir(filename)

            status = "Copying {0:n} blocks...".format(box.volume)
            if fileFormat == "schematic":
                schematic = showProgress(status,
                            self.editor.level.extractSchematicIter(box), cancel=True)
            else:
                schematic = showProgress(status,
                            self.editor.level.extractZipSchematicIter(box, filename), cancel=True)
            if schematic == "Canceled":
                return None

            return schematic

    @alertException
    def exportSelection(self):
        schematic = self._copySelection()
        if schematic:
            self.editor.exportSchematic(schematic)


class SelectionOperation(Operation):
    changedLevel = False

    def __init__(self, selectionTool, points):
        self.selectionTool = selectionTool
        self.points = points

    def perform(self, recordUndo=True):
        self.undoPoints = self.selectionTool.getSelectionPoints()
        self.selectionTool.setSelectionPoints(self.points)

    def undo(self):
        points = self.points
        self.points = self.undoPoints
        self.perform()
        self.points = points


class NudgeSelectionOperation (Operation):
    changedLevel = False

    def __init__(self, selectionTool, direction):
        self.selectionTool = selectionTool
        self.direction = direction
        self.oldPoints = selectionTool.getSelectionPoints()

        self.newPoints = map(lambda p: map(operator.add, p, direction), self.oldPoints)

    def perform(self, recordUndo=True):
        self.selectionTool.setSelectionPoints(self.newPoints)

    oldSelection = None

    def undo(self):
        self.selectionTool.setSelectionPoints(self.oldPoints)
