module YAXTools
using Distributed
import ..YAXArrays: YAXdir
export freshworkermodule, passobj, @everywhereelsem,
@loadOrGenerate, PickAxisArray
struct PickAxisArray{T,N,AT<:AbstractArray,P,PERM}
    parent::AT
end

function PickAxisArray(parent, indmask, perm=nothing)
    f  = findall(isequal(true),indmask)
    f2 = findall(isequal(Colon()), indmask)
    o = sort([f;f2])
    o = isempty(f2) ? o : replace(o, map(i->i=>Colon(),f2)...)
    nsub = 0
    for i in 1:length(o)
      if o[i] isa Colon
        nsub +=1
      else
        o[i] = o[i]-nsub
      end
    end
    if perm !== nothing
      length(perm) != length(f2) && error("Not a valid permutation")
      perm = (perm...,)
    end
    PickAxisArray{eltype(parent),length(indmask),typeof(parent),(o...,),perm}(parent)
end
indmask(p::PickAxisArray{<:Any,<:Any,<:Any,i}) where i = i
getind(i,j) = i[j]
getind(i,j::Colon) = j
permout(::PickAxisArray{<:Any,<:Any,<:Any,<:Any,P},x) where P = permutedims(x,P)
permout(::PickAxisArray{<:Any,<:Any,<:Any,<:Any,nothing},x) = x
function Base.view(p::PickAxisArray, i::Integer...)
  inew = map(j->getind(i,j),indmask(p))
  r = permout(p,view(p.parent,inew...))
  r
end
function Base.getindex(p::PickAxisArray, i::Integer...)
    inew = map(j->getind(i,j),indmask(p))
    permout(p,getindex(p.parent,inew...))
end
anycol(t::Tuple{}) = false
anycol(t::Tuple) = anycol(first(t), Base.tail(t))
anycol(::Colon,t::Tuple) = true
anycol(i,::Tuple{}) = false
anycol(::Colon,::Tuple{}) = true
anycol(i,t::Tuple) = anycol(first(t),Base.tail(t))
ncol(t::Tuple) = ncol(first(t), Base.tail(t),0)
ncol(::Colon,t::Tuple,n) = ncol(first(t),Base.tail(t), n+1)
ncol(i,::Tuple{},n) = n
ncol(::Colon,::Tuple{},n) = n+1
ncol(i,t::Tuple,n) = ncol(first(t),Base.tail(t),n)

function Base.eltype(p::PickAxisArray{T}) where T
  im = indmask(p)
  if anycol(im)
    Array{T,ncol(im)}
  else
    T
  end
end
Base.getindex(p::PickAxisArray,i::CartesianIndex) = p[i.I...]

"""
    macro loadOrGenerate(x...,expression)

Takes a list of `variablename=>"Storage Name"` pairs. Checks if all datasets can be found
on disk and loads them. If not, the datasets will be regenerated by evaluating the given expression.

To force recalculation, call `YAXArrays.recalculate(true)` before evaluating the macro.

### Example

The following lines will check if cubes with the names "Filled" and "Normalized"
exist on disk, load them and assign the variable names `cube_filled` and `cube_norm`.
If the datasets to not exist on disk, they are generated and saved under the given names.

````julia
@loadOrGenerate cube_filled=>"Filled" cube_norm=>"Normalized" begin
cube_filled = mapCube(gapFillMSC,d)
cube_norm   = mapCube(normalize_TS,d)
end

````
"""
macro loadOrGenerate(x...)
  code=x[end]
  x=x[1:end-1]
  x2=map(x) do i
    isa(i,Symbol) ? (i,string(i)) : (i.head==:call && i.args[1]==:(=>)) ? (i.args[2],i.args[3]) : error("Wrong Argument type")
  end
  xnames=map(i->i[2],x2)
  loadEx=map(x2) do i
    :($(i[1]) = loadcube($(i[2])))
  end
  loadEx=Expr(:block,loadEx...)
  saveEx=map(x2) do i
    :(savecube($(i[1]),$(i[2])))
  end
  saveEx=Expr(:block,saveEx...)
  rmEx=map(x2) do i
    :(rmcube($(i[2])))
  end
  rmEx=Expr(:block,rmEx...)
  esc(quote
    if !YAXArrays.recalculate() && all(i->isdir(joinpath(YAXdir(),i)),$xnames)
      $loadEx
    else
      $rmEx
      $code
      $saveEx
    end
  end)
end

# Here we define ouur own reexport macro copied from
# https://github.com/simonster/Reexport.jl/blob/master/src/Reexport.jl

macro reexport(ex)
    isa(ex, Expr) && (ex.head == :module ||
                      ex.head == :using ||
                      (ex.head == :toplevel &&
                       all(e->isa(e, Expr) && e.head == :using, ex.args))) ||
        error("@reexport: syntax error")

    if ex.head == :module
        modules = Any[ex.args[2]]
        ex = Expr(:toplevel, ex, :(using .$(ex.args[2])))
    elseif ex.head == :using && all(e->isa(e, Symbol), ex.args)
        modules = Any[ex.args[end]]
    elseif ex.head == :using && ex.args[1].head == :(:)
        symbols = [e.args[end] for e in ex.args[1].args[2:end]]
        return esc(Expr(:toplevel, ex, :(eval(Expr(:export, $symbols...)))))
    else
        modules = Any[e.args[end] for e in ex.args]
    end

    esc(Expr(:toplevel, ex,
             [:(eval(Expr(:export, names($mod)...))) for mod in modules]...))
end

end
