"""
`module  JAC.PhotoExcitation`
    ... a submodel of JAC that contains all methods for computing photo-excitation properties between some initial
        and final-state multiplets.
"""
module PhotoExcitation

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

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

        + multipoles              ::Array{EmMultipole,1}         ... Specifies the multipoles of the radiation field that are to be included.
        + gauges                  ::Array{UseGauge,1}            ... Specifies the gauges to be included into the computations.
        + calcForStokes           ::Bool                         ... True, if the excitation cross sections are to be calculated (and false otherwise)
                                                                     for given Stokes parameter of the incident plane-wave photons.
        + calcPhotonDm            ::Bool                         ... True, if the photon density matrix of a subsequently emitted fluorescence photon is to be
                                                                     calculated and false otherwise.
        + calcTensors             ::Bool                         ... True, if the statistical tensors of the excited atom 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).
        + photonEnergyShift       ::Float64                      ... An overall energy shift for all photon energies.
        + mimimumPhotonEnergy     ::Float64                      ... minimum transition energy for which (photon) transitions are included into the
                                                                     computation.
        + maximumPhotonEnergy     ::Float64                      ... maximum transition energy for which (photon) transitions are included.
        + stokes                  ::ExpStokes                    ... Stokes parameters of the incident radiation.
    """
    struct Settings
        multipoles                ::Array{EmMultipole,1}
        gauges                    ::Array{UseGauge,1}
        calcForStokes             ::Bool
        calcPhotonDm              ::Bool
        calcTensors               ::Bool
        printBeforeComputation    ::Bool
        selectLines               ::Bool
        selectedLines             ::Array{Tuple{Int64,Int64},1}
        photonEnergyShift         ::Float64
        mimimumPhotonEnergy       ::Float64
        maximumPhotonEnergy       ::Float64
        stokes                    ::ExpStokes
    end

    """
    `PhotoExcitation.Settings()`  ... 'empty' constructor for the default values of photo-excitation line computations
    """
    function Settings()
        Settings(EmMultipole[], UseGauge[], false, false, false, false, false, Array{Tuple{Int64,Int64},1}[], 0., 0., 0.)
    end

    # `Base.show(io::IO, settings::PhotoExcitation.Settings)`  ... prepares a proper printout of the variable settings::PhotoExcitation.Settings.
    function Base.show(io::IO, settings::PhotoExcitation.Settings)
        println(io, "multipoles:               $(settings.multipoles)  ")
        println(io, "use-gauges:               $(settings.gauges)  ")
        println(io, "calcForStokes:            $(settings.calcForStokes)  ")
        println(io, "calcPhotonDm:             $(settings.calcPhotonDm)  ")
        println(io, "calcTensor:               $(settings.calcTensor)  ")
        println(io, "printBeforeComputation:   $(settings.printBeforeComputation)  ")
        println(io, "selectLines:              $(settings.selectLines)  ")
        println(io, "selectedLines:            $(settings.selectedLines)  ")
        println(io, "photonEnergyShift:        $(settings.photonEnergyShift)  ")
        println(io, "mimimumPhotonEnergy:      $(settings.mimimumPhotonEnergy)  ")
        println(io, "maximumPhotonEnergy:      $(settings.maximumPhotonEnergy)  ")
        println(io, "stokes:                   $(settings.stokes)  ")
    end

    """
    `struct  PhotoExcitation.Line`
        ... defines a type for a photo-excitation line that may include the definition of sublines and their
            corresponding amplitudes.

        + initialLevel   ::Level                       ... initial-(state) level
        + finalLevel     ::Level                       ... final-(state) level
        + omega          ::Float64                     ... Transition frequency of this line; can be shifted w.r.t. the level energies.
        + crossSection   ::EmProperty                  ... Total cross section of this line.
        + staTensor      ::Array{TensorComp,1}         ... Array of statistical tensor components rho_kq
        + hasSublines    ::Bool                        ... Determines whether the individual sublines are defined in terms of their
                                                           multipolarity, amplitude, or not; cf. PhotoEmission.Channel
        + channels       ::Array{PhotoEmission.Channel,1}  ... List of radiative (photon) channels
    """
    struct  Line
        initialLevel     ::Level
        finalLevel       ::Level
        omega            ::Float64
        crossSection     ::EmProperty
        staTensor        ::Array{TensorComp,1}
        hasSublines      ::Bool
        channels         ::Array{PhotoEmission.Channel,1}
    end

    """
    `PhotoExcitation.Line(initialLevel::Level, finalLevel::Level, photonRate::Float64)`
        ... constructor an photo-excitation line between a specified initial and final level.
    """
    function Line(initialLevel::Level, finalLevel::Level, omega::Float64, crossSection::EmProperty)
       Line(initialLevel, finalLevel, omega, crossSection, EmProperty(0., 0.), false, PhotoEmission.Channel[])
    end

    # `Base.show(io::IO, line::PhotoExcitation.Line)`  ... prepares a proper printout of the variable line::PhotoExcitation.Line.
    function Base.show(io::IO, line::PhotoExcitation.Line)
        println(io, "initialLevel:         $(line.initialLevel)  ")
        println(io, "finalLevel:           $(line.finalLevel)  ")
        println(io, "omega:                $(line.omega)  ")
        println(io, "crossSection:         $(line.crossSection)  ")
        println(io, "staTensor:            $(line.staTensor)  ")
        println(io, "hasSublines:          $(line.hasSublines)  ")
        println(io, "channels:             $(line.channels)  ")
    end

    """
    `PhotoExcitation.computeAmplitudesProperties(line::PhotoExcitation.Line, grid::Radial.Grid, settings::PhotoExcitation.Settings)`
        ... to compute all amplitudes and properties of the given line; a line::PhotoExcitation.Line is returned for which
            the amplitudes and properties have now been evaluated.
    """
    function  computeAmplitudesProperties(line::PhotoExcitation.Line, grid::Radial.Grid, settings::PhotoExcitation.Settings)
        newChannels = PhotoEmission.Channel[]
        for  channel  in  line.channels
            ##x println("channel = $channel ")
            amplitude = PhotoEmission.amplitude("absorption", channel.multipole, channel.gauge, line.omega, line.finalLevel, line.initialLevel, grid)
            push!( newChannels, PhotoEmission.Channel( channel.multipole, channel.gauge, amplitude) )
        end
        # Calculate the crossSection and stastistical tensors if requested
        crossSection = EmProperty(-1., -1.)    # Cross sections for linear-polarized plane-wave photons
        staTensor    = TensorComp[]
        if  settings.calcTensors
            push!(staTensor, TensorComp(0, 0, 1.))
        end
        line = PhotoExcitation.Line( line.initialLevel, line.finalLevel, line.omega, crossSection, staTensor, true, newChannels)
        return( line )
    end

    """
    `PhotoExcitation.computeCrossSection(line::PhotoExcitation.Line, stokes::ExpStokes)`
        ... to compute the excitation cross section for the excitation of unpolarized atoms by plane-wave photons, whose polarization
            is described by the given (experimental) Stokes parameters. A cs::EmProperty is returned.
    """
    function  computeCrossSection(line::PhotoExcitation.Line, stokes::ExpStokes)
        csCoulomb = -2.;    csBabushkin = -2.
        cs = EmProperty( csCoulomb, csBabushkin )
        return( cs )
    end

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

    """
    `PhotoExcitation.computeMatrix_obsolete(Mp::EmMultipole, gauge::EmGauge, omega::Float64, finalBasis::Basis, initialBasis::Basis,
                                            grid::Radial.Grid, settings::PhotoExcitation.Settings)`
        ... to compute the transition matrix (O^Mp_rs) = (<finalCSF_r|| O^Mp (omega; gauge) ||initialCSF_s>) of the Mp multiplet field
            for the given transition energy and gauge, and between the CSF_r from the finalBasis and the CSF_s from the initialBasis.
            A (non-quadratic) matrix::Array{Float64,2} with dimensions [length(finalBasis.csfs) x length(initialBasis.csfs)] is returned.
            Note that this transition matrix is specific to transitions of the given multipolarity and omega.
    """
    function computeMatrix_obsolete(Mp::EmMultipole, gauge::EmGauge, omega::Float64, finalBasis::Basis, initialBasis::Basis,
                                    grid::Radial.Grid, settings::PhotoExcitation.Settings)
        nf = length(finalBasis.csfs);    ni = length(initialBasis.csfs)

        print("Compute radiative T^L matrix of dimension $nf x $ni for the given initial- and final-state bases ...")
        matrix = zeros(Float64, nf, ni)
        for  r = 1:nf
            for  s = 1:ni
                wa = compute("angular coefficients: 1-p, Ratip2013", Mp.L, finalBasis.csfs[r], initialBasis.csfs[s])
                me = 0.
                for  coeff in wa
                    jj = Basics.subshell_2j(finalBasis.orbitals[coeff.a].subshell)
                    me = me + coeff.T * sqrt( jj + 1) * InteractionStrength.multipole(Mp, gauge, omega, finalBasis.orbitals[coeff.a],
                                                                                initialBasis.orbitals[coeff.b], grid)
                end
                matrix[r,s] = me
            end
        end
        println("   ... done.")
        return( matrix )
    end

    """
    `PhotoExcitation.computeStatisticalTensor(k::Int64, q::Int64, line::PhotoExcitation.Line, stokes::ExpStokes)`
        ... to compute the statistical tensor (component) rho_{k,q} of the final level for the excitation of unpolarized atoms by
            plane-wave photons, whose polarization is described by the given (experimental) Stokes parameters.
            A rho_kq::EmProperty is returned.
    """
    function  computeStatisticalTensor(k::Int64, q::Int64, line::PhotoExcitation.Line, stokes::ExpStokes)
        if  k == q  ||  k == -q   rhoCoulomb = -3.;    rhoBabushkin = -3.    else   rhoCoulomb = 0.;    rhoBabushkin = 0.    end
        rho = EmProperty( rhoCoulomb, rhoBabushkin )
        return( rho )
    end

    """
    `PhotoExcitation.determineChannels(finalLevel::Level, initialLevel::Level, settings::PhotoExcitation.Settings)`
        ... to determine a list of PhotoExcitation.Channel for a transitions from the initial to final level and by taking
            into account the particular settings of for this computation; an Array{PhotoExcitation.Channel,1} is returned.
    """
    function determineChannels(finalLevel::Level, initialLevel::Level, settings::PhotoExcitation.Settings)
        channels = PhotoEmission.Channel[];
        symi = LevelSymmetry(initialLevel.J, initialLevel.parity);    symf = LevelSymmetry(finalLevel.J, finalLevel.parity)
        for  mp in settings.multipoles
            if   AngularMomentum.isAllowedMultipole(symi, mp, symf)
                hasMagnetic = false
                for  gauge in settings.gauges
                    # Include further restrictions if appropriate
                    if     string(mp)[1] == 'E'  &&   gauge == Basics.UseCoulomb      push!(channels, PhotoEmission.Channel(mp, Basics.Coulomb,   0.) )
                    elseif string(mp)[1] == 'E'  &&   gauge == Basics.UseBabushkin    push!(channels, PhotoEmission.Channel(mp, Basics.Babushkin, 0.) )
                    elseif string(mp)[1] == 'M'  &&   !(hasMagnetic)               push!(channels, PhotoEmission.Channel(mp, Basics.Magnetic,  0.) );
                                                        hasMagnetic = true;
                    end
                end
            end
        end
        ##x println("PhotoExcitation.determineChannels-aa: channels = $channels ")
        return( channels )
    end

    """
    `PhotoExcitation.determineLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, settings::PhotoExcitation.Settings)`
        ... to determine a list of photo-excitation Line's for transitions between the levels from the given initial- and
            final-state multiplets and by taking into account the particular selections and settings for this computation;
            an Array{PhotoExcitation.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::PhotoExcitation.Settings)
        if    settings.selectLines    selectLines   = true;   selectedLines = Basics.determine("selected lines", settings.selectedLines)
        else                          selectLines   = false
        end

        lines = PhotoExcitation.Line[]
        for  i = 1:length(initialMultiplet.levels)
            for  f = 1:length(finalMultiplet.levels)
                if  selectLines  &&  !(haskey(selectedLines, (i,f)) )    continue   end
                ##x println("PhotoExcitation.determineLines-aa: angular i = $i, f = $f")
                omega    = abs( finalMultiplet.levels[f].energy - initialMultiplet.levels[i].energy) + settings.photonEnergyShift
                if   omega == 0.  ||  omega < settings.mimimumPhotonEnergy  ||  omega > settings.maximumPhotonEnergy    continue   end

                channels = PhotoExcitation.determineChannels(finalMultiplet.levels[f], initialMultiplet.levels[i], settings)
                if   length(channels) == 0   continue   end
                push!( lines, PhotoExcitation.Line(initialMultiplet.levels[i], finalMultiplet.levels[f], omega, EmProperty(0., 0.),
                                                   TensorComp[], true, channels) )
            end
        end
        return( lines )
    end

    """
    `PhotoExcitation.displayLines(lines::Array{PhotoExcitation.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{PhotoExcitation.Line,1})
        println(" ")
        println("  Selected photo-excitation lines:")
        println(" ")
        println("  ", TableStrings.hLine(120))
        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(14, "Energy"; na=4);
        sb = sb * TableStrings.center(14, TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.flushleft(30, "List of multipoles"; na=4);             sb = sb * TableStrings.hBlank(34)
        println(sa);    println(sb);    println("  ", TableStrings.hLine(120))
        #
        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)
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", line.omega)) * "    "
            mpGaugeList = Tuple{Basics.EmMultipole,Basics.EmGauge}[]
            for  i in 1:length(line.channels)
                push!( mpGaugeList, (line.channels[i].multipole, line.channels[i].gauge) )
            end
            ##x println("PhotoExcitation.diplayLines-ad: mpGaugeList = ", mpGaugeList)
            sa = sa * TableStrings.multipoleGaugeTupels(50, mpGaugeList)
            println( sa )
        end
        println("  ", TableStrings.hLine(120))
        #
        return( nothing )
    end

    """
    `PhotoExcitation.displayCrossSections(stream::IO, lines::Array{PhotoExcitation.Line,1}, settings::PhotoExcitation.Settings)`
        ... to list all results, energies, cross sections, etc. of the selected lines. A neat table is printed but nothing
            is returned otherwise.
    """
    function  displayCrossSections(stream::IO, lines::Array{PhotoExcitation.Line,1}, settings::PhotoExcitation.Settings)
        println(stream, " ")
        println(stream, "  Photo-excitation cross sections for (completely) linearly-polarized plane-wave photons:")
        println(stream, " ")
        println(stream, "  ", TableStrings.hLine(105))
        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(14, "Energy"   ; na=3);
        sb = sb * TableStrings.center(14,TableStrings.inUnits("energy"); na=3)
        sa = sa * TableStrings.center(10, "Multipoles"; na=3);                        sb = sb * TableStrings.hBlank(14)
        sa = sa * TableStrings.center(30, "Cou -- Cross section -- Bab"; na=2);
        sb = sb * TableStrings.center(30, TableStrings.inUnits("cross section")*"          "*
                                              TableStrings.inUnits("cross section"); na=2)
        println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(105))
        #
        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)
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", line.omega)) * "    "
            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)
            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))   * "    "
            ##x sa = sa * @sprintf("%.6e", line.anisotropy.Coulomb)      * "    "
            ##x sa = sa * @sprintf("%.6e", line.anisotropy.Babushkin)    * "    "
            println(stream, sa)
        end
        println(stream, "  ", TableStrings.hLine(105))
        #
        #
        if  settings.calcForStokes
            stokes = settings.stokes
            println(stream, " ")
            println(stream, "  Photo-excitation cross sections for incident plane-wave photons with given $stokes:")
            println(stream, " ")
            println(stream, "  ", TableStrings.hLine(105))
            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(14, "Energy"   ; na=3);
            sb = sb * TableStrings.center(14,TableStrings.inUnits("energy"); na=3)
            sa = sa * TableStrings.center(10, "Multipoles"; na=3);                        sb = sb * TableStrings.hBlank(14)
            sa = sa * TableStrings.center(30, "Cou -- Cross section -- Bab"; na=2);
            sb = sb * TableStrings.center(30, TableStrings.inUnits("cross section")*"          "*
                                                  TableStrings.inUnits("cross section"); na=2)
            println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(105))
            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)
                sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", line.omega)) * "    "
                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)
                crossSection = PhotoExcitation.computeCrossSection(line, stokes)
                sa = sa * @sprintf("%.6e", Defaults.convertUnits("cross section: from atomic", crossSection.Coulomb))     * "    "
                sa = sa * @sprintf("%.6e", Defaults.convertUnits("cross section: from atomic", crossSection.Babushkin))   * "    "
                println(stream, sa)
            end
            println(stream, "  ", TableStrings.hLine(105))
        end
        #
        #
        if  settings.calcTensors
            stokes = settings.stokes
            println(stream, " ")
            println(stream, "  Statistical tensors rho_kq  and alignment parameters A_kq for the excitation by incident plane-wave photons")
            println(stream, "  with given $stokes:")
            println(stream, " ")
            println(stream, "  ", TableStrings.hLine(145))
            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(14, "Energy"   ; na=3);
            sb = sb * TableStrings.center(14,TableStrings.inUnits("energy"); na=3)
            sa = sa * TableStrings.center(10, "Multipoles"; na=1);                        sb = sb * TableStrings.hBlank(12)
            sa = sa * TableStrings.center(12, "     k    q    "; na=0);                   sb = sb * TableStrings.hBlank(14)
            sa = sa * TableStrings.center(26, "Cou -- rho_kq -- Bab"; na=5);              sb = sb * TableStrings.hBlank(14)
            sa = sa * TableStrings.center(26, "Cou --  A_kq  -- Bab"; na=2);              sb = sb * TableStrings.hBlank(14)
            println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(145))
            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)
                sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", line.omega)) * "    "
                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)
                println(stream, sa);   sc = TableStrings.hBlank( length(sa)-1 )
                tc00   = PhotoExcitation.computeStatisticalTensor(0, 0, line, stokes)
                for  k = 2:2
                    for  q = -k:k
                        tc = PhotoExcitation.computeStatisticalTensor(k, q, line, stokes)
                        if  abs(tc.Coulomb) == abs(tc.Babushkin) == 0.    continue    end
                        #
                        aCoulomb = tc.Coulomb / tc00.Coulomb;    aBabushkin = tc.Babushkin / tc00.Babushkin
                        sd = sc * TableStrings.level(k) * TableStrings.level(q) * "   "
                        sd = sd * @sprintf("%.6e", tc.Coulomb)     * "  "
                        sd = sd * @sprintf("%.6e", tc.Babushkin)   * "    "
                        sd = sd * @sprintf("%.6e", aCoulomb)       * "  "
                        sd = sd * @sprintf("%.6e", aBabushkin)     * "    "
                        println(stream, sd)
                    end
                end
            end
            println(stream, "  ", TableStrings.hLine(145))
        end
        #
        #
        if  settings.calcTensors   &&   settings.calcPhotonDm
            stokes = settings.stokes
            println(stream, " ")
            println(stream, "  Reduced density matrix of the fluorescence photons at selected solid angles for the prior excitation by incident plane-wave photons")
            println(stream, "  with given $stokes:")
            println(stream, " ")
            println(stream, "  Not yet implemented !!")
        end
        #
        return( nothing )
    end

end # module


module Microclimate

using Unitful, NCDatasets
using Unitful: W, m, °C, hr, mol, K, s, J, Mg, g, kg, L, kPa, Pa

export AbstractEnvironment,
       AbstractMicroclimEnvironment,
       AbstractMicroclimGrid, MicroclimGrid,
       AbstractMicroclimPoint, MicroclimPoint,
       AbstractMicroclimInstant, MicroclimInstant,
       LinearLayerInterpolator, LinearLayerInterpolators

export radiation, snowdepth, airtemperature, relhumidity, windspeed,
       soiltemperature, soilwaterpotential, soilwatercontent

export mean_soiltemperature, mean_soilwaterpotential, mean_soilwatercontent

export weightedmean, layermax

export load_grid, load_point

radiation_type = typeof(0.1W*m^-2)
snowdepth_type = typeof(0.1m)
airtemperature_type = typeof(0.1K)
relhumidity_type = typeof(0.001)
windspeed_type = typeof(0.1m*s^-1)
soiltemperature_type = typeof(0.1K)
soilwaterpotential_type = typeof(0.0kPa)
soilwatercontent_type = typeof(0.001)

abstract type AbstractEnvironment end
abstract type AbstractMicroclimEnvironment{S,I,A,R} <: AbstractEnvironment end
abstract type AbstractMicroclimGrid{S,I,A,R} <: AbstractMicroclimEnvironment{S,I,A,R} end
abstract type AbstractMicroclimPoint{S,I,A,R} <: AbstractMicroclimEnvironment{S,I,A,R} end

struct MicroclimGrid{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC} <: AbstractMicroclimGrid{S,I,A,R}
    radiation::RA
    snowdepth::SN
    airtemperature::AT
    relhumidity::RH
    windspeed::WS
    soiltemperature::ST
    soilwaterpotential::WP
    soilwatercontent::WC

    MicroclimGrid{S,I,A,R}(radiation::RA, snowdepth::SN, airtemperature::AT, relhumidity::RH,
                            windspeed::WS, soiltemperature::ST, soilwaterpotential::WP,
                            soilwatercontent::WC) where {S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC} =
        new{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC}(radiation, snowdepth, airtemperature,
            relhumidity, windspeed, soiltemperature, soilwaterpotential, soilwatercontent)

    MicroclimGrid{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC}(radiation::RA, snowdepth::SN, airtemperature::AT, relhumidity::RH,
                            windspeed::WS, soiltemperature::ST, soilwaterpotential::WP,
                            soilwatercontent::WC) where {S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC} =
        new{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC}(radiation, snowdepth, airtemperature,
            relhumidity, windspeed, soiltemperature, soilwaterpotential, soilwatercontent)
end

struct MicroclimPoint{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC} <: AbstractMicroclimPoint{S,I,A,R}
    radiation::RA
    snowdepth::SN
    airtemperature::AT
    relhumidity::RH
    windspeed::WS
    soiltemperature::ST
    soilwaterpotential::WP
    soilwatercontent::WC

    MicroclimPoint{S,I,A,R}(radiation::RA, snowdepth::SN, airtemperature::AT, relhumidity::RH,
                            windspeed::WS, soiltemperature::ST, soilwaterpotential::WP,
                            soilwatercontent::WC) where {S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC} =
        new{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC}(radiation, snowdepth, airtemperature,
            relhumidity, windspeed, soiltemperature, soilwaterpotential, soilwatercontent)

    MicroclimPoint{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC}(radiation::RA, snowdepth::SN, airtemperature::AT, relhumidity::RH,
                            windspeed::WS, soiltemperature::ST, soilwaterpotential::WP,
                            soilwatercontent::WC) where {S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC} =
        new{S,I,A,R,RA,SN,AT,RH,WS,ST,WP,WC}(radiation, snowdepth, airtemperature,
            relhumidity, windspeed, soiltemperature, soilwaterpotential, soilwatercontent)
end

abstract type AbstractMicroclimInstant end

struct MicroclimInstant{M,I,T} <: AbstractMicroclimInstant
    microclimate::M
    interp::I
    t::T
end
MicroclimInstant(microclim, height::Number, t) = MicroclimInstant(microclim, LinearLayerInterpolators(microclim, height), t)

# From external data without units
MicroclimPoint{S,I,A,R}(radiation::Vector{<:AbstractFloat}, snowdepth::Vector{<:AbstractFloat},
                      airtemperature::Array{<:AbstractFloat}, relhumidity::Array{<:AbstractFloat},
                      windspeed::Array{<:AbstractFloat}, soiltemperature::Array{<:AbstractFloat},
                      soilwaterpotential::Array{<:AbstractFloat}, soilwatercontent::Array{<:AbstractFloat}
                     ) where {S,I,A,R} = begin

    rad = to_radiation.(radiation)
    snow = to_snowdepth.(snowdepth)
    airt = to_airtemperature.(airtemperature)
    relh = to_relhumidity.(relhumidity)
    wind = to_windspeed.(windspeed)
    soilt = to_soiltemperature.(soiltemperature)
    soilwp = to_soilwaterpotential.(soilwaterpotential)
    soilwc = to_soilwatercontent.(soilwatercontent)

    args = (rad, snow, airt, relh, wind, soilt, soilwp, soilwc)
    MicroclimPoint{I,A,R,typeof.(args)...}(args...)
end

# From microclim dataset in tuples and a cartesian index
# Requires conversion to float from Int
MicroclimPoint{S,I,A,R}(radiation::Vector, snowdepth::Vector, airtemperature::Tuple,
                        relhumidity::Tuple, windspeed::Tuple, soiltemperature::Tuple,
                        soilwaterpotential::Tuple, soilwatercontent::Tuple, i::CartesianIndex) where {S,I,A,R} = begin
    rad = to_radiation.(catpoint(radiation, i) .* 0.1)
    snow = to_snowdepth.(catpoint(snowdepth, i) .* 0.001)
    airt = to_airtemperature.(catpoint(airtemperature, i) .* 0.1)
    relh = to_relhumidity.(catpoint(relhumidity, i) .* 0.001)
    wind = to_windspeed.(catpoint(windspeed, i) .* 0.1)
    soilt = to_soiltemperature.(catpoint(soiltemperature, i) .* 0.1)
    soilwp = to_soilwaterpotential.(catpoint(soilwaterpotential, i) .* 0.1)
    soilwc = to_soilwatercontent.(catpoint(soilwatercontent, i) .* 0.001)

    args = (rad, snow, airt, relh, wind, soilt, soilwp, soilwc)
    MicroclimPoint{S,I,A,R,typeof.(args)...}(args...)
end

# From MiriclimGrid and a cartesian index
MicroclimPoint(env::AbstractMicroclimGrid{S,I,A,R}, i::CartesianIndex) where {S,I,A,R} =
    MicroclimPoint{S,I,A,R}(radiation(env), snowdepth(env), airtemperature(env),
                   relhumidity(env), windspeed(env), soiltemperature(env),
                   soilwaterpotential(env), soilwatercontent(env), i)

struct MicroclimateLayers{L<:Tuple}
    layers::L
end

include("interpolation.jl")
include("netcdf.jl")

# unit conversions
to_radiation(x) = x * W*m^-2
to_snowdepth(x) = x * m
to_airtemperature(x) = x * °C |> K
to_relhumidity(x) = x
to_windspeed(x) = x * m*s^-1
to_soiltemperature(x) = x * °C |> K
to_soilwaterpotential(x) = x * kPa
to_soilwatercontent(x) = x

# Get layer specifications from type system
get_shade(layers::AbstractMicroclimEnvironment{S,I,A,R}) where {S,I,A,R} = S
get_increments(layers::AbstractMicroclimEnvironment{S,I,A,R}) where {S,I,A,R} = I
get_nextlayer(layers::AbstractMicroclimEnvironment{S,I,A,R}) where {S,I,A,R} = A
get_range(layers::AbstractMicroclimEnvironment{S,I,A,R}) where {S,I,A,R} = R

@inline catpoint(layers::Tuple{}, i::CartesianIndex) = Float64[]
@inline catpoint(layers::Tuple, i::CartesianIndex) =
    cat([[cat((getindex(y, i, :) for y in l)..., dims=1)...] for l in layers]..., dims=2)
@inline catpoint(layers::Vector, i::CartesianIndex) = cat((getindex(y, i, :) for y in layers)..., dims=1)

# Accessors for single layer data
for name in (:radiation, :snowdepth)
    eval(quote
        $name(env::AbstractEnvironment) = env.$name
        $name(env::AbstractMicroclimPoint, interp, i) = lin_interp($name(env), i)
        $name(env::AbstractMicroclimPoint, i) = lin_interp($name(env.microclimate), i)
        $name(env::AbstractMicroclimInstant) = lin_interp($name(env.microclimate), env.t)
    end)
end

# Accessor functions for range and increment data
for (name, mode) in ((:airtemperature, :range), (:relhumidity, :range), (:windspeed, :range),
                     (:soiltemperature, :increment), (:soilwaterpotential, :increment),
                     (:soilwatercontent, :increment))
    eval(quote
        $name(env::AbstractMicroclimEnvironment) = env.$name
        $name(env::AbstractMicroclimPoint, interp::LinearLayerInterpolators, i) =
            $name(env::AbstractMicroclimPoint, interp.$mode, i)
        $name(env::AbstractMicroclimPoint, interp::LinearLayerInterpolator, i) =
            interp_layer($name(env), interp, i)
        $name(env::AbstractMicroclimPoint, height::Number, i) =
            $name(env, $(Symbol(mode, :_interpolator))(height), i)
        $name(env::AbstractMicroclimInstant) =
            $name(env.microclimate, env.interp, env.t)
        $name(env::AbstractMicroclimInstant, i::Number) =
            $name(env.microclimate, env.interp, i)
        $name(env::AbstractMicroclimInstant, interp::AbstractLayerInterpolator) =
            $name(env.microclimate, interp, env.t)
    end)
end

# Accessors for weighted mean of 8 layer data
for name in (:soiltemperature, :soilwatercontent, :soilwaterpotential)
    eval(quote
        $(Symbol(:mean_, name))(env::AbstractMicroclimPoint, x, i) =
            weightedmean(env, $name(env), x, i)
        $(Symbol(:mean_, name))(env::AbstractMicroclimInstant) =
            weightedmean(env, $name(env.microclimate), env.interp, env.t)
        $(Symbol(:mean_, name))(env::AbstractMicroclimInstant, i::Number) =
            weightedmean(env, $name(env.microclimate), env.interp, i)
        $(Symbol(:mean_, name))(env::AbstractMicroclimInstant, interp::AbstractLayerInterpolator) =
            weightedmean(env, $name(env.microclimate), interp, env.t)
    end)
end

end # module


#LIKELIHOOD SCORING FUNCS
function IPM_likelihood(sources::AbstractVector{<:Tuple{<:AbstractMatrix{<:AbstractFloat},<:Integer}}, observations::AbstractMatrix{<:Integer}, obs_lengths::AbstractVector{<:Integer}, bg_scores::AbstractArray{<:AbstractFloat}, mix::BitMatrix, revcomp::Bool=REVCOMP, returncache::Bool=false, cache::AbstractVector{<:AbstractFloat}=zeros(0), clean::AbstractVector{<:Bool}=Vector(falses(size(observations)[2])))
    source_wmls=[size(source[1])[1] for source in sources]
    O = size(bg_scores)[2]
    source_stops=[obsl-wml+1 for wml in source_wmls, obsl in obs_lengths] #stop scannng th source across the observation as the source reaches the end
    L=maximum(obs_lengths)+1

    obs_src_idxs=mix_pull_idxs(mix) #get vectors of sources emitting in each obs

    revcomp ? (srcs=[cat(source[1],revcomp_pwm(source[1]),dims=3) for source in sources]; motif_expectations = [((MOTIF_EXPECT/2)/obsl) for obsl in obs_lengths]; mat_dim=2) : (srcs=[source[1] for source in sources]; ; motif_expectations = [(MOTIF_EXPECT/obsl) for obsl in obs_lengths]; mat_dim=1) #setup appropriate reverse complemented sources if necessary and set log_motif_expectation-nMica has 0.5 per base for including the reverse complement, 1 otherwise

    lme_vec=zeros(length(sources))

    obs_lhs=Vector{Vector{Float64}}() #setup likelihood vecs for threaded operation-reassembled on return
    nt=Threads.nthreads()
    for t in 1:nt-1
        push!(obs_lhs,zeros(Int(floor(O/nt))))
    end
    push!(obs_lhs, zeros(Int(floor(O/nt)+(O%nt))))

    Threads.@threads for t in 1:nt
        revcomp && (weavevec=zeros(3))
        revcomp ? (score_mat=zeros(maximum(source_stops),2)) : (score_mat=zeros(maximum(source_stops)))
        opt = floor(O/nt) #obs per thread
        score_matrices=Vector{typeof(score_mat)}(undef, length(sources)) #preallocate
        osi_emitting=Vector{Int64}() #preallocate
        lh_vec = zeros(L) #preallocated likelihood vector is one position (0 initialiser) longer than the longest obs

        for i in 1:Int(opt+(t==nt)*(O%nt))
            o=Int(i+(t-1)*opt)
            if clean[o]
                obs_lhs[t][i]=cache[o]
            else
                obsl = obs_lengths[o]
                oidxs=obs_src_idxs[o]
                mixwmls=source_wmls[oidxs]

                obs_cardinality = length(oidxs) #the more sources, the greater the cardinality_penalty
                if obs_cardinality > 0
                    revcomp ? score_sources_ds!(score_mat, score_matrices, view(srcs,oidxs), view(observations,:,o), view(source_stops,oidxs,o)) :
                         score_sources_ss!(score_mat, score_matrices, view(srcs,oidxs), view(observations,:,o), view(source_stops,oidxs,o)) #get scores for this observation

                    lme_vec.=motif_expectations[o]
                    penalty_sum = sum(lme_vec[1:obs_cardinality])
                    penalty_sum > 1. && (penalty_sum=1.)
                    cardinality_penalty=log(1.0-penalty_sum)
                else
                    cardinality_penalty=0.0
                end

                revcomp ? (obs_lhs[t][i]=weave_scores_ds!(weavevec, lh_vec, obsl, view(bg_scores,:,o), score_matrices, oidxs, mixwmls, log(motif_expectations[o]), cardinality_penalty, osi_emitting)) :
                    (obs_lhs[t][i]=weave_scores_ss!(lh_vec, obsl, view(bg_scores,:,o), score_matrices, oidxs, mixwmls, log(motif_expectations[o]), cardinality_penalty, osi_emitting))

                empty!(osi_emitting)

            end
        end
    end

    returncache ? (return lps([lps(obs_lhs[t]) for t in 1:nt]), vcat(obs_lhs...)) : (return lps([lps(obs_lhs[t]) for t in 1:nt]))
end
                @inline function mix_pull_idxs(A::AbstractArray{Bool})
                    n=count(A)
                    S=[Vector{Int64}() for o in 1:size(A,1)]
                    cnt=1
                    for (i,a) in pairs(A)
                        if a
                            push!(S[i[1]],i[2])
                            cnt+=1
                        end
                    end
                    return S
                end

                @inline function revcomp_pwm(pwm::AbstractMatrix{<:AbstractFloat}) #in order to find a motif on the reverse strand, we scan the forward strand with the reverse complement of the pwm, reordered 3' to 5', so that eg. an PWM for an ATG motif would become one for a CAT motif
                    return pwm[end:-1:1,end:-1:1]
                end

                @inline function score_sources_ds!(score_mat, score_matrices::AbstractVector{<:AbstractMatrix{<:AbstractFloat}}, sources, observation::AbstractVector{<:Integer}, source_stops)
                    for (s,source) in enumerate(sources)
                        for t in 1:source_stops[s]
                            for position in 1:size(source,1)
                                score_loc = t+position-1 #score_loc is the position of the obs to be scored by PWM
                                score_mat[t,1] += source[position,observation[score_loc],1] #add the appropriate log PWM value from the source to the score
                                score_mat[t,2] += source[position,observation[score_loc],2] #add the appropriate log PWM value from the source to the score

                            end
                        end
                        score_matrices[s]=score_mat[1:source_stops[s],:] #copy score matrix to vector
                        score_mat.=0. #reset score matrix
                    end
                end

                @inline function score_sources_ss!(score_mat, score_matrices::AbstractVector{<:AbstractArray{<:AbstractFloat}}, sources, observation::AbstractVector{<:Integer}, source_stops)
                    for (s,source) in enumerate(sources)
                        for t in 1:source_stops[s]
                            for position in 1:size(source,1)
                                score_loc = t+position-1 #score_loc is the position of the obs to be scored by PWM
                                score_mat[t] += source[position,observation[score_loc]] #add the appropriate log PWM value from the source to the score

                            end
                        end
                        score_matrices[s]=score_mat[1:source_stops[s]] #copy score matrix to vector
                        score_mat.=0. #reset score matrix
                    end
                end

                @inline function weave_scores_ds!(weavevec, lh_vec, obsl::Integer, bg_scores::SubArray, score_mat::AbstractVector{<:AbstractMatrix{<:AbstractFloat}}, obs_source_indices::AbstractVector{<:Integer}, source_wmls::AbstractVector{<:Integer}, log_motif_expectation::AbstractFloat, cardinality_penalty::AbstractFloat, osi_emitting)
                    for i in 2:obsl+1 #i=1 is ithe lh_vec initializing 0, i=2 is the score of the first background position (ie t=1)
                        t=i-1
                        score = lps(lh_vec[i-1], bg_scores[t], cardinality_penalty)

                        #logic: all observations are scored from t=wml to the end of the obs-therefore check at each position for new sources to add (indexed by vector position to retrieve source wml and score matrix)
                        if length(osi_emitting)<length(obs_source_indices)
                            for n in 1:length(obs_source_indices)
                                if !(n in osi_emitting)
                                    t>= source_wmls[n] && (push!(osi_emitting,n))
                                end
                            end
                        end

                        for n in osi_emitting
                            wml = source_wmls[n]
                            from_score = lh_vec[i-wml+1] #score at the first position of the PWM
                            score_array = score_mat[n] #get the source score matrix
                            score_idx = t - wml + 1 #translate t to score_array idx for emission score
                            f_emit_score = score_array[score_idx,1] #emission score at the last position of the PWM
                            r_emit_score = score_array[score_idx,2]

                            weavevec .= score, lps(from_score, f_emit_score, log_motif_expectation), lps(from_score, r_emit_score, log_motif_expectation)

                            score=logsumexp(weavevec)
                        end
                        lh_vec[i] = score
                    end
                    return lh_vec[obsl+1]
                end

                @inline function weave_scores_ss!(lh_vec, obsl::Integer, bg_scores::SubArray, score_mat::AbstractVector{<:AbstractVector{<:AbstractFloat}}, obs_source_indices::AbstractVector{<:Integer}, source_wmls::AbstractVector{<:Integer}, log_motif_expectation::AbstractFloat, cardinality_penalty::AbstractFloat, osi_emitting)
                    for i in 2:obsl+1 #i=1 is ithe lh_vec initializing 0, i=2 is the score of the first background position (ie t=1)
                        t=i-1
                        score = lps(lh_vec[i-1], bg_scores[t], cardinality_penalty)
                        #logic: all observations are scored from t=wml to the end of the obs-therefore check at each position for new sources to add (indexed by vector position to retrieve source wml and score matrix)
                        if length(osi_emitting)<length(obs_source_indices)
                            for n in 1:length(obs_source_indices)
                                if !(n in osi_emitting)
                                    t>= source_wmls[n] && (push!(osi_emitting,n))
                                end
                            end
                        end

                        for n in osi_emitting
                            wml = source_wmls[n]
                            from_score = lh_vec[i-wml+1] #score at the first position of the PWM
                            score_array = score_mat[n] #get the source score matrix
                            score_idx = t - wml + 1 #translate t to score_array idx for emission score
                            f_emit_score = score_array[score_idx] #emission score at the last position of the PWM

                            score=logaddexp(score, lps(from_score, f_emit_score, log_motif_expectation))
                        end
                        lh_vec[i] = score
                    end
                    return lh_vec[obsl+1]
                end


using Images, MXNet

### LOADING THE MODEL

const MODEL_NAME = "weights/mobilenet-v2/mobilenet_v2"
const MODEL_CLASS_NAMES = "weights/mobilenet-v2/synset.txt"

nnet = mx.load_checkpoint(MODEL_NAME, 0, mx.FeedForward);
synset = readlines(MODEL_CLASS_NAMES);

img = imresize(load("sample-images/bird-3183441_640.jpg"), (224, 224));
img = permutedims(Float16.(channelview(img)), (3, 2, 1));

img[:, :, :] *= 256.0;
img[:, :, :] -= 128.;
img[:, :, :] /= 128.;

img = reshape(img, 224, 224, 3, 1);

mx_data = mx.zeros((224, 224, 3, 1));
mx_data[1:1] = img;
data_provider = mx.ArrayDataProvider(:data => mx_data);

### PREDICTING

@time pred = mx.predict(nnet, data_provider);
mxval, mxindx = findmax(pred[:, 1]);
println(mxval, " ", mxindx, " ", synset[mxindx])


mutable struct Chunk
	blks::SMatrix{64,64,Block,4096}
	ligs::SVector{UInt8,2048}
	tm::Int
end
function get_lig(c::Chunk,x::Int,y::Int)::UInt8
	id=(x<<5)|(y>>1)+1
	return (y&1==0) ? c.ligs[id]>>4 : c.ligs[id]&0xf
end
function set_lig(c::Chunk,x::Int,y::Int,v::UInt8)::UInt8
	id=(x<<5)|(y>>1)+1
	(y&1==0) ? c.ligs[id]=c.ligs[id]&0xf|(v<<4) : c.ligs[id]=c.ligs[id]&0xf0|v
end
function generate(c::Chunk,x::Int,y::Int;map_generator::MapGenerator)
	use(map_generator,c,x,y)
	if haskey(sav,Pair(x,y))
		t=@inbounds sav[Pair(x,y)]
		for i in t
			x0=i.first>>6
			y0=i.first&63
			c.blks[x0,y0]=i.second
		end
	end
end
const visible_width=480
const visible_height=480
const visible_size=32
# 不应使用特性改动
"将(lx,ly)左上角显示到画板(px,py)"
function draw(c::Chunk,lx::Int,ly::Int,px::Int,py::Int;envl::UInt8)
	rx=min(lx+15-px>>5,64)
	ry=min(ly+15-py>>5,64)
	for i in lx:rx
		for j in ly:ry
			tl=get_lig(c,i,j)
			if tl<envl
				tl=envl
			end
			x0=px+(i-lx)<<5
			y0=py+(j-ly)<<5
			if tl==0x0
				fill_rect(dcon,x0,y0,32,32;color=RGB(0,0,0))
				continue
			end
			b=c.blks[i,j]
			if hole(b) fill_rect(dcon,x0,y0,32,32;color=RGB(15/16,15/16,15/16))
			end
			b_show(b,dcon,x0,y0)
			if tl!=0x15
				fill_rect(dcon,x0,y0,32,32;color=RGBA(0,0,0,(15-tl)/16))
			end
		end
	end
end

mutable struct Dimension
	cs::Dict{Pair{Int,Int},Chunk}
end
"画板(0,0)显示坐标(x,y)"
function draw(d::Dimension,x::Float,y::Float)
#=
区块坐标
(x,y)------+---------+
  |    A   |    B    |
  +--(lx+64,ly+64)---+
  |    C   |    D    |
  +--------+---(x+15,y+15)
lx+64-x=64-tx
lx+64<x+15 <=> tx>49 <=> 额外区块需渲染
||显示的区块左上角坐标|取整后|不取整显示坐标/32|显示坐标/32|
|:-:|:-:|:-:|:-:|:-:|
|A|(tx,ty)|(tx1,ty1)|(0,0)|(tx1-tx,ty1-ty)|
|B|(tx,0)|(tx1,0)|(0,64-ty)|(tx1-tx,64-ty)|
|C|(0,ty)|(0,ty1)|(64-tx,0)|(64-tx,ty1-ty)|
|D|(0,0)|(0,0)|(64-tx,64-ty)|(64-tx,64-ty)|
=#
	envl=envlight()
	tx=mod(x,64)
	ty=mod(y,64)
	tx1=floor(Int,tx)
	ty1=floor(Int,ty)
	lx=floor(Int,x)>>5<<5
	ly=floor(Int,y)>>5<<5
	draw(d.cs[Pair(lx,ly)],tx1+1,ty1+1,floor((tx1-tx)*32),floor((ty1-ty)*32);envl=envl) # A
	if ty>49
		draw(d.cs[Pair(lx,ly+64)],tx1+1,1,floor((tx1-tx)*32),floor((64-ty)*32);envl=envl) # B
	end
	if tx>49
		lx+=64
		draw(d.cs[Pair(lx,ly)],1,ty1+1,floor((64-tx)*32),floor((ty1-ty)*32);envl=envl) # C
		if ty>49
			draw(d.cs[Pair(lx,ly+64)],1,1,floor((64-tx)*32),floor((64-ty)*32);envl=envl) # D
		end
	end
end

mutable struct Game
	ds::Dict{String,Dimension}
	ps::Vector{玩家}
	set::Dict{String,Any}
end
function Game()
	return Game(Dict{String,Dimension}(),Vector{玩家}(),Dict{String,Any}())
end
function pushsave(lx::Int,ly::Int,tx::Int,ty::Int,b::Block)
	p=Pair(lx,ly)
	if !haskey(sav,p)
		sav[p]=Dict{Int,Block}()
	end
	sav[p][tx>>6|ty]=b
end
function getlig(x::Int,y::Int)
	tx=x&63;ty=y&63
	lx=x>>6;ly=y>>6
	c=chk(dim,lx,ly)
	return get_lig(c,tx,ty)
end
function setlig(x::Int,y::Int,v::UInt8)
	tx=x&63;ty=y&63
	lx=x>>6;ly=y>>6
	c=chk(dim,lx,ly)
	set_lig(c,tx,ty,v)
end
function getblk(x::Int,y::Int)
	tx=x&63+1;ty=y&63+1
	lx=x>>6;ly=y>>6
	c=chk(dim,lx,ly)
	return c.blks[tx,ty]
end
function setblk(x::Int,y::Int,b::Block;player_put::Bool=false)
	tx=x&63+1;ty=y&63+1
	lx=x>>6;ly=y>>6
	c=chk(dim,lx,ly)
	e_exist(c.blks[tx,ty],x,y)
	c.blks[tx,ty]=b
	if player_put
		e_put(b,x,y)
	else
		exist(b,x,y)
	end
	pushsave(lx,ly,tx,ty,b)
end

function calc_facing(x::Int,y::Int;to::Entity=ply)
	x0=x-to.x;y0=y-to.y
	if x0>0
		if x0<y0 return 0x2
		elseif x0<-y0 return 0x0
		else return 0x1
		end
	else
		if x0>y0 return 0x0
		elseif x0>-y0 return 0x2
		else return 0x3
		end
	end
end


using AbstractTrees

struct TrajectoryLog
    x
    v
    chart

    g
    ginv
    Γ

    JftWJf
    JftWginvℱ
    A
    B
    vmΓmvm

    TrajectoryLog() = new([],[],[],[],[],[],[],[],[],[],[])
end

function clear!(traj_log::TrajectoryLog)
    empty!(traj_log.x)
    empty!(traj_log.v)
    empty!(traj_log.chart)
    empty!(traj_log.g)
    empty!(traj_log.ginv)
    empty!(traj_log.Γ)
    empty!(traj_log.JftWJf)
    empty!(traj_log.JftWginvℱ)
    empty!(traj_log.A)
    empty!(traj_log.B)
    empty!(traj_log.vmΓmvm)
end

mutable struct TreeNode{T}
    data::T # Manifold for root, Task for leaves, TaskMap otherwise
    children::Vector{TreeNode}
    chart::Chart
    coord_rep::CoordinateRep
    traj_log::TrajectoryLog
    parent::TreeNode

    function TreeNode(data::M; CM::Chart{I,M}=default_chart(M),
            robot_coord_rep=ChartRep()) where {M<:Manifold,I}
        new{M}(data, [], CM, robot_coord_rep, TrajectoryLog())
    end
    function TreeNode(data::T; CN=codomain_manifold(data),
            task_coord_rep=ChartRep()) where T<:Union{TaskMapX,TaskX}
        new{T}(data, [], CN, task_coord_rep, TrajectoryLog())
    end
end

AbstractTrees.children(node::TreeNode) = node.children
AbstractTrees.printnode(io::IO, node::TreeNode{T}) where T = print(io, T)
function AbstractTrees.printnode(io::IO, node::TreeNode{T}, inds) where T
    if !isempty(inds)
        print(io, '(')
        show(IOContext(io, :compact => true), inds[end])
        print(io, ") ")
    end
    AbstractTrees.printnode(io, T)
end

function add_child!(node::TreeNode{M}, taskx::Union{TaskMapX,TaskX};
        CN=default_chart(codomain_manifold(taskx)), task_coord_rep=ChartRep()) where M <: Manifold
    if domain_manifold(taskx) == M
        new_node = TreeNode(taskx; CN, task_coord_rep)
        push!(node.children, new_node)
        return new_node
    else
        throw(ArgumentError("Domain manifold mismatch"))
    end
    nothing
end

function add_child!(node::TreeNode{<:TaskMapX}, taskx::Union{TaskMapX,TaskX};
        CN=default_chart(codomain_manifold(taskx)), task_coord_rep=ChartRep()) where M <: Manifold
    if codomain_manifold(node.data) == domain_manifold(taskx)
        new_node = TreeNode(taskx; CN, task_coord_rep)
        push!(node.children, new_node)
        return new_node
    else
        throw(ArgumentError("Manifold mismatch"))
    end
    nothing
end

add_child!(node::TreeNode{<:TaskX}, args...) = throw(ArgumentError("All tasks must be leaf nodes"))

function check_tree(root::TreeNode)
    for node in Leaves(root)
        if typeof(node.data) <: Union{Manifold, TaskMapX}
            error("Check failed: All leaf nodes must be tasks")
        end
    end
    println("Check passed")
end

function set_charts_emb!(node::TreeNode{M}, xme, CM) where M <: Manifold
    !isglobal(node.chart) ? node.chart = choose_chart_emb(xme, node.chart) : nothing
    for child in children(node)
        set_charts_emb!(child, xme, node.chart)
    end
end
function set_charts_chart!(node::TreeNode{M}, xm, CM) where M <: Manifold
    !isglobal(CM) ? node.chart = choose_chart_chart(xm, CM) : nothing
    for child in children(node)
        set_charts_chart!(child, xm, node.chart)
    end
end
function set_charts_emb!(node::TreeNode, xme, CM)
    taskx = node.data
    !isglobal(node.chart) ? node.chart = choose_chart_emb(xme, taskx, CM, node.chart) : nothing
    for child in children(node)
        set_charts_emb!(child, task_map_emb(xme, taskx, CM, node.chart), node.chart)
    end
end
function set_charts_chart!(node::TreeNode, xm, CM)
    taskx = node.data
    !isglobal(node.chart) ? node.chart = choose_chart_chart(xm, taskx, CM, node.chart) : nothing
    for child in children(node)
        set_charts_chart!(child, task_map_chart(xm, taskx, CM, node.chart), node.chart)
    end
end

function clear_logs!(node::TreeNode)
    clear!(node.traj_log)
    for child in node.children
        clear_logs!(child)
    end
    nothing
end


using CineFiles
using ImageCore
using Images: load
using TiffImages
using Test
using Glob
using XMLDict: parse_xml
using FixedPointNumbers: nbitsfrac

# In order to test against arbitary cine files, export tif image of first frame with gamma = 1
# and xml file using PCC. Ensure the cine file, tif image and xml file have the same name and
# place in either data or proprietary_data folders.

struct cine_test_files
    cine_path::String
    xml_path::String
    first_frame_path::String
end

function cine_test_files(cine_path::String)
    base_name = splitext(cine_path)[1]
    xml_path = base_name * ".xml"
    first_frame_path = base_name * ".tif"
    paths = (cine_path, xml_path, first_frame_path)
    exists = isfile.(paths)
    if all(exists)
        cine_test_files(paths...)
    else
        @error "Missing file(s) $(paths[[.~exists...]])"
    end
end

function compare_images(cine_file, tiff_image, frame_no = 1)
    cine_frame = cine_file[frame_no]
    cine_frame_gamma = (cine_frame) .+ cine_file.header.setup.fOffset
    # Have not included gamma adjustment yet
    cine_frame_gamma[cine_frame_gamma.<0] .= 0
    return all(isapprox.(cine_frame_gamma, tiff_image; atol = 0.01))
end

cine_file_paths = cine_test_files.(glob("*.cine", "data"))
append!(cine_file_paths, cine_test_files.(glob("*.cine", "proprietary_data")))

@testset "CineFiles.jl" begin
    @testset "Dependency internals" begin
        # nbitsfrac is unexported, so we'll test to make sure it doesn't break
        @test nbitsfrac(N0f8)  == 8
        @test nbitsfrac(N4f12) == 12
        @test nbitsfrac(N0f16) == 16
    end

    @time cf8 = CineFile(joinpath("data", "8bpp.cine"))
    @testset "8 bit grayscale" begin
        @test length(cf8) == 202
        @test size(cf8[1]) == (16, 128)
        @test eltype(cf8) == Matrix{Gray{N0f8}}
    end

    cf12 = CineFile(joinpath("data", "12bpp.cine"))
    @testset "12 bit grayscale" begin
        @test length(cf12) == 202
        @test size(cf12[1]) == (16, 128)
        @test eltype(cf12) == Matrix{Gray{N4f12}}
    end

    @test cf12[1] ≈ cf8[1]

    first_load_time = (@elapsed cf12[2])
    cached_load_time = (@elapsed cf12[2])
    @show first_load_time, cached_load_time
    @test first_load_time > cached_load_time

    @testset "Tiff compare first frame" begin
        for file in cine_file_paths
            @info file.cine_path
            cf = CineFile(file.cine_path)
            tiff_img = load(file.first_frame_path)
            @test compare_images(cf, tiff_img)
        end
    end

    @testset "XML compare camera version" begin
        for file in cine_file_paths
            @info file.cine_path
            cf = CineFile(file.cine_path)
            xml_data = parse_xml(read(file.xml_path, String))
            @test Int(cf.header.setup.CameraVersion) ==
                  parse(Int, xml_data["CameraSetup"]["CameraVersion"])
        end
    end
end


"""
`Colorant{T,N}` is the abstract super-type of all types in ColorTypes,
and refers to both (opaque) colors and colors-with-transparency (alpha
channel) information.  `T` is the element type (extractable with
`eltype`) and `N` is the number of *meaningful* entries (extractable
with `length`), i.e., the number of arguments you would supply to the
constructor.
"""
Compat.@compat abstract type Colorant{T,N} end

# Colors (without transparency)
"""
`Color{T,N}` is the abstract supertype for a color (or
grayscale) with no transparency.
"""
Compat.@compat abstract type Color{T, N} <: Colorant{T,N} end

"""
`AbstractRGB{T}` is an abstract supertype for red/green/blue color types that
can be constructed as `C(r, g, b)` and for which the elements can be
extracted as `red(c)`, `green(c)`, `blue(c)`. You should *not* make
assumptions about internal storage order, the number of fields, or the
representation. One `AbstractRGB` color-type, `RGB24`, is not
parametric and does not have fields named `r`, `g`, `b`.
"""
Compat.@compat abstract type AbstractRGB{T}      <: Color{T,3} end

# Types with transparency
"""
`TransparentColor{C,T,N}` is the abstract type for any
color-with-transparency.  The `C` parameter refers to the type of the
pure color (without transparency) and can be extracted with
`color_type`. `T` is the element type of both `C` and the `alpha`
channel, and `N` has the same meaning as in `Colorant` (and is 1 larger
than the corresponding color type).

