from itertools import chain, product
from xml.etree import ElementTree
from collections import defaultdict
from utils import decode_gid, types, parse_properties, read_points
from constants import *

"""
Class declarations and other important stuff
"""

class TiledElement(object):
    def set_properties(self, node):
        """
        read the xml attributes and tiled "properties" from a xml node and fill
        in the values into the object's dictionary.  Names will be checked to
        make sure that they do not conflict with reserved names.
        """

        # set the attributes reserved for tiled
        [ setattr(self, k, types[str(k)](v)) for (k,v) in node.items() ]
        # set the attributes that are derived from tiled 'properties'
        for k,v in parse_properties(node).items():
            if k in self.reserved:
                msg = "{0} \"{1}\" has a property called \"{2}\""
                print msg.format(self.__class__.__name__, self.name, k, self.__class__.__name__)
                msg = "This name is reserved for {0} objects and cannot be used."
                print msg.format(self.__class__.__name__)
                print "Please change the name in Tiled and try again."
                print v
                raise ValueError

            setattr(self, k, types[str(k)](v))

class TiledMap(TiledElement):
    """
    Contains the tile layers, tile images, object groups, and objects from a
    Tiled TMX map.
    """

    reserved = "version orientation width height tilewidth tileheight properties tileset layer objectgroup".split()

    def __init__(self, filename=None):
        from collections import defaultdict

        TiledElement.__init__(self)
        self.tilesets = []          # list of TiledTileset objects
        self.tilelayers   = []      # list of TiledLayer objects
        self.imagelayers  = []      # list of TiledImageLayer objects
        self.objectgroups = []      # list of TiledObjectGroup objects
        self.all_layers = []        # list of all layers in proper order
        self.tile_properties = {}   # dict of tiles that have metadata
        self.filename = filename

        self.layernames = {}

        # only used tiles are actually loaded, so there will be a difference
        # between the GIDs in the Tile map data (tmx) and the data in this
        # class and the layers.  This dictionary keeps track of that difference.
        self.gidmap = defaultdict(list)

        # should be filled in by a loader function
        self.images = []

        # defaults from the TMX specification
        self.version = 0.0
        self.orientation = None
        self.width = 0                  # width of map in tiles
        self.height = 0                 # height of map in tiles
        self.tilewidth = 0              # width of a tile in pixels
        self.tileheight = 0             # height of a tile in pixels

        self.imagemap = {}  # mapping of gid and trans flags to real gids
        self.maxgid = 1

        if filename: self.load()

    def __repr__(self):
        return "<{0}: \"{1}\">".format(self.__class__.__name__, self.filename)

    def getTileImage(self, x, y, layer):
        """
        return the tile image for this location
        x and y must be integers and are in tile coordinates, not pixel

        return value will be 0 if there is no tile with that location.
        """

        try:
            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
        except (IndexError, ValueError):
            msg = "Coords: ({0},{1}) in layer {2} is not valid."
            raise Exception, msg.format(x, y, layer)
        except TypeError:
            msg = "Tiles must be specified in integers."
            raise TypeError, msg

        else:
            try:
                return self.images[gid]
            except (IndexError, ValueError):
                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}"
                raise Exception, msg.format(x, y, layer, gid)

    def getTileGID(self, x, y, layer):
        """
        return GID of a tile in this location
        x and y must be integers and are in tile coordinates, not pixel
        """

        try:
            return self.tilelayers[int(layer)].data[int(y)][int(x)]
        except (IndexError, ValueError):
            msg = "Coords: ({0},{1}) in layer {2} is invalid"
            raise Exception, msg.format(x, y, layer)

    def getDrawOrder(self):
        """
        return a list of objects in the order that they should be drawn
        this will also exclude any layers that are not set to visible

        may be useful if you have objects and want to control rendering
        from tiled
        """

        raise NotImplementedError

    def getTileImages(self, r, layer):
        """
        return a group of tiles in an area
        expects a pygame rect or rect-like list/tuple

        useful if you don't want to repeatedly call getTileImage
        """

        raise NotImplementedError

    def getObjects(self):
        """
        Return iterator of all the objects associated with this map
        """

        return chain(*(i for i in self.objectgroups))

    def getTileProperties(self, (x, y, layer)):
        """
        return the properties for the tile, if any
        x and y must be integers and are in tile coordinates, not pixel

        returns a dict of there are properties, otherwise will be None
        """

        try:
            gid = self.tilelayers[int(layer)].data[int(y)][int(x)]
        except (IndexError, ValueError):
            msg = "Coords: ({0},{1}) in layer {2} is invalid."
            raise Exception, msg.format(x, y, layer)

        else:
            try:
                return self.tile_properties[gid]
            except (IndexError, ValueError):
                msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}"
                raise Exception, msg.format(x, y, layer, gid)
            except KeyError:
                return None

    def getLayerData(self, layer):
        """
        Return the data for a layer.

        Data is an array of arrays.

        >>> pos = data[y][x]
        """

        try:
            return self.tilelayers[layer].data
        except IndexError:
            msg = "Layer {0} does not exist."
            raise ValueError, msg.format(layer)

    def getTileLocation(self, gid):
        # experimental way to find locations of a tile by the GID

        p = product(xrange(self.width),
                    xrange(self.height),
                    xrange(len(self.tilelayers)))

        return [ (x,y,l) for (x,y,l) in p
               if self.tilelayers[l].data[y][x] == gid ]

    def getTilePropertiesByGID(self, gid):
        try:
            return self.tile_properties[gid]
        except KeyError:
            return None

    def setTileProperties(self, gid, d):
        """
        set the properties of a tile by GID.
        must use a standard python dict as d
        """

        try:
            self.tile_properties[gid] = d
        except KeyError:
            msg = "GID #{0} does not exist."
            raise ValueError, msg.format(gid)

    def getTilePropertiesByLayer(self, layer):
        """
        Return a list of tile properties (dict) in use in this tile layer.
        """

        try:
            layer = int(layer)
        except:
            msg = "Layer must be an integer.  Got {0} instead."
            raise ValueError, msg.format(type(layer))

        p = product(range(self.width),range(self.height))
        layergids = set( self.tilelayers[layer].data[y][x] for x, y in p )

        props = []
        for gid in layergids:
            try:
                props.append((gid, self.tile_properties[gid]))
            except:
                continue

        return props

    def registerGID(self, real_gid, flags=0):
        """
        used to manage the mapping of GID between the tmx data and the internal
        data.

        number returned is gid used internally
        """

        if real_gid:
            try:
                return self.imagemap[(real_gid, flags)][0]
            except KeyError:
                # this tile has not been encountered before, or it has been
                # transformed in some way.  make a new GID for it.
                gid = self.maxgid
                self.maxgid += 1
                self.imagemap[(real_gid, flags)] = (gid, flags)
                self.gidmap[real_gid].append((gid, flags))
                return gid

        else:
            return 0

    def mapGID(self, real_gid):
        """
        used to lookup a GID read from a TMX file's data
        """

        try:
            return self.gidmap[int(real_gid)]
        except KeyError:
            return None
        except TypeError:
            msg = "GIDs must be an integer"
            raise TypeError, msg

    def loadTileImages(self, filename):
        raise NotImplementedError

    def load(self):
        """
        parse a map node from a tiled tmx file
        """
        etree = ElementTree.parse(self.filename).getroot()
        self.set_properties(etree)

        # initialize the gid mapping
        self.imagemap[(0,0)] = 0

        for node in etree.iter():
            if node.tag == 'tileset':
                self.tilesets.append(TiledTileset(self, node))
            else:
                if node.tag == 'layer':
                    self.addTileLayer(TiledLayer(self, node))
                elif node.tag == 'imagelayer':
                    self.addImageLayer(TiledImageLayer(self, node))
                elif node.tag == 'objectgroup':
                    self.objectgroups.append(TiledObjectGroup(self, node))
                    self.all_layers.append(self.objectgroups[-1])

        # "tile objects", objects with a GID, have need to have their
        # attributes set after the tileset is loaded
        for o in self.getObjects():
            p = self.getTilePropertiesByGID(o.gid)
            if p:
                o.__dict__.update(p)

        # tilesets need to be parsed after all the layer in order
        # to get all the tile properties properly
        for t in self.tilesets:
            t.parse(t.node)
            t.node = False

    def addTileLayer(self, layer):
        """
        Add a TiledLayer layer object to the map.
        """

        if not isinstance(layer, TiledLayer):
            msg = "Layer must be an TiledLayer object.  Got {0} instead."
            raise ValueError, msg.format(type(layer))

        self.tilelayers.append(layer)
        self.all_layers.append(layer)
        self.layernames[layer.name] = layer

    def addImageLayer(self, layer):
        """
        Add a TiledImageLayer layer object to the map.
        """

        if not isinstance(layer, TiledImageLayer):
            msg = "Layer must be an TiledImageLayer object.  Got {0} instead."
            raise ValueError, msg.format(type(layer))

        self.imagelayers.append(layer)
        self.all_layers.append(layer)
        self.layernames[layer.name] = layer

    def getTileLayerByName(self, name):
        """
        Return a TiledLayer object with the name.
        This is case-sensitive.
        """

        try:
            return self.layernames[name]
        except KeyError:
            msg = "Layer \"{0}\" not found."
            raise ValueError, msg.format(name)

    def getTileLayerOrder(self):
        """
        Return a list of the map's layers in drawing order.
        """

        return list(self.tilelayers)

    @property
    def visibleTileLayers(self):
        """
        Returns a list of TileLayer objects that are set 'visible'.

        Layers have their visibility set in Tiled.  Optionally, you can over-
        ride the Tiled visibility by creating a property named 'visible'.
        """

        return [layer for layer in self.tilelayers if layer.visible]

class TiledImageLayer(TiledElement):
    reserved = "source name width height opacity visible".split()

    def __init__(self, parent, node):
        TiledElement.__init__(self)
        self.parent = parent

        # defaults from the specification
        self.name = None
        # width and height in tiles meaningless according to the specification
        #self.width = None
        #self.height = None
        self.opacity = 1
        self.visible = 1

        self.parse(node)

    def parse(self, node):
        """
        basic implementation of imagelayers.

        only works with non embebed images.
        """

        self.name = node.get('name', None)
        self.opacity = node.get('opacity', 1)
        self.visible = node.get('visible', 1)

        # image node things
        image_node = node.find('image', None)
        if image_node != None:

            self.source = image_node.get('source', None)
            self.trans = image_node.get('trans', None)
            self.width = image_node.get('width', None)
            self.height = image_node.get('height', None)

    def __repr__(self):
        return "<{0}: \"{1}\">".format(self.__class__.__name__, self.name)

