# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.


__doc__ = """Glasnost Common Tools"""

__version__ = '$Revision$'[11:-2]


try:
    import glasnost
except ImportError:
    class G:
        applicationName = 'glasnost'
    glasnost = G()

    

import ConfigParser
import gettext
import random

import context


config = None
configDir = '/etc/%s' % glasnost.applicationName # changed on 'make install'


def buildPython22ClassLinearization(aClass, baseClasses):
    if aClass in baseClasses:
        baseClasses.remove(aClass)
    baseClasses.append(aClass)
    for baseClass in aClass.__bases__:
        buildPython22ClassLinearization(baseClass, baseClasses)


def createMultiId(listIds):
    """Create new Id, with list of Id

    Keyword arguments:
    ==================

    *list*:
        list with len >= 1

    Return:
    =======
    one Id. This Id can be splited with splitSubId.
    """
    return ':'.join(listIds)


def extractDispatcherId(id):
    """Return the dispatcher ID string from an server ID string
   
    Keyword argument:
    =================

    *id*:
        The server ID string.
        
    Exception:
    ==========

    *AssertionError*:
        The server ID doesn't begin by 'glasnost://'.
            
    *Standard Exception*:
        The server ID folowing the protocol specifier is not valid.
    
    """

    try:
        assert id.startswith('glasnost://')
        splittedApplicationId = id[11:].split('/', 1)
        return 'glasnost://%s' % splittedApplicationId[0]
    except (AssertionError, IndexError):
        raise Exception('Malformed id or application id = "%s"' % str(id))


def extractHostNameAndPort(id):
    return extractDispatcherId(id)[11:]


def extractLocalId(id):
    try:
        assert id.startswith('glasnost://')
        splittedId = id[11:].split('/', 2)
        if len(splittedId) <= 2:
            return ''
        else:
            return splittedId[2]
    except (AssertionError, IndexError):
        raise Exception('Malformed id = "%s"' % str(id))


def extractRole(id):
    try:
        assert id.startswith('glasnost://')
        splittedApplicationId = id[11:].split('/', 2)
        if len(splittedApplicationId) == 1:
            return ''
        else:
            return splittedApplicationId[1]
    except (AssertionError, IndexError):
        raise Exception('Malformed id or application id = "%s"' % str(id))


def extractServerId(id):
    """Extract the server ID from an object ID.
    
    Keyword argument:
    =================

    *id*:
        The object ID.

    Return the server ID.

    Exception:
    ==========

    *AssertionError*:
        The object ID string is not a glasnost service ('glasnost://').

    *IndexError*:
        The given object ID is incomplete.

    """
    
    try:
        assert id.startswith('glasnost://')
        splittedId = id[11:].split('/', 2)
        if len(splittedId) == 1 or not splittedId[1]:
            # Id is a dispacherId.
            return 'glasnost://%s' % splittedId[0]
        else:
            return 'glasnost://%s/%s' % (splittedId[0], splittedId[1])
    except (AssertionError, IndexError):
        raise Exception('Malformed id or application id = "%s"' % str(id))


def getAllThingClasses():
    thingClasses = context.getVar('thingClasses')
    return thingClasses.getAll()


def getC3ClassLinearization(c):
    # An implementation of the C3 class hierarchy Linearization.
    # Cf. http://www.webcom.com/haahr/dylan/linearization-oopsla96.html
    linearization = [c]
    if c.__bases__:
        basesByPrecedenceOrder = list(c.__bases__)
        basesByPrecedenceOrder.reverse()
        remainingInputs = [getC3ClassLinearization(base)
                           for base in c.__bases__] + [basesByPrecedenceOrder]
        while remainingInputs:
            # The first class which is not in the tail of each remaining input
            # is the next class to append to the linearization.
            for remainingInput in remainingInputs:
                candidate = remainingInput[0]
                for remainingInput2 in remainingInputs:
                    if candidate in remainingInput2[1:]:
                        break
                else:
                    break
            else:
                raise Exception(
                    'Inconsistent class hierarchy for class %s' % c)
            # A good candidate has been found.
            # Add it  to the partial linearization and remove it from the
            # remaining inputs.
            linearization.append(candidate)
            newRemainingInputs = []
            for remainingInput in remainingInputs:
                if remainingInput[0] == candidate:
                    del remainingInput[0]
                    if not remainingInput:
                        continue
                newRemainingInputs.append(remainingInput)
            remainingInputs = newRemainingInputs
    return linearization


def getConfig(section, value, default = None, vars = None, raw = 0):
    """Retrieve values from the Glasnost config file.

    Keyword arguments:
    ==================

    *section*:
        The section string in the config file where the attribute is.

    *value*:
        The attribute name string to get value.

    *default*:
        The default value to set if the attribute is not found.

    *vars*:
        A dictionnary used to override the set "%" attributes of the config
        file.

    *raw*:
        If true, the vars dictionnary is not used.

    Return:
    =======
    
    + Return the wanted value
    
    + The default value if no attribute was found.
    
    + None if no attribute was found and no default value given.
    
    """
    
    global config
    if config is None:
        config = ConfigParser.ConfigParser()
        config.readfp(open(configDir + '/config'))
        return getConfig(section, value, default = default, vars = vars,
                         raw = raw)
    else:
        try:
            return config.get(section, value, vars = vars, raw = raw)
        except ConfigParser.NoOptionError:
            return default
        except ConfigParser.NoSectionError:
            return default


