## materialize
mtb_materialize(x, ::Integer) = x
mtb_materialize(bc::Broadcasted, TN::Integer) = mtb_copy(instantiate(bc), TN)

mtb_materialize!(dest, x, TN::Integer) =
    mtb_materialize!(dest, instantiate(Broadcasted(identity, (x,), axes(dest))), TN)

mtb_materialize!(dest, bc::Broadcasted{Style}, TN::Integer) where {Style} =
    mtb_materialize!(combine_styles(dest, bc), dest, bc, TN)

mtb_materialize!(::BroadcastStyle, dest, bc::Broadcasted{Style}, TN::Integer) where {Style} =
    mtb_copyto!(dest, instantiate(Broadcasted{Style}(bc.f, bc.args, axes(dest))), TN)

## copyto
mtb_copy(bc::Broadcasted{Style{Tuple}}, ::Integer) = copy(bc)
mtb_copy(bc::FilledBC, ::Integer) = copy(bc)
mtb_copy(bc::Broadcasted{<:Union{Nothing,Unknown}}, ::Integer) = copy(bc)
function mtb_copy(bc::Broadcasted, TN::Integer)
    ElType = Base.promote_typejoin_union(Broadcast._broadcast_getindex_eltype(bc))
    Base.isconcretetype(ElType) && return mtb_copyto!(similar(bc, ElType), bc, TN)
    @warn "$(ElType) is not concrete, call Base.copy instead"
    copy(bc)
end

## copyto!
mtb_copyto!(dest::AbstractArray, bc::Broadcasted, TN::Integer) =
    mtb_copyto!(dest, convert(Broadcasted{Nothing}, bc), TN)

function mtb_copyto!(dest::AbstractArray, bc::FilledBC, TN::Integer)
    if bc.f === identity && bc.args isa Tuple{Any} && Broadcast.isflat(bc)
        return fill!(dest, bc.args[1][])
    else
        return mtb_copyto!(dest, convert(Broadcasted{Nothing}, bc), TN)
    end
end

function mtb_copyto!(dest::AbstractArray, bc::Broadcasted{Nothing}, TN::Integer)
    length(dest) == 1 && return copyto!(dest, bc)
    axes(dest) == axes(bc) || throwdm(axes(dest), axes(bc))
    if bc.f === identity && bc.args isa Tuple{AbstractArray}
        A = only(bc.args)
        axes(dest) == axes(A) && return copyto!(dest, A)
    end
    bc′ = preprocess(dest, bc)
    Inds = eachindex(bc′)
    mtb_call(mtb_config(TN, Inds)) do ran
        iter = @inbounds view(Inds, ran)
        @inbounds @simd for I in iter
            dest[I] = bc′[I]
        end
        nothing
    end
    dest
end

function mtb_copyto!(dest::BitArray, bc::Broadcasted{Nothing}, TN::Integer)
    axes(dest) == axes(bc) || throwdm(axes(dest), axes(bc))
    ischunkedbroadcast(dest, bc) && return chunkedcopyto!(dest, bc)
    length(dest) < 256 &&  invoke(copyto!, Tuple{AbstractArray,Broadcasted{Nothing}}, dest, bc)
    destc = dest.chunks
    bc′ = preprocess(dest, bc)
    Inds = eachindex(bc′)
    mtb_call(mtb_config(TN, Inds, bitcache_size)) do ran
        tmp = Vector{Bool}(undef, bitcache_size)
        iter = @inbounds view(Inds, ran)
        cind = 1 + (first(ran) - firstindex(Inds)) >> 6
        @inbounds for P in Iterators.partition(iter, bitcache_size)
            ind = 0
            @simd for I in P
                tmp[ind += 1] = bc′[I]
            end
            while ind < bitcache_size
                tmp[ind += 1] = false
            end
            dumpbitcache(destc, cind, tmp)
            cind += bitcache_chunks
        end
        nothing
    end
    dest
end

import Polyester: @batch
## mtb_call
@static if threads_provider == "Polyester"
    function mtb_call(@nospecialize(kernal::Function), (f, l, s, n)::NTuple{4,Int})
        @batch for tid in 1:n
            k = f + s * tid
            kernal(k - s:min(k - 1, l))
        end
        nothing
    end
else
    function mtb_call(@nospecialize(kernal::Function), (f, l, s, n)::Dims{4})
        if n > 3
            n′ = n >> 1 + (n & 1)
            task = Threads.@spawn mtb_call(kernal, (f, l, s, n′))
            mtb_call(kernal, (f + s * n′, l, s, n - n′))
            wait(task)
        elseif n == 3
            task₁ = Threads.@spawn kernal($f:$(f += s) - 1)
            task₂ = Threads.@spawn kernal($f:$(f += s) - 1)
            kernal(f:min(f + s - 1, l))
            wait(task₁)
            wait(task₂)
        else
            task = Threads.@spawn kernal($f:$(f += s) - 1)
            kernal(f:min(f + s - 1, l))
            wait(task)
        end
        nothing
    end
end

##Multi-threading config
@inline function mtb_config(threads::Integer, ax::AbstractArray, batch::Integer=1)
    len = cld(length(ax), threads * batch) * batch
    num = cld(length(ax), len)
    firstindex(ax), lastindex(ax), len, num
end
