# encoding: utf8

from __future__ import absolute_import, division, print_function, unicode_literals

from datetime import datetime, timedelta

from aspen import Response, json
from aspen.utils import to_rfc822, utcnow
from dependency_injection import resolve_dependencies
from postgres.cursors import SimpleCursorBase

import gratipay


BEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')

# Difference between current time and credit card expiring date when
# card is considered as expiring
EXPIRING_DELTA = timedelta(days = 30)


def dict_to_querystring(mapping):
    if not mapping:
        return u''

    arguments = []
    for key, values in mapping.iteritems():
        for val in values:
            arguments.append(u'='.join([key, val]))

    return u'?' + u'&'.join(arguments)


def use_tildes_for_participants(website, request):
    if request.path.raw.startswith('/~/'):
        to = '/~' + request.path.raw[3:]
        if request.qs.raw:
            to += '?' + request.qs.raw
        website.redirect(to)
    elif request.path.raw.startswith('/~'):
        request.path.__init__('/~/' + request.path.raw[2:])


def canonicalize(redirect, path, base, canonical, given, arguments=None):
    if given != canonical:
        assert canonical.lower() == given.lower()  # sanity check
        remainder = path[len(base + given):]

        if arguments is not None:
            arguments = dict_to_querystring(arguments)

        newpath = base + canonical + remainder + arguments or ''
        redirect(newpath)


def get_participant(state, restrict=True, resolve_unclaimed=True):
    """Given a Request, raise Response or return Participant.

    If restrict is True then we'll restrict access to owners and admins.

    """
    redirect = state['website'].redirect
    request = state['request']
    user = state['user']
    slug = request.line.uri.path['username']
    qs = request.line.uri.querystring
    _ = state['_']

    if restrict:
        if user.ANON:
            raise Response(403, _("You need to log in to access this page."))

    from gratipay.models.participant import Participant  # avoid circular import
    participant = Participant.from_username(slug)

    if participant is None:
        raise Response(404)

    canonicalize(redirect, request.line.uri.path.raw, '/~/', participant.username, slug, qs)

    if participant.is_closed:
        if user.ADMIN:
            return participant
        raise Response(410)

    if participant.claimed_time is None and resolve_unclaimed:
        to = participant.resolve_unclaimed()
        if to:
            # This is a stub account (someone on another platform who hasn't
            # actually registered with Gratipay yet)
            redirect(to)
        else:
            # This is an archived account (result of take_over)
            if user.ADMIN:
                return participant
            raise Response(404)

    if restrict:
        if participant != user.participant:
            if not user.ADMIN:
                raise Response(403, _("You are not authorized to access this page."))

    return participant


def get_team(state):
    """Given a Request, raise Response or return Team.
    """
    redirect = state['website'].redirect
    request = state['request']
    user = state['user']
    slug = request.line.uri.path['team']
    qs = request.line.uri.querystring

    from gratipay.models.team import Team  # avoid circular import
    team = Team.from_slug(slug)

    if team is None:
        # Try to redirect to a Participant.
        from gratipay.models.participant import Participant # avoid circular import
        participant = Participant.from_username(slug)
        if participant is not None:
            qs = '?' + request.qs.raw if request.qs.raw else ''
            redirect('/~' + request.path.raw[1:] + qs)
        raise Response(404)

    canonicalize(redirect, request.line.uri.path.raw, '/', team.slug, slug, qs)

    if team.is_closed and not user.ADMIN:
        raise Response(410)

    return team


def update_cta(website):
    nusers = website.db.one("""
        SELECT nusers FROM paydays
        ORDER BY ts_end DESC LIMIT 1
    """, default=(0.0, 0))
    nreceiving_from = website.db.one("""
        SELECT nreceiving_from
          FROM teams
         WHERE slug = 'Gratipay'
    """, default=0)
    website.support_current = cur = int(round(nreceiving_from / nusers * 100)) if nusers else 0
    if cur < 10:    goal = 20
    elif cur < 15:  goal = 30
    elif cur < 25:  goal = 40
    elif cur < 35:  goal = 50
    elif cur < 45:  goal = 60
    elif cur < 55:  goal = 70
    elif cur < 65:  goal = 80
    elif cur > 70:  goal = None
    website.support_goal = goal


def _execute(this, sql, params=[]):
    print(sql.strip(), params)
    super(SimpleCursorBase, this).execute(sql, params)

