"""
`module  JAC.PhotoDoubleIonization`
    ... a submodel of JAC that contains all methods for computing double Auger and autoionization amplitudes and rates.
"""
module PhotoDoubleIonization

    using Printf, JAC, ..AngularMomentum, ..AtomicState, ..Basics, ..ManyElectron, ..PhotoIonization, ..TableStrings

    """
    `struct  PhotoDoubleIonization.Settings`  ... defines a type for the settings in estimating single-photon double-ionization cross sections.

        + multipoles              ::Array{EmMultipole}      ... Specifies the multipoles of the radiation field that are to be included.
        + gauges                  ::Array{UseGauge}         ... Specifies the gauges to be included into the computations.
        + photonEnergies          ::Array{Float64,1}        ... List of photon energies.
        + green                   ::Array{GreenChannel,1}   ... Precalculated and user-specified Green function of the ion.
        + NoEnergySharings        ::Int64                   ... Number of energy sharings that are used in the computations for each line.
        + calcAnisotropy          ::Bool                    ... True, if the beta anisotropy parameters are to be calculated and false o/w.
        + printBefore             ::Bool                    ... True, if all energies and lines are printed before their evaluation.
        + maxKappa                ::Int64                   ... Maximum kappa value of partial waves to be included.
        + lineSelection           ::LineSelection           ... Specifies the selected levels, if any.
    """
    struct Settings
        multipoles                ::Array{EmMultipole}
        gauges                    ::Array{UseGauge}
        photonEnergies            ::Array{Float64,1}
        green                     ::Array{GreenChannel,1}
        NoEnergySharings          ::Int64
        calcAnisotropy            ::Bool
        printBefore               ::Bool
        maxKappa                  ::Int64
        lineSelection             ::LineSelection
    end

    """
    `PhotoDoubleIonization()`  ... constructor for the default values of PhotoDoubleIonization line computations
    """
    function Settings()
        Settings(EmMultipole[E1], UseGauge[UseCoulomb, UseBabushkin], Float64[], GreenChannel[], 0, false, false, 0, LineSelection())
    end

    # `Base.show(io::IO, settings::PhotoDoubleIonization.Settings)`  ... prepares a proper printout of the variable settings::PhotoDoubleIonization.Settings.
    function Base.show(io::IO, settings::PhotoDoubleIonization.Settings)
        println(io, "multipoles:                   $(settings.multipoles)  ")
        println(io, "gauges:                       $(settings.gauges)  ")
        println(io, "photonEnergies:               $(settings.photonEnergies)  ")
        println(io, "green:                         (settings.green)  ")
        println(io, "NoEnergySharings:             $(settings.NoEnergySharings)  ")
        println(io, "calcAnisotropy:               $(settings.calcAnisotropy)  ")
        println(io, "printBefore:                  $(settings.printBefore)  ")
        println(io, "lineSelection:                $(settings.lineSelection)  ")
    end

    """
    `struct  PhotoDoubleIonization.ReducedChannel`
        ... defines a type for a PhotoDoubleIonization (reduced) channel to help characterize a scattering (continuum) state of many
            electron-states with two free electrons.

        + multipole      ::EmMultipole          ... Multipole of the photon absorption.
        + gauge          ::EmGauge              ... Gauge for dealing with the (coupled) radiation field.
        + omega          ::Float64              ... photon energy
        + energy1        ::Float64              ... energy of the partial wave_1
        + kappa1         ::Int64                ... partial-wave of the free electron_1
        + phase1         ::Float64              ... phase of the partial wave_1
        + xSymmetry      ::LevelSymmetry        ... angular momentum and parity after coupling electron1.
        + energy2        ::Float64              ... energy of the partial wave_2
        + kappa2         ::Int64                ... partial-wave of the free electron_2
        + phase2         ::Float64              ... phase of the partial wave_2
        + tSymmetry      ::LevelSymmetry        ... total angular momentum and parity of the scattering state
        + amplitude      ::Complex{Float64}     ... Auger amplitude associated with the given channel.
    """
    struct  ReducedChannel
        multipole        ::EmMultipole
        gauge            ::EmGauge
        omega            ::Float64
        energy1          ::Float64
        kappa1           ::Int64
        phase1           ::Float64
        xSymmetry        ::LevelSymmetry
        energy2          ::Float64
        kappa2           ::Int64
        phase2           ::Float64
        tSymmetry        ::LevelSymmetry
        amplitude        ::Complex{Float64}
    end

    # `Base.show(io::IO, channel::PhotoDoubleIonization.ReducedChannel)`
    #       ... prepares a proper printout of the variable cannel::PhotoDoubleIonization.ReducedChannel.
    function Base.show(io::IO, channel::PhotoDoubleIonization.ReducedChannel)
        ## sa = "reduced DA channel for incoming photon with  " *
        ##      "kappa1=$(channel.kappa1), energy1=$(channel.energy1), phase1=$(channel.phase1), J^P_x=(channel.xSymmetry), " *
        ##     "kappa2=$(channel.kappa2), energy2=$(channel.energy2), phase2=$(channel.phase2), J^P_t=(channel.tSymmetry), amp=$(channel.amplitude) "
        ## println(io, sa)
        println(io, "multipole:                    $(channel.multipole)  ")
        println(io, "gauge:                        $(channel.gauge)  ")
        println(io, "omega:                        $(channel.omega)  ")
        println(io, "energy1:                      $(channel.energy1)  ")
        println(io, "kappa1:                       $(channel.kappa1)  ")
        println(io, "phase1:                       $(channel.phase1)  ")
        println(io, "xSymmetry:                    $(channel.xSymmetry)  ")
        println(io, "energy2:                      $(channel.energy2)  ")
        println(io, "kappa2:                       $(channel.kappa2)  ")
        println(io, "phase2:                       $(channel.phase2)  ")
        println(io, "tSymmetry:                    $(channel.tSymmetry)  ")
        println(io, "amplitude:                    $(channel.amplitude)  ")
    end

    """
    `struct  PhotoDoubleIonization.Sharing`
        ... defines a type for a PhotoDoubleIonization sharing to help characterize energy sharing between the two emitted electrons.

        + omega          ::Float64         ... Energy of the incident photon
        + epsilon1       ::Float64         ... Energy of (free) electron 1.
        + epsilon2       ::Float64         ... Energy of (free) electron 2.
        + weight         ::Float64         ... Gauss-Lengendre weight of this sharing for energy-integrated quantities.
        + differentialCs ::EmProperty      ... differential cross section of this energy sharing.
        + channels       ::Array{PhotoDoubleIonization.ReducedChannel,1}  ... List of PhotoDoubleIonization (reduced) channels of this line.
    """
    struct  Sharing
        omega            ::Float64
        epsilon1         ::Float64
        epsilon2         ::Float64
        weight           ::Float64
        differentialCs   ::EmProperty
        channels         ::Array{PhotoDoubleIonization.ReducedChannel,1}
    end

    # `Base.show(io::IO, sharing::PhotoDoubleIonization.Sharing)`  ... prepares a proper printout of the variable sharing::PhotoDoubleIonization.Sharing.
    function Base.show(io::IO, sharing::PhotoDoubleIonization.Sharing)
        println(io, "omega:                  $(sharing.omega)  ")
        println(io, "epsilon1:               $(sharing.epsilon1)  ")
        println(io, "epsilon2:               $(sharing.epsilon2)  ")
        println(io, "weight:                 $(sharing.weight)  ")
        println(io, "differentialCs:         $(sharing.differentialCs)  ")
        println(io, "channels:               $(sharing.channels)  ")
    end

    """
    `struct  PhotoDoubleIonization.Line`  ... defines a type for a double Auger line that includes sharings and their reduced amplitudes.

        + initialLevel   ::Level            ... initial-(state) level
        + finalLevel     ::Level            ... final-(state) level
        + omega          ::Float64          ... photon energy of this line
        + totalCs        ::EmProperty       ... Total rate of this line.
        + sharings       ::Array{PhotoDoubleIonization.Sharing,1}  ... List of PhotoDoubleIonization sharings of this line.
    """
    struct  Line
        initialLevel     ::Level
        finalLevel       ::Level
        omega            ::Float64
        totalCs          ::EmProperty
        sharings         ::Array{PhotoDoubleIonization.Sharing,1}
    end

    # `Base.show(io::IO, line::PhotoDoubleIonization.Line)`  ... prepares a proper printout of the variable line::PhotoDoubleIonization.Line.
     function Base.show(io::IO, line::PhotoDoubleIonization.Line)
        println(io, "initialLevel:           $(line.initialLevel)  ")
        println(io, "finalLevel:             $(line.finalLevel)  ")
        println(io, "omega:                  $(line.omega)  ")
        println(io, "totalCs:                $(line.totalCs)  ")
        println(io, "sharings:               $(line.sharings)  ")
    end

    """
    `PhotoDoubleIonization.computeAmplitudesProperties(line::PhotoDoubleIonization.Line, grid::Radial.Grid, settings::PhotoDoubleIonization.Settings)`
        ... to compute all amplitudes and properties of the given line; a line::PhotoDoubleIonization.Line is returned for which the amplitudes
            and properties have now been evaluated.
    """
    function  computeAmplitudesProperties(line::PhotoDoubleIonization.Line, grid::Radial.Grid, settings::PhotoDoubleIonization.Settings)
        newSharings = PhotoDoubleIonization.Sharing[]
        for sharing in line.sharings
            newChannels = PhotoDoubleIonization.ReducedChannel[]
            for ch in sharing.channels
                # Generate a continuum orbital
                phase     = 1.3
                amplitude = 1.0im
                push!( newChannels, ReducedChannel(ch.multipole, ch.gauge, ch.omega, ch.energy1, ch.kappa1, phase, ch.xSymmetry,
                                                                                ch.energy2, ch.kappa2, phase, ch.tSymmetry, amplitude) )
            end
            # Calculate the differential cross section
            diffCs = EmProperty(-1., -1.)
            push!( newSharings, PhotoDoubleIonization.Sharing( sharing.omega, sharing.epsilon1, sharing.epsilon2, sharing.weight, diffCs, newChannels) )
        end
        # Calculate the total cross section
        totalCs = EmProperty(-1., -1.)
        line = PhotoDoubleIonization.Line( line.initialLevel, line.finalLevel, line.omega, totalCs, newSharings)
        return( line )
    end

    """
    `PhotoDoubleIonization.computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                                       settings::PhotoDoubleIonization.Settings; output=true)`
        ... to compute the double-Auger transition amplitudes and all properties as requested by the given settings. A list of
            lines::Array{PhotoDoubleIonization.Lines} is returned.
    """
    function  computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                           settings::PhotoDoubleIonization.Settings; output=true)
        println("")
        printstyled("PhotoDoubleIonization.computeLines(): The computation of single-photon double-ionization cs starts now ... \n", color=:light_green)
        printstyled("---------------------------------------------------------------------------------------------------------- \n", color=:light_green)
        println("")
        #
        lines = PhotoDoubleIonization.determineLines(finalMultiplet, initialMultiplet, settings)
        # Display all selected lines before the computations start
        if  settings.printBefore    PhotoDoubleIonization.displayLines(lines)    end
        # Calculate all amplitudes and requested properties
        newLines = PhotoDoubleIonization.Line[]
        for  line in lines
            newLine = PhotoDoubleIonization.computeAmplitudesProperties(line, grid, settings)
            push!( newLines, newLine)
        end
        # Print all results to screen
        PhotoDoubleIonization.displayTotalCrossSections(stdout, lines, settings)
        PhotoDoubleIonization.displayDifferentialCrossSections(stdout, lines, settings)
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        if  printSummary   PhotoDoubleIonization.displayTotalCrossSections(iostream, lines, settings)
                           PhotoDoubleIonization.displayDifferentialCrossSections(iostream, lines, settings)     end
        #
        if    output    return( lines )
        else            return( nothing )
        end
    end

    """
    `PhotoDoubleIonization.determineLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, settings::PhotoDoubleIonization.Settings)`
        ... to determine a list of PhotoDoubleIonization.Line's for transitions between levels from the initial- and final-state multiplets,
            and  by taking into account the particular selections and settings for this computation; an Array{PhotoDoubleIonization.Line,1} is
            returned. Apart from the level specification and sharing, all physical properties are set to zero during the initialization process.
    """
    function  determineLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, settings::PhotoDoubleIonization.Settings)
        lines = PhotoDoubleIonization.Line[]
        for  iLevel  in  initialMultiplet.levels
            for  fLevel  in  finalMultiplet.levels
                if  Basics.selectLevelPair(iLevel, fLevel, settings.lineSelection)
                    energy   = fLevel.energy - iLevel.energy
                    for  omega in settings.photonEnergies
                        channels = PhotoDoubleIonization.determineSharingsAndChannels(fLevel, iLevel, omega, energy, settings)
                        push!( lines, PhotoDoubleIonization.Line(iLevel, fLevel, omega, EmProperty(0., 0.,), channels) )
                    end
                end
            end
        end
        return( lines )
    end

    """
    `PhotoDoubleIonization.determineSharingsAndChannels(finalLevel::Level, initialLevel::Level, omega::Float64, energy::Float64,
                                                        settings::PhotoDoubleIonization.Settings)`
        ... to determine a list of PhotoDoubleIonization Sharing's and Channel's for a transitions from the initial to final level and by taking into
            account the particular settings of for this computation; an Array{PhotoDoubleIonization.Sharing,1} is returned.
    """
    function determineSharingsAndChannels(finalLevel::Level, initialLevel::Level, omega::Float64, energy::Float64, settings::PhotoDoubleIonization.Settings)
        sharings = PhotoDoubleIonization.Sharing[];    eSharings = Basics.determineEnergySharings(energy, settings.NoEnergySharings)
        for  es in eSharings
            epsilon1  = es[1];    epsilon2 = es[2];    weight = es[3]
            channels  = PhotoDoubleIonization.ReducedChannel[];
            symi      = LevelSymmetry(initialLevel.J, initialLevel.parity);    symf = LevelSymmetry(finalLevel.J, finalLevel.parity)
            for  mp in settings.multipoles
                for  gauge in settings.gauges
                    symList = AngularMomentum.allowedMultipoleSymmetries(symi, mp)
                    for  symt in symList
                        couplings = AngularMomentum.allowedDoubleKappaCouplingSequence(symf, symt, settings.maxKappa)
                        for  coupling  in  couplings
                            # Include further restrictions if appropriate
                            if     string(mp)[1] == 'E'  &&   gauge == Basics.UseCoulomb
                                push!(channels, ReducedChannel(mp, Basics.Coulomb,   omega, epsilon1, coupling[1], 0., coupling[2],
                                                                                epsilon2, coupling[3], 0., symt, Complex(0.)) )
                            elseif string(mp)[1] == 'E'  &&   gauge == Basics.UseBabushkin
                                push!(channels, ReducedChannel(mp, Basics.Babushkin, omega, epsilon1, coupling[1], 0., coupling[2],
                                                                                epsilon2, coupling[3], 0., symt, Complex(0.)) )
                            elseif string(mp)[1] == 'M'
                                push!(channels, ReducedChannel(mp, Basics.Magnetic,  omega, epsilon1, coupling[1], 0., coupling[2],
                                                                                epsilon2, coupling[3], 0., symt, Complex(0.)) )
                            end
                        end
                    end
                end
            end
            push!(sharings, PhotoDoubleIonization.Sharing(omega, epsilon1, epsilon2, weight, EmProperty(0., 0.), channels) )
        end
        return( sharings )
    end

    """
    `PhotoDoubleIonization.displayDifferentialCrossSections(stream::IO, lines::Array{PhotoDoubleIonization.Line,1}, settings::PhotoDoubleIonization.Settings)`
        ... to display all differential rates, etc. of the selected lines. A neat table is printed but nothing is returned otherwise.
    """
    function  displayDifferentialCrossSections(stream::IO, lines::Array{PhotoDoubleIonization.Line,1}, settings::PhotoDoubleIonization.Settings)
        #
        # First, print lines and sharings
        nx = 130
        println(stream, " ")
        println(stream, "  Energy-differential cross sections of selected photo-double ionization lines:")
        println(stream, " ")
        println(stream, "  ", TableStrings.hLine(nx))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=0);                         sb = sb * TableStrings.hBlank(18)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=4);                         sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.flushleft(38, "Energies (all in " * TableStrings.inUnits("energy") * ")"; na=3);
        sb = sb * TableStrings.flushleft(38, "  i -- f        epsilon_1     epsilon_2"; na=3)
        sa = sa * TableStrings.center(14, "Weight"; na=0);                            sb = sb * TableStrings.hBlank(14)
        sa = sa * TableStrings.center(34, "Cou -- diff. cross section -- Bab"; na=3)
        sb = sb * TableStrings.center(34, TableStrings.inUnits("cross section") * "        " *
                                          TableStrings.inUnits("cross section"); na=3)
        println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(nx))
        #
        for  line in lines
            sa  = "";      isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                           fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            energy = line.finalLevel.energy - line.initialLevel.energy
            sa = sa * @sprintf("%.5e", Defaults.convertUnits("energy: from atomic", energy)) * "    "
            #
            for  (is, sharing)  in  enumerate(line.sharings)
                if  is == 1     sb = sa     else    sb = TableStrings.hBlank( length(sa) )    end
                sb = sb * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", sharing.epsilon1))                        * "    "
                sb = sb * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", sharing.epsilon2))                        * "    "
                sb = sb * @sprintf("%.4e",                                              sharing.weight)                           * "       "
                sb = sb * @sprintf("%.5e", Defaults.convertUnits("cross section: from atomic", sharing.differentialCs.Coulomb))   * "   "
                sb = sb * @sprintf("%.5e", Defaults.convertUnits("cross section: from atomic", sharing.differentialCs.Babushkin)) * "   "
                println(stream,  sb )
            end
        end
        println(stream, "  ", TableStrings.hLine(nx))
        #
        return( nothing )
    end

    """
    `PhotoDoubleIonization.displayLines(lines::Array{PhotoDoubleIonization.Line,1})`
        ... to display a list of lines, sharings and channels that have been selected due to the prior settings. A neat table
            of all selected transitions and energies is printed but nothing is returned otherwise.
    """
    function  displayLines(lines::Array{PhotoDoubleIonization.Line,1})
        #
        # First, print lines and sharings
        nx = 94
        println(" ")
        println("  Selected photo-double ionization lines & sharings:")
        println(" ")
        println("  ", TableStrings.hLine(nx))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=0);                         sb = sb * TableStrings.hBlank(18)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=4);                         sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.flushleft(54, "Energies (all in )" * TableStrings.inUnits("energy") * ")"; na=5);
        sb = sb * TableStrings.flushleft(54, "  i -- f         omega        epsilon_1    epsilon_2"; na=5)
        println(sa);    println(sb);    println("  ", TableStrings.hLine(nx))
        #
        for  line in lines
            sa  = "";      isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                           fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            energy = line.finalLevel.energy - line.initialLevel.energy
            sa = sa * @sprintf("%.5e", Defaults.convertUnits("energy: from atomic", energy)) * "    "
            #
            for  (is, sharing)  in  enumerate(line.sharings)
                if  is == 1     sb = sa     else    sb = TableStrings.hBlank( length(sa) )    end
                sb = sb * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", sharing.omega))    * "    "
                sb = sb * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", sharing.epsilon1)) * "   "
                sb = sb * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", sharing.epsilon2)) * "   "
                println( sb )
            end
        end
        println("  ", TableStrings.hLine(nx))
        #
        #
        # Second, print lines and channles
        nx = 150
        println(" ")
        println("  Selected photo-double ionization lines & channels:")
        println(" ")
        println("  ", TableStrings.hLine(nx))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=0);                         sb = sb * TableStrings.hBlank(18)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=4);                         sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.flushleft(100, "Channels (all energies in " * TableStrings.inUnits("energy") * ")" ; na=5);
        sb = sb * TableStrings.flushleft(100, "Multipole  Gauge      omega       (epsilon, kappa)_1   -->    J^P_x   -->  (epsilon, kappa)_2   -->    J^P_t"; na=5)
        println(sa);    println(sb);    println("  ", TableStrings.hLine(nx))
        #
        for  line in lines
            sa  = "";      isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                           fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            for (ic, ch) in enumerate(line.sharings[1].channels)
                if  ic == 1     sb = sa     else    sb = TableStrings.hBlank( length(sa) )    end
                skappa1 = "  " * string(ch.kappa1);         skappa2 = "  " * string(ch.kappa2);
                sxsym   = "      " * string(ch.xSymmetry);  stsym   = "      " * string(ch.tSymmetry)
                sb = sb * string(ch.multipole) * "         " * string(ch.gauge)[1:3] * "      "
                sb = sb * @sprintf("%.3e", Defaults.convertUnits("energy: from atomic", ch.omega)) * "     "
                sb = sb * " (" * @sprintf("%.3e", Defaults.convertUnits("energy: from atomic", ch.energy1)) * ", " * skappa1[end-2:end] * ")    -->"
                sb = sb * sxsym[end-8:end] * "   -->  "
                sb = sb * " (" * @sprintf("%.3e", Defaults.convertUnits("energy: from atomic", ch.energy2)) * ", " * skappa2[end-2:end] * ")    -->"
                sb = sb * stsym[end-8:end]
                println( sb )
            end
        end
        println("  ", TableStrings.hLine(nx))
        #
        return( nothing )
    end

    """
    `PhotoDoubleIonization.displayTotalCrossSections(stream::IO, lines::Array{PhotoDoubleIonization.Line,1}, settings::PhotoDoubleIonization.Settings)`
        ... to display all total rates, etc. of the selected lines. A neat table is printed but nothing is returned otherwise.
    """
    function  displayTotalCrossSections(stream::IO, lines::Array{PhotoDoubleIonization.Line,1}, settings::PhotoDoubleIonization.Settings)
        #
        # First, print lines and sharings
        nx = 103
        println(stream, " ")
        println(stream, "  Total rates of selected photo-double ionization lines:")
        println(stream, " ")
        println(stream, "  ", TableStrings.hLine(nx))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=0);                         sb = sb * TableStrings.hBlank(18)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=2);                         sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.center(12, "f--Energy--i"; na=3)
        sb = sb * TableStrings.center(12,TableStrings.inUnits("energy"); na=2)
        sa = sa * TableStrings.center(12, "omega"     ; na=4)
        sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=5)
        sa = sa * TableStrings.center(30, "Cou --   cross section   -- Bab"; na=4)
        sb = sb * TableStrings.center(30, TableStrings.inUnits("cross section") * "        " *
                                          TableStrings.inUnits("cross section"); na=3)
        println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(nx))
        #
        for  line in lines
            sa  = "";      isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                           fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            energy = line.finalLevel.energy - line.initialLevel.energy
            sa = sa * @sprintf("%.5e", Defaults.convertUnits("energy: from atomic", energy)) * "    "
            #
            sb = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", line.omega))                    * "         "
            sb = sb * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic", line.totalCs.Coulomb))   * "     "
            sb = sb * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic", line.totalCs.Babushkin)) * "   "
            println(stream,  sb )
        end
        println(stream, "  ", TableStrings.hLine(nx))
        #
        return( nothing )
    end