All transparent types should support two modes of construction:

    P(color, alpha)
    P(component1, component2, component3, alpha) (assuming a 3-component color)

For a `Colorant` `c`, the color component can be extracted with
`color(c)`, and the alpha channel with `alpha(c)`. Note that types
such as `ARGB32` do not have a field named `alpha`.

Most concrete types, like `RGB`, have both `ARGB` and `RGBA`
transparent analogs.  These two indicate different internal storage
order (see `AlphaColor` and `ColorAlpha`, and the `alphacolor` and
`coloralpha` functions).
"""
Compat.@compat abstract type TransparentColor{C<:Color,T,N} <: Colorant{T,N} end

"""
`AlphaColor` is an abstract supertype for types like `ARGB`, where the
alpha channel comes first in the internal storage order. **Note** that
the constructor order is still `(color, alpha)`.
"""
Compat.@compat abstract type AlphaColor{C,T,N} <: TransparentColor{C,T,N} end

"""
`ColorAlpha` is an abstract supertype for types like `RGBA`, where the
alpha channel comes last in the internal storage order.
"""
Compat.@compat abstract type ColorAlpha{C,T,N} <: TransparentColor{C,T,N} end

# These are types we'll dispatch on. Not exported.
Compat.@compat AbstractGray{T}                    = Color{T,1}
Compat.@compat Color3{T}                          = Color{T,3}
Compat.@compat TransparentGray{C<:AbstractGray,T} = TransparentColor{C,T,2}
Compat.@compat Transparent3{C<:Color3,T}          = TransparentColor{C,T,4}
Compat.@compat TransparentRGB{C<:AbstractRGB,T}   = TransparentColor{C,T,4}
Compat.@compat ColorantNormed{T<:Normed,N}        = Colorant{T,N}

"""
`RGB` is the standard Red-Green-Blue (sRGB) colorspace.  Values of the
individual color channels range from 0 (black) to 1 (saturated). If
you want "Integer" storage types (e.g., 255 for full color), use `N0f8(1)`
instead (see FixedPointNumbers).
"""
immutable RGB{T<:Fractional} <: AbstractRGB{T}
    r::T # Red [0,1]
    g::T # Green [0,1]
    b::T # Blue [0,1]

    (::Type{RGB{T}}){T}(r::T, g::T, b::T) = new{T}(r, g, b)
    # T might be a Normed, and so some inputs will result in an
    # error. Try to make it a nice error.
    function (::Type{RGB{T}}){T}(r::Real, g::Real, b::Real)
        checkval(T, r, g, b)
        new{T}(_rem(r,T), _rem(g,T), _rem(b,T))
    end
end
# For types that support Fractional, we need this to avoid a
# StackOverflow. For color types that only support AbstractFloat, this
# is handled by @make_constructors.
RGB{T<:Fractional}(r::T, g::T, b::T) = RGB{T}(r, g, b)

"""
`BGR` is a variant of `RGB` with the opposite storage order.  Note
that the constructor is still called in the order `BGR(r, g, b)`.
This storage order is noteworthy because on little-endian machines,
`BGRA` (with transparency) can be `reinterpret`ed to the `UInt32`
color format used by libraries such as Cairo and OpenGL.
"""
immutable BGR{T<:Fractional} <: AbstractRGB{T}
    b::T
    g::T
    r::T

    (::Type{BGR{T}}){T}(r::T, g::T, b::T) = new{T}(b, g, r)
    function (::Type{BGR{T}}){T}(r::Real, g::Real, b::Real)
        checkval(T, r, g, b)
        new{T}(_rem(b,T), _rem(g,T), _rem(r,T))
    end
end
BGR{T<:Fractional}(r::T, g::T, b::T) = BGR{T}(r, g, b)

"""
`RGB1` is a variant of `RGB` which has a padding element inserted at
the beginning. In some applications it may have useful
memory-alignment properties.

