using MOCNeutronTransport

# I can't remember if the bottom left corner of the CMFD grid needs to be (0, 0, 0)
# To compensate, we do some extra math to ensure that happens.
grid_offset = 0.1 # Buffer between boundary of the model and the edge of the CMFD grid (cm)
# Bounding box for the CMFD grid
# xmin, xmax, ymin, ymax
bb = (0.0, 7.46252 + 2*grid_offset,
      0.0, 8.31535 + 2*grid_offset)

# Model setup
# ----------------------------------------------------------------------------------------
gmsh.initialize()
# Import CAD file
gmsh.merge("xsec.step")
# Visualize the model
# gmsh.fltk.run()

# Shift the model to the origin of the xy-plane
# Get the dim tags of all dimension 2 entites
dim_tags = gmsh.model.get_entities(2)
# Rotate the entities in dim_tags in the built-in CAD representation by angle radians
# around the axis of revolution defined by the point (x, y, z) and the direction (ax, ay, az).
# gmsh.model.occ.rotate(dim_tags, x, y, z, ax, ay, az, angle)
gmsh.model.occ.rotate(dim_tags, 0, 1, 0, 0, 1, 0, -π/2)
# Translate it so that the corner is at the origin
gmsh.model.occ.translate(dim_tags, 37.3225 - 0.0099 + 10*grid_offset,
                                   44.09 - 0.00174209 + 10*grid_offset,
                                   -436.5625)