end # module


"""
    simulate(mdl)

    Simulation function

    # Arguments

    # Examples
"""
function simulate(mdl, data::StructVector; feedback = x -> missing, init_θ=mdl())
    initial_state = data.s[1]
    initial_hidden = data.h[1]
    state_transition(history) = data.s[min(length(history)+1, length(data))]
    hidden_transition(history) = data.h[min(length(history)+1, length(data))]
    ending_condition(history) = length(history) > length(data)

    return simulate(mdl;
            state_transition = state_transition,
            hidden_transition = hidden_transition,
            feedback = feedback,
            initial_state = initial_state,
            initial_hidden = initial_hidden,
            ending_condition = ending_condition,
            init_θ = init_θ)
end

function simulate(mdl;
    state_transition = x -> 1,
    feedback = x -> missing,
    hidden_transition = x -> missing,
    initial_state = 1,
    initial_hidden = missing,
    ending_condition = x -> length(x) > 100,
    init_θ = mdl())

    # Initialize
    θ = init_θ
    P_ = AnimalBehavior.observ(mdl, initial_state; θ...)
    a_type = Distributions.eltype(typeof(P_))
    r_type = Base.return_types(feedback, (StructVector,))[1]

    history = StructVector(s=[initial_state], a=Vector{Any}([missing]), r=Vector{Any}([missing]), h=Vector{Any}([initial_hidden]))
    latent = StructVector([deepcopy(θ)])

    while !ending_condition(history)
        # current state
        s = history[end].s

        # action
        P = AnimalBehavior.observ(mdl, s; θ...)
        a = rand(P)
        history.a[end] = a

        # feedback
        r = feedback(history)
        history.r[end] = r

        # update
        AnimalBehavior.evol!(mdl, s, a, r; θ...)

        # next state
        ns = state_transition(history)
        nh = hidden_transition(history)
        push!(history, (s=ns, a=missing, r=missing, h=nh))
        push!(latent, deepcopy(θ))
    end
    data = StructVector(s = history[1:end-1].s,
                        a = convert.(a_type,history[1:end-1].a),
                        r = convert.(r_type, history[1:end-1].r),
                        h = history[1:end-1].h)
    return Simulation(mdl.name, data, latent[1:end-1])