Like all other AbstractRGB objects, the constructor is still called
`RGB1(r, g, b)`.
"""
immutable RGB1{T<:Fractional} <: AbstractRGB{T}
    alphadummy::T
    r::T
    g::T
    b::T

    (::Type{RGB1{T}}){T}(r::T, g::T, b::T) = new{T}(one(T), r, g, b)
    function (::Type{RGB1{T}}){T}(r::Real, g::Real, b::Real)
        checkval(T, r, g, b)
        new{T}(one(T), _rem(r,T), _rem(g,T), _rem(b,T))
    end
end
RGB1{T<:Fractional}(r::T, g::T, b::T) = RGB1{T}(r, g, b)

"""
`RGB4` is a variant of `RGB` which has a padding element inserted at
the end. In some applications it may have useful
memory-alignment properties.

Like all other AbstractRGB objects, the constructor is still called
`RGB4(r, g, b)`.
"""
immutable RGB4{T<:Fractional} <: AbstractRGB{T}
    r::T
    g::T
    b::T
    alphadummy::T

    (::Type{RGB4{T}}){T}(r::T, g::T, b::T) = new{T}(r, g, b, one(T))
    function (::Type{RGB4{T}}){T}(r::Real, g::Real, b::Real)
        checkval(T, r, g, b)
        new{T}(_rem(r,T), _rem(g,T), _rem(b,T), one(T))
    end
end
RGB4{T<:Fractional}(r::T, g::T, b::T) = RGB4{T}(r, g, b)

"`HSV` is the Hue-Saturation-Value colorspace."
immutable HSV{T<:AbstractFloat} <: Color{T,3}
    h::T # Hue in [0,360)
    s::T # Saturation in [0,1]
    v::T # Value in [0,1]
end

"`HSB` (Hue-Saturation-Brightness) is an alias for `HSV`."
const HSB = HSV

"`HSL` is the Hue-Saturation-Lightness colorspace."
immutable HSL{T<:AbstractFloat} <: Color{T,3}
    h::T # Hue in [0,360)
    s::T # Saturation in [0,1]
    l::T # Lightness in [0,1]
end

"`HSI` is the Hue-Saturation-Intensity colorspace."
immutable HSI{T<:AbstractFloat} <: Color{T,3}
    h::T
    s::T
    i::T
end

"""
`XYZ` is the CIE 1931 XYZ colorspace. It is a linear colorspace,
meaning that mathematical operations such as addition, subtraction,
and scaling make "colorimetric sense" in this colorspace.
"""
immutable XYZ{T<:AbstractFloat} <: Color{T,3}
    x::T
    y::T
    z::T
end

"`xyY` is the CIE 1931 xyY (chromaticity + luminance) space"
immutable xyY{T<:AbstractFloat} <: Color{T,3}
    x::T
    y::T
    Y::T
end

"`Lab` is the CIELAB colorspace."
immutable Lab{T<:AbstractFloat} <: Color{T,3}
    l::T # Luminance in approximately [0,100]
    a::T # Red/Green
    b::T # Blue/Yellow
end

"`LCHab` is the Luminance-Chroma-Hue, Polar-Lab colorspace"
immutable LCHab{T<:AbstractFloat} <: Color{T,3}
    l::T # Luminance in [0,100]
    c::T # Chroma
    h::T # Hue in [0,360)
end

"`Luv` is the CIELUV colorspace"
immutable Luv{T<:AbstractFloat} <: Color{T,3}
    l::T # Luminance
    u::T # Red/Green
    v::T # Blue/Yellow
end

"`LCHuv` is the Luminance-Chroma-Hue, Polar-Luv colorspace"
immutable LCHuv{T<:AbstractFloat} <: Color{T,3}
    l::T # Luminance
    c::T # Chroma
    h::T # Hue
end

"`DIN99` is the (L99, a99, b99) adaptation of CIELAB"
immutable DIN99{T<:AbstractFloat} <: Color{T,3}
    l::T # L99
    a::T # a99
    b::T # b99
end

"`DIN99d` is the (L99d, a99d, b99d) improvement on DIN99"
immutable DIN99d{T<:AbstractFloat} <: Color{T,3}
    l::T # L99d
    a::T # a99d
    b::T # b99d
end

"`DIN99o` is the (L99o, a99o, b99o) adaptation of CIELAB"
immutable DIN99o{T<:AbstractFloat} <: Color{T,3}
    l::T # L99o
    a::T # a99o
    b::T # b99o
end

"""
`LMS` is the Long-Medium-Short colorspace based on activation of the
three cone photoreceptors.  Like `XYZ`, this is a linear color space.
"""
immutable LMS{T<:AbstractFloat} <: Color{T,3}
    l::T # Long
    m::T # Medium
    s::T # Short
end

"`YIQ` is a color encoding, for example used in NTSC transmission."
immutable YIQ{T<:AbstractFloat} <: Color{T,3}
    y::T
    i::T
    q::T
end

"`YCbCr` is the Y'CbCr color encoding often used in digital photography or video"
immutable YCbCr{T<:AbstractFloat} <: Color{T,3}
    y::T
    cb::T
    cr::T
end

"""
`RGB24` uses a `UInt32` representation of color, 0xAARRGGBB, where
R=red, G=green, B=blue and A is irrelevant. This format is often used
by external libraries such as Cairo.

