using SimJulia
using Distributions
using RandomStreams
using Distributions
const SEED = 12345
rand_dist(rng::MRG32k3a, Dist::Distribution) = quantile(Dist, rand(rng))
seeds = [SEED, SEED, SEED, SEED, SEED, SEED]
gen = MRG32k3aGen(seeds)
include("tally.jl")
include("tallystore.jl")
println("M/M/1 with processes")
#rates
λ = 1.9
μ = 2.0
ρ = λ/μ
warmup = 500 # this should be set with a formal procedure
type System
    W
    arrival
    service
    counter
    arrgen
    servgen
    
    function System(text::String)
        s = new()
        s.W = Tally(text)
        s.arrival = Exponential(1.0/λ)
        s.service = Exponential(1.0/μ)
        s.arrgen = next_stream(gen)
        s.servgen = next_stream(gen)
        return s;
    end
end
function restart(s::System)
    init(s.W)
    next_substream!(s.arrgen)
    next_substream!(s.servgen)
end
s = System("Waiting Times")
# Allow a simulation with a fixed number of clients
function source(env::Environment, s::System, limit::Float64)
    i = 0
    while (true)
        yield(Timeout(env, rand_dist(s.arrgen, s.arrival)))
        if (now(env) > limit) break end
        i += 1
        Process(env, customer, s, i, s.counter)
    end
end
function source_fixed(env::Environment, s::System, counter::Resource, nCusts::Int64)
    for i = 1:nCusts
        yield(Timeout(env, rand_dist(s.arrgen, s.arrival)))
        Process(env, customer, s, i, counter)
    end
end
function customer(env::Environment, s::System, idx::Int, counter::Resource)
    # Record the arrival time in the system
    arrive = now(env)
    yield(Request(counter))
    # The simulation clock now contains the time when the client goes to the server.
    # Record the waiting time
    if (idx > warmup)
        wait = now(env) - arrive
        add(s.W, wait)
    end
    yield(Timeout(env, rand_dist(s.servgen, s.service)))
    yield(Release(counter))
end
function onesim(s::System)    
    env = Environment()
    s.counter = Resource(env, 1)
    
    # We consider a system over 24 hours
    Process(env, source, s, 48*60.0)
    run(env)
end
onesim(s)
average(s.W)
nobs(s.W)
variance(s.W)
meanWaits = TallyStore("Average waiting times")
totWaits = TallyStore("Cumulative waiting times")
nObs = TallyStore("Number of observations")
nSim = 50
for n=1:nSim
    restart(s)
    onesim(s)
    w = average(s.W)
    N = nobs(s.W)
    add(meanWaits, w)
    add(totWaits, sum(s.W))
    add(nObs, Float64(N))
    println("Sim $n. Average waiting time: $w. Number of observations: $N")
end
average(meanWaits.t)
variance(meanWaits.t)
var(meanWaits.obs)
totw = average(totWaits.t)
no = average(nObs.t)
totw/no
function ci_normal(n::Int64, mean::Float64, stdev::Float64, α::Float64)
    z = quantile(Normal(), 1-α/2)
    w = z*stdev/sqrt(n)
    # Lower bound
    l = mean - w
    # Upper bound
    u = mean + w
    
    return l, u
end
ci_normal(nobs(meanWaits.t), average(meanWaits.t), stdev(meanWaits.t), 0.05)
function ci_tdist(n::Int64, mean::Float64, stdev::Float64, α::Float64)
    z = quantile(TDist(n-1), 1-α/2)
    w = z*stdev/sqrt(n)
    # Lower bound
    l = mean - w
    # Upper bound
    u = mean + w
    
    return l, u
end
ci_tdist(nobs(meanWaits.t)-1, average(meanWaits.t), stdev(meanWaits.t), 0.05)
Average waiting time according to M/M/1 formulas. In stationnary regime, the mean waiting time is $$ \overline{W} = \frac{\frac{\rho}{\mu}}{1-\rho} = \frac{\rho}{\mu-\lambda}. $$
w = (ρ/μ)/(1-ρ)
covariance = cov(totWaits.obs, nObs.obs)
cor(totWaits.obs, nObs.obs)
function ci_ratio(x, y::TallyStore, α::Float64)
    n = nobs(x.t)
    z = quantile(TDist(n-1), 1-α/2)
    μ1 = average(x.t)
    μ2 = average(y.t)
    # Ratio estimator
    if (μ2 == 0)
        ν = NaN
    else
        ν = μ1/μ2
    end
    
    # Variance of the ratio
    σ = sqrt((variance(x.t) + variance(y.t)*ν*ν - 2*cov(x.obs, y.obs)*ν)/(μ2*μ2))
    w = z*σ/sqrt(n)
    # Lower bound
    l = ν - w
    # Upper bound
    u = ν + w
    
    return ν, l, u
end
ci_ratio(totWaits, nObs, 0.05)