cdef class Counter(AnalysisObject):
    """
    Weight counter. Like a histogram without any axis (and hence only one bin).

    Call fill() like with a histogram. Sums of weights can be returned, with val()
    and err() being shorthand for the sum of weights and its binomial error.

    Counter(path="", title="").
      Construct a counter with optional path and title but no bins.
    """

    cdef inline c.Counter* cptr(self) except NULL:
        return <c.Counter*> self.ptr()
    # TODO: remove
    cdef inline c.Counter* _Counter(self) except NULL:
        return <c.Counter*> self.ptr()


    def __init__(self, path="", title=""):
        cutil.set_owned_ptr(self, new c.Counter(<string>path,
                                                <string>title))


    def __repr__(self):
        return "<%s '%s' sumw=%0.2g, err=%s>" % (self.__class__.__name__, self.path(), self.val(), self.err())


    def reset(self):
        """None -> None.
        Reset the counter."""
        self.cptr().reset()


    def clone(self):
        """None -> Couner.
        Clone this Counter."""
        return cutil.new_owned_cls(Counter, self.cptr().newclone())


    def fill(self, weight=1.0, fraction=1.0):
        """([w]) -> None.
        Fill with given optional weight."""
        return self.cptr().fill(weight, fraction)

    def set(self, numEntries, sumW, sumW2):
        """
        (float, float, float) -> None

        Set the distribution counters with the arguments provided.
        """
        self.cptr().set(numEntries, sumW, sumW2)

    def numEntries(self):
        """None -> float
        Number of times this counter was filled."""
        return self.cptr().numEntries()

    def effNumEntries(self):
        """None -> float
        Effective number of times this counter was filled, computed from weights."""
        return self.cptr().effNumEntries()

    def sumW(self):
        """() -> float
        Sum of weights filled into this counter."""
        return self.cptr().sumW()

    def sumW2(self):
        """() -> float
        Sum of weights filled into this counter."""
        return self.cptr().sumW2()

    def val(self):
        """() -> float
        Sum of weights filled into this counter."""
        return self.cptr().val()

    def err(self):
        """() -> float
        Binomial uncertainty on the sum of weights filled into this counter."""
        return self.cptr().err()

    def relErr(self):
        """() -> float
        Relative binomial uncertainty on the sum of weights filled into this counter."""
        return self.cptr().relErr()


    def scaleW(self, w):
        """ (float) -> None.
        Rescale the weights in this counter by the factor w."""
        self.cptr().scaleW(w)


    def mkEstimate(self, path = "", source = ""):
        """None -> Estimate.
        Convert this Counter to a Estimate."""
        tmp = c.Counter_est(self.cptr(), path, source)
        return cutil.new_owned_cls(Estimate, tmp)

    def mkScatter(self, path = ""):
        """None -> Scatter1D.
        Convert this Counter to a Scatter1D, with x representing the value and error."""
        cdef c.Scatter1D s1 = self.cptr().mkScatter(path)
        return cutil.new_owned_cls(Scatter1D, s1.newclone())

    def divideBy(Counter self, Counter other, efficiency=False):
        if efficiency:
            tmp = c.Counter_eff_Counter(self.cptr(), other.cptr())
            return cutil.new_owned_cls(Estimate, tmp)
        else:
            tmp = c.Counter_div_Counter(self.cptr(), other.cptr())
            return cutil.new_owned_cls(Estimate, tmp)

    def lengthContent(self, fixed_length = False):
        """Length of serialisaed data vector for MPI communication."""
        return self.cptr().lengthContent(fixed_length)

    def serializeContent(self, fixed_length = False):
        """Data serialisation for MPI communication."""
        return self.cptr().serializeContent(fixed_length)

    def deserializeContent(self, data):
        """Data deserialisation for MPI communication."""
        cdef vector[double] cdata
        cdata = [ float(x) for x in data ]
        self.cptr().deserializeContent(cdata)

    def lengthMeta(self, skipPath = True, skipTitle = True):
        """Length of serialisaed meta-data vector for MPI communication."""
        return self.cptr().lengthMeta(skipPath, skipTitle)

    def serializeMeta(self, skipPath = True, skipTitle = True):
        """Meta-data serialisation for MPI communication."""
        return self.cptr().serializeMeta(skipPath, skipTitle)

    def deserializeMeta(self, data, resetPath = False, resetTitle = False):
        """Data deserialisation for MPI communication."""
        cdef vector[string] cdata
        cdata = [ str(x) for x in data ]
        self.cptr().deserializeMeta(cdata, resetPath, resetTitle)

    ## In-place special methods

    def __iadd__(Counter self, Counter other):
        c.Counter_iadd_Counter(self.cptr(), other.cptr())
        return self

    def __isub__(Counter self, Counter other):
        c.Counter_isub_Counter(self.cptr(), other.cptr())
        return self


    ## Unbound special methods

    def __add__(Counter self, Counter other):
        h = Counter()
        cutil.set_owned_ptr(h, c.Counter_add_Counter(self.cptr(), other.cptr()))
        return h

    def __sub__(Counter self, Counter other):
        h = Counter()
        cutil.set_owned_ptr(h, c.Counter_sub_Counter(self.cptr(), other.cptr()))
        return h

    def __div__(Counter self, Counter other):
        return self.divideBy(other)

    def __truediv__(Counter self, Counter other):
        return self.divideBy(other)