end


__precompile__()
module ATA

using Printf
using CSV
using DelimitedFiles
using DataFrames
using Distributions
using Distributed
using Random
using LinearAlgebra
using JLD2
using StatsBase
using Requires
using Psychometrics
using FileIO

include("structs/structs.jl")
include("log/log.jl")
include("utils.jl")
include("build/build.jl")
include("opt.jl")
include("print/print.jl")
include("compact_ata.jl")

function __init__()
    @require JuMP = "4076af6c-e467-56ae-b986-b466b2749572" include("jump/jump.jl")

    @require Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" begin
        function add_Cbc!(m)
            JuMP.set_optimizer(m, Cbc.Optimizer)
        end
    end
    @require Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b" begin
        function add_Gurobi!(m)
            JuMP.set_optimizer(m, Gurobi.Optimizer)
        end
    end
    @require CPLEX = "a076750e-1247-5638-91d2-ce28b192dca0" begin
        function add_CPLEX!(m)
            JuMP.set_optimizer(m, CPLEX.Optimizer)
        end
    end
    @require GLPK = "60bf3e95-4087-53dc-ae20-288a0d20c6a6" begin
        function add_GLPK!(m)
            JuMP.set_optimizer(m, GLPK.Optimizer)
        end
    end
    @require Xpress = "9e70acf3-d6c9-5be6-b5bd-4e2c73e3e054" begin
        function add_Xpress!(m)
            JuMP.set_optimizer(m, Gurobi.Optimizer)
        end
    end
    @require KNITRO = "67920dd8-b58e-52a8-8622-53c4cffbe346" begin
        function add_KNITRO!(m)
            JuMP.set_optimizer(m, KNITRO.Optimizer)
        end
    end
    @require Juniper = "2ddba703-00a4-53a7-87a5-e8b9971dde84" begin
        function add_Juniper!(m)
            JuMP.set_optimizer(m, Juniper.Optimizer)
        end
    end
    @require SCIP = "bdc5fa1a-e2a0-4ec3-bc77-c59bd234be9e" begin
        function add_SCIP!(m)
            JuMP.set_optimizer(m, SCIP.Optimizer)
        end
    end
    @require ATAPlot = "372623d9-1dd6-4d20-a513-3f20705132c0" begin
        include("plot.jl")
    end
    @require ATADash = "236b7dbe-4167-40cb-a459-bcf8ce4b2cbd" begin
        function ata_app()
            ATADash.ata_app()
        end
    end