def log_cursor(f):
    "Prints sql and params to stdout. Works globaly so watch for threaded use."
    def wrapper(*a, **kw):
        try:
            SimpleCursorBase.execute = _execute
            ret = f(*a, **kw)
        finally:
            del SimpleCursorBase.execute
        return ret
    return wrapper


def format_money(money):
    format = '%.2f' if money < 1000 else '%.0f'
    return format % money


def excerpt_intro(text, length=175, append=u'…'):
    if not text:
        return ''
    if len(text) > length:
        return text[:length] + append
    return text


def is_card_expiring(expiration_year, expiration_month):
    now = datetime.utcnow()
    expiring_date = datetime(expiration_year, expiration_month, 1)
    delta = expiring_date - now
    return delta < EXPIRING_DELTA


def set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):
    cookies[key] = value
    cookie = cookies[key]
    if expires:
        if isinstance(expires, timedelta):
            expires += utcnow()
        if isinstance(expires, datetime):
            expires = to_rfc822(expires).encode('ascii')
        cookie[b'expires'] = expires
    if httponly:
        cookie[b'httponly'] = True
    if path:
        cookie[b'path'] = path
    if gratipay.use_secure_cookies:
        cookie[b'secure'] = True


def erase_cookie(cookies, key, **kw):
    set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)


def filter_profile_nav(user, participant, pages):
    out = []
    for foo, bar, show_them, show_others in pages:
        if (user.participant == participant and show_them) \
        or (user.participant != participant and show_others) \
        or user.ADMIN:
            out.append((foo, bar, show_them, show_others))
    return out


def to_javascript(obj):
    """For when you want to inject an object into a <script> tag.
    """
    return json.dumps(obj).replace('</', '<\\/')


class LazyResponse(Response):

    def __init__(self, code, lazy_body, **kw):
        Response.__init__(self, code, '', **kw)
        self.lazy_body = lazy_body

    def render_body(self, state):
        f = self.lazy_body
        self.body = f(*resolve_dependencies(f, state).as_args)

gratipay/utils/__init__.py
# encoding: utf8

from __future__ import absolute_import, division, print_function, unicode_literals

from datetime import datetime, timedelta

from aspen import Response, json
from aspen.utils import to_rfc822, utcnow
from dependency_injection import resolve_dependencies
from postgres.cursors import SimpleCursorBase

import gratipay


BEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')

# Difference between current time and credit card expiring date when
# card is considered as expiring
EXPIRING_DELTA = timedelta(days = 30)


def dict_to_querystring(mapping):
    if not mapping:
        return u''

    arguments = []
    for key, values in mapping.iteritems():
        for val in values:
            arguments.append(u'='.join([key, val]))

    return u'?' + u'&'.join(arguments)


def use_tildes_for_participants(website, request):
    if request.path.raw.startswith('/~/'):
        to = '/~' + request.path.raw[3:]
        if request.qs.raw:
            to += '?' + request.qs.raw
        website.redirect(to)
    elif request.path.raw.startswith('/~'):
        request.path.__init__('/~/' + request.path.raw[2:])


def canonicalize(redirect, path, base, canonical, given, arguments=None):
    if given != canonical:
        assert canonical.lower() == given.lower()  # sanity check
        remainder = path[len(base + given):]

        if arguments is not None:
            arguments = dict_to_querystring(arguments)

        newpath = base + canonical + remainder + arguments or ''
        redirect(newpath)


def get_participant(state, restrict=True, resolve_unclaimed=True):
    """Given a Request, raise Response or return Participant.

    If restrict is True then we'll restrict access to owners and admins.

    """
    redirect = state['website'].redirect
    request = state['request']
    user = state['user']
    slug = request.line.uri.path['username']
    qs = request.line.uri.querystring
    _ = state['_']

    if restrict:
        if user.ANON:
            raise Response(403, _("You need to log in to access this page."))

    from gratipay.models.participant import Participant  # avoid circular import
    participant = Participant.from_username(slug)

    if participant is None:
        raise Response(404)

    canonicalize(redirect, request.line.uri.path.raw, '/~/', participant.username, slug, qs)

    if participant.is_closed:
        if user.ADMIN:
            return participant
        raise Response(410)

    if participant.claimed_time is None and resolve_unclaimed:
        to = participant.resolve_unclaimed()
        if to:
            # This is a stub account (someone on another platform who hasn't
            # actually registered with Gratipay yet)
            redirect(to)
        else:
            # This is an archived account (result of take_over)
            if user.ADMIN:
                return participant
            raise Response(404)

    if restrict:
        if participant != user.participant:
            if not user.ADMIN:
                raise Response(403, _("You are not authorized to access this page."))

    return participant


