# -*- coding: utf-8 -*-
## GroupUserFolder
## Copyright (C)2006 Ingeniweb

## 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; see the file COPYING. If not, write to the
## Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""

"""
__version__ = "$Revision:  $"
# $Source:  $
# $Id: class_utility.py 30098 2006-09-08 12:35:01Z encolpe $
__docformat__ = 'restructuredtext'

import string
import re
import threading
import string

# Base classes global vars management
_BASECLASSESLOCK = threading.RLock()
_BASECLASSES = {}
_BASEMETALOCK = threading.RLock()
_BASEMETA = {}

def showaq(self, indent=''):
    "showaq"
    rval = ""
    obj = self
    base = getattr(obj, 'aq_base', obj)
    try: id = base.id
    except: id = str(base)
    try: id = id()
    except: pass

    if hasattr(obj, 'aq_self'):
        if hasattr(obj.aq_self, 'aq_self'):
            rval = rval + indent + "(" + id + ")\n"
            rval = rval + indent + "|  \\\n"
            rval = rval + showaq(obj.aq_self, '|   ' + indent)
            rval = rval + indent + "|\n"
        if hasattr(obj, 'aq_parent'):
            rval = rval + indent + id + "\n"
            rval = rval + indent + "|\n"
            rval = rval + showaq(obj.aq_parent, indent)
    else:
        rval = rval + indent + id + "\n"
    return rval


def listBaseMetaTypes(cl, reverse = 0):
    """
    listBaseMetaTypes(cl, reverse = 0) => list of strings

    List all base meta types for this class.
    """
    # Look for the class in _BASEMETA cache
    try:
        return _BASEMETA[cl][reverse]

    except KeyError:
        _populateBaseMetaTypes(cl)
        return listBaseMetaTypes(cl, reverse)

def isBaseMetaType(meta, cl):
    try:
        return _BASEMETA[cl][2].has_key(meta)

    except KeyError:
        _populateBaseMetaTypes(cl)
        return isBaseMetaType(meta, cl)

def _populateBaseMetaTypes(cl):
    """Fill the base classes structure"""
    # Fill the base classes list
    try:
        ret = [cl.meta_type]
    except AttributeError:
        ret = []

    for b in cl.__bases__:
        ret = list(listBaseMetaTypes(b, 1)) + ret

    # Fill the base classes dict
    bases = {}
    for b in ret:
        bases[b] = 1

    _BASEMETALOCK.acquire()
    try:
        rev = ret[:]
        rev.reverse()
        _BASEMETA[cl] = (tuple(rev), tuple(ret), bases)
    finally:
        _BASEMETALOCK.release()

def objectIds(container, meta_types = []):
    """
    """
    return map(lambda x: x[0], objectItems(container, meta_types))

def objectValues(container, meta_types = []):
    """
    """
    return map(lambda x: x[1], objectItems(container, meta_types))

def objectItems(container, meta_types = []):
    """
    objectItems(container, meta_types = [])
    Same as a container's objectItem method, meta_types are scanned in the base classes too.
    Ie. all objects derivated from Folder will be returned by objectItem(x, ['Folder'])
    """
    # Convert input type
    if type(meta_types) not in (type(()), type([])):
        meta_types = [meta_types]

    # Special case where meta_types is empty
    if not meta_types:
        return container.objectItems()

    # Otherwise : check parent for each meta_type
    ret = []
    for (id, obj) in container.objectItems():
        for mt in meta_types:
            if isBaseMetaType(mt, obj.__class__):
                ret.append((id, obj))
                break

    return ret



def listBaseClasses(cl, reverse = 0):
    """
    listBaseClasses(cl, reverse = 0) => list of classes

    List all the base classes of an object.
    When reverse is 0, return the self class first.
    When reverse is 1, return the self class last.

    WARNING : reverse is 0 or 1, it is an integer, NOT A BOOLEAN ! (optim issue)

    CACHE RESULTS

    WARNING : for optimization issues, the ORIGINAL tuple is returned : please do not change it !
    """
    # Look for the class in _BASECLASSES cache
    try:
        return _BASECLASSES[cl][reverse]

    except:
        _populateBaseClasses(cl)
        return listBaseClasses(cl, reverse)


def isBaseClass(base, cl):
    """
    isBaseClass(base, cl) => Boolean
    Return true if base is a base class of cl
    """
    try:
        return _BASECLASSES[cl][2].has_key(base)
    except:
        _populateBaseClasses(cl)
        return isBaseClass(base, cl)


def _populateBaseClasses(cl):
    """Fill the base classes structure"""
    # Fill the base classes list
    ret = [cl]
    for b in cl.__bases__:
        ret = list(listBaseClasses(b, 1)) + ret

    # Fill the base classes dict
    bases = {}
    for b in ret:
        bases[b] = 1

    _BASECLASSESLOCK.acquire()
    try:
        rev = ret[:]
        rev.reverse()
        _BASECLASSES[cl] = (tuple(rev), tuple(ret), bases)
    finally:
        _BASECLASSESLOCK.release()