def getConfigNoCache(section, value, default = None, vars = None, raw = 0):
    conf = ConfigParser.ConfigParser()
    conf.readfp(open(configDir + '/config'))
    try:
        return conf.get(section, value, vars = None, raw = raw)
    except ConfigParser.NoOptionError:
        return default
    except ConfigParser.NoSectionError:
        return default


def getThingClass(thingCategory, thingName):
    """Return the class of a thing."""

    thingClasses = context.getVar('thingClasses')
    thingClass = thingClasses.get(thingCategory, thingName)
    return thingClass


def importThing(thingImport, parentSlot = None):
    thing = newThing(
            thingImport['__thingCategory__'], thingImport['__thingName__'])
    thing.importFromXmlRpc(thingImport, parentSlot = parentSlot)
    return thing


def makeApplicationId(id, role):
    """Compute the application ID string.
    
    Keyword arguments:
    ==================

    *id*:
        The server ID string.

    *role*:
        The server role String

    Return the application ID string. Formaly, it is the dispatcher address
    folowed by the given role.
   
    Exception:
    ==========

    *AssertionError*:
        The server ID doesn't begin by 'glasnost://'.
            
    *Standard Exception*:
        The application ID folowing the protocol specifier is not valid.
  
    """

    if role:
        return '%s/%s' % (extractDispatcherId(id), role)
    else:
        return extractDispatcherId(id)


def makeHttpHostNameAndPort(httpHostName, httpPort):
    if httpHostName is None and httpPort is None:
        return None
    elif httpPort is None:
        return httpHostName
    elif httpHostName is None:
        return 'localhost:%s' % httpPort
    else:
        return '%s:%s' % (httpHostName, httpPort)


def makeHttpPathAndQuery(httpPath, httpQuery):
    if httpPath is None and httpQuery is None:
        return None
    elif httpQuery is None:
        return httpPath
    elif httpPath is None:
        return '/?%s' % httpQuery
    else:
        return '%s?%s' % (httpPath, httpQuery)


def makeHttpScriptDirectoryPath(httpScriptPath):
    if httpScriptPath is None:
        return '/'
    splittedPath = httpScriptPath.split('/')
    del splittedPath[-1]
    path = '/'.join(splittedPath)
    if not path or path[0] != '/':
        path = '/' + path
    if not path[-1] == '/':
        path += '/'
    return path


def makepassword(
    length = 8,
    allowChar = 'ABCDEFGHIJKLMNPQRSTUVWXYabcdefghijmnopqrstuvwxyz0123456789'):
    """Generate a random password.

    @param length: length of the result string
    @type length: int
    @param CARAC: allowed charactere for the result string
    @type allowChar: String
    """
    g = random.Random()
    result = ''
    for i in range(length):
        result += allowChar[g.randrange(len(allowChar))]
    return result


def newCard(*prototypeIds, **fields):
    card = newThing('object', 'cards.Card')
    if prototypeIds:
        card.prototypeIds = list(prototypeIds)
    for slotName, value in fields.items():
        card.getSlot(slotName).setValue(value)
    return card


def newThing(thingCategory, thingName, *arguments, **keywords):
    """Instantiate a new thing.

    This is a generic method designed to instantiate any kind of thing.

    Keyword arguments:
    ==================

    *thingCategory*:
        The category name of the class to instantiate (i.e.: 'object').

    *thingName*:
        The class name to instantiate (i.e.: 'AdminPeople'). The class name is
        linked to a category. Same class names could be used in differents
        categories.

    *\*arguments*:
        The arguments sequence to pass to the class constructor.

    *\*\*keywords*:
        The keyword dictionnary to pass to the class contructor.

    Return the wanted thing instance (category + class name).

    """

    return getThingClass(thingCategory, thingName)(*arguments, **keywords)


def splitId(id):
    return [extractDispatcherId(id)[11:], extractRole(id), extractLocalId(id)]


def splitMultiId(id):
    """Return list of sub id

    Keyword arguments:
    ==================

    *id*:
        The id to split
        example:  glasnost://host.tld/serverName/id0:id1:id2:id3
        become: [glasnost://host.tld/serverName/id0, id1, id2, id3]

    Return:
    =======
    list of all sub id, this list have one or more component.
    """
    try:
        assert id.startswith('glasnost://')
        splittedId = id[11:].split('/',2)
        assert len(splittedId) == 3
        applicationHostNameAndPort, applicationRole, localId = splittedId
        result = localId.split(':')
        result[0] = 'glasnost://%s/%s/%s' % (applicationHostNameAndPort,
                                             applicationRole, result[0])
    except IndexError:
        raise Exception('Malformed id = %s' % str(id))
    except AssertionError:
        raise Exception('Malformed id = %s' % str(id))
    return result


def translation(domains, languages = None):
    localeDirectoryPath = context.getVar('localeDirectoryPath')
    translation = gettext.NullTranslations()

    for domain in domains:
        try:
            trans = gettext.translation(domain, localeDirectoryPath, [languages[0]])
        except IOError:
            continue
        if not hasattr(trans, '_catalog'):
            continue
        if translation.__class__.__name__ == 'NullTranslations':
            translation = trans
            continue
        translation._catalog.update(trans._catalog)
        
    return translation