end

export mean,
    start_ata,
    add_bank!,
    add_constraints!,
    add_overlap!,
    add_obj_fun!,
    assemble!,
    print_results,
    plot_results,
    print_infos,
    print_last_info,
    load_design!,
    item_info,
    item_char,
    resp_gen,
    student_likelihood,
    fs_to_items,#_mycopy,  fast_sort!, _myqle#, optimize
    ata_app,
    compact_ata

ATA

end


__precompile__()

module AxisArrayVariables

using JuMP
using AxisArrays
using MacroTools

export @axis_variable

# Keyword arguments use :(=) on v0.6 and :kw on v0.5, and their escaping rules
# are slightly different:
@static if VERSION >= v"0.6-"
    make_kwarg(key, val) = Expr(:(=), esc(key), esc(val))
else
    make_kwarg(key, val) = Expr(:kw, key, esc(val))
end

macro axis_variable(m, varexpr, args...)
    jump_args = Expr[esc(e) for e in args]
    (var, axis_args, lb, ub) = @match varexpr begin
        (var_[axis_args__] |
        (var_[axis_args__] <= ub_) |
        (var_[axis_args__] >= lb_) |
        (lb_ <= var_[axis_args__] <= ub_)) => (var, axis_args, lb, ub)
    end
    if var === nothing || axis_args === nothing
        error("Unrecognized expression")
    end
    if lb !== nothing
        unshift!(jump_args, make_kwarg(:lowerbound, lb))
    end
    if ub !== nothing
        unshift!(jump_args, make_kwarg(:upperbound, ub))
    end
    axes = []
    domains = Expr[]
    for arg in axis_args
        if @capture(arg, name_ = domain_)
            push!(axes, Expr(:call, Expr(:curly, :Axis, QuoteNode(name)), esc(domain)))
            push!(domains, esc(domain))
        else
            push!(axes, esc(arg))
            push!(domains, esc(arg))
        end
    end
    range_exprs = [:(1:length($a)) for a in domains]
    quote
        ranges = $(Expr(:vect, range_exprs...))
        vars = let
            local $(esc(var))
            $(Expr(:macrocall, Symbol("@variable"), esc(m), Expr(:ref, esc(var), range_exprs...), jump_args...))
        end
        $(esc(var)) = $(Expr(:call, :AxisArray, :vars, axes...))
    end
