using Winston
using SDE
using Diffusion
using LinProc
include(Pkg.dir("SDE","src", "misc.jl"))

pic = 1
function dp(p)
	Winston.display(p)
	Winston.file(p, "examples/img/talk$pic.png")
	global pic
	pic += 1
end

function lyap(B::Real, ma::Real)
    ma/(2.*B)
end

function rgb(r,g, b)
	r = clamp(r,0.,1.)
	g = clamp(g,0.,1.)
	b = clamp(b,0.,1.)
 	prod(("#",
	repr(uint8(round(r*255.)))[3:],
	repr(uint8(round(g*255.)))[3:],
	repr(uint8(round(b*255.)))[3:]))
end

function addpath1(p, t, y)

	add(p, Curve(t,y, "color","black", "linewidth", 20./M))

	p
end

function addpath1(p, t, y, l)
	l = clamp(l, 0.0, 1.0)
	add(p, Curve(t,y, "color",rgb(l, 0., 1.-l, ), "linewidth", 20./M*(l+1.)/2.))
	#print(rgb(l, 1.-l, 1. ), " ")
	p
end
#M = 2000
M = 100

T = 0.25
N = 1000

R1 = (0., T)
R2 = (-2., 3.)
ts = linspace(0., T,N)
dt = diff(ts)
B = -5.
sigma = 2.
a = sigma^2
beta = 0.
lambda = lyap(B, -a)
p = FramedPlot()
setattr(p, "xrange", R1)
setattr(p, "yrange", R2)

for m in	 1:M

	y = euler(0., 1., (t,x)-> B*x + beta, (t,x) -> sigma, dt)
	p = addpath1(p, ts, y)
end
dp(p)

srand(1)
p = FramedPlot()
setattr(p, "xrange", R1)
setattr(p, "yrange", R2)
for m in 1:M

	y = sigma*brown1(1./sigma, T, N)
	p = addpath1(p, ts, y)
end
dp(p)

p = FramedPlot()
setattr(p, "xrange", R1)
setattr(p, "yrange", R2)

srand(1)
llmax = -Inf
for m in 1:M

	y = 1. .+ sigma*brown1(0., T, N)
	ll = sum( 1/a*(B.*y[1:end-1] + beta).*diff(y)) + 0.5*T*a
	llmax = max(llmax, ll)
	#print(round(exp(ll)), " ")
end
println(exp(llmax))
srand(1)
for m in 1:M

	y =  1. .+ sigma*brown1(0., T, N)
	ll = sum( 1/a*(B.*y[1:end-1] + beta).*diff(y)) + 0.5*T*a

	p = addpath1(p, ts, y, exp(ll-llmax))
end
dp(p)

srand(1)
p = FramedPlot()
setattr(p, "xrange", R1)
setattr(p, "yrange", R2)
su0 = 0.
su0v = 0.
for m in 1:M

	y = sigma*bb(1./sigma, 0.5/sigma, T, N)
	su0 +=  y[N/2]
	su0v +=  y[N/2]^2
	p = addpath1(p, ts, y)
end
(z0, se0) = mc2(M, su0, su0v)
add( p, SymmetricErrorBarsY(T/2, [z0], [se0]) )
dp(p)

p = FramedPlot()
setattr(p, "xrange", R1)
setattr(p, "yrange", R2)
srand(1)
llmax = -Inf
lmean = 0.
for m in 1:M

	y = sigma*bb(1./sigma, 0.5/sigma, T, N)
	ll = sum( 1/a*(B.*y[1:end-1] + beta).*diff(y) - 0.5/a*(B.*y[1:end-1] + beta).^2 .* dt )
	#print(round(exp(ll)), " ")
	llmax = max(llmax, ll)
	lmean += exp(ll)

end
lmean /= M
println(exp(llmax))
srand(1)

Z = zeros(2)
Z2 = zeros(2,2)

for m in 1:M

	y = sigma*bb(1./sigma, 0.5/sigma, T, N)
	ll = sum( 1/a*(B.*y[1:end-1] + beta).*diff(y) - 0.5/a*(B.*y[1:end-1] + beta).^2 .* dt )

	Z += [y[N/2] * exp(ll), exp(ll)]
	Z2 += [y[N/2] * exp(ll), exp(ll)]*[y[N/2] * exp(ll) exp(ll)]
	p = addpath1(p, ts, y, exp(ll-llmax))
end
(z1, se1) = selfadjmc(M, Z, Z2)
add( p, SymmetricErrorBarsY(T/2, [z1], [se1]) )
dp(p)

srand(1)
p = FramedPlot()
setattr(p, "xrange", R1)
setattr(p, "yrange", R2)

su2 = 0.
su2v = 0.

for m in 1:M

	y = euler(0., 1., (s,x) -> scalar(LinProc.bstar(T, 0.5, B, beta, a, lambda)(s,x)), (t,x) -> sigma, dt)
	su2 += y[N/2]
	su2v += y[N/2]^2
	p = addpath1(p, ts, y)

end
(z2, se2) = mc2(M, su2, su2v)
add( p, SymmetricErrorBarsY(T/2, [z2], [se2]) )
dp(p)

println("Expect. at T/2: unweighted ($z0 $se0) weighted  ($z1 $se1) bridge  ($z2 $se2) ")
println("Average weight: ", lmean, " theoretical ", exp(LinProc.lp(T, 1.0, 0.5, B, beta, lambda*eye(1)))/exp(LinProc.lp0(T, 1.0, 0.5, 0, 1/a*eye(1))))


``` this is a julia source file, and random stuff
```

using InstantiateFromURL
github_project("QuantEcon/quantecon-notebooks-julia", version = "0.4.0")

x = 2

using Plots
function snapabove(g, a)
  function f(x)
    if x > a     # "a" is captured in the closure f
      return g(x)
    else
      return g(a)
    end
  end
  return f  # closure with the embedded a
end

f(x) = x^2
h = snapabove(f, 2.0)

gr(fmt=:png);
plot(h, 0.0:0.1:5.0)

for i in 1:3
    print(i)
end
x_values = 1:5

for x = x_values
  println(x*x)
end

[ i + j for i in 1:3, j in 4:6 ]
function foo(x)
    if x > 0
        return "positive"
    end
    return "nonpositive"
end

x_vec = [0.0 2.0 3.0 4.0]
y_vec = sin.(x_vec)

using LinearAlgebra, Statistics

function chisq(k)
  @assert k > 0
  z = randn(k)
  return sum(z -> z^2, z)
end

y = chisq.([2 4 6])

f(x, y) = [1, 2, 3] ⋅ x + y   # "⋅" can be typed by \cdot<tab>
f([3, 4, 5], 2)   # uses vector as first parameter
f.(Ref([3, 4, 5]), [2, 3])

twice(f,x) = f(f(x))
f(x) = x^2
@show twice(f, 2.0)

twice(x -> x^2, 2.0)

a = 5
g(x) = a * x
twice(g,2.0)

using Expectations, Distributions
@show d = Exponential(2.0)
f(x) = x^2
@show expectation(f, d);  # E(f(x))

function multiplyit(a, g)
    return x -> a * g(x)  # function with `g` used in the closure
end
f(x) = x^2
h = multiplyit(2.0, f)    # use our quadratic, returns a new function which doubles the result
h(2)     #

a = zeros(2,2)
println(a)
x = Array{Float64}(undef, 2, 2)
println(x)
x[1,1] = 1;
println(x)

a = [1 2; 3 4]
a[:, 2] = [4 5]

a[:,2] = [3;3]
println(a)

a = [1 2; 3 4]
b = a' # transpose
c = Matrix(b)  # convert to matrix
d = collect(b)
a = [0,1,-1]
b = sort(a, rev=false)
println(b)
println(a)

a = zeros(1,3,4);
b = dropdims(a,dims = 1)

using Parameters
function f(parameters)
    @unpack α, β = parameters # poor style, error prone if adding parameters
    return α + β
end
parameters = (α = 0.1, γ = "test", β = 0.2)
f(parameters)

function f(x)
  if x > 0
    return x
  else
    return NaN
  end
end

using Base: show_supertypes
show_supertypes(Int32)
@show subtypes(Int32)

struct Foo
    a::Float64
    b::Int64
    c::Vector{Float64}
end
foo_nt = FooNotTyped(2.0, 3, [1.0, 2.0, 3.0])
typeof(foo_nt)

f(x) = x
x_iv = [1.0]
#f(x) = 1.1 * x # fixed-point blows

# BAD!
xstar = fixedpoint(f, x_iv).zero # assumes convergence
xsol = nlsolve(f, x_iv).zero # assumes convergence

# GOOD!
result = fixedpoint(f, x_iv)
converged(result) || error("Failed to converge in $(result.iterations) iterations")
xstar = result.zero

f(x) = 1.1 * x

result = nlsolve(f, x_iv)
converged(result) || error("Failed to converge in $(result.iterations) iterations")
xsol = result.zero

using LinearAlgebra, Statistics
using QuantEcon, QuadGK, FastGaussQuadrature, Distributions, Expectations
using Interpolations, Plots, LaTeXStrings, ProgressMeter

li = LinearInterpolation(x, y)
li_spline = CubicSplineInterpolation(x, y)

@show li(0.3) # evaluate at a single point

scatter(x, y, label = "sampled data", markersize = 4)
plot!(xf, li.(xf), label = "linear")
plot!(xf, li_spline.(xf), label = "spline")

f(x) = x > 0.0 ? x : nothing
@code_warntype f(1)

function f(a, b)
    y = (a + 8b)^2
    return 7y
end

@code_native f(1.0,2.0)


using OrdinaryDiffEq, Test, DiffEqBase
using ForwardDiff, Printf
using DiffEqProblemLibrary.ODEProblemLibrary: importodeproblems; importodeproblems()
import DiffEqProblemLibrary.ODEProblemLibrary: prob_ode_linear, prob_ode_2Dlinear,
                            prob_ode_bigfloatlinear, prob_ode_bigfloat2Dlinear
# use `PRINT_TESTS = true` to print the tests, including results
const PRINT_TESTS = false
print_results(x) = if PRINT_TESTS; @printf("%s \n", x) end

# points and storage arrays used in the interpolation tests
const interpolation_points = 0:1//2^(4):1
const interpolation_results_1d = fill(zero(typeof(prob_ode_linear.u0)), length(interpolation_points))
const interpolation_results_2d = Vector{typeof(prob_ode_2Dlinear.u0)}(undef, length(interpolation_points))
for idx in eachindex(interpolation_results_2d)
  interpolation_results_2d[idx] = zero(prob_ode_2Dlinear.u0)
end

f_linear_inplace = (du,u,p,t) -> begin @. du = 1.01 * u end
(::typeof(f_linear_inplace))(::Type{Val{:analytic}}, u0, p, t) = exp(1.01*t)*u0
prob_ode_linear_inplace = ODEProblem(f_linear_inplace, [0.5], (0.,1.))
const interpolation_results_1d_inplace = Vector{typeof(prob_ode_linear_inplace.u0)}(undef, length(interpolation_points))
for idx in eachindex(interpolation_results_1d_inplace)
  interpolation_results_1d_inplace[idx] = zero(prob_ode_linear_inplace.u0)
end

const deriv_test_points = range(0, stop=1, length=5)

