"""
`module  JAC.PhotoRecombination`
    ... a submodel of JAC that contains all methods for computing photo-recomnbination, i.e. radiative recombination
        and radiative electron capture properties between some initial and final-state multiplets.
"""
module PhotoRecombination

    using Printf, ..AngularMomentum, ..Basics, ..Continuum, ..ManyElectron, ..PhotoEmission, ..Radial, ..Nuclear, ..TableStrings
    ##x global JAC_counter = 0

    """
    `struct  Settings`  ... defines a type for the details and parameters of computing photo recombination lines.

        + 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.
        + electronEnergies        ::Array{Float64,1}             ... List of electron energies.
        + ionEnergies             ::Array{Float64,1}             ... List of ion energies.
        + useIonEnergies          ::Bool                         ... Make use of ion energies in [MeV/u] to obtain the electron energies.
        + calcAnisotropy          ::Bool                         ... True, if the overall anisotropy is to be calculated.
        + calcTensors             ::Bool                         ... True, if the statistical tensors are to be calculated and false otherwise.
        + printBeforeComputation  ::Bool                         ... True, if all energies and lines are printed before their evaluation.
        + selectLines             ::Bool                         ... True, if lines are selected individually for the computations.
        + selectedLines           ::Array{Tuple{Int64,Int64},1}  ... List of lines, given by tupels (inital-level, final-level).
    """
    struct Settings
        multipoles                ::Array{EmMultipole}
        gauges                    ::Array{UseGauge}
        electronEnergies          ::Array{Float64,1}
        ionEnergies               ::Array{Float64,1}
        useIonEnergies            ::Bool
        calcAnisotropy            ::Bool
        calcTensors               ::Bool
        printBeforeComputation    ::Bool
        selectLines               ::Bool
        selectedLines             ::Array{Tuple{Int64,Int64},1}
    end

    """
    `PhotoRecombination.Settings()`  ... constructor for the default values of photo recombination line computations
    """
    function Settings()
        Settings(EmMultipole[], UseGauge[], Float64[], Float64[], false, false, false, false, false, Tuple{Int64,Int64}[])
    end

    # `Base.show(io::IO, settings::PhotoRecombination.Settings)`
    #		... prepares a proper printout of the variable settings::PhotoRecombination.Settings.
    function Base.show(io::IO, settings::PhotoRecombination.Settings)
        println(io, "multipoles:               $(settings.multipoles)  ")
        println(io, "gauges:                   $(settings.gauges)  ")
        println(io, "electronEnergies:         $(settings.electronEnergies)  ")
        println(io, "ionEnergies:              $(settings.ionEnergies)  ")
        println(io, "useIonEnergies:           $(settings.useIonEnergies)  ")
        println(io, "calcAnisotropy:           $(settings.calcAnisotropy)  ")
        println(io, "calcTensors:              $(settings.calcTensors)  ")
        println(io, "printBeforeComputation:   $(settings.printBeforeComputation)  ")
        println(io, "selectLines:              $(settings.selectLines)  ")
        println(io, "selectedLines:            $(settings.selectedLines)  ")
    end

    """
    `struct  PhotoRecombination.Channel`
        ... defines a type for a photorecombination channel to help characterize a single multipole and scattering
            (continuum) state of many electron-states with a single free electron.

        + multipole      ::EmMultipole          ... Multipole of the photon emission/absorption.
        + gauge          ::EmGauge              ... Gauge for dealing with the (coupled) radiation field.
        + kappa          ::Int64                ... partial-wave of the free electron
        + symmetry       ::LevelSymmetry        ... total angular momentum and parity of the scattering state
        + phase          ::Float64              ... phase of the partial wave
        + amplitude      ::Complex{Float64}     ... Rec amplitude associated with the given channel.
    """
    struct  Channel
        multipole        ::EmMultipole
        gauge            ::EmGauge
        kappa            ::Int64
        symmetry         ::LevelSymmetry
        phase            ::Float64
        amplitude        ::Complex{Float64}
    end

    """
    `struct  PhotoRecombination.Line`
        ... defines a type for a Photorecombination line that may include the definition of channels.

        + initialLevel   ::Level                  ... initial-(state) level
        + finalLevel     ::Level                  ... final-(state) level
        + electronEnergy ::Float64                ... Energy of the (incoming free) electron.
        + photonEnergy   ::Float64                ... Energy of the emitted photon.
        + crossSection   ::EmProperty             ... Cross section for this electron capture.
        + hasChannels    ::Bool                   ... Determines whether the individual (sub-) channels are defined in terms of their
                                                      free-electron energy, kappa, multipole, etc., or not.
        + channels       ::Array{PhotoRecombination.Channel,1}    ... List of photorecombination channels of this line.
    """
    struct  Line
        initialLevel     ::Level
        finalLevel       ::Level
        electronEnergy   ::Float64
        photonEnergy     ::Float64
        crossSection     ::EmProperty
        hasChannels      ::Bool
        channels         ::Array{PhotoRecombination.Channel,1}
    end

    """
    `PhotoRecombination.Line()`
        ... constructor for an `empty instance` of a photorecombination line between a specified initial
            and final level.
    """
    function Line(initialLevel::Level, finalLevel::Level)
        Line(initialLevel::Level, finalLevel::Level, 0., 0., EmProperty(0., 0.), false, Channel[])
    end

    # `Base.show(io::IO, line::PhotoRecombination.Line)`  ... prepares a proper printout of the variable line::PhotoRecombination.Line.
    function Base.show(io::IO, line::PhotoRecombination.Line)
        println(io, "initialLevel:      $(line.initialLevel)  ")
        println(io, "finalLevel:        $(line.finalLevel)  ")
        println(io, "electronEnergy:    $(line.electronEnergy)  ")
        println(io, "photonEnergy:      $(line.photonEnergy)  ")
        println(io, "crossSection:      $(line.crossSection)  ")
        println(io, "hasChannels:       $(line.hasChannels)  ")
        println(io, "channels:          $(line.channels)  ")
    end

    """
    `PhotoRecombination.amplitude(kind::String, channel::PhotoRecombination.Channel, energy::Float64, finalLevel::Level,
                                  continuumLevel::Level, grid::Radial.Grid)`
        ... to compute the kind = (photorecombination) amplitude
            < alpha_f J_f || O^(photorecombination) || (alpha_i J_i, epsilon kappa) J_t>  due to the electron-photon
            interaction for the given final and continuum level, the partial wave of the outgoing electron as well as
            the given multipole and gauge. A value::ComplexF64 is returned.
    """
    function amplitude(kind::String, channel::PhotoRecombination.Channel, energy::Float64, finalLevel::Level,
                       continuumLevel::Level, grid::Radial.Grid)
        if      kind in [ "photorecombination"]
        #-----------------------------------
            amplitude = PhotoEmission.amplitude("emission", channel.multipole, channel.gauge, energy, finalLevel, continuumLevel, grid)
            amplitude = im^Basics.subshell_l(Subshell(101, channel.kappa)) * exp( im*channel.phase ) * amplitude
        else    error("stop b")
        end

        return( amplitude )
    end

    """
    `PhotoRecombination.computeAmplitudesProperties(line::PhotoRecombination.Line, nm::Nuclear.Model, grid::Radial.Grid, nrContinuum::Int64,
                                                    settings::PhotoRecombination.Settings)`
        ... to compute all amplitudes and properties of the given line; a line::PhotoRecombination.Line is returned for
            which the amplitudes and properties are now evaluated.
    """
    function  computeAmplitudesProperties(line::PhotoRecombination.Line, nm::Nuclear.Model, grid::Radial.Grid, nrContinuum::Int64,
                                          settings::PhotoRecombination.Settings)
        newChannels = PhotoRecombination.Channel[];;   contSettings = Continuum.Settings(false, nrContinuum);    csC = 0.;    csB = 0.
        for channel in line.channels
            newfLevel = Basics.generateLevelWithSymmetryReducedBasis(line.finalLevel)
            newfLevel = Basics.generateLevelWithExtraSubshell(Subshell(101, channel.kappa), newfLevel)
            newiLevel = Basics.generateLevelWithSymmetryReducedBasis(line.initialLevel)
            cOrbital, phase  = Continuum.generateOrbitalForLevel(line.electronEnergy, Subshell(101, channel.kappa), newiLevel, nm, grid, contSettings)
            newcLevel  = Basics.generateLevelWithExtraElectron(cOrbital, channel.symmetry, newiLevel)
            newChannel = PhotoRecombination.Channel(channel.multipole, channel.gauge, channel.kappa, channel.symmetry, phase, 0.)
            amplitude  = PhotoRecombination.amplitude("photorecombination", channel, line.photonEnergy, newfLevel, newcLevel, grid)
            push!( newChannels, PhotoRecombination.Channel(newChannel.multipole, newChannel.gauge, newChannel.kappa, newChannel.symmetry,
                                                           newChannel.phase, amplitude) )
            if       channel.gauge == Basics.Coulomb     csC = csC + abs(amplitude)^2
            elseif   channel.gauge == Basics.Babushkin   csB = csB + abs(amplitude)^2
            elseif   channel.gauge == Basics.Magnetic    csB = csB + abs(amplitude)^2;   csC = csC + abs(amplitude)^2
            end
        end
        Ji2 = AngularMomentum.twoJ(line.initialLevel.J)
        csFactor     = 8 * pi^3 * Defaults.getDefaults("alpha")^3 * line.photonEnergy / (Ji2 + 1)
        crossSection = EmProperty(csFactor * csC, csFactor * csB)
        newLine = PhotoRecombination.Line( line.initialLevel, line.finalLevel, line.electronEnergy, line.photonEnergy, crossSection, true, newChannels)
        return( newLine )
    end

    """
    `PhotoRecombination.computeAnisotropyParameter(nu::Int64, gauge::EmGauge, line::PhotoRecombination.Line)`
        ... to compute the anisotropy parameter of the emitted photons for the photorecombination of an initially unpolarized ion.
            A value::ComplexF64 is returned.
    """
    function  computeAnisotropyParameter(nu::Int64, gauge::EmGauge, line::PhotoRecombination.Line)
        if  !line.hasChannels   error("No channels are defined for the given PhotoRecombination.line.")         end
        wa = 0.0im;    Ji = line.initialLevel.J;    Jf = line.finalLevel.J
        wn = 0.;
        for  ch in line.channels
            if  gauge != ch.gauge  &&  gauge != Basics.Magnetic   continue    end
            wn = wn + conj(ch.amplitude) * ch.amplitude
        end

        for  cha  in line.channels
            if  gauge != cha.gauge  &&  gauge != Basics.Magnetic   continue    end
            J = cha.symmetry.J;    L = cha.multipole.L;    if  cha.multipole.electric   p = 1   else    p = 0   end
            j = AngularMomentum.kappa_j(cha.kappa);    l = AngularMomentum.kappa_l(cha.kappa)
            #
            for  chp  in line.channels
                if  gauge != chp.gauge  &&  gauge != Basics.Magnetic   continue    end
                Jp = chp.symmetry.J;    Lp = chp.multipole.L;    if  chp.multipole.electric   pp = 1   else    pp = 0   end
                jp = AngularMomentum.kappa_j(chp.kappa);     lp = AngularMomentum.kappa_l(chp.kappa)
                #
                if  1 + (-1)^(L + p + Lp + pp - nu) == 0    continue    end
                wa = wa + 1.0im^(L + p - Lp - pp) * AngularMomentum.phaseFactor([Ji, -1, AngularJ64(1//2), -1, Jf]) *
                                 sqrt( AngularMomentum.bracket([AngularJ64(L), AngularJ64(Lp), l, lp, j, jp, J, Jp]) ) *
                                 AngularMomentum.ClebschGordan( l, AngularM64(0), lp, AngularM64(0),  AngularJ64(nu),  AngularM64(0)) *
                                 AngularMomentum.ClebschGordan( AngularJ64(L), AngularM64(1), AngularJ64(Lp), AngularM64(-1),
                                                                    AngularJ64(nu), AngularM64(0)) *
                                 AngularMomentum.Wigner_6j(J, Jp, AngularJ64(nu), AngularJ64(Lp), AngularJ64(L), Jf) *
                                 AngularMomentum.Wigner_6j(J, Jp, AngularJ64(nu), jp, j, Ji) *
                                 AngularMomentum.Wigner_6j(j, jp, AngularJ64(nu), lp, l, AngularJ64(1//2)) *
                                 conj(cha.amplitude) * chp.amplitude
            end
        end

        value = - 0.5 * wa / wn
        return( value )
    end

    """
    `PhotoRecombination.computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, nm::Nuclear.Model, grid::Radial.Grid,
                                         settings::PhotoRecombination.Settings; output::Bool=true)`
        ... to compute the photo recombination transition amplitudes and all properties as requested by the given settings.
            A list of lines::Array{PhotoRecombination.Lines} is returned.
    """
    function  computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, nm::Nuclear.Model, grid::Radial.Grid,
                           settings::PhotoRecombination.Settings; output::Bool=true)
        println("")
        printstyled("PhotoRecombination.computeLines(): The computation of photo-recombination properties starts now ... \n", color=:light_green)
        printstyled("--------------------------------------------------------------------------------------------------- \n", color=:light_green)
        println("")
        lines = PhotoRecombination.determineLines(finalMultiplet, initialMultiplet, settings)
        # Display all selected lines before the computations start
        if  settings.printBeforeComputation    PhotoRecombination.displayLines(lines)    end
        # Determine maximum energy and check for consistency of the grid
        maxEnergy = 0.;   for  line in lines   maxEnergy = max(maxEnergy, line.electronEnergy)   end
        nrContinuum = Continuum.gridConsistency(maxEnergy, grid)
        # Calculate all amplitudes and requested properties
        newLines = PhotoRecombination.Line[]
        for  line in lines
            newLine = PhotoRecombination.computeAmplitudesProperties(line, nm, grid, nrContinuum, settings)
            push!( newLines, newLine)
        end
        # Print all results to screen
        PhotoRecombination.displayResults(stdout, newLines, settings)
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        if  printSummary   PhotoRecombination.displayResults(iostream, newLines, settings)    end
        #
        if    output    return( lines )
        else            return( nothing )
        end
    end

    """
    `PhotoRecombination.determineChannels(finalLevel::Level, initialLevel::Level, settings::PhotoRecombination.Settings)`
        ... to determine a list of RecChannel for a transitions from the initial to final level and by taking into account
            the particular settings of for this computation; an Array{PhotoRecombination.Channel,1} is returned.
    """
    function determineChannels(finalLevel::Level, initialLevel::Level, settings::PhotoRecombination.Settings)
        channels = PhotoRecombination.Channel[];
        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(symf, mp)
                for  symt in symList
                    kappaList = AngularMomentum.allowedKappaSymmetries(symi, symt)
                    for  kappa in kappaList
                        # Include further restrictions if appropriate
                        if     string(mp)[1] == 'E'  &&   gauge == Basics.UseCoulomb
                            push!(channels, PhotoRecombination.Channel(mp, Basics.Coulomb,   kappa, symt, 0., Complex(0.)) )
                        elseif string(mp)[1] == 'E'  &&   gauge == Basics.UseBabushkin
                            push!(channels, PhotoRecombination.Channel(mp, Basics.Babushkin, kappa, symt, 0., Complex(0.)) )
                        elseif string(mp)[1] == 'M'
                            push!(channels, PhotoRecombination.Channel(mp, Basics.Magnetic,  kappa, symt, 0., Complex(0.)) )
                        end
                    end
                end
            end
        end
        return( channels )
    end

    """
    `PhotoRecombination.determineLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, settings::PhotoRecombination.Settings)`
        ... to determine a list of PhotoRecombination.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{PhotoRecombination.Line,1} is returned. Apart from the level specification, all physical properties are set
            to zero during the initialization process.
    """
    function  determineLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, settings::PhotoRecombination.Settings)
        if    settings.selectLines    selectLines   = true;
                       selectedLines = Basics.determineSelectedLines(settings.selectedLines, initialMultiplet, finalMultiplet)
        else                          selectLines   = false
        end

        lines = PhotoRecombination.Line[]
        for  i = 1:length(initialMultiplet.levels)
            for  f = 1:length(finalMultiplet.levels)
                if  selectLines  &&  !((i,f) in selectedLines )    continue   end
                ##x println("PhotoRecombination.determineLines-aa:  i = $i, f = $f")
                #
                if  settings.useIonEnergies
                    error("Not yet implemented: Given ion energies.")
                else
                    for  en in settings.electronEnergies
                        if  en < 0    continue   end
                        # Electron energies are still in 'pre-defined' units; convert to Hartree
                        en_au = Defaults.convertUnits("energy: to atomic", en)
                        omega = en_au + initialMultiplet.levels[i].energy - finalMultiplet.levels[f].energy

                        channels = PhotoRecombination.determineChannels(finalMultiplet.levels[f], initialMultiplet.levels[i], settings)
                        push!( lines, PhotoRecombination.Line(initialMultiplet.levels[i], finalMultiplet.levels[f], en_au, omega,
                                                              EmProperty(0., 0.), true, channels) )
                    end
                end
            end
        end
        return( lines )
    end

    """
    `PhotoRecombination.displayLines(lines::Array{PhotoRecombination.Line,1})`
        ... to display a list of lines 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{PhotoRecombination.Line,1})
        println(" ")
        println("  Selected photorecombination lines:")
        println(" ")
        println("  ", TableStrings.hLine(175))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=2);                                sb = sb * TableStrings.hBlank(20)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=4);                                sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.center(10, "Energy_if"; na=2);
        sb = sb * TableStrings.center(10, TableStrings.inUnits("energy"); na=2)
        sa = sa * TableStrings.center(12, "Energy e_r"; na=1);
        sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=2)
        sa = sa * TableStrings.center(10, "omega"; na=5);
        sb = sb * TableStrings.center(10, TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.flushleft(57, "List of multipoles, gauges, kappas and total symmetries"; na=4)
        sb = sb * TableStrings.flushleft(57, "partial (multipole, gauge, total J^P)                  "; na=4)
        println(sa);    println(sb);    println("  ", TableStrings.hLine(175))
        #
        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.initialLevel.energy - line.finalLevel.energy
            sa = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", energy))              * "   "
            sa = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", line.electronEnergy)) * "   "
            sa = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", line.photonEnergy))   * "    "
            kappaMultipoleSymmetryList = Tuple{Int64,EmMultipole,EmGauge,LevelSymmetry}[]
            for  i in 1:length(line.channels)
                push!( kappaMultipoleSymmetryList, (line.channels[i].kappa, line.channels[i].multipole, line.channels[i].gauge,
                                                    line.channels[i].symmetry) )
            end
            wa = TableStrings.kappaMultipoleSymmetryTupels(85, kappaMultipoleSymmetryList)
            sb = sa * wa[1];    println( sb )
            for  i = 2:length(wa)
                sb = TableStrings.hBlank( length(sa) ) * wa[i];    println( sb )
            end
        end
        println("  ", TableStrings.hLine(175), "\n")
        #
        return( nothing )
    end

    """
    `PhotoRecombination.displayResults(stream::IO, lines::Array{PhotoRecombination.Line,1}, settings::PhotoRecombination.Settings)`
        ... to list all results, energies, cross sections, etc. of the selected lines. A neat table is printed but nothing is
            returned otherwise.
    """
    function  displayResults(stream::IO, lines::Array{PhotoRecombination.Line,1}, settings::PhotoRecombination.Settings)
        println(stream, " ")
        println(stream, "  Photorecombination cross sections for beta^2 * gamma^2 = 1:")
        println(stream, "  ")
        println(stream, "  ", TableStrings.hLine(133))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"   ; na=0);                       sb = sb * TableStrings.hBlank(20)
        sa = sa * TableStrings.center(16, "i--J^P--f"   ; na=3);                       sb = sb * TableStrings.hBlank(19)
        sa = sa * TableStrings.center(12, "i--Energy--f"; na=4)
        sb = sb * TableStrings.center(12,TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.center(12, "omega"     ; na=4)
        sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.center(12, "Energy e_p"; na=3)
        sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=3)
        sa = sa * TableStrings.center(10, "Multipoles"; na=1);                              sb = sb * TableStrings.hBlank(13)
        sa = sa * TableStrings.center(30, "Cou -- Cross section -- Bab"; na=3)
        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(133))
        #
        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(17, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(17, TableStrings.symmetries_if(isym, fsym); na=3)
            en = line.initialLevel.energy - line.finalLevel.energy
            sa = sa * @sprintf("%.6e", Defaults.convertUnits("energy: from atomic", en))                  * "    "
            sa = sa * @sprintf("%.6e", Defaults.convertUnits("energy: from atomic", line.photonEnergy))   * "    "
            sa = sa * @sprintf("%.6e", Defaults.convertUnits("energy: from atomic", line.electronEnergy)) * "    "
            multipoles = EmMultipole[]
            for  ch in line.channels
                multipoles = push!( multipoles, ch.multipole)
            end
            multipoles = unique(multipoles);   mpString = TableStrings.multipoleList(multipoles) * "          "
            sa = sa * TableStrings.flushleft(11, mpString[1:10];  na=2)
            sa = sa * @sprintf("%.6e", Defaults.convertUnits("cross section: from atomic", line.crossSection.Coulomb))     * "    "
            sa = sa * @sprintf("%.6e", Defaults.convertUnits("cross section: from atomic", line.crossSection.Babushkin))   * "    "
            println(stream, sa)
        end
        println(stream, "  ", TableStrings.hLine(133))
        #
        #
        if  settings.calcAnisotropy
            println(stream, " ")
            println(stream, "  Anisotropy angular parameters beta_nu of the emitted photons for initially unpolarized ions:")
            println(stream, " ")
            println(stream, "  ", TableStrings.hLine(133))
            sa = "  ";   sb = "  "
            sa = sa * TableStrings.center(18, "i-level-f"   ; na=0);                       sb = sb * TableStrings.hBlank(20)
            sa = sa * TableStrings.center(16, "i--J^P--f"   ; na=3);                       sb = sb * TableStrings.hBlank(19)
            sa = sa * TableStrings.center(12, "i--Energy--f"; na=4)
            sb = sb * TableStrings.center(12,TableStrings.inUnits("energy"); na=4)
            sa = sa * TableStrings.center(12, "omega"     ; na=4)
            sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=4)
            sa = sa * TableStrings.center(12, "Energy e_p"; na=3)
            sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=3)
            sa = sa * TableStrings.center(10, "Multipoles"; na=5);                         sb = sb * TableStrings.hBlank(15)
            sa = sa * TableStrings.flushleft(57, "beta's"; na=4)
            println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(133))
            #
            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(17, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
                sa = sa * TableStrings.center(17, TableStrings.symmetries_if(isym, fsym); na=3)
                en = line.initialLevel.energy - line.finalLevel.energy
                sa = sa * @sprintf("%.6e", Defaults.convertUnits("energy: from atomic", en))                  * "    "
                sa = sa * @sprintf("%.6e", Defaults.convertUnits("energy: from atomic", line.photonEnergy))   * "    "
                sa = sa * @sprintf("%.6e", Defaults.convertUnits("energy: from atomic", line.electronEnergy)) * "    "
                multipoles = EmMultipole[]
                for  ch in line.channels
                    multipoles = push!( multipoles, ch.multipole)
                end
                multipoles = unique(multipoles);   mpString = TableStrings.multipoleList(multipoles) * "          "
                sa = sa * TableStrings.flushleft(11, mpString[1:10];  na=3)
                be = PhotoRecombination.computeAnisotropyParameter(1, Basics.Coulomb,   line);   sa = sa * @sprintf("%.4e", be.re) * " (1, C);  "
                be = PhotoRecombination.computeAnisotropyParameter(1, Basics.Babushkin, line);   sa = sa * @sprintf("%.4e", be.re) * " (1, B);  "
                println(stream, sa);   sa = TableStrings.hBlank(102)
                be = PhotoRecombination.computeAnisotropyParameter(2, Basics.Coulomb,   line);   sa = sa * @sprintf("%.4e", be.re) * " (2, C);  "
                be = PhotoRecombination.computeAnisotropyParameter(2, Basics.Babushkin, line);   sa = sa * @sprintf("%.4e", be.re) * " (2, B);  "
                println(stream, sa);   sa = TableStrings.hBlank(102)
                be = PhotoRecombination.computeAnisotropyParameter(3, Basics.Coulomb,   line);   sa = sa * @sprintf("%.4e", be.re) * " (3, C);  "
                be = PhotoRecombination.computeAnisotropyParameter(3, Basics.Babushkin, line);   sa = sa * @sprintf("%.4e", be.re) * " (3, B);  "
                println(stream, sa);   sa = TableStrings.hBlank(102)
                be = PhotoRecombination.computeAnisotropyParameter(4, Basics.Coulomb,   line);   sa = sa * @sprintf("%.4e", be.re) * " (4, C);  "
                be = PhotoRecombination.computeAnisotropyParameter(4, Basics.Babushkin, line);   sa = sa * @sprintf("%.4e", be.re) * " (4, B);  "
                println(stream, sa);   sa = TableStrings.hBlank(102)
            end
            println(stream, "  ", TableStrings.hLine(133))
        end
        #
        #
        if  settings.calcTensors
            println(stream, " ")
            println(stream, "  Reduced statistical tensors of the recombined ion ... not yet implemented !!")
            println(stream, " ")
        end
        #
        return( nothing )
    end

end # module


###############################################################################
#
#  NfRelOrd
#
###############################################################################

# I don't like that we have to drag S around
# Still hoping for julia/#18466

mutable struct NfRelOrdSet{T}
  nf::RelativeExtension{T}

  function NfRelOrdSet{T}(K::RelativeExtension{T}) where {T}
    a = new(K)
    return a
  end
end

mutable struct NfRelOrd{T, S} <: Ring
  nf::RelativeExtension{T}
  basis_nf::Vector{RelativeElement{T}}
  basis_mat::Generic.Mat{T}
  basis_mat_inv::Generic.Mat{T}
  basis_pmat::PMat{T, S}
  pseudo_basis::Vector{Tuple{RelativeElement{T}, S}}

  disc_abs::NfOrdIdl # used if T == nf_elem
  disc_rel#::NfRelOrdIdl{T} # used otherwise; is a forward declaration
  parent::NfRelOrdSet{T}

  isequation_order::Bool

  ismaximal::Int                   # 0 Not known
                                   # 1 Known to be maximal
                                   # 2 Known to not be maximal

  trace_mat::Generic.Mat{T}

  inv_coeff_ideals::Vector{S}

  function NfRelOrd{T, S}(K::RelativeExtension{T}) where {T, S}
    z = new{T, S}()
    z.nf = K
    z.parent = NfRelOrdSet{T}(K)
    z.isequation_order = false
    z.ismaximal = 0
    return z
  end

  function NfRelOrd{T, S}(K::RelativeExtension{T}, M::PMat{T, S}) where {T, S}
    z = NfRelOrd{T, S}(K)
    z.nf = K
    z.parent = NfRelOrdSet{T}(K)
    z.basis_pmat = M
    z.basis_mat = M.matrix
    return z
  end

  function NfRelOrd{T, S}(K::RelativeExtension{T}, M::Generic.Mat{T}) where {T, S}
    z = NfRelOrd{T, S}(K)
    z.nf = K
    z.parent = NfRelOrdSet{T}(K)
    z.basis_mat = M
    z.basis_pmat = pseudo_matrix(M)
    return z
  end
end

###############################################################################
#
#  NfRelOrdElem
#
###############################################################################

mutable struct NfRelOrdElem{T} <: RingElem
  parent#::NfRelOrd{T, S} # I don't want to drag the S around
  elem_in_nf::RelativeElement{T}
  elem_in_basis::Vector{T}
  has_coord::Bool

  function NfRelOrdElem{T}(O::NfRelOrd{T}) where {T}
    z = new{T}()
    z.parent = O
    z.elem_in_nf = zero(nf(O))
    z.elem_in_basis = Vector{T}(degree(O))
    z.has_coord = false
    return z
  end

  function NfRelOrdElem{T}(O::NfRelOrd{T}, a::RelativeElement{T}) where {T}
    z = new{T}()
    z.parent = O
    z.elem_in_nf = a
    z.elem_in_basis = Vector{T}(degree(O))
    z.has_coord = false
    return z
  end

  function NfRelOrdElem{T}(O::NfRelOrd{T}, a::RelativeElement{T}, arr::Vector{T}) where {T}
    z = new{T}()
    z.parent = O
    z.elem_in_nf = a
    z.elem_in_basis = arr
    z.has_coord = true
    return z
  end
end

###############################################################################
#
#  NfRelOrdFracIdl
#
###############################################################################

mutable struct NfRelOrdFracIdlSet{T, S}
  order::NfRelOrd{T, S}

  function NfRelOrdFracIdlSet{T, S}(O::NfRelOrd{T, S}) where {T, S}
    a = new(O)
    return a
  end
end

mutable struct NfRelOrdFracIdl{T, S}
  order::NfRelOrd{T, S}
  parent::NfRelOrdFracIdlSet{T, S}
  basis_pmat::PMat{T, S}
  pseudo_basis::Vector{Tuple{RelativeElement{T}, S}}
  basis_mat::Generic.Mat{T}
  basis_mat_inv::Generic.Mat{T}
  den::fmpz

  norm
  has_norm::Bool

  function NfRelOrdFracIdl{T, S}(O::NfRelOrd{T, S}) where {T, S}
    z = new{T, S}()
    z.order = O
    z.parent = NfRelOrdFracIdlSet{T, S}(O)
    z.has_norm = false
    return z
  end

  function NfRelOrdFracIdl{T, S}(O::NfRelOrd{T, S}, M::PMat{T, S}) where {T, S}
    z = NfRelOrdFracIdl{T, S}(O)
    z.basis_pmat = M
    z.basis_mat = M.matrix
    return z
  end

  function NfRelOrdFracIdl{T, S}(O::NfRelOrd{T, S}, a::Array{Tuple{T1, S}}) where {T1 <: RelativeElement{T}, S} where T
    z = NfRelOrdFracIdl{T, S}(O)
    z.pseudo_basis = a
    return z
  end
end

###############################################################################
#
#  NfRelOrdIdl
#
###############################################################################

mutable struct NfRelOrdIdlSet{T, S}
  order::NfRelOrd{T, S}

  function NfRelOrdIdlSet{T, S}(O::NfRelOrd{T, S}) where {T, S}
    a = new(O)
    return a
  end
end

mutable struct NfRelOrdIdl{T, S}
  order::NfRelOrd{T, S}
  parent::NfRelOrdIdlSet{T, S}
  basis_pmat::PMat{T, S}
  pseudo_basis::Vector{Tuple{RelativeElement{T}, S}}
  basis_mat::Generic.Mat{T}
  basis_mat_inv::Generic.Mat{T}

  norm
  has_norm::Bool
  is_prime::Int            # 0: don't know
                           # 1 known to be prime
                           # 2 known to be not prime
  splitting_type::Tuple{Int, Int}

  minimum
  non_index_div_poly::fq_poly # only used if the ideal is a prime ideal not dividing the index
  p_uniformizer::NfRelOrdElem{T}
  anti_uniformizer::RelativeElement{T}

  function NfRelOrdIdl{T, S}(O::NfRelOrd{T, S}) where {T, S}
    z = new{T, S}()
    z.order = O
    z.parent = NfRelOrdIdlSet{T, S}(O)
    z.has_norm = false
    z.is_prime = 0
    z.splitting_type = (0,0)
    return z
  end

  function NfRelOrdIdl{T, S}(O::NfRelOrd{T, S}, M::PMat{T, S}) where {T, S}
    z = NfRelOrdIdl{T, S}(O)
    z.basis_pmat = M
    z.basis_mat = M.matrix
    return z
  end

  function NfRelOrdIdl{T, S}(O::NfRelOrd{T, S}, a::Array{Tuple{T1, S}}) where {T1 <: RelativeElement{T}, S} where T
    z = NfRelOrdIdl{T, S}(O)
    z.pseudo_basis = a
    return z
  end
end


abstract type Tri end
abstract type Tet end

"""
$(TYPEDEF)

Data structure for holding geometry data.
"""
struct eMesh{T1<:Union{Nothing,Tri},T2<:Union{Nothing,Tet}}
    point::Vector{SVector{3,Float64}}
    tri::Union{Nothing,Vector{SVector{3,Int64}}}
    tet::Union{Nothing,Vector{SVector{4,Int64}}}
    ϵ::Union{Nothing,Vector{Float64}}
    function eMesh( point::Vector{SVector{3,Float64}},
                    tri::Union{Nothing,Vector{SVector{3,Int64}}},
                    tet::Union{Nothing,Vector{SVector{4,Int64}}}=nothing,
                    ϵ::Union{Nothing,Vector{Float64}}=nothing)

        T1_ = ifelse(tri == nothing, Nothing, Tri)
        T2_ = ifelse(tet == nothing, Nothing, Tet)
        if T2_ == Tet
            @assert(isa(ϵ, Vector{Float64}))
            @assert(length(ϵ) == length(point), "length(ϵ) = $(length(ϵ)) but length(point) = $(length(point))")
            (length(ϵ) != 0) && @assert(0.0 < maximum(ϵ), "normalized penetration extent must be non-negative")
            (length(ϵ) != 0) && @assert(minimum(ϵ) == 0.0, "normalized penetration extent must be zero on the surface of the volume mesh")
            for k = 1:length(tet)
                (0.0 < volume(point[tet[k]])) || error("inverted tetrahedron")
            end
        else
            @assert(ϵ == nothing)
        end
        (T1_ == T2_ == Nothing) && error("a whole lot of nothing")
        return new{T1_,T2_}(deepcopy(point), deepcopy(tri), deepcopy(tet), deepcopy(ϵ))
    end
    function eMesh(hm::HomogenousMesh, tet::Union{Nothing,Vector{SVector{4,Int64}}}=nothing,
            ϵ::Union{Nothing,Vector{Float64}}=nothing)

        point = [SVector{3,Float64}(k) for k = hm.vertices]
        tri = [SVector{3,Int64}(k) for k = hm.faces]
        return eMesh(point, tri, tet, ϵ)
    end
    eMesh{Tri,Nothing}() = eMesh(Vector{SVector{3,Float64}}(), Vector{SVector{3,Int64}}(), nothing, nothing)
    eMesh{Nothing,Tet}() = eMesh(Vector{SVector{3,Float64}}(), nothing, Vector{SVector{4,Int64}}(), Vector{Float64}())
    eMesh{Tri,Tet}()     = eMesh(Vector{SVector{3,Float64}}(), Vector{SVector{3,Int64}}(), Vector{SVector{4,Int64}}(), Vector{Float64}())
end

"""
$(SIGNATURES)

Converts an `eMesh` to a mesh that contains only tetrahedrons.
You need to do this before adding an `eMesh` as contact geometry.
"""
as_tet_eMesh(e_mesh::eMesh{Tri,Tet}) = eMesh(e_mesh.point, nothing, e_mesh.tet, e_mesh.ϵ)
as_tet_eMesh(e_mesh::eMesh{Nothing,Tet}) = deepcopy(e_mesh)

"""
$(SIGNATURES)

Converts an `eMesh` to a mesh that contains only triangles.
You need to do this before adding an `eMesh` as contact geometry.
"""
as_tri_eMesh(e_mesh::eMesh{Tri,Tet}, is_repair::Bool=true) = eMesh(e_mesh.point, e_mesh.tri, nothing, nothing)
as_tri_eMesh(e_mesh::eMesh{Tri,Nothing}, is_repair::Bool=true) = deepcopy(e_mesh)
function as_tri_eMesh(eM::eMesh{Nothing,Tet}, is_repair::Bool=true)
	i3 = Vector{SVector{3,Int64}}()
	for k = 1:n_tet(eM)
		i_tet = eM.tet[k]
		ϵ_tet = eM.ϵ[i_tet]
		i_tet_new = sort_so_big_ϵ_last(ϵ_tet, i_tet)
		push!(i3, i_tet_new[1:3])
	end
	eM_tri = eMesh(eM.point, i3)
	if is_repair
		mesh_repair!(eM_tri)
	end
	return eM_tri
end

vertex_pos_for_tri_ind(eM::eMesh{Tri,T2}, k::Int64) where {T2} = eM.point[eM.tri[k]]
vertex_pos_for_tet_ind(eM::eMesh{T1,Tet}, k::Int64) where {T1} = eM.point[eM.tet[k]]

n_point(eM::eMesh) = length(eM.point)
n_tri(eM::eMesh) = length(eM.tri)
n_tet(eM::eMesh) = length(eM.tet)
get_tri(eM::eMesh) = eM.tri
get_tet(eM::eMesh) = eM.tet
get_point(eM::eMesh) = eM.point

function Base.empty!(e_mesh::eMesh{T1,T2}) where {T1,T2}
    empty!(e_mesh.point)
    (T1 == Nothing) || empty!(e_mesh.tri)
    (T2 == Nothing) || empty!(e_mesh.tet)
    (T2 == Nothing) || empty!(e_mesh.ϵ)
    return nothing
end

function Base.isempty(e_mesh::eMesh{T1,T2}) where {T1,T2}
    is_emp = isempty(e_mesh.point)
    if T1 == Tri
        is_emp = is_emp || isempty(e_mesh.tri)
    end
    if T2 == Tet
        is_emp = is_emp || isempty(e_mesh.tet)
        is_emp = is_emp || isempty(e_mesh.ϵ)
    end
    return is_emp
end

function Base.append!(eM_1::eMesh{T1,T2}, eM_2::eMesh{T1,T2}) where {T1,T2}
    n_1 = n_point(eM_1)
    if T1 != Nothing
        for k = eM_2.tri
            push!(eM_1.tri, k .+ n_1)
        end
    end
    if T2 != Nothing
        for k = eM_2.tet
            push!(eM_1.tet, k .+ n_1)
        end
        append!(eM_1.ϵ, eM_2.ϵ)
    end
    append!(eM_1.point, eM_2.point)
    return nothing
end

### VERIFICATION
verify_mesh(eM::eMesh{Tri,Nothing}) = verify_mesh_tri(eM)
verify_mesh(eM::eMesh{Nothing,Tet}) = verify_mesh_tet(eM)
function verify_mesh(eM::eMesh{Tri,Tet})
	eM_tri = as_tri_eMesh(eM)
    eM_tet = as_tet_eMesh(eM)
	verify_mesh_tri(eM_tri)
	verify_mesh_tet(eM_tet)
    eM_tri_2 = as_tri_eMesh(eM_tet)
    (area(eM_tri) ≈ area(eM_tri_2)) || error("surfaces where penetration extent is 0.0 is not the same as the tri surface")
	return nothing
end

function verify_mesh_tet(eM::eMesh{Nothing,Tet})
    length_tet = n_tet(eM)
    length_point = n_point(eM)
    length_ϵ = length(eM.ϵ)
    for k = 1:length_tet
        iΔ = eM.tet[k]
        point_Δ = eM.point[iΔ]
        (0.0 < volume(point_Δ)) || error("tet with negative volume")
    end
    (length_ϵ == length_point) || error("number of points not equal to number or ϵ")
    if !isempty(eM.ϵ)
        (minimum(eM.ϵ) < 0.0) && error("normalized penetration extent must be non-negative")
        (minimum(eM.ϵ) == 0.0) || error("normalized penetration extent must be 0.0 somewhere")
    end
    return nothing
end

function verify_mesh_tri(eM::eMesh{Tri,Nothing})
    for k = 1:n_tri(eM)
        iΔ = eM.tri[k]
        point_Δ = eM.point[iΔ]
    end
    return nothing
end

### MESH MANIPULATION
"""
$(SIGNATURES)

Creates a transformation matrix by creating a `basic_dh` from `extra_arguments` and then transforms the `eMesh`.
See [`basic_dh`](@ref) for how to create common transformation types.
Reflections are not allowed.
"""
function transform!(e_mesh::eMesh{T1,T2}, extra_args...) where {T1,T2}
    dh = basic_dh(extra_args...)
    point = e_mesh.point
    for k = 1:n_point(e_mesh)
        point[k] = dh_vector_mul(dh, point[k])
    end
end

struct PointObj
    p::SVector{3,Float64}
    w::Float64
    b::Bool
    k::Int64
end

function crop_mesh(eM::eMesh{Tri,Nothing}, plane::SMatrix{1,4,Float64,4})
    function add_cropped_triangle!(eM_new::eMesh{Tri,Nothing}, iΔ::SVector{3,Int64})
        function plane_dot_2(eM_new::eMesh{Tri,Nothing}, plane::SMatrix{1,4,Float64,4}, k::Int64)
            p = get_point(eM_new)[k]
            w = dot(plane[:], onePad(p))
            b = -1.0e-12 < w
            return PointObj(p, w, b, k)
        end

        o1 = plane_dot_2(eM_new, plane, iΔ[1])
        o2 = plane_dot_2(eM_new, plane, iΔ[2])
        o3 = plane_dot_2(eM_new, plane, iΔ[3])
        s_bool = o1.b + o2.b + o3.b
        (s_bool == 3) && (push!(eM_new.tri, iΔ))  # all entire triangle
        ((s_bool == 0) || (s_bool == 3)) && (return nothing)  # exit function
        (o1.b == o2.b) && ((o1, o2, o3) = (o2, o3, o1))  # put 1 and 2 on opposite sides
        (o2.b == o3.b) && ((o1, o2, o3) = (o3, o1, o2))  # put 2 and 3 on opposite sides
        (o1.b == o2.b) || (o2.b == o3.b) && error("something is wrong")  # sanity check
        push!(eM_new.point, weightPoly(o1.p, o2.p, o1.w, o2.w))
        i12 = length(get_point(eM_new))
        push!(eM_new.point, weightPoly(o2.p, o3.p, o2.w, o3.w))
        i23 = length(get_point(eM_new))
        if o2.b  # add top of triangle
            push!(eM_new.tri, SVector{3,Int64}(i12, o2.k, i23))
        else  # add bottom quadralateral
            push!(eM_new.tri, SVector{3,Int64}(o1.k, i12, i23))
            push!(eM_new.tri, SVector{3,Int64}(o1.k, i23, o3.k))
        end
    end

    eM_new = eMesh{Tri,Nothing}()
    append!(eM_new.point, eM.point)
    for iΔ = eM.tri
        add_cropped_triangle!(eM_new, iΔ)
    end
    mesh_repair!(eM_new)
    return eM_new
end

function invert!(eM::eMesh{Tri,Nothing})
    for k = 1:n_tri(eM)
        tri_k = eM.tri[k]
        eM.tri[k] = SVector{3,Float64}(tri_k[3], tri_k[2], tri_k[1])
    end
end

### MESH REPAIR
function mesh_repair!(e_mesh::eMesh{T1,T2}) where {T1,T2}
    mesh_remove_unused_points!(e_mesh)
    mesh_inplace_rekey!(e_mesh)
    mesh_remove_unused_points!(e_mesh)
    return delete_triangles!(e_mesh)
end

function remove_degenerate!(eM::eMesh)
	area_or_vol(v::SVector{3,SVector{3,Float64}}) = area(v)
	area_or_vol(v::SVector{4,SVector{3,Float64}}) = volume(v)

	tol = 1.0e-6
	for vec_ind = (eM.tet, eM.tri)
		if vec_ind != nothing
			v = [area_or_vol(eM.point[vec_ind[k]]) for k = 1:length(vec_ind)]
			max_v = maximum(v)
			i_bad = findall(v .< max_v * tol)
			deleteat!(vec_ind, i_bad)
		end
	end
end

rekey!(v::Nothing, i::Vector{Int64}) = nothing
rekey!(v::Vector{SVector{N,Int64}}, i::Vector{Int64}) where {N} = replace!(x -> i[x], v)

function mesh_inplace_rekey(e_mesh::eMesh{T1,T2}) where {T1,T2}
    e_mesh_copy = deepcopy(e_mesh)
    mesh_inplace_rekey!(e_mesh_copy)
    return e_mesh_copy
end

function mesh_inplace_rekey!(e_mesh::eMesh{T1,T2}) where {T1,T2}
    # Expresses the mesh using the minumum number of points

    function shortest_side(e_mesh::eMesh)
        shortest_side(v::Nothing) = Inf
        shortest_side(ind::Vector{SVector{N,Int64}}) where {N} =  minimum([shortest_side(e_mesh.point[k]) for k = ind])
        function shortest_side(v::SVector{N,SVector{3,Float64}}) where {N}
            d_min = Inf
            for k = 1:N
                for kk = 1:(k-1)
                    d_min = min(d_min, norm(v[k] - v[kk]))
                end
            end
            return d_min
        end

        return min(shortest_side(e_mesh.tri), shortest_side(e_mesh.tet))
    end

    function inplace_rekey(point::Vector{SVector{3,Float64}}, min_side_length::Float64)
        balltree = BallTree(point; reorder = false)
        idxs = inrange(balltree, point, min_side_length * 0.499)  # Assume that all points closer to each other than hald a side length are duplicates
        return first.(sort!.(idxs))
    end

    if !isempty(e_mesh)  # reducing over an empty collection is not allowed
        min_side_length = shortest_side(e_mesh)
        new_key = inplace_rekey(e_mesh.point, min_side_length)
        rekey!(e_mesh.tri, new_key)
        rekey!(e_mesh.tet, new_key)
        mesh_remove_unused_points!(e_mesh)
        return new_key
    end
    return nothing
end

function mesh_remove_unused_points!(e_mesh::eMesh{T1,T2}) where {T1,T2}
    truth_vector = falses(n_point(e_mesh))
    (T1 != Nothing) && (for k = e_mesh.tri; truth_vector[k] = true; end)
    (T2 != Nothing) && (for k = e_mesh.tet; truth_vector[k] = true; end)
    new_key = cumsum(truth_vector)
    rekey!(e_mesh.tri, new_key)
    rekey!(e_mesh.tet, new_key)
    ind_truth = findall(truth_vector)
    point_new = e_mesh.point[ind_truth]
    empty!(e_mesh.point)
    append!(e_mesh.point, point_new)
    if T2 != Nothing
        ϵ_new = e_mesh.ϵ[ind_truth]
        empty!(e_mesh.ϵ)
        append!(e_mesh.ϵ, ϵ_new)
    end
    return nothing
end

delete_triangles!(e_mesh::eMesh{Nothing,T2}) where {T2} = -9999
function delete_triangles!(e_mesh::eMesh{Tri,T2}) where {T2}
	# TODO: fix this to only delete triangles for connected meshes

    ### make the first index lowest
    for k = 1:n_tri(e_mesh)
        iΔ = e_mesh.tri[k]
        min_index = minimum(iΔ)
        (min_index == iΔ[1]) || (iΔ = SVector(iΔ[3], iΔ[1], iΔ[2]))
        (min_index == iΔ[1]) || (iΔ = SVector(iΔ[3], iΔ[1], iΔ[2]))
    end

    ### create a dictionary of key repition counts
    key_type = Vector{Tuple{Int64,SVector{3,Int64}}}
    dict_ind = Dict{SVector{3,Int64},key_type}()
    for k = 1:n_tri(e_mesh)
        iΔ = e_mesh.tri[k]
        sort_iΔ = sort(iΔ)
        !haskey(dict_ind, sort_iΔ) && (dict_ind[sort_iΔ] = key_type() )
        push!(dict_ind[sort_iΔ], (k, iΔ))
    end

    ### check for issues is triangle pairs
    i_delete = Vector{Int64}()
    for (key_k, val_k) = dict_ind
        length_val_k = length(val_k)
        if length_val_k == 2
            val_k1 = val_k[1]
            val_k2 = val_k[2]
            (val_k1[2] == val_k2[2]) && error("non-opposing triangles")
            push!(i_delete, val_k1[1], val_k2[1])
        elseif 3 <= length_val_k
            error("something is wrong")
        end
    end

    ### Actually delete the opposing triangles
    i_delete = sort(i_delete)
    deleteat!(e_mesh.tri, i_delete)
	return length(i_delete)
end

function sub_div_mesh(eM_ico::eMesh{Tri,T2}, n_div::Int64) where {T2}
    n_end(n::Int64) = div((n + 1) * n, 2)
    n_start(n::Int64) = 1 + n_end(n - 1)

    function sub_div_triangle(p::SVector{3,SVector{3,Float64}}, n_div::Int64)
        function sub_div_triangle_vert_index(n_div::Int64)
            i_tri = Vector{SVector{3,Int64}}()
            for k = 1:n_div
                for kk = 0:(k - 1)
                    i1 = n_start(k) + kk
                    i2 = i1 + k
                    i3 = i2 + 1
                    push!(i_tri, SVector{3,Int64}(i1, i2, i3))
                end
                for kk = 0:(k - 2)
                    i1 = n_start(k) + kk
                    i2 = i1 + k + 1
                    i3 = i2 - k
                    push!(i_tri, SVector{3,Int64}(i1, i2, i3))
                end
            end
            return i_tri
        end

        function get_new_point(n_vert::Int64, n_div::Int64, p::SVector{3,SVector{3,Float64}})
            function find_layer_1(i_point::Int64)
                i_end_layer = 1
                i_layer = 1
                while i_end_layer < i_point
                    i_layer += 1
                    i_end_layer += i_layer
                end
                norm_extent_1 = ifelse(i_point == 1, 0.0, (i_end_layer - i_point) / (i_layer - 1) )
                return i_layer, norm_extent_1
            end

            i_layer, norm_extent_1 = find_layer_1(n_vert)
            ϕ_1 = (n_div - i_layer + 1) / n_div
            ϕ_2 = (1 - ϕ_1) * norm_extent_1
            ϕ_3 = 1 - ϕ_1 - ϕ_2
            ϕ = SVector{3,Float64}(ϕ_1, ϕ_2, ϕ_3)
            return sum(p .* ϕ)
        end

        point = Vector{SVector{3,Float64}}()
        i_tri_div = sub_div_triangle_vert_index(n_div)
        for k = 1:n_end(n_div + 1)
            push!(point, get_new_point(k, n_div, p))
        end
        return eMesh(point, i_tri_div, nothing, nothing)
    end

    eM_ico_div = eMesh{Tri,Nothing}()
    for k = 1:n_tri(eM_ico)
        p = vertex_pos_for_tri_ind(eM_ico, k)
        append!(eM_ico_div, sub_div_triangle(p, n_div))
    end
    mesh_repair!(eM_ico_div)
    return eM_ico_div
end

### BASIC SHAPES
"""
$(SIGNATURES)

Outputs an `eMesh` for a half-plane.
"""
function eMesh_half_plane(plane_w::Float64=1.0, is_include_vis_sides::Bool=false)
    v1, v2, v3 = [SVector{3,Float64}(cos(theta), sin(theta), 0.0) for theta = (0.0, 2*pi/3, 4*pi/3)]
    v4 = SVector{3,Float64}(0.0, 0.0, -1.0) * plane_w
    point = [v1, v2, v3, v4]
    if is_include_vis_sides
        tri = [SVector{3,Int64}(1,2,3), SVector{3,Int64}(1,3,4),  SVector{3,Int64}(1,4,2), SVector{3,Int64}(2,4,3)]
    else
        tri = [SVector{3,Int64}(1,2,3)]
    end
    tet = [SVector{4,Int64}(4,1,2,3)]
    ϵ = [0.0, 0.0, 0.0, plane_w]
    return eMesh(point, tri, tet, ϵ)
end

"""
$(SIGNATURES)

Outputs an `eMesh` for a sphere. Larger values of `n_div` create finer discretizations.
"""
function eMesh_sphere(rad::Union{Float64,SVector{3,Float64}}=1.0, n_div::Int64=4)
    function make_icosahedron()
        φ = Base.MathConstants.golden

        v = Vector{SVector{3,Float64}}()
        for s_1 = (-1.0, +1.0)
            for s_2 = (-1.0, +1.0)
                push!(v, SVector{3,Float64}(    0.0,     s_1, φ * s_2) )
                push!(v, SVector{3,Float64}(    s_1, φ * s_2,     0.0) )
                push!(v, SVector{3,Float64}(φ * s_2,     0.0,     s_1) )
            end
        end

        n_ico_vert = 12
        d = zeros(n_ico_vert, n_ico_vert)
        for k = 1:n_ico_vert
            for kk = 1:n_ico_vert
                d[k, kk] = norm(v[k] - v[kk])
            end
        end

        v_face_vert = Vector{SVector{3,Int64}}()
        b = d .== 2.0
        for i1 = 1:n_ico_vert
            for i2 = 1:n_ico_vert
                for i3 = 1:n_ico_vert
                    if (i1 < i2 < i3) && b[i1, i2] && b[i2, i3] && b[i1, i3]
                        p1 = v[i1]
                        p2 = v[i2]
                        p3 = v[i3]
                        n = triangleNormal(p1, p2, p3)
                        c = normalize(centroid(p1, p2, p3))
                        i_face = ifelse(n ≈ c, SVector{3,Int64}(i1, i2, i3), SVector{3,Int64}(i1, i3, i2))
                        push!(v_face_vert, i_face)
                    end
                end
            end
        end

        push!(v, SVector{3,Float64}(0,0,0))
        v_tet = Vector{SVector{4,Int64}}()
        for k = 1:length(v_face_vert)
            push!(v_tet, SVector{4,Int64}(13, v_face_vert[k]...))
        end
        ϵ = vcat(zeros(n_ico_vert), 1.0)
        return eMesh(v, v_face_vert, v_tet, ϵ)
    end

    function volumize_about(eM::eMesh{Tri,Nothing})
        i_tet = Vector{SVector{4,Int64}}()
        n_vert = n_point(eM)
        n_center = n_vert + 1
        for k = 1:n_tri(eM)
            push!(i_tet, SVector{4,Int64}(n_center, eM.tri[k]...))
        end
        ϵ = vcat(zeros(n_vert), 1.0)
        point = deepcopy(eM.point)
        push!(point, zeros(SVector{3,Float64}))
        return eMesh(point, eM.tri, i_tet, ϵ)
    end

    function project_to_sphere!(eM::eMesh)
        for k = 1:n_point(eM)
            eM.point[k] = normalize(eM.point[k])
        end
        return nothing
    end

    eM_ico = make_icosahedron()
    eM_ico_div = sub_div_mesh(eM_ico, n_div)
    project_to_sphere!(eM_ico_div)

    rad = ones(SVector{3,Float64}) .* rad
    transform!(eM_ico_div, Diagonal(rad))

    return volumize_about(eM_ico_div)
end

function output_box_ind()
    oriented_box_faces = (
        SVector{4, Int64}(1,3,5,7),  # -x
        SVector{4, Int64}(2,6,4,8),  # +x
        SVector{4, Int64}(1,5,2,6),  # -y
        SVector{4, Int64}(3,4,7,8),  # +y
        SVector{4, Int64}(1,2,3,4),  # -z
        SVector{4, Int64}(5,7,6,8),  # +z
    )
    ϵ = zeros(Float64,8)
    push!(ϵ, 1.0)
    tri = Vector{SVector{3,Int64}}()
    tet = Vector{SVector{4,Int64}}()
    for k = 1:6
        bf_k = oriented_box_faces[k]
        push!(tri, bf_k[SVector{3,Int64}(1,3,4)])
        push!(tri, bf_k[SVector{3,Int64}(1,4,2)])
    end
    for k = 1:length(tri)
        tri_k = tri[k]
        push!(tet, SVector{4,Int64}(9, tri_k[1], tri_k[2], tri_k[3]))
    end
    return tri, tet, ϵ
end

"""
$(SIGNATURES)

Outputs an `eMesh` for a box.
"""
function eMesh_box(r::Union{Float64,SVector{3,Float64}}=1.0, c::SVector{3,Float64}=zeros(SVector{3,Float64}))
    point = [
        SVector{3,Float64}(-1,-1,-1),
        SVector{3,Float64}(+1,-1,-1),
        SVector{3,Float64}(-1,+1,-1),
        SVector{3,Float64}(+1,+1,-1),
        SVector{3,Float64}(-1,-1,+1),
        SVector{3,Float64}(+1,-1,+1),
        SVector{3,Float64}(-1,+1,+1),
        SVector{3,Float64}(+1,+1,+1),
        SVector{3,Float64}( 0, 0, 0),
    ]
    tri, tet, ϵ = output_box_ind()
    e_mesh = eMesh(point, tri, tet, ϵ)
    r = r .* ones(SVector{3,Float64})
    transform!(e_mesh, SMatrix{3,3,Float64,9}(r[1], 0, 0, 0, r[2], 0, 0, 0, r[3]))
    transform!(e_mesh, c)
    return e_mesh
end

const SF3 = SVector{3,Float64}
const SI3 = SVector{3,Int64}
const SI4 = SVector{4,Int64}

function create_2D_circle(rad::Float64=1.0; n::Int64=12)
    points_2D = Vector{SF3}()
    tri_2D = Vector{SI3}()
    i_center = n + 1
    θ_range = LinRange(0.0, 2 * π, n + 1)[2:end]
    for (k, θ) = enumerate(θ_range)
        s, c = sincos(θ)
        push!(points_2D, rad * SF3(c, s, 0.0))
        push!(tri_2D, SI3(k, mod1(k + 1, n), i_center))
    end
    push!(points_2D, SF3(0, 0, 0))
    return eMesh(points_2D, tri_2D)
end

function smallest_first(v::SI4)
	_, i = findmin(v)
	return SI4(v[i], v[mod1(i + 1, 4)], v[mod1(i + 2, 4)], v[mod1(i + 3, 4)])
end

function add_stuff_for_extrude!(tri::Vector{SI3}, tet::Vector{SI4}, k::Int64, i_tri_k::SI3, n_✴_2D::Int64)
	b1, b2, b3 = i_tri_k
	t4, t5, t6 = i_tri_k .+ n_✴_2D
	i_center = k + n_✴_2D * 2
	oriented_slice_faces = (
        SI4(b1,b2,t5,t4),
        SI4(b2,b3,t6,t5),
        SI4(b1,t4,t6,b3)
    )
    tri_add = Vector{SI3}()
    push!(tri_add, SI3(b1,b3,b2))
    push!(tri_add, SI3(t4,t5,t6))
    for bf_k = oriented_slice_faces
		bf_k = smallest_first(bf_k)
		push!(tri_add, bf_k[SI3(1,2,3)])
        push!(tri_add, bf_k[SI3(1,3,4)])
    end
    for tri_k = tri_add
        push!(tri, tri_k)
        push!(tet, SI4(i_center, tri_k[1], tri_k[2], tri_k[3]))
    end
end

function verify_trianges_have_same_normal(surf::eMesh{Tri,Nothing})
	n̂ = [triangleNormal(vertex_pos_for_tri_ind(surf, k)) for k = 1:n_tri(surf)]
	n̂_mean = n̂[1]
	for n̂_k = n̂
		(n̂_k ≈ n̂_mean) || error("All triangles must have the same normal.")
	end
	return n̂_mean
end

"""
$(SIGNATURES)

Extrudes a planar `eMesh` of type `eMesh{Tri,Nothing}` into a type `eMesh{Tri,Tet}` in the direction of the plane
normal. This direction is detected automatically.
The function errors if the points do not lie on a plane.
"""
function extrude_mesh(surf::eMesh{Tri,Nothing}, thick::Float64)
	n̂ = verify_trianges_have_same_normal(surf)

    tri_2D    = surf.tri
    points_2D = surf.point
	n_▲_2D = length(tri_2D )
	n_✴_2D = length(points_2D)
    point_lo = points_2D .- [n̂ * thick / 2]
    point_hi = points_2D .+ [n̂ * thick / 2]
    point_centroid = [centroid(points_2D[k]) for k = tri_2D]
    point = vcat(point_lo, point_hi, point_centroid)
    ϵ = vcat(zeros(2 * n_✴_2D), ones(n_▲_2D))
    tri = Vector{SI3}()
    tet = Vector{SI4}()
    for (k, i_tri_k) = enumerate(tri_2D)
		add_stuff_for_extrude!(tri, tet, k, i_tri_k, n_✴_2D)
    end
    eM_thick = eMesh(point, tri, tet, ϵ)
	mesh_repair!(eM_thick)
    return eM_thick
end

"""
$(SIGNATURES)

Outputs an `eMesh` for a cylinder.
"""
function eMesh_cylinder(rad::Float64=1.0, height::Float64=1.0; n::Int64=6)
	eM_circle = create_2D_circle(rad, n=n)
	return extrude_mesh(eM_circle, height)
end


#InspectDR: Base functionnality and types
#-------------------------------------------------------------------------------

#AnnotationArea
#TODO: Themes: Define StyleInfo/style?
#TODO: Themes: Vector/Dict of StyleInfo/Layout?

#==Abstracts
===============================================================================#
abstract Plot
abstract PlotCanvas

#==Constants
===============================================================================#
const DInf = convert(DReal, Inf)
const DNaN = convert(DReal, NaN)

const COLOR_TRANSPARENT = ARGB32(0, 0, 0, 0)

const COLOR_BLACK = RGB24(0, 0, 0)
const COLOR_WHITE = RGB24(1, 1, 1)

const COLOR_RED = RGB24(1, 0, 0)
const COLOR_GREEN = RGB24(0, 1, 0)
const COLOR_BLUE = RGB24(0, 0, 1)

#==Plot-level structures
===============================================================================#
#=Input structures are high-level constructs describing the plot.  They will
be broken down to lower-level structures later on.
=#

type LineAttributes <: AttributeList
	style
	width #[0, 10]
	color
end
line(;style=:solid, width=1, color=COLOR_BLACK) =
	LineAttributes(style, width, color)

#"line" constructor:
#TODO: Inadequate
#eval(genexpr_attriblistbuilder(:line, LineAttributes, reqfieldcnt=0))

type GlyphAttributes <: AttributeList #Don't use "Symbol" - name used by Julia
#==IMPORTANT:
Edge width & color taken from LineAttributes
==#
	shape #because "type" is reserved
	size #of glyph.  edge width taken from LineAttributes
	color #glyph linecolor. = nothing to match line.color
	fillcolor
end
glyph(;shape=:none, size=3, color=nothing, fillcolor=nothing) =
	GlyphAttributes(shape, size, color, fillcolor)

#"glyph" constructor:
#TODO: Inadequate
#eval(genexpr_attriblistbuilder(:glyph, GlyphAttributes, reqfieldcnt=0))

type GridAttributes <: AttributeList
	#Bool values
	vmajor
	vminor
	hmajor
	hminor
end
grid(;vmajor=false, vminor=false, hmajor=false, hminor=false) =
	GridAttributes(vmajor, vminor, hmajor, hminor)

#Dispatchable scale:
immutable AxisScale{T}
	call{T}(t::Type{AxisScale{T}}) = error("$t not supported")
	call(::Type{AxisScale{:lin}}) = new{:lin}()
	call(::Type{AxisScale{:log2}}) = new{:log2}()
	call(::Type{AxisScale{:log10}}) = new{:log10}()
	call(::Type{AxisScale{:dB10}}) = new{:dB10}()
	call(::Type{AxisScale{:dB20}}) = new{:dB20}()

	#Aliases:
	call(::Type{AxisScale{:log}}) = new{:log10}()
end
AxisScale(t::Symbol) = AxisScale{t}()

#Specifies configuration of axes:
abstract Axes

#Rectilinear axis (ex: normal cartesian +logarithmic, ...):
immutable AxesRect <: Axes
	xscale::AxisScale
	yscale::AxisScale
end
AxesRect(xscale::Symbol, yscale::Symbol) = AxesRect(AxisScale{xscale}(), AxisScale{yscale}())
AxesRect() = AxesRect(:lin, :lin)

#Curvilinear axes (ex: polar plots):
immutable AxesCurv <: Axes
	rscale::AxisScale #Radial scale could be
end
AxesCurv(rscale::Symbol=:lin) = AxesCurv(AxisScale{rscale}())

immutable AxesSmith{T} <: Axes
	ref::Float64 #Y/Zref
	call{T}(t::Type{AxesSmith{T}}, ref::Real) = error("$t not supported")
	call(::Type{AxesSmith{:Z}}, ref::Real) = new{:Z}(ref)
	call(::Type{AxesSmith{:Y}}, ref::Real) = new{:Y}(ref)
end
AxesSmith(t::Symbol, ref::Real=1) = AxesSmith{t}(ref)
AxesSmith(t::Symbol, ref::Void) = AxesSmith(t)

type Waveform{T}
	id::DisplayString
	ds::T
	line::LineAttributes
	glyph::GlyphAttributes
	ext::PExtents2D
end

#Input waveform:
typealias IWaveform Waveform{IDataset}

#Display waveform (concrete (x, y) pair):
typealias DWaveform Waveform{Vector{Point2D}}

#=TODO:
typealias DWaveformCplx Waveform{PointComplex}
to track x-values of complex data??
=#

type Annotation
	title::DisplayString
	xlabel::DisplayString
	ylabel::DisplayString
	timestamp::DisplayString
end
Annotation(;title="") = Annotation(title, "", "", Libc.strftime(time()))

type Font
	name::AbstractString
	_size::Float64
	bold::Bool
end
Font(name::AbstractString, _size::Real; bold::Bool=false) = Font(name, _size, bold)
Font(_size::Real; bold::Bool=false) = Font(defaults.fontname, _size, bold)

#Legend layout/style
type LegendLStyle
	enabled::Bool
	#autosize::Bool Auto-compute width from text.
	font::Font
	#position: left/right/...
	width::Float64
	#border::Bool
	#wborder
	vgap::Float64 #Vertical spacing between (% of text height).
	linegap::Float64 #Spacing between line and label (% of M character).
	linelength::Float64 #length of line segment to display.
end

#Plot layout
#TODO: Split Layout into "StyleInfo" - which includes Layout??
type Layout
	htitle::Float64 #Title allocation
	waxlabel::Float64 #Vertical axis label allocation (width)
	haxlabel::Float64 #Horizontal axis label allocation (height)
	wnolabels::Float64 #Width to use where no labels are displayed

	wticklabel::Float64 #y-axis values allocation (width)
	hticklabel::Float64 #x-axis values allocation (height)
	hlabeloffset::Float64
	vlabeloffset::Float64

	tframe::Float64 #Frame thickness

	wdata::Float64 #Suggested width of data (graph) area
	hdata::Float64 #Suggested height of data (graph) area

	fnttitle::Font
	fntaxlabel::Font
	fntticklabel::Font
	fnttime::Font

	grid::GridAttributes
	legend::LegendLStyle
	xlabelformat::TickLabelStyle
	ylabelformat::TickLabelStyle
	showtimestamp::Bool
end
Layout(;fontname::AbstractString=defaults.fontname, fontscale=defaults.fontscale) =
	Layout(
	20*fontscale, 20*fontscale, 20*fontscale, 45, #Title/main labels
	60*fontscale, 15*fontscale, 3, 7, 2, #Ticks/frame
	defaults.wdata, defaults.hdata,
	Font(fontname, fontscale*14, bold=true), #Title
	Font(fontname, fontscale*14), Font(fontname, fontscale*12), #Axis/Tick labels
	Font(fontname, fontscale*8), #Time stamp
	GridAttributes(true, false, true, false),
	LegendLStyle(false, Font(fontname, fontscale*12), 100.0, .25, 0.5, 20),
	TickLabelStyle(), TickLabelStyle(), defaults.showtimestamp
)

#Tag data as being part of a given coordinate system:
immutable CoordSystem{ID}; end
typealias DeviceCoord CoordSystem{:dev}
typealias NormCoord CoordSystem{:norm}
typealias DataCoord CoordSystem{:data}

immutable TypedCoord{CT<:CoordSystem}
	v::DReal
end
#Annotation coordinates can match data or be normalized to plot bounds (0 -> 1):
typealias AnnotationCoord Union{TypedCoord{NormCoord}, TypedCoord{DataCoord}}
coord(s::Symbol, v::DReal) = TypedCoord{CoordSystem{s}}(v)

type TextAnnotation
	text::DisplayString
	pt::Point2D #Data coordinates - set NaN to use offsets only
	xoffset::DReal #Normalized to [0,1] plot bounds
	yoffset::DReal #Normalized to [0,1] plot bounds
	font::Font
	color::Colorant
	angle::DReal #Degrees
	align::Symbol #tl, tc, tr, cl, cc, cr, bl, bc, br
end
#Don't use "text"... high probability of collisions when exported...
atext(text::AbstractString; x::Real=DNaN, y::Real=DNaN, xoffset=0, yoffset=0,
	font=Font(10), color=COLOR_BLACK, angle=0, align=:bl) =
	TextAnnotation(text, Point2D(x,y), xoffset, yoffset, font, color, angle, align)

type HVMarker
	vmarker::Bool #false: hmarker
	pos::DReal
	line::LineAttributes
end
vmarker(pos, line=InspectDR.line()) = HVMarker(true, pos, line)
hmarker(pos, line=InspectDR.line()) = HVMarker(false, pos, line)

#2D plot.
type Plot2D <: Plot
	layout::Layout
	axes::Axes
	annotation::Annotation

	#Plot extents (access using getextents):
	ext_data::PExtents2D #Maximum extents of data
	ext_full::PExtents2D #Defines "full" zoom when combined with ext_data
	ext::PExtents2D #Current/active extents

	data::Vector{IWaveform}
	markers::Vector{HVMarker}
	atext::Vector{TextAnnotation}

	#Display data cache:
	invalid_ddata::Bool #Is cache of display data invalid?
	display_data::Vector{DWaveform} #Clipped to current extents

	#Maximum # of x-pts in display:
	#TODO: move to layout?
	xres::Int
end

Plot2D(;title="") = Plot2D(Layout(), AxesRect(), Annotation(title=title),
	PExtents2D(), PExtents2D(), PExtents2D(), [], [], [], true, [], 1000
)

type Multiplot
	title::DisplayString

	subplots::Vector{Plot}
	ncolumns::Int

	#Default width/height of plots
	wplot::Float64
	hplot::Float64

	htitle::Float64 #Title height allocation
	fnttitle::Font
end
Multiplot(;title="", ncolumns=1, titlefont=Font(defaults.fontname, 20),
		wplot=defaults.wplot, hplot=defaults.hplot) =
	Multiplot(title, [], ncolumns, wplot, hplot, 30, titlefont)

#==Constructor-like functions
===============================================================================#

#axes function:
#Interface is a bit irregular, but should be easy to use...
#-------------------------------------------------------------------------------
function axes(a1::Symbol)
	if :smith == a1
		return AxesSmith(:Z)
	elseif :polar == a1
		return AxesCurv()
	else
		throw(MethodError(axes, (a1,)))
	end
end

function axes(a1::Symbol, a2::Symbol; ref = nothing)
	if :smith == a1
		return AxesSmith(a2, ref)
	elseif ref != nothing
		error("cannot set ref for axes(:$a1, :$a2)")
	end

	if :polar == a1
		return AxesCurv(a2)
	else
		return AxesRect(a1, a2)
	end
end

function axes(a1::Symbol, a2::Symbol, a3::Symbol)
	if :polar == a1
		return AxesCurv(a2, a3)
	else
		throw(MethodError(axes, (a1,a2,a3)))
	end
end

#==Accessors
===============================================================================#
_width(style::LegendLStyle) = style.width

getextents(d::IWaveform) = getextents(d.ds)
function getextents(dlist::Vector{IWaveform})
	result = PExtents2D(DNaN, DNaN, DNaN, DNaN)
	for d in dlist
		result = union(result, d.ext)
	end
	return result
end

#==Mutators
===============================================================================#
settitle(mplot::Plot2D, title::DisplayStringArg) =
	(mplot.annotation.title = DisplayString(title))

settitle(mplot::Multiplot, title::DisplayStringArg) =
	(mplot.title = DisplayString(title))

#=="add" interface
===============================================================================#

function _add(mp::Multiplot, plot::Plot2D)
	push!(mp.subplots, plot)
	return plot
end
_add{T<:Plot}(mp::Multiplot, ::Type{T}) = _add(mp, T())

#Set dataf1=false to overwrite optimizations for functions of 1 argument.
function _add(plot::Plot2D, x::Vector, y::Vector; id::AbstractString="", dataf1=true)
	if dataf1
		dataf1 = isincreasing(x) #Can we use optimizations?
	end
	ext = PExtents2D() #Don't care at the moment
	ds = IWaveform(id, IDataset{dataf1}(x, y), line(), glyph(), ext)
	push!(plot.data, ds)
	return ds
end

function _add(plot::Plot2D, marker::HVMarker)
	push!(plot.markers, marker)
	return marker
end

function _add(plot::Plot2D, a::TextAnnotation)
	push!(plot.atext, a)
	return a
end

#==Mapping/interpolation functions
===============================================================================#

#Mapping functions, depending on axis type:
#-------------------------------------------------------------------------------
datamap{T<:Number}(::Type{T}, ::AxisScale) = (x::T)->DReal(x)
datamap{T<:Number}(::Type{T}, ::AxisScale{:dB10}) = (x::T)->(5*log10(DReal(abs2(x))))
datamap{T<:Number}(::Type{T}, ::AxisScale{:dB20}) = (x::T)->(10*log10(DReal(abs2(x))))
datamap{T<:Number}(::Type{T}, ::AxisScale{:log2}) =
	(x::T)->(x<0? DNaN: log2(DReal(x)))
datamap{T<:Number}(::Type{T}, ::AxisScale{:log10}) =
	(x::T)->(x<0? DNaN: log10(DReal(x)))
#TODO: find a way to show negative values for log10?

datamap_rev{T<:Number}(::Type{T}, ::AxisScale) = (x::T)->DReal(x)
#NOTE: Values dBs remain in dBs for readability
datamap_rev{T<:Number}(::Type{T}, ::AxisScale{:log2}) = (x::T)->(2.0^x)
datamap_rev{T<:Number}(::Type{T}, ::AxisScale{:log10}) = (x::T)->(10.0^x)

datamap_rev{T<:Number}(v::T, s::AxisScale) = datamap_rev(T,s)(v) #One-off conversion

#Extents mapping functions, depending on axis type:
#-------------------------------------------------------------------------------
extentsmap(::AxisScale) = (x::DReal)->x #Most axis types don't need to re-map extents.
#NOTE: Extents in dBs remain extents in dBs
extentsmap(t::AxisScale{:log2}) = datamap(DReal, t)
extentsmap(t::AxisScale{:log10}) = datamap(DReal, t)

extentsmap_rev(::AxisScale) = (x::DReal)->x #Most axis types don't need to re-map extents.
#NOTE: Extents in dBs remain extents in dBs
extentsmap_rev(t::AxisScale{:log2}) = datamap_rev(DReal, t)
extentsmap_rev(t::AxisScale{:log10}) = datamap_rev(DReal, t)

#==Plot extents
===============================================================================#

function invalidate_extents(plot::Plot2D)
	#If extents are no longer valid, neither is display cache:
	plot.invalid_ddata = true
end

function invalidate_datalist(plot::Plot2D)
	plot.invalid_ddata = true
end

rescale(ext::PExtents2D, axes::Axes) = ext #Default
function rescale(ext::PExtents2D, axes::AxesRect)
	xmap = extentsmap(axes.xscale)
	ymap = extentsmap(axes.yscale)
	return PExtents2D(
		xmap(ext.xmin), xmap(ext.xmax),
		ymap(ext.ymin), ymap(ext.ymax)
	)
end
rescale_rev(ext::PExtents2D, axes::Axes) = ext #Default
function rescale_rev(ext::PExtents2D, axes::AxesRect)
	xmap = extentsmap_rev(axes.xscale)
	ymap = extentsmap_rev(axes.yscale)
	return PExtents2D(
		xmap(ext.xmin), xmap(ext.xmax),
		ymap(ext.ymin), ymap(ext.ymax)
	)
end

#Accessor:
getextents(plot::Plot2D) = plot.ext
getextents_xfrm(plot::Plot2D) = rescale(plot.ext, plot.axes)

#Full extents are always merged (ext_full expected to be incomplete):
getextents_full(plot::Plot2D) = merge(plot.ext_data, plot.ext_full)

#Set active plot extents using data coordinates:
function setextents(plot::Plot2D, ext::PExtents2D, hallowed::Bool=true, vallowed::Bool=true)
	xmin = ext.xmin; xmax = ext.xmax; ymin = ext.ymin; ymax = ext.ymax
	if !hallowed
		xmin = plot.ext.xmin
		xmax = plot.ext.xmax
	end
	if !vallowed
		ymin = plot.ext.ymin
		ymax = plot.ext.ymax
	end
	#Automatically fill-in any NaN fields, if possible:
	plot.ext = merge(getextents_full(plot), PExtents2D(xmin, xmax, ymin, ymax))
	invalidate_extents(plot)
end

#Set active plot extents using xfrm display coordinates:
setextents_xfrm(plot::Plot2D, ext::PExtents2D, hallowed::Bool=true, vallowed::Bool=true) =
	setextents(plot, rescale_rev(ext, plot.axes), hallowed, vallowed)

function setextents_full(plot::Plot2D, ext::PExtents2D)
	plot.ext_full = ext
end

#==Plot/graph bounding boxes
===============================================================================#

aspect_square(::Axes) = false
aspect_square(::AxesSmith) = true

#Returns a centered bounding box with square aspect ratio.
function squarebounds(bb::BoundingBox)
	w = width(bb); h = height(bb)
	xmin = bb.xmin; xmax = bb.xmax
	ymin = bb.ymin; ymax = bb.ymax
	if w < h
		hdim = w / 2
		c = (ymin + ymax) / 2
		ymin = c - hdim; ymax = c + hdim
	else
		hdim = h / 2
		c = (xmin + xmax) / 2
		xmin = c - hdim; xmax = c + hdim
	end
	return BoundingBox(xmin, xmax, ymin, ymax)
end

#Get bounding box of graph (plot data area):
function graphbounds(plotb::BoundingBox, lyt::Layout)
	xmin = plotb.xmin + lyt.waxlabel + lyt.wticklabel
	xmax = plotb.xmax
	xmax -= lyt.legend.enabled? _width(lyt.legend): lyt.wnolabels
	ymin = plotb.ymin + lyt.htitle
	ymax = plotb.ymax - lyt.haxlabel - lyt.hticklabel

	#Avoid division by zero, inversions, ...
	if xmin >= xmax
		c = (xmin + xmax)/2
		xmin = c - 0.5
		xmax = c + 0.5
	end
	if ymin >= ymax
		c = (ymin + ymax)/2
		ymin = c - 0.5
		ymax = c + 0.5
	end

	return BoundingBox(xmin, xmax, ymin, ymax)
end

function graphbounds(plotb::BoundingBox, lyt::Layout, axes::Axes)
	graphbb = graphbounds(plotb, lyt)
	if aspect_square(axes)
		graphbb = squarebounds(graphbb)
	end
	return graphbb
end

#Get bounding box of entire plot:
function plotbounds(lyt::Layout, graphw::Float64, graphh::Float64)
	xmax = graphw + lyt.waxlabel + lyt.wticklabel
	xmax += lyt.legend.enabled? _width(lyt.legend): lyt.wnolabels
	ymax = graphh + lyt.htitle + lyt.haxlabel + lyt.hticklabel
	return BoundingBox(0, xmax, 0, ymax)
end

#Get suggested plot bounds:
function plotbounds(lyt::Layout, axes::Axes)
	wdata = lyt.wdata; hdata = lyt.hdata
	if aspect_square(axes)
		wdata = hdata = min(wdata, hdata)
	end
	return plotbounds(lyt, wdata, hdata)
end

#==Pre-processing display data
===============================================================================#

function _reduce(input::IWaveform, ext::PExtents2D, xres_max::Integer)
	return DWaveform(input.id, _reduce(input.ds, ext, xres_max), input.line, input.glyph, input.ext)
end

_reduce(inputlist::Vector{IWaveform}, ext::PExtents2D, xres_max::Integer) =
	map((input)->_reduce(input, ext, xres_max::Integer), inputlist)

#Rescale input dataset:
#-------------------------------------------------------------------------------
function _rescale{T<:Number}(d::Vector{T}, scale::AxisScale)
	#Apparently, passing functions as arguments is not efficient in Julia.
	#-->Specializing on AxisScale, hoping to improve efficiency on dmap:
	dmap = datamap(T, scale)

	result = Array(DReal, length(d))
	for i in 1:length(d)
		result[i] = dmap(d[i])
	end
	return result
end
_rescale{T<:Number}(d::Vector{T}, scale::AxisScale{:lin}) = d #Optimization: Linear scale does not need re-scaling
_rescale{T<:IDataset}(input::T, xscale::AxisScale, yscale::AxisScale) =
	T(_rescale(input.x, xscale), _rescale(input.y, yscale))

function _rescale(input::IWaveform, xscale::AxisScale, yscale::AxisScale)
	ds = _rescale(input.ds, xscale, yscale)
	return IWaveform(input.id, ds, input.line, input.glyph, getextents(ds))
end

#Specialized on xscale/yscale for efficiency:
_rescale(inputlist::Vector{IWaveform}, xscale::AxisScale, yscale::AxisScale) =
	map((input)->_rescale(input, xscale, yscale), inputlist)

_rescale(inputlist::Vector{IWaveform}, axes::AxesRect) = _rescale(inputlist, axes.xscale, axes.yscale)

_rescale(pt::Point2D, xscale::AxisScale, yscale::AxisScale) =
	Point2D(datamap(DReal, xscale)(pt.x), datamap(DReal, yscale)(pt.y))
_rescale(pt::Point2D, axes::AxesRect) = _rescale(pt, axes.xscale, axes.yscale)

#Preprocess input dataset (rescale/reduce quantity of data/...):
#-------------------------------------------------------------------------------
#   (Updates display_data)
function preprocess_data(plot::Plot2D)
	#TODO: Find a way to preprocess x-vectors referencing same data only once?

	#Rescale data
	wfrmlist = _rescale(plot.data, plot.axes)

	#Update extents:
	plot.ext_data = rescale_rev(getextents(wfrmlist), plot.axes) #Update max extents
	setextents(plot, plot.ext) #Update extents, resolving any NaN fields.
	ext = getextents_xfrm(plot) #Read back extents, in transformed coordinates

	#Reduce data:
	plot.display_data = _reduce(wfrmlist, ext, plot.xres)
	plot.invalid_ddata = false

	#NOTE: Rescaling before data reduction is somewhat inefficient, but makes
	#      it easier to interpolate data in _reduce step.
	#TODO: Find a way to efficiently reduce before re-scaling???
end

#==High-level display functions
===============================================================================#
function update_ddata(plot::Plot2D)
	invalidate_extents(plot) #Always compute below:
	#TODO: Conditionnaly compute (only when data changed/added)?

	if plot.invalid_ddata
		preprocess_data(plot)
	end
end

#Last line


"""
Wrapper for minimization function
"""

## Minimization function
"""
Method to resize bounds if float is passed
"""
function _resize_bounds(lx, nx::Int)
    if length(lx) == 1 && nx > 1
        lx = lx*ones(nx)
    end
    return lx
end

"""
    minimize(func!::Function, x0::Vector, ng::Int; kwargs...)

Minimize function, common interface to IPOPT and SNOPT

Objective function structure:

```julia
function func!(g, x)
    <modify g>
    return <objective value to be minimized>
end
```

# Arguments
    - `func!::Function`: fitness function with signature `rosenbrock!(g, x)` and returning objective
    - `x0::Vector: initial guess
    - `ng::Int: number of constraints
    - `lx::Vector: lower bound on x
    - `ux::Vector: upper bound on x
    - `lg::Vector: lower bound on constraints
    - `ug::Vector: upper bound on constraints
    - `solver::String`: solver, "ipopt" or "snopt"
    - `options::Dict`: options used by solver
    - `sparsity::AbstractSparsityPattern`: sparsity pattern, defualt is DensePattern()
    - `derivatives::AbstractDiffMethod`: derivative method, ForwardFD() or CentralFD() or ForwardAD() or ReverseAD()
    - `outputfile::Boolean`: whether to create output file
    - `verbosity::Int`: verbosirty level, >= 0

# Returns
    - `Tuple`: xstar, fstar, info
"""
function minimize(func!::Function, x0::Vector, ng::Int; kwargs...)
    # unpack values
    lx = _assign_from_kwargs(Dict(kwargs), :lx, -Inf)
    ux = _assign_from_kwargs(Dict(kwargs), :ux, Inf)
    lg = _assign_from_kwargs(Dict(kwargs), :lg, -Inf)
    ug = _assign_from_kwargs(Dict(kwargs), :ug, 0.0)
    solver      = _assign_from_kwargs(Dict(kwargs), :solver, "ipopt")
    options     = _assign_from_kwargs(Dict(kwargs), :options,  Dict())
    sparsity    = _assign_from_kwargs(Dict(kwargs), :sparsity, DensePattern())
    derivatives = _assign_from_kwargs(Dict(kwargs), :derivatives, ForwardFD())
    outputfile  = _assign_from_kwargs(Dict(kwargs), :outputfile, false)
    verbosity   = _assign_from_kwargs(Dict(kwargs), :verbosity, 0)

    # snopt-specific settings
    lencw       = _assign_from_kwargs(Dict(kwargs), :lencw, 500)
    iSumm       = _assign_from_kwargs(Dict(kwargs), :iSumm, 6)   # print to screen if 6

    # initialize number of decision variables
    nx = length(x0)

    # resize bounds if input length is 1
    lx = _resize_bounds(lx, nx)
    ux = _resize_bounds(ux, nx)
    lg = _resize_bounds(lg, ng)
    ug = _resize_bounds(ug, ng)

    # create cache
    # if verbosity>0
    #     println("derivatives: $derivatives")
    # end
    cache = _create_cache(sparsity, derivatives, func!, nx, ng)

    # determine sparsity pattern
    rows, cols = _get_sparsity(sparsity, nx, ng)

    if cmp(solver, "ipopt") == 0
        xstar, fstar, info = minimize_ipopt(options, cache, x0, lx, ux, lg, ug, rows, cols, outputfile)
    elseif cmp(solver, "snopt") == 0
        xstar, fstar, info = minimize_snopt(options, cache, x0, lx, ux, lg, ug, rows, cols, outputfile, lencw, iSumm)
    end
    #print("typeof-cache: ")
    #println(typeof(cache))
    return xstar, fstar, info
end

"""
    _assign_from_kwargs(keyargs::Dict, keyname, default)

Utility to unpack kwargs
"""
function _assign_from_kwargs(keyargs::Dict, keyname, default)
    if haskey(keyargs, keyname)==true
        var = keyargs[keyname]
    else
        var = default
    end
    return var
end


import Geometry

"""
    struct JacobianMatrix{T} <: AbstractMatrix{T}
Jacobian matrix. Block-diagonal local matrix represents intraelement operations,
off-diagonal represents connectivity between elements.

`diag[:,iS1,:,iS2,iK]` is a square matrix d(f(u[:,iS1,iK))/d(u[:,iS2,iK])
for states iS1, iS2, element iK. Designed so that `diag[:,:,:,:,iK]` can be
in-place reshaped for matrix multiplication.

`offdiag[iFN2,iS2,iN1,iS1,iF,iK1]` is a rectangular matrix
d(f(u[iN1,iS1,iK1])/d(u[iFN2,iS2,mesh.e2e[iF,iK1]]), or effect of neighbor iF on
element iK1. Again, can in-place reshape for matrix multiplication. Note that
for upper triangular portion of global matrix (`mesh.e2e[iF,iK1]<iK1`),
the matrix `offdiag[:,:,:,:,iF,iK1]` actually yields the transpose of
operator needed. That is, the lower triangular part represents the effect of
neighbor on face nodes of iK1, and the upper triangular part represents the
transpose of the effect of neighbor's face nodes on iK1. This format therefore
only needs one array to store all off-diagonals.
"""
struct JacobianMatrix{T} <: AbstractMatrix{T}
    diag::Array{T,5}
    offdiag::Array{T,6}
    mesh::Geometry.Mesh

    function JacobianMatrix(diag::Array{T,5}, offdiag::Array{T,6}, mesh::Geometry.Mesh) where {T}
        @assert size(diag,1) == size(diag,3) "Diagonal of Jacobian must be square!"
        @assert size(diag,2) == size(diag,4) "Number of Jacobian states must be consistent!"
        @assert size(offdiag,5) == Geometry.N_FACES "Number of Jacobian neighbors does not match geometry!"
        new{T}(diag,offdiag,mesh)
    end
end

"""
    size(A::JacobianMatrix)
Returns a tuple containing the dimensions of actual operator A
"""
function Base.size(A::JacobianMatrix)
    (m,ns,n,ns2,ne) = size(A.diag)
    return (m*ns*ne, n*ns2*ne)
end
"""
    getindex(A::JacobianMatrix, i::Integer)
Linear scalar indexing into Jacobian matrix
"""
@inline function Base.getindex(A::JacobianMatrix, i::Integer)
    (sz1,sz2) = size(A)
    j = rem1(i, sz1); i = div(i, sz2, RoundUp)
    return getindex(A,i,j)
end
"""
    getindex(A::JacobianMatrix{T}, i::Integer, j::Integer)
2-dimensional scalar indexing into JacobianMatrix. Not efficient.
"""
@inline function Base.getindex(A::JacobianMatrix{T}, i::Integer, j::Integer) where {T}
    @boundscheck checkbounds(Bool, A,i,j)
    (m,ns,n,ns2,ne) = size(A.diag)
    im = rem1(i ,m ); i2 = div(i ,m , RoundUp)
    is = rem1(i2,ns); i3 = div(i2,ns, RoundUp)
    ie = rem1(i3,ne)
    jn = rem1(j ,n ); j2 = div(j ,n , RoundUp)
    js = rem1(j2,ns); j3 = div(j2,ns, RoundUp)
    je = rem1(j3,ne)
    if ie==je
        # On diagonal
        if checkbounds(Bool,A.diag, im,is,jn,js,ie)
            return @inbounds A.diag[im,is,jn,js,ie]
        end
    elseif je ∈ A.mesh.e2e[:,ie]
        iF = findfirst(x->x==je,A.mesh.e2e[:,ie])
        if je < ie
            # Lower triangular part stored directly
            iFN2 = findfirst(x->x==jn,A.mesh.ef2n[:,iF])
            iFN = 0
            if !isnothing(iFN2)
                # We need the original face node
                iFN = mesh.nfn2fn[iFN2]
            else
                # Node is not on the face => no Jacobian dependence
                return zero(T)
            end
            return A.offdiag[iFN,is,jn,js,iF,ie]
        else
            # Upper triangular part stored transposed
            iFN = findfirst(x->x==im,A.mesh.ef2n[:,iF])
            iFN2 = 0
            if !isnothing(iFN)
                # We need the neighbor's face node
                iFN2 = mesh.nfn2fn[iFN]
            else
                # Node is not on the face => no Jacobian dependence
                return zero(T)
            end
            return A.offdiag[iFN2,js,im,is,iF,ie]
        end
    else
        # Non-neighboring elements => no Jacobian dependence
        return zero(T)
    end
end
"""
    setindex!(A::JacobianMatrix, v, i::Integer)
Linear scalar indexed assignment of `JacobianMatrix`
"""
@inline function Base.setindex!(A::JacobianMatrix, v, i::Integer)
    (sz1,sz2) = size(A)
    j = rem1(i, sz1); i = div(i, sz2, RoundUp)
    setindex!(A, v, i, j)
end
"""
    setindex!(A::JacobianMatrix{T}, v, i::Integer, j::Integer)
2-dimensional scalar assignment of `JacobianMatrix`. Not efficient.
"""
@inline function Base.setindex!(A::JacobianMatrix{T}, v, i::Integer, j::Integer) where {T}
    @boundscheck checkbounds(Bool, A,i,j)
    (m,ns,n,ns2,ne) = size(A.diag)
    im = rem1(i ,m ); i2 = div(i ,m , RoundUp)
    is = rem1(i2,ns); i3 = div(i2,ns, RoundUp)
    ie = rem1(i3,ne)
    jn = rem1(j ,n ); j2 = div(j ,n , RoundUp)
    js = rem1(j2,ns); j3 = div(j2,ns, RoundUp)
    je = rem1(j3,ne)
    if ie==je
        # On diagonal
        if checkbounds(Bool,A.diag, im,is,jn,js,ie)
            @inbounds setindex!(A.diag, v, im,is,jn,js,ie)
        end
    elseif je ∈ A.mesh.e2e[:,ie]
        iF = findfirst(x->x==je,A.mesh.e2e[:,ie])
        if je < ie
            # Lower triangular part stored directly
            iFN2 = findfirst(x->x==jn,A.mesh.ef2n[:,iF])
            iFN = 0
            if !isnothing(iFN2)
                # We need the original face node
                iFN = mesh.nfn2fn[iFN2]
            else
                if !iszero(v)
                    # Node is not on the face => no Jacobian dependence
                    throw(ArgumentError("Cannot set off-diagonal entry ($i,$j) to non-zero value $v"))
                else
                    return v
                end
            end
            return setindex!(A.offdiag, v, iFN,is,jn,js,iF,ie)
        else
            # Upper triangular part stored transposed
            iFN = findfirst(x->x==im,A.mesh.ef2n[:,iF])
            iFN2 = 0
            if !isnothing(iFN)
                # We need the neighbor's face node
                iFN2 = mesh.nfn2fn[iFN]
            else
                if !iszero(v)
                    # Node is not on the face => no Jacobian dependence
                    throw(ArgumentError("Cannot set off-diagonal entry ($i,$j) to non-zero value $v"))
                else
                    return v
                end
            end
            return setindex!(A.offdiag, v, iFN2,js,im,is,iF,ie)
        end
    else
        # Non-neighboring elements => no Jacobian dependence
        if !iszero(v)
            throw(ArgumentError("Cannot set off-diagonal entry ($i,$j) to non-zero value $v"))
        else
            return v
        end
    end
end

(==)(A::JacobianMatrix, B::JacobianMatrix) = (A.diag == B.diag && A.offdiag == B.offdiag)
(-)(A::JacobianMatrix) = JacobianMatrix(-A.diag, -A.offdiag, A.mesh)
function (+)(A::JacobianMatrix, B::JacobianMatrix)
    @assert A.mesh === B.mesh
    JacobianMatrix(A.diag+B.diag, A.offdiag+B.offdiag, A.mesh)
end
function (-)(A::JacobianMatrix, B::JacobianMatrix)
    @assert A.mesh === B.mesh
    JacobianMatrix(A.diag-B.diag,A.offdiag-B.offdiag,mesh)
end
# TODO: Matrix addition with LocalMatrix as well?
(*)(c::Number, A::JacobianMatrix) = JacobianMatrix(c*A.diag, c*A.offdiag, A.mesh)
(*)(A::JacobianMatrix, c::Number) = JacobianMatrix(c*A.diag, c*A.offdiag, A.mesh)
(/)(A::JacobianMatrix, c::Number) = JacobianMatrix(A.diag / c, A.offdiag / c, A.mesh)
(*)(A::JacobianMatrix, B::JacobianMatrix) = error("Too expensive an operation!")

" Store `faces` on `volume` array according to given mask: vol[mask] += face "
function distribute_face_to_nodes!(volume, faces, mask)
    volume_shaped = reshape(volume, size(volume,1), prod(size(volume)[2:end])))
    faces_shaped = reshape(faces, size(faces,1), prod(size(faces)[2:end]))
    for j = 1:size(volume_shaped,2)
        for i = 1:size(mask,1)
            volume_shaped[mask[i],j] += faces_shaped[i,j]
        end
    end
end
" Collect `volume` into `faces` array according to given mask: face = vol[mask] "
function collect_face_from_nodes!(faces, volume, mask)
    volume_shaped = reshape(volume, size(volume,1), prod(size(volume)[2:end])))
    faces_shaped = reshape(faces, size(faces,1), prod(size(faces)[2:end]))
    for j = 1:size(volume_shaped,2)
        for i = 1:size(mask,1)
            faces_shaped[i,j] = volume_shaped[mask[i],j]
        end
    end
end

function (*)(A::JacobianMatrix{T1}, x::AbstractVector{T2}) where {T1, T2}
    (m,ns,n,ns2,ne) = size(A.diag)
    b = zeros(typeof(zero(T1)*zero(T2)), m*ns*ne)
    b_shaped = reshape(b, m,ns,ne)
    b_mult   = reshape(b, m*ns,ne)
    x_shaped = reshape(x, n,ns2,ne)
    x_mult   = reshape(x, n*ns2,ne)
    faces_mult = zeros(typeof(zero(T1)*zero(T2)), A.mesh.n_face_nodes*ns)
    faces_shaped = reshape(faces, A.mesh.n_face_nodes, ns)
    for iK = 1:ne
        # Diagonal of A
        @views Adiag = reshape(A.diag[:,:,:,:,iK], m*ns, n*ns2)
        @views b_mult[:,iK] .+= Adiag*x_mult[:,iK]
        # Off-diagonal of A
        for iF = 1:Geometry.N_FACES
            nK = A.mesh.e2e[iF,iK]
            if nK > 0
                Aoff = reshape(@view A.offdiag[:,:,:,:,iF,iK], A.mesh.n_faces_nodes*ns,m*ns)
                nF = A.mesh.e2f[iF,iK]
                if nK < iK
                    # Lower triangular part stored directly
                    @views faces_mult .= Aoff*x_mult[:,iK]
                    @views distribute_face_to_nodes!(b_shaped[:,:,iK], faces_shaped, A.mesh.ef2n[:,iF])
                else
                    # Upper triangular part stored transposed
                    @views collect_face_from_nodes!(faces_shaped, x_shaped[:,:,nK], A.mesh.ef2n[:,nF])
                    @views b_mult[:,iK] .+= Aoff'*faces_mult
                end
            end
        end
    end
    return b
end
function (*)(A::JacobianMatrix{T1}, x::SolutionVector{T2}) where {T1, T2}
    (m,ns,n,ns2,ne) = size(A.diag)
    b_shaped = zeros(typeof(zero(T1)*zero(T2)), m,ns,ne)
    b_mult = reshape(b, m*ns,ne)
    x_mult = reshape(x.data, n*ns2,ne)
    faces_mult = zeros(typeof(zero(T1)*zero(T2)), A.mesh.n_face_nodes*ns)
    faces_shaped = reshape(faces, A.mesh.n_face_nodes, ns)
    @views for iK = 1:ne
        # Diagonal of A
        Adiag = reshape(A.diag[:,:,:,:,iK], m*ns, n*ns2)
        b_mult[:,iK] .+= Adiag*x_mult[:,iK]
        # Off-diagonal of A
        for iF = 1:Geometry.N_FACES
            nK = A.mesh.e2e[iF,iK]
            if nK > 0
                Aoff = reshape(A.offdiag[:,:,:,:,iF,iK], A.mesh.n_faces_nodes*ns,m*ns)
                nF = A.mesh.e2f[iF,iK]
                if nK < iK
                    # Lower triangular part stored directly
                    faces_mult .= Aoff*x_mult[:,iK]
                    distribute_face_to_nodes!(b_shaped[:,:,iK], faces_shaped, A.mesh.ef2n[:,iF])
                else
                    # Upper triangular part stored transposed
                    collect_face_from_nodes!(faces_shaped, x.data[:,:,nK], A.mesh.ef2n[:,nF])
                    b_mult[:,iK] .+= Aoff'*faces_mult
                end
            end
        end
    end
    return SolutionVector(b_shaped)
end
"""
    mul!(y::SolutionVector, A::JacobianMatrix, x::SolutionVector, α::Number, β::Number)
Return A x α + y β by overwriting y
"""
function LinearAlgebra.mul!(y::SolutionVector{T1}, A::JacobianMatrix, x::SolutionVector, α::Number, β::Number) where {T1}
    (m,ns,n,ns2,ne) = size(A.diag)
    (nx,ns2x,nex) = true_size(x)
    (my,nsy,ney) = true_size(y)
    @assert m == my && ns  == nsy  && ne == ney "JacobianMatrix $(size(A.diag)), SolutionVector $(true_size(y)) mismatched size"
    @assert n == nx && ns2 == ns2x && ne == nex "JacobianMatrix $(size(A.diag)), SolutionVector $(true_size(x)) mismatched size"
    y_shaped = reshape(y.data, m*ns*ne)
    y_mult   = reshape(y.data, m*ns,ne)
    x_shaped = reshape(x.data, n,ns2,ne)
    x_mult   = reshape(x.data, n*ns2,ne)
    faces_mult = zeros(T1, A.mesh.n_face_nodes*ns)
    faces_shaped = reshape(faces, A.mesh.n_face_nodes, ns)
    @views for iK = 1:ne
        # Diagonal of A
        Adiag = reshape(A.diag[:,:,:,:,iK], m*ns, n*ns2)
        # y[iK] = Adiag*x[iK]*α + y[iK]*β
        mul!(y_mult[:,iK], Adiag, x_mult[:,iK], α, β)
        # Off-diagonal of A
        for iF = 1:Geometry.N_FACES
            nK = A.mesh.e2e[iF,iK]
            if nK > 0
                Aoff = reshape(A.offdiag[:,:,:,:,iF,iK], A.mesh.n_faces_nodes*ns,m*ns)
                nF = A.mesh.e2f[iF,iK]
                if nK < iK
                    # Lower triangular part stored directly
                    faces_mult .= Aoff*x_mult[:,iK]
                    # y[iK] += Aoff*faces[iK]*α
                    distribute_face_to_nodes!(y_shaped[:,:,iK], faces_shaped, A.mesh.ef2n[:,iF])
                else
                    # Upper triangular part stored transposed
                    collect_face_from_nodes!(faces_shaped, x_shaped[:,:,nK], A.mesh.ef2n[:,nF])
                    # y[iK] += Aoff'*faces[nK]*α
                    mul!(y_mult[:,iK], Aoff', faces_mult, α, 1.0)
                end
            end
        end
    end
end

"""
    ldiv!(A::JacobianMatrix, x::SolutionVector)
In-place linear solve A\\x using numerical iterative solver based on
preconditioned GMRES.
TODO: Decide actual algorithm and create production-level version of code.
"""
function LinearAlgebra.ldiv!(A::JacobianMatrix, x::SolutionVector)
    println("my ldiv!")
    (m,ns,n,ns2,ne) = size(A.diag)
    (nx,ns2x,nex) = true_size(x)
    @assert nx == m == n && ns2x == ns2 == ns && ne == nex "JacobianMatrix A $(size(A.diag)) cannot \\ x $(true_size(x))"
    # TODO: program the solver here
    error("Not yet programmed!")
end
function \(A::LocalMatrix, x::SolutionVector)
    println("my \\")
    (m,ns,n,ns2,ne) = size(A.diag)
    (nx,ns2x,nex) = true_size(x)
    @assert nx == m == n && ns2x == ns2 == ns && ne == nex "JacobianMatrix A $(size(A.diag)) cannot \\ x $(true_size(x))"
    b = deepcopy(x)
    LinearAlgebra.ldiv!(A, b)
end

function L2_error!(u::SolutionVector, u_true::SolutionVector, M::JacobianMatrix)
    u_true -= u
    return L2_norm(u_true, M)
end
function L2_norm(u::SolutionVector, M::JacobianMatrix)
    Mu = M*u
    (n,ns,ne) = true_size(Mu)
    l2_norms = zeros(ns)
    for iK = 1:ne
        for iS = 1:ns
            @views l2_norms[iS] += u.data[:,iS,iK]'*Mu.data[:,iS,iK]
        end
    end
    return l2_norms
end