class TiledTileset(TiledElement):
    reserved = "firstgid source name tilewidth tileheight spacing margin image tile properties".split()

    def __init__(self, parent, node):
        TiledElement.__init__(self)
        self.parent = parent

        # defaults from the specification
        self.firstgid = 0
        self.source = None
        self.name = None
        self.tilewidth = 0
        self.tileheight = 0
        self.spacing = 0
        self.margin = 0
        self.tiles = {}

        self.node = node
        #~ self.parse(node)

    def __repr__(self):
        return "<{0}: \"{1}\">".format(self.__class__.__name__, self.name)

    def parse(self, node):
        """
        parse a tileset element and return a tileset object and properties for
        tiles as a dict

        a bit of mangling is done here so that tilesets that have external
        TSX files appear the same as those that don't
        """
        import os

        # if true, then node references an external tileset
        source = node.get('source', False)
        if source:
            if source[-4:].lower() == ".tsx":

                # external tilesets don't save this, store it for later
                self.firstgid = int(node.get('firstgid'))

                # we need to mangle the path - tiled stores relative paths
                dirname = os.path.dirname(self.parent.filename)
                path = os.path.abspath(os.path.join(dirname, source))
                try:
                    node = ElementTree.parse(path).getroot()
                except IOError:
                    msg = "Cannot load external tileset: {0}"
                    raise Exception, msg.format(path)

            else:
                msg = "Found external tileset, but cannot handle type: {0}"
                raise Exception, msg.format(self.source)

        self.set_properties(node)

        # since tile objects [probably] don't have a lot of metadata,
        # we store it separately in the parent (a TiledMap instance)
        for child in node.getiterator('tile'):
            real_gid = int(child.get("id"))
            p = parse_properties(child)
            p['width'] = self.tilewidth
            p['height'] = self.tileheight
            for gid, flags in self.parent.mapGID(real_gid + self.firstgid):
                self.parent.setTileProperties(gid, p)

        image_node = node.find('image')
        self.source = image_node.get('source')
        self.trans = image_node.get("trans", None)

class TiledLayer(TiledElement):
    reserved = "name x y width height opacity properties data".split()

    def __init__(self, parent, node):
        TiledElement.__init__(self)
        self.parent = parent
        self.data = []

        # defaults from the specification
        self.name = None
        self.opacity = 1.0
        self.visible = True

        self.parse(node)

    def __repr__(self):
        return "<{0}: \"{1}\">".format(self.__class__.__name__, self.name)

    def parse(self, node):
        """
        parse a layer element
        """
        from utils import group
        from itertools import product, imap
        from struct import unpack
        import array

        self.set_properties(node)

        data = None
        next_gid = None

        data_node = node.find('data')

        encoding = data_node.get("encoding", None)
        if encoding == "base64":
            from base64 import decodestring
            data = decodestring(data_node.text.strip())

        elif encoding == "csv":
            next_gid = imap(int, "".join(
                line.strip() for line in data_node.text.strip()
                ).split(","))

        elif encoding:
            msg = "TMX encoding type: {0} is not supported."
            raise Exception, msg.format(encoding)

        compression = data_node.get("compression", None)
        if compression == "gzip":
            from StringIO import StringIO
            import gzip
            fh = gzip.GzipFile(fileobj=StringIO(data))
            data = fh.read()
            fh.close()

        elif compression == "zlib":
            import zlib
            data = zlib.decompress(data)

        elif compression:
            msg = "TMX compression type: {0} is not supported."
            raise Exception, msg.format(str(attr["compression"]))

        # if data is None, then it was not decoded or decompressed, so
        # we assume here that it is going to be a bunch of tile elements
        # TODO: this will probably raise an exception if there are no tiles
        if encoding == next_gid is None:
            def get_children(parent):
                for child in parent.findall('tile'):
                    yield int(child.get('gid'))

            next_gid = get_children(data_node)

        elif data:
            # data is a list of gids. cast as 32-bit ints to format properly
            # create iterator to efficiently parse data
            next_gid=imap(lambda i:unpack("<L", "".join(i))[0], group(data, 4))

        # using bytes here limits the layer to 256 unique tiles
        # may be a limitation for very detailed maps, but most maps are not
        # so detailed.
        [ self.data.append(array.array("H")) for i in xrange(self.height) ]

        for (y, x) in product(xrange(self.height), xrange(self.width)):
            self.data[y].append(self.parent.registerGID(*decode_gid(next(next_gid))))

class TiledObjectGroup(TiledElement, list):
    """
    Stores TiledObjects.  Supports any operation of a normal list.
    """
    reserved = "name color x y width height opacity object properties".split()

    def __init__(self, parent, node):
        TiledElement.__init__(self)
        self.parent = parent

        # defaults from the specification
        self.name = None

        self.parse(node)

    def __repr__(self):
        return "<{0}: \"{1}\">".format(self.__class__.__name__, self.name)

    def parse(self, node):
        """
        parse a objectgroup element and return a object group
        """

        self.set_properties(node)

        for child in node.findall('object'):
            o = TiledObject(self.parent, child)
            self.append(o)

class TiledObject(TiledElement):
    reserved = "name type x y width height gid properties polygon polyline image".split()

    def __init__(self, parent, node):
        TiledElement.__init__(self)
        self.parent = parent

        # defaults from the specification
        self.name = None
        self.type = None
        self.x = 0
        self.y = 0
        self.width = 0
        self.height = 0
        self.gid = 0

        self.parse(node)

    def __repr__(self):
        return "<{0}: \"{1}\">".format(self.__class__.__name__, self.name)

    def parse(self, node):
        self.set_properties(node)

        # correctly handle "tile objects" (object with gid set)
        if self.gid:
            self.gid = self.parent.registerGID(self.gid)


#
#  Copyright 2001 - 2006 Ludek Smid [http://www.ospace.net/]
#
#  This file is part of Pygame.UI.
#
#  Pygame.UI is free software; you can redistribute it and/or modify
#  it under the terms of the Lesser GNU General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.
#
#  Pygame.UI is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  Lesser GNU General Public License for more details.
#
#  You should have received a copy of the Lesser GNU General Public License
#  along with Pygame.UI; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

import sys
import string
import pygame.scrap
from pygame.locals import *
from Const import *
from WordUtils import *
from Widget import Widget, registerWidget
from Fonts import *
import pygame.key

from ige import log

# keys mapping
mapping = {
	K_KP0: '0', K_KP1: '1', K_KP2: '2', K_KP3: '3', K_KP4: '4',
	K_KP5: '5', K_KP6: '6', K_KP7: '7', K_KP8: '8', K_KP9: '9',
}