end

end


module RayTracer

using Zygote, Flux, Images, Distributed, Statistics
import Base.show

using Compat
import Compat.isnothing

# Rendering Utilities
include("utils.jl")
include("light.jl")
include("materials.jl")
include("objects.jl")
include("camera.jl")
include("optimize.jl")

# Acceleration Structures
include("bvh.jl")

# Renderers
include("renderers/blinnphong.jl")
include("renderers/rasterizer.jl")
include("renderers/accelerated_raytracer.jl")

# Image Utilities
include("imutils.jl")

# Differentiable Rendering
include("gradients/zygote.jl")
include("gradients/numerical.jl")

end


immutable HLineGeometry <: Gadfly.GeometryElement
    color::Union(ColorValue, Nothing)
    size::Union(Measure, Nothing)

    function HLineGeometry(; color=nothing,
                           size::Union(Measure, Nothing)=nothing)
        new(color === nothing ? nothing : Color.color(color),
            size)
    end
end

const hline = HLineGeometry

function element_aesthetics(::HLineGeometry)
    [:yintercept]
end

# Generate a form for the hline geometry
function render(geom::HLineGeometry, theme::Gadfly.Theme, aes::Gadfly.Aesthetics)
    Gadfly.assert_aesthetics_defined("Geom.hline", aes, :yintercept)

    color = geom.color === nothing ? theme.default_color : geom.color
    size = geom.size === nothing ? theme.line_width : geom.size

    return compose!(
        context(),
        Compose.line([[(0w, y), (1w, y)] for y in aes.yintercept]),
        stroke(color),
        linewidth(size),
        svgclass("xfixed"))
end


using GAP, Hecke, Printf, ArgParse

include(joinpath(Hecke.pkgdir,"examples/FieldEnumeration/FieldEnumeration.jl"))

###############################################################################
#
#  Read-Write
#
###############################################################################

function ArgParse.parse_item(::Type{fmpz}, x::AbstractString)
  if in('^', x)
    l = split(x, '^')
    if length(l) != 2
      throw(error("Could not parse $x as fmpz"))
    end
    l = string.(l)
    return (parse(fmpz, l[1]))^parse(Int, l[2])
  else
    return parse(fmpz, string(x))
  end
end

function parse_commandline()
  s = ArgParseSettings()

  @add_arg_table s begin
    "--order", "-o"
      help = "Order of the group"
      arg_type = Int
      default = -1
    "--id", "-i"
      help = "Id of the group"
      arg_type = Int
      default = -1
    "--number", "-n"
      help = "Number of the group"
      arg_type = Int
      default = -1
    "--tamely-ramified"
      help = "Only tamely ramified fields"
      action = :store_true
    "--only-real"
      help = "Only totally real fields"
      action = :store_true
    "--only-complex"
      help = "Only totally complex fields"
      action = :store_true
    "--disc-bound"
      help = "Discriminant bound"
      arg_type = fmpz
    "--rootdisc-bound"
      help = "Discriminant bound"
      arg_type = Float64
    "--only-cm"
      help = "Only CM fields"
      action = :store_true
    "--max-ab-subfields"
      help = "File containing maximal abelian subextensions"
    "--simplify"
      help = "Simplify the field"
      action = :store_true
    "--output"
      help = "Output file"
      arg_type = String
      default = ""
    "--output-type"
      help = "Type of output file. Options are default (uses _write_fields), polys, nfdb"
      default = "default"
    "--silent"
      help = "No verbose out"
      action = :store_true
  end

  return parse_args(s)
end

function main()
  parsed_args = parse_commandline()

  local grp_order::Int
  local grp_id::Int
  local dbound::Union{fmpz, Nothing}
  local only_real::Bool
  local only_complex::Bool
  local only_tame::Bool
  local grp_no::Int
  local only_cm::Bool
  local maxabsubfields::Union{String, Nothing}
  local simplify::Bool
  local output::String
  local rdbound::Union{Float64, Nothing}
  local silent::Bool

  for (arg, val) in parsed_args
    #println("$arg => $val")
    if arg == "order"
      grp_order = val
    elseif arg == "id"
      grp_id = val
    elseif arg == "disc-bound"
      dbound = val
    elseif arg == "rootdisc-bound"
      rdbound = val
    elseif arg == "only-real"
      only_real = val
    elseif arg == "only-complex"
      only_complex = val
    elseif arg == "tamely-ramified"
      only_tame = val
    elseif arg == "number"
      grp_no = val
    elseif arg == "only-cm"
      only_cm = val
    elseif arg == "max-ab-subfields"
      maxabsubfields = val
    elseif arg == "simplify"
      simplify = val
    elseif arg == "silent"
      silent = val
    elseif arg == "output"
      output = val
    end
  end

  sprint_formatted(fmt, args...) = @eval @sprintf($fmt, $(args...))

  if grp_id != -1
    n = grp_order
    i = grp_id
  end

  if !silent
    @info "Small group ($n, $i)"

    if dbound !== nothing
      @info "Discriminant bound $dbound"
    end

    if rdbound !== nothing
      @info "Root discriminant bound $rdbound"
    end

    @info "Simplifying fields: $simplify"

    if output == ""
      output = "fields_$(i)_$(n).nfdb"
    end

    if isfile(output)
      error("Output file $output already exists. You may specify the output file by setting --output")
    end

    @info "Maximal abelian fields: $maxabsubfields"
    @info "Only real fields: $only_real"
    @info "Only complex fields: $only_complex"
    @info "Only CM: $only_cm"

    @info "Output to: $output"

    set_verbose_level(:FieldsNonFancy, 3)
  end

  if dbound == nothing && rdbound == nothing
    error("One of --disc-bound or --rootdisc-bound must be specified")
  elseif dbound != nothing && rdbound != nothing
    error("Only one of --disc-bound or --rootdisc-bound can be set")
  end

  if only_complex && only_real
    error("Only one of --only-real or --only-complex can be set")
  end

  if dbound === nothing
    dbound = fmpz(BigInt(ceil(BigFloat(rdbound)^(n))))
    if !silent
      @info "Translated root discriminant bound to: $dbound"
    end
  end

  if grp_id != -1
    @assert grp_order != -1
    n = grp_order
    i = grp_id
    grp_no = findfirst(isequal((n, i)), small_solvable_groups)
  else
    @assert grp_no != -1
    n, i = small_solvable_groups[grp_no]
  end

  flush(stdout)

  if maxabsubfields isa String
    maxabsub = Hecke._read_from_file(maxabsubfields)
    l = fields(n, i, maxabsub, dbound, only_real = only_real)
  else
    l = fields(n, i, dbound, only_real = only_real)
  end

  flush(stdout)

  # Determine the automorphism groups
  if only_cm
    Hecke.assure_automorphisms.(l)
  end

  flush(stdout)

  ll = map(v -> v.field, l)

  flush(stdout)

  if only_cm
    ffields = [ (x, discriminant(maximal_order(x))) for x in ll if Hecke.iscm_field(x)[1]]
  else
    ffields = [ (x, discriminant(maximal_order(x))) for x in ll ]
  end

  flush(stdout)

  sort!(ffields, lt = (x, y) -> abs(x[2]) <= abs(y[2]))

  res = eltype(Hecke.NFDB{1})[]

  for (x, y) in ffields
    if simplify
      x, _ = Hecke.simplify(x)
    end
    r = Hecke._create_record(x)
    r[:discriminant] = y
    push!(res, r)
  end

  DBout = Hecke.NFDB(res)
  t = "fields($n, $i, $dbound, only_real = $only_real, simplify = $simplify)"
  if rdbound !== nothing
    t = t * " (rd bound $rdbound)"
  end
  if maxabsubfields isa String
    t = t * " with maximal abelian subfields from $maxabsubfields"
  end
  Hecke.add_meta!(DBout, :title => t)
  open(output, "w") do f
    Base.write(f, DBout)
  end
