text:
module TestLabeledArray

using FactCheck
using DataCubes

facts("LabeledArray tests") do
  context("constructor tests") do
    col1 = nalift(rand(10, 50))
    col2 = nalift(reshape(1:500, 10, 50))
    col3 = nalift(reshape(map(i->string("sym_",i), 1:500), 10, 50))
    col4 = nalift(hcat(rand(10,30), fill(:testsym, 10, 20)))
    axis1c1 = DictArray(k1=nalift(collect(101:110)), k2=nalift(collect(201:210)))
    axis1c2 = nalift(map(i->string("a_",i), 1:50))
    d = DictArray(c1=col1, c2=col2, c3=col3, c4=col4)
    larr1 = LabeledArray(d, (axis1c1, axis1c2))
    larr2 = LabeledArray(d, axis1=axis1c1, axis2=axis1c2)
    @fact larr1 --> larr2
    @fact convert(LabeledArray, LabeledArray([LDict(:a=>Nullable(3),:b=>Nullable{Int}()), LDict(:a=>Nullable(5),:b=>Nullable(3))],axis1=darr(k=[:x,:y]))) --> @larr(a=[3,5],b=[NA,3],axis[darr(k=[:x,:y])])
    @fact convert(LabeledArray, LabeledArray(nalift([LDict(:a=>Nullable(3),:b=>Nullable{Int}()), LDict(:a=>Nullable(5),:b=>Nullable(3))]),axis1=darr(k=[:x,:y]))) --> @larr(a=[3,5],b=[NA,3],axis[darr(k=[:x,:y])])
    @fact convert(LabeledArray, LabeledArray(map(Nullable,[LDict(:a=>Nullable(3),:b=>Nullable{Int}()), LDict(:a=>Nullable(5),:b=>Nullable(3))]),axis1=darr(k=[:x,:y]))) --> @larr(a=[3,5],b=[NA,3],axis1[darr(k=[:x,:y])])
    @fact convert(LabeledArray, [LDict(:a=>Nullable(3),:b=>Nullable{Int}()), LDict(:a=>Nullable(5),:b=>Nullable(3))]) --> @larr(a=[3,5],b=[NA,3])
    @fact convert(LabeledArray, nalift([LDict(:a=>Nullable(3),:b=>Nullable{Int}()), LDict(:a=>Nullable(5),:b=>Nullable(3))])) --> @larr(a=[3,5],b=[NA,3])
    @fact @larr(a=[1,2,3],b=1,axis1[k=[:A,:B,:C]]) --> larr(a=[1,2,3],b=[1,1,1],axis1=darr(k=[:A,:B,:C]))
    @fact larr(@larr(a=[1,2,3],b=1,axis[k=[:A,:B,:C]]), c=[:x,:y,:z], :d=>[1,2,3], axis1=darr(k=["X", "Y", "Z"])) --> larr(axis1=darr(k=["X","Y","Z"]), a=[1,2,3],b=[1,1,1],d=[1,2,3],c=[:x,:y,:z])
    @fact @larr(@larr(a=[1 NA;3 4;NA NA],:b=>[1.0 1.5;:sym 'a';"X" "Y"],c=1,axis[:U,NA,:W],axis[r=['m','n']]), c=[NA NA;3 4;5 6], :d=>:X, axis1[k=["g","h","i"]]) --> @larr(a=[1 NA;3 4;NA NA],b=[1.0 1.5;:sym 'a';"X" "Y"],c=[NA NA;3 4;5 6],d=reshape(fill(:X,6),3,2),axis2[r=['m','n']],axis1[k=["g","h","i"]])
    @fact_throws larr(a=[1,2,3],axis1=[1 2 3])
    @fact_throws LabeledArray([1 2 3],([1,2,3],[4,5,6]))
    @fact_throws LabeledArray([1,2,3],([1,2,3],[4,5,6]))
  end
  context("array related method tests") do
    arr = LabeledArray(
      DictArray(:a=>nalift(reshape(1:20, 5, 4)),
                :b=>nalift(reshape(2.0*(1:20),5,4)),
                2=>nalift(fill(:sym, 5, 4)),
                :third=>nalift(reshape(map(i->string("str_",i), 1:20), 5, 4))),
          axis1=DictArray(k1=nalift(["a","a","b","b","b"]), k2=nalift(collect(101:105))),
          axis2=DictArray(r1=nalift([:alpha,:beta,:gamma,:delta])))
    @fact arr[3,2] --> LDict(:a=>Nullable(8), :b=>Nullable(16.0), 2=>Nullable(:sym), :third=>Nullable("str_8"))
    @fact dcube.getindexpair(arr,3,2).second.second --> LDict(:a=>Nullable(8), :b=>Nullable(16.0), 2=>Nullable(:sym), :third=>Nullable("str_8"))
    @fact arr[1:3,2] --> LabeledArray(
                            DictArray(:a=>nalift([6,7,8]),
                                      :b=>nalift([12.0,14.0,16.0]),
                                      2=>nalift([:sym,:sym,:sym]),
                                      :third=>nalift(["str_6","str_7","str_8"])),
                            axis1=DictArray(k1=nalift(["a","a","b"]), k2=nalift(collect(101:103))))
    @fact arr[2:2,2:4] --> LabeledArray(
                            DictArray(:a=>nalift([7 12 17]),
                                      :b=>nalift([14.0 24.0 34.0]),
                                      2=>nalift([:sym :sym :sym]),
                                      :third=>nalift(["str_7" "str_12" "str_17"])),
                            axis=DictArray(k1=nalift(["a"]), k2=nalift([102])),
                            axis2=DictArray(r1=nalift([:beta,:gamma,:delta])))
    @fact collapse_axes(peel(larr(a=reshape(1:15,3,5),axis1=[10,11,12])[1,:])) --> collapse_axes(peel(larr(a=[1 4 7 10 13], axis1=[10])))
    @fact larr(a=reshape(1:15,3,5),axis1=[10,11,12])[:,1] --> larr(a=[1,2,3], axis1=[10,11,12])
    @fact collapse_axes(peel(larr(a=reshape(1:15,3,5),axis1=[10,11,12],axis2=[:,:y,:z,:u,:v])[1,:])) --> collapse_axes(peel(larr(a=[1 4 7 10 13], axis1=[10],axis2=[:,:y,:z,:u,:v])))
    @fact larr(a=reshape(1:15,3,5),axis1=[10,11,12],axis2=[:x,:y,:z,:u,:v])[:,1] --> larr(a=[1,2,3], axis1=[10,11,12])
    @fact size(arr) --> (5,4)
    #@fact eltype(arr) --> LDict{Any}
    @fact pick(arr,:a)[1,1].value --> 1
    @fact_throws pick(arr,:nonexistcol)
    @fact pick(larr(a=[1 2 3;4 5 6],axis1=darr(k=[:X,:Y])),:k) --> pick(larr(a=rand(2,3),axis1=darr(k=[:X,:Y])),:k)
    @fact pickaxis(larr(a=[1 2 3;4 5 6],axis=darr(k=[:X,:Y])),1) --> darr(k=[:X,:Y])
    @fact pickaxis(larr(a=[1 2 3;4 5 6],axis=darr(k=[:X,:Y])),1,:k) --> nalift([:X,:Y])
    @fact pick(arr,:b)[1,1].value --> 2.0
    @fact pick(arr,(:a,))[1][1,1].value --> 1
    @fact pick(arr,(:b,))[1][1,1].value --> 2.0
    @fact endof(arr) --> length(arr)
    @fact transpose(arr) --> LabeledArray(DictArray(mapvalues(transpose, arr.data.data)),
                                           axis1=arr.axes[2],
                                           axis2=arr.axes[1])
    @fact permutedims(arr, (2,1)) --> transpose(arr)
    @fact_throws permutedims(arr, (2,1,3))
    @fact LabeledArray(DictArray(a=nalift([1 2 3])), axes2=nalift([100,101,102])) ==
          LabeledArray(DictArray(a=nalift([1 2 3])), axes2=nalift([100,101,102])) --> true
    @fact copy(arr) !== arr --> true
    @fact size(cat(1, arr, arr)) --> ntuple(n->n==1 ? size(arr,1)*2 : size(arr,n), ndims(arr))
    @fact size(cat(2, arr, arr)) --> ntuple(n->n==2 ? size(arr,2)*2 : size(arr,n), ndims(arr))
    @fact cat(2, arr) === arr --> true
    @fact_throws cat(larr([1 2 3;4 5 6]), larr([10 11;12 13;14 15]))
    @fact size(vcat(arr, arr)) --> ntuple(n->n==1 ? size(arr,1)*2 : size(arr,n), ndims(arr))
    @fact size(hcat(arr, arr)) --> ntuple(n->n==2 ? size(arr,2)*2 : size(arr,n), ndims(arr))
    @fact size(repeat(arr, inner=[5,2], outer=[3,4])) --> (size(arr,1)*5*3, size(arr,2)*2*4)
    @fact @larr(a=[1 2 3;4 5 6], 'x'=[:a :b :c;:x :y :z])[:a] --> nalift([1 2 3;4 5 6])
    @fact @larr(a=[1 2 3;4 5 6], b=[:a :b :c;:x :y :z])[:a,:b] --> Any[nalift([1 2 3;4 5 6]), nalift([:a :b :c;:x :y :z])]
    @fact @larr(a=[1 2 3;4 5 6], b=[:a :b :c;:x :y :z])[[:a,:b]] --> @darr(a=[1 2 3;4 5 6], b=[:a :b :c;:x :y :z])
    @fact @larr(a=[1 2 3;4 5 6], b=[:a :b :c;:x :y :z])[(:a,:b)] --> (nalift([1 2 3;4 5 6]), nalift([:a :b :c;:x :y :z]))
    @fact @larr(@larr(a=[1 2 3;4 5 6],axis1[k=[:x,:y]],axis2[r=['a','b','c']]), axis1[[1,NA]],axis2[[3,NA,1]]) --> @larr(a=[1 2 3;4 5 6],axis1[1,NA],axis2[3,NA,1])
    @fact @larr(@larr(a=[1 2 3;4 5 6],axis1[k=[:x,:y]],axis2[r=['a','b','c']]), axis2[[3,NA,1]]) --> @larr(a=[1 2 3;4 5 6],axis1[k=[:x,:y]],axis2[[3,NA,1]])
    @fact @larr([1 2 3;4 5 6]) --> LabeledArray(nalift([1 2 3;4 5 6]))
    @fact convert(LabeledArray, @larr([1 2 3;4 5 6])) --> larr([1 2 3;4 5 6])
  end
  context("additional method tests") do
    d1 = larr(:a=>nalift(reshape(1:20, 5, 4)),
              2=>nalift(fill(:sym, 5, 4)),
              :third=>nalift(reshape(map(i->string("str_",i), 1:20), 5, 4)))
    d2 = larr(:x=>nalift(reshape(1:20, 5, 4)),
              :third=>nalift(fill(:sym, 5, 4)),
              :z=>nalift(reshape(map(i->string("str_",i), 1:20), 5, 4)))
    @fact pick(merge(d1, d2), [:a,2]) --> pick(d1, [:a,2])
    @fact pick(merge(d1, d2), [:x,:third,:z]) --> pick(d2, [:x,:third,:z])
    @fact keys(peel(merge(d1, d2))) --> Any[:a,2,:third,:x,:z]
    @fact peel(delete(merge(d1, d2), :a,2,:third)) --> pick(d2, [:x, :z])
    @fact merge(larr(a=[1 2 3;4 5 6]), b=3.0) --> larr(a=[1 2 3;4 5 6], b=fill(3.0,2,3))
    @fact merge(larr(a=[1 2 3;4 5 6]), darr(x=[:a :b :c;:d :e :f])) --> larr(a=[1 2 3;4 5 6], x=[:a :b :c;:d :e :f])
    @fact merge(darr(a=[1 2 3;4 5 6]), larr(x=[:a :b :c;:d :e :f])) --> larr(a=[1 2 3;4 5 6], x=[:a :b :c;:d :e :f])
    @fact merge(larr(a=[1 2 3;4 5 6]), darr(x=[:a :b :c;:d :e :f]), b=3.0) --> larr(a=[1 2 3;4 5 6], x=[:a :b :c;:d :e :f], b=fill(3.0,2,3))
    @fact merge(darr(a=[1 2 3;4 5 6]), larr(x=[:a :b :c;:d :e :f]), b=3.0) --> larr(a=[1 2 3;4 5 6], x=[:a :b :c;:d :e :f], b=fill(3.0,2,3))
    @fact pick(d1, [:a]) --> DictArray(a=pick(d1, :a))
    @fact mapslices(x->LDict(:c=>Nullable(length(x))),@larr(a=[1 2 3;4 5 6],b=["a" "b" "c";"d" "e" "f"],axis1[k=[:x,:y]],axis2[r=[:m,:n,:p]]),[1]) --> @larr(c=[2,2,2], axis1[r=[:m,:n,:p]])
    @fact mapslices(x->LDict(:c=>Nullable(length(x))),@larr(a=[1 2 3;4 5 6],b=["a" "b" "c";"d" "e" "f"],axis1[k=[:x,:y]],axis2[r=[:m,:n,:p]]),1) --> @larr(c=[2,2,2], axis1[r=[:m,:n,:p]])
    @fact mapslices(x->LDict(:c=>Nullable(length(x))),@larr(a=[1 2 3;4 5 6],b=["a" "b" "c";"d" "e" "f"],axis1[k=[:x,:y]],axis2[r=[:m,:n,:p]]),[2]) --> @larr(c=[3,3], axis1[k=[:x,:y]])
    @fact mapslices(x->LDict(:c=>Nullable(length(x))),@larr(a=[1 2 3;4 5 6],b=["a" "b" "c";"d" "e" "f"],axis1[k=[:x,:y]],axis2[r=[:m,:n,:p]]),2) --> @larr(c=[3,3], axis1[k=[:x,:y]])
    @fact mapslices(x->Nullable(length(x)),@larr(a=[1 2 3;4 5 6],b=["a" "b" "c";"d" "e" "f"]),[1]) --> LabeledArray(@nalift([2,2,2]), axes1=@nalift([1,2,3]))
    @fact size(mapslices(identity, @larr(a=[1 2 3;4 5 6]), [])) --> (2,3)
    @fact mapslices(x->LDict(:c1=>DataCubes.naop_plus(x[:a],x[:b]),:c2=>Nullable(10)), @larr(a=[1 2 3;4 5 6],b=[1.0 2.0 3.0;4.0 5.0 6.0]), []) --> @larr(c1=[2.0 4.0 6.0;8.0 10.0 12.0], c2=@rap reshape(_,(2,3)) fill (10,6)...)
    #@fact typeof(mapslices(identity, @larr(a=[1 2 3;4 5 6]), [])) --> Array{Pair{Nullable{Int64},Pair{Nullable{Int64},DataCubes.LDict{Symbol,Nullable{Int64}}}},2}
    @fact typeof(mapslices(identity, @larr(a=[1 2 3;4 5 6]), [])) --> DataCubes.LabeledArray{DataCubes.LDict{Symbol,Nullable{Int64}},2,Tuple{DataCubes.DefaultAxis,DataCubes.DefaultAxis},DataCubes.DictArray{Symbol,2,DataCubes.AbstractArrayWrapper{Nullable{Int64},2,Array{Nullable{Int64},2}},Nullable{Int64}}}
    @fact eltype(mapslices(identity, @larr(a=[1 2 3;4 5 6]), [])) --> DataCubes.LDict{Symbol,Nullable{Int64}}
    #@fact eltype(mapslices(identity, @larr(a=[1 2 3;4 5 6]), [])) --> Pair{Nullable{Int64},Pair{Nullable{Int64},DataCubes.LDict{Symbol,Nullable{Int64}}}}
    @fact mapslices(x -> x, @larr(a=[1 2 3;4 5 6]), []) --> @larr(a=[1 2 3;4 5 6])
    @fact mapslices(x -> darr(a=[x[:a].value+i for i in 0:3]), @larr(a=[1 2 3;4 5 6]), []) --> reshape(larr(a=[1,2,3,4,4,5,6,7,2,3,4,5,5,6,7,8,3,4,5,6,6,7,8,9]), 4, 2, 3)
    @fact mapslices(x -> larr(a=[x[:a].value+i for i in 0:3]), @larr(a=[1 2 3;4 5 6]), []) --> reshape(larr(a=[1,2,3,4,4,5,6,7,2,3,4,5,5,6,7,8,3,4,5,6,6,7,8,9]), 4, 2, 3)

    @fact mapslices(x->darr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"]), [1]) --> larr(l=reshape(repeat(collect(1:4),outer=[10]),4,5,2),p=reshape(repeat([:U,:V,:W,:O],outer=[10]),4,5,2),axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"])
    @fact mapslices(x->darr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"]), [1]) --> larr(l=reshape(repeat(collect(1:4),outer=[10]),4,5,2),p=reshape(repeat([:U,:V,:W,:O],outer=[10]),4,5,2),axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"])
    @fact mapslices(x->larr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"]), [1]) --> larr(l=reshape(repeat(collect(1:4),outer=[10]),4,5,2),p=reshape(repeat([:U,:V,:W,:O],outer=[10]),4,5,2),axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"])
    @fact mapslices(x->larr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O], axis1=[:m,:n,:p,:q]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis3=["X","Y"]), [1]) --> larr(l=reshape(repeat(collect(1:4),outer=[10]),4,5,2),p=reshape(repeat([:U,:V,:W,:O],outer=[10]),4,5,2),axis1=[:m,:n,:p,:q],axis3=["X","Y"])
    @fact mapslices(x->[11,21,31,41], larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis3=["X","Y"]), [1]) --> larr(reshape(repeat([11,21,31,41],outer=[10]),4,5,2),axis3=["X","Y"])

    @fact mapslices(x->darr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"]), [2]) --> larr(l=reshape(repeat(collect(1:4),outer=[6]),4,3,2),p=reshape(repeat([:U,:V,:W,:O],outer=[6]),4,3,2),axis2=[:a,:b,:c],axis3=["X","Y"])
    @fact mapslices(x->darr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"]), [2]) --> larr(l=reshape(repeat(collect(1:4),outer=[6]),4,3,2),p=reshape(repeat([:U,:V,:W,:O],outer=[6]),4,3,2),axis2=[:a,:b,:c],axis3=["X","Y"])
    @fact mapslices(x->larr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis2=[:m,:n,:p,:o,:q],axis3=["X","Y"]), [2]) --> larr(l=reshape(repeat(collect(1:4),outer=[6]),4,3,2),p=reshape(repeat([:U,:V,:W,:O],outer=[6]),4,3,2),axis2=[:a,:b,:c],axis3=["X","Y"])
    @fact mapslices(x->larr(:l=>[1,2,3,4],:p=>[:U,:V,:W,:O], axis1=[:m,:n,:p,:q]), larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis3=["X","Y"]), [2]) --> larr(l=reshape(repeat(collect(1:4),outer=[6]),4,3,2),p=reshape(repeat([:U,:V,:W,:O],outer=[6]),4,3,2),axis1=[:m,:n,:p,:q],axis2=[:a,:b,:c],axis3=["X","Y"])
    @fact mapslices(x->[11,21,31,41], larr(a=rand(3,5,2),axis1=[:a,:b,:c],axis3=["X","Y"]), [2]) --> larr(reshape(repeat([11,21,31,41],outer=[6]),4,3,2),axis2=[:a,:b,:c],axis3=["X","Y"])

    @fact mapslices(x->x[:a][1,1].value==1 ? LDict(:a=>Nullable(1)) : LDict(:a=>nalift([1 2 3])), larr(a=[1 2 3;4 5 6]),[1]) --> nalift(DataCubes.simplify_array(Any[LDict(:a=>1), LDict(:a=>[1 2 3]), LDict(:a=>[1 2 3])]))
    @fact (i=0;mapslices(x->x[:a][1,1].value<=2 ? (i+=1;LDict(:a=>i)) : LDict(:b=>-1), larr(a=[1 2 3;4 5 6],axis1=[:X,:Y]),[1])) --> nalift(DataCubes.simplify_array(Any[LDict(:a=>1), LDict(:a=>2), LDict(:b=>-1)]))
    @fact (i=0;mapslices(x->x[:a][1,1].value<=2 ? (i+=1;LDict(:a=>i)) : LDict(:a=>-1), larr(a=[1 2 3;4 5 6],axis1=[:X,:Y]),[1])) --> larr(a=[1,2,-1])

    @fact mapslices(x->[1], larr(a=Int[]), [1])  --> isnull
    @fact size(mapslices(x->[1], larr(a=rand(0,5,3)), [2])) --> (0,3)
    @fact mapslices(x->msum(x,1,2), larr(reshape(1:24,2,3,4),axis=[:a,:b],axis=['x','y','z']),2,1) --> larr(reshape([1,3,6,10,15,21,7,15,24,34,45,57,13,27,42,58,75,93,19,39,60,82,105,129],2,3,4),axis=[:a,:b],axis=['x','y','z'])
    @fact mapslices(x->msum(x,2,1), 3.0*larr(reshape(1:24,2,3,4),axis=[:a,:b],axis=['x','y','z']),[2,1]) --> 3.0*larr(reshape([1,3,6,10,15,21,7,15,24,34,45,57,13,27,42,58,75,93,19,39,60,82,105,129],2,3,4),axis=[:a,:b],axis=['x','y','z'])

    @fact map(x->LDict(:c=>x[:a]), @larr(a=[1,2,3],b=[4,5,6],axis[[:x,:y,:z]])) --> @larr(c=[1,2,3],axis1[[:x,:y,:z]])
    @fact map(x->x[:a], @larr(a=[1,2,3],b=[4,5,6],axis[[:x,:y,:z]])) --> @larr([1,2,3],axis1[[:x,:y,:z]])
    @fact dcube.create_dict(@larr(a=[1 NA NA;4 5 6],b=[NA NA 6; 7 8 9],axis2[r=[:x,:y,:z]]))[Nullable(2)][LDict(:r=>Nullable(:z))][:a].value --> 6
    @fact Set(keys(dcube.create_dict(@larr(a=[1 NA NA;4 5 6],b=[NA NA 6; 7 8 9],axis2[r=[:x,:y,:z]]))[Nullable(1)])) --> Set([LDict(:r=>Nullable(:x)), LDict(:r=>Nullable(:z))])
    @fact dcube.create_dict(@larr(a=[1 NA NA;4 5 6],b=[NA NA 6; 7 8 9],axis2[r=[:x,:y,:z]]))[Nullable(1)][LDict(:r=>Nullable(:x))] --> LDict(:a=>Nullable(1), :b=>Nullable{Int}())
    @fact reverse(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]])) --> @larr(a=[4 5 6;1 2 3],axis2[r=[:x,:y,:z]])
    @fact_throws reverse(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]), 1)
    @fact reverse(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[1]) --> @larr(a=[4 5 6;1 2 3],axis2[r=[:x,:y,:z]])
    @fact reverse(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2]) --> @larr(a=[3 2 1;6 5 4],axis2[r=[:z,:y,:x]])
    @fact reverse(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),1:2) --> @larr(a=[6 5 4;3 2 1],axis2[r=[:z,:y,:x]])
    @fact flipdim(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),2) --> @larr(a=[3 2 1;6 5 4],axis2[r=[:z,:y,:x]])
    @fact flipdim(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),1,2) --> @larr(a=[6 5 4;3 2 1],axis2[r=[:z,:y,:x]])
    @fact reshape(larr(a=[1 2 3;4 5 6],b=[10 11 12;13 14 15],axis1=darr(k1=[:a,:b],k2=[100,101]),axis2=[:m,:n,:p]),6) --> larr(a=[1,4,2,5,3,6],b=[10,13,11,14,12,15], axis1=darr(k1=repmat([:a,:b],3),k2=repmat([100,101],3),x1=[:m,:m,:n,:n,:p,:p]))
    @fact_throws reshape(larr(a=[1 2 3;4 5 6],b=[10 11 12;13 14 15],axis1=darr(k1=[:a,:b],k2=[100,101]),axis2=[:m,:n,:p]),2,1,5)
    @fact reshape(larr(a=[1 2 3;4 5 6],b=[10 11 12;13 14 15],axis1=darr(k1=[:a,:b],k2=[100,101]),axis2=[:m,:n,:p]),(6,)) --> larr(a=[1,4,2,5,3,6],b=[10,13,11,14,12,15], axis1=darr(k1=repmat([:a,:b],3),k2=repmat([100,101],3),x1=[:m,:m,:n,:n,:p,:p]))
    @fact reshape(larr(a=[1 2 3;4 5 6],b=[10 11 12;13 14 15],axis1=darr(k1=[:a,:b],k2=[100,101]),axis2=[:m,:n,:p]),1,6) --> @rap transpose reshape(_,6,1) larr(a=[1,4,2,5,3,6],b=[10,13,11,14,12,15], axis1=darr(k1=repmat([:a,:b],3),k2=repmat([100,101],3),x1=[:m,:m,:n,:n,:p,:p]))
    @fact_throws reshape(larr(a=[1 2 3;4 5 6],b=[10 11 12;13 14 15],axis1=darr(k1=[:a,:b],k2=[100,101]),axis2=[:m,:n,:p]),1,3)
    @fact reshape(larr([1 2 3;4 5 6],axis1=[10,11]),1,6) --> larr([1 4 2 5 3 6], axis2=repmat([10,11],3))
    @fact reducedim((x,y)->x+y[:a].value, larr(a=reshape(1:24,2,3,4)),[1],0) --> larr([3 15 27 39;7 19 31 43;11 23 35 47])
    @fact reducedim((x,y)->x+y[:a].value, larr(a=reshape(1:24,2,3,4)),[1,2],0) --> larr([21,57,93,129])
    @fact reducedim((x,y)->x+y[:a].value, larr(a=reshape(1:24,2,3,4)),[1,2,3],0).value --> 300
    @fact reducedim((x,y)->x+y[:a].value, larr(a=reshape(1:24,2,3,4),axis1=darr(k1=[:a,:b]),axis3=[:x,:y,:z,:w]),[1],0) --> larr([3 15 27 39;7 19 31 43;11 23 35 47],axis2=[:x,:y,:z,:w])
    @fact reducedim((x,y)->x+y[:a].value, larr(a=reshape(1:24,2,3,4),axis1=darr(k1=[:a,:b]),axis3=[:x,:y,:z,:w]),[1,2],0) --> larr([21,57,93,129],axis1=[:x,:y,:z,:w])
    @fact reducedim((x,y)->x+y[:a].value, larr(a=reshape(1:24,2,3,4),axis1=darr(k1=[:a,:b]),axis3=[:x,:y,:z,:w]),[1,2,3],0).value --> 300
    # runnability test when the result is empty.
    @fact size(@rap transpose @select((@rap transpose larr(r=rand(8,20), axis1=darr(a=rand(8),b=101:108))), :r=_r.*100, where[_r.>1])) --> (0,0)
    @fact reorder(larr('x'=>1:10, 3=>11:20, axis1=101:110), 3) --> larr(3=>11:20, 'x'=>1:10, axis1=101:110)
    @fact reorder(larr(c1=1:10,c2=11:20),:c2,:c1) --> reorder(larr(c1=1:10,c2=11:20),:c2)
    @fact reorder(larr(c1=1:10,c2=11:20),:c2,:c1) --> larr(c2=11:20,c1=1:10)
    @fact rename(larr('x'=>1:10, 3=>11:20, axis1=101:110), 1) --> larr(1=>1:10, 3=>11:20, axis1=101:110)
    @fact rename(larr(c1=1:10,c2=11:20),:a,:b) --> larr(a=1:10,b=11:20)
    @fact cat(1, larr(a=[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis1=[:u,:v]), larr(b=['x' 'y' 'z'], d=[:m :n :p], axis1=[3])) --> larr(reshape(@larr(a=[1,4,NA,2,5,NA,3,6,NA],b=['a','d','x','b','e','y','c','f','z'],d=[NA,NA,:m,NA,NA,:n,NA,NA,:p]), 3, 3), axis1=[:u,:v,3])
    @fact cat(1, larr(a=1.0*[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis1=[:u,:v]), larr(b=['x' 'y' 'z'], d=[:m :n :p], axis1=[3])) --> larr(reshape(@larr(a=[1.0,4.0,NA,2.0,5.0,NA,3.0,6.0,NA],b=['a','d','x','b','e','y','c','f','z'],d=[NA,NA,:m,NA,NA,:n,NA,NA,:p]), 3, 3), axis1=[:u,:v,3])
    @fact cat(2, larr(a=[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis1=[:u,:v]), larr(b=['x','y'], d=[:m,:n], axis1=[:u,:v])) --> larr(reshape(@larr(a=[1,4,2,5,3,6,NA,NA], b=['a','d','b','e','c','f','x','y'], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis1=[:u,:v])
    @fact cat(2, larr(a=1.0*[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis1=[:u,:v]), larr(b=['x','y'], d=[:m,:n], axis1=[:u,:v])) --> larr(reshape(@larr(a=[1.0,4.0,2.0,5.0,3.0,6.0,NA,NA], b=['a','d','b','e','c','f','x','y'], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis1=[:u,:v])
    @fact cat(2, larr(a=[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f']), larr(b=[10,11], d=[:m,:n])) --> reshape(@larr(a=[1,4,2,5,3,6,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4)
    @fact cat(2, larr(a=1.0*[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f']), larr(b=[10,11], d=[:m,:n])) --> reshape(@larr(a=[1.0,4.0,2.0,5.0,3.0,6.0,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4)
    @fact cat(2, larr(a=[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis2=darr(r=[:x,:y,:z])), larr(b=[10,11], d=[:m,:n])) --> larr(reshape(@darr(a=[1,4,2,5,3,6,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis2=@darr(r=[:x,:y,:z,NA]))
    @fact cat(2, larr(a=1.0*[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis2=darr(r=[:x,:y,:z])), larr(b=[10,11], d=[:m,:n])) --> larr(reshape(@darr(a=[1.0,4.0,2.0,5.0,3.0,6.0,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis2=@darr(r=[:x,:y,:z,NA]))
    # this will cause a NullException().
    #@fact cat(2, larr(a=[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'],axis1=[100,200]), larr(b=[10 11]', d=[:m :n]',axis1=[100,200],axis2=darr(r=[:k]))) --> larr(reshape(@darr(a=[1,4,2,5,3,6,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis1=[100,200],axis2=[1,2,3,LDict(:r=>Nullable(:k))])
    @fact cat(2, larr(a=[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'],axis1=[100,200]), larr(b=[10 11]', d=[:m :n]',axis1=[100,200],axis2=[:k])) --> larr(reshape(@darr(a=[1,4,2,5,3,6,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis1=[100,200],axis2=[1,2,3,:k])
    @fact cat(2, larr(a=1.0*[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'],axis1=[100,200]), larr(b=[10 11]', d=[:m :n]',axis1=[100,200],axis2=[:k])) --> larr(reshape(@darr(a=[1.0,4.0,2.0,5.0,3.0,6.0,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis1=[100,200],axis2=[1,2,3,:k])
    @fact cat(2, larr(a=[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis2=[:x,:y,:z]), larr(b=[10,11], d=[:m,:n])) --> larr(reshape(@darr(a=[1,4,2,5,3,6,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis2=[Nullable(:x),Nullable(:y),Nullable(:z),Nullable{Symbol}()])
    @fact cat(2, larr(a=1.0*[1 2 3;4 5 6], b=['a' 'b' 'c';'d' 'e' 'f'], axis2=[:x,:y,:z]), larr(b=[10,11], d=[:m,:n])) --> larr(reshape(@darr(a=[1.0,4.0,2.0,5.0,3.0,6.0,NA,NA], b=['a','d','b','e','c','f',10,11], d=[NA,NA,NA,NA,NA,NA,:m,:n]), 2, 4), axis2=[Nullable(:x),Nullable(:y),Nullable(:z),Nullable{Symbol}()])
    @fact merge(larr(a=[1,2,3],b=[:x,:y,:z],axis1=[:a,:b,:c]),darr(c=[4,5,6],b=[:m,:n,:p]),darr(a=["X","Y","Z"])) --> larr(a=["X","Y","Z"],b=[:m,:n,:p],c=[4,5,6],axis1=[:a,:b,:c])
    @fact @larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]])[2:-1:1,2] --> larr(a=[5,2])
    @fact @larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]])[[2,1],2] --> larr(a=[5,2])
    @fact @larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]])[[2,1],2:3] --> larr(a=[5 6;2 3],axis2=darr(r=[:y,:z]))
    @fact sub(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2) --> getindex(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2)
    @fact sub(@larr(a=1.0*[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2) --> getindex(@larr(a=1.0*[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2)
    @fact sub(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2:3) --> getindex(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2:3)
    @fact sub(@larr(a=1.0*[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2:3) --> getindex(@larr(a=1.0*[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2:3)
    @fact slice(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2) --> larr(a=[5,2])
    @fact slice(@larr(a=1.0*[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),[2,1],2) --> larr(a=1.0*[5,2])
    @fact slice(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),1, 2:3) --> larr(a=[2,3], axis1=darr(r=[:y,:z]))
    @fact slice(@larr(a=1.0*[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),1, 2:3) --> larr(a=1.0*[2,3], axis1=darr(r=[:y,:z]))
    @fact sub(@larr(a=[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),1, 2:3) --> larr(a=[2 3], axis2=darr(r=[:y,:z]))
    @fact sub(@larr(a=1.0*[1 2 3;4 5 6],axis2[r=[:x,:y,:z]]),1, 2:3) --> larr(a=1.0*[2 3], axis2=darr(r=[:y,:z]))
    @fact_throws larr(a=[1,2,3], axis1=[4,5,6], axis2=[:a,:b,:c])
    @fact_throws larr(a=[1,2,3], axis1=[4,5])
    @fact_throws larr(axis1=[4,5])
    @fact_throws LabeledArray(a=[1,2,3], ([4,5,6],[7,8,9]))
    @fact_throws larr(a=rand(4,5), axis1=rand(4,2))
    @fact size(similar(larr(rand(3,5)))) --> (3,5)
    @fact typeof(similar(larr(rand(3,5)))) --> LabeledArray{Nullable{Float64},2,Tuple{DataCubes.DefaultAxis,DataCubes.DefaultAxis},DataCubes.AbstractArrayWrapper{Nullable{Float64},2,DataCubes.FloatNAArray{Float64,2,Array{Float64,2}}}}

    @fact size(similar(larr(rand(3,5),axis2=[:a,:b,:c,:d,:e]))) --> (3,5)
    @fact typeof(similar(larr(rand(3,5),axis2=[:a,:b,:c,:d,:e]))) --> LabeledArray{Nullable{Float64},2,Tuple{DataCubes.DefaultAxis,DataCubes.AbstractArrayWrapper{Nullable{Symbol},1,Array{Nullable{Symbol},1}}},DataCubes.AbstractArrayWrapper{Nullable{Float64},2,DataCubes.FloatNAArray{Float64,2,Array{Float64,2}}}}
    @fact size(similar(larr(k=rand(3,5),axis=[:a,:b,:c]))) --> (3,5)
    @fact typeof(similar(larr(k=rand(3,5),axis=[:a,:b,:c]))) --> LabeledArray{DataCubes.LDict{Symbol,Nullable{Float64}},2,Tuple{DataCubes.AbstractArrayWrapper{Nullable{Symbol},1,Array{Nullable{Symbol},1}},DataCubes.DefaultAxis},DataCubes.DictArray{Symbol,2,DataCubes.AbstractArrayWrapper{Nullable{Float64},2,DataCubes.FloatNAArray{Float64,2,Array{Float64,2}}},Nullable{Float64}}}
    #@fact typeof(similar(larr(a=rand(3,5), axis1=darr(k=[1,2,3])))) --> LabeledArray{DataCubes.LDict{Symbol,Nullable{Float64}},2,Tuple{DataCubes.DictArray{Symbol,1,DataCubes.AbstractArrayWrapper{Nullable{Int64},1,Array{Nullable{Int64},1}},Nullable{Int64}},Array{Nullable{Int64},1}},DataCubes.DictArray{Symbol,2,DataCubes.AbstractArrayWrapper{Nullable{Float64},2,DataCubes.FloatNAArray{Float64,2,Array{Float64,2}}},Nullable{Float64}}}
    #@fact typeof(similar(larr(rand(3,5), axis1=darr(k=[1,2,3])))) --> LabeledArray{Nullable{Float64},2,Tuple{DataCubes.DictArray{Symbol,1,DataCubes.AbstractArrayWrapper{Nullable{Int64},1,Array{Nullable{Int64},1}},Nullable{Int64}},Array{Nullable{Int64},1}},DataCubes.AbstractArrayWrapper{Nullable{Float64},2,DataCubes.FloatNAArray{Float64,2,Array{Float64,2}}}}
    #@fact typeof(similar(larr(rand(3,5), axis1=[1,2,3]))) --> LabeledArray{Nullable{Float64},2,Tuple{DataCubes.AbstractArrayWrapper{Nullable{Int64},1,Array{Nullable{Int64},1}},Array{Nullable{Int64},1}},DataCubes.AbstractArrayWrapper{Nullable{Float64},2,DataCubes.FloatNAArray{Float64,2,Array{Float64,2}}}}
    @fact sortperm(larr(a=[1,7,4,3,5,3],axis1=enumeration([1,12,13,14,15,16])),1,:a) --> ([1,4,6,3,5,2],)
    @fact sortperm(larr(a=enumeration([1,7,4,3,5,3]),axis1=enumeration([1,12,13,14,15,16])),1,:a) --> ([1,2,3,4,6,5],)

    context("show tests") do
      @fact show(larr(slice([1,2],1))) --> nothing
      @fact show(larr(a=[])) --> nothing
      @fact show(larr(a=rand(2))) --> nothing
      @fact show(larr(a=rand(2), axis=[:X,:Y])) --> nothing
      @fact show(larr(a=rand(2,3), axis1=[:a,:b], axis2=["X","Y","Z"])) --> nothing
      @fact show(larr(a=rand(2,3), axis2=["X","Y","Z"])) --> nothing
      @fact show(larr(a=rand(2,3), axis2=darr(k=["X","Y","Z"]))) --> nothing
      @fact show(larr(a=rand(2,3), axis=["X","Y"])) --> nothing
      @fact show(larr(a=rand(2,3))) --> nothing
      @fact show(larr(a=rand(2,3,2), axis3=[:x,:y])) --> nothing
      @fact show(larr(a=rand(2,3,2,2))) --> nothing
      @fact show(larr(a=rand(2,3,2,2))) --> nothing
      @fact (dcube.set_showsize!(5,5);show(larr(a=rand(10,10)))) --> nothing
      @fact (dcube.set_showheight!(3);show(larr(a=rand(10,10)))) --> nothing
      @fact (dcube.set_showwidth!(3);show(larr(a=rand(10,10)))) --> nothing
      @fact (dcube.set_default_showsize!();nothing) --> nothing
      @fact (dcube.set_showalongrow!(false);show(larr(a=rand(3),b=rand(3),c=fill(:X,3)))) --> nothing
      @fact (dcube.set_showalongrow!(false);show(larr(a=rand(3),b=rand(3),c=fill(:X,3), axis=['X','Y','Z']))) --> nothing
      @fact (dcube.set_showalongrow!(false);show(larr(a=rand(3),b=rand(3),c=fill(:X,3), axis=darr(k=['X','Y','Z'])))) --> nothing
      @fact (dcube.set_showalongrow!(true);show(larr(a=rand(3),b=rand(3),c=fill(:X,3)))) --> nothing
      @fact (dcube.set_showalongrow!(false);show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing
      @fact (dcube.set_showalongrow!(false);show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5),axis1=darr(k=['X','Y','Z'])))) --> nothing
      @fact (dcube.set_showalongrow!(true);show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5),axis1=darr(k=['X','Y','Z'])))) --> nothing
      @fact (dcube.set_showalongrow!(false);show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5),axis2=darr(k=['X','Y','Z','U','V'])))) --> nothing
      @fact (dcube.set_showalongrow!(true);show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5),axis2=darr(k=['X','Y','Z','U','V'])))) --> nothing
      @fact (dcube.set_showalongrow!(true);show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing
      @fact (dcube.set_format_string!(Float64, "%0.2f");show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing
      @fact (dcube.set_format_string!(Float64, "%0.8g");show(larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing

      @fact (dcube.set_dispsize!(5,5);writemime(STDOUT,MIME("text/html"),larr(a=rand(10,10)))) --> nothing
      @fact (dcube.set_dispheight!(3);writemime(STDOUT,MIME("text/html"),larr(a=rand(10,10)))) --> nothing
      @fact (dcube.set_dispwidth!(3);writemime(STDOUT,MIME("text/html"),larr(a=rand(10,10)))) --> nothing
      @fact (dcube.set_dispwidth!(3);writemime(STDOUT,MIME("text/html"),larr(a=rand(2,3,4)))) --> nothing
      @fact (dcube.set_dispwidth!(3);writemime(STDOUT,MIME("text/html"),larr(a=slice([1,2],1)))) --> nothing
      @fact (dcube.set_default_dispsize!();nothing) --> nothing
      @fact (dcube.set_dispalongrow!(false);writemime(STDOUT,MIME("text/html"),larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing
      @fact (dcube.set_dispalongrow!(true);writemime(STDOUT,MIME("text/html"),larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing
      @fact (dcube.set_format_string!(Float64, "%0.2f");writemime(STDOUT,MIME("text/html"),larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing
      @fact (dcube.set_format_string!(Float64, "%0.8g");writemime(STDOUT,MIME("text/html"),larr(a=rand(3,5),b=rand(3,5),c=fill(:X,3,5)))) --> nothing
    end
  end
end

end


text:
using Test, YaoBlocks, YaoArrayRegister, LuxurySparse, CacheServers

@testset "constructor" begin
    @test CacheFragment(X) isa CacheFragment{XGate,UInt8,Any}
    @test CacheFragment{XGate,Int,PermMatrix{ComplexF64,Int}}(X) isa
          CacheFragment{XGate,Int,PermMatrix{ComplexF64,Int}}
    @test CacheFragment{XGate,Int}(X) isa CacheFragment{XGate,Int,Any}
end

@testset "CacheServer API" begin
    frag = CacheFragment(X)
    @test update!(frag, mat(X)) === frag
    @test pull(frag) == mat(X)
    clear!(frag)
    @test_throws KeyError pull(frag)
end

test_server = DefaultServer{AbstractBlock,CacheFragment}()

@testset "constructor" begin
    c = CachedBlock(test_server, X, 2)
    @test c isa CachedBlock{DefaultServer{AbstractBlock,CacheFragment},XGate}

    blk = kron(4, 2 => Rx(0.3))
    @test first(chsubblocks(c, blk) |> subblocks) == blk
end

@testset "methods" begin
    g = CachedBlock(test_server, X, 3)
    @test_throws KeyError pull(g)

    update_cache(ComplexF64, g)
    @test pull(g) ≈ mat(X)

    clear!(g)
    @test_throws KeyError pull(g)

    @test mat(g) ≈ mat(X)

    clear!(g)

    @test state(apply!(arrayreg(bit"1"), g)) ≈ state(arrayreg(bit"0"))
    @test pull(g) ≈ mat(X)

    clear!(g)
    @test state(YaoBlocks._apply!(arrayreg(bit"1"), g, 2)) ≈ state(arrayreg(bit"0"))
    @test_throws KeyError pull(g)
end

@testset "direct inherited methods" begin
    g = chain([X, Z, Y, I2])
    g = CachedBlock(test_server, g, 2)

    @test g[1] isa XGate
    @test g[3] isa YGate

    g[4] = Z
    @test g[4] isa ZGate

    g = chain(X, Y)
    g = CachedBlock(test_server, g, 2)

    @test g[1] isa XGate
    @test g[2] isa YGate

    #@test iterate(g) == iterate(g.content)
    #@test length(g) == length(g.content)

    @test subblocks(g) == (g.content,)

    gg = chain(g, g)
    cgg = CachedBlock(test_server, gg, 2)
    @test cgg isa CachedBlock
    @test content(cgg)[1] == g
end

@testset "matrix-chain cache" begin
    A = matblock(rand(ComplexF64, 2, 2))
    B = matblock(rand(ComplexF64, 2, 2))
    C = cache(chain(A, B))

    update_cache(ComplexF64, C)
    @test pull(C) ≈ mat(C)
end

ext: jl


text:
#####
##### Optimize
#####

function run_passes(ci::CodeInfo, nargs::Int, sv::OptimizationState)
    preserve_coverage = coverage_enabled(sv.mod)
    ir = convert_to_ircode(ci, copy_exprargs(ci.code), preserve_coverage, nargs, sv)
    ir = slot2reg(ir, ci, nargs, sv)
    ir = compact!(ir)
    ir = ssa_inlining_pass!(ir, ir.linetable, sv.inlining, ci.propagate_inbounds)
    ir = compact!(ir)
    ir = getfield_elim_pass!(ir) # SROA
    ir = adce_pass!(ir)
    ir = type_lift_pass!(ir)
    ir = compact!(ir)
    verify_ir(ir)
    verify_linetable(ir.linetable)
    return ir
end

function optimize(interp::MixtapeInterpreter, opt::OptimizationState,
                  params::OptimizationParams, @nospecialize(result))
    nargs = Int(opt.nargs) - 1
    mi = opt.linfo
    meth = mi.def
    ir = run_passes(opt.src, nargs, opt)
    try
        fn = resolve(GlobalRef(mi.def.module, mi.def.name))
        as = map(resolve, mi.specTypes.parameters[2:end])
        if allow(interp.ctx, mi.def.module, fn, as...) && show_after_inference(interp.ctx)
            print("@ ($(meth.file), L$(meth.line))\n")
            print("| (inf) $(mi.def.module).$fn\n")
            display(opt.src)
        end
        if debug(interp.ctx)
            println("@ ($(meth.file), L$(meth.line))")
            println("| end (inf): $(meth.module).$(fn)")
            println("@ ($(meth.file), L$(meth.line))")
            println("| beg (opt): $(meth.module).$(fn)")
        end
        if allow(interp.ctx, mi.def.module, fn, as...)
            ir = optimize!(interp.ctx, ir)
            verify_ir(ir)
        end
        ret = Core.Compiler.finish(opt, params, ir, result)
        if allow(interp.ctx, mi.def.module, fn, as...) &&
           show_after_optimization(interp.ctx)
            print("@ ($(meth.file), L$(meth.line))\n")
            print("| (opt) $(opt.linfo.def.module).$fn\n")
            display(opt.src)
        end
        if debug(interp.ctx)
            println("@ ($(meth.file), L$(meth.line))")
            println("| end (opt): $(meth.module).$(fn)")
        end
        return ret
    catch e
        push!(interp, e)
    end
    return Core.Compiler.finish(opt, params, ir, result)
end


text:
# This file is a part of Julia. License is MIT: http://julialang.org/license

abstract AbstractCartesianIndex{N} # This is a hacky forward declaration for CartesianIndex
typealias ViewIndex Union{Real, AbstractArray}
typealias ScalarIndex Real

# L is true if the view itself supports fast linear indexing
immutable SubArray{T,N,P,I,L} <: AbstractArray{T,N}
    parent::P
    indexes::I
    offset1::Int       # for linear indexing and pointer, only valid when L==true
    stride1::Int       # used only for linear indexing
    function SubArray(parent, indexes, offset1, stride1)
        @_inline_meta
        check_parent_index_match(parent, indexes)
        new(parent, indexes, offset1, stride1)
    end
end
# Compute the linear indexability of the indices, and combine it with the linear indexing of the parent
function SubArray(parent::AbstractArray, indexes::Tuple)
    @_inline_meta
    SubArray(linearindexing(viewindexing(indexes), linearindexing(parent)), parent, ensure_indexable(indexes), index_dimsum(indexes...))
end
function SubArray{P, I, N}(::LinearSlow, parent::P, indexes::I, ::NTuple{N,Any})
    @_inline_meta
    SubArray{eltype(P), N, P, I, false}(parent, indexes, 0, 0)
end
function SubArray{P, I, N}(::LinearFast, parent::P, indexes::I, ::NTuple{N,Any})
    @_inline_meta
    # Compute the stride and offset
    stride1 = compute_stride1(parent, indexes)
    SubArray{eltype(P), N, P, I, true}(parent, indexes, compute_offset1(parent, stride1, indexes), stride1)
end

check_parent_index_match(parent, indexes) = check_parent_index_match(parent, index_ndims(indexes...))
check_parent_index_match{T,N}(parent::AbstractArray{T,N}, ::NTuple{N, Bool}) = nothing
check_parent_index_match{N}(parent, ::NTuple{N, Bool}) =
    throw(ArgumentError("number of indices ($N) must match the parent dimensionality ($(ndims(parent)))"))

# This computes the linear indexing compatability for a given tuple of indices
viewindexing() = LinearFast()
# Leading scalar indexes simply increase the stride
viewindexing(I::Tuple{ScalarIndex, Vararg{Any}}) = (@_inline_meta; viewindexing(tail(I)))
# Slices may begin a section which may be followed by any number of Slices
viewindexing(I::Tuple{Slice, Slice, Vararg{Any}}) = (@_inline_meta; viewindexing(tail(I)))
# A UnitRange can follow Slices, but only if all other indices are scalar
viewindexing(I::Tuple{Slice, UnitRange, Vararg{ScalarIndex}}) = LinearFast()
# In general, ranges are only fast if all other indices are scalar
viewindexing(I::Tuple{Union{Range, Slice}, Vararg{ScalarIndex}}) = LinearFast()
# All other index combinations are slow
viewindexing(I::Tuple{Vararg{Any}}) = LinearSlow()
# Of course, all other array types are slow
viewindexing(I::Tuple{AbstractArray, Vararg{Any}}) = LinearSlow()

# Simple utilities
size(V::SubArray) = (@_inline_meta; map(n->Int(unsafe_length(n)), indices(V)))

similar(V::SubArray, T::Type, dims::Dims) = similar(V.parent, T, dims)

parent(V::SubArray) = V.parent
parentindexes(V::SubArray) = V.indexes

parent(a::AbstractArray) = a
"""
    parentindexes(A)

From an array view `A`, returns the corresponding indexes in the parent.
"""
parentindexes(a::AbstractArray) = ntuple(i->OneTo(size(a,i)), ndims(a))

## SubArray creation
# We always assume that the dimensionality of the parent matches the number of
# indices that end up getting passed to it, so we store the parent as a
# ReshapedArray view if necessary. The trouble is that arrays of `CartesianIndex`
# can make the number of effective indices not equal to length(I).
_maybe_reshape_parent(A::AbstractArray, ::NTuple{1, Bool}) = reshape(A, Val{1})
_maybe_reshape_parent{_}(A::AbstractArray{_,1}, ::NTuple{1, Bool}) = reshape(A, Val{1})
_maybe_reshape_parent{_,N}(A::AbstractArray{_,N}, ::NTuple{N, Bool}) = A
_maybe_reshape_parent{N}(A::AbstractArray, ::NTuple{N, Bool}) = reshape(A, Val{N}) # TODO: DEPRECATE FOR #14770
"""
    view(A, inds...)

Like [`getindex`](@ref), but returns a view into the parent array `A` with the
given indices instead of making a copy.  Calling [`getindex`](@ref) or
[`setindex!`](@ref) on the returned `SubArray` computes the
indices to the parent array on the fly without checking bounds.
"""
function view(A::AbstractArray, I...)
    @_inline_meta
    J = to_indices(A, I)
    @boundscheck checkbounds(A, J...)
    unsafe_view(_maybe_reshape_parent(A, index_ndims(J...)), J...)
end

function unsafe_view(A::AbstractArray, I::ViewIndex...)
    @_inline_meta
    SubArray(A, I)
end
# When we take the view of a view, it's often possible to "reindex" the parent
# view's indices such that we can "pop" the parent view and keep just one layer
# of indirection. But we can't always do this because arrays of `CartesianIndex`
# might span multiple parent indices, making the reindex calculation very hard.
# So we use _maybe_reindex to figure out if there are any arrays of
# `CartesianIndex`, and if so, we punt and keep two layers of indirection.
unsafe_view(V::SubArray, I::ViewIndex...) = (@_inline_meta; _maybe_reindex(V, I))
_maybe_reindex(V, I) = (@_inline_meta; _maybe_reindex(V, I, I))
_maybe_reindex{C<:AbstractCartesianIndex}(V, I, ::Tuple{AbstractArray{C}, Vararg{Any}}) =
    (@_inline_meta; SubArray(V, I))
# But allow arrays of CartesianIndex{1}; they behave just like arrays of Ints
_maybe_reindex{C<:AbstractCartesianIndex{1}}(V, I, A::Tuple{AbstractArray{C}, Vararg{Any}}) =
    (@_inline_meta; _maybe_reindex(V, I, tail(A)))
_maybe_reindex(V, I, A::Tuple{Any, Vararg{Any}}) = (@_inline_meta; _maybe_reindex(V, I, tail(A)))
function _maybe_reindex(V, I, ::Tuple{})
    @_inline_meta
    @inbounds idxs = to_indices(V.parent, reindex(V, V.indexes, I))
    SubArray(V.parent, idxs)
end

## Re-indexing is the heart of a view, transforming A[i, j][x, y] to A[i[x], j[y]]
#
# Recursively look through the heads of the parent- and sub-indexes, considering
# the following cases:
# * Parent index is array  -> re-index that with one or more sub-indexes (one per dimension)
# * Parent index is Colon  -> just use the sub-index as provided
# * Parent index is scalar -> that dimension was dropped, so skip the sub-index and use the index as is

typealias AbstractZeroDimArray{T} AbstractArray{T, 0}

reindex(V, ::Tuple{}, ::Tuple{}) = ()

# Skip dropped scalars, so simply peel them off the parent indices and continue
reindex(V, idxs::Tuple{ScalarIndex, Vararg{Any}}, subidxs::Tuple{Vararg{Any}}) =
    (@_propagate_inbounds_meta; (idxs[1], reindex(V, tail(idxs), subidxs)...))

# Slices simply pass their subindexes straight through
reindex(V, idxs::Tuple{Slice, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) =
    (@_propagate_inbounds_meta; (subidxs[1], reindex(V, tail(idxs), tail(subidxs))...))

# Re-index into parent vectors with one subindex
reindex(V, idxs::Tuple{AbstractVector, Vararg{Any}}, subidxs::Tuple{Any, Vararg{Any}}) =
    (@_propagate_inbounds_meta; (idxs[1][subidxs[1]], reindex(V, tail(idxs), tail(subidxs))...))

# Parent matrices are re-indexed with two sub-indices
reindex(V, idxs::Tuple{AbstractMatrix, Vararg{Any}}, subidxs::Tuple{Any, Any, Vararg{Any}}) =
    (@_propagate_inbounds_meta; (idxs[1][subidxs[1], subidxs[2]], reindex(V, tail(idxs), tail(tail(subidxs)))...))

# In general, we index N-dimensional parent arrays with N indices
@generated function reindex{T,N}(V, idxs::Tuple{AbstractArray{T,N}, Vararg{Any}}, subidxs::Tuple{Vararg{Any}})
    if length(subidxs.parameters) >= N
        subs = [:(subidxs[$d]) for d in 1:N]
        tail = [:(subidxs[$d]) for d in N+1:length(subidxs.parameters)]
        :(@_propagate_inbounds_meta; (idxs[1][$(subs...)], reindex(V, tail(idxs), ($(tail...),))...))
    else
        :(throw(ArgumentError("cannot re-index $(ndims(V)) dimensional SubArray with fewer than $(ndims(V)) indices\nThis should not occur; please submit a bug report.")))
    end
end

# In general, we simply re-index the parent indices by the provided ones
typealias SlowSubArray{T,N,P,I} SubArray{T,N,P,I,false}
function getindex{T,N}(V::SlowSubArray{T,N}, I::Vararg{Int,N})
    @_inline_meta
    @boundscheck checkbounds(V, I...)
    @inbounds r = V.parent[reindex(V, V.indexes, I)...]
    r
end

typealias FastSubArray{T,N,P,I} SubArray{T,N,P,I,true}
function getindex(V::FastSubArray, i::Int)
    @_inline_meta
    @boundscheck checkbounds(V, i)
    @inbounds r = V.parent[V.offset1 + V.stride1*i]
    r
end
# We can avoid a multiplication if the first parent index is a Colon or UnitRange
typealias FastContiguousSubArray{T,N,P,I<:Tuple{Union{Slice, UnitRange}, Vararg{Any}}} SubArray{T,N,P,I,true}
function getindex(V::FastContiguousSubArray, i::Int)
    @_inline_meta
    @boundscheck checkbounds(V, i)
    @inbounds r = V.parent[V.offset1 + i]
    r
end

function setindex!{T,N}(V::SlowSubArray{T,N}, x, I::Vararg{Int,N})
    @_inline_meta
    @boundscheck checkbounds(V, I...)
    @inbounds V.parent[reindex(V, V.indexes, I)...] = x
    V
end
function setindex!(V::FastSubArray, x, i::Int)
    @_inline_meta
    @boundscheck checkbounds(V, i)
    @inbounds V.parent[V.offset1 + V.stride1*i] = x
    V
end
function setindex!(V::FastContiguousSubArray, x, i::Int)
    @_inline_meta
    @boundscheck checkbounds(V, i)
    @inbounds V.parent[V.offset1 + i] = x
    V
end

linearindexing{T<:FastSubArray}(::Type{T}) = LinearFast()
linearindexing{T<:SubArray}(::Type{T}) = LinearSlow()

# Strides are the distance between adjacent elements in a given dimension,
# so they are well-defined even for non-linear memory layouts
strides{T,N,P,I}(V::SubArray{T,N,P,I}) = substrides(V.parent, V.indexes)

substrides(parent, I::Tuple) = substrides(1, parent, 1, I)
substrides(s, parent, dim, ::Tuple{}) = ()
substrides(s, parent, dim, I::Tuple{ScalarIndex, Vararg{Any}}) = (substrides(s*size(parent, dim), parent, dim+1, tail(I))...)
substrides(s, parent, dim, I::Tuple{Slice, Vararg{Any}}) = (s, substrides(s*size(parent, dim), parent, dim+1, tail(I))...)
substrides(s, parent, dim, I::Tuple{Range, Vararg{Any}}) = (s*step(I[1]), substrides(s*size(parent, dim), parent, dim+1, tail(I))...)
substrides(s, parent, dim, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("strides is invalid for SubArrays with indices of type $(typeof(I[1]))"))

stride(V::SubArray, d::Integer) = d <= ndims(V) ? strides(V)[d] : strides(V)[end] * size(V)[end]

compute_stride1{N}(parent::AbstractArray, I::NTuple{N,Any}) =
    (@_inline_meta; compute_stride1(1, fill_to_length(indices(parent), OneTo(1), Val{N}), I))
compute_stride1(s, inds, I::Tuple{}) = s
compute_stride1(s, inds, I::Tuple{ScalarIndex, Vararg{Any}}) =
    (@_inline_meta; compute_stride1(s*unsafe_length(inds[1]), tail(inds), tail(I)))
compute_stride1(s, inds, I::Tuple{Range, Vararg{Any}}) = s*step(I[1])
compute_stride1(s, inds, I::Tuple{Slice, Vararg{Any}}) = s
compute_stride1(s, inds, I::Tuple{Any, Vararg{Any}}) = throw(ArgumentError("invalid strided index type $(typeof(I[1]))"))

iscontiguous(A::SubArray) = iscontiguous(typeof(A))
iscontiguous{S<:SubArray}(::Type{S}) = false
iscontiguous{F<:FastContiguousSubArray}(::Type{F}) = true

first_index(V::FastSubArray) = V.offset1 + V.stride1 # cached for fast linear SubArrays
function first_index(V::SubArray)
    P, I = parent(V), V.indexes
    s1 = compute_stride1(P, I)
    s1 + compute_offset1(P, s1, I)
end

# Computing the first index simply steps through the indices, accumulating the
# sum of index each multiplied by the parent's stride.
# The running sum is `f`; the cumulative stride product is `s`.
# If the result is one-dimensional and it's a Colon, then linear
# indexing uses the indices along the given dimension. Otherwise
# linear indexing always starts with 1.
compute_offset1(parent, stride1::Integer, I::Tuple) =
    (@_inline_meta; compute_offset1(parent, stride1, find_extended_dims(I)..., I))
compute_offset1(parent, stride1::Integer, dims::Tuple{Int}, inds::Tuple{Slice}, I::Tuple) =
    (@_inline_meta; compute_linindex(parent, I) - stride1*first(indices(parent, dims[1])))  # index-preserving case
compute_offset1(parent, stride1::Integer, dims, inds, I::Tuple) =
    (@_inline_meta; compute_linindex(parent, I) - stride1)  # linear indexing starts with 1

function compute_linindex{N}(parent, I::NTuple{N,Any})
    @_inline_meta
    IP = fill_to_length(indices(parent), OneTo(1), Val{N})
    compute_linindex(1, 1, IP, I)
end
function compute_linindex(f, s, IP::Tuple, I::Tuple{ScalarIndex, Vararg{Any}})
    @_inline_meta
    Δi = I[1]-first(IP[1])
    compute_linindex(f + Δi*s, s*unsafe_length(IP[1]), tail(IP), tail(I))
end
function compute_linindex(f, s, IP::Tuple, I::Tuple{Any, Vararg{Any}})
    @_inline_meta
    Δi = first(I[1])-first(IP[1])
    compute_linindex(f + Δi*s, s*unsafe_length(IP[1]), tail(IP), tail(I))
end
compute_linindex(f, s, IP::Tuple, I::Tuple{}) = f

find_extended_dims(I) = (@_inline_meta; _find_extended_dims((), (), 1, I...))
_find_extended_dims(dims, inds, dim) = dims, inds
_find_extended_dims(dims, inds, dim, ::ScalarIndex, I...) =
    (@_inline_meta; _find_extended_dims(dims, inds, dim+1, I...))
_find_extended_dims(dims, inds, dim, i1, I...) =
    (@_inline_meta; _find_extended_dims((dims..., dim), (inds..., i1), dim+1, I...))

unsafe_convert{T,N,P,I<:Tuple{Vararg{RangeIndex}}}(::Type{Ptr{T}}, V::SubArray{T,N,P,I}) =
    unsafe_convert(Ptr{T}, V.parent) + (first_index(V)-1)*sizeof(T)

pointer(V::FastSubArray, i::Int) = pointer(V.parent, V.offset1 + V.stride1*i)
pointer(V::FastContiguousSubArray, i::Int) = pointer(V.parent, V.offset1 + i)
pointer(V::SubArray, i::Int) = _pointer(V, i)
_pointer{T}(V::SubArray{T,1}, i::Int) = pointer(V, (i,))
_pointer(V::SubArray, i::Int) = pointer(V, ind2sub(indices(V), i))

function pointer{T,N,P<:Array,I<:Tuple{Vararg{RangeIndex}}}(V::SubArray{T,N,P,I}, is::Tuple{Vararg{Int}})
    index = first_index(V)
    strds = strides(V)
    for d = 1:length(is)
        index += (is[d]-1)*strds[d]
    end
    return pointer(V.parent, index)
end

# indices are taken from the range/vector
# Since bounds-checking is performance-critical and uses
# indices, it's worth optimizing these implementations thoroughly
indices(S::SubArray) = (@_inline_meta; _indices_sub(S, S.indexes...))
_indices_sub(S::SubArray) = ()
_indices_sub(S::SubArray, ::Real, I...) = (@_inline_meta; _indices_sub(S, I...))
function _indices_sub(S::SubArray, i1::AbstractArray, I...)
    @_inline_meta
    (unsafe_indices(i1)..., _indices_sub(S, I...)...)
end

## Compatability
# deprecate?
function parentdims(s::SubArray)
    nd = ndims(s)
    dimindex = Array{Int}(nd)
    sp = strides(s.parent)
    sv = strides(s)
    j = 1
    for i = 1:ndims(s.parent)
        r = s.indexes[i]
        if j <= nd && (isa(r,Union{Slice,Range}) ? sp[i]*step(r) : sp[i]) == sv[j]
            dimindex[j] = i
            j += 1
        end
    end
    dimindex
end

"""
    replace_ref_end!(ex)

Recursively replace occurrences of the symbol :end in a "ref" expression (i.e. A[...]) `ex`
with the appropriate function calls (`endof`, `size` or `trailingsize`). Replacement uses
the closest enclosing ref, so

    A[B[end]]

should transform to

    A[B[endof(B)]]

"""
replace_ref_end!(ex) = replace_ref_end_!(ex, nothing)[1]
# replace_ref_end_!(ex,withex) returns (new ex, whether withex was used)
function replace_ref_end_!(ex, withex)
    used_withex = false
    if isa(ex,Symbol) && ex == :end
        withex === nothing && error("Invalid use of end")
        return withex, true
    elseif isa(ex,Expr)
        if ex.head == :ref
            ex.args[1], used_withex = replace_ref_end_!(ex.args[1],withex)
            S = isa(ex.args[1],Symbol) ? ex.args[1]::Symbol : gensym(:S) # temp var to cache ex.args[1] if needed
            used_S = false # whether we actually need S
            # new :ref, so redefine withex
            nargs = length(ex.args)-1
            if nargs == 0
                return ex, used_withex
            elseif nargs == 1
                # replace with endof(S)
                ex.args[2], used_S = replace_ref_end_!(ex.args[2],:($endof($S)))
            else
                n = 1
                J = endof(ex.args)
                for j = 2:J-1
                    exj, used = replace_ref_end_!(ex.args[j],:($size($S,$n)))
                    used_S |= used
                    ex.args[j] = exj
                    if isa(exj,Expr) && exj.head == :...
                        # splatted object
                        exjs = exj.args[1]
                        n = :($n + length($exjs))
                    elseif isa(n, Expr)
                        # previous expression splatted
                        n = :($n + 1)
                    else
                        # an integer
                        n += 1
                    end
                end
                ex.args[J], used = replace_ref_end_!(ex.args[J],:($trailingsize($S,$n)))
                used_S |= used
            end
            if used_S && S !== ex.args[1]
                S0 = ex.args[1]
                ex.args[1] = S
                ex = Expr(:let, ex, :($S = $S0))
            end
        else
            # recursive search
            for i = eachindex(ex.args)
                ex.args[i], used = replace_ref_end_!(ex.args[i],withex)
                used_withex |= used
            end
        end
    end
    ex, used_withex
end

"""
    @view A[inds...]

Creates a `SubArray` from an indexing expression. This can only be applied directly to a
reference expression (e.g. `@view A[1,2:end]`), and should *not* be used as the target of
an assignment (e.g. `@view(A[1,2:end]) = ...`).  See also [`@views`](@ref)
to switch an entire block of code to use views for slicing.
"""
macro view(ex)
    if Meta.isexpr(ex, :ref)
        ex = replace_ref_end!(ex)
        if Meta.isexpr(ex, :ref)
            ex = Expr(:call, view, ex.args...)
        else # ex replaced by let ...; foo[...]; end
            assert(Meta.isexpr(ex, :let) && Meta.isexpr(ex.args[1], :ref))
            ex.args[1] = Expr(:call, view, ex.args[1].args...)
        end
        Expr(:&&, true, esc(ex))
    else
        throw(ArgumentError("Invalid use of @view macro: argument must be a reference expression A[...]."))
    end
end

############################################################################
# @views macro code:

# maybeview is like getindex, but returns a view for slicing operations
# (while remaining equivalent to getindex for scalar indices and non-array types)
@propagate_inbounds maybeview(A, args...) = getindex(A, args...)
@propagate_inbounds maybeview(A::AbstractArray, args...) = view(A, args...)
@propagate_inbounds maybeview(A::AbstractArray, args::Number...) = getindex(A, args...)
@propagate_inbounds maybeview(A) = getindex(A)
@propagate_inbounds maybeview(A::AbstractArray) = getindex(A)

# _views implements the transformation for the @views macro.
# @views calls esc(_views(...)) to work around #20241,
# so any function calls we insert (to maybeview, or to
# size and endof in replace_ref_end!) must be interpolated
# as values rather than as symbols to ensure that they are called
# from Base rather than from the caller's scope.
_views(x) = x
function _views(ex::Expr)
    if ex.head in (:(=), :(.=))
        # don't use view for ref on the lhs of an assignment,
        # but still use views for the args of the ref:
        lhs = ex.args[1]
        Expr(ex.head, Meta.isexpr(lhs, :ref) ?
                      Expr(:ref, _views.(lhs.args)...) : _views(lhs),
             _views(ex.args[2]))
    elseif ex.head == :ref
        Expr(:call, maybeview, _views.(ex.args)...)
    else
        h = string(ex.head)
        # don't use view on the lhs of an op-assignment a[i...] += ...
        if last(h) == '=' && Meta.isexpr(ex.args[1], :ref)
            lhs = ex.args[1]

            # temp vars to avoid recomputing a and i,
            # which will be assigned in a let block:
            a = gensym(:a)
            i = [gensym(:i) for k = 1:length(lhs.args)-1]

            # for splatted indices like a[i, j...], we need to
            # splat the corresponding temp var.
            I = similar(i, Any)
            for k = 1:length(i)
                if Meta.isexpr(lhs.args[k+1], :...)
                    I[k] = Expr(:..., i[k])
                    lhs.args[k+1] = lhs.args[k+1].args[1] # unsplat
                else
                    I[k] = i[k]
                end
            end

            Expr(:let,
                 Expr(first(h) == '.' ? :(.=) : :(=), :($a[$(I...)]),
                      Expr(:call, Symbol(h[1:end-1]),
                           :($maybeview($a, $(I...))),
                           _views.(ex.args[2:end])...)),
                 :($a = $(_views(lhs.args[1]))),
                 [:($(i[k]) = $(_views(lhs.args[k+1]))) for k=1:length(i)]...)
        else
            Expr(ex.head, _views.(ex.args)...)
        end
    end
end

"""
    @views expression

Convert every array-slicing operation in the given expression
(which may be a `begin`/`end` block, loop, function, etc.)
to return a view.   Scalar indices, non-array types, and
explicit `getindex` calls (as opposed to `array[...]`) are
unaffected.

Note that the `@views` macro only affects `array[...]` expressions
that appear explicitly in the given `expression`, not array slicing that
occurs in functions called by that code.
"""
macro views(x)
    esc(_views(replace_ref_end!(x)))
end

max_stars_repo_name: oxinabox/julia

max_issues_repo_name: oxinabox/julia

max_forks_repo_name: oxinabox/julia


text:
using Test
using ComplexMixtures, PDBTools
const CM = ComplexMixtures

@testset "Merge" begin

  dir="./data/NAMD"
  atoms = readPDB("$dir/structure.pdb")
  tmao = Selection(select(atoms,"resname TMAO"),natomspermol=14)
  water = Selection(select(atoms,"water"),natomspermol=3)

  # save(R,"$dir/merged.json")
  R_save = load("$dir/merged.json")

  options = Options(firstframe=1,lastframe=2,seed=321,StableRNG=true,nthreads=1,silent=true)
  traj = Trajectory("$dir/trajectory.dcd",tmao,water)
  R1 = mddf(traj,options)

  options = Options(firstframe=3,lastframe=6,seed=321,StableRNG=true,nthreads=1,silent=true)
  traj = Trajectory("$dir/trajectory.dcd",tmao,water)
  R2 = mddf(traj,options)

  R = merge([R1,R2])
  @test isapprox(R,R_save,debug=true)

end


text:
#!/usr/bin/env julia

using PrettyPrinting
using NarrativeTest

args = !isempty(ARGS) ? ARGS : [relpath(joinpath(dirname(abspath(PROGRAM_FILE)), "../doc/src"))]
exit(!runtests(args))


text:
# minimalistic compatibility definitions
const Toplevel = Gtk.WindowLeaf
pack(x...;y...) = nothing
Canvas(w, x, y) = ((w |> (c=show(Gtk.@Canvas(x,y)))); c)


text:
### A Pluto.jl notebook ###
# v0.17.5

using Markdown
using InteractiveUtils

# ╔═╡ bfc8e740-7302-11ec-19e2-8995cf677fbf
# hideall
let
    docs_dir = dirname(dirname(@__DIR__))
    pkg_dir = dirname(docs_dir)

    using Pkg: Pkg
    Pkg.activate(docs_dir)
    Pkg.develop(; path=pkg_dir)
    Pkg.instantiate()

    # Putting the include here to avoid Pluto getting confused about cell order.
    include(joinpath(docs_dir, "src", "tutorials_utils.jl"))
end;

# ╔═╡ 423d0cd9-010f-43b2-a39e-8f8f5c0233a8
using CSV

# ╔═╡ a78a82f2-2ee5-41a5-a0e3-bae1639e3859
using DataFrames

# ╔═╡ 0f6e5024-402e-47ba-ab6c-bd75b5b5bfb0
using TuringGLM

# ╔═╡ 9ae04dd9-c612-4e88-bb02-38d0f325bfe9
md"""
For our example on **Poisson Regression**, let's use a famous dataset called `roaches` (Gelman & Hill, 2007), which is data on the efficacy of a
pest management system at reducing the number of roaches in urban apartments.
It has 262 observations and the following variables:

* `y` -- number of roaches caught.
* `roach1` -- pretreatment number of roaches.
* `treatment` -- binary/dummy (0 or 1) for treatment indicator.
* `senior` -- binary/dummy (0 or 1) for only elderly residents in building.
* `exposure2` -- number of days for which the roach traps were used
"""

# ╔═╡ 15670545-e2c5-4396-89d8-dddd919925d9
url = "https://github.com/TuringLang/TuringGLM.jl/raw/main/data/roaches.csv";

# ╔═╡ 84b8c1ec-779c-4113-ae6a-084955eba852
roaches = CSV.read(download(url), DataFrame)

# ╔═╡ ca606aee-3115-4e43-97ca-4aae60a387bf
md"""
Using `y` as dependent variable and `roach1`, `treatment`, and `senior` as independent variables:
"""

# ╔═╡ 9f88708a-7825-4c96-a720-ac7bf25e2591
fm = @formula(y ~ roach1 + treatment + senior)

# ╔═╡ 65f4f379-e9a5-4571-bc80-77c024f3f560
md"""
We instantiate our model with `turing_model` passing a keyword argument `model=Poisson` to indicate that the model is a Poisson Regression:
"""

# ╔═╡ 9147ed9e-a047-42ca-aa36-f522fad8388b
model = turing_model(fm, roaches; model=Poisson);

# ╔═╡ aeeac527-2c32-407f-84a1-912cc74f51b7
md"""
Sample the model using the `NUTS` sampler and 2,000 samples:
"""

# ╔═╡ abe86f76-2d0f-42e5-8faa-52aa3ddea126
chn = sample(model, NUTS(), 2_000);

# ╔═╡ d322ced4-9334-4a15-b3f9-e7b05a8a7d52
# hide
plot_chains(chn)

# ╔═╡ 71fe7834-117d-4d52-a86d-25d4a52bc9a0
md"""
## References

Gelman, A., & Hill, J. (2007). Data analysis using regression and multilevel/hierarchical models. Cambridge university press.
"""

# ╔═╡ Cell order:
# ╠═bfc8e740-7302-11ec-19e2-8995cf677fbf
# ╠═9ae04dd9-c612-4e88-bb02-38d0f325bfe9
# ╠═423d0cd9-010f-43b2-a39e-8f8f5c0233a8
# ╠═a78a82f2-2ee5-41a5-a0e3-bae1639e3859
# ╠═15670545-e2c5-4396-89d8-dddd919925d9
# ╠═84b8c1ec-779c-4113-ae6a-084955eba852
# ╠═0f6e5024-402e-47ba-ab6c-bd75b5b5bfb0
# ╠═ca606aee-3115-4e43-97ca-4aae60a387bf
# ╠═9f88708a-7825-4c96-a720-ac7bf25e2591
# ╠═65f4f379-e9a5-4571-bc80-77c024f3f560
# ╠═9147ed9e-a047-42ca-aa36-f522fad8388b
# ╠═aeeac527-2c32-407f-84a1-912cc74f51b7
# ╠═abe86f76-2d0f-42e5-8faa-52aa3ddea126
# ╠═d322ced4-9334-4a15-b3f9-e7b05a8a7d52
# ╠═71fe7834-117d-4d52-a86d-25d4a52bc9a0


text:
# initializing and optimizing embeddings

function initialize_embedding(graph::AbstractMatrix{T}, n_components, ::Val{:spectral}) where {T}
    local embed
    try
        embed = spectral_layout(graph, n_components)
        # expand
        expansion = 10 / maximum(embed)
        embed .= (embed .* expansion) .+ (1//10000) .* randn.(T)
        embed = collect(eachcol(embed))
    catch e
        @info "$e\nError encountered in spectral_layout; defaulting to random layout"
        embed = initialize_embedding(graph, n_components, Val(:random))
    end
    return embed
end

function initialize_embedding(graph::AbstractMatrix{T}, n_components, ::Val{:random}) where {T}
    return [20 .* rand(T, n_components) .- 10 for _ in 1:size(graph, 1)]
end

"""
    initialize_embedding(graph::AbstractMatrix{<:Real}, ref_embedding::AbstractMatrix{T<:AbstractFloat}) -> embedding

Initialize an embedding of points corresponding to the columns of the `graph`, by taking weighted means of
the columns of `ref_embedding`, where weights are values from the rows of the `graph`.

The resulting embedding will have shape `(size(ref_embedding, 1), size(graph, 2))`, where `size(ref_embedding, 1)`
is the number of components (dimensions) of the `reference embedding`, and `size(graph, 2)` is the number of
samples in the resulting embedding. Its elements will have type T.
"""
function initialize_embedding(graph::AbstractMatrix{<:Real}, ref_embedding::AbstractMatrix{T})::Vector{Vector{T}} where {T<:AbstractFloat}
    embed = (ref_embedding * graph) ./ (sum(graph, dims=1) .+ eps(T))
    return Vector{T}[eachcol(embed)...]
end

"""
    spectral_layout(graph, embed_dim) -> embedding

Initialize the graph layout with spectral embedding.
"""
function spectral_layout(graph::SparseMatrixCSC{T},
                         embed_dim::Integer) where {T<:Real}
    graph_f64 = convert.(Float64, graph)
    D_ = Diagonal(dropdims(sum(graph_f64; dims=2); dims=2))
    D = inv(sqrt(D_))
    # normalized laplacian
    L = Symmetric(I - D*graph*D)

    k = embed_dim+1
    num_lanczos_vectors = max(2k+1, round(Int, sqrt(size(L, 1))))
    # get the 2nd - embed_dim+1th smallest eigenvectors
    eigenvals, eigenvecs = eigs(L; nev=k,
                                   ncv=num_lanczos_vectors,
                                   which=:SM,
                                   tol=1e-4,
                                   v0=ones(Float64, size(L, 1)),
                                   maxiter=size(L, 1)*5)
    layout = permutedims(eigenvecs[:, 2:k])::Array{Float64, 2}
    return convert.(T, layout)
end

"""
    optimize_embedding(graph, query_embedding, ref_embedding, n_epochs, initial_alpha, min_dist, spread, gamma, neg_sample_rate, _a=nothing, _b=nothing; move_ref=false) -> embedding

Optimize an embedding by minimizing the fuzzy set cross entropy between the high and low dimensional simplicial sets using stochastic gradient descent.
Optimize "query" samples with respect to "reference" samples.

# Arguments
- `graph`: a sparse matrix of shape (n_samples, n_samples)
- `query_embedding`: a vector of length (n_samples,) of vectors representing the embedded data points to be optimized ("query" samples)
- `ref_embedding`: a vector of length (n_samples,) of vectors representing the embedded data points to optimize against ("reference" samples)
- `n_epochs`: the number of training epochs for optimization
- `initial_alpha`: the initial learning rate
- `gamma`: the repulsive strength of negative samples
- `neg_sample_rate`: the number of negative samples per positive sample
- `_a`: this controls the embedding. If the actual argument is `nothing`, this is determined automatically by `min_dist` and `spread`.
- `_b`: this controls the embedding. If the actual argument is `nothing`, this is determined automatically by `min_dist` and `spread`.

# Keyword Arguments
- `move_ref::Bool = false`: if true, also improve the embeddings in `ref_embedding`, else fix them and only improve embeddings in `query_embedding`.
"""
function optimize_embedding(graph,
                            query_embedding,
                            ref_embedding,
                            n_epochs,
                            initial_alpha,
                            min_dist,
                            spread,
                            gamma,
                            neg_sample_rate,
                            _a=nothing,
                            _b=nothing;
                            move_ref::Bool=false)
    a, b = fit_ab(min_dist, spread, _a, _b)
    self_reference = query_embedding === ref_embedding

    alpha = initial_alpha
    for e in 1:n_epochs
        @inbounds for i in 1:size(graph, 2)
            for ind in nzrange(graph, i)
                j = rowvals(graph)[ind]
                p = nonzeros(graph)[ind]
                if rand() <= p
                    sdist = evaluate(SqEuclidean(), query_embedding[i], ref_embedding[j])
                    if sdist > 0
                        delta = (-2 * a * b * sdist^(b-1))/(1 + a*sdist^b)
                    else
                        delta = 0
                    end
                    @simd for d in eachindex(query_embedding[i])
                        grad = clamp(delta * (query_embedding[i][d] - ref_embedding[j][d]), -4, 4)
                        query_embedding[i][d] += alpha * grad
                        if move_ref
                            ref_embedding[j][d] -= alpha * grad
                        end
                    end

                    for _ in 1:neg_sample_rate
                        k = rand(eachindex(ref_embedding))
                        if i == k && self_reference
                            continue
                        end
                        sdist = evaluate(SqEuclidean(), query_embedding[i], ref_embedding[k])
                        if sdist > 0
                            delta = (2 * gamma * b) / ((1//1000 + sdist)*(1 + a*sdist^b))
                        else
                            delta = 0
                        end
                        @simd for d in eachindex(query_embedding[i])
                            if delta > 0
                                grad = clamp(delta * (query_embedding[i][d] - ref_embedding[k][d]), -4, 4)
                            else
                                grad = 4
                            end
                            query_embedding[i][d] += alpha * grad
                        end
                    end

                end
            end
        end
        alpha = initial_alpha*(1 - e//n_epochs)
    end

    return query_embedding
end


text:
include("neighbor.jl")

#=
Include the metric functions.
Include at least one metric function for every supported ValueType,
e.g. one for Color and one for Char.
When there are many implementations of metric having same signature, choose one.
=#
# TODO cruft here
include("metric/colorCauchy.jl")
include("metric/character/nieve.jl")
# include("colorMetric.jl")
# include("pointDifference.jl")

#=
Return difference between points in two different Arrays.
(E.G. difference between colors of pixels in 2D images.)

This hides dealing with bounds and masking in the corpus (see below.)
It generically calls pointDifference, a metric function (see elsewhere.)

The points belong to a patch.
Assert the center point of the corpus patch is ensured in bounds,
but the given point might not be.
Assert the center of the target patch is ensured in bounds.
And the center of the target patch is in the synth region.
And each given point of the target patch is ensured in bounds (by construction.)
Any given target point can be in the synth region or its complement (the context.)

In other words, patchPointCorpus is wild:
-possibly out of bounds
-possibly masked (a selection mask defines non-rectangular regions)
When masked or out of bounds, return a maximum difference.

Not a separate function in the original code.
=#

#=
OLD, when points were PointInMaskedImage i.e. carried their frame.

function comparePointsTargetWithCorpus(patchPointTarget, patchPointCorpus)
    result = 0

    # in bounds of the corpus (within the outer boundary)
    # and in the selected region of corpus (within interior boundaries so to speak)
    if isInBoundsAndSelected(patchPointCorpus)
        # call the metric.  See metric/ dir
        result = pointDifference(
            patchPointTarget,
            patchPointCorpus)
    else
        # We don't distinguish between out of bounds or masked, both yield maximum diff
        #@debug "Max color diff, masked or out of bounds" patchPointCorpus.point
        result = maxPointDifference()
    end

    return result
end
=#

#=
Specialized: first argument is Neighbor.
Where a Neighbor caches the value of its point.
=#
function comparePointsTargetWithCorpus(
    neighbor::Neighbor{ValueType, DimensionCount},
    corpusImage::MaskedImage{ValueType, DimensionCount}, # frame
    patchPointCorpus::CartesianIndex{DimensionCount}     # framed point
    ) where {DimensionCount, ValueType}

    # result has the return type of the metric
    result::Float32 = zero(Float32)

    #=
    Is in bounds of the corpus (within the outer boundary)
    and in the selected region of corpus (within interior boundaries so to speak).

    Named "clippedOrMaskedCorpus" in the original code.
    =#
    if isInBoundsAndSelected(corpusImage, patchPointCorpus)
        result = pointDifference(
            neighbor.value,                         # target value
            corpusImage.image[patchPointCorpus])    # corpus value
    else
        # We don't distinguish between out of bounds or masked, both yield maximum diff
        #@debug "Max point diff, masked or out of bounds" patchPointCorpus.point
        result = maxPointDifference
    end

    # @debug "Compare point, yields" patchPointCorpus result
    return result
end

max_stars_repo_name: bootchk/resynthesizerJulia

max_issues_repo_name: bootchk/resynthesizerJulia

max_forks_repo_name: bootchk/resynthesizerJulia


text:
"""
Device has two channels: 1 & 2
# Supported functions
- `initialize()`
- `terminate()`
- `enable_output()`
- `disable_output()`
- `get_output()`
- `get_frequency()`
- `set_frequency()`
- `get_amplitude()`
- `set_amplitude()`
- `get_burst_num_cycles()`
- `set_burst_num_cycles()`
- `get_time_offset()`: Not implemented
- `set_time_offset()`: Not implemented
- `get_voltage_offset()`
- `set_voltage_offset()`
- `get_burst_period()`
- `set_burst_period()`
- `get_mode()`
- `set_mode_burst(; mode=:trigger, trig_src=:timer)`
- `set_mode_cw()`

Creating a Sin Wave Example:
```
wave = initialize(Keysight33612A, "10.1.30.36")
set_mode_cw(wave) # Set to continuous waveform mode
set_function(wave, "SIN")
set_frequency(wave, "1e3") # Frequency val can be string or number
set_amplitude(wave, 0.1)
set_voltage_offset(wave, 0)
enable_output(wave) # Starts wave
```
"""
struct Keysight33612A <: WaveformGenerator end

enable_output(obj::Instr{Keysight33612A}; chan=1)  = write(obj, "OUTPUT$chan ON")
disable_output(obj::Instr{Keysight33612A}; chan=1) = write(obj, "OUTPUT$chan OFF")
get_output(obj::Instr{Keysight33612A}; chan=1)     = write(obj, "OUTPUT$chan?")

get_voltage_offset(obj::Instr{Keysight33612A}; chan=1) =
    f_query(obj, "SOURCE$chan:VOLTAGE:OFFSET?") * V
set_voltage_offset(obj::Instr{Keysight33612A}, num::Voltage; chan=1) =
    write(obj, "SOURCE$chan:VOLTAGE:OFFSET $(raw(num))")

get_amplitude(obj::Instr{Keysight33612A}; chan=1) =
    f_query(obj, "SOURCE$chan:VOLTAGE?") * V
set_amplitude(obj::Instr{Keysight33612A}, num::Voltage; chan=1) =
    write(obj, "SOURCE$chan:VOLTAGE $(raw(num))")

get_frequency(obj::Instr{Keysight33612A}; chan=1) =
    f_query(obj, "SOURCE$chan:FREQUENCY?") # +4.0E+05
set_frequency(obj::Instr{Keysight33612A}, num::Frequency; chan=1) =
    write(obj, "SOURCE$chan:FREQUENCY $(raw(num)))") # +4.0E+05

"""
    get_function(instr)
    get_function(instr; chan=2)

# Keywords
- `chan`: Specify channel: Default is 1
# Returns
- `String`: Will return one of these shortened forms:
{SINusoid|SQUare|TRIangle|RAMP|PULSe|PRBS|NOISe|ARB|DC}
"""
get_function(obj::Instr{Keysight33612A}; chan=1) = query(obj, "SOURCE$chan:FUNCTION?") # +4.0E+05

"""
set_function(instr, func; chan=1)

# Arguments
- `func::String`: Acceptable inputs:
{SINusoid|SQUare|TRIangle|RAMP|PULSe|PRBS|NOISe|ARB|DC}

# Keywords
- `chan`: Specify channel: Default is 1
"""
set_function(obj::Instr{Keysight33612A}, func; chan=1) = write(obj, "SOURCE$chan:FUNCTION $func") # +4.0E+05

"""
    Sets the burst mode of a device to Triggered Mode

"""
set_burst_mode_trigger(obj::Instr{Keysight33612A}; chan=1) = write(obj, "SOURCE$chan:BURST:MODE TRIG")
"""
Sets the burst mode of a device to Gated Mode
"""
set_burst_mode_gated(obj::Instr{Keysight33612A}; chan=1) = write(obj, "SOURCE$chan:BURST:MODE GATED")

"""
    get_burst_mode(instr)
    get_burst_mode(instr; chan=1)

Returns the burst mode of a device:
    "TRIG" ~ If the device is in Triggered Mode
    "GAT" ~ If the device is in Gated Mode
"""
get_burst_mode(obj::Instr{Keysight33612A}; chan=1) = query(obj, "SOURCE$chan:BURST:MODE?")

"""
```
    set_mode_burst(instr)
```
Changes Waveform Generator mode from continous waveform to burst

By default it sets the type of burst mode to triggered mode. Gated
mode can also be set by using the optional flag:

```
    set_mode_burst(instr; mode=:gated)
```

The optional trig_src flag sets the trigger source for burst
triggered mode. Right now the default is Timer. To implement
more trigger sources see page 130 of the manual for 33612A

"""
function set_mode_burst(obj::Instr{Keysight33612A};
                         chan=1,
                         mode=:trigger,
                         trig_src=:timer)
    if mode == :trigger
        set_burst_mode_trigger(obj)
    elseif mode == :gated
        set_burst_mode_gated(obj)
    else
        error("""mode flag cannot be set to: $mode\n
              available modes are [:trigger, :gated]""")
    end
    if trig_src == :timer
        set_trigger_source_timer(obj; chan=chan)
    else
        error("""trig_src flag cannot be set to: $trig_src\n
              available modes are [:timer]""")
    end
    write(obj, "SOURCE$chan:BURST:STATE ON")
end

"""
set_mode_cw(instr)
set_mode_cw(instr; chan=2)

Puts the device in continuous waveform/turns off burst mode

# Keywords
- `chan`: Specify channel: Default is 1
"""
set_mode_cw(obj::Instr{Keysight33612A}; chan=1) = write(obj, "SOURCE$chan:BURST:STATE OFF")

"""
    get_mode(instr)
    get_mode(instr; chan=2)

# Keywords
- `chan`: Specify channel: Default is 1
Returns:
    "CW" ~ if device is in continous wavefrom mode
    "BURST" ~ if device is in BURST mode
"""
get_mode(obj::Instr{Keysight33612A}; chan=1) = query(obj, "SOURCE$chan:BURST:STATE?") == "1" ? "BURST" : "CW"

"""
    get_burst_num_of_cycles(instr)
    get_burst_num_of_cycles(instr; chan=2)

# Keywords
- `chan`: Specify channel: Default is 1

# Returns
- `Float64`: number of cycles burst mode is set to
"""
get_burst_num_cycles(obj::Instr{Keysight33612A}; chan=1) = f_query(obj, "SOURCE$chan:BURST:NCYCLES?")

"""
    set_burst_num_of_cycles(instr, cycles)
    set_burst_num_of_cycles(instr, cycles; chan=2)

Sets the number of cycles for burst mode

# Arguments
- `cycles`
# Keywords
- `chan`: Specify channel: Default is 1
"""
set_burst_num_cycles(obj::Instr{Keysight33612A}, num; chan=1) = write(obj, "SOURCE$chan:BURST:NCYCLES $(Float64(num))")

"""
    set_burst_period(obj, num; chan=1)

# Arguments
- `num`: The number of seconds (This value can also be "MIN" or "MAX")
# Keywords
- `chan`: Specify channel: Default is 1
"""
set_burst_period(obj, num; chan::Integer=1) = write(obj, "SOURCE$chan:BURST:INTERNAL:PERIOD $(Float64(num))")

"""
    get_burst_period(instr)
    get_burst_period(instr; chan=2)

# Keywords
- `chan`: Specify channel: Default is 1
# Returns
- `Float64`: number of cycles burst mode is set to
"""
get_burst_period(obj; chan::Integer=1) = f_query(obj, "SOURCE$chan:BURST:INTERNAL:PERIOD?")

set_trigger_source_timer(obj; chan::Integer=1) = write(obj, "TRIGGER$chan:SOURCE TIMER")

status(obj::Instr{Keysight33612A}) = query(obj, "APPLY?")


text:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
# ==============================================================================
#
#   Functions related to decorations in strings.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

export drop_inactive_properties, get_decorations, get_and_remove_decorations
export parse_decoration, remove_decorations, update_decoration

"""
    drop_inactive_properties(decoration::Decoration)

Drop the inactive properties of `decoration` by changing them to inactive. This
operation can be useful to avoid unnecessary escape sequences if the decorations
are reset.
"""
function drop_inactive_properties(decoration::Decoration)

    # Unpack fields.
    foreground = decoration.foreground
    background = decoration.background
    bold       = decoration.bold
    underline  = decoration.underline
    reversed   = decoration.reversed

    # If a field is inactive or if it is a reset to a default value, drop it by
    # returning to default.
    if foreground == "39"
        foreground =  ""
    end

    if background == "49"
        background = ""
    end

    if bold == inactive
        bold = unchanged
    end

    if underline == inactive
        underline = unchanged
    end

    if reversed == inactive
        reversed = unchanged
    end

    return Decoration(;
        foreground,
        background,
        bold,
        underline,
        reversed
    )
end

"""
    get_decorations(str::AbstractString)

Return a string with the decorations in `str`.
"""
function get_decorations(str::AbstractString)
    buf = IOBuffer(sizehint = sizeof(str))

    for m in eachmatch(_REGEX_ANSI, str)
        write(buf, m.match)
    end

    return String(take!(buf))
end

"""
    get_and_remove_decorations(str::AbstractString)

Get and remove the decorations in `str`. The first returned string contains the
decorations whereas the second contains the plain text.
"""
function get_and_remove_decorations(str::AbstractString)
    buf_decorations = IOBuffer(sizehint = floor(Int, sizeof(str)))
    buf_plain_str = IOBuffer(sizehint = floor(Int, sizeof(str)))

    str_i = 1

    # Loop for each match of a ANSI escape sequence.
    for m in eachmatch(_REGEX_ANSI, str)
        # Write everything from the previous match up to the last character
        # before the current match.
        if m.offset - 1 > 0
            str_f = prevind(str, m.offset)
            write(buf_plain_str, SubString(str, str_i, str_f))
        end

        write(buf_decorations, m.match)

        # `str_i` now have the index just after the current match.
        str_i = m.offset + ncodeunits(m.match)
    end

    # Write the rest of the string.
    write(buf_plain_str, SubString(str, str_i, lastindex(str)))

    return String(take!(buf_decorations)), String(take!(buf_plain_str))
end

"""
    parse_decoration(code::AbstractString)

Parse the decoration in the string `str` and returns an object of type
`Decoration` with it.
"""
function parse_decoration(code::AbstractString)
    state = :text
    buf = IOBuffer(sizehint = floor(Int, sizeof(code)))
    decoration = Decoration()

    for c in code
        state = _process_string_state(c, state)

        if state == :escape_state_begin
            take!(buf)
            write(buf, c)

        elseif state == :escape_state_end
            write(buf, c)
            str = String(take!(buf))
            decoration = update_decoration(decoration, str)

        elseif state == :text
            take!(buf)

        else
            write(buf, c)

        end
    end

    return decoration
end

"""
    remove_decorations(str::AbstractString)

Remove all the decorations added by ANSI escape sequences from the string `str`.
"""
function remove_decorations(str::AbstractString)

    # After some testing, it turns out that using the ANSI regex is way faster
    # than using the string state.
    return replace(str, _REGEX_ANSI => "")
end

"""
    update_decoration(decoration::Decoration, str::String)

Update the current `decoration` given the decorations in the string `str`.
"""
function update_decoration(decoration::Decoration, code::String)
    state = :text
    buf = IOBuffer()

    for c in code
        state = _process_string_state(c, state)

        if state == :escape_state_begin
            take!(buf)

        elseif state == :escape_state_end
            str = String(take!(buf))
            decoration = _parse_ansi_code(decoration, str)

        elseif state == :text
            take!(buf)

        elseif state != :escape_state_opening
            write(buf, c)

        end
    end

    return decoration
end

################################################################################
#                                     API
################################################################################

String(d::Decoration) = convert(String, d)

# Convert  `Decoration` to string.
function convert(::Type{String}, d::Decoration)
    # Check if we have a reset.
    d === _DEFAULT_DECORATION && return ""
    d.reset && return "$(_CSI)0m"

    # TODO: Check if we can avoid adding so many `_CSI`.
    buf = IOBuffer()

    if !isempty(d.foreground)
        write(buf, _CSI)
        write(buf, d.foreground)
        write(buf, "m")
    end

    if !isempty(d.background)
        write(buf, _CSI)
        write(buf, d.background)
        write(buf, "m")
    end

    if d.bold != unchanged
        write(buf, _CSI)
        d.bold == active ? write(buf, "1") : write(buf, "22")
        write(buf, "m")
    end

    if d.underline != unchanged
        write(buf, _CSI)
        d.underline == active ? write(buf, "4") : write(buf, "24")
        write(buf, "m")
    end

    if d.reversed != unchanged
        write(buf, _CSI)
        d.reversed == active ? write(buf, "7") : write(buf, "27")
        write(buf, "m")
    end

    return String(take!(buf))
end