# coding: utf8
# Debian Contributors data mining on emails
#
# Copyright (C) 2013  Enrico Zini <enrico@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import math

def _smooth(days, decay_days):
    """
    Return the smooth factor after 'days' days of decay, considering a maximum
    decay timespan of 'decay_days'
    """
    # http://en.wikipedia.org/wiki/Bump_function
    if days >= decay_days:
        return 0
    return math.e * math.exp(-1/(1-(days/decay_days)**2))

class ContributorFrequencyCheck(object):
    def __init__(self, ident, time_unit_length=86400, contribution_age=7, min_activity_time=60):
        self.ident = ident
        self.time_unit_length = time_unit_length
        self.decay_days = contribution_age
        self.min_activity_time = min_activity_time
        self.stamp_set = set()
        self.stamp_min = None
        self.stamp_max = None

    def add_stamp(self, ts):
        ts = ts // self.time_unit_length
        self.stamp_set.add(ts)
        if self.stamp_min is None:
            self.stamp_min = ts
            self.stamp_max = ts
        elif ts < self.stamp_min:
            self.stamp_min = ts
        elif ts > self.stamp_max:
            self.stamp_max = ts

    @property
    def contrib_range(self):
        return self.stamp_min * self.time_unit_length, self.stamp_max * self.time_unit_length

    def heat_function(self):
        # Precompute the smooth factors
        smooth_factors = [ _smooth(i, self.decay_days) for i in xrange(self.decay_days) ]
        # Compute the heat function over the whole activity timespan
        # TODO: can be optimizing using a deque of decay_days items, as a
        # moving window of the alst decay_days days as we progress on the time
        # axis
        for ts in xrange(int(self.stamp_min), int(self.stamp_max) + 1):
            val = 0.0
            if ts in self.stamp_set:
                val += 1
            for i in xrange(1, self.decay_days):
                if (ts - i) in self.stamp_set:
                    val += smooth_factors[i]
            yield ts, val

    def is_contributor(self):
        threshold = 1
        first_ts_above_threshold = None
        for ts, val in self.heat_function():
            if val > threshold:
                if first_ts_above_threshold is None:
                    first_ts_above_threshold = ts
                else:
                    if ts - first_ts_above_threshold > self.min_activity_time:
                        return True
            else:
                first_ts_above_threshold = None
        return False

    def test_dump_heat_function(self, fname=None):
        if fname is None:
            fname = self.ident.type + "-" + self.ident.id
        with open(fname, "w") as fd:
            for ts, val in self.heat_function():
                print("{} {}".format(ts, val), file=fd)

