#from numpy import arange, array, empty,zeros

cimport numpy as np
import numpy as np
from numpy.random import multinomial

##### SSA #########
# http://pyinsci.blogspot.de/2008/10/fast-gillespies-direct-algorithm-in.html

DTYPE = np.double
ctypedef np.double_t DTYPE_t
ctypedef np.int_t INT_t

cdef class Model:

    cdef object vn,rates,inits,pv
    cdef np.ndarray tm,res,time,series
    cdef int pvl,nvars,steps
    cdef object ts

    def __init__(
            self,
            vnames,
            rates,
            inits,
            tmat,
            propensity
    ):
        '''
         * variable_names: list of strings
         * rates: list of fixed rate parameters
         * inits: list of initial values of variables
         * propensity: list of lambda functions of the form:
            lambda r,ini: some function of rates ans inits.
        '''
        self.vn = vnames
        self.rates = rates
        self.inits = inits
        self.tm = tmat
        self.pv = propensity#[compile(eq,'errmsg','eval') for eq in propensity]
        self.pvl = len(self.pv) #length of propensity vector
        self.nvars = len(self.inits) #number of variables
        self.time = np.zeros(1)
        self.series = np.zeros(1)
        self.steps = 0

    def run(
            self,
            method='SSA',
            int tmax=10,
            int reps=1
    ):
        cdef np.ndarray[DTYPE_t,ndim=3] res = np.zeros((tmax,self.nvars,reps),dtype=np.float64)
        tvec = np.arange(tmax)
        self.res = res
        cdef int i, steps
        if method =='SSA':
            for i in range(0, reps):
                steps = self.GSSA(tmax,i)
            print(steps,' steps')
        elif method == 'SSAct':
            pass
        self.time=tvec
        self.series=self.res
        self.steps=steps

    def getStats(self):
        return self.time,self.series,self.steps

    cpdef int GSSA(
            self,
            int tmax = 50,
            int round = 0
    ):
        '''
        Gillespie Direct algorithm
        '''
        ini = self.inits
        r = self.rates
        pvi = self.pv
        cdef int l,steps,i,tim
        cdef double a0,tc, tau
        #cdef np.ndarray[INT_t] tvec
        cdef np.ndarray[DTYPE_t] pv
        l=self.pvl
        pv = np.zeros(l, dtype=np.float64)
        tm = self.tm
        #tvec = np.arange(tmax,dtype=int)
        tc = 0
        steps = 0
        self.res[0,:,round]= ini
        a0=1.
        for tim in range(1, tmax):
            while tc < tim:
                for i in range(l):
                    pv[i] = pvi[i](r,ini)
                #pv = abs(array([eq() for eq in pvi]))# #propensity vector
                a0 = np.sum(pv) #a_sum(pv,l) #sum of all transition probabilities
                #print ini#,tim, pv, a0
                tau = (-1/a0)*np.log(np.random.random())
                event = multinomial(1,(pv/a0)) # event which will happen on this iteration
                ini += tm[:,event.nonzero()[0][0]]
                #print tc, ini
                tc += tau
                steps +=1
                if a0 == 0: break
            self.res[tim,:,round] = ini
            if a0 == 0: break
#        tvec = tvec[:tim]
#        self.res = res[:tim,:,round]
        return steps

    def CR(self,pv):
        """
        Composition reaction algorithm
        """
        pass

# cdef double a_sum(
#         np.ndarray a,
#         int len
# ):
#     cdef double s
#     cdef int i
#     s=0
#     for i in range(len):
#         s+=a[i]
#     return s

def l1(
        np.ndarray r,
        np.ndarray ini
):
    return r[0]*ini[0]*ini[1]

def l2(
        np.ndarray
        r,np.ndarray ini
):
    return r[1]*ini[1]