`RGB24` colors do not have fields named `r`, `g`, `b`, but you can
still extract the individual components with `red(c)`, `green(c)`,
`blue(c)`.  You can construct them directly from a `UInt32`, or as
`RGB(r, g, b)`.
"""
immutable RGB24 <: AbstractRGB{N0f8}
    color::UInt32

    RGB24(col::UInt32, ::Type{Val{true}}) = new(col)
end
# The Val{true} constructor solves a consistency problem, one that's
# most obvious for Gray24: Gray24(x) interprets x as a value between 0
# and 1; since a UInt32 can have value 0 or 1 (among other
# possibilities), having Gray24(x::UInt32) mean something entirely
# different (interpreting the UInt32 as a bit pattern rather than a
# value) would be inconsistent, confusing, and error-prone. Because we
# can construct an RGB24 from a grayscale value (just repeating the
# value for all 3 color channels), the same problem extends to all the
# "24/32" color types.

# However, some constructors "build" RGB24s as a UInt32 bit pattern,
# so we need some way of turning a UInt32 directly into an RGB24.
# This is the role of the Val{true} constructor. This is an internal
# implementation detail, and user code should not use it directly:
# use reinterpret(RGB24, x::UInt32) instead. (The Val{true}
# constructor is used to implement reinterpret, see traits.jl.)
RGB24() = reinterpret(RGB24, UInt32(0))
_RGB24(r::UInt8, g::UInt8, b::UInt8) = reinterpret(RGB24, UInt32(r)<<16 | UInt32(g)<<8 | UInt32(b))
RGB24(r::N0f8, g::N0f8, b::N0f8) = _RGB24(reinterpret(r), reinterpret(g), reinterpret(b))
function RGB24(r, g, b)
    checkval(N0f8, r, g, b)
    RGB24(_rem(r,N0f8), _rem(g,N0f8), _rem(b,N0f8))
end
@deprecate RGB24(x::UInt32) reinterpret(RGB24, x)

"""
`ARGB32` uses a `UInt32` representation of color, 0xAARRGGBB, where
R=red, G=green, B=blue and A is the alpha channel. This format is
often used by external libraries such as Cairo.  On a little-endian
machine, this type has the exact same storage format as `BGRA{N0f8}`.

