# -*- coding: UTF-8 -*-


# By: Frederic Peters <fpeters@entrouvert.be>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2003 Entr'ouvert & Emmanuel Raviart
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# 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.


"""Environs Handling Module

Environs variables are global variables stored in a stack."""


import threading


class _Environ:
    next = None
    variables = None

    def __init__(self, variables):
        self.variables = variables

    def delVar(self, name):
        """Remove a variable from top-level environ."""

        if self.variables.has_key(name):
            del self.variables[name]

    def getVar(self, name, default = "none"):
        """Return the first value of a variable in the environ stack.

        If no corresponding variable is found, the default value is returned.
        If no default value is specified, None is returned.
        """

        if self.variables.has_key(name):
            return self.variables[name]
        elif self.next is None:
            if default == "none":
                raise KeyError(name)
            return default
        else:
            return self.next.getVar(name, default = default)

    def getVarNames(self):
        """Return the names of all variables defined in the environ stack."""

        return self.getVarNames2([])

    def getVarNames2(self, varNames):
        for name in self.variables.keys():
            if not name in varNames:
                varNames.append(name)
        if self.next is None:
            return varNames
        else:
            return self.next.getVarNames2(varNames)

    def setVar(self, name, value):
        """Set a variable on top of environ stack."""

        self.variables[name] = value


def clear():
    """Empty environ stack."""
    
    threading.currentThread()._expressionEnviron = None
    

def delVar(name):
    """Remove a variable from top-level environ.
    
    If the variable is defined in top-level environ, it is removed, otherwise
    nothing is done.
    """

    environ = get()
    environ.delVar(name)


def get(**signatureVariables):
    """Get a environ with given signature in environ stack.

    If signatureVariables is empty, it returns the first environ.
    Otherwise, it returns the first environ which contains all signature
    variables (and values).
    """

    thread = threading.currentThread()
    environ = thread._expressionEnviron
    if not signatureVariables:
        return environ
    while environ is not None:
        variables = environ.variables
        for name, value in signatureVariables.items():
            if not variables.has_key(name) or variables[name] != value:
                break
        else:
            return environ
        environ = environ.next
    return None


def getVar(name, default = "none"):
    """Return the first value of a variable in the environ stack.

    If no corresponding variable is found, the default value is returned.
    If no default value is specified, None is returned.
    """
    environ = get()
    return environ.getVar(name, default = default)


def getVarNames():
    """Get all the variables names defined in environ stack."""
    
    environ = get()
    return environ.getVarNames()


def initFromOther(otherEnviron, **environVariables):
    """Create a new current environ on top of given environ."""
    
    environ = _Environ(environVariables)
    environ.next = otherEnviron
    threading.currentThread()._expressionEnviron = environ
    

def initThread(thread):
    """Initialise a new thread environ."""
    
    environ = _Environ({})
    environ.next = threading.currentThread()._expressionEnviron
    thread._expressionEnviron = environ


def isClear():
    """Test if the environ stack is empty."""
    
    thread = threading.currentThread()
    return hasattr(thread, "_expressionEnviron") \
           and thread._expressionEnviron is None


def pull(**signatureVariables):
    """Remove the first environ or environs from the stack.

    If signatureVariables is empty, it removes the first environ.
    Otherwise, it removes all the environs until there is a environ which
    contains all signature variables (and values).
    """
    
    thread = threading.currentThread()
    environ = thread._expressionEnviron
    while environ is not None:
        variables = environ.variables
        for name, value in signatureVariables.items():
            if not variables.has_key(name) or variables[name] != value:
                break
        else:
            thread._expressionEnviron = environ.next
            return
        environ = environ.next
    raise Exception("Unable to find signature in environ.")


def push(**environVariables):
    """Create a new environ on top of current one."""
    
    thread = threading.currentThread()
    environ = _Environ(environVariables)
    environ.next = thread._expressionEnviron
    thread._expressionEnviron = environ
    return environ


def set(environ):
    """Set the current environ."""
    
    threading.currentThread()._expressionEnviron = environ


def setVar(name, value):
    """Set a variable on top of environ stack.

    If the variable does not exist, it is created.
    """
    
    environ = get()
    environ.setVar(name, value)