end

main()


module EIIndex

using LightGraphs
using LightGraphs.SimpleGraphs
using Plots: histogram, vline!
import Plots: plot

export ei_global, ei_group, ei_indiv, cugtest_edges, plot

include("ei_index.jl")
include("cugtest.jl")

end#module


# invert the ITMIX 2 with IV for ESA

include("itmix-setup.jl") # run this first on single process to make sure all precompilation this through
                          # before the parallel run starts

if nprocs()<2
    if Sys.CPU_CORES>=17
        addprocs(17) # /2 to get to physical cores
    else
        addprocs(Sys.CPU_CORES÷2 + 1) # /2 to get to physical cores
    end
end

@everywhere begin
    const debugging = true
    include("itmix-setup.jl")
end

# whether doing a testing, semi-testing or production run
runtyp = ["test", "testmid", "prodlow", "prod"][3]
runtyp == :test && println("\n\nTEST RUN !!!!!!!!\n\n")

fit_target = [BM.FitTarget.h, BM.FitTarget.h_iv, BM.FitTarget.length][2]  # making this a global to avoid below odd bug.
pl_kws = Dict{Symbol,Any}()

dir = "results_Werder/esa-14nov"
skip_iv_point_measurements = false

for gid in BM.ITMIXGlacier.(BM.itmix_glaciers_iv, 2)
    if skip_iv_point_measurements && gid in BM.ITMIXGlacier.(BM.itmix_glaciers_iv_point, 2)
        continue
    end
    println("\n\n============================  Running Glacier $(BM.getname(gid))")
    for rm_iv in [true, false]
        println("\n\n============================  rm_iv = $(rm_iv)")
        println("\n\n============================")

        gl,gb,pp,pm,pn,pl = BM.init_forward(gid; pl_kws...);

        if nprocs()>1
            pmap(expnr->run_one_exp(expnr, gl, gb, pp, pm, pn, pl, runtyp, fit_target; rm_iv=rm_iv, dir=dir), 0:16)
        else
            map(expnr->run_one_exp(expnr, gl, gb, pp, pm, pn, pl, runtyp, fit_target; rm_iv=rm_iv, dir=dir), 0:16)
        endI==1 ? true : false
    end
end


export AbstractVar, Var, SubseqVar, Ignore
export @V_str
export AbstractBindings, EmptyBindings, Bindings
export lookupfirst, lookupall, lookupequiv, lookup
export ubind, toDict

abstract type AbstractVar end

"""
A Unification vatiable.
"""
struct Var <: AbstractVar
    name::Symbol

    Var(name::AbstractString) = Var(Symbol(name))
    Var(name::Symbol) = new(name)
end

# Do we need to include some notion of scope?
function same(var1::AbstractVar, var2::AbstractVar)::Bool
    typeof(var1) == typeof(var2) &&
        var1.name == var2.name
end

same(var1::AbstractVar, ::Any) = false
same(::Any, Var1::AbstractVar) = false

"""
A Unification variable for subsequences.
The associated value will be a SubArray.
The presence of more than one SubseqVar in
the same sequence allows for multiple
unifications, and combiniatorial explosion
in finding them.
"""
struct SubseqVar <: AbstractVar
    name::Symbol

    SubseqVar(name::AbstractString) = SubseqVar(Symbol(name))
    SubseqVar(name::Symbol) = new(name)
end

"""
Ignore match3es anything but captures nothing.
"""
struct Ignore end

macro V_str(name)
    @assert name isa AbstractString
    if length(name) == 0
        :(Ignore())
    elseif endswith(name, "...")
        :(SubseqVar($name))
    else
        :(Var($name))
    end
end

abstract type AbstractBindings end

"""
The endmost tail of a bindings chain.
"""
struct EmptyBindings <: AbstractBindings
end

Base.iterate(::EmptyBindings) = nothing
Base.iterate(::AbstractBindings, ::EmptyBindings) = nothing

Base.IteratorSize(::Type{<:AbstractBindings}) = Base.SizeUnknown()
Base.IteratorEltype(::Type{<:AbstractBindings}) = Tuple{AbstractVar, Any}

struct Bindings <: AbstractBindings
    var::AbstractVar
    val::Any
    tail::AbstractBindings

    Bindings(var::AbstractVar, val::Any, tail::AbstractBindings) =
        new(var, val, tail)
end

Base.iterate(b::Bindings, state::Bindings=b) = (state.var, state.val), state.tail

"""
    lookupfirst(::AbstractBindings, ::AbstractVar)
Return the `val` of the first entry in the `Bindings` about the`AbstractVar`.
"""
function lookupfirst(bindings::AbstractBindings, var::AbstractVar)
    for (v, val) in bindings
        @debug "$v, $val"
        if same(v, var)
            return val, true
        end
        # Binding a var to another var is symetric:
        if same(val, var)
            return v, true
        end
    end
    return nothing, false
end

"""
    lookupall(::Bindings, ::AbstractVar)::Set{Any}
Return a `Vector` of all of the `val`s in the `Bindings` which
correspond with the specified `AbstractVar`.
"""
function lookupall(bindings::AbstractBindings, var::AbstractVar)::Set{Any}
    found = Set{Any}()
    for (v, val) in bindings
        if same(v, var)
            push!(found, val)
        elseif same(val, var)
            # Binding a variable to a variable is symetric:
            push!(found, v)
        end
    end
    return found
end

"""
    lookupequiv(::Bindings, ::AbstractVar)
Return a `Set` of all of the values associated with the `AbstractVar`
and another Set of all equivalent (transitively bound) `AbstractVar`s.
"""
function lookupequiv(bindings::AbstractBindings, var::AbstractVar)
    found = Set{Any}()
    queue = Vector{AbstractVar}()
    push!(queue, var)
    done = Set{AbstractVar}()
    while !isempty(queue)
        var = pop!(queue)
        if var in done    ##### Probably using == rather than same
            continue
        end
        push!(done, var)
        vals = lookupall(bindings, var)
        # With Set I don't think we can skip over the ones already returned.
        for val in vals
            if val isa AbstractVar
                push!(queue, val)
            else
                push!(found, val)
            end
        end
    end
    return found, done
end

"""
    lookup(bindings::AbstractBindings, var::AbstractVar)
Return the value assoiciated with `var` in `bindings`.
The second return value is `true` if a unique value is found
and `false` otherwise.
"""
function lookup(bindings::AbstractBindings, var::AbstractVar)
    values, _ = lookupequiv(bindings, var)
    if length(values) == 1
        return first(values), true
    end
    return nothing, false
end

"""
    ubind(continuation, var::AbstractVar, val::Any, [::AbstractBindings])
Call `continuation` with the binding of `var` to `val` added
to `bindings`.
"""
function ubind(continuation, var::AbstractVar, value::Any,
               # Maybe we shouldn't default bindings.  FOrgetting to
               # include bindings in a call to ubind seems to be a
               # common source of errors.
               bindings::AbstractBindings = EmptyBindings())
    continuation(Bindings(var, value, bindings))