`ARGB32` colors do not have fields named `alpha`, `r`, `g`, `b`, but
you can still extract the individual components with `alpha(c)`,
`red(c)`, `green(c)`, `blue(c)`.  You can construct them directly from
a `UInt32`, or as `ARGB32(r, g, b, alpha)`.
"""
immutable ARGB32 <: AlphaColor{RGB24, N0f8, 4}
    color::UInt32

    ARGB32(col::UInt32, ::Type{Val{true}}) = new(col)
end
ARGB32() = reinterpret(ARGB32, UInt32(0xff)<<24)
_ARGB32(r::UInt8, g::UInt8, b::UInt8, alpha::UInt8) = reinterpret(ARGB32, UInt32(alpha)<<24 | UInt32(r)<<16 | UInt32(g)<<8 | UInt32(b))
ARGB32(r::N0f8, g::N0f8, b::N0f8, alpha::N0f8 = N0f8(1)) = _ARGB32(reinterpret(r), reinterpret(g), reinterpret(b), reinterpret(alpha))
ARGB32(r, g, b, alpha = 1) = ARGB32(N0f8(r), N0f8(g), N0f8(b), N0f8(alpha))
ARGB32{T}(c::AbstractRGB{T}, alpha = alpha(c)) = ARGB32(red(c), green(c), blue(c), alpha)
@deprecate ARGB32(x::UInt32) reinterpret(ARGB32, x)

"""
`Gray` is a grayscale object. You can extract its value with `gray(c)`.
"""
immutable Gray{T<:Union{Fractional,Bool}} <: AbstractGray{T}
    val::T

    (::Type{Gray{T}}){T}(val::T) = new{T}(val)
    function (::Type{Gray{T}}){T}(val::Real)
        checkval(T, val)
        new{T}(_rem(val,T))
    end
end
Gray{T<:Union{Fractional,Bool}}(val::T) = Gray{T}(val)

"""
`Gray24` uses a `UInt32` representation of color, 0xAAIIIIII, where
I=intensity (grayscale value) and A is irrelevant. Each II pair is
assumed to be the same.  This format is often used by external
libraries such as Cairo.