def get_team(state):
    """Given a Request, raise Response or return Team.
    """
    redirect = state['website'].redirect
    request = state['request']
    user = state['user']
    slug = request.line.uri.path['team']
    qs = request.line.uri.querystring

    from gratipay.models.team import Team  # avoid circular import
    team = Team.from_slug(slug)

    if team is None:
        # Try to redirect to a Participant.
        from gratipay.models.participant import Participant # avoid circular import
        participant = Participant.from_username(slug)
        if participant is not None:
            qs = '?' + request.qs.raw if request.qs.raw else ''
            redirect('/~' + request.path.raw[1:] + qs)
        raise Response(404)

    canonicalize(redirect, request.line.uri.path.raw, '/', team.slug, slug, qs)

    if team.is_closed and not user.ADMIN:
        raise Response(410)

    return team


def update_cta(website):
    nusers = website.db.one("""
        SELECT nusers FROM paydays
        ORDER BY ts_end DESC LIMIT 1
    """, default=0)
    nreceiving_from = website.db.one("""
        SELECT nreceiving_from
          FROM teams
         WHERE slug = 'Gratipay'
    """, default=0)
    website.support_current = cur = int(round(nreceiving_from / nusers * 100)) if nusers else 0
    if cur < 10:    goal = 20
    elif cur < 15:  goal = 30
    elif cur < 25:  goal = 40
    elif cur < 35:  goal = 50
    elif cur < 45:  goal = 60
    elif cur < 55:  goal = 70
    elif cur < 65:  goal = 80
    elif cur > 70:  goal = None
    website.support_goal = goal


def _execute(this, sql, params=[]):
    print(sql.strip(), params)
    super(SimpleCursorBase, this).execute(sql, params)

def log_cursor(f):
    "Prints sql and params to stdout. Works globaly so watch for threaded use."
    def wrapper(*a, **kw):
        try:
            SimpleCursorBase.execute = _execute
            ret = f(*a, **kw)
        finally:
            del SimpleCursorBase.execute
        return ret
    return wrapper


def format_money(money):
    format = '%.2f' if money < 1000 else '%.0f'
    return format % money


def excerpt_intro(text, length=175, append=u'…'):
    if not text:
        return ''
    if len(text) > length:
        return text[:length] + append
    return text


def is_card_expiring(expiration_year, expiration_month):
    now = datetime.utcnow()
    expiring_date = datetime(expiration_year, expiration_month, 1)
    delta = expiring_date - now
    return delta < EXPIRING_DELTA


def set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):
    cookies[key] = value
    cookie = cookies[key]
    if expires:
        if isinstance(expires, timedelta):
            expires += utcnow()
        if isinstance(expires, datetime):
            expires = to_rfc822(expires).encode('ascii')
        cookie[b'expires'] = expires
    if httponly:
        cookie[b'httponly'] = True
    if path:
        cookie[b'path'] = path
    if gratipay.use_secure_cookies:
        cookie[b'secure'] = True


def erase_cookie(cookies, key, **kw):
    set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)


def filter_profile_nav(user, participant, pages):
    out = []
    for foo, bar, show_them, show_others in pages:
        if (user.participant == participant and show_them) \
        or (user.participant != participant and show_others) \
        or user.ADMIN:
            out.append((foo, bar, show_them, show_others))
    return out


def to_javascript(obj):
    """For when you want to inject an object into a <script> tag.
    """
    return json.dumps(obj).replace('</', '<\\/')


class LazyResponse(Response):

    def __init__(self, code, lazy_body, **kw):
        Response.__init__(self, code, '', **kw)
        self.lazy_body = lazy_body

    def render_body(self, state):
        f = self.lazy_body
        self.body = f(*resolve_dependencies(f, state).as_args)

gratipay/utils/__init__.py
diff --git a/gratipay/utils/__init__.py b/gratipay/utils/__init__.py
index ed2a5adb80..8624a6b076 100644
--- a/gratipay/utils/__init__.py
+++ b/gratipay/utils/__init__.py
@@ -136,7 +136,7 @@ def update_cta(website):
     nusers = website.db.one("""
         SELECT nusers FROM paydays
         ORDER BY ts_end DESC LIMIT 1
-    """, default=(0.0, 0))
+    """, default=0)
     nreceiving_from = website.db.one("""
         SELECT nreceiving_from
           FROM teams