end

"""
    ubind(continuation, pairs::Vector{Pair{<:AbstractVar, <:Any}}, ::AbstractBindings=EmptyBindings())
Call `continuation` on a chain of `Bindings` where the `Pair`s are added to
the head of the chain such that the first pair is at the
head of the chain.
"""
function ubind(continuation, pairs::Vector{<:Pair{<:AbstractVar, <:Any}},
               bindings::AbstractBindings=EmptyBindings())
    for i = lastindex(pairs) : -1 :firstindex(pairs)
        p = pairs[i]
        bindings = Bindings(p.first, p.second, bindings)
    end
    continuation(bindings)
end

#=
function toDict(bindings::AbstractBindings)::Dict{AbstractVar, Any}
    d = Dict{AbstractVar, Any}()
    b = bindings
    while true
        if haskey(d, b.var)
            if d[b.var] != b.val
                throw(ErrorException(
                    "More than one distinct value for $(b.var): $(d[b.var]), $(b.val)"))
            end
        else
            d[b.var] = b.val
        end
    end
    return d
end
=#


immutable ArgsIndex
    index::Dict{Symbol, Int}
    args::Vector{Set{Symbol}}
end
ArgsIndex() = ArgsIndex(Dict{Symbol,Int}(), Vector{Set{Symbol}}())

Base.getindex(ai::ArgsIndex, t::Symbol) = ai.args[ai.index[t]]
function Base.setindex!(ai::ArgsIndex, s, t::Symbol)
    i = get!(ai.index, t, length(ai.index)+1)
    i == length(ai.index) ? push!(ai.args, s) : (ai.args[i] = s)
    return s
end
Base.get!(ai::ArgsIndex, t::Symbol) = haskey(ai.index, t) ? (ai[t]) : (ai[t] = Set{Symbol}())

"""
    abstract QueryHelper

Leaf subtypes `T <: QueryHelper` contain data extracted from query arguments
and stored in fields of `T` as resources for collection machineries.
"""
abstract Verb

immutable Select{F} <: Verb
    res_field::Symbol
    f::F
    ai::ArgsIndex
end
immutable Filter{F} <: Verb
    f::F
    ai::ArgsIndex
end
immutable OrderBy{F} <: Verb
    f::F
    ai::ArgsIndex
end
immutable GroupBy{F} <: Verb
    is_predicate::Bool
    f::F
    ai::ArgsIndex
end
immutable Summarize{F,G} <: Verb
    res_field::Symbol
    f::F
    g::G
    ai::ArgsIndex
end
immutable LeftJoin{F,G} <: Verb
    fs::Tuple{F,G}
    ais::Tuple{ArgsIndex,ArgsIndex}
end
immutable OuterJoin{F,G} <: Verb
    fs::Tuple{F,G}
    ais::Tuple{ArgsIndex,ArgsIndex}
end
immutable InnerJoin{F,G} <: Verb
    fs::Tuple{F,G}
    ais::Tuple{ArgsIndex,ArgsIndex}
end
immutable CrossJoin{F,G} <: Verb
    fs::Tuple{F,G}
    ais::Tuple{ArgsIndex,ArgsIndex}
end


using Test
using ZXCalculus
using YaoHIR, YaoLocations
using YaoHIR.IntrinsicOperation
using CompilerPluginTools