You can extract the single gray value with `gray(c)`.  You can
construct them directly from a `UInt32`, or as `Gray24(i)`. Note that
`i` is interpreted on a scale from 0 (black) to 1 (white).
"""
immutable Gray24 <: AbstractGray{N0f8}
    color::UInt32

    Gray24(c::UInt32, ::Type{Val{true}}) = new(c)
end
Gray24() = reinterpret(Gray24, UInt32(0))
_Gray24(val::UInt8) = (g = UInt32(val); reinterpret(Gray24, g<<16 | g<<8 | g))
Gray24(val::N0f8) = _Gray24(reinterpret(val))
function Gray24(val::Real)
    checkval(N0f8, val)
    Gray24(val%N0f8)
end
@deprecate Gray24(x::UInt32) reinterpret(Gray24, x)

"""
`AGray32` uses a `UInt32` representation of color, 0xAAIIIIII, where
I=intensity (grayscale value) and A=alpha. Each II pair is
assumed to be the same.  This format is often used by external
libraries such as Cairo.

You can extract the single gray value with `gray(c)` and the alpha as
`alpha(c)`.  You can construct them directly from a `UInt32`, or as
`AGray32(i,alpha)`. Note that `i` and `alpha` are interpreted on a
scale from 0 (black) to 1 (white).
"""
immutable AGray32 <: AlphaColor{Gray24, N0f8, 2}
    color::UInt32

    AGray32(c::UInt32, ::Type{Val{true}}) = new(c)
end
AGray32() = reinterpret(AGray32, 0xff000000)
_AGray32(val::UInt8, alpha::UInt8 = 0xff) = (g = UInt32(val); reinterpret(AGray32, UInt32(alpha)<<24 | g<<16 | g<<8 | g))
AGray32(val::N0f8, alpha::N0f8 = N0f8(1)) = _AGray32(reinterpret(val), reinterpret(alpha))
function AGray32(val::Real, alpha = 1)
    checkval(N0f8, val, alpha)
    AGray32(val%N0f8, alpha%N0f8)
end
function AGray32(g::Gray24, alpha = 1)
    checkval(N0f8, alpha)
    reinterpret(AGray32, UInt32(reinterpret(_rem(alpha,N0f8)))<<24 | g.color)
end
AGray32(g::AbstractGray, alpha = 1) = AGray32(gray(g), alpha)
@deprecate AGray32(x::UInt32) reinterpret(AGray32, x)

# Generated code:
#   - more constructors for colors
#   - TransparentColor definitions (e.g., ARGB), exports, and constructors
#   - coloralpha(::Color) and alphacolor(::Color) traits for corresponding types

# Note: with the exceptions of `alphacolor` and `coloralpha`, all
# traits in the rest of this file are intended just for internal use

const color3types = map(s->getfield(ColorTypes,s),
  filter(names(ColorTypes, false)) do s
    isdefined(ColorTypes, s) || return false
    t = getfield(ColorTypes, s)
    isa(t, Type) && t <: Colorant && !isabstract(t) && length(fieldnames(t))>1
  end)
# The above should have filtered out every non-DataType that's not also a
# wrapped UnionAll-wrapped DataType. By avoiding the explicit UnionAll check
# here, we remain compatible with pre-0.6 julia.
const parametric3 = filter(x->!isa(x, DataType) || !isempty(x.parameters), color3types)

# Provide the field names in the order expected by the constructor
colorfields{C<:Color}(::Type{C}) = (fieldnames(C)...)
colorfields{C<:RGB1}(::Type{C}) = (:r, :g, :b)
colorfields{C<:RGB4}(::Type{C}) = (:r, :g, :b)
colorfields{C<:BGR }(::Type{C}) = (:r, :g, :b)
colorfields{P<:TransparentColor}(::Type{P}) = tuple(colorfields(color_type(P))..., :alpha)
colorfields(c::Colorant) = colorfields(typeof(c))

# Generate convenience constructors for a type
macro make_constructors(C, fields, elty)
    # elty = default element type when supplied with Integer arguments
    fields = fields.args
    Tfields = Expr[:($f::T) for f in fields]
    realfields = Expr[:($f::Real) for f in fields]
    zfields = zeros(Int, length(fields))
    esc(quote
        # More constructors for the non-alpha version
        $C{T<:Integer}($(Tfields...)) = $C{$elty}($(fields...))
        $C($(realfields...)) = $C(promote($(fields...))...)
        $C() = $C{$elty}($(zfields...))
    end)
end

# Generate transparent versions
macro make_alpha(C, acol, cola, fields, constrfields, ub, elty)
    # ub = upper-bound on T in C{T}
    # elty = default element type when supplied with Integer arguments
    fields = fields.args
    constrfields = constrfields.args
    N = length(fields)+1
    Tfields       = Expr[:($f::T)    for f in fields]
    Tconstrfields = Expr[:($f::T)    for f in constrfields]
    realfields    = Expr[:($f::Real) for f in constrfields]
    cfields       = Expr[:(c.$f)     for f in constrfields]
    cinnerfields  = Expr[:(c.$f)     for f in fields]
    remfields     = Expr[:(_rem($f,T)) for f in fields]
    zfields       = zeros(Int, length(fields))
    Tconstr = Expr(:<:, :T, ub)
    exportexpr = Expr(:export, acol, cola)
    esc(quote
        immutable $acol{$Tconstr} <: AlphaColor{$C{T}, T, $N}
            alpha::T
            $(Tfields...)

            (::Type{$acol{T}}){T}($(Tconstrfields...), alpha::T=one(T)) = new{T}(alpha, $(fields...))
            function (::Type{$acol{T}}){T}($(realfields...), alpha::Real=one(T))
                checkval(T, $(fields...), alpha)
                new{T}(_rem(alpha,T), $(remfields...))
            end
            (::Type{$acol{T}}){T}(c::$C, alpha::Real=one(T)) = $acol{T}($(cfields...), alpha)
        end
        immutable $cola{$Tconstr} <: ColorAlpha{$C{T}, T, $N}
            $(Tfields...)
            alpha::T

            (::Type{$cola{T}}){T}($(Tconstrfields...), alpha::T=one(T)) = new{T}($(fields...), alpha)
            function (::Type{$cola{T}}){T}($(realfields...), alpha::Real=one(T))
                checkval(T, $(fields...), alpha)
                new{T}($(remfields...), _rem(alpha,T))
            end
            (::Type{$cola{T}}){T}(c::$C, alpha::Real=one(T)) = $cola{T}($(cfields...), alpha)
        end
        $exportexpr
        alphacolor{C<:$C}(::Type{C}) = $acol
        coloralpha{C<:$C}(::Type{C}) = $cola

        # More constructors for the alpha versions
        $acol{T<:Integer}($(Tconstrfields...), alpha::T=1) = $acol{$elty}($(fields...), alpha)
        $acol(c::$C, alpha::Real=one(eltype(c))) = $acol{eltype(c)}(c, alpha)
        function $acol($(constrfields...))
            p = promote($(constrfields...))
            T = typeof(p[1])
            $acol{T}(p...)
        end
        function $acol($(constrfields...), alpha::Real)
            p = promote($(constrfields...), alpha)
            T = typeof(p[1])
            $acol{T}(p...)
        end
        $acol(c::Color, alpha::Real) = $acol($C(c), alpha)
        $acol() = $acol{$elty}($(zfields...))

        $cola{T<:Integer}($(Tconstrfields...), alpha::T=1) = $cola{$elty}($(fields...), alpha)
        $cola(c::$C, alpha::Real=one(eltype(c))) = $cola{eltype(c)}(c, alpha)
        function $cola($(constrfields...))
            p = promote($(constrfields...))
            T = typeof(p[1])
            $cola{T}(p...)
        end
        function $cola($(constrfields...), alpha::Real)
            p = promote($(constrfields...), alpha)
            T = typeof(p[1])
            $cola{T}(p...)
        end
        $cola(c::Color, alpha::Real) = $cola($C(c), alpha)
        $cola() = $cola{$elty}($(zfields...))
    end)
end

eltype_default{C<:AbstractRGB  }(::Type{C}) = N0f8
eltype_default{C<:AbstractGray }(::Type{C}) = N0f8
eltype_default{C<:Color  }(::Type{C}) = Float32
eltype_default{P<:Colorant        }(::Type{P}) = eltype_default(color_type(P))

# Upper bound on element type for each color type
eltype_ub{P<:Colorant        }(::Type{P}) = eltype_ub(eltype_default(P))
eltype_ub{T<:FixedPoint   }(::Type{T}) = Fractional
eltype_ub{T<:AbstractFloat}(::Type{T}) = AbstractFloat

ctypes = union(setdiff(parametric3, [RGB1,RGB4]), [Gray])

# the arg list for C below should be identical to ctypes above.
for (C, acol, cola) in [(DIN99d, :ADIN99d, :DIN99dA),
                        (DIN99o, :ADIN99o, :DIN99oA),
                        (DIN99, :ADIN99, :DIN99A),
                        (HSI, :AHSI, :HSIA),
                        (HSL, :AHSL, :HSLA),
                        (HSV, :AHSV, :HSVA),
                        (LCHab, :ALCHab, :LCHabA),
                        (LCHuv, :ALCHuv, :LCHuvA),
                        (LMS, :ALMS, :LMSA),
                        (Lab, :ALab, :LabA),
                        (Luv, :ALuv, :LuvA),
                        (XYZ, :AXYZ, :XYZA),
                        (YCbCr, :AYCbCr, :YCbCrA),
                        (YIQ, :AYIQ, :YIQA),
                        (xyY, :AxyY, :xyYA),
                        (BGR, :ABGR, :BGRA),
                        (RGB, :ARGB, :RGBA),
                        (Gray, :AGray, :GrayA)]
    fn  = Expr(:tuple, fieldnames(C)...)
    cfn = Expr(:tuple, colorfields(C)...)
    elty = eltype_default(C)
    ub   = eltype_ub(C)
    Csym = isdefined(Core, :UnionAll) ? Base.datatype_name(Base.unwrap_unionall(C)) : C.name.name
    @eval @make_constructors $Csym $fn $elty
    @eval @make_alpha $Csym $acol $cola $fn $cfn $ub $elty
end

# RGB1 and RGB4 require special handling because of the alphadummy field
@eval @make_constructors RGB1 (r,g,b) $N0f8
@eval @make_constructors RGB4 (r,g,b) $N0f8
alphacolor{C<:Gray24}(::Type{C}) = AGray32
alphacolor{C<:RGB24}(::Type{C}) = ARGB32
alphacolor{C<:RGB1}(::Type{C}) = ARGB
alphacolor{C<:RGB4}(::Type{C}) = ARGB
coloralpha{C<:RGB1}(::Type{C}) = RGBA
coloralpha{C<:RGB4}(::Type{C}) = RGBA

"""
`alphacolor(RGB)` returns `ARGB`, i.e., the corresponding transparent
color type with storage order (alpha, color).
""" alphacolor

"""
`coloralpha(RGB)` returns `RGBA`, i.e., the corresponding transparent
color type with storage order (color, alpha).
""" coloralpha

### Validating the inputs for Normed constructors

# Throw helpful errors in case of trouble. The inlining here is
# carefully designed to reduce the impact on runtime performance, and
# we also avoid splatting.

@inline function isok{T<:Normed}(::Type{T}, x)
    Δ = eps(T)/2 # as long as the number rounds to a valid number, that's OK
    (-Δ <= x) & (x < typemax(T)+Δ)
end

@inline checkval{T<:Normed}(::Type{T}, a) = isok(T, a) || throw_colorerror(T, a)

@inline function checkval{T<:Normed}(::Type{T}, a, b)
    isok(T, a) & isok(T, b) || throw_colorerror(T, a, b)
end
@inline function checkval{T<:Normed}(::Type{T}, a, b, c)
    isok(T, a) & isok(T, b) & isok(T, c) || throw_colorerror(T, a, b, c)
end
@inline function checkval{T<:Normed}(::Type{T}, a, b, c, d)
    isok(T, a) & isok(T, b) & isok(T, c) & isok(T, d) || throw_colorerror(T, a, b, c, d)
end

checkval{T<:Normed}(::Type{T}, a::T) = nothing
checkval{T<:Normed}(::Type{T}, a::T, b::T) = nothing
checkval{T<:Normed}(::Type{T}, a::T, b::T, c::T) = nothing
checkval{T<:Normed}(::Type{T}, a::T, b::T, c::T, d::T) = nothing

checkval{T}(::Type{T}, a) = nothing
checkval{T}(::Type{T}, a, b) = nothing
checkval{T}(::Type{T}, a, b, c) = nothing
checkval{T}(::Type{T}, a, b, c, d) = nothing

@noinline throw_colorerror{T}(::Type{T}, g) = throw_colorerror(T, (g,))
@noinline throw_colorerror{T}(::Type{T}, g, a) = throw_colorerror(T, (g,a))
@noinline throw_colorerror{T}(::Type{T}, r, g, b) = throw_colorerror(T, (r, g, b))
@noinline throw_colorerror{T}(::Type{T}, r, g, b, a) = throw_colorerror(T, (r, g, b, a))

function throw_colorerror_{T<:Normed}(::Type{T}, values)
    io = IOBuffer()
    showcompact(io, typemin(T)); Tmin = String(take!(io))
    showcompact(io, typemax(T)); Tmax = String(take!(io))
    bitstring = sizeof(T) == 1 ? "an 8-bit" : "a $(8*sizeof(T))-bit"
    throw(ArgumentError("""
