// Copyright ©2016 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package distuv

import (
	"math"
	"math/rand"
)

// Bernoulli represents a random variable whose value is 1 with probability p and
// value of zero with probability 1-P. The value of P must be between 0 and 1.
// More information at https://en.wikipedia.org/wiki/Bernoulli_distribution.
type Bernoulli struct {
	P      float64
	Source *rand.Rand
}

// CDF computes the value of the cumulative density function at x.
func (b Bernoulli) CDF(x float64) float64 {
	if x < 0 {
		return 0
	}
	if x < 1 {
		return 1 - b.P
	}
	return 1
}

// Entropy returns the entropy of the distribution.
func (b Bernoulli) Entropy() float64 {
	if b.P == 0 {
		return 0
	}
	if b.P == 1 {
		return 1
	}
	q := 1 - b.P
	return -b.P*math.Log(b.P) - q*math.Log(q)
}

// ExKurtosis returns the excess kurtosis of the distribution.
func (b Bernoulli) ExKurtosis() float64 {
	pq := b.P * (1 - b.P)
	return (1 - 6*pq) / pq
}

// LogProb computes the natural logarithm of the value of the probability density function at x.
func (b Bernoulli) LogProb(x float64) float64 {
	if x == 0 {
		return math.Log(1 - b.P)
	}
	if x == 1 {
		return math.Log(b.P)
	}
	return math.Inf(-1)
}

// Mean returns the mean of the probability distribution.
func (b Bernoulli) Mean() float64 {
	return b.P
}

// Median returns the median of the probability distribution.
func (b Bernoulli) Median() float64 {
	p := b.P
	switch {
	case p < 0.5:
		return 0
	case p > 0.5:
		return 1
	default:
		return 0.5
	}
}

// NumParameters returns the number of parameters in the distribution.
func (Bernoulli) NumParameters() int {
	return 1
}

// Prob computes the value of the probability distribution at x.
func (b Bernoulli) Prob(x float64) float64 {
	return math.Exp(b.LogProb(x))
}

// Quantile returns the inverse of the cumulative probability distribution.
func (b Bernoulli) Quantile(p float64) float64 {
	if p < 0 || 1 < p {
		panic(badPercentile)
	}
	if p < 1-b.P {
		return 0
	}
	return 1
}

// Rand returns a random sample drawn from the distribution.
func (b Bernoulli) Rand() float64 {
	var rnd float64
	if b.Source == nil {
		rnd = rand.Float64()
	} else {
		rnd = b.Source.Float64()
	}
	if rnd < b.P {
		return 1
	}
	return 0
}

// Skewness returns the skewness of the distribution.
func (b Bernoulli) Skewness() float64 {
	return (1 - 2*b.P) / math.Sqrt(b.P*(1-b.P))
}

// StdDev returns the standard deviation of the probability distribution.
func (b Bernoulli) StdDev() float64 {
	return math.Sqrt(b.Variance())
}

// Survival returns the survival function (complementary CDF) at x.
func (b Bernoulli) Survival(x float64) float64 {
	return 1 - b.CDF(x)
}

// Variance returns the variance of the probability distribution.
func (b Bernoulli) Variance() float64 {
	return b.P * (1 - b.P)
}