chain = Chain()
push_gate!(chain, Val(:Sdag), 1)
push_gate!(chain, Val(:Z), 1)
push_gate!(chain, Val(:H), 1)
push_gate!(chain, Val(:S), 1)
push_gate!(chain, Val(:S), 2)
push_gate!(chain, Val(:H), 4)
push_gate!(chain, Val(:CNOT), 3, 2)
push_gate!(chain, Val(:CZ), 4, 1)
push_gate!(chain, Val(:H), 2)
push_gate!(chain, Val(:T), 2)
push_gate!(chain, Val(:CNOT), 3, 2)
push_gate!(chain, Val(:Tdag), 2)
push_gate!(chain, Val(:CNOT), 1, 4)
push_gate!(chain, Val(:H), 1)
push_gate!(chain, Val(:T), 2)
push_gate!(chain, Val(:S), 3)
push_gate!(chain, Val(:H), 4)
push_gate!(chain, Val(:T), 1)
push_gate!(chain, Val(:T), 2)
push_gate!(chain, Val(:T), 3)
push_gate!(chain, Val(:X), 3)
push_gate!(chain, Val(:H), 2)
push_gate!(chain, Val(:H), 3)
push_gate!(chain, Val(:Sdag), 4)
push_gate!(chain, Val(:S), 3)
push_gate!(chain, Val(:X), 4)
push_gate!(chain, Val(:CNOT), 3, 2)
push_gate!(chain, Val(:H), 1)
push_gate!(chain, Val(:shift), 4, ZXCalculus.Phase(1//2))
push_gate!(chain, Val(:Rx), 4, ZXCalculus.Phase(1//1))
push_gate!(chain, Val(:Rx), 3, ZXCalculus.Phase(1//4))
push_gate!(chain, Val(:Rx), 2, ZXCalculus.Phase(1//4))
push_gate!(chain, Val(:S), 3)

ir = @make_ircode begin
end
bir = BlockIR(ir, 4, chain)
zxd = convert_to_zxd(bir)
convert_to_chain(zxd)
pt_zxd = phase_teleportation(zxd)
@test tcount(pt_zxd) <= tcount(zxd)
pt_chain = convert_to_chain(pt_zxd)
@test length(pt_chain) <= length(chain)

zxg = clifford_simplification(zxd)
cl_chain = circuit_extraction(zxg)

zxg = full_reduction(zxd)
fl_chain = circuit_extraction(zxg)
ZXCalculus.generate_layout!(zxg)
@test ZXCalculus.qubit_loc(zxg, 40) == 0//1
ZXCalculus.spider_sequence(zxg)

pt_bir = phase_teleportation(bir)
cl_bir = clifford_simplification(bir)
fl_bir = full_reduction(bir)

@test length(pt_chain) == length(pt_bir.circuit)
@test length(cl_chain) == length(cl_bir.circuit)
@test length(fl_chain) == length(fl_bir.circuit)

@testset "issue#80" begin
    ir = @make_ircode begin
    end

    circuit = Chain(Gate(X, Locations(1)), Gate(X, Locations(1)))
    bir = BlockIR(ir, 1, circuit)
    bir = clifford_simplification(bir)
    bir = clifford_simplification(bir)
    @test bir.circuit == Chain(Gate(H, Locations((1, ))), Gate(H, Locations((1, ))))
end

@testset "generate_layout!" begin
    circ = Chain(Gate(H, Locations(2)), Gate(T, Locations(4)), Gate(H, Locations(1)), Gate(AdjointOperation{SGate}(S), Locations(2)), Gate(H, Locations(2)), Gate(X, Locations(3)), Gate(AdjointOperation{SGate}(S), Locations(1)), Gate(Z, Locations(1)), Gate(H, Locations(2)), Gate(X, Locations(1)), Gate(Z, Locations(1)), Gate(T, Locations(5)), Ctrl(Gate(X, Locations(5)), CtrlLocations(1)), Gate(H, Locations(1)), Gate(T, Locations(1)), Ctrl(Gate(X, Locations(3)), CtrlLocations(5)), Gate(H, Locations(1)), Gate(X, Locations(4)), Ctrl(Gate(X, Locations(5)), CtrlLocations(4)), Gate(S, Locations(2)), Ctrl(Gate(X, Locations(1)), CtrlLocations(3)), Gate(X, Locations(2)), Ctrl(Gate(X, Locations(3)), CtrlLocations(1)), Gate(X, Locations(2)), Gate(S, Locations(3)), Gate(Z, Locations(2)), Gate(Z, Locations(5)), Gate(X, Locations(2)), Gate(X, Locations(1)), Ctrl(Gate(X, Locations(3)), CtrlLocations(5)), Gate(S, Locations(4)), Gate(X, Locations(3)), Ctrl(Gate(X, Locations(1)), CtrlLocations(2)), Gate(AdjointOperation{SGate}(S), Locations(4)), Gate(Z, Locations(2)), Gate(AdjointOperation{SGate}(S), Locations(5)), Ctrl(Gate(X, Locations(1)), CtrlLocations(2)), Gate(Z, Locations(5)), Ctrl(Gate(X, Locations(1)), CtrlLocations(4)), Ctrl(Gate(X, Locations(3)), CtrlLocations(4)), Gate(H, Locations(4)), Gate(Z, Locations(1)), Gate(X, Locations(4)), Gate(Z, Locations(3)), Gate(H, Locations(4)), Ctrl(Gate(X, Locations(1)), CtrlLocations(4)), Ctrl(Gate(X, Locations(4)), CtrlLocations(1)), Gate(X, Locations(4)), Gate(S, Locations(3)), Gate(AdjointOperation{SGate}(S), Locations(2)), Gate(Z, Locations(3)), Gate(S, Locations(5)), Ctrl(Gate(X, Locations(3)), CtrlLocations(5)), Gate(H, Locations(2)), Gate(Z, Locations(4)), Gate(H, Locations(1)), Gate(X, Locations(1)), Gate(X, Locations(2)), Ctrl(Gate(X, Locations(5)), CtrlLocations(3)), Ctrl(Gate(X, Locations(1)), CtrlLocations(3)), Gate(Z, Locations(4)), Gate(S, Locations(5)), Gate(S, Locations(5)), Ctrl(Gate(X, Locations(5)), CtrlLocations(1)), Gate(T, Locations(4)), Gate(Z, Locations(2)), Gate(X, Locations(4)), Gate(H, Locations(2)), Gate(AdjointOperation{SGate}(S), Locations(3)), Gate(H, Locations(5)), Gate(T, Locations(2)), Gate(AdjointOperation{SGate}(S), Locations(5)), Gate(AdjointOperation{SGate}(S), Locations(4)), Gate(H, Locations(2)), Gate(S, Locations(2)), Gate(AdjointOperation{SGate}(S), Locations(3)), Ctrl(Gate(X, Locations(5)), CtrlLocations(1)), Gate(H, Locations(5)), Ctrl(Gate(X, Locations(4)), CtrlLocations(3)), Gate(H, Locations(4)), Gate(Z, Locations(2)), Gate(H, Locations(2)), Gate(S, Locations(2)), Gate(H, Locations(4)), Ctrl(Gate(X, Locations(1)), CtrlLocations(2)), Gate(X, Locations(4)), Gate(H, Locations(1)), Gate(AdjointOperation{SGate}(S), Locations(5)), Gate(X, Locations(1)), Gate(X, Locations(3)), Gate(Z, Locations(1)), Ctrl(Gate(X, Locations(1)), CtrlLocations(2)), Gate(T, Locations(3)), Gate(X, Locations(4)), Gate(X, Locations(3)), Gate(AdjointOperation{SGate}(S), Locations(1)), Gate(AdjointOperation{SGate}(S), Locations(3)), Gate(H, Locations(5)), Gate(S, Locations(4)), Gate(Z, Locations(4)))
    ir = @make_ircode begin end
    bir = BlockIR(ir, 5, circ)
    zxd = ZXDiagram(bir)
    zxg = ZXGraph(zxd)
    full_reduction(zxg)
    ZXCalculus.generate_layout!(zxg)
    @test ZXCalculus.generate_layout!(zxg) !== nothing
end

function random_circuit(nbits, ngates; T = 0.1, CZ = 0.0, CNOT = 0.1)
    ir = @make_ircode begin
    end
    CLIFF = 1 - T - CZ - CNOT
    circ = Chain()
    for _ = 1:ngates
        x = rand()
        nbits == 1 && (x = x*(CLIFF+T))
        if x <= CLIFF
            g = rand([:X, :X, :Z, :Z, :S, :Sdag, :H, :H])
            push_gate!(circ, Val(g), rand(1:nbits))
        elseif x - CLIFF <= T
            g = rand([:T, :Tdag])
            push_gate!(circ, Val(g), rand(1:nbits))
        elseif x - CLIFF - T <= CZ
            loc = rand(1:nbits)
            ctrl = loc
            while ctrl == loc
                ctrl = rand(1:nbits)
            end
            push_gate!(circ, Val(:CZ), loc, ctrl)
        else
            loc = rand(1:nbits)
            ctrl = loc
            while ctrl == loc
                ctrl = rand(1:nbits)
            end
            push_gate!(circ, Val(:CNOT), loc, ctrl)
        end
    end
    return BlockIR(ir, nbits, circ)
end


###
### Mer specific specializations of src/biosequence/counting.jl
###

for i in [(:_count_a, :a_bitcount), (:_count_c, :c_bitcount), (:_count_g, :g_bitcount), (:_count_t, :t_bitcount)]
    @eval begin
        @inline function $(i[1])(alph::A, head::UInt64, tail...) where {A<:NucleicAcidAlphabet}
            return $(i[2])(head, alph) + $(i[1])(alph, tail...)
        end
        @inline $(i[1])(alph::A) where {A<:NucleicAcidAlphabet} = 0
    end
end

@inline function _count_gc(alph::A, head::UInt64, tail...) where {A<:NucleicAcidAlphabet}
    return gc_bitcount(head, alph) + _count_gc(alph, tail...)
end
@inline _count_gc(::A) where {A<:NucleicAcidAlphabet} = 0

count_a(x::Kmer{A,K,N})  where {A<:NucleicAcidAlphabet,K,N} = _count_a(A(), x.data...) - n_unused(x)
count_c(x::Kmer{A,K,N})  where {A<:NucleicAcidAlphabet,K,N} = _count_c(A(), x.data...)
count_g(x::Kmer{A,K,N})  where {A<:NucleicAcidAlphabet,K,N} = _count_g(A(), x.data...)
count_t(x::Kmer{A,K,N})  where {A<:NucleicAcidAlphabet,K,N} = _count_t(A(), x.data...)

count_gc(x::Kmer{A,K,N}) where {A<:NucleicAcidAlphabet,K,N} = _count_gc(A(), x.data...)
Base.count(::typeof(isGC), x::Kmer{A,K,N}) where {A<:NucleicAcidAlphabet,K,N} = count_gc(x)

# TODO: Expand to Amino Acid Kmers as well...
@inline function Base.count(::typeof(!=), a::Kmer{A,K,N}, b::Kmer{A,K,N}) where {A<:NucleicAcidAlphabet,K,N}
    ad = a.data
    bd = b.data
    sum = 0
    @inbounds for i in 1:N
        sum += BioSequences.mismatch_bitcount(ad[i], bd[i], A())
    end
    return sum
end

# TODO: Expand to Amino Acid Kmers as well...
@inline function Base.count(::typeof(==), a::Kmer{A,K,N}, b::Kmer{A,K,N}) where {A<:NucleicAcidAlphabet,K,N}
    ad = a.data
    bd = b.data
    sum = 0
    @inbounds for i in 1:N
        sum += BioSequences.match_bitcount(ad[i], bd[i], A())
    end
    return sum - n_unused(a)
end