#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2009 Zuza Software Foundation
#
# This file is part of Pootle.
#
# Pootle 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.
#
# Pootle 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 Pootle; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

class Value(object):
    """Python descriptor for marshalling GET/POST state into Python
    variables and back.

    The descriptor is given a name (see __init__) which it uses to
    read its state from a dictionary (containing GET/POST variables).

    The descriptor stores its state within the object from which it is
    invoked using its own name prefix with an underscore (so if x is a
    descripor in class Foo, and foo is an instance of Foo, then the
    actual value of x is stored in foo._x). If its state is missing
    from its host object (i.e. if _x is not an attribute of foo), the
    descriptor will return its default value (so foo.x will give a
    default value).

    This is a base class which will marshal strings from and to a
    GET/POST dictionary.

    How-To Guide for descriptors: http://users.rcn.com/python/download/Descriptor.htm
    """
    default_value = None

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

    def _member_name(self):
        """Return the name that we'll use to store this descriptor's
        data under in its host object. A decriptor foo will have its
        data stored under _foo."""
        return '_' + self.var_name

    def __get__(self, obj, type=None):
        return getattr(obj, self._member_name(), self.default_value)

    def __set__(self, obj, value):
        setattr(obj, self._member_name(), value)

    def __delete__(self, obj):
        delattr(obj, self._member_name())

    def add_to_dict(self, obj, dct):
        """If the descriptor's value is not equal to its default
        value, then encode it to a string and store it in the GET/POST
        dictionary C{dct}."""
        value = self.__get__(obj)
        if value != self.default_value:
            dct[self.var_name] = self._encode(value)

    def read_from_params(self, obj, params):
        """Read the value of this descriptor from the GET/POST
        dictionary C{params}, decode it and use it to set the
        descriptor value."""
        try:
            self.__set__(obj, self._decode(params[self.var_name]))
        except KeyError:
            pass

    def _decode(self, value):
        return value

    def _encode(self, value):
        return value

    def __repr__(self):
        return "Value<%s>" % self.var_name

class BooleanValue(Value):
    default_value = False

    def _decode(self, value):
        if value == 'True':
            return True
        else:
            return False

    def _encode(self, value):
        if value:
            return 'True'
        else:
            return 'False'

class IntValue(Value):
    """Read a GET/POST variable into an integer. The descriptor's
    default value is supplied when initializing an IntValue. This
    value is used when the descriptor has no state stored in its host
    object or when the GET/POST variable doesn't decode to an integer.

        >>> class Foo(object):
        ...     bar = IntValue('bar', 1)
        >>> foo = Foo()
        >>> foo.bar
        1
        >>> foo.__class__.bar._decode('10')
        10
        >>> foo.__class__.bar._decode('a')
        1
        >>> foo.__class__.bar._encode(20)
        '20'
        >>> foo.__class__.bar.read_from_params(foo, {'bar': '5'})
        >>> foo.bar
        5
        >>> get_vars = {}
        >>> foo.__class__.bar.add_to_dict(foo, get_vars)
        >>> get_vars
        {'bar': '5'}
    """
    def __init__(self, var_name, default_value):
        super(IntValue, self).__init__(var_name)
        self.default_value = default_value

    def _decode(self, value):
        try:
            return int(value)
        except ValueError:
            return self.default_value

    def _encode(self, value):
        assert isinstance(value, int)
        return str(value)

class ChoiceValue(Value):
    """Read a string GET/POST variable ensuring that it matches one of
    the choices specified when constructing this object. If not, set
    this descriptor value to the first choice.

        >>> class Foo(object):
        ...     bar = ChoiceValue('bar', ('chocolate', 'strawberry', 'caramel'))
        >>> foo = Foo()
        >>> foo.__class__.bar._decode('strawberry')
        'strawberry'
        >>> foo.__class__.bar._decode('banana')
        'chocolate'
    """

    def __init__(self, var_name, choices):
        super(ChoiceValue, self).__init__(var_name)
        self.default_value = choices[0]
        self._choices = choices

    def _decode(self, value):
        if value in self._choices:
            return value
        else:
            return self.default_value

    def _encode(self, value):
        if value in self._choices:
            return value
        else:
            return self.default_value

class ListValue(Value):
    """Read a comma separated GET/POST variable into a Python list of
    strings.

        >>> class Foo(object):
        ...     bar = ListValue('bar')
        >>> foo = Foo()
        >>> foo.__class__.bar._decode('a,b,c')
        ['a', 'b', 'c']
        >>> foo.__class__.bar._encode(['a', 'b', 'c'])
        'a,b,c'
        >>> foo.__class__.bar.read_from_params(foo, {'bar': 'x,y,z'})
        >>> foo.bar
        ['x', 'y', 'z']
        >>> get_vars = {}
        >>> foo.__class__.bar.add_to_dict(foo, get_vars)
        >>> get_vars
        {'bar': 'x,y,z'}
    """
    default_value = []

    def _decode(self, value):
        if value in ('', None):
            return []
        else:
            return value.split(',')

    def _encode(self, value):
        return ','.join(str(item) for item in value)

def get_descriptors(cls, descriptors, visited):
    """Enumerate a class and all its subclasses in a depth-first post
    order traversal and collect all the Python descriptors into the
    list 'descriptors'"""
    for base in cls.__bases__:
        if base not in visited:
            visited.add(base)
            descriptors = get_descriptors(base, descriptors, visited)
    descriptors.extend(descriptor for descriptor in cls.__dict__.itervalues()
                       if isinstance(descriptor, Value))
    return descriptors

class State(object):
    """Base class for classes which, using any of the *Value classes
    as descriptors, will read GET or POST variables intelligently.

    To see an example implementation, look for TranslatePageState."""

    def get_descriptors(self):
        """Return a list of all the *Value descriptors (all defined
        above) that are defined in the class of the current object as
        well as its superclasses.

        Thus, if we have the following subclass:

            >>> class FooState(State):
            ...     bar = IntValue('bar', 0)
            ...     baz = ListValue('baz')
            >>> state = FooState()
            >>> sorted(state.get_descriptors())
            ['bar', 'baz']
        """
        return get_descriptors(self.__class__, [], set())

    def iter_items(self):
        """Iterate through all the *Value descriptors returning
        (descriptor name, descriptor value) pairs.

            >>> class FooState(State):
            ...     bar = IntValue('bar', 0)
            ...     baz = ListValue('baz')
            >>> state = FooState(bar=1, baz=['a', 'b', 'c'])
            >>> sorted(list(state.iter_items()))
            [('bar', 1), ('baz', ['a', 'b', 'c'])]
        """
        for member in self.get_descriptors():
            yield member.var_name, getattr(self, member.var_name)


    def __init__(self, data={}, **initial):
        """Initialize a state object possibly reading initial state
        from C{data} (which is in raw text) and overriding those
        values with the keyword parameters C{initial} (these
        parameters are not raw, so for an IntValue, you'd pass an
        integer).
        """
        for member in self.get_descriptors():
            member.read_from_params(self, data)
        for key, value in initial.iteritems():
            setattr(self, key, value)

    def encode(self):
        """Encode all the state members in this object to a GET/POST
        dictionary."""
        result = {}
        for member in self.get_descriptors():
            member.add_to_dict(self, result)
        return result