class Text(Widget):
	"""Text edit widget."""

	def __init__(self, parent, **kwargs):
		Widget.__init__(self, parent)
		# data
		self.__dict__['text'] = [""]
		self.__dict__['offsetRow'] = 0
		self.__dict__['cursorRow'] = 0
		self.__dict__['cursorColumn'] = 0
		self.__dict__['action'] = None
		self.__dict__['editable'] = 1
		self.__dict__['vertScrollbar'] = None
		self.__dict__['selStart'] = None
		self.__dict__['selEnd'] = None
		# flags
		self.processKWArguments(kwargs)
		parent.registerWidget(self)

	def toClipboard(self):
		payload = string.join(self.text, "\n")

		if ("linux" in sys.platform):
			pygame.scrap.put("STRING", payload)
			pygame.scrap.put("STRING", payload)
		else:
			try:
				from Tkinter import Tk
				window = Tk()
				window.withdraw()
				window.clipboard_clear()
				window.clipboard_append(payload)
				window.destroy()
			except ImportError:
				log.debug("Failed to write to clipboard -- Tkinter is not present on system.")

	def draw(self, surface):
		self.theme.drawText(surface, self)
		return self.rect

	def attachVScrollbar(self, scrollbar):
		self.vertScrollbar = scrollbar
		scrollbar.subscribeAction("*", self)
		scrollbar.action = "onScroll"
		scrollbar.slider.min = 0
		scrollbar.slider.max = len(self.text) + 100
		scrollbar.slider.position = self.offsetRow
		scrollbar.slider.shown = 1

	def onScroll(self, widget, action, data):
		self.offsetRow = self.vertScrollbar.slider.position

	def onCursorChanged(self):
		# force redraw
		self.cursorColumn += 1
		self.cursorColumn -= 1
		# super
		Widget.onCursorChanged(self)

	def deleteSelection(self):
		# we have some selection
		if self.selStart != None and self.selEnd != None:
			# Reorder selStart and selEnd if needed
			if self.selStart and (self.selEnd[0] < self.selStart[0] or (self.selEnd[0] == self.selStart[0] and self.selEnd[1] < self.selStart[1])):
				temp = self.selStart
				self.selStart = self.selEnd
				self.selEnd = temp
			# one-line selection
			if self.selStart[0] == self.selEnd[0]:
				# text before selection
				textBefore = self.text[self.selEnd[0]][:self.selStart[1]]
				# text after selection
				textAfter = self.text[self.selEnd[0]][self.selEnd[1]:]
				# store new text without selection
				self.text[self.selEnd[0]] = u'%s%s' % (textBefore, textAfter)
			else:
				# handle multi-line selection
				# delete end of selection
				self.text[self.selEnd[0]] = self.text[self.selEnd[0]][self.selEnd[1]:]
				# delete fully selected rows
				start = self.selStart[0]+1
				for row in range(start,self.selEnd[0]):
					self.text.pop(start)
				# delete selection on first row
				self.text[self.selStart[0]] = self.text[self.selStart[0]][:self.selStart[1]]
				# join the rows that are spanned
				self.text[self.selStart[0]] = u'%s%s' % (self.text[self.selStart[0]], self.text[self.selStart[0]+1])
				del self.text[self.selStart[0]+1]
			# move cursor to selection begining
			self.cursorColumn = self.selStart[1]
			self.cursorRow = self.selStart[0]
			# clear selection
			self.selStart = self.selEnd = None

	def processKeyUp(self, evt):
		# consume K_RETURN (acceptButton on Window will not work)
		# can be choosable on construction?
		if evt.key == K_RETURN:
			return NoEvent
		else:
			return Widget.processKeyUp(self, evt)

	def processKeyDown(self, evt):
		if not self.editable:
			return Widget.processKeyDown(self, evt)

		# process keys
		if evt.key == K_BACKSPACE:
			if self.selStart != None:
				self.deleteSelection()
			elif self.cursorColumn > 0:
				self.text[self.cursorRow] = u'%s%s' % (self.text[self.cursorRow][:self.cursorColumn - 1], self.text[self.cursorRow][self.cursorColumn:])
				self.cursorColumn -= 1
			else:
				if self.cursorRow > 0:
					self.cursorColumn = len(self.text[self.cursorRow - 1])
					self.text[self.cursorRow - 1] = u'%s%s' % (self.text[self.cursorRow - 1], self.text[self.cursorRow])
					del self.text[self.cursorRow]
					self.cursorRow -= 1

		elif evt.key == K_DELETE:
			if self.selStart != None:
				self.deleteSelection()
			elif self.cursorColumn < len(self.text[self.cursorRow]):
				self.text[self.cursorRow] = u'%s%s' % (self.text[self.cursorRow][:self.cursorColumn], self.text[self.cursorRow][self.cursorColumn + 1:])
			elif self.cursorRow < len(self.text) - 1:
				self.text[self.cursorRow] = u'%s%s' % (self.text[self.cursorRow], self.text[self.cursorRow + 1])
				del self.text[self.cursorRow + 1]

		elif evt.key == K_ESCAPE:
			self.app.setFocus(None)

		elif evt.key == K_LEFT:
			if evt.mod & KMOD_SHIFT:
				if self.selStart == None:
					self.selStart = (self.cursorRow,self.cursorColumn)
			if evt.mod & KMOD_CTRL:
				# move one word left
				# take words on line
				words = splitter(self.text[self.cursorRow])
				if len(words) == 0:
					#move to previous line
					if self.cursorRow > 0:
						self.cursorRow -= 1
						words2 = splitter(self.text[self.cursorRow])
						if len(words2) > 0:
							#move cursor to begining of last word
							self.cursorColumn = words2[-1][1]
						else:
							#no words on line, so move cursor to the end of line
							self.cursorColumn = len(self.text[self.cursorRow])
					else:
						#we are on first line, so move cursor to begining of line
						self.cursorColumn = 0
				idxs = getIdxFromColumn(words, self.cursorColumn)
				if idxs != (-1, -1):
					if idxs[0] == -1:
						#cursor is before first word, jump to beginig of last word on previous line
						if self.cursorRow > 0:
							self.cursorRow -= 1
							words2 = splitter(self.text[self.cursorRow])
							if len(words2) > 0:
								#move cursor to begining of last word
								self.cursorColumn = words2[-1][1]
							else:
								#no words on line, so move cursor to the end of line
								self.cursorColumn = len(self.text[self.cursorRow])
						else:
							#we are on first line, so move cursor to begining of line
							self.cursorColumn = 0
					elif idxs[0] == idxs[1]:
						#we are inside word, so move cursor to begining of word
						self.cursorColumn = words[idxs[0]][1]
					elif idxs[1] == -1:
						#cursor is after last word, we must jump to begining of last word
						self.cursorColumn = words[idxs[0]][1]
					else:
						#cursor is between words, we must jump to begining of left word
						self.cursorColumn = words[idxs[0]][1]
			elif self.cursorColumn > 0: self.cursorColumn -= 1
			elif self.cursorRow > 0:
				self.cursorRow -= 1
				self.cursorColumn = len(self.text[self.cursorRow])
			if evt.mod & KMOD_SHIFT:
				self.selEnd = (self.cursorRow,self.cursorColumn)
				if self.selStart == self.selEnd:
					self.selStart = self.selEnd = None
			else:
				self.selStart = self.selEnd = None

		elif evt.key == K_RIGHT:
			if evt.mod & KMOD_SHIFT:
				if self.selStart == None:
					self.selStart = (self.cursorRow,self.cursorColumn)
			if evt.mod & KMOD_CTRL:
				# move one word right
				# take words on line
				words = splitter(self.text[self.cursorRow])
				if len(words) == 0:
					#move to next line
					if self.cursorRow < len(self.text) - 1:
						self.cursorRow += 1
						words2 = splitter(self.text[self.cursorRow])
						if len(words2) > 0:
							self.cursorColumn = words2[0][1]
						else:
							#on next line are only separators (or is empty), so move to column 0
							self.cursorColumn = 0
					else:
						#we are on last line, so move cursor to end of line
						self.cursorColumn = len(self.text[self.cursorRow])
				idxs = getIdxFromColumn(words, self.cursorColumn)
				if idxs != (-1, -1):
					if idxs[0] == idxs[1] or self.cursorColumn == words[idxs[1]][1]:
						#cursor is inside of word or is on begining of word, so move on begining of next word
						if idxs[1] + 1 < len(words):
							#there is next word on line
							self.cursorColumn = words[idxs[1] + 1][1]
						else:
							#we must jump to begining first word on next line
							if self.cursorRow < len(self.text) - 1:
								self.cursorRow += 1
								words2 = splitter(self.text[self.cursorRow])
								if len(words2) > 0:
									self.cursorColumn = words2[0][1]
								else:
									#on next line are only separators (or is empty), so move to column 0
									self.cursorColumn = 0
							else:
								#we are on last line, so move cursor to end of line
								self.cursorColumn = len(self.text[self.cursorRow])
					elif idxs[0] == -1:
						#cursor is before first word, jump to beginig of fist word
						self.cursorColumn = words[idxs[1]][1]
					elif idxs[1] == -1:
						#cursor is after last word, we must jump to begining first word on next line
						if self.cursorRow < len(self.text) - 1:
							self.cursorRow += 1
							words2 = splitter(self.text[self.cursorRow])
							if len(words2) > 0:
								self.cursorColumn = words2[0][1]
							else:
								#on next line are only separators (or is empty), so move to column 0
								self.cursorColumn = 0
						else:
							#we are on last line, so move cursor to end of line
							self.cursorColumn = len(self.text[self.cursorRow])
					else:
						#cursor is between words
						self.cursorColumn = words[idxs[1]][1]
			elif self.cursorColumn < len(self.text[self.cursorRow]): self.cursorColumn += 1
			elif self.cursorRow < len(self.text):
				# move to the next row
				if self.cursorRow < len(self.text) - 1:
					self.cursorRow += 1
					self.cursorColumn = 0
			if evt.mod & KMOD_SHIFT:
				self.selEnd = (self.cursorRow,self.cursorColumn)
				if self.selStart == self.selEnd:
					self.selStart = self.selEnd = None
			else:
				self.selStart = self.selEnd = None

		elif evt.key == K_UP:
			if evt.mod & KMOD_SHIFT:
				if self.selStart == None:
					self.selStart = (self.cursorRow,self.cursorColumn)

			if self.cursorRow > 0:
				self.cursorRow -= 1
				self.cursorColumn = min(self.cursorColumn, len(self.text[self.cursorRow]))

			if self.cursorRow - self.offsetRow < 0:
				self.vertScrollbar.onButton1(self, "", "")

			if evt.mod & KMOD_SHIFT:
				self.selEnd = (self.cursorRow,self.cursorColumn)
				if self.selStart == self.selEnd:
					self.selStart = self.selEnd = None
			else:
				self.selStart = self.selEnd = None

		elif evt.key == K_DOWN:
			if evt.mod & KMOD_SHIFT:
				if self.selStart == None:
					self.selStart = (self.cursorRow,self.cursorColumn)

			if self.cursorRow < len(self.text) - 1:
				self.cursorRow += 1
				self.cursorColumn = min(self.cursorColumn, len(self.text[self.cursorRow]))

			if self.cursorRow - self.offsetRow >= self.theme.getTextDrawLines(self):
				self.vertScrollbar.onButton2(self, "", "")

			if evt.mod & KMOD_SHIFT:
				self.selEnd = (self.cursorRow,self.cursorColumn)
				if self.selStart == self.selEnd:
					self.selStart = self.selEnd = None
			else:
				self.selStart = self.selEnd = None

		elif evt.key == K_TAB:
			pass

		elif evt.key == K_END:
			if evt.mod & KMOD_SHIFT:
				if self.selEnd != None:
					self.selEnd = (self.selEnd[0], len(self.text[self.cursorRow]))
				else:
					self.selStart = (self.cursorRow, self.cursorColumn)
					self.selEnd = (self.cursorRow, len(self.text[self.cursorRow]))
			else:
				self.selStart = self.selEnd = None

			self.cursorColumn = len(self.text[self.cursorRow])

		elif evt.key == K_HOME:
			if evt.mod & KMOD_SHIFT:
				if self.selStart != None:
					self.selStart = (self.selStart[0], 0)
				else:
					self.selStart = (self.cursorRow, 0)
					self.selEnd = (self.cursorRow, self.cursorColumn)
			else:
				self.selStart = self.selEnd = None

			self.cursorColumn = 0


# coding: utf-8

# python
from datetime import datetime  #, timedelta

# 3rd-party
import pytest

# this app
from timetra.diary.models import Fact
from timetra.diary.storage import Storage, UnknownActivity, AmbiguousActivityName

FIXTURE_ROOT = 'tests/fixtures'
CACHE_ROOT = 'tests/.testcache'

class MockBackend:
    def __init__(self, initial_data):
        self.data = initial_data

    def add(self, fact):
        self.data.append(fact)

    def get(self, date_time):
        for fact in self.data:
            if fact.since == date_time:
                return fact

    def find(self, since=None, until=None, activity=None, description=None,
             tag=None):
        for fact in self.data:
            # NOTE: overlapping facts (that partially fit) are not considered matching
            if since and fact.since < since:
                continue
            if until and fact.until > until:
                continue
            if activity and fact.activity != activity:
                continue
            if description and description not in fact.description:
                continue
            if tag and tag not in fact.tags:
                continue
            yield fact

    def update(self, fact, values):
        for item in self.data:
            if item.since == fact.since and item.activity == fact.activity:
                item.update(values)
                return True
        return False

    def delete(self, since, activity):
        for i, fact in enumerate(self.data):
            if fact.since == since and fact.activity == activity:
                del self.data[i]
                return True
        return False

    def get_latest(self):
        return list(sorted(self.data, key=lambda x: x.since))[-1]

    def get_known_activities(self):
        seen = {}
        for fact in self.data:
            pair = (fact.category, fact.activity)
            if pair not in seen:
                seen[pair] = 1
        return [{'activity': a, 'category': c} for c,a in sorted(seen)]

