# cython: language_level=3
# cython: boundscheck=False
# cython: nonecheck=False
# cython: wraparound=False
# cython: initializedcheck=False
# cython: cdivision=True

# amalgamation structure that can become a cluster
# Author: Pavel "DRUHG" Artamonov
# License: 3-clause BSD

import numpy as np
cimport numpy as np
import sys

cdef np.double_t EPS = sys.float_info.min


from libc.math cimport fabs, pow

cdef np.double_t merge_means(np.intp_t na, np.double_t meana,
                             np.intp_t nb, np.double_t meanb
                            ):
    # https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
    # Chan et al.[10] Welford's online algorithm
    cdef np.double_t delta

    # nx = na + nb
    delta = meanb - meana
    delta = meana + delta*nb/(na + nb)
    # use this for big n's
    # mu = (mu*n + mu_2*n_2) / nx
    # m2a = m2a + m2b + delta**2*na*nb/nx
    return delta


cdef class Amalgamation (object):
    # declarations are in pxd file
    # https://cython.readthedocs.io/en/latest/src/userguide/sharing_declarations.html

    def __init__(self, int size = 1, double energy = 0., int clusters = 1):
        self.size = size
        self.energy = energy
        self.clusters = clusters


    cdef Border _whole(self, np.double_t g, Amalgamation other):
        cdef Border brd

        brd.dis = pow(g, 1.)
        brd.quantity = pow(1.*min(self.size, other.size), 0.5)
        brd.measure = pow((1.*self.clusters + other.clusters)/max(self.clusters, other.clusters), +0.25)

        brd.jump = brd.dis*brd.quantity
        brd.limit = brd.jump*brd.measure
        brd.jump = brd.limit
        # print (max(self.size, other.size), min(self.size, other.size), 'whole', 1.*quality*quantity*measure,'=', 1.*self.clusters, quality, quantity, measure)
        return brd

    cdef np.intp_t limit_to_ought(self, np.double_t g, Amalgamation other):
        # Die Schranke und das Sollen
        # can a new whole overcome its' parts?
        cdef Border brd

        brd = self._whole(g, other)
        # print (whole > self.energy + EPS, whole, self.energy + EPS)
        return brd.limit > self.energy + EPS

    cdef Amalgamation merge_amalgamations(self, np.double_t g, Amalgamation other):
        cdef:
            np.intp_t osize, oclusters
            np.double_t oenergy
            Border brd
            Amalgamation ret

        ret = self
        if self.size == 1:
            ret = Amalgamation(1, 0., 1)
# ----------------------
        osize, oenergy, oclusters = other.size, other.energy, other.clusters

        brd = self._whole(g, other)
# ----------------------
        if brd.limit > self.energy + EPS:
            ret.energy = brd.jump*ret.size
            ret.clusters = 1
# ----------------------
        if brd.limit > oenergy + EPS:
            oenergy = brd.jump*osize
            oclusters = 1
# ----------------------

        ret._amalgamate(osize, oenergy, oclusters)
        return ret

    cdef void _amalgamate(self, np.intp_t size, np.double_t energy, np.intp_t clusters):
        # self.energy += energy
        self.energy = merge_means(self.clusters, self.energy, clusters, energy)
        self.size += size
        self.clusters += clusters