element type $T is $bitstring type representing $(2^(8*sizeof(T))) values from $Tmin to $Tmax,
  but the values $values do not lie within this range.
  See the READMEs for FixedPointNumbers and ColorTypes for more information."""))
end

function throw_colorerror(::Type{N0f8}, values::Tuple{Vararg{Integer}})
    # Let's try to read the user's mind
    if all(x->0<=x<=255, values)
        if length(values) == 1
            vstr = "$(values[1]) is an integer"
            Tstr = "Gray"
        else
            vstr = "$values are integers"
            if length(values) == 2
                Tstr = "AGray"
            elseif length(values) == 3
                Tstr = "RGB"
            else
                Tstr = "RGBA"
            end
        end
        args = join(map(v->"$v/255", values), ',')
        throw(ArgumentError("""
$vstr in the range 0-255, but integer inputs are encoded with the N0f8
  type, an 8-bit type representing 256 discrete values between 0 and 1.
  Consider dividing your input values by 255, for example: $Tstr{N0f8}($args)
  See the READMEs for FixedPointNumbers and ColorTypes for more information."""))
    end
    throw_colorerror_(N0f8, values)
end

function throw_colorerror{T<:Normed}(::Type{T}, values::Tuple)
    throw_colorerror_(T, values)
end

_rem{T<:Normed}(x,::Type{T}) = x % T
_rem{T}(x, ::Type{T})        = x