#!/usr/bin/env python

# pylint: disable=superfluous-parens
# superfluous-parens: Parentheses are good for portability and clarity

"""Compute a boxcar average."""

import collections
import sys
import typing

averagetuple = collections.namedtuple('averagetuple', 'average independent_total')


class Num_den_boxcar_average(object):
    """Compute a boxcar average."""

    # pylint: disable=too-few-public-methods,too-many-instance-attributes
    def __init__(
            self,
            average_over: int,
            numerator_attr: str,
            denominator_attr: str,
            independent_total_attr: str,
    ) -> None:
        """Initialize."""
        if numerator_attr is None and denominator_attr is not None:
            sys.stderr.write('%s: Internal error: numerator_attr is none but denominator_attr is not\n' % sys.argv[0])
            sys.exit(1)
        self.average_over = average_over
        self.numerator_attr = numerator_attr
        self.denominator_attr = denominator_attr
        self.independent_total_attr = independent_total_attr
        self.max_size = average_over
        self.index = 0
        self.lst: typing.List[float] = []
        self.full = False
        self.numerator = 0
        self.denominator = 0
        if independent_total_attr is not None:
            self.independent_total = 0

    def add(self, obj: typing.Any):
        # Technically, obj needs a numerator_attr and denominator_attr, not just any old type.
        """We return an object that has a .average attribute."""
        # pylint: disable=too-many-branches
        if self.full:
            old_obj = self.lst[self.index]
            self.lst[self.index] = obj
        else:
            if len(self.lst) == self.index:
                self.lst.append(obj)
            else:
                sys.stderr.write('%s: Internal error: weird lst length\n' % sys.argv[0])
                sys.exit(1)
        new_numerator = getattr(self.lst[self.index], self.numerator_attr)
        new_denominator = getattr(self.lst[self.index], self.denominator_attr)
        if self.independent_total_attr is not None:
            new_independent_var = getattr(self.lst[self.index], self.independent_total_attr)
        if self.full:
            old_numerator = getattr(old_obj, self.numerator_attr)
            old_denominator = getattr(old_obj, self.denominator_attr)
            if self.independent_total_attr is not None:
                old_independent_var = getattr(old_obj, self.independent_total_attr)
            self.numerator = self.numerator + new_numerator - old_numerator
            self.denominator = self.denominator + new_denominator - old_denominator
            if self.independent_total_attr is not None:
                self.independent_total = self.independent_total + new_independent_var - old_independent_var
        else:
            self.numerator += new_numerator
            self.denominator += new_denominator
            if self.independent_total_attr is not None:
                self.independent_total += new_independent_var
        if self.index >= self.max_size - 1:
            self.full = True
        if self.full:
            if self.denominator == 0:
                average = None
            else:
                average = self.numerator / self.denominator
        else:
            average = None
        self.index = (self.index + 1) % self.max_size
        if self.independent_total_attr is None:
            avtup = averagetuple(average, None)
        else:
            avtup = averagetuple(average, self.independent_total)
#        if average != None:
#            print >> sys.stderr, "numerator is %f, denominator is %f, average is %f" % (self.numerator, self.denominator, average)
        return avtup


if __name__ == '__main__':
    class Test_class(object):
        """Facilitate testing."""

        # pylint: disable=too-few-public-methods
        def __init__(self, fred: float, barney: int, wilma: int) -> None:
            """Initialize."""
            self.fred = fred
            self.barney = barney
            self.wilma = wilma

        def __str__(self):
            """Convert to str."""
            return 'fred: %d, barney: %d, wilma: %d' % (self.fred, self.barney, self.wilma)
    BOXCAR_AVERAGE = Num_den_boxcar_average(2, numerator_attr='fred', denominator_attr='barney', independent_total_attr='wilma')
    for value in [
        Test_class(1.0, 1, 1),
        Test_class(2.0, 2, 2),
        Test_class(3.0, 3, 3),
        Test_class(4.0, 4, 4),
        Test_class(5.0, 5, 5),
        Test_class(8.0, 6, 10),
        Test_class(10.0, 7, 20),
        Test_class(20.0, 8, 30),
        Test_class(30.0, 9, 40),
        Test_class(40.0, 10, 50),
        Test_class(50.0, 11, 60),
    ]:
        result = BOXCAR_AVERAGE.add(value)
        print('%s %s %s' % (value, result.average, result.independent_total))