# Model is in mm. Convert to cm by shrinking everything by 1/10
gmsh.model.occ.dilate(dim_tags, 0, 0, 0, 1//10, 1//10, 0)
# Synchronize the CAD and Gmsh models
gmsh.model.occ.synchronize()
# Verify results
# gmsh.fltk.run()

# Assign materials
# ------------------------------------------------------------------------------------------
# Elements containing fuel were determined visually, since the numbering scheme isn't obvious
fuel_tags = [4, 5, 6, 9, 10, 11, 12, 13, 15, 20, 21, 25, 27, 31, 32, 33, 36, 38]
clad_tags = [t[2] for t in dim_tags]
# Determine which tags are clad by doing fuel\clad
filter!(x->x ∉  fuel_tags, clad_tags)
# Assign the materials as physical groups
p = gmsh.model.add_physical_group(2, fuel_tags)
gmsh.model.set_physical_name(2, p, "MATERIAL_UO2")
p = gmsh.model.add_physical_group(2, clad_tags)
gmsh.model.set_physical_name(2, p, "MATERIAL_CLAD")
# Verify results
# gmsh.fltk.run()

# Overlay CMFD/hierarchical grid
# -------------------------------------------------------------------------------------------
# The arguments to gmsh_overlay_rectangular_grid are:
# (bb, material, nx, ny) or (bb, material, x, y)
#
# bb: The bounding box for the grid
#   bb = (xmin, xmax, ymin, ymax)::NTuple{4, AbstractFloat}
# material: The material with which to fill empty space/give to entities without a material
#   material::String
#   Note the material string must start with something that when converted to uppercase
#       yields "MATERIAL_", ex: "Material_A" or "material_A"
# (nx, ny)/(x, y): The hierarchical grid divisions.
#   The grid is mapped to MPACT in the following manner:
#       1st level: Assembly map
#       2nd level: Assemblies/lattices (same map, since 2D)
#       3rd level: Ray tracing modules (must be equal size)
#       4th level: Coarse cells in the CMFD grid
#   This has two input variations
#   Input 1) nx, ny
#       The number of equal x and y divisions in each level of the hierarchy.
#       nx::Vector{Int}, ny::Vector{Int}
#       Example:
#           nx = [3, 2, 1, 1]
#           The 1st level (assembly map) will be split into 3 pieces in x.
#           The 2nd level will split each of the 1st level grid elements into 2 piece in x
#               Theses elements are now 1/6 the total grid's x width
#           The 3rd level will be the same as above
#           The 4th level will be the same as above
#   Input 2) x, y
#       The x and y locations of the divisions. These need not be equally spaced.
#       x::Vector{Vector{T}}, y::Vector{Vector{T}}
#       Note that end points don't need to be included, and that any divisions in the coarser
#           grid levels are propagated downward.
#       Example:
#          For bb = (0, 1, 0, 1)
#          x = [[0.5],[0.2, 0.75]]
#          is equal to
#          x = [[0.0, 0.5, 1.0], [0.0, 0.2, 0.5, 0.75, 1.0]]
#   Final tidbit:
#       Any missing enities are assumed to be 1 division, where the last element in the input
#           vector is assumed to be the coarse cells.
#       Example:
#           nx = [1, 1, 4, 3] gives the same output as [4, 3]
#           4 RT modules (in x), 3 coarse cells (in x) per module = 12 total coarse cells (in x)
nx = [9, 1]
ny = [10, 1]
grid_tags = gmsh_overlay_rectangular_grid(bb, "MATERIAL_MODERATOR", nx, ny)
# Verify results
# gmsh.fltk.run()

# Mesh
# ------------------------------------------------------------------------------------------------
# Set the characteristic edge length of the mesh cells
lc = 0.3 # cm
gmsh.model.mesh.set_size(gmsh.model.get_entities(0), lc)
# Optional mesh optimization:
niter = 2 # The optimization iterations

# Triangles
# gmsh.model.mesh.generate(2) # 2 is dimension of mesh
# for () in 1:niter
#     gmsh.model.mesh.optimize("Laplace2D")
# end

# Quadrilaterals
# The default recombination algorithm might leave some triangles in the mesh, if
# recombining all the triangles leads to badly shaped quads. In such cases, to
# generate full-quad meshes, you can either subdivide the resulting hybrid mesh
# (with `Mesh.SubdivisionAlgorithm' set to 1), or use the full-quad
# recombination algorithm, which will automatically perform a coarser mesh
# followed by recombination, smoothing and subdivision.
# Mesh recombination algorithm (0: simple, 1: blossom, 2: simple full-quad, 3: blossom full-quad)
# Default = 1
# gmsh.option.set_number("Mesh.RecombineAll", 1) # recombine all triangles
# gmsh.option.set_number("Mesh.Algorithm", 8) # Frontal-Delaunay for quads. Better 2D algorithm
# gmsh.option.set_number("Mesh.RecombinationAlgorithm", 1)
# gmsh.model.mesh.generate(2)
# for () in 1:niter
#     gmsh.model.mesh.optimize("Laplace2D")
#     gmsh.model.mesh.optimize("Relocate2D")
#     gmsh.model.mesh.optimize("Laplace2D")
# end

# 2nd order triangles
gmsh.model.mesh.generate(2) # Triangles first for high order meshes.
gmsh.model.mesh.set_order(2)
for () in 1:niter
    gmsh.model.mesh.optimize("HighOrderElastic")
    gmsh.model.mesh.optimize("Relocate2D")
    gmsh.model.mesh.optimize("HighOrderElastic")
end

# 2nd order quadrilaterals
# These can be problematic for large lc. They have trouble respecting CAD boundaries.
# gmsh.option.set_number("Mesh.RecombineAll", 1) # recombine all triangles
# gmsh.option.set_number("Mesh.Algorithm", 8) # Frontal-Delaunay for quads. Better 2D algorithm
# gmsh.option.set_number("Mesh.RecombinationAlgorithm", 3)
# gmsh.model.mesh.generate(2)
# gmsh.model.mesh.set_order(2)
# for () in 1:niter
#     gmsh.model.mesh.optimize("HighOrderElastic")
#     gmsh.model.mesh.optimize("Relocate2D")
#     gmsh.model.mesh.optimize("HighOrderElastic")
# end

# gmsh.fltk.run()

# Mesh conversion
# ----------------------------------------------------------------------------------------------------
# We want to write an Abaqus file from Gmsh, read the Abaqus file into Julia, and convert that into
# a rectangularly partitioned hierarchical XDMF file, the final input to MPACT
#
# Write Abaqus file
gmsh.write("fnr.inp")
gmsh.finalize() # done with Gmsh. Finalize
mesh = read_abaqus_2d("fnr.inp")
# write_vtk_2d("fnr.vtk", mesh) # Can print vtk for sanity check. No cell sets
# write_xdmf_2d("fnr.xdmf", mesh) # Can print single grid XDMF
# Convert mesh into Hierarchical Rectangularly Partitioned Mesh
HRPM = partition_rectangularly(mesh)
write_xdmf_2d("fnr.xdmf", HRPM)

# Mass conservation
# ----------------------------------------------------------------------------------------------------
# See how well your mesh conserves mass. Analytic solution not available, so fine tri6 mesh used as
# reference
fuel_area = area(mesh, "MATERIAL_UO2")
fuel_area_ref = 6.119632103711788
println("Fuel area (reference): $fuel_area_ref")
println("Fuel area ( computed): $fuel_area")
err = 100*(fuel_area - fuel_area_ref)/fuel_area_ref
println("Error (relative): $err %")
println("")

clad_area = area(mesh, "MATERIAL_CLAD")
clad_area_ref = 19.884367501087866
println("Clad area (reference): $clad_area_ref")
println("Clad area ( computed): $clad_area")
err = 100*(clad_area - clad_area_ref)/clad_area_ref
println("Error (relative): $err %")


module ImageContrastAdjustment

using ImageCore, ColorTypes, ColorVectorSpace, FixedPointNumbers
using MappedArrays: of_eltype

# TODO: port HistogramAdjustmentAPI to ImagesAPI
include("HistogramAdjustmentAPI/HistogramAdjustmentAPI.jl")
import .HistogramAdjustmentAPI: AbstractHistogramAdjustmentAlgorithm,
                                adjust_histogram, adjust_histogram!

# TODO Relax this to all image color types
const GenericGrayImage = AbstractArray{<:Union{Number, AbstractGray}}

include("core.jl")
include("build_histogram.jl")
include("algorithms/common.jl")
include("algorithms/equalization.jl")
include("algorithms/linear_stretching.jl")
include("algorithms/contrast_stretching.jl")
include("algorithms/gamma_correction.jl")
include("algorithms/matching.jl")
include("algorithms/midway_equalization.jl")

export
    # main types and functions
    Equalization,
    MidwayEqualization,
    Matching,
    GammaCorrection,
    LinearStretching,
    ContrastStretching,
    build_histogram,
    adjust_histogram,
    adjust_histogram!

end # module


abstract Geometry

immutable Conductor <: Geometry
    name::String
    outperm::Real
    tran::Tuple{Real,Real,Real}
    shape::Shape
end

function Conductor(name::String,outperm::Real,shape::Shape)
    return Conductor(name,outperm,(0,0,0),shape)
end

immutable Dielectric <: Geometry
    name::String
    outperm::Real
    inperm::Real
    tran::Tuple{Real,Real,Real}
    ref::Tuple{Real,Real,Real}
    shape::Shape
end

function Dielectric(name::String,outperm::Real,inperm::Real,ref::Tuple{Real,Real,Real},shape::Shape)
    return Dielectric(name,outperm,inperm,(0,0,0),ref,shape)
end

immutable ConductorGroup <: Geometry
    name::String
    tran::Tuple{Real,Real,Real}
    geoms::Vector{Conductor}
end

function ConductorGroup(name::String,geoms::Vector{Conductor})
    return ConductorGroup(name,(0,0,0),geoms)
end

immutable DielectricGroup <: Geometry
    name::String
    tran::Tuple{Real,Real,Real}
    geoms::Vector{Dielectric}
end

function DielectricGroup(name::String,geoms::Vector{Conductor})
    return DielectricGroup(name,(0,0,0),geoms)
end

move(tran::Tuple{Real,Real,Real},g::Conductor) = Conductor(g.name,g.outperm,g.tran+tran,g.shape)
move(tran::Tuple{Real,Real,Real},g::Dielectric) = Dielectric(g.name,g.outperm,g.inperm,g.tran+tran,g.ref+tran,g.shape)
move(tran::Tuple{Real,Real,Real},g::ConductorGroup) = ConductorGroup(g.name,g.tran+tran,g.geoms)
move(tran::Tuple{Real,Real,Real},g::DielectricGroup) = DielectricGroup(g.name,g.tran+tran,g.geoms)

function regroup(name::String,tran,groups::Vector{ConductorGroup})
    # Return a new conductor group with the given name
    return ConductorGroup(name,tran,geometries(groups))
end

function regroup(name::String,tran,groups::Vector{DielectricGroup})
    # Return a new dielectric group with the given name
    return ConductorGroup(name,tran,geometries(groups))
end

function geometries{T<:Union{ConductorGroup,DielectricGroup}}(groups::Vector{T})
    # Extract the geometries from each group applying the group
    # translation directly to the extracted geometry
    return mapreduce(group->mapreduce(g->move(group.tran,g),vcat,group.geoms),vcat,groups)
end

function shapes{T<:Geometry}(geoms::Vector{T})
    # Extract all the shapes from a list of geometries
    return mapreduce(shapes,vcat,geoms)
end

function shapes(g::Union{Conductor,Dielectric})
    # Extract shape from geometry
    return g.shape
end

function shapes(g::Union{ConductorGroup,DielectricGroup})
    # Extract shapes from geometry group
    return [e.shape for e in g.geoms]
end

function listfull{T<:Geometry}(geoms::Vector{T})
    listfile   = list(geoms)
    shapefiles = hierfile.(unique(shapes(geoms)))
    return join([listfile;"End";shapefiles],NL)
end

hierfile(shape::Shape) = "File "*shape.name*NL*getfile(shape)*NL*"End"

function list{T<:Geometry}(geoms::Vector{T})
    return join(listline.(geoms),NL)
end

prtu(tu) = @sprintf(" % .5E % .5E % .5E",tu...)

function listline(g::Conductor)
    lg = "G $(g.name)"
    lf = "C $(g.shape.name) $(g.outperm)"*prtu(g.tran)
    return join([lg,lf],NL)
end

function listline(g::Dielectric)
    lg = "G $(g.name)"
    lf = "D $(g.shape.name) $(g.outperm) $(g.inperm)"*prtu(g.tran)*prtu(g.ref)*" - 0x7a49a5ff"
    return join([lg,lf],NL)
end

function listline(g::ConductorGroup)
    lg = "G $(g.name)"
    lf = ["C $(c.shape.name) $(c.outperm)"*prtu(c.tran+g.tran) for c in g.geoms]
    return join([lg,join(lf," +$NL")],NL)
end

function listline(g::DielectricGroup)
    lg = "G $(g.name)"
    lf = ["D $(d.shape.name) $(d.outperm) $(d.inperm)"*prtu(d.tran+g.tran)*prtu(d.ref+g.tran)*" - 0x7a49a5ff" for d in g.geoms]
    return join([lg,join(lf,NL)],NL)
end

import Base.+
+(a::Tuple{Real,Real,Real},b::Tuple{Real,Real,Real}) = (a[1]+b[1],a[2]+b[2],a[3]+b[3])


VERSION >= v"0.4.0-dev+6521" && __precompile__()

module WORLD

# A light-weight julia wrapper for WORLD.

export
    # Types
    DioOption,
    CheapTrickOption,
    D4COption,

    # WORLD functions
    dio,
    stonemask,
    cheaptrick,
    d4c,
    synthesis,

    # utils
    get_fftsize_for_cheaptrick,

    # matlab functions
    interp1!,
    interp1,

    # conversion
    sp2mc,  # spectrum envelope to mel-cesptrum
    mc2sp   # mel-cepstrum to spectrum envelope

### Binary dependency loading ###
deps = joinpath(Pkg.dir("WORLD"), "deps", "deps.jl")
if isfile(deps)
    include(deps)
else
    error("WORLD not properly installed. Please run Pkg.build(\"WORLD\")")
end

### Check binary dependency compatibility ###
# This should prevent segfaults when you are using WORLD on system paths (e.g.
# /usr/local/lib/libworld.so) and accidentaly change the verion of WORLD.
try
    # function GetWORLDVersion was added in v0.2.1-2
    versionstr = bytestring(ccall((:GetWORLDVersion, libworld), Ptr{Cchar}, ()))
    global const version = convert(VersionNumber, versionstr)
catch e
    try
        ccall((:GetFFTSizeForStar, libworld), Cint, (Cint,), 48000)
        version = v"0.1.4"
    catch e
        version = v"0.2.0"
    end
end

# v0.2.1-4 breaks API of CheapTrick and D4C, and it's hard to support both old
# and new WORLD library, so throws an error when older version of WORLD is
# detected.
if version < v"0.2.1-4"
    error("""Binary dependency incompatibility

          WORLD version 0.2.1-4 or later must be required ($(version) is detected).
          If you have WORLD installed on your system paths, please get the latest
          stable WORLD and install it, and then re-build WORLD.jl with:

          julia>  Pkg.build(\"WORLD\")

          If not, simpily re-building WORLD.jl with the above command. This
          should install the proper version of WORLD.

          If you want to use WORLD.jl with older WORLD, please downgrade WORLD.jl
          for your need. E.g.

          julia> cd(Pkg.dir(\"WORLD\"))
          shell> git checkout v0.1.3 # that supprts v0.2.0 to v0.2.1-3.
         """)
end

for name in ["bridge", "mcep", "deprecated"]
    include(string(name, ".jl"))
end

end # module WORLD


export Sequential,learn!

mutable struct Sequential <: Models
    common
    window_size
    Sequential(gpu_flg=false)=new(ModelCommon(gpu_flg),0)
end

function learn!(model::Models;data,t_data,window_size,max_epoch,verification_params=nothing,verification=nothing,notebook=false)
    D = size(data,3) #データ次元数
    T = window_size #内部ユニット数
    N = size(data,1) #バッチ数
    max_ite = size(t_data,2) #イテレーション数
    continue_flg = true
    p = Progress(max_epoch*max_ite,1,"Progress : ")
    model.common.epoch = max_epoch
    model.common.loss = zeros(max_epoch)
    model.window_size = window_size

    @inbounds for epoch in 1:max_epoch
        ite_total_loss = 0 #損失合計
        avg_loss = 0 #1エポックの平均損失
        model.common.now_epoch = epoch
        @inbounds for ite in 1:max_ite
            #ミニバッチ作成
            st = Int(1+(ite-1)*T) #data切り取り位置
            ed = Int(T*ite)
            xs = view(data,:,st:ed,:)
            t = view(t_data,:,ite,:)
            #順伝播
            model.common.layers[end].t = t #教師データ挿入
            loss = predict!(model,xs,true)
            #ite毎の平均損失を加算
            ite_total_loss += sum(loss)/length(loss)
            #逆伝播
            backward!(model)
            #更新
            update!(model.common.optimizer,model)

            next!(p)
        end

        reset!(model)

        avg_loss = ite_total_loss/max_ite
        model.common.loss[epoch] = avg_loss

        if notebook
            print(model.common.learn_io,"\n","\e[0F","\e[2K","epoch: ",epoch," | loss: ",avg_loss," | ")
        else
            print(model.common.learn_io,"\n","epoch:",epoch," | ","loss:",avg_loss," | ")
        end

        #検証
        if verification!=nothing
            continue_flg = verification(model,verification_params)
            reset!(model)
        end

        if notebook
            IJulia.clear_output(true)
            plot(model.common.learn_plot) |> display
        else
            print(model.common.learn_io,"\e[2F") #上(プログレスバーの先頭へ)
        end

        take!(model.common.learn_io) |> String |> println

        # min_avg_loss，その時のep,今のep,avg_loss
        if model.common.min_loss > avg_loss || epoch==1
            model.common.min_loss = avg_loss
            model.common.min_epoch = epoch
        end

    end

    return 0
end


# Has been renamed to avoid it being found by the postprocessing scripts
#
config[@__DIR__] = (
    directory=@__DIR__,
    case="Al",
    n_repeat=12,
    kwargs_scf=(smearing=Smearing.Gaussian(), temperature=0.001, randomise_guess=true),
    firstmixkey="Kerker",
    #
    construct_localiser=(lattice, atoms) -> identity,
    mixings=Dict(
        "Kerker"     => (α, localiser) -> KerkerMixing(α=α, kTF=1.0),
        "None"       => (α, localiser) -> SimpleMixing(α=α),
    ),
)


using ModernGL, GeometryTypes, GLAbstraction, GLFW, Images, FileIO, Reactive, LinearAlgebra

const GLA = GLAbstraction

kitten = load(GLAbstraction.dir("tutorials", "images", "kitten.png"))
puppy  = load(GLAbstraction.dir("tutorials", "images", "puppy.png"))

# Create the window. This sets all the hints and makes the context current.
window = GLFW.Window(name="Depth and Stencils 1", resolution=(800,600),
                     windowhints=[(GLFW.DEPTH_BITS,32), (GLFW.STENCIL_BITS, 8)])

GLFW.MakeContextCurrent(window)
GLA.set_context!(window)

# The cube. This could be more efficiently represented using indexes,
# but the tutorial doesn't do it that way.
vertex_positions = Vec3f0[
    # The cube
    (-0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    (-0.5f0,  0.5f0, -0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),

    (-0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0,  0.5f0),
    (-0.5f0, -0.5f0,  0.5f0),

    (-0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0, -0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),
    (-0.5f0, -0.5f0,  0.5f0),
    (-0.5f0,  0.5f0,  0.5f0),

    ( 0.5f0,  0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),

    (-0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0, -0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    ( 0.5f0, -0.5f0,  0.5f0),
    (-0.5f0, -0.5f0,  0.5f0),
    (-0.5f0, -0.5f0, -0.5f0),

    (-0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0, -0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    ( 0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0,  0.5f0),
    (-0.5f0,  0.5f0, -0.5f0)]

floor_positions = Vec3f0[
    # The floor
    (-1.0f0, -1.0f0, -0.5f0),
    ( 1.0f0, -1.0f0, -0.5f0),
    ( 1.0f0,  1.0f0, -0.5f0),
    ( 1.0f0,  1.0f0, -0.5f0),
    (-1.0f0,  1.0f0, -0.5f0),
    (-1.0f0, -1.0f0, -0.5f0)
]

vertex_texcoords = Vec2f0[
                          # The cube
                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),

                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),

                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),

                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),

                          (0.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (0.0f0, 0.0f0),
                          (0.0f0, 1.0f0),

                          (0.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (0.0f0, 0.0f0),
                          (0.0f0, 1.0f0)]

floor_texcoords = Vec2f0[
                          # The floor
                          (0.0f0, 0.0f0),
                          (1.0f0, 0.0f0),
                          (1.0f0, 1.0f0),
                          (1.0f0, 1.0f0),
                          (0.0f0, 1.0f0),
                          (0.0f0, 0.0f0)]

vertex_colors = fill(Vec3{Float32}(1,1,1), 36)
floor_colors = fill(Vec3{Float32}(0,0,0),6)

vertex_shader = GLA.vert"""
#version 150

in vec3 position;
in vec3 color;
in vec2 texcoord;

out vec3 Color;
out vec2 Texcoord;

uniform vec3 overrideColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;

void main()
{
    Color = overrideColor * color;
    Texcoord = texcoord;
    gl_Position = proj * view * model * vec4(position, 1.0);
}
"""
fragment_shader = load(joinpath(dirname(@__FILE__), "shaders", "puppykitten_color.frag"))
fragment_shader = load(joinpath("tutorials", "shaders", "puppykitten_color.frag"))
prog = GLA.Program(GLA.Shader(vertex_shader), fragment_shader)

model1 = Mat4(diagm(0=>ones(Float32, 4)))
model2 = Mat4{Float32}(
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, -1, 0,
        0,0,-1,1.0f0
    )

function lookat(eyePos, lookAt, up)
    z  = normalize(eyePos-lookAt)
    x  = normalize(cross(up, z))
    y  = normalize(cross(z, x))
    T0 = 0.0f0
    return Mat4{Float32}(
        x[1], y[1], z[1], T0,
        x[2], y[2], z[2], T0,
        x[3], y[3], z[3], T0,
        -dot(x,eyePos),-dot(y,eyePos),-dot(z,eyePos),1.0f0
    )
end

function perspectiveprojection(fovy, aspect, znear::T, zfar::T) where T
    h = T(tan(deg2rad(fovy)) * znear)
    w = T(h * aspect)
    bottom = -h
    top = h
    left = -w
    right = w

    (right == left || bottom == top || znear == zfar) && return Mat4{T}(I)
    T0, T1, T2 = zero(T), one(T), T(2)
    return Mat4{T}(
        T2 * znear / (right - left), T0, T0, T0,
        T0, T2 * znear / (top - bottom), T0, T0,
        (right + left) / (right - left), (top + bottom) / (top - bottom), -(zfar + znear) / (zfar - znear), -T1,
        T0, T0, -(T2 * znear * zfar) / (zfar - znear), T0
    )
end

view = lookat(Vec3((2.5, 2.5, 2)), Vec3((0, 0, 0)), Vec3((0, 0, 1)))
proj = perspectiveprojection(45f0, 600f0/600f0, 1f0, 10f0)

tex_kitten = GLA.Texture(collect(kitten'))
tex_puppy  = GLA.Texture(collect(puppy'))

## Now render the distinct objects. Rather than always using std_renderobject,
## here we control the settings manually.
# The cube
buffers_cube = GLA.generate_buffers(prog, GLA.GEOMETRY_DIVISOR, position = vertex_positions,
                                                           color = vertex_colors,
                                                           texcoord=vertex_texcoords)

vao_cube = GLA.VertexArray(buffers_cube)
# The floor. This is drawn without writing to the depth buffer, but we
# write stencil values.
buffers_floor = GLA.generate_buffers(prog, GLA.GEOMETRY_DIVISOR, position = floor_positions,
                                                           color = floor_colors,
                                                           texcoord=floor_texcoords)
vao_floor = GLA.VertexArray(buffers_floor)

glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LEQUAL)
glClearColor(1,1,1,1) # make the background white, so we can see the floor
glClearStencil(0)     # clear the stencil buffer with 0

while !GLFW.WindowShouldClose(window)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    GLA.bind(prog)

    GLA.gluniform(prog, :texKitten, 0, tex_kitten) #first texture sampler
    GLA.gluniform(prog, :texPuppy, 1, tex_puppy) # second texture sampler

    # Render Cube
    GLA.gluniform(prog, :model, model1)
    GLA.gluniform(prog, :view, view)
    GLA.gluniform(prog, :proj, proj)
    GLA.gluniform(prog, :overrideColor, Vec3{Float32}((1,1,1)))

    glDisable(GL_STENCIL_TEST)
    GLA.bind(vao_cube)
    GLA.draw(vao_cube)

    # Render Floor
    GLA.gluniform(prog, :model, model1)
    glDepthMask(GL_FALSE)                  # don't write to depth buffer
    glEnable(GL_STENCIL_TEST)              # use stencils
    glStencilMask(0xff)                    # do write to stencil buffer
    glStencilFunc(GL_ALWAYS, 1, 0xff)      # all pass
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)  # replace stencil value
    glClear(GL_STENCIL_BUFFER_BIT)         # start with empty buffer
    GLA.bind(vao_floor)
    GLA.draw(vao_floor)

    # Render reflection
    GLA.gluniform(prog, :model, model2)
    GLA.gluniform(prog, :overrideColor, Vec3{Float32}((0.3,0.3,0.3)))

    glStencilFunc(GL_EQUAL, 1, 0xff)
    glStencilMask(0x00)

    GLA.bind(vao_cube)
    GLA.draw(vao_cube)

    GLFW.SwapBuffers(window)
    GLFW.PollEvents()
    if GLFW.GetKey(window, GLFW.KEY_ESCAPE) == GLFW.PRESS
        GLFW.SetWindowShouldClose(window, true)
    end
end
GLFW.DestroyWindow(window)  # needed if you're running this from the REPL


"""
Definition of simulation data types for juliaPBF
    All variables are treated using the struct
    in SimulationDataStruct. the result of phy
    sical calculation will be updated into the
    struct.
"""
## EXPORT TYPES
export
    ParticleData,
    BoundaryData,
    GridData
## EXPORT TYPE FUNCTIONs
export
    # Change
    changeGridID,
    changeLambda,
    changeVel,
    changePos,
    changeTemppos,
    changeMass,
    # Add
    addVel,
    addPos,
    addTemppos,
    addmMass,
    # ETC
    updatePos

""" a Particle Data Type """
struct ParticleData ## Array of Struct
        gridID      ::Int64
        vel         ::Vec2
        pos         ::Vec2
        temppos     ::Vec2
        lambda      ::Float64
        mass        ::Float64
        phase       ::Int64 # SimulationData와 맞춰진. 이걸타고 들어가서 Fluid/Solid 나뉠것임
end
# CHANGE VALUEs
changeGridID(a::ParticleData, b::Int64)   = ParticleData(b,        a.vel,   a.pos,   a.temppos,   a.lambda,   a.mass,   a.phase)
changeLambda(a::ParticleData, b::Float64) = ParticleData(a.gridID, a.vel,   a.pos,   a.temppos,   b,          a.mass,   a.phase)
changeVel(a::ParticleData, b::Vec2)       = ParticleData(a.gridID, b,       a.pos,   a.temppos,   a.lambda,   a.mass,   a.phase)
changePos(a::ParticleData, b::Vec2)       = ParticleData(a.gridID, a.vel,   b,       a.temppos,   a.lambda,   a.mass,   a.phase)
changeTemppos(a::ParticleData, b::Vec2)   = ParticleData(a.gridID, a.vel,   a.pos,   b,           a.lambda,   a.mass,   a.phase)
changeMass(a::ParticleData, b::Float64)   = ParticleData(a.gridID, a.vel,   a.pos,   a.temppos,   a.lambda,   b,        a.phase)
# ADD VALUEs
addVel(a::ParticleData, b::Vec2)          = ParticleData(a.gridID, a.vel+b, a.pos,   a.temppos,   a.lambda,   a.mass,   a.phase)
addPos(a::ParticleData, b::Vec2)          = ParticleData(a.gridID, a.vel,   a.pos+b, a.temppos,   a.lambda,   a.mass,   a.phase)
addTemppos(a::ParticleData, b::Vec2)      = ParticleData(a.gridID, a.vel,   a.pos,   a.temppos+b, a.lambda,   a.mass,   a.phase)
addMass(a::ParticleData, b::Float64)      = ParticleData(a.gridID, a.vel,   a.pos,   a.temppos,   a.lambda,   a.mass+b, a.phase)
# ETC
updatePos(a::ParticleData)                = ParticleData(a.gridID, a.vel,   a.temppos,a.temppos,  a.lambda,   a.mass,   a.phase)
isless(a::ParticleData,b::ParticleData)   = isless(a.gridID::Int64,b.gridID::Int64) #for Sorting by gridID

""" Boundary Data Type """
struct BoundaryData
    points          ::Vector{Vec2}
    lines           ::Vector{iVec2} # Point Index 짝궁
end
""" Grid Data Type """
struct GridData
    nNearGrid       ::Vector{Int64} # Cumulate 형식
    nearGridIndex   ::Vector{Int64} # Near Grid Index를 1줄행렬로 가지고 있음
    nGridParticles  ::Vector{Int64} # 그리드 내 속한 입자 수
end


using ..MultiInterface

export
    importance_sample,
    support_quality,
    support_quality_rank,
    support_quality_from_rank,
    __OptVec,
    __Opt

# These are the new operator definitions where the type signature is specified

"""__OptVec{T} = Union{Vector{Union{}}, Vector{T}}"""
__OptVec{T} = Union{Vector{Union{}}, Vector{T}}
"""__Opt{T} = Union{Nothing, T}"""
__Opt{T} = Union{Nothing, T}

# to support
MultiInterface.get_imp(::Nothing, args...) = nothing

@interface is_deterministic(sf::SFunc)::Bool
@interface sample(sf::SFunc{I,O}, i::I)::O where {I,O}
@interface sample_logcpdf(sf::SFunc{I,O}, i::I)::Tuple{O, AbstractFloat} where {I,O}
@interface invert(sf::SFunc{I,O}, o::O)::I where {I,O}
@interface lambda_msg(sf::SFunc{I,O}, i::SFunc{<:__Opt{Tuple{}}, O})::SFunc{<:__Opt{Tuple{}}, I} where {I,O}
@interface marginalize(sf::SFunc{I,O}, i::SFunc{<:__Opt{Tuple{}}, I})::SFunc{<:__Opt{Tuple{}}, O} where {I,O}
@interface logcpdf(sf::SFunc{I,O}, i::I, o::O)::AbstractFloat where {I,O}
@interface cpdf(sf::SFunc{I,O}, i::I, o::O)::AbstractFloat where {I,O}
@interface log_cond_prob_plus_c(sf::SFunc{I,O}, i::I, o::O)::AbstractFloat where {I,O}
@interface f_expectation(sf::SFunc{I,O}, i::I, fn::Function) where {I,O}
@interface expectation(sf::SFunc{I,O}, i::I)::O where {I,O}
@interface variance(sf::SFunc{I,O}, i::I)::O where {I,O}
@interface get_score(sf::SFunc{Tuple{I},O}, i::I)::AbstractFloat where {I,O}
@interface get_log_score(sf::SFunc{Tuple{I},O}, i::I)::AbstractFloat where {I,O}

@impl begin
    struct SFuncExpectation end

    function expectation(sf::SFunc{I,O}, i::I) where {I,O}
        return f_expectation(sf, i, x -> x)
    end
end

@interface support(sf::SFunc{I,O},
                    parranges::NTuple{N,Vector},
                    size::Integer,
                    curr::Vector{<:O}) where {I,O,N}

function importance_sample end

"""
    support_quality_rank(sq::Symbol)

Convert the support quality symbol into an integer for comparison.
"""
function support_quality_rank(sq::Symbol)
    if sq == :CompleteSupport return 3
    elseif sq == :IncrementalSupport return 2
    else return 1 end
end

"""
    support_quality_from_rank(rank::Int)

Convert the rank back into the support quality.
"""
function support_quality_from_rank(rank::Int)
    if rank == 3 return :CompleteSupport
    elseif rank == 2 return :IncrementalSupport
    else return :BestEffortSupport() end
end

@interface support_quality(sf::SFunc, parranges)

@impl begin
    struct SFuncSupportQuality end

    function support_quality(s::SFunc, parranges)
            :BestEffortSupport
    end
end

@interface bounded_probs(sf::SFunc{I,O},
                         range::__OptVec{<:O},
                         parranges::NTuple{N,Vector})::Tuple{Vector{<:AbstractFloat}, Vector{<:AbstractFloat}} where {I,O,N}

@interface make_factors(sf::SFunc{I,O},
                        range::__OptVec{<:O},
                        parranges::NTuple{N,Vector},
                        id,
                        parids::Tuple)::Tuple{Vector{<:Scruff.Utils.Factor}, Vector{<:Scruff.Utils.Factor}} where {I,O,N}

#= Statistics computation not included in the release
@interface initial_stats(sf::SFunc)

# TODO create an abstract type Stats{I,O}
# (range, parranges, pi's, lambda's)
@interface expected_stats(sf::SFunc{I,O},
                          range::__OptVec{<:O},
                          parranges::NTuple{N,Vector},
                          pis::NTuple{M,Dist},
                          child_lambda::Score{<:O}) where {I,O,N,M}

@interface accumulate_stats(sf::SFunc, existing_stats, new_stats)
@interface maximize_stats(sf::SFunc, stats)
=#
@interface compute_bel(sf::SFunc{I,O},
                      range::__OptVec{<:O},
                      pi::Dist{<:O},
                      lambda::Score)::Dist{<:O} where {I,O}

@interface compute_lambda(sf::SFunc,
                          range::__OptVec,
                          lambda_msgs::Vector{<:Score})::Score

@interface send_pi(sf::SFunc{I,O},
                   range::__OptVec{O},
                   bel::Dist{O},
                   lambda_msg::Score)::Dist{<:O} where {I,O}

@interface outgoing_pis(sf::SFunc,
                        range::__OptVec,
                        bel::Dist,
                        incoming_lambdas::__OptVec{<:Score})::Vector{<:Dist}

@interface outgoing_lambdas(sf::SFunc{I,O},
                  lambda::Score,
                  range::__OptVec,
                  parranges::NTuple{N,Vector},
                  incoming_pis::Tuple)::Vector{<:Score} where {N,I,O}

@interface compute_pi(sf::SFunc{I,O},
                     range::__OptVec{O},
                     parranges::NTuple{N,Vector},
                     incoming_pis::Tuple)::Dist{<:O} where {N,I,O}

@interface send_lambda(sf::SFunc{I,O},
                       lambda::Score,
                       range::__OptVec,
                       parranges::NTuple{N,Vector},
                       incoming_pis::Tuple,
                       parent_idx::Integer)::Score where {N,I,O}


module RegressionTools

using Distances: euclidean
using StatsFuns: logistic, softplus, logit
using PLINK

import Base.vecnorm

export threshold!
export threshold
export update_residuals!
export update_indices!
export update_col!
export update_weights!
export update_xk!
export fill_perm!
export update_xb!
export count_partialnz
export fill_partial!
export update_partial_residuals!
export difference!
export ypatzmw!
export project_k!
export selectpermk!
export selectpermk
export mce
export logistic_loglik
export update_x!
export df_norm
export logistic_grad!
export logistic!
export log2xb!
export fit_logistic
export mask!
export cv_get_folds
export vecnorm
export issymmetric
export vec!

const Float = Union{Float32, Float64}

depwarn("NOTE: As of Julia v0.6, this package is deprecated and no longer maintained.\n")

# do not load any functions; deprecated package will now be empty
#include("regtool.jl")
#include("logistic.jl")

end # end module RegressionTools


# Functions related to an individual's genome:
# - meiosis
# - mutation
# - genome creation
# - auxiliary functions

"""
    meiosis(genome, maternal)

Carry out meiosis on a genome (marked as maternal or not). Returns a haploid
gamete genome. (genome => array of chromosomes)
"""
function meiosis(genome::Array{Chromosome,1}, maternal::Bool, heterozygosity::Bool)
    #NOTE This function assumes that a genome is sorted, with all paternal chromosomes
    # in the first half and all maternals in the second (or vice versa).
    gametelength = Int(length(genome)/2)
    gamete = Array{Chromosome,1}(undef, gametelength)
    i = 1
    while i <= gametelength
        rand(Bool) ? g = i : g = i+gametelength
        # We do not deepcopy parental genes for performance reasons. However, that
        # means that if these genes are to be mutated, they must be deepcopied first.
        gamete[i] = heterozygosity ? LineageChromosome(genome[g].genes, maternal, genome[g].lineage) :
            DefaultChromosome(genome[g].genes, maternal)
        i += 1
    end
    gamete
end

"""
    gettraitdict(chromosomes, traitnames)

Convert a genome (an array of chromosomes) into a dict of traits and their values.
"""
function gettraitdict(chrms::Array{Chromosome, 1}, traitnames::Array{String, 1})
    #TODO can this be made more efficient? It's called really often...
    traitdict = Dict{String, Float64}()
    traits = Array{Trait,1}()
    nchrms = 0
    ngenes = 0
    for chrm in chrms
        nchrms += 1
        for gene in chrm.genes
            ngenes += 1
            append!(traits, gene.codes)
        end
    end
    for traitidx in eachindex(traitnames)
        wantedtraits = skipmissing(map(x -> x.value, filter(x -> x.nameindex == traitidx, traits)))
        traitdict[traitnames[traitidx]] = mean(wantedtraits)
        traitdict[traitnames[traitidx] * "sd"] = std(wantedtraits)
    end
    traitdict["ngenes"] = ngenes
    traitdict["nlnkgunits"] = nchrms
    traitdict
end

"""
    gettraitdictfast(chromosomes, traitnames)

Convert a genome (an array of chromosomes) into a dict of traits and their values.
This is an optimised version that can be run if `degpleiotropy` is 0 and `linkage` is "none".
"""
function gettraitdictfast(chrms::Array{Chromosome, 1}, traitnames::Array{String, 1})
    # Makes use of the fact that with `degpleiotropy == 0` and `linkage == "none"`,
    # there is exactly one trait per chromosome (one gene per chromosome and one trait per gene),
    # and the chromosomes are arranged in trait-order.
    genomesize = length(chrms)
    traitdict = Dict{String, Float64}()
    haploidlength = Int(genomesize/2)
    values = Array{Float64,}(undef,genomesize)
    for c in eachindex(chrms)
        @inbounds values[c] = chrms[c].genes[1].codes[1].value
    end
    for traitidx in eachindex(traitnames)
        wantedtraits = (values[traitidx], values[traitidx+haploidlength])
        traitdict[traitnames[traitidx]] = mean(wantedtraits)
        traitdict[traitnames[traitidx] * "sd"] = std(wantedtraits)
    end
    traitdict["ngenes"] = genomesize
    traitdict["nlnkgunits"] = genomesize
    traitdict
end

"""
    getseqsimilarity(seqone, seqtwo)

Compare two strings and return similarity.
"""
function getseqsimilarity(indgene::AbstractString, mategene::AbstractString)
    basediffs = 0
    #FIXME this should find out whether indgene or mategene is shorter and use that,
    # then count the length difference towards the base diff
    for i in eachindex(indgene)
        try
            indgene[i] != mategene[i] && (basediffs += 1) # alternatively use bioinformatics tools
        catch # e.g., in case of differently long genes
            basediffs += 1
        end
    end
    seqidentity = 1 - (basediffs / length(indgene))
end

"""
    getseqs(genome, traitname)

Find and return the sequences of genes that code for the given trait.
"""
function getseqs(genome::Array{Chromosome, 1}, traitname::String)
    seqs = String[]
    traitidx = findfirst(x -> x == traitname, setting("traitnames"))
    for chrm in genome
        for gene in chrm.genes
            if any(x -> x.nameindex == traitidx, gene.codes)
                setting("compressgenes") ? push!(seqs, num2seq(gene.sequence)) : push!(seqs, gene.sequence)
            end
        end
    end
    seqs
end

"""
    iscompatible(mate, individual)

Check to see whether two individual organisms are reproductively compatible by
comparing gene sequences (which sequence depends on setting("speciation")).
"""
function iscompatible(mate::Individual, ind::Individual)
    mate.lineage != ind.lineage && return false
    indgenes, mategenes = "", ""
    if setting("speciation") == "off"
        return true # all conspecifics are compatible, no speciation can happen
    elseif setting("speciation") == "neutral"
        # default, use a non-coding sequence for mutation-order speciation
        indgenes = join(getseqs(ind.genome, "compat"))
        mategenes = join(getseqs(mate.genome, "compat"))
    elseif setting("speciation") == "ecological"
        # use all coding sequences for ecological speciation
        for tr in setting("traitsforecospec")
            (tr == "compat") && continue
            indgenes *= join(getseqs(ind.genome, tr))
            mategenes *= join(getseqs(mate.genome, tr))
        end
    end
    seqidentity = getseqsimilarity(indgenes, mategenes)
    seqidentity >= ind.traits["seqsimilarity"]
end

"""
    seq2num(sequence)

Convert a DNA base sequence (a string) into binary and then into an integer.
This saves memory.
"""
#XXX Actually, this hardly seems to make a difference :-(
function seq2num(sequence::String)
    # This function effectively uses the same technique as seq2bignum, but it skips the
    # intermediate allocations and should therefore be more efficient.
    num::Int64 = 0  # Int64 allows for max length of 21bp
    for b in eachindex(sequence)
        if sequence[end+1-b] == 'a'
            num += 2^(3*(b-1)) * 4 # b'100'
        elseif sequence[end+1-b] == 'c'
            num += 2^(3*(b-1)) * 5 # b'101'
        elseif sequence[end+1-b] == 'g'
            num += 2^(3*(b-1)) * 6 # b'110'
        elseif sequence[end+1-b] == 't'
            num += 2^(3*(b-1)) * 7 # b'111'
        end
    end
    num
end

function intseq(n::Int)
    #XXX doesn't seem to work either? I'm starting to think I'm optimising in the wrong place :-(
    num::Int64 = 0  # Int64 allows for max length of 21bp
    for b in 0:(n-1)
        # basically like seq2bignum(), but skips all intermediate allocations
        num += 2^(3*b) * rand(4:7)
    end
    num
end

"""
    seq2bignum(sequence)

Convert a DNA base sequence (a string) into binary and then into a BigInt (for
larger genes). This saves memory.
"""
function seq2bignum(sequence::String)
    bases = "acgt"
    binary = ""
    for base in sequence
        binary *= string(findfirst(x -> x == base, bases) + 3, base = 2)
    end
    parse(BigInt, binary, base = 2) # BigInt allows arbitrary-length sequences
end

"""
    num2seq(n)

Convert an integer into binary and then into a DNA base sequence string.
"""
function num2seq(n::Integer)
    bases = "acgt"
    binary = string(n, base = 2)
    sequence = ""
    for i in 1:3:(length(binary) - 2)
        sequence *= string(bases[parse(Int, binary[i:(i + 2)], base = 2) - 3])
    end
    sequence
end

"""
    createtraits()

Create an array of trait objects generated from the default trait values (with a
random offset).
"""
function createtraits()
    #XXX this is all very ugly. (case/switch w/ v. 2.0+?)
    traitnames = setting("traitnames")
    traits = Array{Trait,1}(undef,length(traitnames))
    # exponential distributions of body sizes:
    repoffset = setting("maxrepsize") - setting("minrepsize")
    seedoffset = setting("maxseedsize") - setting("minseedsize")
    tempoffset = setting("maxtemp") - setting("mintemp")
    precoffset = setting("maxprec") - setting("minprec")
    #FIXME this needs to be changed to account for allometric relationships
    repsize, seedsize = 0, 0
    while true
        repsize = exp(setting("minrepsize") + repoffset * rand())
        seedsize = exp(setting("minseedsize") + seedoffset * rand())
        repsize > seedsize && break
    end
    for idx in eachindex(traitnames)
        if occursin("dispshape", traitnames[idx])
            traits[idx] = Trait(idx, rand() * setting("dispshape"))
        elseif occursin("dispmean", traitnames[idx])
            traits[idx] = Trait(idx, rand() * setting("dispmean"))
        elseif occursin("precopt", traitnames[idx])
            traits[idx] = Trait(idx, setting("minprec") + rand() * precoffset)
        elseif occursin("prectol", traitnames[idx])
            traits[idx] = Trait(idx, rand() * setting("maxbreadth"))
        elseif occursin("repsize", traitnames[idx])
            traits[idx] = Trait(idx, repsize)
        elseif occursin("seqsimilarity", traitnames[idx]) && setting("fixtol")
            traits[idx] = Trait(idx, setting("tolerance"))
        elseif occursin("seqsimilarity", traitnames[idx]) && !setting("fixtol")
            traits[idx] = Trait(idx, rand()) # assortative mating might evolve
        elseif occursin("seedsize", traitnames[idx])
            traits[idx] = Trait(idx, seedsize)
        elseif occursin("tempopt", traitnames[idx])
            traits[idx] = Trait(idx, setting("mintemp") + rand() * tempoffset)
        elseif occursin("temptol", traitnames[idx])
            traits[idx] = Trait(idx, rand() * setting("maxbreadth"))
        else
            traits[idx] = Trait(idx, rand())
        end
    end
    traits
end

"""
    creategenes(ngenes, traits)

Randomly create a given number of gene objects, with their base sequence and
associated traits. Returns the result as an array of AbstractGenes.
"""
function creategenes(ngenes::Int, traits::Array{Trait,1})
    genes = Array{AbstractGene,1}(undef, ngenes)
    bases = ['a','c','g','t']
    compatidx = findfirst(x -> x == "compat", setting("traitnames"))
    # initialise each gene with an arbitrary sequence
    for i in 1:ngenes
        if setting("compressgenes") #default
            genes[i] = Gene(intseq(setting("smallgenelength")), Trait[])
        else
            sequence = String(rand(bases, setting("smallgenelength")))
            genes[i] = StringGene(sequence, Trait[])
        end
    end
    # assign traits to the genes
    if setting("degpleiotropy") == 0
        # Disable polygenic inheritance and pleiotropy: make sure one gene codes for one trait.
        for tr in eachindex(traits)
            (tr == compatidx) && (genes[tr] = createcompatgene(bases, compatidx))
            genes[tr].codes = [traits[tr]]
        end
    else # allow for pleiotropy as well as polygenic inheritance
        for trait in traits
            (trait.nameindex == compatidx) && continue
            # We're actually setting polygenic inheritance here, rather than pleiotropy.
            # Pleiotropy is introduced indirectly, because a higher number of coding genes
            # increases the likelihood of picking a gene that already codes for other traits.
            ncodinggenes = rand(Geometric(1 - setting("degpleiotropy"))) + 1
            codinggenes = rand(genes,ncodinggenes)
            for gene in codinggenes
                push!(gene.codes,deepcopy(trait))
            end
        end
        push!(genes, createcompatgene(bases, compatidx))
    end
    genes
end

"""
    createcompatgene(bases, compatidx)

Create the gene that is used to determine reproductive compatibility.
"""
function createcompatgene(bases::Array{Char,1}, compatidx::Int)::AbstractGene
    # Note: we only need big genes for the compatibility gene, because we need a longer base
    # sequence than offered by `smallgenelength` if we want to do phylogenetic analyses.
    setting("usebiggenes") ? seql = setting("biggenelength") : seql = setting("smallgenelength")
    cseq = String(rand(bases, seql))
    ctrait = [Trait(compatidx, 0.5)]
    if setting("compressgenes")
        if setting("usebiggenes")
            return BigGene(seq2bignum(cseq), ctrait)
        else
            return Gene(seq2num(cseq), ctrait)
        end
    else
        return StringGene(cseq, ctrait)
    end
end

"""
    createchrms(nchrms, genes)

Randomly distribute the passed genes into the given number of haploid chromosomes.
Returns a diploid genome (array of chromosome objects).
"""
function createchrms(nchrms::Int,genes::Array{AbstractGene,1},lineage::String)::Array{Chromosome,1}
    chromosomes = Array{Chromosome,1}(undef,nchrms*2)
    if nchrms == 1 # linkage == "full"
        chromosomes[1] = @Chromosome(genes, true, lineage)
        chromosomes[2] = @Chromosome(deepcopy(genes),false, lineage)
    elseif nchrms == length(genes) # linkage == "none"
        for g in eachindex(genes)
            chromosomes[g] = @Chromosome([genes[g]], true, lineage)
            chromosomes[nchrms+g] = @Chromosome([deepcopy(genes[g])], false, lineage)
        end
    else # linkage == "random"
        chrmsplits = sort(rand(1:length(genes), nchrms-1))
        for chr in 1:nchrms
            if chr==1 # first chromosome
                cgenes = genes[1:chrmsplits[chr]]
            elseif chr==nchrms # last chromosome
                cgenes = genes[(chrmsplits[chr-1]+1):end]
            else
                cgenes = genes[(chrmsplits[chr-1]+1):chrmsplits[chr]]
            end
            chromosomes[chr] = @Chromosome(cgenes, true, lineage)
            chromosomes[nchrms+chr] = @Chromosome(deepcopy(cgenes), false, lineage)
        end
    end
    chromosomes
end

"""
    varyalleles!(genes, locivar)

Mutate gene traits in the passed array of genes.
"""
function varyalleles!(genes::Array{AbstractGene, 1}, locivar::Float64)
    locivar == 0 && return
    for gene in genes
        mutate!(gene.codes, locivar)
    end
end

"""
    varyalleles!(chromosomes, locivar)

Mutate gene traits in the passed array of chromosomes.
"""
function varyalleles!(chrms::Array{Chromosome, 1}, locivar::Float64)
    locivar == 0 && return
    for chrm in chrms
        varyalleles!(chrm.genes, locivar)
    end
end

"""
    mutate!(traits, locivar)

Loop over an array of traits, mutating each value in place along a normal distribution.
`locivar` can be used to scale the variance of the normal distribution used to draw new
trait values (together with `setting(phylconstr]`).
"""
function mutate!(traits::Array{Trait, 1}, locivar::Float64 = 1.0)
    setting("phylconstr") * locivar == 0 && return
    for trait in traits
        traitname = setting("traitnames")[trait.nameindex]
        occursin("seqsimilarity", traitname) && setting("fixtol") && continue
        oldvalue = trait.value
        occursin("tempopt", traitname) && (oldvalue -= 273)
        while oldvalue <= 0 # make sure sd of Normal dist != 0
            oldvalue = abs(rand(Normal(0,0.01)))
        end
        newvalue = rand(Normal(oldvalue, oldvalue * setting("phylconstr") * locivar))
        #FIXME there are no traits with "prob" in their name?!
        # Is this meant to cap compat/seqsimilarity/selfing?
        (newvalue > 1 && occursin("prob", traitname)) && (newvalue=1.0)
        while newvalue <= 0
            newvalue = rand(Normal(oldvalue, oldvalue * setting("phylconstr") * locivar))
        end
        occursin("tempopt", traitname) && (newvalue += 273)
        trait.value = newvalue
    end
end

"""
    mutate!(individual, temp)

Mutate an individual's genome (sequence and traits) in place.
"""
function mutate!(ind::Individual, temp::Float64)
    muts = setting("mutationrate") * exp(-act/(boltz*temp))
    nmuts = rand(Poisson(muts))
    nmuts == 0 && return
    chrmidcs = rand(eachindex(ind.genome), nmuts)
    for c in chrmidcs
        # We need to deepcopy genes here, because they are not automatically deepcopied during
        # reproduction for performance reasons
        ind.genome[c] = deepcopy(ind.genome[c])
        length(ind.genome[c].genes) == 0 && continue
        g = rand(eachindex(ind.genome[c].genes))
        gseq = ind.genome[c].genes[g].sequence
        setting("compressgenes") ? charseq = collect(num2seq(gseq)) : charseq = collect(gseq)
        i = rand(eachindex(charseq))
        newbase = rand(collect("acgt"),1)[1]
        while newbase == charseq[i]
            newbase = rand(collect("acgt"),1)[1]
        end
        charseq[i] = newbase
        mutate!(ind.genome[c].genes[g].codes)
        if setting("compressgenes")
            if length(charseq) > 21
                ind.genome[c].genes[g].sequence = seq2bignum(String(charseq))
            else
                ind.genome[c].genes[g].sequence = seq2num(String(charseq))
            end
        else
            ind.genome[c].genes[g].sequence = String(charseq)
        end
    end
    if setting("degpleiotropy") == 0 && setting("linkage") == "none"
        ind.traits = gettraitdictfast(ind.genome, setting("traitnames"))
    else
        ind.traits = gettraitdict(ind.genome, setting("traitnames"))
    end
end

"""
    mutate!(patch)

Mutate all seed individuals in a patch.
"""
function mutate!(patch::Patch)
    setting("mode") == "zosterops" ? temp = setting("bodytemp") : temp = patch.temp
    for ind in patch.seedbank
        mutate!(ind, temp)
    end
end

"""
    mutate!(world)

Mutate the world. (That sounds scary!)
"""
function mutate!(world::Array{Patch, 1})
    for patch in world
        (patch.isisland || !setting("static")) && mutate!(patch)
    end
end