diff --git a/templates/profile.html b/templates/profile.html
index 399562f537..992d6369ef 100644
--- a/templates/profile.html
+++ b/templates/profile.html
@@ -52,7 +52,7 @@
         <tr>
             <td class="label">{{ _("Giving") }}</td>
             {% if participant.anonymous_giving %}
-                {% if user.ADMIN %}
+                {% if user.ADMIN or (user.participant == participant)%}
                 <td>{{ format_currency(participant.giving, 'USD') }}</td>
                 <td>[{{ participant.ngiving_to }}]</td>
                 {% else %}
diff --git a/tests/py/test_privacy_json.py b/tests/py/test_privacy_json.py
index 985a888c4a..2047b68483 100644
--- a/tests/py/test_privacy_json.py
+++ b/tests/py/test_privacy_json.py
@@ -58,3 +58,57 @@ def test_participant_does_show_up_on_search(self):
     def test_participant_doesnt_show_up_on_search(self):
         self.hit_privacy('POST', data={'toggle': 'is_searchable'})
         assert 'alice' not in self.client.GET("/search.json?q=alice").body
+
+    # Related to anonymous_giving
+
+    def test_anon_can_see_giving_for_non_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=False)
+        assert '10.79' in self.client.GET('/~bob/').body
+        assert '342' in self.client.GET('/~bob/').body
+
+    def test_auth_can_see_giving_for_non_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=False)
+        assert '10.79' in self.client.GET('/~bob/', auth_as='alice').body
+        assert '342' in self.client.GET('/~bob/', auth_as='alice').body
+
+    def test_admin_can_see_giving_for_non_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=False)
+        self.make_participant('admin', is_admin=True)
+        assert '10.79' in self.client.GET('/~bob/', auth_as='admin').body
+        assert '342' in self.client.GET('/~bob/', auth_as='admin').body
+        assert '[342]' not in self.client.GET('/~bob/', auth_as='admin').body
+
+    def test_self_can_see_giving_for_non_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=False)
+        assert '10.79' in self.client.GET('/~bob/', auth_as='bob').body.decode('utf8')
+        assert '342' in self.client.GET('/~bob/', auth_as='bob').body.decode('utf8')
+        assert '[342]' not in self.client.GET('/~bob/', auth_as='bob').body.decode('utf8')
+
+    def test_anon_cannot_see_giving_for_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=True)
+        assert '10.79' not in self.client.GET('/~bob/').body
+        assert '342' not in self.client.GET('/~bob/').body
+
+    def test_auth_cannot_see_giving_for_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=True)
+        assert '10.79' not in self.client.GET('/~bob/', auth_as='alice').body
+        assert '342' not in self.client.GET('/~bob/', auth_as='alice').body
+
+    def test_admin_can_see_giving_for_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=True)
+        self.make_participant('admin', is_admin=True)
+        assert '10.79' in self.client.GET('/~bob/', auth_as='admin').body
+        assert '[342]' in self.client.GET('/~bob/', auth_as='admin').body
+
+    def test_self_can_see_giving_for_anonymous_giving(self):
+        self.make_participant('bob', claimed_time='now',
+                              giving=10.79, ngiving_to=342, anonymous_giving=True)
+        assert '10.79' in self.client.GET('/~bob/', auth_as='bob').body.decode('utf8')
+        assert '[342]' in self.client.GET('/~bob/', auth_as='bob').body.decode('utf8')

log spam during test
What's this `TypeError` about? Seems spurious ...

```
pid-13897 thread-4384100352 (Thread-1) Traceback (most recent call last):
pid-13897 thread-4384100352 (Thread-1)   File "/Users/whit537/personal/gratipay/gratipay.com/gratipay/cron.py", line 26, in f
pid-13897 thread-4384100352 (Thread-1)     func()
pid-13897 thread-4384100352 (Thread-1)   File "/Users/whit537/personal/gratipay/gratipay.com/gratipay/main.py", line 82, in <lambda>
pid-13897 thread-4384100352 (Thread-1)     cron(env.update_cta_every, lambda: utils.update_cta(website))
pid-13897 thread-4384100352 (Thread-1)   File "/Users/whit537/personal/gratipay/gratipay.com/gratipay/utils/__init__.py", line 145, in update_cta
pid-13897 thread-4384100352 (Thread-1)     website.support_current = cur = int(round(nreceiving_from / nusers * 100)) if nusers else 0
pid-13897 thread-4384100352 (Thread-1) TypeError: unsupported operand type(s) for /: 'int' and 'tuple'
```