# perform the regression tests
# NOTE: If you want to add new tests (for new algorithms), you have to run the
#       commands below to get numerical values for `tol_ode_linear` and
#       `tol_ode_2Dlinear`.
function regression_test(alg, tol_ode_linear, tol_ode_2Dlinear; test_diff1 = false, nth_der = 1, dertol=1e-6)
  PRINT_TESTS && println("\n", alg)

  sol = solve(prob_ode_linear, alg, dt=1//2^(2), dense=true)
  sol(interpolation_results_1d, interpolation_points)
  sol(interpolation_points[1])
  sol2 = solve(prob_ode_linear, alg, dt=1//2^(4), dense=true, adaptive=false)
  for i in eachindex(sol2)
    print_results( @test maximum(abs.(sol2[i] - interpolation_results_1d[i])) < tol_ode_linear )
  end
  for N in 1:nth_der
    # prevent CI error
    nth_der != 1 && @show N
    if test_diff1
      sol(interpolation_results_1d, interpolation_points, Val{N})
      der = sol(interpolation_points[1], Val{N})
      @test interpolation_results_1d[1] ≈ der
      for t in deriv_test_points
        deriv = sol(t, Val{N})
        @test deriv ≈ ForwardDiff.derivative(t -> sol(t, Val{N-1}), t) rtol=dertol
      end
    end
  end

  sol = solve(prob_ode_linear_inplace, alg, dt=1//2^(2), dense=true)
  sol(interpolation_results_1d_inplace, interpolation_points, idxs=1:1)
  sol(interpolation_results_1d_inplace, interpolation_points)
  sol(interpolation_points[1], idxs=1:1)
  sol(interpolation_points[1])
  for N in 1:nth_der
    # prevent CI error
    nth_der != 1 && @show N
    if test_diff1
      sol(interpolation_results_1d_inplace, interpolation_points, Val{N}, idxs=1:1)
      sol(interpolation_results_1d_inplace, interpolation_points, Val{N})
      sol(interpolation_points[1], Val{N}, idxs=1:1)
      der = sol(interpolation_points[1], Val{N})
      @test interpolation_results_1d_inplace[1] ≈ der
      for t in deriv_test_points
        deriv = sol(t, Val{N}, idxs=1)
        @test deriv ≈ ForwardDiff.derivative(t -> sol(t, Val{N-1}; idxs=1), t) rtol=dertol
      end
    end
  end

  sol  = solve(prob_ode_2Dlinear, alg, dt=1//2^(2), dense=true)
  sol(interpolation_results_2d,  interpolation_points)
  sol(interpolation_points[1])
  sol2 = solve(prob_ode_2Dlinear, alg, dt=1//2^(4), dense=true, adaptive=false)
  for i in eachindex(sol2)
    print_results( @test maximum(maximum.(abs.(sol2[i] - interpolation_results_2d[i]))) < tol_ode_2Dlinear)
  end
end

# Some extra tests using Euler()
prob = prob_ode_linear
sol  = solve(prob, Euler(), dt=1//2^(2), dense=true)
interpd_1d = sol(0:1//2^(4):1)
sol2 = solve(prob, Euler(), dt=1//2^(4), dense=true)
sol3 = solve(prob, Euler(), dt=1//2^(5), dense=true)

prob = prob_ode_2Dlinear
sol  = solve(prob, Euler(), dt=1//2^(2), dense=true)
interpd = sol(0:1//2^(4):1)

interpd_idxs = sol(0:1//2^(4):1,idxs=1:2:5)

@test minimum([interpd_idxs[i] == interpd[i][1:2:5] for i in 1:length(interpd)])

interpd_single = sol(0:1//2^(4):1,idxs=1)

@test typeof(interpd_single.u) <: Vector{Float64}

@test typeof(sol(0.5,idxs=1)) <: Float64

A = rand(4,2)
sol(A,0.777)
A == sol(0.777)
sol2 = solve(prob, Euler(), dt=1//2^(4), dense=true)

@test maximum(map((x)->maximum(abs.(x)),sol2 - interpd)) < .2

sol(interpd, 0:1//2^(4):1)

@test maximum(map((x)->maximum(abs.(x)),sol2 - interpd)) < .2

sol  = solve(prob, Euler(), dt=1//2^(2), dense=false)

@test !(sol(0.6)[4,2] ≈ 0)

# Euler
regression_test(Euler(), 0.2, 0.2)

# Midpoint
regression_test(Midpoint(), 1.5e-2, 2.3e-2)

println("SSPRKs")

# SSPRK22
regression_test(SSPRK22(), 1.5e-2, 2.5e-2; test_diff1 = true, nth_der = 2, dertol = 1e-15)

# SSPRK33
regression_test(SSPRK33(), 7.5e-4, 1.5e-3; test_diff1 = true, nth_der = 2, dertol = 1e-15)

# SSPRK53
regression_test(SSPRK53(), 2.5e-4, 4.0e-4; test_diff1 = true)

# SSPRK63
regression_test(SSPRK63(), 1.5e-4, 3.0e-4; test_diff1 = true)

# SSPRK73
regression_test(SSPRK73(), 9.0e-5, 2.0e-4; test_diff1 = true)

# SSPRK83
regression_test(SSPRK83(), 6.5e-5, 1.5e-4; test_diff1 = true)

# SSPRK432
regression_test(SSPRK432(), 4.0e-4, 8.0e-4; test_diff1 = true, nth_der = 2, dertol = 1e-13)

# SSPRK932
regression_test(SSPRK932(), 6.0e-5, 1.0e-4; test_diff1 = true)

# SSPRK54
regression_test(SSPRK54(), 3.5e-5, 5.5e-5)

# SSPRK104
regression_test(SSPRK104(), 1.5e-5, 3e-5)

println("RKs")

# RK4
regression_test(RK4(), 4.5e-5, 1e-4)

# CarpenterKennedy2N54
regression_test(CarpenterKennedy2N54(), 3.0e-5, 5.0e-5)

# DP5
regression_test(DP5(), 5e-6, 1e-5; test_diff1 = true, nth_der = 4, dertol = 1e-14)

# BS3
regression_test(BS3(), 5e-4, 8e-4)

# OwrenZen3
regression_test(OwrenZen3(), 1.5e-4, 2.5e-4; test_diff1 = true, nth_der = 3, dertol = 1e-9)

# OwrenZen4
regression_test(OwrenZen4(), 6.5e-6, 1.5e-5; test_diff1 = true, nth_der = 4, dertol = 1e-10)

# OwrenZen5
regression_test(OwrenZen5(), 1.5e-6, 2.5e-6; test_diff1 = true, nth_der = 5, dertol = 1e-8)

# Tsit5
regression_test(Tsit5(), 2e-6, 4e-6; test_diff1 = true, nth_der = 4, dertol = 1e-6)

# TanYam7
regression_test(TanYam7(), 4e-4, 6e-4)

# TsitPap8
regression_test(TsitPap8(), 1e-3, 3e-3)

# Feagin10
regression_test(Feagin10(), 6e-4, 9e-4)

# BS5
regression_test(BS5(), 4e-8, 6e-8; test_diff1 = true, nth_der = 1, dertol = 1e-12)
regression_test(BS5(lazy=false), 4e-8, 6e-8; test_diff1 = true, nth_der = 1, dertol = 1e-12)

prob = prob_ode_linear
sol  = solve(prob, BS5(), dt=1//2^(1), dense=true, adaptive=false)
interpd_1d_long = sol(0:1//2^(7):1)
sol2 = solve(prob, BS5(), dt=1//2^(7), dense=true, adaptive=false)
print_results( @test maximum(map((x)->maximum(abs.(x)),sol2 - interpd_1d_long)) < 2e-7 )

# DP8
regression_test(DP8(), 2e-7, 3e-7; test_diff1 = true, nth_der = 1, dertol = 1e-15)

prob = prob_ode_linear
sol  = solve(prob, DP8(), dt=1//2^(2), dense=true)
sol(interpd_1d_long, 0:1//2^(7):1) # inplace update
sol2 = solve(prob, DP8(), dt=1//2^(7), dense=true, adaptive=false)
print_results( @test maximum(map((x)->maximum(abs.(x)),sol2 - interpd_1d_long)) < 2e-7 )

println("Verns")

# Vern6
regression_test(Vern6(), 7e-8, 7e-8; test_diff1 = true, nth_der = 1, dertol = 1e-9)
regression_test(Vern6(lazy=false), 7e-8, 7e-8; test_diff1 = true, nth_der = 1, dertol = 1e-9)

prob = remake(prob_ode_bigfloatlinear;u0=big(0.5))

sol  = solve(prob, Vern6(), dt=1//2^(2), dense=true)
interpd_1d_big = sol(0:1//2^(7):1)
sol2 = solve(prob, Vern6(), dt=1//2^(7), dense=true, adaptive=false)
print_results( @test maximum(map((x)->maximum(abs.(x)),sol2[:] - interpd_1d_big)) < 5e-8 )

prob_ode_bigfloatveclinear = ODEProblem((u,p,t)->p*u,[big(0.5)],(0.0,1.0),big(1.01))
prob = prob_ode_bigfloatveclinear
sol  = solve(prob, Vern6(), dt=1//2^(2), dense=true)
interpd_big = sol(0:1//2^(4):1)
sol2 = solve(prob, Vern6(), dt=1//2^(4), dense=true, adaptive=false)
print_results( @test maximum(map((x)->maximum(abs.(x)),sol2 - interpd_big)) < 5e-8 )

# Vern7
regression_test(Vern7(), 3e-9, 5e-9; test_diff1 = true, nth_der = 1, dertol = 1e-10)
regression_test(Vern7(lazy=false), 3e-9, 5e-9; test_diff1 = true, nth_der = 1, dertol = 1e-10)

# Vern8
regression_test(Vern8(), 3e-8, 5e-8; test_diff1 = true, nth_der = 1, dertol = 1e-7)
regression_test(Vern8(lazy=false), 3e-8, 5e-8; test_diff1 = true, nth_der = 1, dertol = 1e-7)

# Vern9
regression_test(Vern9(), 1e-9, 2e-9; test_diff1 = true, nth_der = 4, dertol = 2e-2)
regression_test(Vern9(lazy=false), 1e-9, 2e-9; test_diff1 = true, nth_der = 4, dertol = 2e-2)

println("Rosenbrocks")

# Rosenbrock23
regression_test(Rosenbrock23(), 3e-3, 6e-3; test_diff1 = true, nth_der = 1, dertol = 1e-14)

# Rosenbrock32
regression_test(Rosenbrock32(), 4e-4, 6e-4; test_diff1 = true, nth_der = 1, dertol = 1e-14)

# Rodas4
regression_test(Rodas4(), 8.5e-6, 2e-5)

# GenericImplicitEuler
regression_test(GenericImplicitEuler(), 0.2, 0.354)

# GenericTrapezoid
regression_test(GenericTrapezoid(), 7e-3, 1.4e-2)

# ExplicitRK
regression_test(ExplicitRK(), 7e-5, 2e-4)

prob = prob_ode_linear
sol  = solve(prob, ExplicitRK(), dt=1//2^(2), dense=true)
# inplace interp of solution
sol(interpd_1d_long,0:1//2^(7):1)
sol2 = solve(prob, ExplicitRK(), dt=1//2^(7), dense=true, adaptive=false)
print_results( @test maximum(map((x)->maximum(abs.(x)),sol2 - interpd_1d_long)) < 6e-5 )

prob = prob_ode_2Dlinear
sol  = solve(prob, ExplicitRK(), dt=1//2^(2), dense=true)
sol(interpd, 0:1//2^(4):1)
sol2 = solve(prob, ExplicitRK(), dt=1//2^(4), dense=true, adaptive=false)
print_results( @test maximum(map((x)->maximum(abs.(x)),sol2 - interpd)) < 2e-4 )


using Test, MyTestRepo

x = [0.0, 2.0, 4.0]
y = [0.0, 3.0, 5.0]
α = lsq_fit(x, y)
@test isapprox(α, [1/6, 1.25])

plot_fit(x, y, "solution.png")


@testset "$TEST $G" begin

g5 = DG(4)
add_edge!(g5,1,2); add_edge!(g5,2,3); add_edge!(g5,1,3); add_edge!(g5,3,4)
z = dfs_tree(g5, 1)

@test ne(z) == 3
@test nv(z) == 4
@test !has_edge(z, 1, 3)

@test topological_sort_by_dfs(g5) == [1, 2, 3, 4]

# TODO add back
# @test !has_cycles(g5)
# @test is_tree(g5)

# g = DG(3)
# add_edge!(g,1,2); add_edge!(g,2,3); add_edge!(g,3,1)
# @test has_cycles(g)
# @test !is_tree(g)

# g = G(4)
# add_edge!(g,1,2); add_edge!(g,2,3); add_edge!(g,2,4);
# @test !has_cycles(g)
# @test is_tree(g)

# add_edge!(g,1,3);
# @test has_cycles(g)
# @test !is_tree(g)

end # testset


# extensionsequences.jl

#################################################
# Abstract extension type and generic interface
#################################################

# In general, extension sequences specialize on the type of the underlying
# data. This means operations with extension sequences are typically fast.
# Since a point to an array is stored, memory is allocated every time an
# extension sequence is constructed. This may be avoided in future versions
# of Julia.

"""
Any subtype of ExtensionSequence embeds an indexable vectorlike object with finite length
and extends it into a bi-infinite sequence. The extension can be indexed with any
integer.

Indexing with an index in the allowed range of the embedded vector simply returns
the corresponding element. Indexing with indices outside this range result in
a computation. For example, for periodic extensions, the indices are mapped to
the allowed range using periodicity.

Usually the indices of the embedded vector `a` range from `1` to `length(a)`, but
that need not be the case. The extension respects the range returned by
`linearindices(a)`. One could use an OffsetArray with indices `-2:2`.

In current versions of Julia, construction of an ExtensionSequence allocates
memory, because the types store a reference to the embedded vector.
"""
abstract ExtensionSequence{T} <: Sequence{T}

# We assume that the embedded vector is in the field 'a'.
# A convention we follow is that variable 'k' refers to indices of the sequence,
# and variable 'i' to indices of the embedded vector.

"The embedded vector of the extension sequence."
subvector(s::ExtensionSequence) = s.a

"The length of the embedded vector of the extension sequence."
sublength(s::ExtensionSequence) = length(linearindices(s.a))

"The first linear index of the embedded vector."
first_subindex(s::ExtensionSequence) = first(linearindices(s.a))

"The last linear index of the embedded vector."
last_subindex(s::ExtensionSequence) = last(linearindices(s.a))

# The indexing logic can refer to the `element` function defined for all extension types
# Concrete subtypes of ExtensionSequence have to implement `extensiontype`.
getindex(s::ExtensionSequence, k) = element(extensiontype(s), subvector(s), k)

# By implementing `conj` and `reverse`, we get `transpose` and `ctranspose` for free
# Concrete types should implement a `similar` function that returns a sequence of
# the same type but with a different embedded vector.
for op in (:conj,)
    @eval $op(s::ExtensionSequence) = similar(s, $op(subvector(s)))
end

reverse(s::ExtensionSequence) = _reverse(s, subvector(s))
_reverse(s, a) = similar(s, reverse_indices(a))

#######################
# Periodic extensions
#######################

"""
A PeriodicSequence extends a vector `a` periodically to a bi-infinite sequence.

The indices of `a` map to `a`. Indices outside this range are mapped
by periodization modulo the length of `a`.
"""
immutable PeriodicSequence{A,T} <: ExtensionSequence{T}
    a       :: A
end

PeriodicSequence(a::AbstractVector) = PeriodicSequence{typeof(a),eltype(a)}(a)

similar(p::PeriodicSequence, a) = PeriodicSequence(a)

extensiontype(s::PeriodicSequence) = PeriodicExtension()

#######################
# CompactSequence
#######################

"""
CompactSequence extends a vector `a` with zeros to a bi-infinite sequence.

The indices of `a` map to `a`. Indices outside this range correspond
to zero values.
"""
immutable CompactSequence{A,T} <: ExtensionSequence{T}
    a :: A
end

typealias ZeroPaddingSequence CompactSequence

CompactSequence{A}(a::A) = CompactSequence{A,eltype(A)}(a)

similar(s::CompactSequence, a) = CompactSequence(a)

extensiontype(s::CompactSequence) = ZeroPadding()

iscompact(::CompactSequence) = true

nzrange(s::CompactSequence) = linearindices(subvector(s))

#######################
# Constant padding
#######################

"""
A ConstantPaddingSequence extends a vector `a` with a given `constant` to a bi-infinite sequence.

The indices of `a` map to `a`. Indices outside this range correspond to the constant
value.
"""
immutable ConstantPaddingSequence{A,T} <: ExtensionSequence{T}
    a           ::  A
    constant    ::  T
end

ConstantPaddingSequence(a::AbstractVector, constant) = ConstantPaddingSequence{typeof(a),eltype(a)}(a, constant)

constant(s::ConstantPaddingSequence) = s.constant

similar(s::ConstantPaddingSequence, a) = ConstantPaddingSequence(a, constant(s))

extensiontype(s::ConstantPaddingSequence) = ConstantPadding(s.constant)

#######################
# Symmetric extensions
#######################

"""
A SymmetricSequence extends a vector 'a' symmetrically to a bi-infinite sequence.

The indices of `a` map to `a`. Indices outside this range are mapped by symmetrizing.

The symmetry around each of the endpoints can be whole-point (the endpoint is not repeated)
or half-point (the endpoint is repeated). The symmetry can also be even (symmetric)
or odd (anti-symmetric).

Definition:

immutable SymmetricSequence{SYM_LEFT,SYM_RIGHT,PAR_LEFT,PAR_RIGHT,A,T} <: ExtensionSequence{T}

Type parameters indicate the type of symmetry and its parity:
- SYM_LEFT:  either :wp (whole point) or :hp (half point) near left endpoint
- SYM_RIGHT: either :wp or :hp for right endpoint
- PAR_LEFT: either :odd or :even symmetry near left endpoint
- PAR_RIGHT: also :odd or :even
- A: the type of the embedded vector
- T: the eltype of the sequence
"""
immutable SymmetricSequence{SYM_LEFT,SYM_RIGHT,PAR_LEFT,PAR_RIGHT,A,T} <: ExtensionSequence{T}
    a :: A
end

SymmetricSequence(a::AbstractVector) = symmetric_extension_wholepoint_even(a)

similar{SL,SR,PL,PR}(s::SymmetricSequence{SL,SR,PL,PR}, a) =
    SymmetricSequence{SL,SR,PL,PR,typeof(a),eltype(a)}(a)

# Provide four of the sixteen combinations for convenience. Construct the others by explicitly
# specifying the type parameters symbols.
symmetric_extension_wholepoint_even(a) =
    SymmetricSequence{:wp,:wp,:even,:even,typeof(a),eltype(a)}(a)

symmetric_extension_halfpoint_even(a) =
    SymmetricSequence{:hp,:hp,:even,:even,typeof(a),eltype(a)}(a)

symmetric_extension_wholepoint_odd(a) =
    SymmetricSequence{:wp,:wp,:odd,:odd,typeof(a),eltype(a)}(a)

symmetric_extension_halfpoint_odd(a) =
    SymmetricSequence{:hp,:hp,:odd,:odd,typeof(a),eltype(a)}(a)

# We have to be careful when reversing a symmetric extension: the endpoints change places
function reverse{SL,SR,PL,PR}(s::SymmetricSequence{SL,SR,PL,PR})
    b = reverse_indices(subvector(s))
    SymmetricSequence{SR,SL,PR,PL,typeof(b),eltype(b)}(b)
end

for op in (:left_parity, :right_parity, :left_symmetry, :right_symmetry)
    @eval $op(s::SymmetricSequence) = $op(extensiontype(s))
end

extensiontype{SL,SR,PL,PR}(s::SymmetricSequence{SL,SR,PL,PR}) =
    SymmetricExtension{SL,SR,PL,PR}()


"""
```
binarize!(UnimodalRosin(), img)
```

Binarizes the unimodal image passed into the `img` parameter into background (`Gray(0)`)
pixels, and foreground (`Gray(1)`) pixels using Rosin's algorithm.

# Arguments

## `img`
An `AbstractGray` image which is to be binarized into background (`Gray(0)`)
and foreground (`Gray(1)`) pixels.

# Output
Modifies the `img` parameter to become the binarized image.

# Details
This algorithm first selects the bin in the histogram with the highest frequency.
The algorithm then searches from the location of the maximum bin to the last bin
of the histogram for the first bin with a frequency of 0 (known as the minimum bin.).
A line is then drawn that passes through both the maximum and minimum bins. The
bin with the greatest orthogonal distance to the line is chosen as the threshold
value.

## Assumptions
This algorithm assumes that:

* The histogram is unimodal
* There is always at least one bin that has a frequency of 0. If not, the
algorithm will use the last bin as the minimum bin.
* If the histogram includes multiple bins with a frequency of 0, the algorithm
will select the first zero bin as its minimum.
* If there are multiple bins with the greatest orthogonal distance, the leftmost
bin is selected as the threshold.

# Example

Compute the threshold for the "moonsurface" image in the TestImages package.
```julia

using ImageBinarization

img = testimage("moonsurface")
binarize!(UnimodalRosin(),img)
```

# Reference
[1] P. L. Rosin, “Unimodal thresholding,” Pattern Recognition, vol. 34, no. 11, pp. 2083–2096, Nov. 2001.

# See Also
"""
function binarize(algorithm::UnimodalRosin,  img::AbstractArray{T,2}) where T <: AbstractGray
  binarize!(algorithm, copy(img))
end

function binarize!(algorithm::UnimodalRosin,  img::AbstractArray{T,2}) where T <: AbstractGray
  edges, counts = build_histogram(img,  256)
  t = find_threshold(HistogramThresholding.UnimodalRosin(), counts[1:end], edges)
  map!(img,img) do val
    val < t ? Gray(0) : Gray(1)
  end
end


using Gaston
g = Gaston;
set_terminal("x11");

c = g.CurveConf();
c.legend = "Random";
c.plotstyle = "errorbars";

y = exp(-(1:.1:4.9));
ylow = y - 0.05*rand(40);
yhigh = y + 0.05*rand(40);
g.addcoords(1:40,y,c);
g.adderror(0.1*rand(40));

a = g.AxesConf();
a.title = "Error bars (ydelta)";
g.addconf(a);
g.llplot();


using Reactive
using Interact

export trim_viz, function_viz, fzeros_viz,
       bisection_viz, limit_viz,
       derivative_viz, critical_pts_viz,
       newton_viz, riemann_viz

"""

Some visualizations in the `MTH229` package:

- `trim_viz(f, a, b). Shows a graph of `f` with controls to trim large negative and positive `y` values.

- `functions_viz(f, a, b)`. Shows where a function is positive, increasing, or concave up over `[a,b]`.

- `fzeros_viz(f, a, b)`. Shows zeros of a function over an interval using `fzeros`

- `bisection_viz(f, a , b)`. For a bracketing interval, shows first few bisection steps.

- `limit_viz(f, c, dir="+")`. Shows limiting value as `x` approaches `c`.

- `derivative_viz(f, c)`. Shows the secant line and its approach to the tangent line for different values of "`h`."

- `critical_pts_viz(f, a, b)`. Shows zeros of the derivative found with `fzeros`. (So *may* show some places where derivative is undefined too.)

- `newton_viz(f, x0). A simple function to visualize some `steps` of Newton's method. The values of `a` and `b` are optional, but, if not set, the x-viewing window will be determined by the points of the sequence of approximations

- `riemann_viz(f, a, b)`. Show the Riemann sum partition in action.

"""
visualizations = nothing
export visualizations

"""

`trim_viz(f::Function, a, b)`: plot `f` over `[a,b]` with trimming of large and small values

"""
function trim_viz(f::Function, a=-5, b=5)
    trim(f, lo, hi) = x -> f(x) < lo ? NaN : (f(x) > hi ? NaN : f(x))
    @manipulate for lo in -[2^i for i in 1:10], hi in [2^i for i in 1:10]
        plot(trim(f, lo, hi), a, b)
    end
end

"""

Simple illustration of how to find positive, increasing or concavity graphically. Uses `plotif` helper.

```
function_viz(sin, a, b)
```
"""
function function_viz(f::Function, a=-5, b=5)
    @manipulate for u in ["positive", "increasing", "concave up"]
        if u == "positive"
            p = plotif(f, f, a, b, title="Highlighting where f > 0")
        elseif u == "increasing"
            p = plotif(f, D(f), a, b, title="Showng where f' > 0")
        else
            p = plotif(f, D(f,2), a, b, title="Showing where f'' > 0")
        end
        p
    end
end

"""

fzeros_viz: show zeros of a function over `[a,b]`. Uses `fzeros`, so may miss some.

```
fzeros_viz(f, a, b)
```
"""
function fzeros_viz(f::Function, a, b)
    zs = fzeros(f, a, b)
    plot(f, a, b, legend=false)
    scatter!(zs, 0*zs)
end

"""

`bisection_viz(f, a, b)`: for a bracketing interval `[a,b]`, show first few steps of the bisection method

```
bisection_viz(sin, 3, 4)
```
"""
function bisection_viz(f, a, b)
    lo,hi = a, b
    rt = fzero(f, lo, hi)

    @manipulate for n=slider(1:8, value=1, label="n")
        cvged = false
        a,b = lo, hi
        for i in 1:n
            c = (a + b)/2
            for j in 2:i
                if f(c) == 0
                    cvged = true
                    break
                elseif f(a)*f(c) < 0
                    b = c
                else
                    a = c
                end
            end
        end

        p = plot(f, lo, hi, linewidth=3, legend=false)
        scatter!(p, [rt], [0], markersize=3)
        if cvged
            scatter!(p, [rt], [0], markersize=6)
        else
            plot!(p, [a, (a + b)/2], [0,0], linewidth=3)
            plot!(p, [(a + b)/2, b], [0,0], linewidth=3)
        end
        p
    end
end

"""
`limit_viz(f, c)`. Show graph of `f` and how `x` approaches `c`

```
f(x) = sin(x) / x
limit_viz(f, 0)
```
"""
function limit_viz(f::Function, c, dir="+")
    L = N(limit(f, c))
    @manipulate for i in slider(0:5, value=0, label="n")
        h = (dir == "+" ? (1/2)^i : -(1/2)^i)
        s = (dir == "+" ? "+" : "-")
        val = f(c + h)
        plot(f, c-1, c + 1, linewidth=3, title="At c $s (1/2)^$i f is $(round(val,3))", legend=false)
        plot!([c-1, c+1], L * [1,1])
        plot!([c-1, c+1], val * [1,1])
        scatter!([c+h], [f(c+h)], markersize=3)
    end
end

"""
Simple illustration of how secant line approaches tangent line as `h` goes to `0`. This plots `f` from `[c-1/2, c + 1/2]` and draws a secant line for different values of `h`.

Example:
```
derivative_viz(sin, pi/4)
```
"""
function derivative_viz(f::Function, c, a=c-1/2, b=c+1/2)

    @manipulate for j in slider(1:8, value=1, label="h=(1/2)^i")
	h = (1/2)^j
        m = (f(c + h) - f(c)) / h
        err = abs(f'(c) - m)
        e = Int(round(log2(1 / err), 0))

	p = plot(f, a, b, title="Plotting secant line with h=(1/2)^$j, error is about (1/2)^(-$e)", linewidth=3, legend=false)
	m = (f(c + h) - f(c)) / h
	plot!(p, x -> f(c) + m * (x-c), a, b, linewidth=3)
        scatter!(p, [c, c+h], [f(c), f(c+h)], markersize=3)
#	plot!(p, x -> f(c) + f'(c)*(x-c))
        p
    end

end

"""

critical_pts_viz: show critical points of a function over `[a,b]`. Uses `fzeros`, so may miss some.

```
critical_pts_viz(f, a, b)
```
"""
function critical_pts_viz(f::Function, a, b)
    zs = fzeros(f', a, b)
    plot(f, a, b, legend=false)
    scatter!(zs, f.(zs))
end

"""
newton_viz

Simple visualization of Newton's method

```
f(x) = log(x) - 1/10
newton_viz(f, 1) # newton_viz(f, 1, steps=20)
"""
function newton_viz(f::Function, x0, a=Inf, b = -Inf; steps=10)
    @manipulate for nsteps = slider(1:steps, value=1, label="no. steps")
        newton_vis(f, x0, a, b; steps=nsteps)
    end
end

"""

Riemann visualization.

Show right-riemann approximation for different values of `n`.

```
riemann_viz(sin, 0, pi)
```

"""
function riemann_viz(f::Function, a, b)
    @manipulate for j in slider(1:8, value=1, label="no. rectangles")
        n = 2^j
        val = riemann(f, a, b, n)
        act_val = quadgk(f, a, b)[1]
        err = abs(val - act_val)
        e = Int(round(log2(1 / abs(val - act_val)), 0))
        p = plot(f, a, b, linewidth=3, legend=false, title="Riemann sum with n=2^$j: $(round(val,3)). Error is about 2^(-$e)")
        delta = (b-a)/n
        for i in 1:n
            plot!(p, a + delta * [i-1, i, i, i-1, i-1], f(a + i * delta) * [0,0,1,1,0], color=:blue)
        end
        p
    end
end


function a_star(initial, goal_test, next_points, heuristic)
    node = nothing
    # value = Node.cost + Node.heuristic
    frontier = PriorityQueue{Node,Float64}() # value from low -> high
    node = Node(initial, nothing, 0, heuristic(initial))
    enqueue!(frontier, node, node.cost+node.heuristic)

    explored = Dict{Tuple{Int64,Int64}, Float64}() # Node.point,Node.cost

    while !isempty(frontier)
        current_node = dequeue!(frontier)
        current_pt = current_node.point

        if goal_test(current_pt)
            return current_node
        end

        for next_pt in next_points(current_pt)
            next_cost = current_node.cost + 1

            if !in(next_pt,keys(explored)) # has not visited next_pt or
                explored[next_pt] = next_cost
                node = Node(next_pt, current_node, next_cost, heuristic(next_pt))
                enqueue!(frontier, node, node.cost+node.heuristic)
            elseif next_cost < explored[next_pt] # next_pt is lower cost
                explored[next_pt] = next_cost
                node = Node(next_pt, current_node, next_cost, heuristic(next_pt))
                frontier[node] = node.cost+node.heuristic
            end
        end

        #empty!(frontier)
    end

    # solution not found
    return nothing
end

"""
    bfs(initial::Tuple{Int64,Int64}, goal_test, next_ponts)

Solve a maze using breadth-first search algorithm.  Returns the `Node` at the
goal or nothing is no solution found.  Use the parent of `Node` to trace back
to the start.

# Arguments
- `initial`: Starting point in the maze
- `goal_test`: Function`(Tuple{Int64,Int64})` to test if the goal has reached
- `next_points`: Function`(Tuple{Int64,Int64}) -> Vector{Tuple{Int64,Int64}}` to get next points to move to

"""
function bfs(initial::P, goal_test, next_points) where {P} # point type
    # P = Tuple{Int64,Int64} # point type
    frontier = Queue{Node{P}}()
    enqueue!(frontier, Node{P}(initial)) # starts with initial guess

    # Positions where we have been to
    explored = Set{P}()
    push!(explored, initial)

    while !isempty(frontier)
        current_node = dequeue!(frontier)
        current_pt = current_node.point
        #@show current_pt
        if goal_test(current_pt)
            #@show current_pt
            return current_node
        end

        for next_pt in next_points(current_pt)
            if in(next_pt, explored)
                continue
            end

            push!(explored, next_pt)
            enqueue!(frontier, Node{P}(next_pt, current_node))
        end

        #empty!(frontier)
    end

    # no solution
    return nothing
end

"""
    dfs(initial, goal_test, successors)

Depth-first search

# Arguments
- `initial`: Starting point in the maze
- `goal_test`: Function to test if goal has reached
- `successors`: Function to get a list of next locations to try
"""
function dfs(
    initial::Tuple{Int64,Int64}, # MazeLocation,
    goal_test, # function to test if goal reached
    successors, # function that takes MazeLocation and returns a list of next MazeLocations
    )

    ds = DataStructures

    frontier = ds.Stack{Node}()
    push!(frontier, Node(initial))

    explored = Set{Tuple{Int64,Int64}}()
    push!(explored, initial)

    while !isempty(frontier)
        current_node = pop!(frontier) # Node
        current_pt = current_node.point
        if goal_test(current_pt)
            return current_node # break
        end

        for nextPoint in successors(current_pt)
            if in(nextPoint, explored)
                continue
            end
            push!(explored, nextPoint)
            push!(frontier, Node(nextPoint, current_node))
        end

        # @show explored
        # empty!(explored)
    end

    # no solution
    return nothing
end

function mst(g::WeightedGraph{V,E}, start::Int64) where {V,E<:WeightedEdge}
    vertex_count = length(g.vertices)

    result = Vector{WeightedEdge}()
    if !(1 <= start <= vertex_count)
        return result
    end

    que = PriorityQueue{WeightedEdge,Float64}()
    visited = zeros(Bool, vertex_count) #Vector{Bool}(undef, vertex_count)

    # Internal function has access to local variables
    # such as `visited`.
    function visit(index)
        visited[index] = true
        for edge in edges_of(g, index)
            if !visited[edge[2]] # neighbor not yet visited
                enqueue!(que, edge, weight(edge))
            end
        end
    end

    visit(start)
    while !isempty(que)
        edge = dequeue!(que)
        if visited[edge[2]]
            continue
        end

        push!(result, edge)
        visit(edge[2])
    end

    return result
end

# function print_weighted_path(g::WeightedGraph, edges::Vector{WeightedEdge})
#     for edge in edges
#         println("$(vertex_at(g, edge[1])) $(weight(edge)) > $(vertex_at(g, edge[2]))")
#     end
# end

"""
    DijkstraResult

Results from Dijkstra algorithm
"""
struct DijkstraResult
    """
    Distance to each node from the starting node
    """
    distances::Vector{Float64}
    """
    node index => edge that leads to the node in the shortest path
    """
    path_db::Dict{Int64,WeightedEdge}
end

function dijkstra(g::WeightedGraph{V,E}, start::V)::DijkstraResult where {V,E<:WeightedEdge}
    start_ind = index_of(g, start)
    vertex_count = length(g.vertices)

    # Initialization to `undef` is fine
    distances = Vector{Float64}(undef, vertex_count)
    distances[start_ind] = 0
    visited = zeros(Bool, vertex_count)
    visited[start_ind] = true

    path_db = Dict{Int64,WeightedEdge}()
    que = PriorityQueue{DijkstraNode,Float64}()
    enqueue!(que, DijkstraNode(start_ind, 0), 0)

    while !isempty(que)
        node = dequeue!(que)
        distance2node = distances[node.index]

        for edge in edges_of(g, node.index)
            next_index = edge[2] # neighbor

            distance_to_next_index1 = distances[next_index] # undef || current distance
            distance_to_next_index2 = distance2node + weight(edge) # new distance

            if !visited[next_index]
                visited[next_index] = true
                distances[next_index] = distance_to_next_index2 # so undef is fine
                # Record the edge that has the shortest distance to next node
                path_db[next_index] = edge
                enqueue!(que, DijkstraNode(next_index, distance_to_next_index2), distance_to_next_index2)
            elseif distance_to_next_index2 < distance_to_next_index1
                visited[next_index] = true # not necessary
                distances[next_index] = distance_to_next_index2 # so undef is fine
                # Record the edge that has the shortest distance to next node
                path_db[next_index] = edge
                delete!(que, DijkstraNode(next_index, distance_to_next_index1))
                enqueue!(que, DijkstraNode(next_index, distance_to_next_index2), distance_to_next_index2)
            end
        end

        #empty!(que) #avoid infinite loop during construction
    end

    return DijkstraResult(distances, path_db)
end

"""
    distances_to_distance_db(g::WeightedGraph{V,E}, distances::Vector{Float64})::Dict{V,Float64} where {V,E<:WeightedEdge}

Convert the distance array to distance map.  Return mapping of
```
Node -> distance to Node from starting Node
```
Notice the starting `Node` was specified during the solution process and not here.
"""
function distances_to_distance_db(g::WeightedGraph{V,E}, distances::Vector{Float64})::Dict{V,Float64} where {V,E<:WeightedEdge}
    db = Dict{V,Float64}()

    for (i,dist) in enumerate(distances)
        db[vertex_at(g,i)] = dist
    end

    return db
end

"""
    shortest_path(g::WeightedGraph{V,E}, path_db::Dict{Int64,E}, start::Int64, finish::Int64)::Vector{E} where {V,E<:WeightedEdge}

Calculate the shortest path from `start` to `finish` using `path_db`.
"""
function shortest_path(path_db::Dict{Int64,E}, start::Int64, finish::Int64)::Vector{E} where {E<:WeightedEdge}
    path = Vector{E}()

    if isempty(path_db)
        return path
    end

    edge = path_db[finish]
    push!(path, edge)
    while edge[1] != start
        edge = path_db[edge[1]]
        push!(path, edge)
    end

    path = Base.reverse(path)

    return path

end


@testset "filter_topk" begin

  @test_throws ArgumentError filter_topk(spzeros(5,5), -1)

  A = sparse([1.0 2.0 3.0;
       4.0 5.0 6.0;
       7.0 8.0 9.0])

  B = Array(filter_topk(A,1))
  @test B == [0.0 0.0 0.0;
              0.0 0.0 0.0;
              7.0 8.0 9.0]

  B = Array(filter_topk(A,0))
  @test B == [0.0 0.0 0.0;
              0.0 0.0 0.0;
              0.0 0.0 0.0]

  B = Array(filter_topk(sparse(
             [0.0 0.0 0.0;
              0.0 0.0 0.0]
  ), 1))
  @test B == [0.0 0.0 0.0;
              0.0 0.0 0.0]

  @test filter_topk!(spzeros(0,0),0) == spzeros(0,0)
  @test filter_topk!(spzeros(0,0),5) == spzeros(0,0)

  @test filter_topk!(spzeros(0,5),5) == spzeros(0,5)
  @test filter_topk!(spzeros(5,0),5) == spzeros(5,0)

  @test filter_topk!(spzeros(4,5),5) == spzeros(4,5)

  @test filter_topk!(spzeros(4,5),5) == spzeros(4,5)

  B = Array(filter_topk(sparse(
             [2.0 3.0 5.0;
              0.0 0.0 6.0;
              0.0 0.5 0.5]
  ), 3))
  @test B == [2.0 3.0 5.0;
   0.0 0.0 6.0;
   0.0 0.5 0.5]

end


# circ operator

f(x) = x + 1
g(x) = x - 1

println((f ∘ g)(1))

# Map composition

println(map(first ∘ reverse ∘ uppercase, split("compose Functions Using map")))

# Function chaining by piping

1:10 |> sum |> x -> x^2 |> println

# Dot Syntax

arr = [1, 24, 36]

operatedArr = sqrt.(arr)
println(operatedArr)


using Unitful

@testset "Dipoles" begin

    dips = Dipole[]

    @testset "Create" begin

        dip1 = Dipole("Talairach", 1u"m", 2u"m", 1u"m", 0, 0, 0, 1, 1, 1)
        dip2 = Dipole("Talairach", 1u"m", 2u"m", 3u"m", 0, 0, 0, 2, 2, 2)

        dips = push!(dips, dip1)
        dips = push!(dips, dip2)

        @test length(dips) == 2
    end

    @testset "Show" begin

        show(dips[1])
        show(dips)
    end

    @testset "Mean" begin

        b = mean(dips)
        @test b.x == 1.0u"m"
        @test b.y == 2.0u"m"
        @test b.z == 2.0u"m"
    end

    @testset "Std" begin

        b = std(dips)
        @test b.x == 0.0u"m"
        @test b.y == 0.0u"m"
        @test b.z == std([1, 3])u"m"
    end
end


export auto_play

"""
    auto_play(answer::String; scorer::Function = min_max_score, verbose::Bool = true)
    auto_play(; scorer::Function = min_max_score, verbose::Bool = true)
    auto_play(a::Int; scorer::Function = min_max_score)

Computer solves a Wordle puzzle without user inputs. The number of steps to solve is returned.

* In the first case, `answer` is a five-letter word given by the user.
* In the second case, the five-letter word is randomly chosen by the computer.
* In the third case, an integer index into `ANS_LIST` is the input, and `verbose` is set to `false`.

The `scorcer` function is one of:
* `min_max_score`
* `entropy_score`
* `equi_score`

The first guess is chosen from the dictionary `FIRST_GUESS` depending on the `scorer` function.
"""
function auto_play(answer::String; scorer::Function = min_max_score, verbose::Bool = true)
    a = ANS_DICT[uppercase(answer)]  # this is the word we want to guess; throws an error if bad word

    stop = code_2_byte((2, 2, 2, 2, 2))
    history = Dict{Int,Byte}()

    g = FIRST_GUESS[scorer]
    if scorer == equi_score
        g = mod1(rand(Int), NG)
    end

    x = fast_wordle_score(g, a)
    history[g] = x
    if verbose
        wordle_print(g, x)
    end

    while x != stop
        possibles = filter_answers(history)
        g = best_guess(possibles, scorer)
        x = fast_wordle_score(g, a)
        if verbose
            wordle_print(g, x)
        end
        history[g] = x
    end
    return length(history)
end

function auto_play(; scorer::Function = min_max_score, verbose::Bool = true)
    a = mod1(rand(Int), NA)
    answer = ANS_LIST[a]
    auto_play(answer, scorer = scorer, verbose = verbose)
end

function auto_play(a::Int; scorer::Function = min_max_score)
    answer = ANS_LIST[a]
    auto_play(answer, scorer = scorer, verbose = false)

end

export score_all

"""
    score_all(scorer::Function = min_max_score)
Use the selected `scorer` function to solve all possible Wordle words
once each and return a vector containing the number of steps for each
solution.
"""
function score_all(scorer::Function = min_max_score)
    result = zeros(Int, NA)
    PM = Progress(NA)
    for a = 1:NA
        result[a] = auto_play(a, scorer = scorer)
        next!(PM)
    end
    return result
end

export all_play

scorer_list = [min_max_score, entropy_score, L2_score, equi_score]

export all_play

"""
    all_play(word::String)
Compare all solving methods to solve for the word.
"""
function all_play(answer::String)
    for s in scorer_list
        println("\nSolving with $s")
        steps = auto_play(answer, scorer=s)
        println("Solution in $steps steps")
    end
end


using RobotDynamics
using Rotations
using StaticArrays, LinearAlgebra
using BenchmarkTools

# Define the model struct to inherit from `RigidBody{R}`
struct Satellite{R,T} <: RigidBody{R}
    mass::T
    J::Diagonal{T,SVector{3,T}}
end
RobotDynamics.control_dim(::Satellite) = 6

# Define some simple "getter" methods that are required to evaluate the dynamics
RobotDynamics.mass(model::Satellite) = model.mass
RobotDynamics.inertia(model::Satellite) = model.J

# Define the 3D forces at the center of mass, in the world frame
function RobotDynamics.forces(model::Satellite, x::StaticVector, u::StaticVector)
    q = orientation(model, x)
    F = @SVector [u[1], u[2], u[3]]
    q*F  # world frame
end

# Define the 3D moments at the center of mass, in the body frame
function RobotDynamics.moments(model::Satellite, x::StaticVector, u::StaticVector)
    return @SVector [u[4], u[5], u[6]]  # body frame
end

# Build model
T = Float64
R = UnitQuaternion{T}
mass = 1.0
J = Diagonal(@SVector ones(3))
model = Satellite{R,T}(mass, J)

# Initialization
x,u = rand(model)
z = KnotPoint(x,u,0.1)
∇f = RobotDynamics.DynamicsJacobian(model)

# Continuous dynamics
dynamics(model, x, u)
jacobian!(∇f, model, z)

# Performance improvements for continuous dynamics Jacobian
b1 = @benchmark jacobian!($∇f, $model, $z)

function RobotDynamics.wrench_jacobian!(F, model::Satellite, z)
    x = state(z)
    u = control(z)
    q = orientation(model, x)
    ir, iq, iv, iω, iu = RobotDynamics.gen_inds(model)
    iF = SA[1,2,3]
    iM = SA[4,5,6]
    F[iF, iq] .= Rotations.∇rotate(q, u[iF])
    F[iF, iu[iF]] .= RotMatrix(q)
    for i = 1:3
        F[iM[i], iu[i+3]] = 1
    end
    return F
end
b2 = @benchmark jacobian!($∇f, $model, $z)

function RobotDynamics.wrench_sparsity(::Satellite)
    SA[false true  false false true;
       false false false false true]
end
b3 = @benchmark jacobian!($∇f, $model, $z)
println("Analytical Wrench Jacobian:  ", judge(minimum(b2), minimum(b1)))
println("w/ Wrench Jacobian Sparsity: ", judge(minimum(b3), minimum(b1)))

# Discrete dynamics
discrete_dynamics(RK2, model, z)
discrete_jacobian!(RK2, ∇f, model, z)


# This file calculates the roots of a sincfun in barycentric form
# based on the work of P.W. Lawrence, SIAM J. MATRIX ANAL. APPL., 34:1277--1300, 2013.

## TODO: This is simply the implementation via Julia's eig() routine. It could be faster
## using the specific algorithm in the paper. Also, once a differentiation command is
## implemented, it will be possible to find roots in BigFloat precision using Newton iteration.

export roots

function complexroots{D<:Domain,T<:Float64}(sf::sincfun{D,T})
    wv,xv,fv = (-one(T)).^(collect(0:sf.n-1)).*sf.ωv, ψ(sf.domain,convert(T,π)/2*sinh.(sf.jh)), sf.fϕv.*sf.ϕpv
    cutoff = abs.(wv) .≥ 10eps(T)
    wv,xv,fv = wv[cutoff],xv[cutoff],fv[cutoff]
    n = length(wv)
    wv,fv = wv/norm(wv),fv/norm(fv)
    A = sparse([zero(T) -fv'; wv diagm(xv)])
    s = [one(T);T[abs(fv[i]) == zero(T) ? one(T) : sqrt(abs(wv[i])/abs(fv[i])) for i=1:n]]
    S = sparse(collect(1:n+1),collect(1:n+1),s)
    B = diagm([zero(T);ones(T,n)])
    rts,V = eig(full(S\A*S),B)
    rts = rts[abs.(rts).<Inf]
end

function roots{D<:Domain,T<:Float64}(sf::sincfun{D,T})
    rts = complexroots(sf)
    rts = sort(real(rts[imag(rts) .== zero(T)]))
end

function roots{D<:Finite,T<:Float64}(sf::sincfun{D,T})
    rts = complexroots(sf)
    rts = real(rts[imag(rts) .== zero(T)])
    a,b = sf.domain.ab
    rts = sort(rts[a + 10sf.ωscale*eps(T) .< abs.(rts) .< b - 10sf.ωscale*eps(T)])
end

# This is a first attempt to use Lawrence's O(n^2) algorithm to transform the arrowhead matrix
# to upper Hessenberg.

#=
function complexroots{D<:Domain,T<:Float64}(sf::sincfun{D,T})
    wv,xv,fv = (-one(T)).^([0:sf.n-1]).*sf.ωv, sf.jh, sf.fϕpv
    cutoff = abs.(wv) .≥ 10eps(T)
    wv,xv,fv = wv[cutoff],xv[cutoff],fv[cutoff]
    #wvint,xvint,fvint = interlace2(wv),interlace2(xv),interlace2(fv)
    t,d,c = toupperhessenberg(wv,xv,fv)
    A = diagm([zero(T),d])+diagm(t,1)+diagm(t,-1)
    [A[1,i] += c[i] for i=1:length(c)]
    B = diagm([zero(T),ones(T,length(xv))])
    rts,V = eig(A,B)
    rts = rts[abs.(rts).<Inf]
    rts = ψ.(sf.domain,convert(T,π)/2*sinh(rts))
end

function toupperhessenberg{T<:Number}(w::Vector{T},x::Vector{T},f::Vector{T})
    @assert length(w) == length(x) == length(f)
    n = length(w)
    w,f = w/norm(w),f/norm(f)
    t,d = zeros(T,n),zeros(T,n)
    q = zeros(T,n,n)
    q[:,1] = w
    t[1] = norm(q[:,1])
    q[:,1] /= t[1]
    d[1] = dot(q[:,1],x.*q[:,1])
    q[:,2] = (x-d[1]).*q[:,1]
    t[2] = norm(q[:,2])
    q[:,2] /= t[2]
    for i=2:n-1
        d[i] = dot(q[:,i],x.*q[:,i])
        q[:,i+1] = (x-d[i]).*q[:,i]-t[i]*q[:,i-1]
        t[i+1] = norm(q[:,i+1])
        q[:,i+1] /= t[i+1]
    end
    d[n] = dot(q[:,n],x.*q[:,n])
    c = [zero(T), vec(-f'*q)]
    c[2] -= t[1]
    return t,d,c
end
=#
# Base.LinAlg.givensAlgorithm(f::T,g::T) returns cs, sn, r


# ---------------------------------------------------------------------------
# Violin Plot

const _violin_warned = [false]

function violin_coords(y; trim::Bool=false)
    kd = KernelDensity.kde(y, npoints = 200)
    if trim
        xmin, xmax = Plots.ignorenan_extrema(y)
        inside = Bool[ xmin <= x <= xmax for x in kd.x]
        return(kd.density[inside], kd.x[inside])
    end
    kd.density, kd.x
end

@recipe function f(::Type{Val{:violin}}, x, y, z; trim=true, side=:both)
    xsegs, ysegs = Segments(), Segments()
    glabels = sort(collect(unique(x)))
    bw = d[:bar_width]
    bw == nothing && (bw = 0.8)
    for (i,glabel) in enumerate(glabels)
        widths, centers = violin_coords(y[filter(i -> _cycle(x,i) == glabel, 1:length(y))], trim=trim)
        isempty(widths) && continue

        # normalize
        hw = 0.5_cycle(bw, i)
        widths = hw * widths / Plots.ignorenan_maximum(widths)

        # make the violin
        xcenter = Plots.discrete_value!(d[:subplot][:xaxis], glabel)[1]
        if (side==:right)
          xcoords = vcat(widths, zeros(length(widths))) + xcenter
        elseif (side==:left)
          xcoords = vcat(zeros(length(widths)), -reverse(widths)) + xcenter
        else
          xcoords = vcat(widths, -reverse(widths)) + xcenter
        end
        ycoords = vcat(centers, reverse(centers))

        push!(xsegs, xcoords)
        push!(ysegs, ycoords)
    end

    seriestype := :shape
    x := xsegs.pts
    y := ysegs.pts
    ()
end
Plots.@deps violin shape


module TwentyFour

import Combinatorics.multiset_permutations
import Base: length, show, getindex

export XXIV, solve

"""
`solve(nums...)` solves a Twenty-Four puzzle for the given numbers.
Returns a `String` giving the solution.
Example:
```
julia> solve(4,2,5,1)
"(4+2)*(5-1)"
```
"""
function solve(nums...)::String
  @assert length(nums)>1 "Please give two or more integers or rationals"
  X = XXIV(nums...)
  solver(X)
  if X.solved
    return X.solution[2:end-1]
  else
    return "No solution"
  end
end

"""
The default goal of a *Twenty Four* problem is 24.
"""
global DEFAULT_GOAL = 24

global const OPS = [ (+), (-), (*), (/)]
global const OP_SYMS = [ "+", "-", "*", "/"]

"""
`XXIV` is an instance of a Twenty Four problem.
"""
mutable struct XXIV
  nums::Vector{Rational{Int}}
  numstrs::Vector{String}
  goal::Int
  solved::Bool
  solution::String

  function XXIV(num_list::Vector{Rational{Int}})
    new(deepcopy(num_list),map(str,num_list),DEFAULT_GOAL,false,"")
  end
end

function XXIV(vals...)
  num_list = [ Rational(x) for x in vals ]
  return XXIV(num_list)
end

XXIV() = XXIV(Rational{Int}[])

getindex(X::XXIV,i::Int) = X.nums[i]

function show(io::IO,X::XXIV)
  n = length(X)
  print(io, "[ ")
  for a in X.numstrs
    print(io,a*" ")
  end
  print(io,"] with goal $(X.goal)")
  if X.solved
    print(io," and solution $(X.solution)")
  end
  nothing
end

length(X::XXIV) = length(X.nums)

"""
`get_goal(X::XXIV)` returns the targe goal of this instance
of a *Twenty Four* problem. This is typically 24.
"""
get_goal(X::XXIV) = X.goal

"""
`set_goal(X::XXIV, g)` sets the goal of this *Twenty Four* problem
to `g`. Default `g` is 24.
"""
function set_goal(X::XXIV, g::Int=DEFAULT_GOAL)
  if X.goal == g
    return nothing
  end
  X.goal = g
  X.solved=false
  X.solution=""
  nothing
end

"""
`str(x::Rational{Int})` returns a nice string form of `x`.
"""
function str(x::Rational{Int})
  if denominator(x)==1
    return "$(numerator(x))"
  end
  return "$(numerator(x))/$(denominator(x))"
end

function solver(X::XXIV)
  n = length(X)
  # basis cases
  if n==0
    X.solved = false
    X.solution = ""
    return 0
  end

  if n == 1
    a = X[1]
    if a == X.goal
      X.solved = true
      X.solution = X.numstrs[1]
      return true
    else
      X.solved = false
      X.solution = ""
      return false
    end
  end

  for i=1:n
    for j=1:n
      if i!=j
        ii = i
        jj = j
        if i>j
          ii,jj = jj,ii
        end

        a = X.nums[i]
        sa = X.numstrs[i]
        b = X.nums[j]
        sb = X.numstrs[j]

        for t = 1:4
          XX = deepcopy(X)

          f = OPS[t]
          sf = OP_SYMS[t]

          ab = f(a,b)
          sab = "(" * sa * sf * sb * ")"
          deleteat!(XX.nums,(ii,jj))
          deleteat!(XX.numstrs,(ii,jj))
          append!(XX.nums,[ab])
          append!(XX.numstrs,[sab])
          try
            if solver(XX)
              X.solution = XX.solution
              X.solved = true
              return true
            end
            catch
          end

        end # next t

      end  # endif
    end # next j
  end # next i
  return false

end  # end of function

end # end of module


using LinearSolve, LinearAlgebra, SparseArrays
using CUDA
using Test

n = 8
A = Matrix(I,n,n)
b = ones(n)
A1 = A/1; b1 = rand(n); x1 = zero(b)
A2 = A/2; b2 = rand(n); x2 = zero(b)

prob1 = LinearProblem(A1, b1; u0=x1)
prob2 = LinearProblem(A2, b2; u0=x2)

cache_kwargs = (;verbose=true, abstol=1e-8, reltol=1e-8, maxiter=30,)

function test_interface(alg, prob1, prob2)
    A1 = prob1.A; b1 = prob1.b; x1 = prob1.u0
    A2 = prob2.A; b2 = prob2.b; x2 = prob2.u0

    y = solve(prob1, alg; cache_kwargs...)
    @test A1 *  y  ≈ b1

    cache = SciMLBase.init(prob1,alg; cache_kwargs...) # initialize cache
    y = solve(cache)
    @test A1 *  y  ≈ b1

    cache = LinearSolve.set_A(cache,copy(A2))
    y = solve(cache)
    @test A2 *  y  ≈ b1

    cache = LinearSolve.set_b(cache,b2)
    y = solve(cache)
    @test A2 *  y  ≈ b2

    return
end

test_interface(GPUOffloadFactorization(), prob1, prob2)

A1 = prob1.A; b1 = prob1.b; x1 = prob1.u0
y = solve(prob1)
@test A1 *  y  ≈ b1


using ParetoSmoothedImportanceSampling
using StanSample, StatsFuns
using Printf
using JSON
using Test

ProjDir = @__DIR__

include(joinpath(ProjDir, "cvit.jl"))

# Data
data = JSON.parsefile(joinpath(ProjDir, "wells.data.json"))
y = Float64.(data["switched"])
x = Float64[data["arsenic"]  data["dist"]]
n, m = size(x)

# Model
model_str = read(open(joinpath(ProjDir, "arsenic_logistic.stan")), String)
sm1 = SampleModel("arsenic_logistic", model_str)

data1 = (p = m, N = n, y = Int.(y), x = x)
# Fit the model in Stan
rc1 = stan_sample(sm1; data=data1)
if success(rc1)
    nt1 = read_samples(sm1)

    # Compute LOO and standard error
    log_lik = nt1.log_lik'
    loo, loos, pk = psisloo(log_lik)
    elpd_loo = sum(loos)
    se_elpd_loo = std(loos) * sqrt(n)
    @printf(">> elpd_loo = %.1f, SE(elpd_loo) = %.1f\n", elpd_loo, se_elpd_loo)

    # Check the shape parameter k of the generalized Pareto distribution
    if all(pk .< 0.5)
        println("All Pareto k estimates OK (k < 0.5)")
    else
        pkn1 = sum((pk .>= 0.5) & (pk .< 1))
        pkn2 = sum(pk .>= 1)
        @printf(">> %d (%.0f%%) PSIS Pareto k estimates between 0.5 and 1\n", pkn1, pkn1/n*100)
        @printf(">> %d (%.0f%%) PSIS Pareto k estimates greater than 1\n", pkn2, pkn2/n*100)
    end
end
println()

# Fit a second model, using log(arsenic) instead of arsenic
x2 = Float64[log.(data["arsenic"])  data["dist"]]

# Model
data2 = (p = m, N = n, y = Int.(y), x = x2)
# Fit the model in Stan
rc2 = stan_sample(sm1; data=data2)

if success(rc2)
    nt2 = read_samples(sm1)
    # Compute LOO and standard error
    log_lik = nt2.log_lik'
    loo2, loos2, pk2 = psisloo(log_lik)
    elpd_loo = sum(loos2)
    se_elpd_loo = std(loos2) * sqrt(n)
    @printf(">> elpd_loo = %.1f, SE(elpd_loo) = %.1f\n", elpd_loo, se_elpd_loo)

    # Check the shape parameter k of the generalized Pareto distribution
    if all(pk .< 0.5)
        println("All Pareto k estimates OK (k < 0.5)")
    else
        pkn1 = sum((pk .>= 0.5) & (pk .< 1))
        pkn2 = sum(pk .>= 1)
        @printf(">> %d (%.0f%%) PSIS Pareto k estimates between 0.5 and 1\n", pkn1, pkn1/n*100)
        @printf(">> %d (%.0f%%) PSIS Pareto k estimates greater than 1\n", pkn2, pkn2/n*100)
    end
end

if success(rc1) && success(rc2)
    ## Compare the models
    loodiff = loos - loos2
    @printf("elpd_diff = %.1f, SE(elpd_diff) = %.1f\n",sum(loodiff), std(loodiff) * sqrt(n))
end
println()

## k-fold-CV
# k-fold-CV should be used if several khats>0.5
# in this case it is not needed, but provided as an example

model_str = read(open(joinpath(ProjDir, "arsenic_logistic_t.stan")), String)
sm3 = SampleModel("arsenic_logistic_t", model_str);

cvitr, cvitst = cvit(n, 10, true)
kfcvs = similar(loos)
for cvi in 1:3
    standatacv = (p = m, N = length(cvitr[cvi]), Nt = length(cvitst[cvi]),
        x = x[cvitr[cvi],:], y = Int.(y[cvitr[cvi]]),
        xt = x[cvitst[cvi],:], yt = Int.(y[cvitst[cvi]]))

    # Fit the model in Stan
    rc3 = stan_sample(sm3; data=standatacv)
    if success(rc3)
        nt3 = read_samples(sm3)
        # Compute LOO and standard error
        log_likt = nt3.log_likt'
        local n_sam, n_obs = size(log_likt)
        kfcvs[cvitst[cvi]] .=
            reshape(logsumexp(log_likt .- log(n_sam), dims=1), n_obs)
    end
end


"""
    GreedyIP(m::Int, s::Int)

 - `m` is the desired number of inducing points
 - `s` is the minibatch size on which to select a new inducing point

Greedy approach first proposed by Titsias[1].
Algorithm loops over minibatches of data and select the best ELBO improvement.

[1] Titsias, M. Variational Learning of Inducing Variables in Sparse Gaussian Processes. Aistats 5, 567–574 (2009).
"""
struct Greedy <: OffIPSA
    m::Int # Number of inducing points
    s::Int # Minibatch size for selection
    function Greedy(m, s)
        m > 0 || throw(ArgumentError("Number of inducing points should be positive"))
        s > 0 || throw(ArgumentError("Size of the minibatch should be positive"))
        return new(m, s)
    end
end

function inducingpoints(
    rng::AbstractRNG,
    alg::Greedy,
    X::AbstractVector;
    y,
    kernel::Kernel,
    noise::Real,
    kwargs...,
)
    noise > 0 || throw(ArgumentError("Noise should be positive"))
    length(X) == length(y) || throw(ArgumentError("y and X have different lengths"))
    if alg.m >= length(X)
        return edge_case(alg.m, length(X), X)
    end
    return greedy_ip(rng, X, y, kernel, alg.m, alg.s, noise)
end

Base.show(io::IO, ::Greedy) = print(io, "Greedy Selection of Inducing Points")

function greedy_ip(
    rng::AbstractRNG,
    X::AbstractVector,
    y::AbstractVector,
    kernel::Kernel,
    m::Int,
    s::Int,
    noise::Real,
)
    N = length(X) # Number of samples
    i = rand(rng, 1:N) # Take a random initial point
    Z = collect.(X[i:i]) # Initialize empty array of IPs
    IP_set = Set{Int}(i) # Keep track of selected points
    f = AbstractGPs.GP(kernel) # GP object to compute the elbo
    for _ in 2:m
        # Evaluate on a subset of the points of a maximum size of 1000
        X_test = Set(sample(rng, 1:N, min(1000, N); replace=false))
        best_i = 0
        best_L = -Inf
        # Parse over random points of this subset
        new_candidates = collect(setdiff(X_test, IP_set))
        # Sample a minibatch of candidates
        d = sample(
            rng,
            collect(setdiff(X_test, IP_set)),
            min(s, length(new_candidates));
            replace=false,
        )
        for j in d # Loop over every sample and evaluate the elbo addition with each new sample
            new_Z = vcat(Z, X[j:j])
            L = AbstractGPs.elbo(f(X[collect(X_test)], noise), y[collect(X_test)], f(new_Z))
            if L > best_L
                best_i = j
                best_L = L
            end
        end
        push!(Z, X[best_i])
        push!(IP_set, best_i)
    end
    return Z
end

# function elbo(Z::AbstractVector, X::AbstractVector, y::AbstractVector, kernel::Kernel, σ²::Real)
#     Knm = kernelmatrix(kernel, X, Z)
#     Kmm = kernelmatrix(kernel, Z) + T(jitt) * I
#     Qff = Knm * (Kmm \ Knm')
#     Kt = kerneldiagmatrix(kernel, X) .+ T(jitt) - diag(Qff)
#     Σ = inv(Kmm) + Knm' * Knm / σ²
#     invQnn = 1/σ² * I - 1/ (σ²)^2 * Knm * inv(Σ) * Knm'
#     logdetQnn = logdet(Σ) + logdet(Kmm)
#     return -0.5 * dot(y, invQnn * y) - 0.5 * logdetQnn -
#            1.0 / (2 * σ²) * sum(Kt)
# end


@testset "Conjugation action" begin
    G = PermutationGroups.AbstractAlgebra.SymmetricGroup(4)
    S = [perm"(1,2)(4)", perm"(1,2,3,4)"]
    @test gens(G) == S

    pt = perm"(4)"
    @test Orbit(S, pt) isa Orbit
    @test length(Orbit(S, pt)) == 1

    pt = perm"(1,2)(4)"
    @test Orbit(S, pt) isa Orbit
    @test length(Orbit(S, pt)) == 6
    @test all(==(permtype(pt)), permtype.(Orbit(S, pt)))

    pt = perm"(1,2)(4)"
    @test Schreier(S, pt) isa Schreier
    tr = Schreier(S, pt)
    @test all(pt^tr[p] == p for p in tr)

    pt = perm"(2,3,4)"
    @test Schreier(S, pt) isa Schreier
    tr = Schreier(S, pt)
    @test all(pt^tr[p] == p for p in tr)
end


using Random: seed!

struct Bandit{A<:Integer, R<:AbstractFloat}
    actionspace::ActionSpace{A}
    rwdparams::Matrix{R}
    optsol::R
    function Bandit{A, R}(n=10, seed=1234) where {A<:Integer, R<:AbstractFloat}
        seed!(seed)
        rwdparams = randn(R, n, 2)
        new(ActionSpace(A(n)), rwdparams, maximum(rwdparams[:, 1]))
    end
end

Bandit() = Bandit{Int64, Float64}()

function step!(env::Bandit, action::Integer)
    reward = env.rwdparams[action, 1] + env.rwdparams[action, 2] * randn(eltype(env.rwdparams))
    regret = abs(env.optsol - env.rwdparams[action, 1])
    return reward, regret
end

reset!(::Bandit) = nothing


using Plasmo
using Plots, PlasmoPlots
gr()

graph = OptiGraph()

@optinode(graph,n1)
@variable(n1, y >= 2)
@variable(n1,x >= 0)
@constraint(n1,x + y >= 3)
@objective(n1, Min, y)

@optinode(graph,n2)
@variable(n2, y)
@variable(n2,x >= 0)
@constraint(n2,x + y >= 3)
@objective(n2, Min, y)

@optinode(graph,n3)
@variable(n3, y )
@variable(n3,x >= 0)
@constraint(n3,x + y >= 3)
@objective(n3, Min, y)

@linkconstraint(graph, n1[:x] + n2[:x] + n3[:x] == 3)

plt_graph1 = layout_plot(graph,node_labels = true,markersize = 60,labelsize = 30,
linewidth = 4,layout_options = Dict(:tol => 0.01,:iterations => 2));

plt_matrix1 = matrix_plot(graph,node_labels = true,markersize = 30);


#=
Very heavily inspired by Calculus.jl, but with an emphasis on performance and DiffEq API convenience.
=#

#=
Compute the finite difference interval epsilon.
Reference: Numerical Recipes, chapter 5.7.
=#
@inline function compute_epsilon(::Type{Val{:forward}}, x::T, relstep::Real, absstep::Real, dir::Real) where T<:Number
    return max(relstep*abs(x), absstep)*dir
end

@inline function compute_epsilon(::Type{Val{:central}}, x::T, relstep::Real, absstep::Real, dir=nothing) where T<:Number
    return max(relstep*abs(x), absstep)
end

@inline function compute_epsilon(::Type{Val{:hcentral}}, x::T, relstep::Real, absstep::Real, dir=nothing) where T<:Number
    return max(relstep*abs(x), absstep)
end

@inline function compute_epsilon(::Type{Val{:complex}}, x::T, ::Union{Nothing,T}=nothing, ::Union{Nothing,T}=nothing, dir=nothing) where T<:Real
    return eps(T)
end

@inline function default_relstep(fdtype::DataType, ::Type{T}) where T<:Number
    if fdtype==Val{:forward}
        return sqrt(eps(real(T)))
    elseif fdtype==Val{:central}
        return cbrt(eps(real(T)))
    elseif fdtype==Val{:hcentral}
        eps(T)^(1/4)
    else
        return one(real(T))
    end
end

function fdtype_error(funtype::Type{T}=Float64) where T
    if funtype<:Real
        error("Unrecognized fdtype: valid values are Val{:forward}, Val{:central} and Val{:complex}.")
    elseif funtype<:Complex
        error("Unrecognized fdtype: valid values are Val{:forward} or Val{:central}.")
    else
        error("Unrecognized returntype: should be a subtype of Real or Complex.")
    end
end


__precompile__(true)

module ToeplitzMatrices

import Compat.view

import StatsBase
include("iterativeLinearSolvers.jl")

import Base: *, \, full, getindex, print_matrix, size, tril, triu, inv, A_mul_B!, Ac_mul_B,
    A_ldiv_B!, convert
import Base.LinAlg: BlasReal, DimensionMismatch

export Toeplitz, SymmetricToeplitz, Circulant, TriangularToeplitz, Hankel,
       chan, strang

# Abstract
abstract AbstractToeplitz{T<:Number} <: AbstractMatrix{T}

size(A::AbstractToeplitz) = (size(A, 1), size(A, 2))
getindex(A::AbstractToeplitz, i::Integer) = A[mod(i, size(A,1)), div(i, size(A,1)) + 1]

convert(::Type{Matrix}, S::AbstractToeplitz) = full(S)
convert{T}(::Type{AbstractMatrix{T}}, S::AbstractToeplitz) = convert(AbstractToeplitz{T}, S)
convert{T}(::Type{AbstractArray{T}}, S::AbstractToeplitz) = convert(AbstractToeplitz{T}, S)

# Convert an abstract Toeplitz matrix to a full matrix
function full{T}(A::AbstractToeplitz{T})
    m, n = size(A)
    Af = Array(T, m, n)
    for j = 1:n
        for i = 1:m
            Af[i,j] = A[i,j]
        end
    end
    return Af
end

# Fast application of a general Toeplitz matrix to a column vector via FFT
function A_mul_B!{T}(α::T, A::AbstractToeplitz{T}, x::StridedVector{T}, β::T,
      y::StridedVector{T})
    m = size(A,1)
    n = size(A,2)
    N = length(A.vcvr_dft)
    if m != length(y)
        throw(DimensionMismatch(""))
    end
    if n != length(x)
        throw(DimensionMismatch(""))
    end
    if N < 512
        y[:] *= β
        for j = 1:n
            tmp = α * x[j]
            for i = 1:m
                y[i] += tmp*A[i,j]
            end
        end
        return y
    end
    for i = 1:n
        A.tmp[i] = x[i]
    end
    for i = n+1:N
        A.tmp[i] = 0
    end
    A_mul_B!(A.tmp, A.dft, A.tmp)
    for i = 1:N
        A.tmp[i] *= A.vcvr_dft[i]
    end
    A.dft \ A.tmp
    for i = 1:m
        y[i] *= β
        y[i] += α * (T <: Real ? real(A.tmp[i]) : A.tmp[i])
    end
    return y
end

# Application of a general Toeplitz matrix to a general matrix
function A_mul_B!{T}(α::T, A::AbstractToeplitz{T}, B::StridedMatrix{T}, β::T,
    C::StridedMatrix{T})
    l = size(B, 2)
    if size(C, 2) != l
        throw(DimensionMismatch("input and output matrices must have same number of columns"))
    end
    for j = 1:l
        A_mul_B!(α, A, view(B, :, j), β, view(C, :, j))
    end
    return C
end

# Translate three to five argument A_mul_B!
A_mul_B!(y, A::AbstractToeplitz, x) = A_mul_B!(one(eltype(A)), A, x, zero(eltype(A)), x)

# * operator
function (*)(A::AbstractToeplitz, B::StridedVecOrMat)
    T = promote_type(eltype(A), eltype(B))
    A_mul_B!(one(T), convert(AbstractMatrix{T}, A), convert(AbstractArray{T}, B), zero(T),
        size(B,2) == 1 ? zeros(T, size(A, 1)) : zeros(T, size(A, 1), size(B, 2)))
end

# Left division of a general matrix B by a general Toeplitz matrix A, i.e. the solution x of Ax=B.
function A_ldiv_B!(A::AbstractToeplitz, B::StridedMatrix)
    if size(A, 1) != size(A, 2)
        error("Division: Rectangular case is not supported.")
    end
    for j = 1:size(B, 2)
        A_ldiv_B!(A, view(B, :, j))
    end
    return B
end

function (\)(A::AbstractToeplitz, b::AbstractVector)
    T = promote_type(eltype(A), eltype(b))
    if T != eltype(A)
        throw(ArgumentError("promotion of Toeplitz matrices not handled yet"))
    end
    bb = similar(b, T)
    copy!(bb, b)
    A_ldiv_B!(A, bb)
end

# General Toeplitz matrix
type Toeplitz{T<:Number,S<:Number} <: AbstractToeplitz{T}
    vc::Vector{T}
    vr::Vector{T}
    vcvr_dft::Vector{S}
    tmp::Vector{S}
    dft::Base.DFT.Plan{S}
end

# Ctor
function Toeplitz(vc::Vector, vr::Vector)
    m, n = length(vc), length(vr)
    if vc[1] != vr[1]
        error("First element of the vectors must be the same")
    end

    T = promote_type(eltype(vc), eltype(vr), Float32)
    vcp, vrp = Vector{T}(vc), Vector{T}(vr)

    tmp = Array(promote_type(T, Complex{Float32}), m + n - 1)
    copy!(tmp, vcp)
    for i = 1:n - 1
        tmp[i + m] = vrp[n - i + 1]
    end
    dft = plan_fft!(tmp)
    return Toeplitz(vcp, vrp, dft*tmp, similar(tmp), dft)
end

convert{T}(::Type{AbstractToeplitz{T}},A::Toeplitz) = convert(Toeplitz{T},A)
convert{T}(::Type{Toeplitz{T}},A::Toeplitz) = Toeplitz(convert(Vector{T},A.vc),
                                                       convert(Vector{T},A.vr))

# Size of a general Toeplitz matrix
function size(A::Toeplitz, dim::Int)
    if dim == 1
        return length(A.vc)
    elseif dim == 2
        return length(A.vr)
    elseif dim > 2
        return 1
    else
        error("arraysize: dimension out of range")
    end
end

# Retrieve an entry
function getindex(A::Toeplitz, i::Integer, j::Integer)
    m = size(A,1)
    n = size(A,2)
    if i > m || j > n
        error("BoundsError()")
    end

    if i >= j
        return A.vc[i - j + 1]
    else
        return A.vr[1 - i + j]
    end
end

# Form a lower triangular Toeplitz matrix by annihilating all entries above the k-th diaganal
function tril(A::Toeplitz, k = 0)
    if k > 0
        error("Second argument cannot be positive")
    end
    Al = TriangularToeplitz(copy(A.vc), 'L', length(A.vr))
    if k < 0
      for i in -1:-1:k
          Al.ve[-i] = zero(eltype(A))
      end
    end
    return Al
end

# Form a lower triangular Toeplitz matrix by annihilating all entries below the k-th diaganal
function triu(A::Toeplitz, k = 0)
    if k < 0
        error("Second argument cannot be negative")
    end
    Al = TriangularToeplitz(copy(A.vr), 'U', length(A.vc))
    if k > 0
      for i in 1:k
          Al.ve[i] = zero(eltype(A))
      end
    end
    return Al
end

A_ldiv_B!(A::Toeplitz, b::StridedVector) =
    copy!(b, IterativeLinearSolvers.cgs(A, zeros(eltype(b), length(b)), b, strang(A), 1000, 100eps())[1])

# Symmetric
type SymmetricToeplitz{T<:BlasReal} <: AbstractToeplitz{T}
    vc::Vector{T}
    vcvr_dft::Vector{Complex{T}}
    tmp::Vector{Complex{T}}
    dft::Base.DFT.Plan
end
function SymmetricToeplitz{T<:BlasReal}(vc::Vector{T})
    tmp = convert(Array{Complex{T}}, [vc; zero(T); reverse(vc[2:end])])
    dft = plan_fft!(tmp)
    return SymmetricToeplitz(vc, dft*tmp, similar(tmp), dft)
end

convert{T}(::Type{AbstractToeplitz{T}},A::SymmetricToeplitz) = convert(SymmetricToeplitz{T},A)
convert{T}(::Type{SymmetricToeplitz{T}},A::SymmetricToeplitz) = SymmetricToeplitz(convert(Vector{T},A.vc))

function size(A::SymmetricToeplitz, dim::Int)
    if 1 <= dim <= 2
        return length(A.vc)
    else
        error("arraysize: dimension out of range")
    end
end

getindex(A::SymmetricToeplitz, i::Integer, j::Integer) = A.vc[abs(i - j) + 1]

A_ldiv_B!(A::SymmetricToeplitz, b::StridedVector) =
    copy!(b, IterativeLinearSolvers.cg(A, zeros(length(b)), b, strang(A), 1000, 100eps())[1])

# Circulant
type Circulant{T<:Number,S<:Number} <: AbstractToeplitz{T}
    vc::Vector{T}
    vcvr_dft::Vector{S}
    tmp::Vector{S}
    dft::Base.DFT.Plan
end

function Circulant{T<:Real}(vc::Vector{T})
    TT = promote_type(eltype(vc), Float32)
    tmp = zeros(promote_type(TT, Complex{Float32}), length(vc))
    return Circulant(real(vc), fft(vc), tmp, plan_fft!(tmp))
end
function Circulant(vc::Vector)
    T = promote_type(eltype(vc), Float32)
    tmp = zeros(promote_type(T, Complex{Float32}), length(vc))
    return Circulant(vc, fft(vc), tmp, plan_fft!(tmp))
end

convert{T}(::Type{AbstractToeplitz{T}},A::Circulant) = convert(Circulant{T},A)
convert{T}(::Type{Circulant{T}},A::Circulant) = Circulant(convert(Vector{T},A.vc))

function size(C::Circulant, dim::Integer)
    if 1 <= dim <= 2
        return length(C.vc)
    else
        error("arraysize: dimension out of range")
    end
end

function getindex(C::Circulant, i::Integer, j::Integer)
    n = size(C, 1)
    if i > n || j > n
        error("BoundsError()")
    end
    return C.vc[mod(i - j, length(C.vc)) + 1]
end

function Ac_mul_B{T<:Real}(A::Circulant{T}, B::Circulant{T})
    tmp = similar(A.vcvr_dft)
    for i = 1:length(tmp)
        tmp[i] = conj(A.vcvr_dft[i]) * B.vcvr_dft[i]
    end
    return Circulant(real(A.vc)(A.dft \ tmp), tmp, A.tmp, A.dft)
end
function Ac_mul_B(A::Circulant, B::Circulant)
    T = promote_type(eltype(A), eltype(B))
    tmp = similar(A.vcvr_dft, T)
    for i = 1:length(tmp)
        tmp[i] = conj(A.vcvr_dft[i]) * B.vcvr_dft[i]
    end
    tmp2 = A.dft \ tmp
    return Circulant(Vector{T}(tmp2), tmp, Vector{T}(A.tmp), eltype(A) == T ? A.dft : plan_fft!(tmp2))
end

function A_ldiv_B!{T}(C::Circulant{T}, b::AbstractVector{T})
    n = length(b)
    size(C, 1) == n || throw(DimensionMismatch(""))
    for i = 1:n
        C.tmp[i] = b[i]
    end
    C.dft * C.tmp
    for i = 1:n
        C.tmp[i] /= C.vcvr_dft[i]
    end
    C.dft \ C.tmp
    for i = 1:n
        b[i] = (T <: Real ? real(C.tmp[i]) : C.tmp[i])
    end
    return b
end

function inv{T<:Real}(C::Circulant{T})
    vdft = 1 ./ C.vcvr_dft
    return Circulant(real(C.dft \ vdft), copy(vdft), similar(vdft), C.dft)
end
function inv(C::Circulant)
    vdft = 1 ./ C.vcvr_dft
    return Circulant(C.dft \ vdft, copy(vdft), similar(vdft), C.dft)
end

function strang{T}(A::AbstractMatrix{T})
    n = size(A, 1)
    v = Array(T, n)
    n2 = div(n, 2)
    for i = 1:n
        if i <= n2 + 1
            v[i] = A[i,1]
        else
            v[i] = A[1, n - i + 2]
        end
    end
    return Circulant(v)
end
function chan{T}(A::AbstractMatrix{T})
    n = size(A, 1)
    v = Array(T, n)
    for i = 1:n
        v[i] = ((n - i + 1) * A[i, 1] + (i - 1) * A[1, min(n - i + 2, n)]) / n
    end
    return Circulant(v)
end

# Triangular
type TriangularToeplitz{T<:Number,S<:Number} <: AbstractToeplitz{T}
    ve::Vector{T}
    uplo::Char
    vcvr_dft::Vector{S}
    tmp::Vector{S}
    dft::Base.DFT.Plan
end

function TriangularToeplitz(ve::Vector, uplo::Symbol)
    n = length(ve)

    T = promote_type(eltype(ve), Float32)
    vep = Vector{T}(ve)

    tmp = zeros(promote_type(T, Complex{Float32}), 2n - 1)
    if uplo == :L
        copy!(tmp, vep)
    else
        tmp[1] = vep[1]
        for i = 1:n - 1
            tmp[n + i] = ve[n - i + 1]
        end
    end
    dft = plan_fft!(tmp)
    return TriangularToeplitz(vep, string(uplo)[1], dft * tmp, similar(tmp), dft)
end

function convert(::Type{Toeplitz}, A::TriangularToeplitz)
    if A.uplo == 'L'
        Toeplitz(A.ve, [A.ve[1]; zeros(length(A.ve) - 1)])
    else
        @assert A.uplo == 'U'
        Toeplitz([A.ve[1]; zeros(length(A.ve) - 1)], A.ve)
    end
end

convert{T}(::Type{AbstractToeplitz{T}},A::TriangularToeplitz) = convert(TriangularToeplitz{T},A)
convert{T}(::Type{TriangularToeplitz{T}},A::TriangularToeplitz) =
    TriangularToeplitz(convert(Vector{T},A.ve),A.uplo=='U'?(:U):(:L))

function size(A::TriangularToeplitz, dim::Int)
    if dim == 1 || dim == 2
        return length(A.ve)
    elseif dim > 2
        return 1
    else
        error("arraysize: dimension out of range")
    end
end

function getindex{T}(A::TriangularToeplitz{T}, i::Integer, j::Integer)
    if A.uplo == 'L'
        return i >= j ? A.ve[i - j + 1] : zero(T)
    else
        return i <= j ? A.ve[j - i + 1] : zero(T)
    end
end

function (*)(A::TriangularToeplitz, B::TriangularToeplitz)
    n = size(A, 1)
    if n != size(B, 1)
        throw(DimensionMismatch(""))
    end
    if A.uplo == B.uplo
        return TriangularToeplitz(conv(A.ve, B.ve)[1:n], A.uplo)
    end
    return Triangular(full(A), A.uplo) * Triangular(full(B), B.uplo)
end

Ac_mul_B(A::TriangularToeplitz, b::AbstractVector) =
    TriangularToeplitz(A.ve, A.uplo == 'U' ? :L : :U) * b

# NB! only valid for lower triangular
function smallinv{T}(A::TriangularToeplitz{T})
    n = size(A, 1)
    b = zeros(T, n)
    b[1] = 1 ./ A.ve[1]
    for k = 2:n
        tmp = zero(T)
        for i = 1:k-1
            tmp += A.uplo == 'L' ? A.ve[k - i + 1]*b[i] : A.ve[i + 1] * b[k - i]
        end
        b[k] = -tmp/A.ve[1]
    end
    return TriangularToeplitz(b, symbol(A.uplo))
end

function inv{T}(A::TriangularToeplitz{T})
    n = size(A, 1)
    if n <= 64
        return smallinv(A)
    end
    np2 = nextpow2(n)
    if n != np2
        return TriangularToeplitz(inv(TriangularToeplitz([A.ve, zeros(T, np2 - n)],
            symbol(A.uplo))).ve[1:n], symbol(A.uplo))
    end
    nd2 = div(n, 2)
    a1 = inv(TriangularToeplitz(A.ve[1:nd2], symbol(A.uplo))).ve
    return TriangularToeplitz([a1, -(TriangularToeplitz(a1, symbol(A.uplo)) *
        (Toeplitz(A.ve[nd2 + 1:end], A.ve[nd2 + 1:-1:2]) * a1))], symbol(A.uplo))
end

# A_ldiv_B!(A::TriangularToeplitz,b::StridedVector) = inv(A)*b
A_ldiv_B!(A::TriangularToeplitz, b::StridedVector) =
    copy!(b, IterativeLinearSolvers.cgs(A, zeros(eltype(b), length(b)), b, chan(A), 1000, 100eps())[1])

# extend levinson
StatsBase.levinson!(x::StridedVector, A::SymmetricToeplitz, b::StridedVector) =
    StatsBase.levinson!(A.vc, b, x)
function StatsBase.levinson!(C::StridedMatrix, A::SymmetricToeplitz, B::StridedMatrix)
    n = size(B, 2)
    if n != size(C, 2)
        throw(DimensionMismatch("input and output matrices must have same number of columns"))
    end
    for j = 1:n
        StatsBase.levinson!(view(C, :, j), A, view(B, :, j))
    end
    C
end
StatsBase.levinson(A::AbstractToeplitz, B::StridedVecOrMat) =
    StatsBase.levinson!(zeros(size(B)), A, copy(B))

# BlockTriangular
# type BlockTriangularToeplitz{T<:BlasReal} <: AbstractMatrix{T}
#     Mc::Array{T,3}
#     uplo::Char
#     Mc_dft::Array{Complex{T},3}
#     tmp::Vector{Complex{T}}
#     dft::Base.DFT.Plan
# end
# function BlockTriangularToeplitz{T<:BlasReal}(Mc::Array{T,3}, uplo::Symbol)
#     n, p, _ = size(Mc)
#     tmp = zeros(Complex{T}, 2n)
#     dft = plan_fft!(tmp)
#     Mc_dft = Array(Complex{T}, 2n, p, p)
#     for j = 1:p
#         for i = 1:p
#             Mc_dft[1,i,j] = complex(Mc[1,i,j])
#             for t = 2:n
#                 Mc_dft[t,i,j] = uplo == :L ? complex(Mc[t,i,j]) : zero(Complex{T})
#             end
#             Mc_dft[n+1,i,j] = zero(Complex{T})
#             for t = n+2:2n
#                 Mc_dft[t,i,j] = uplo == :L ? zero(Complex{T}) : complex(Mc[2n-t+2,i,j])
#             end
#             dft(view(Mc_dft, 1:2n, 1:p, 1:p))
#         end
#     end
#     return BlockTriangularToeplitz(Mc, string(uplo)[1], Mc_dft, tmp, dft, idft)
# end

#= Hankel Matrix
 A Hankel matrix is a matrix that is constant across the anti-diagonals:

  [a_0 a_1 a_2 a_3 a_4
   a_1 a_2 a_3 a_4 a_5
   a_2 a_3 a_4 a_5 a_6]

 This is precisely a Toeplitz matrix with the columns reversed:
                             [0 0 0 0 1
  [a_4 a_3 a_2 a_1 a_0        0 0 0 1 0
   a_5 a_4 a_3 a_2 a_1   *    0 0 1 0 0
   a_6 a_5 a_4 a_3 a_2]       0 1 0 0 0
                              1 0 0 0 0]
 We represent the Hankel matrix by wrapping the corresponding Toeplitz matrix.=#

# Hankel Matrix
type Hankel{T<:Number} <: AbstractMatrix{T}
    T::Toeplitz{T}
end

# Ctor: vc is the leftmost column and vr is the bottom row.
function Hankel(vc,vr)
    if vc[end] != vr[1]
        error("First element of rows must equal last element of columns")
    end
    n = length(vr)
    p = [vc; vr[2:end]]
    Hankel(Toeplitz(p[n:end],p[n:-1:1]))
end

convert(::Type{Array},A::Hankel) = convert(Matrix,A)
convert(::Type{Matrix},A::Hankel) = full(A)

convert{T}(::Type{AbstractMatrix{T}},A::Hankel) = convert(Hankel{T},A)
convert{T}(::Type{Hankel{T}},A::Hankel) = Hankel(convert(Toeplitz{T},A.T))

# Size
size(H::Hankel,k...) = size(H.T,k...)

# Full version of a Hankel matrix
function full{T}(A::Hankel{T})
    m, n = size(A)
    Af = Array(T, m, n)
    for j = 1:n
        for i = 1:m
            Af[i,j] = A[i,j]
        end
    end
    return Af
end

# Retrieve an entry by two indices
getindex(A::Hankel, i::Integer, j::Integer) = A.T[i,end-j+1]

# Retrieve an entry by one index
getindex(H::Hankel, i::Integer) = H[mod(i, size(H,1)), div(i, size(H,1)) + 1]

# Fast application of a general Hankel matrix to a general vector
*(A::Hankel,b::AbstractVector) = A.T * reverse(b)

# Fast application of a general Hankel matrix to a general matrix
*(A::Hankel,B::AbstractMatrix) = A.T * flipdim(B, 1)
## BigFloat support

(*){T<:BigFloat}(A::Toeplitz{T}, b::Vector) = irfft(
    rfft([
        A.vc;
        reverse(A.vr[2:end])]
    ) .* rfft([
        b;
        zeros(length(b) - 1)
    ]),
    2 * length(b) - 1
)[1:length(b)]

end #module