class TestStorage:

    def setup_method(self, method):
        mock_backend = MockBackend([
            Fact(
                activity='timetra',
                category='foss',
                since=datetime(2012,5,24, 15,59),
                until=datetime(2012,5,24, 18,38),
                description='Renamed and refactored Timetra: the script becomes a project',
                tags=['in-ekb'],
            ),
            Fact(activity='walk',
                 category='errands',
                 since=datetime(2013,4,13, 4,28,54),
                 until=datetime(2013,4,13, 4,34,23),
                 description='walked the dog',
                 tags=['with-dog', 'in-ekb'],
                 hamster_fact_id=44047,
            )
        ])
        self.storage = Storage(backend=mock_backend)

    def test_find_facts(self):
        xs = list(self.storage.find())
        assert len(xs) == 2
        assert xs[0].activity == 'timetra'
        assert xs[0].description == 'Renamed and refactored Timetra: the script becomes a project'
        assert xs[0].since == datetime(2012,5,24, 15,59)

        assert xs[1].activity == 'walk'
        assert xs[1].since == datetime(2013,4,13, 4,28,54)

    def test_find_facts_since(self):
        xs = list(self.storage.find(since=datetime(2012,1,1)))
        assert len(xs) == 2

        xs = list(self.storage.find(since=datetime(2013,1,1)))
        assert len(xs) == 1

        xs = list(self.storage.find(since=datetime(2014,1,1)))
        assert len(xs) == 0

    def test_find_facts_until(self):
        xs = list(self.storage.find(until=datetime(2012,1,1)))
        assert len(xs) == 0

        xs = list(self.storage.find(until=datetime(2013,1,1)))
        assert len(xs) == 1

        xs = list(self.storage.find(until=datetime(2014,1,1)))
        assert len(xs) == 2

    def test_find_facts_activity(self):
        xs = list(self.storage.find(activity='whatchamacallit'))
        assert len(xs) == 0

        xs = list(self.storage.find(activity='walk'))
        assert len(xs) == 1
        assert 'with-dog' in xs[0].tags

    def test_find_facts_description(self):
        xs = list(self.storage.find(description='whatchamacallit'))
        assert len(xs) == 0

        xs = list(self.storage.find(description='dog'))
        assert len(xs) == 1

        xs = list(self.storage.find(description='the'))
        assert len(xs) == 2

    def test_find_facts_tags(self):
        xs = list(self.storage.find(tag='whatchamacallit'))
        assert len(xs) == 0

        xs = list(self.storage.find(tag='with-dog'))
        assert len(xs) == 1

        xs = list(self.storage.find(tag='in-ekb'))
        assert len(xs) == 2

    def test_get_fact_latest(self):
        fact = self.storage.get_latest()
        assert fact.activity == 'walk'

    def test_get_fact_by_date_and_time(self):
        f1 = self.storage.get(datetime(2012,5,24, 15,59))
        f2 = self.storage.get(datetime(2013,4,13, 4,28,54))

        assert f1.activity == 'timetra'
        assert f2.activity == 'walk'

    def test_add_fact(self):
        xs = list(self.storage.find())
        assert len(xs) == 2

        self.storage.add(Fact(
            category='leisure',
            activity='party',
            since=datetime(2012,12,31, 23,55),
            until=datetime(2013,1,1, 1,30),
            description='Happy New Year!',
            tags=['with-friends']
        ))

        xs = list(self.storage.find())
        assert len(xs) == 3
        assert xs[-1].activity == 'party'

    def test_update_fact(self):
        xs = list(self.storage.find())
        initial = xs[0]
        assert initial.activity == 'timetra'

        # activity name and start time are enough to identify the fact
        self.storage.update(initial, {'activity': 'argh'})

        xs = list(self.storage.find())
        updated = xs[0]
        assert updated.activity == 'argh'

    def test_delete_fact(self):
        xs = list(self.storage.find())
        assert len(xs) == 2

        # activity name and start time are enough to identify the fact
        spec = Fact(activity='timetra', since=datetime(2012,5,24, 15,59))
        self.storage.delete(spec)

        xs = list(self.storage.find())
        assert len(xs) == 1

    def test_resolve_activity(self):
        "Find a single activity matching given mask"

        with pytest.raises(UnknownActivity) as excinfo:
            self.storage.resolve_activity('x')

        with pytest.raises(AmbiguousActivityName) as excinfo:
            self.storage.resolve_activity('a')
        assert 'timetra' in str(excinfo)
        assert 'walk' in str(excinfo)

        assert self.storage.resolve_activity('wa') == {'category': 'errands', 'activity': 'walk'}

    def test_find_activities(self):
        "Collect distinct activity/category pairs from the data"
        activities = self.storage.get_known_activities()
        assert activities == [
            {'category': 'errands', 'activity': 'walk'},
            {'category': 'foss', 'activity': 'timetra'},
        ]

        self.storage.add(Fact(activity='music', category='create'))
        activities = self.storage.get_known_activities()
        assert activities == [
            {'category': 'create', 'activity': 'music'},
            {'category': 'errands', 'activity': 'walk'},
            {'category': 'foss', 'activity': 'timetra'},
        ]

        # activities with same names can co-exist in different categories
        self.storage.add(Fact(activity='music', category='consume'))
        activities = self.storage.get_known_activities()
        assert activities == [
            {'category': 'consume', 'activity': 'music'},
            {'category': 'create', 'activity': 'music'},


#
#    Copyright (C) 2016 Dang Duong
#
#    This file is part of Open Tux World.
#
#    Open Tux World is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Open Tux World is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Open Tux World.  If not, see <http://www.gnu.org/licenses/>.
#
from bge import logic

MOVE_LOWER_AIM = "lower_aim"
ACTION_UPPER_AIM = "upper_aim"
ACTION_UPPER_AIM_ARMED = "upper_aim_armed"

def aim_move(own, forward, back, left, right, started_aim, current_frame):
    if forward:
        if left:
            if current_frame <= 70:
                current_frame = 70

            elif current_frame >= 120:
                current_frame = 80
            own.playAction(MOVE_LOWER_AIM, current_frame, current_frame+1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame += 1

        elif right:
            if current_frame <= 130:
                current_frame = 130

            elif current_frame >= 180:
                current_frame = 140
            own.playAction(MOVE_LOWER_AIM, current_frame, current_frame+1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame += 1

        else:
            if current_frame <= 10:
                current_frame = 10

            elif current_frame >= 60:
                current_frame = 20
            own.playAction(MOVE_LOWER_AIM, current_frame, current_frame+1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame += 1

    elif back:
        if left:
            if current_frame >= 190:
                current_frame = 190

            elif current_frame <= 140:
                current_frame = 180
            own.playAction(MOVE_LOWER_AIM, current_frame, current_frame-1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame -= 1

        elif right:
            if current_frame >= 130:
                current_frame = 130

            elif current_frame <= 80:
                current_frame = 120
            own.playAction(MOVE_LOWER_AIM, current_frame, current_frame-1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame -= 1

        else:
            if current_frame >= 70:
                current_frame = 70

            elif current_frame <= 20:
                current_frame = 60
            own.playAction(MOVE_LOWER_AIM, current_frame, current_frame-1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame -= 1

    elif left:
        if current_frame >= 250:
            current_frame = 250

        elif current_frame <= 200:
            current_frame = 240
        own.playAction(MOVE_LOWER_AIM, current_frame, current_frame-1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
        current_frame -= 1

    elif right:
        if current_frame <= 190:
            current_frame = 190

        elif current_frame >= 240:
            current_frame = 200
        own.playAction(MOVE_LOWER_AIM, current_frame, current_frame+1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
        current_frame += 1

    else:
        if started_aim == 0:
            own.playAction(MOVE_LOWER_AIM, 0, 10, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            started_aim = 1

        else:
            own.playAction(MOVE_LOWER_AIM, 10, 10, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)

    own["started_aim"] = started_aim
    own["current_frame"] = current_frame

def move(own, parent, moving, started_aim, current_frame):
    if moving:
        if "player" in parent and own["run_fast"]:
            if current_frame <= 0:
                current_frame = 0
            elif current_frame >= 42:
                current_frame = 14
            own.playAction("run", current_frame, current_frame+2, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame += 2

        elif parent["run"]:
            if current_frame <= 0:
                current_frame = 0
            elif current_frame >= 42:
                current_frame = 14
            own.playAction("run", current_frame, current_frame+1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame += 1

        else:
            if current_frame <= 0:
                current_frame = 0
            elif current_frame >= 77:
                current_frame = 7
            own.playAction("walk", current_frame, current_frame+1, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
            current_frame += 1

    else:
        own.playAction("walk", 0, 0, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
        current_frame = 0

    own["started_aim"] = started_aim
    own["current_frame"] = current_frame

def action_attack(own, parent, fall, action_name, upper_current_frame, hit, hit_released):

    if fall:
        hit_released = False
        if upper_current_frame <= 0:
            own.playAction(action_name, 0, 0, layer=1, play_mode=logic.KX_ACTION_MODE_PLAY)
            upper_current_frame = 0
        else:
            own.playAction(action_name, upper_current_frame, upper_current_frame-1, layer=1, play_mode=logic.KX_ACTION_MODE_PLAY)
            upper_current_frame -= 1

    else:
        if hit:
            if upper_current_frame < 30:
                if upper_current_frame < 10:
                    upper_current_frame = 10
                else:
                    hit_released = upper_current_frame == 24
                own.playAction(action_name, upper_current_frame, upper_current_frame+1, layer=2, play_mode=logic.KX_ACTION_MODE_PLAY)
                upper_current_frame += 1
                hit = True
            else:
                upper_current_frame = 10
                hit = False

        elif own["aim"]:
            if upper_current_frame >= 10:
                own.playAction(action_name, 10, 10, layer=1, play_mode=logic.KX_ACTION_MODE_PLAY)
                upper_current_frame = 10
            else:
                own.playAction(action_name, upper_current_frame, upper_current_frame+1, layer=1, play_mode=logic.KX_ACTION_MODE_PLAY)
                upper_current_frame += 1

        else:
            if upper_current_frame <= 0:
                own.playAction(action_name, 0, 0, layer=1, play_mode=logic.KX_ACTION_MODE_PLAY)
                upper_current_frame = 0
            else:
                own.playAction(action_name, upper_current_frame, upper_current_frame-1, layer=1, play_mode=logic.KX_ACTION_MODE_PLAY)
                upper_current_frame -= 1

    parent["hit"] = hit
    parent["hit_released"] = hit_released
    own["upper_current_frame"] = upper_current_frame

def action(cont):
    own = cont.owner
    parent = own.parent
    if parent["health"] < 1:
        own.state = logic.KX_STATE2
    else:
        action_name = ACTION_UPPER_AIM_ARMED if parent["item"] != 0 else ACTION_UPPER_AIM
        action_attack(own, parent, parent["fall"], action_name, own["upper_current_frame"], parent["hit"], parent["hit_released"])

def main(cont):
    own = cont.owner
    parent = own.parent
    if parent["fall"]:
        own.playAction("fall", 7, 7, layer=0, play_mode=logic.KX_ACTION_MODE_PLAY)
        own["started_aim"] = 0

    else:
        forward = own["forward"]
        back = own["back"]


# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.

# XEP-0030: Service Discovery

import nbxmpp
from nbxmpp.namespaces import Namespace
from nbxmpp.structs import StanzaHandler
from nbxmpp.errors import StanzaError
from nbxmpp.errors import is_error

from gajim.common import app
from gajim.common.events import ServerDiscoReceived
from gajim.common.events import MucDiscoUpdate
from gajim.common.modules.util import as_task
from gajim.common.modules.base import BaseModule

class Discovery(BaseModule):

    _nbxmpp_extends = 'Discovery'
    _nbxmpp_methods = [
        'disco_info',
        'disco_items',
    ]

    def __init__(self, con):
        BaseModule.__init__(self, con)

        self.handlers = [
            StanzaHandler(name='iq',
                          callback=self._answer_disco_info,
                          typ='get',
                          ns=Namespace.DISCO_INFO),
            StanzaHandler(name='iq',
                          callback=self._answer_disco_items,
                          typ='get',
                          ns=Namespace.DISCO_ITEMS),
        ]

        self._account_info = None
        self._server_info = None

    @property
    def account_info(self):
        return self._account_info

    @property
    def server_info(self):
        return self._server_info

    def discover_server_items(self):
        server = self._con.get_own_jid().domain
        self.disco_items(server, callback=self._server_items_received)

    def _server_items_received(self, task):
        try:
            result = task.finish()
        except StanzaError as error:
            self._log.warning('Server disco failed')
            self._log.error(error)
            return

        self._log.info('Server items received')
        self._log.debug(result)
        for item in result.items:
            if item.node is not None:
                # Only disco components
                continue
            self.disco_info(item.jid, callback=self._server_items_info_received)

    def _server_items_info_received(self, task):
        try:
            result = task.finish()
        except StanzaError as error:
            self._log.warning('Server item disco info failed')
            self._log.warning(error)
            return

        self._log.info('Server item info received: %s', result.jid)
        self._parse_transports(result)
        try:
            self._con.get_module('MUC').pass_disco(result)
            self._con.get_module('HTTPUpload').pass_disco(result)
            self._con.get_module('Bytestream').pass_disco(result)
        except nbxmpp.NodeProcessed:
            pass

        app.ged.raise_event(ServerDiscoReceived())

    def discover_account_info(self):
        own_jid = self._con.get_own_jid().bare
        self.disco_info(own_jid, callback=self._account_info_received)

    def _account_info_received(self, task):
        try:
            result = task.finish()
        except StanzaError as error:
            self._log.warning('Account disco info failed')
            self._log.warning(error)
            return

        self._log.info('Account info received: %s', result.jid)

        self._account_info = result

        self._con.get_module('MAM').pass_disco(result)
        self._con.get_module('PEP').pass_disco(result)
        self._con.get_module('PubSub').pass_disco(result)
        self._con.get_module('Bookmarks').pass_disco(result)
        self._con.get_module('VCardAvatars').pass_disco(result)

        self._con.get_module('Caps').update_caps()

    def discover_server_info(self):
        # Calling this method starts the connect_maschine()
        server = self._con.get_own_jid().domain
        self.disco_info(server, callback=self._server_info_received)

    def _server_info_received(self, task):
        try:
            result = task.finish()
        except StanzaError as error:
            self._log.error('Server disco info failed')
            self._log.error(error)
            return

        self._log.info('Server info received: %s', result.jid)

        self._server_info = result

        self._con.get_module('SecLabels').pass_disco(result)
        self._con.get_module('Blocking').pass_disco(result)
        self._con.get_module('VCardTemp').pass_disco(result)
        self._con.get_module('Carbons').pass_disco(result)
        self._con.get_module('HTTPUpload').pass_disco(result)
        self._con.get_module('Register').pass_disco(result)

        self._con.connect_machine(restart=True)

    def _parse_transports(self, info):
        for identity in info.identities:
            if identity.category not in ('gateway', 'headline'):
                continue

            self._log.info('Found transport: %s %s %s',
                           info.jid, identity.category, identity.type)

            jid = str(info.jid)
            if jid not in app.transport_type:
                app.transport_type[jid] = identity.type

            if identity.type in self._con.available_transports:
                self._con.available_transports[identity.type].append(jid)
            else:
                self._con.available_transports[identity.type] = [jid]

    def _answer_disco_items(self, _con, stanza, _properties):
        from_ = stanza.getFrom()
        self._log.info('Answer disco items to %s', from_)

        if self._con.get_module('AdHocCommands').command_items_query(stanza):
            raise nbxmpp.NodeProcessed

        node = stanza.getTagAttr('query', 'node')
        if node is None:
            result = stanza.buildReply('result')
            self._con.connection.send(result)
            raise nbxmpp.NodeProcessed

        if node == Namespace.COMMANDS:
            self._con.get_module('AdHocCommands').command_list_query(stanza)
            raise nbxmpp.NodeProcessed

    def _answer_disco_info(self, _con, stanza, _properties):
        from_ = stanza.getFrom()
        self._log.info('Answer disco info %s', from_)
        if str(from_).startswith('echo.'):
            # Service that echos all stanzas, ignore it
            raise nbxmpp.NodeProcessed

        if self._con.get_module('AdHocCommands').command_info_query(stanza):
            raise nbxmpp.NodeProcessed

    @as_task
    def disco_muc(self,
                  jid,
                  request_vcard=False,
                  allow_redirect=False):

        _task = yield

        self._log.info('Request MUC info for %s', jid)

        result = yield self._nbxmpp('MUC').request_info(
            jid,
            request_vcard=request_vcard,
            allow_redirect=allow_redirect)

        if is_error(result):
            raise result

        if result.redirected:
            self._log.info('MUC info received after redirect: %s -> %s',
                           jid, result.info.jid)
        else:
            self._log.info('MUC info received: %s', result.info.jid)

        app.storage.cache.set_last_disco_info(result.info.jid, result.info)

        if result.vcard is not None:
            avatar, avatar_sha = result.vcard.get_avatar()
            if avatar is not None:


#!/usr/bin/python

#! SELinux Policy Editor, a simple editor for SELinux policies
#! Copyright (C) 2006 Yuichi Nakamura
#!
#! This program is free software; you can redistribute it and/or modify
#! it under the terms of the GNU General Public License as published by
#! the Free Software Foundation; either version 2 of the License, or
#! (at your option) any later version.
#!
#! This program is distributed in the hope that it will be useful,
#! but WITHOUT ANY WARRANTY; without even the implied warranty of
#! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#! GNU General Public License for more details.
#!
#! You should have received a copy of the GNU General Public License
#! along with this program; if not, write to the Free Software
#! Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

from xml.dom.minidom import parse, parseString
import os
import sys
import string
import re
import getopt
import gettext
from seedit import *
from stat import *
gMatchpathcon = "/usr/sbin/matchpathcon -n"
gAusearch = "/sbin/ausearch"

#key: inode, value:path
gInoPathDir = dict()

#key: inode, value:path
# stores inode and full path obtained from chdir syscall log
gChdirInoPathDir = dict()

#key: pid ,value:path
gChrootStatus = dict()

def clearDict():
    gInoPathDir.clear()
    gChdirInoPathDir.clear()

def errorExit(msg):
    sys.stderr.write(msg)
    sys.exit(1)
    return

#
def popNum(buf):
    maxPop=4 #Max number of pop, if delim of auditlog is changed, all log is poped, so need max nunber of pop.
    result=1

    if len(buf)==0:
        return 0

    if len(buf)<4:
        maxPop=len(buf)

    for i in range(maxPop):
        line = buf[-1-i]
        if line[0] == '-': #Delim of auditlog
            break
        result = result + 1

    return result

'''
loadPolicyFlag is for -l option
'''
def readLog(input, loadPolicyFlag):
    lines=input.readlines()
    lineBuf=[]
    if loadPolicyFlag:
        reg = re.compile("avc:.*granted.*{.*load_policy.*}")
        reg2= re.compile("type=DAEMON_START.*auditd.*start")
        reg3 =re.compile("type=SYSCALL.*syscall=12")

        for line in lines:
            m3 = reg3.search(line)
            if m3:
                #Eleminate unneeded logs(syscall=12 for unconfined domains)
                if getSubjDomain(line) in ("unconfined_t", "system_crond_t"):
                    n = popNum(line) # Count number of related log
                    if  n > 0:
                        for lp in range(n):
                            linebuf.pop()

                    continue

            lineBuf.append(line)
            m = reg.search(line)
            m2 = reg2.search(line)
            if m or m2:
                del lineBuf
                lineBuf=[]
        del lines
        lines=lineBuf

    return lines

def readSPDLSpec(filename):
    """
    Parse spdl spec XML file, return DOM document
    """
    try:
        fh = open(filename)
    except:
        sys.stderr.write("Input file open error:%s" % filename)
        sys.exit(1)

    try:
        domdoc = parseString(fh.read())

    except:
        fh.close()
        sys.stderr.write("XML Parse Error: %s" % filename)
        sys.exit(1)

    fh.close()
    return domdoc

def getInput(opts):
    input=sys.stdin

    for opt,arg in opts:
        if opt in ("-d", "--dmesg"):
            try:
                input=os.popen("dmesg", "r")
            except:
                errorExit("/bin/dmesg can not be used\n")
        if opt in ("-i", "--input"):
            try:
                input=open(arg, "r")
            except:
                errorExit("Input file open error:"+arg+"\n")
        if opt in ("-a","--audit"):
            try:
                input = os.popen("/sbin/ausearch -m avc,syscall,daemon_start")
            except:
                errorExit("Warning ausearch is not available for your system input is /bin/dmesg")
                try:
                    input=os.popen("dmesg", "r")
                except:
                    errorExit("/bin/dmesg can not be used\n")

    return input

def getDomain(line):
    """
    get domain from log entry
    """
    domain = ""
    m = re.compile("scontext=\S+").search(line)
    if m:
        list = string.split(m.group(),":")
        domain = list[2]

    return domain

def getSubjDomain(line):
    """
    get domain from log entry
    """
    domain = ""
    m = re.compile("subj=\S+").search(line)
    if m:
        list = string.split(m.group(),":")
        try:
            domain = list[2]
        except:
            domain =""
    return domain

def getPid(line):
    """
    get Pid from log entry
    """

    m=re.compile("\spid=\d+").search(line)
    if m:
        pid = string.split(m.group(),"=").pop()
        return pid

    return "-1"

def getInode(line):
    m=re.compile("\sino=\d+").search(line)
    if m:
        ino = string.split(m.group(),"=").pop()
        return string.atoi(ino)
    m=re.compile("\sinode=\d+").search(line)
    if m:
        ino = string.split(m.group(),"=").pop()
        return string.atoi(ino)

    return -1

def getName(line):
    m=re.compile("\sname=\".+\"").search(line)
    if m:
        name = string.split(m.group(),"=").pop()
        name = string.replace(name,"\"","")
        return name
    m=re.compile("\spath=\".+\"").search(line)
    if m:
        name = string.split(m.group(),"=").pop()
        name = string.replace(name,"\"","")
        return name
    return ""

def getAuditId(line):
    """
    get Audit event ID in log entry
    """
    m = re.compile("audit\(\d+\.\d+:\d+").search(line)
    if m:
        id = string.split(m.group(),":").pop()
        return id
    else:
        return None

    return None

def getUniqueAuditId(line):
    """
    get conbination of time and Audit event ID in log entry
    It is unique
    """
    m = re.compile("audit\(\d+\.\d+:\d+").search(line)
    if m:
        id = m.group()
        return id
    else:
        return None

    return None

def guessPathByLocate(line):
    """
    Guess path by locate command
    """
    ino = getInode(line)
    name = getName(line)
    comm = "locate "+name
    print "##Guessing fullpath by locate:%s" % (line)

    result=os.popen(comm)
    locates = result.readlines()
    for path in locates:
        path = string.replace(path,"\n","")
        try:
            s = os.lstat(path)
            if ino==s[1]:
                return path

        except:
            pass
    return ""

#Get path name considering chroot
def getPathWithChroot(name, inode,pid, ppid):
    candidatePath=[]

    candidatePath.append(os.path.normpath(name))

    if gChrootStatus.has_key(pid) : #may chroot
        cpath = gChrootStatus[pid]+"/"+name
        candidatePath.append(os.path.normpath(cpath))

    if gChrootStatus.has_key(ppid):
        cpath = gChrootStatus[ppid]+"/"+name
        candidatePath.append(os.path.normpath(cpath))

    for c in candidatePath:
        if inode == getInodeByStat(c):
            return c

    return ""

# return all candidate path with chroot
def getCandidatePathWithChroot(name, inode,pid, ppid):
    candidatePath=[]
    candidatePath.append(os.path.normpath(name))

    if gChrootStatus.has_key(pid) : #may chroot
        cpath = gChrootStatus[pid]+"/"+name
        candidatePath.append(os.path.normpath(cpath))

    if gChrootStatus.has_key(ppid):
        cpath = gChrootStatus[ppid]+"/"+name
        candidatePath.append(os.path.normpath(cpath))

    return candidatePath

##Number of PATH Entry
def countPATHEntry(logs):
    i = 0
    for l in logs:
        if(string.find(l,"name=")>=0 or string.find(l,"path=")>=0):
            i = i+1
    return i

##Guess full path from PATH Entry
def getPATHEntry(logs,inode):
    numPATH=countPATHEntry(logs)
    if numPATH==0:
        return ""

    for l in logs:
        m = re.compile(":.*avc.*:").search(l)
        if m:
            continue
        m = re.compile("path=\S+").search(l)
        if m:
            path = string.split(m.group(),"=").pop()
            path = string.replace(path,"\"","")
            return path

        if (string.find(l,"name=")>0):
            m = re.compile("name=\S+").search(l)
            path = string.split(m.group(),"=").pop()
            path = string.replace(path,"\"","")
            if path == "(null)":
                if numPATH>1:
                    continue
                else:
                    return ""
            if numPATH==1 or inode==-1:
                return path

            ##Multiple PATH Entry
            inodeInPATH=getInode(l)
            if inodeInPATH == inode:
                return path

    return ""

def getCWDEntry(logs):
    for l in logs:

        if(string.find(l,"type=CWD")>=0):
            m=re.compile("cwd=\S+").search(l)
            if m:
                cwd = string.split(m.group(),"=").pop()
                #remove "
                cwd = string.replace(cwd,"\"","")
                return cwd
    return ""

def jointCWDPATH(cwd,path):

    path = cwd+"/"+path
    path = os.path.normpath(path)
    realpath = getPathWithChroot(path, inode, pid,ppid)
    if realpath=="//":
        return ""
    if realpath=="":
        return path
    return realpath

def guessPathByPATHEntry(lines,index):

    """
    First, guess full path by PATH entry
    And return guessed path
    When can not be guessed, "" is returned
    """

    line = lines[index]
    id = getAuditId(line)
    pid = getPid(line)
    ppid = getPpid(lines, index)
    inode = getInode(line)
    domain = getDomain(line)
    if(id):
        logs = getRelatedLog(lines,index)

        path =""
        cwd=""
    else:
        print "Warning no auid in line:%s" % (line)
        return ""

    path = getPATHEntry(logs,inode)

    if path=="":
        return ""

    if path[0]=="/":
        realpath = getPathWithChroot(path, inode,pid,ppid)
        if realpath =="":
            #print "Warning: inode number does not match %s" % (logs)
            #print "All candidate paths"
            #print getCandidatePathWithChroot(path,inode,pid,ppid)
            return path
        else:
            return realpath
    else:
        print "#####"+path
        #need to joint CWD
        cwd = getCWDEntry(logs)
        if cwd=="":
            print "Warning: no CWD %s"
            print logs
            return ""
        path = cwd+"/"+path
        path = os.path.normpath(path)
        realpath = getPathWithChroot(path, inode, pid,ppid)
        if realpath=="//":
            return ""
        if realpath=="":
            #print "Warning: inode number does not match %s" % (logs)
            #print "All candidate paths"
            #print getCandidatePathWithChroot(path,inode,pid,ppid)
            return path
        return realpath

    return ""

def makePathFromType(type):
    type = re.sub("^dir_","", type)
    path = string.replace(type,"_t","")
    path=string.replace(path,"_","/")
    path = "/"+path
    try:
        os.stat(path)
    except:
        #return "notexist_"+path
        return ""
    return path

def getInodeByStat(path):
    if os.path.exists(path):
        s = os.stat(path)
        return s.st_ino
    else:
        return 0

def updateInoPath(path,line):
    if path=="":
        return

    ino = getInode(line)
    secclass = getEqualValue(line,"tclass")
    if secclass == "dir": #when secclass dir,inode number is incorrect(it is for directory) in log
        ino = getInodeByStat(path)

    if ino:
        gInoPathDir[ino]=path
def guessPathByInoDir(line):
    ino = getInode(line)

    if ino:
        if gInoPathDir.has_key(ino):
            return gInoPathDir[ino]

    return ""

def guessFilePath(rule,lines,index):
    """
    guess full path information
    """
    line = lines[index]
    if rule["type"] in gExcLabelList:
        return rule["type"]

    path = guessPathByInoDir(line)
    if path=="":
        path= guessPathByPATHEntry(lines,index)

    if gCross == False:
        if path =="":
            path = guessPathByLocate(line)

    updateInoPath(path,line)
    return path

def matchPermTagList(perm,secclass,permTagList):
    """
    find <permission> tag lists from |permTagList|
    whose value is |perm| and its neighboring <secclass> value is |secclass|
    """

    matchedPermTagList=[]
    for permTag in permTagList:
        if (perm == getAttr(permTag,"value")):
            secclassTagList=permTag.parentNode.getElementsByTagName("secclass")
            secclassList=[]
            for tag in secclassTagList:
                secclassList.append(getAttr(tag,"value"))

            if secclass in secclassList:
                matchedPermTagList.append(permTag)

    return matchedPermTagList

def appendResult(list, str):
    if str not in list:
        list.append(str)
    return list

def findFilePermission(doc,rule,allowtype):
    """
    find file permission using specification in XML
    |security| is security level of outputted policy, high or low can be specified
    """
    result=[]
    security = "low"

    if gHighSecurityFlag==True:
        security ="high"

    if security=="high":
        if rule["secclass"]=="dir" and "write" in rule["permission"]:
            if security=="high":
                result = appendResult(result, "c")
                result = appendResult(result, "r")
                result = appendResult(result, "s")
            else:
                result = appendResult(result, "w")
                result = appendResult(result, "r")
                result = appendResult(result, "s")
            return result
        elif rule["secclass"]=="dir" and "remove_name" in rule["permission"]:
            result = appendResult(result, "dummy")
            return result

    integrateTagList=doc.getElementsByTagName("integrate")
    allowfileNode=None
    for integrateTag in integrateTagList:
        if(getAttr(integrateTag,"value")==allowtype):
            allowfileNode=integrateTag
            break

    permTagList=allowfileNode.getElementsByTagName("permission")
    permList=rule["permission"]
    secclass=rule["secclass"]

    for perm in permList:
        matchedPermTagList=matchPermTagList(perm, rule["secclass"], permTagList)
        for matchedPermTag in matchedPermTagList:
            if checkInternal(matchedPermTag,doc):
                continue
            optionNode= matchedPermTag.parentNode.parentNode.parentNode

            if allowtype=="allowtty" or allowtype=="allowpts":
                option=getAttr(optionNode,"value")
                if option not in result:
                    result.append(option)
            else:
                p = getAttr(optionNode,"value")
                if (security=="low"):
                    if p in ("o","e","c","t","a"):
                        continue
                if (security=="high"):
                    if(p=="w"):
                        continue

                if p not in result:
                    result.append(p)
                    if p in ("w", "x","o", "a", "c", "e"):
                        result = appendResult(result, "r")
                        result = appendResult(result, "s")
                    if p == "r":
                        result = appendResult(result, "s")

    return result

def genAllowfs(rule,domdoc):
    """
    return Dict
    ruletype:   type of SPDL element, this case, allowfs
    domain:     domain
    fsname: name of filesystem
    permission: list of permission
    """
    spRuleList=[]

    spRule= dict()
    spRule["ruletype"]="allowfs"
    spRule["domain"]= rule["domain"]

    if rule["type"] in gDomainList:
        if rule["domain"]==rule["type"]:
            spRule["fsname"]="proc_pid_self"
        else:
            spRule["fsname"]="proc_pid_other"
    else:
        spRule["fsname"] = string.replace(rule["type"],"_t","")

    permList = findFilePermission(domdoc,rule,"allowfile")
    if not permList:
        permList = findFilePermission(domdoc,rule,"allowdev")

    spRule["permission"] = permList

    spRuleList.append(spRule)

    if permList:
        return spRuleList
    else:
        return None

"""
Example:
path:/usr/share/foo/bar
name:foo
returns /usr/share/foo

Example:
path:/usr/foo/share/foo/bar
name:foo
returns /usr/foo/share/foo

Error: returns None
"""
def findNameInPath(path,name):

    (head,tail)=os.path.split(path)

    while head!="/":
        if os.path.basename(head)==name:
            return  head
        (head,tail)=os.path.split(head)

    return None

def genFileAllow(rule,lines,index,domdoc):

    return __genFileAllow(rule,lines,index,domdoc)

def __genFileAllow(rule,lines,index,domdoc):
    """
    return Dict
    ruletype:   type of SPDL element, this case, allowfile
    domain:     domain
    path:       full path
    name:       only file name not fullpath
    permission: list of permission
    """
    spRuleList=[]
    spRule = dict()
    line = lines[index]

    path = guessFilePath(rule,lines,index)

    #for lnk_file class, need special treatment to obtain real fullpath
    if rule["secclass"]=="lnk_file":
        if rule["type"]=="bin_sh_t":
            #for /bin/sh, sometimes fails to guess
            if rule["name"]=="sh":
                path ="/bin/sh"
        elif not os.path.islink(path):
            if path:
                p2 = findNameInPath(path,rule["name"])
                if p2:
                    path=p2
        elif os.path.islink(path):
            if "create" in rule["permission"]:
                ##if creating link, now we have real full path
                pass
            else:
                p = os.readlink(path)
                if rule["name"] == os.path.basename(p):
                    path = p
                else:
                    path = guessPathByLocate(line)
                    updateInoPath(path,line)

    else:
        if path:
            if path[0]=="/":
                path = os.path.realpath(path)

    permList = findFilePermission(domdoc,rule,"allowfile")
    if not permList:
        permList = findFilePermission(domdoc,rule,"allowdev")
    if not permList:
        return []

    if rule["type"] == "security_t":
        return None

    spRule["ruletype"]="allowfile"
    spRule["domain"]=rule["domain"]
    spRule["type"]=rule["type"]
    spRule["name"]=rule["name"]
    spRule["secclass"]=rule["secclass"]

    ##make grab
    if rule["secclass"] == "dir":
        pass
    elif os.path.islink(path):
        pass
    elif os.path.isdir(path):
        path = path + "/*"
    #convert /home -> ~/
    if re.search("^/home/", path):
        spRule["homepath"]=path #path before ~
        path = re.sub("/home/[^/]+","~",path)
        if path =="~":
            path ="~/"

    spRule["path"] = path
    if not os.path.isdir(path):
        permList = appendResult(permList,"r");

    spRule["permission"] = permList

    spRuleList.append(spRule)

    return spRuleList

def __genFileAllowCross(rule,lines,index,domdoc):
    """
    return Dict
    ruletype:   type of SPDL element, this case, allowfile
    domain:     domain
    path:       full path
    name:       only file name not fullpath
    permission: list of permission
    """
    spRuleList=[]
    spRule = dict()
    line = lines[index]

    permList = findFilePermission(domdoc,rule,"allowfile")
    if not permList:
        permList = findFilePermission(domdoc,rule,"allowdev")
    if not permList:
        return []

    if rule["type"] == "security_t":
        return None

    spRule["ruletype"]="allowfile"
    spRule["domain"]=rule["domain"]
    spRule["type"]=rule["type"]
    spRule["path"]=rule["name"]
    if rule.has_key("path"):
        spRule["path"]=rule["path"]
    spRule["secclass"]=rule["secclass"]

    spRule["permission"] = permList

    spRuleList.append(spRule)

    return spRuleList

def checkInternal(permissionTag,domdoc):
    """
    This is to avoid to output SPDL rules that is internally used
    check parent <option> tag for <permission>
    if <option internal="yes" return True
    """
    n = findParentTag(permissionTag,domdoc,"option")

    if getAttr(n,"internal")=="yes":
        return True

    if getAttr(permissionTag,"nomatch")=="yes":
        return True

    return False

def findParentTag(node,domdoc,tagName):
    """
    find parent  tag  named |tagName| for |node|
    """
    while  node is not domdoc  and node.nodeName!=tagName:
        node = node.parentNode

    if node is domdoc and node.nodeName!=tagName:
        return None

    return node

def genExceptionalRule(rule,line,domdoc):
    """
    Generate rules, that can not be obtained from spdl_spec.xml file
    """

    spRuleList=[]

    #allowpriv unlabel
    if rule["type"] in gUnlabeledTypes:
        oneRule=dict()
        oneRule["domain"]=rule["domain"]
        oneRule["type"]=rule["type"]
        oneRule["ruletype"]="allowpriv"
        oneRule["option"]="unlabel"
        oneRule["suboption"]=""
        spRuleList.append(oneRule)

    return spRuleList

def genAllownet(rule,line,domdoc):
    """
    return list of Dict
    dict represents spdl rule to be outputted
    ruletype:   type of SPDL element, defined in <integrate value=
    domain:     domain
    type: type
    option:    <option value=
    suboption:  <option suboption=
    src: source port number
    dest: dest port number
    """
    spRuleList=[]

    integrateTagList=domdoc.getElementsByTagName("integrate")
    permTagList=[]
    for t in integrateTagList:
        ruleName=getAttr(t,"value")
        if ruleName =="allownet":
            permTagList.extend(t.getElementsByTagName("permission"))

    for perm in rule["permission"]:
        list=matchPermTagList(perm,rule["secclass"],permTagList)
        if list:
            for l in list:
                if checkInternal(l,domdoc):
                    continue

                oneRule = dict()
                oneRule["ruletype"]="allownet"
                oneRule["domain"]=rule["domain"]
                oneRule["type"]=rule["type"]
                oneRule["permission"] =perm
                n=findParentTag(l,domdoc,"option")
                if n==None:
                    oneRule["option"]=""
                    oneRule["suboption"]=""
                else:
                    option = getAttr(n,"value")
                    oneRule["option"]= option
                    oneRule["suboption"]=getAttr(n,"suboption")
                    src = getEqualValue(line,"src")
                    dest = getEqualValue(line,"dest")
                    oneRule["src"]= src
                    oneRule["dest"] = dest

                    if option=="net" and src!="":
                        if int(src) < 1024 and getAttr(l,"value")=="name_bind":
                            continue
                spRuleList.append(oneRule)

    return spRuleList

def printResult(outList):

    ## to avoid suggesting same rules
    # key: filename+rule
    # value List of tuple (filename, line, rule)
    outputStr = dict()
    for l in outList:
        (filename, line, rule) = l
        key = filename+rule

        if not outputStr.has_key(key):
            outputStr[key]=[]
        outputStr[key].append((filename,line,rule))

    for key in outputStr.keys():
        list=outputStr[key]
        sys.stdout.write("-------------------------\n")
        sys.stdout.write(_("#SELinux deny log:\n"))
        for l in list:
            (filename, line, rule) = l
            sys.stdout.write("%s" % line)
        sys.stdout.write(_("#Suggested configuration\n"))
        sys.stdout.write(_("File %s: \n%s\n") %(filename, rule))
        sys.stdout.write("-------------------------\n\n")

def outputResult(outputDir,outputFileFlag,outList,verbose):

    #key: filename
    #value List of tuple (line, rule)
    outputStr = dict()

    #sort by filename
    for l in outList:
        (filename, line, rule) = l
        if not outputStr.has_key(filename):
            outputStr[filename]=[]
        outputStr[filename].append((line,rule))

    for file in outputStr.keys():

        output=sys.stdout
        output.write("####"+file+"\n")
        list=outputStr[file]
        for l in list:
            (line, rule)=l
            if verbose:
                output.write("#Log\n"+line+"#Rule\n")
            output.write(rule+"\n")
        output.write("###End of %s\n\n" % file)

    if outputFileFlag:
        sys.stdout.write("Are you sure to add ? (y/[n]\n")
        line=sys.stdin.readline()
        if string.find(line,"y")==-1 and string.find(line,"Y")==-1:
            return
        sys.stdout.write("Saving..\n")
        for file in outputStr.keys():
            #Output to file
            fullPath=outputDir+"/"+file
            try:
                exist=True
                os.stat(fullPath)
            except:
                exist=False
                sys.stderr.write("Warning:Output File %s does not exist.\n" % fullPath)
            if exist:
                fp = open(fullPath,"r")

                tmpFile=fullPath+".tmp"

                try:
                    outfp=open(tmpFile,"w")
                except:
                    sys.stderr.write("Warning:Output File %s open error\n" % tmpFile)

                buf=fp.readline()
                while string.find(buf,"}")==-1 and buf :
                    outfp.write(buf)
                    buf=fp.readline()
                    list=outputStr[file]
                for l in list:
                    (line,rule)=l
                    if verbose:
                        outfp.write("#Log\n#"+line)
                    if re.search("notexist_",rule):
                        outfp.write("#"+rule+"\n")
                    else:
                        outfp.write(rule+"\n")
                outfp.write("}\n")
                fp.close()
                outfp.close()
                os.rename(tmpFile,fullPath)

def genAllowttyPtsCommon(rule,domdoc,generalList,roleTypeList,ruletype):
    spRuleList=[]
    vcsList=["vcs_device_t"]

    if rule["type"] not in generalList and  rule["type"] not in roleTypeList and rule["type"] not in vcsList:
        return []

    integrateTagList=domdoc.getElementsByTagName("integrate")
    permTagList=[]
    for t in integrateTagList:
        ruleName=getAttr(t,"value")
        if ruleName == ruletype:
            permTagList.extend(t.getElementsByTagName("permission"))

    for perm in rule["permission"]:
        list=matchPermTagList(perm,rule["secclass"],permTagList)
        if list:
            for l in list:
                if checkInternal(l,domdoc):
                    continue
                oneRule = dict()
                oneRule["ruletype"]=ruletype
                oneRule["domain"]=rule["domain"]
                if rule["type"] in generalList:
                    oneRule["target"]="general"
                if rule["type"] in vcsList:
                    oneRule["target"]="vcs"
                    ruletype="allowtty"
                else:
                    if ruletype=="allowtty":
                        oneRule["target"] = re.sub("_tty_device_t$","_r", rule["type"])
                    if ruletype=="allowpts":
                        oneRule["target"] = re.sub("_devpts_t$","_r",rule["type"])

                permList = findFilePermission(domdoc,rule,ruletype)
                oneRule["permission"] = permList
                spRuleList.append(oneRule)

    return spRuleList

def genAllowtty(rule,line,domdoc):
    spRuleList=[]

    generalTtyTypeList=("devtty_t", "tty_device_t")
    roleTtyTypeList=[]
    for role in gRoleList:
        roleTtyTypeList.append(re.sub("_t$","", role)+"_tty_device_t")

    spRuleList= genAllowttyPtsCommon(rule,domdoc,generalTtyTypeList, roleTtyTypeList, "allowtty")

    return spRuleList

def genAllowpts(rule,line,domdoc):
    spRuleList=[]

    generalPtsTypeList=("devpts_t", "ptmx_t")
    rolePtsTypeList=[]
    for role in gRoleList:
        rolePtsTypeList.append(re.sub("_t$","", role)+"_devpts_t")

    spRuleList= genAllowttyPtsCommon(rule,domdoc,generalPtsTypeList,  rolePtsTypeList,"allowpts")
    return spRuleList

def genOther(rule,line,domdoc):
    """
    return list of Dict
    dict represents spdl rule to be outputted
    ruletype:   type of SPDL element, defined in <integrate value=
    domain:     domain
    type: type
    option:    <option value=
    suboption:  <option suboption=
    """

    spRuleList=[]
    genRuleType=["allowpriv","allownet","allowcom","allowkey"]

    integrateTagList=domdoc.getElementsByTagName("integrate")
    permTagList=[]
    for t in integrateTagList:
        ruleName=getAttr(t,"value")

        if ruleName in genRuleType:
            permTagList.extend(t.getElementsByTagName("permission"))

    for perm in rule["permission"]:
        list=matchPermTagList(perm,rule["secclass"],permTagList)
        if list:
            for l in list:
                if checkInternal(l,domdoc):
                    continue
                oneRule = dict()
                oneRule["domain"]=rule["domain"]
                oneRule["type"]=rule["type"]
                n=findParentTag(l,domdoc,"option")
                if n==None:
                    oneRule["option"]=""
                    oneRule["suboption"]=""
                else:
                    oneRule["option"]=getAttr(n,"value")
                    oneRule["suboption"]=getAttr(n,"suboption")
                    n=findParentTag(l,domdoc,"integrate")
                if n==None:
                    oneRule["ruletype"]=""
                else:
                    oneRule["ruletype"]=getAttr(n,"value")

                if  (oneRule["ruletype"],oneRule["option"]) in gNeglectRule:
                    continue

                spRuleList.append(oneRule)

    return spRuleList

def genSPDL(rule,lines,index, domdoc):
    """
    generate SPDL based on rule.
    rule is dictionary in parseLine funct
    """

    spRuleList=[]
    line = lines[index]

    spRuleList = genExceptionalRule(rule,line,domdoc)
    if spRuleList:
        return spRuleList

    spRuleList = genAllowtty(rule,line,domdoc)
    if spRuleList:
        return spRuleList

    spRuleList = genAllowpts(rule,line,domdoc)
    if spRuleList:
        return spRuleList

    ##Memo:: allowfs exclusive is not supported now..
    if rule["type"] in gFsLabelList or rule["type"] in gDomainList:
        spRuleList= genAllowfs(rule,domdoc)
        if spRuleList:
            return spRuleList

    if rule["secclass"] in gFileClass:
        spRuleList= genFileAllow(rule,lines,index,domdoc)
        if spRuleList:
            return spRuleList

    spRuleList=genAllownet(rule,line,domdoc)
    if spRuleList:
        return spRuleList

    #allowtty,allowpts is in progress
    ################!!!!!!!!!!In progress..........
    return genOther(rule,line,domdoc)

def getEqualValue(line,name):
    """
    get value like |name|=value from |line|
    """
    value=""
    m = re.compile(name+"=\S+").search(line)
    if m:
        list = string.split(m.group(),"=")
        value = list.pop()

    return value

def parseSyscall(line):
    result =0
    s = getEqualValue(line, "syscall")
    if s!="":
        result = string.atoi(s)

    return result

# Generate full path list from PATH= and CWD=
# returns list of (fullpath, inode number) : inode number is integer
# If failed return ("", inode number)
# Generate full path only for existing file
def genFullPath(lines):
    inode = -1
    path = ""
    cwd = ""
    list = []
    # it is assumed that PATH,CWD appears in sequence(PATH -> CWD).
    for l in lines:

        if l.find("type=PATH") >=0:
            inode = getInode(l)
            m=re.compile("name=\S+").search(l)
            if m:
                path = string.split(m.group(),"=").pop()
                if path.find("\"") == -1:
                    path = ""
                path = string.replace(path,"\"","")

                if path==".":
                        continue

                try:
                    os.stat(path)

                    list.append((path,inode))
                except:
                    pass

        if l.find("type=CWD")>=0 :
            m=re.compile("cwd=\S+").search(l)
            if m:
                cwd = string.split(m.group(),"=").pop()
                cwd = string.replace(cwd,"\"","")
            if path!="":
                if path[0]!="/":
                    path = cwd + "/"+path
                    path = os.path.normpath(path)
                    if path=="//":
                        list.append(("",inode))
                        continue

                    try:
                        os.stat(path)
                        list.append((path,inode))
                    except:
                        list.append(("", inode))

    return list

####
# Guess changed root after chroot, from sys_chroot and chdir logs
# and update gChrootStatus
# if lines[index] is log for chroot or syscall 12, return True
def updateChangedRoot(lines,index):
    line = lines[index]

    reg = re.compile("type=SYSCALL.*syscall=12")

    m = reg.search(line)
    if m:
        updateChdirInoPathDir(lines, index)
        return True

    reg = re.compile("avc:.*granted.*{.*sys_chroot.*}")
    m= reg.search(line)
    if not m:
        return False

    logs = getRelatedLog(lines, index)

    list = genFullPath(logs)
    path =""

    if list:
        (path, inode)=list[0]

        if path =="":
            if gChdirInoPathDir.has_key(inode):
                path = gChdirInoPathDir[inode]

    pid = getPid(lines[index])
    gChrootStatus[pid] = path

    return True

def updateChdirInoPathDir(lines, index):
    logs = getRelatedLog(lines, index)
    list = genFullPath(logs)

    if list:
        (path, inode)=list[0]
        gChdirInoPathDir[inode] = path

def getPpidFromLine(line):
    m=re.compile("\sppid=\d+").search(line)
    if m:
        ppid = string.split(m.group(),"=").pop()
        return ppid
    return "-1"

def getPpid(lines, index):
    """
    get ppid from SYSCALL
    """
    auid = getUniqueAuditId(lines[index])
    if auid == None:
        return -1

    start = index

    i = start -1
    while i>0:
        line = lines[i]
        if auid == getUniqueAuditId(line):
           id = getPpidFromLine(line)
           if id != "-1":
               return id
        else:
            break
        i = i-1

    l = len(lines)
    i = start+1
    while i < l:
        line = lines[i]

        if auid == getUniqueAuditId(line):
            id = getPpidFromLine(line)
            if id != "-1":
                return id
        else:
            break

        i = i+1

    return "-1"

###
# |lines|: whole log data
# Get logs related to log at |index|
# Related logs means, logs whose auid is the same
def getRelatedLog(lines, index):
    result = []
    result.insert(0,lines[index])

    start = index
    auid = getUniqueAuditId(lines[index])
    if auid == None:
        return result

    i = start -1
    while i>0:
        line = lines[i]
        if (string.find(line,"audit") != -1):
            if auid == getUniqueAuditId(line):
                result.insert(0,line)
            else:
                break

        i = i-1

    l = len(lines)
    i = start+1
    while i < l:
        line = lines[i]
        if (string.find(line,"audit") != -1):
            if auid == getUniqueAuditId(line):
                result.append(line)
            else:
                break

        i = i+1

    return result

def parseLine(lines, i):
    """
    Parse one log line and make&return dictionary.
    Keys are following
    domain
    type
    secclass
    permission : list of permission
    """

    avcFlag=False
    denyFlag=False
    rule = dict()
    line = lines[i]
    r = updateChangedRoot(lines, i)

    if r==True:
        return None

    tokenList = string.split(line)
    for token in tokenList:
        if token=="avc:" or token=="message=avc:":
            avcFlag = True
        if token=="denied":
            denyFlag=True
        if(avcFlag and denyFlag):
            break

    if (not avcFlag or not denyFlag):
        return None

    rule["domain"] = ""
    m = re.compile("scontext=\S+").search(line)
    if m:


# -*-coding: utf-8-*-

import logging

from pyramid.view import view_config, view_defaults
from pyramid.httpexceptions import HTTPFound

from . import BaseView
from ..models import DBSession
from ..models.user import User
from ..lib.bl.subscriptions import subscribe_resource
from ..lib.utils.security_utils import get_auth_employee
from ..lib.utils.common_utils import translate as _

from ..forms.users import (
    UserAddForm,
    UserEditForm,
    UserProfileForm,
    UserSearchForm,
    UserAssignForm,
)
from ..lib.events.users import (
    UserCreated,
)
from ..lib.events.resources import (
    ResourceChanged,
    ResourceDeleted,
)

log = logging.getLogger(__name__)

@view_defaults(
    context='..resources.users.UsersResource',
)
class UsersView(BaseView):

    @view_config(
        request_method='GET',
        xhr='True',
        renderer='travelcrm:templates/users/index.mako',
        permission='view'
    )
    def index(self):
        return {
            'title': self._get_title(),
        }

    @view_config(
        name='list',
        xhr='True',
        request_method='POST',
        renderer='json',
        permission='view'
    )
    def list(self):
        form = UserSearchForm(self.request, self.context)
        form.validate()
        qb = form.submit()
        return {
            'total': qb.get_count(),
            'rows': qb.get_serialized()
        }

    @view_config(
        name='view',
        request_method='GET',
        renderer='travelcrm:templates/users/form.mako',
        permission='view'
    )
    def view(self):
        if self.request.params.get('rid'):
            resource_id = self.request.params.get('rid')
            user = User.by_resource_id(resource_id)
            return HTTPFound(
                location=self.request.resource_url(
                    self.context, 'view', query={'id': user.id}
                )
            )
        result = self.edit()
        result.update({
            'title': self._get_title(_(u'View')),
            'readonly': True,
        })
        return result

    @view_config(
        name='add',
        request_method='GET',
        renderer='travelcrm:templates/users/form.mako',
        permission='add'
    )
    def add(self):
        return {
            'title': self._get_title(_(u'Add')),
        }

    @view_config(
        name='add',
        request_method='POST',
        renderer='json',
        permission='add'
    )
    def _add(self):
        form = UserAddForm(self.request)
        if form.validate():
            user = form.submit()
            DBSession.add(user)
            DBSession.flush()

            event = UserCreated(self.request, user)
            event.registry()

            return {
                'success_message': _(u'Saved'),
                'response': user.id
            }
        else:
            return {
                'error_message': _(u'Please, check errors'),
                'errors': form.errors
            }

    @view_config(
        name='edit',
        request_method='GET',
        renderer='travelcrm:templates/users/form.mako',
        permission='edit'
    )
    def edit(self):
        user = User.get(self.request.params.get('id'))
        return {
            'item': user,
            'title': self._get_title(_(u'Edit')),
        }

    @view_config(
        name='edit',
        request_method='POST',
        renderer='json',
        permission='edit'
    )
    def _edit(self):
        user = User.get(self.request.params.get('id'))
        form = UserEditForm(self.request)
        if form.validate():
            form.submit(user)

            event = ResourceChanged(self.request, user)
            event.registry()

            return {
                'success_message': _(u'Saved'),
                'response': user.id,
            }
        else:
            return {
                'error_message': _(u'Please, check errors'),
                'errors': form.errors
            }

    @view_config(
        name='copy',
        request_method='GET',
        renderer='travelcrm:templates/users/form.mako',
        permission='add'
    )
    def copy(self):
        user = User.get_copy(self.request.params.get('id'))
        return {
            'action': self.request.path_url,
            'item': user,
            'title': self._get_title(_(u'Copy')),
        }

    @view_config(
        name='copy',
        request_method='POST',
        renderer='json',
        permission='add'
    )
    def _copy(self):
        return self._add()

    @view_config(
        name='delete',
        request_method='GET',
        renderer='travelcrm:templates/users/delete.mako',
        permission='delete'
    )
    def delete(self):
        return {
            'title': self._get_title(_(u'Delete')),
            'id': self.request.params.get('id')
        }

    @view_config(
        name='delete',
        request_method='POST',
        renderer='json',
        permission='delete'
    )
    def _delete(self):
        errors = False
        ids = self.request.params.getall('id')
        if ids:
            try:
                items = DBSession.query(User).filter(User.id.in_(ids))
                for item in items:
                    DBSession.delete(item)
                    event = ResourceDeleted(self.request, item)
                    event.registry()
                DBSession.flush()
            except:
                errors=True
                DBSession.rollback()
        if errors:
            return {
                'error_message': _(
                    u'Some objects could not be delete'
                ),
            }
        return {'success_message': _(u'Deleted')}

    @view_config(
        name='assign',
        request_method='GET',
        renderer='travelcrm:templates/users/assign.mako',
        permission='assign'
    )
    def assign(self):
        return {
            'id': self.request.params.get('id'),
            'title': self._get_title(_(u'Assign Maintainer')),
        }

    @view_config(
        name='assign',
        request_method='POST',
        renderer='json',
        permission='assign'
    )
    def _assign(self):
        form = UserAssignForm(self.request)
        if form.validate():
            form.submit(self.request.params.getall('id'))
            return {
                'success_message': _(u'Assigned'),
            }
        else:
            return {
                'error_message': _(u'Please, check errors'),
                'errors': form.errors
            }

    @view_config(
        name='profile',
        request_method='GET',
        renderer='travelcrm:templates/users/profile.mako',
    )
    def profile(self):
        employee = get_auth_employee(self.request)
        user = User.get(employee.id)
        return {
            'item': user,
            'title': self._get_title(_(u'Edit Profile')),
        }

    @view_config(
        name='profile',
        request_method='POST',
        renderer='json',
    )
    def _profile(self):
        employee = get_auth_employee(self.request)
        user = User.get(employee.id)
        form = UserProfileForm(self.request)
        if form.validate():
            form.submit(user)

            event = ResourceChanged(self.request, user)
            event.registry()

            return {
                'success_message': _(u'Saved'),
                'response': user.id
            }
        else:
            return {
                'error_message': _(u'Please, check errors'),
                'errors': form.errors
            }

    @view_config(
        name='subscribe',
        request_method='GET',
        renderer='travelcrm:templates/users/subscribe.mako',
        permission='view'
    )
    def subscribe(self):
        return {
            'id': self.request.params.get('id'),
            'title': self._get_title(_(u'Subscribe')),
        }

    @view_config(
        name='subscribe',
        request_method='POST',
        renderer='json',
        permission='view'
    )
    def _subscribe(self):