text:
# Autogenerated wrapper script for Trilinos_jll for x86_64-apple-darwin-libgfortran4
export libamesos, libaztecoo, libbelos, libbelosepetra, libepetra, libepetraext, libifpack, libisorropia, libloca, liblocaepetra, liblocalapack, libnox, libnoxepetra, libnoxlapack, libsacado, libsimpi, libteuchoscomm, libteuchoscore, libteuchosnumerics, libteuchosparameterlist, libteuchosremainder, libtrilinoscouplings, libtrilinosss, libtriutils, libzoltan

using SuiteSparse_jll
using OpenBLAS32_jll
using CompilerSupportLibraries_jll
JLLWrappers.@generate_wrapper_header("Trilinos")
JLLWrappers.@declare_library_product(libamesos, "@rpath/libamesos.12.dylib")
JLLWrappers.@declare_library_product(libaztecoo, "@rpath/libaztecoo.12.dylib")
JLLWrappers.@declare_library_product(libbelos, "@rpath/libbelos.12.dylib")
JLLWrappers.@declare_library_product(libbelosepetra, "@rpath/libbelosepetra.12.dylib")
JLLWrappers.@declare_library_product(libepetra, "@rpath/libepetra.12.dylib")
JLLWrappers.@declare_library_product(libepetraext, "@rpath/libepetraext.12.dylib")
JLLWrappers.@declare_library_product(libifpack, "@rpath/libifpack.12.dylib")
JLLWrappers.@declare_library_product(libisorropia, "@rpath/libisorropia.12.dylib")
JLLWrappers.@declare_library_product(libloca, "@rpath/libloca.12.dylib")
JLLWrappers.@declare_library_product(liblocaepetra, "@rpath/liblocaepetra.12.dylib")
JLLWrappers.@declare_library_product(liblocalapack, "@rpath/liblocalapack.12.dylib")
JLLWrappers.@declare_library_product(libnox, "@rpath/libnox.12.dylib")
JLLWrappers.@declare_library_product(libnoxepetra, "@rpath/libnoxepetra.12.dylib")
JLLWrappers.@declare_library_product(libnoxlapack, "@rpath/libnoxlapack.12.dylib")
JLLWrappers.@declare_library_product(libsacado, "@rpath/libsacado.12.dylib")
JLLWrappers.@declare_library_product(libsimpi, "@rpath/libsimpi.12.dylib")
JLLWrappers.@declare_library_product(libteuchoscomm, "@rpath/libteuchoscomm.12.dylib")
JLLWrappers.@declare_library_product(libteuchoscore, "@rpath/libteuchoscore.12.dylib")
JLLWrappers.@declare_library_product(libteuchosnumerics, "@rpath/libteuchosnumerics.12.dylib")
JLLWrappers.@declare_library_product(libteuchosparameterlist, "@rpath/libteuchosparameterlist.12.dylib")
JLLWrappers.@declare_library_product(libteuchosremainder, "@rpath/libteuchosremainder.12.dylib")
JLLWrappers.@declare_library_product(libtrilinoscouplings, "@rpath/libtrilinoscouplings.12.dylib")
JLLWrappers.@declare_library_product(libtrilinosss, "@rpath/libtrilinosss.12.dylib")
JLLWrappers.@declare_library_product(libtriutils, "@rpath/libtriutils.12.dylib")
JLLWrappers.@declare_library_product(libzoltan, "@rpath/libzoltan.12.dylib")
function __init__()
    JLLWrappers.@generate_init_header(SuiteSparse_jll, OpenBLAS32_jll, CompilerSupportLibraries_jll)
    JLLWrappers.@init_library_product(
        libamesos,
        "lib/libamesos.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libaztecoo,
        "lib/libaztecoo.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libbelos,
        "lib/libbelos.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libbelosepetra,
        "lib/libbelosepetra.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libepetra,
        "lib/libepetra.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libepetraext,
        "lib/libepetraext.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libifpack,
        "lib/libifpack.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libisorropia,
        "lib/libisorropia.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libloca,
        "lib/libloca.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liblocaepetra,
        "lib/liblocaepetra.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        liblocalapack,
        "lib/liblocalapack.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libnox,
        "lib/libnox.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libnoxepetra,
        "lib/libnoxepetra.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libnoxlapack,
        "lib/libnoxlapack.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libsacado,
        "lib/libsacado.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libsimpi,
        "lib/libsimpi.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libteuchoscomm,
        "lib/libteuchoscomm.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libteuchoscore,
        "lib/libteuchoscore.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libteuchosnumerics,
        "lib/libteuchosnumerics.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libteuchosparameterlist,
        "lib/libteuchosparameterlist.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libteuchosremainder,
        "lib/libteuchosremainder.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libtrilinoscouplings,
        "lib/libtrilinoscouplings.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libtrilinosss,
        "lib/libtrilinosss.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libtriutils,
        "lib/libtriutils.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libzoltan,
        "lib/libzoltan.12.12.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
module OBJMeshes

export read_obj, write_obj, tri_vertex_str
export cube_mesh, cylinder_mesh, sphere_mesh, estimate_normals, portal_mesh, portal_background_mesh
export OBJTriangle, OBJMesh

using FileIO
using LinearAlgebra

#push!(LOAD_PATH, pwd())
using ..GfxBase

""" OBJTriangle
A struct that represents a single triangle in a mesh. """
mutable struct OBJTriangle
    positions::Array{Int, 1} # vertex position indices
    uvs::Array{Int, 1} # vertex texture coordinate indices
    normals::Array{Int, 1} # normal vector indices
end

""" OBJMesh
A struct that represents an indexed triangle mesh for reading from
or writing to OBJ format. """
mutable struct OBJMesh
    positions::Array{Vec3, 1} # all vertex positions
    uvs::Array{Vec2, 1} # all texture coordinates
    normals::Array{Vec3, 1} # all vertex normals
    triangles::Array{OBJTriangle, 1} # the OBJTriangles belonging to the mesh
end

""" read_obj(obj_filename)
Read a mesh in OBJ format from file obj_filename."""
function read_obj(obj_filename)
    m = OBJMesh([], [], [], []) # create a mesh
    open(obj_filename) do f
        for (line_number, line) in enumerate(eachline(f))
            if line == "" || line[1] == "#"
                continue # skip comments
            end
            # Read the line and add its contents to the correct field of m:
            tokens = split(strip(line))
            if tokens[1] == "v" # vertex
                push!(m.positions, Vec3([parse(Float64, x) for x in tokens[2:end]]...))
            elseif tokens[1] == "vt" # vertex texture
                push!(m.uvs, Vec2([parse(Float64, x) for x in tokens[2:end]]...))
            elseif tokens[1] == "vn" # vertex normal
                push!(m.normals, Vec3([parse(Float64, x) for x in tokens[2:end]]...))
            elseif tokens[1] == "f"
                # create a OBJTriangle face:
                points = []
                uvs = []
                normals = []
                # handle faces with no texture and/or normals
                for corner in tokens[2:end]
                    indices = split(corner, '/')
                    if length(indices) == 3 # all 3 present, third is a normal
                        push!(normals, parse(Int, indices[3]))
                    end
                    if length(indices) >= 2 && indices[2] != ""
                        # if there are 2 or more and the second isn't blank, it's a texture
                        push!(uvs, parse(Int, indices[2]))
                    end
                    if length(indices) >= 1 # first value is the position
                        push!(points, parse(Int, indices[1]))
                    else # unless it has none, in which case it's not valid
                        error("in line $line_number: face vertex $corner could not be parsed")
                    end
                end
                # create the triangle and add it to the triangles array
                push!(m.triangles, OBJTriangle(points, uvs, normals))
            end
        end
    end
    return m
end

""" write_obj(obj_filename)
Write the given mesh in OBJ format to file obj_filename."""
function write_obj(obj_filename, mesh::OBJMesh)
    open(obj_filename, "w") do f
        # write all positions:
        for v in mesh.positions
            write(f, "v $(v[1]) $(v[2]) $(v[3])\n")
        end

        # write all texture coords:
        for v in mesh.uvs
            write(f, "vt $(v[1]) $(v[2])\n")
        end
        # write all normals:
        for v in mesh.normals
            write(f, "vn $(v[1]) $(v[2]) $(v[3])\n")
        end

        # write all triangles:
        for tri in mesh.triangles
            write(f, "f $(tri_vertex_str(tri))\n")
        end

    end

end

""" tri_vertex_str(triangle)
Return a string with the indices of applicable positions, texture coordinates,
and normals for a given triangle according to the OBJ specification.
In particular, if p, u, and n are position, vertex and normal, each corner
of the triangle is represented as one of the following:
    p       (position only)
    p/u     (position and texture)
    p//n    (position and normal)
    p/u/n   (position, texture, and normal)
"""
function tri_vertex_str(triangle::OBJTriangle)
    # determine whether textures and normals are present:
    write_uv = length(triangle.uvs) == length(triangle.positions)
    write_normals = length(triangle.normals) == length(triangle.positions)
    corners = []
    for i = 1:3
        output = "$(triangle.positions[i])"
        if write_uv && !write_normals
            output = output * "/$(triangle.uvs[i])" # * does concatenation(!)
        elseif !write_uv && write_normals
            output = output * "//$(triangle.normals[i])"
        elseif write_uv && write_normals
            output = output * "/$(triangle.uvs[i])/$(triangle.normals[i])"
        end
        push!(corners, output)
    end
    join(corners, " ")
end

""" cube_mesh()
Return a new OBJMesh representing a 2x2x2 cube centered at the origin and
axis-aligned. """
function cube_mesh()
    positions = []
    uvs = []
    normals = []
    triangles = []
    # key to comments:
    # L/R = x = right/left
    # B/T = y = top/bottom
    # C/F = z = close/far
    push!(positions, Vec3( 1, -1, -1)) # 1 RBC
    push!(positions, Vec3( 1, -1,  1)) # 2 RBF
    push!(positions, Vec3(-1, -1,  1)) # 3 LBF
    push!(positions, Vec3(-1, -1, -1)) # 4 LBC
    push!(positions, Vec3( 1,  1, -1)) # 5 RTC
    push!(positions, Vec3( 1,  1,  1)) # 6 RTF
    push!(positions, Vec3(-1,  1,  1)) # 7 LTF
    push!(positions, Vec3(-1,  1, -1)) # 8 LTC

    # texture coordinates:
    push!(uvs, Vec2(1, 1)) # TR
    push!(uvs, Vec2(0, 1)) # TL
    push!(uvs, Vec2(0, 0)) # BL
    push!(uvs, Vec2(1, 0)) # BR

    #by removing pre-made normals from our objects, we're able to rotate them and calculate new normals (gets rid of black surfaces!)

    # normals:
    #push!(normals, Vec3( 1, 0, 0)) # R
    #push!(normals, Vec3(-1, 0, 0)) # L
    #push!(normals, Vec3( 0, 1, 0)) # U
    #push!(normals, Vec3( 0,-1, 0)) # D
    #push!(normals, Vec3( 0, 0, 1)) # C
    #push!(normals, Vec3( 0, 0,-1)) # F

    # 8 faces, 2 triangles each
    push!(triangles, OBJTriangle([1,2,3], [1,2,3], [])) # bottom face 1
    push!(triangles, OBJTriangle([1,3,4], [1,3,4], [])) # bottom face 2
    push!(triangles, OBJTriangle([1,5,6], [4,1,2], [])) # right face 1
    push!(triangles, OBJTriangle([1,6,2], [4,2,3], [])) # right face 2
    push!(triangles, OBJTriangle([2,6,7], [4,1,2], [])) # far face 1
    push!(triangles, OBJTriangle([2,7,3], [4,2,3], [])) # far face 2
    push!(triangles, OBJTriangle([3,7,8], [2,3,4], [])) # left face 1
    push!(triangles, OBJTriangle([3,8,4], [2,4,1], [])) # left face 2
    push!(triangles, OBJTriangle([4,8,5], [2,3,4], [])) # far face 1
    push!(triangles, OBJTriangle([4,5,1], [2,4,1], [])) # far face 2
    push!(triangles, OBJTriangle([5,8,7], [1,2,3], [])) # top face 1
    push!(triangles, OBJTriangle([5,7,6], [1,3,4], [])) # top face 2

    # julia automatically returns the last value in the function:
    OBJMesh(positions, uvs, normals, triangles)

end

function portal_mesh(x, y)

    positions = []
    uvs = []
    normals = []
    triangles = []
    push!(positions, Vec3(0,0,1)) # top of cap
    #push!(normals, Vec3(0,0,1))
    n = 20
    for i = 1:n+1
        ratio = i/n
        theta = 2*pi*ratio
        push!(positions, Vec3(-sin(theta)*x, -cos(theta)*y, 1))
        #push!(normals, Vec3(-sin(theta),  -cos(theta), 1))
    end
	length_pos = length(positions)
     for i=1:length_pos-1
       push!(triangles, OBJTriangle([i, 1,i+1],[i+length_pos,1,i+length_pos],[]))
     end
     OBJMesh(positions, uvs, normals, triangles)

end

#a copy of the portal mesh function that (hopefully) will be recognized as not-a-portal
function portal_background_mesh(x, y)
    positions = []
    uvs = []
    normals = []
    triangles = []
    push!(positions, Vec3(0,0,1)) # top of cap
    n = 20
    for i = 1:n+1
        ratio = i/n
        theta = 2*pi*ratio
        push!(positions, Vec3(-sin(theta)*x, -cos(theta)*y, 1))
    end
	length_pos = length(positions)
     for i=1:length_pos-1
       push!(triangles, OBJTriangle([i, 1,i+1],[i+length_pos,1,i+length_pos],[]))
     end
     OBJMesh(positions, uvs, normals, triangles)
end

""" cylinder_mesh(n)
Return a new OBJMesh object approximation of a cylinder with radius 1 and
height 2, centered at the origin. The logitudinal axis is aligned with y, and
it is tesselated with n divisions arranged radially around the outer surface.
The ends of the cylinder are disc-shaped caps parallel to the xz plane.
"""
function cylinder_mesh(divisionsU)
  positions = []
  uvs = []
  normals = []
  triangles = []

  n = divisionsU # change to n for ease of typing
  push!(positions, Vec3(0,1,0)) # top of cap
  push!(normals, Vec3(0,1,0))

  push!(positions, Vec3(0,-1,0)) # bottom of cap
  push!(normals, Vec3(0,-1,0))

  push!(positions, Vec3(0,1,-1)) # start of shell
  push!(positions, Vec3(0,-1,-1))
  push!(normals, Vec3(0,0,-1))

  push!(uvs, Vec2(.75, .75)) # initial uv coordinates
  push!(uvs, Vec2(.25, .75))
  push!(uvs, Vec2(0, .5))
  push!(uvs, Vec2(0, 0))
  # positions
  for i = 1:n-1
    ratio = i/n
    theta = 2*pi*ratio
    push!(positions, Vec3(-sin(theta), 1, -cos(theta)))
    push!(positions, Vec3(-sin(theta), -1, -cos(theta)))
    push!(normals, Vec3(-sin(theta), 0, -cos(theta)))
    push!(uvs, Vec2(ratio, .5)) # uvs for shell
    push!(uvs, Vec2(ratio, 0))
  end

  length_pos = length(positions) # for later use
  length_norm = length(normals)

  push!(uvs, Vec2(1, .5)) # missing coordinates
  push!(uvs, Vec2(1, 0))
  push!(uvs, Vec2(.75, 1))
  push!(uvs, Vec2(.25, .5))

  # uvs for caps
  for i=5:length_pos
      x = positions[i][1] # -sin(θ)
      y = positions[i][2] # y ∈ (-1,1)
      z = positions[i][3] # -cos(θ)
    if y == 1.0 # top cap
      push!(uvs, Vec2((x+1)/4+.5, (1-z)/4+.5))
    else # bottom cap
      push!(uvs, Vec2((x+1)/4, (z+1)/4+.5))
    end
  end

  n1 = 3 # normal counters
  n2 = 3

  # create the shell ignoring the top and bottom coordinates
  for i =3:2:length_pos-3
    # create triangles in a ccw order, texture coords are same index
    push!(triangles, OBJTriangle([i,i+1, i+2],[i,i+1,i+2],
                                 [n1,n1,n1+1]))
    push!(triangles, OBJTriangle([i+1,i+3, i+2],[i+1,i+3,i+2]
                                 ,[n2, n2+1, n2+1]))
    n1 += 1 # normals increment half as fast as the shell
    n2 += 1
  end

  # last bit of the shell
  push!(triangles, OBJTriangle([length_pos-1,length_pos,3],[length_pos-1,length_pos,length_pos+1],
                               [length_norm, length_norm, 3]))
  push!(triangles, OBJTriangle([length_pos,4,3],[length_pos, length_pos+2, length_pos+1],
                               [length_norm,3 ,3 ]))

  # create the caps for the cylinder
  for i=3:2:length_pos-2
    push!(triangles, OBJTriangle([i, i+2, 1],[i+length_pos,i+length_pos+2,1],[1,1,1]))
    push!(triangles, OBJTriangle([i+1, 2, i+3],[i+length_pos+1,2,i+length_pos+3],[2,2,2]))
  end

  # last bit of the caps
  push!(triangles, OBJTriangle([length_pos-1, 3, 1], [2*length_pos-1,length_pos+3,1], [1,1,1]))
  push!(triangles, OBJTriangle([length_pos, 2, 4], [2*length_pos,2,length_pos+4], [2,2,2]))

  OBJMesh(positions,uvs ,normals,triangles)
end

""" sphere_mesh(n, m
Create a Latitude-Longitude-tesselated approximation of a sphere with radius 1
centered at the origin. There are n divisions around the equator and m
divisions from pole to pole along each line of longitude. The North pole is at
(0,1,0), the South pole at (0,-1,0)((-1, and points on the Greenwich meridian are
in the x = 0 plane with z > 0. The u texture coordinate depends on longitude,
with u=0 at 180 degrees West and u=1 at 180 degrees East. The v texture
coordinate varies with latitude with v=0 at the South pole and v=1 at the North
pole. Normals should be normal to the ideal sphere surface. See the assignment
for a diagram and further details. """
function sphere_mesh(n, m)
    positions = []
    uvs = []
    normals = []
    triangles = []

    u = 2 * pi / n # normalize
    v = pi / m # normalize

    push!(positions, Vec3(0, -1, 0)) # bottom pole
    push!(normals, Vec3(0,-1,0))

    push!(positions, Vec3(0, 1, 0)) # top pole
    push!(normals, Vec3(0,1,0))

    # In this next block we convert x,y,z to spherical coordinates and
    # add the vectors to the positions array
    for i=1:m-1
      theta = (pi / 2) - (i * v) # π/2 to -π/2
      y = sin(theta) # Spherical y coord
      for j=0:n-1
        phi = j * u
        x = cos(theta) * sin(phi) # Spherical x coord
        z = cos(theta) * cos(phi) # spherical y coord
        push!(positions, Vec3(-x, -y, -z)) # negative as we start from the bottom
        push!(normals, Vec3(-x,-y,-z))
      end
    end
    length_pos = length(positions)
    # uv coords
    for i=0:m
      for j=0:n
        push!(uvs, Vec2(j/n,i/m))
      end
    end

    # create the bottom "ring"
    for i=1:n-1
      push!(triangles, OBJTriangle([1, i+3,i+2],[i,i+n+1,i+n],[1,i+3,i+2]))
    end

    # missing piece of bottom ring
    push!(triangles, OBJTriangle([1,3,n+2],[n, 2*n+1,2*n],[1,3,n+2]))

    t1, t2 = n+1,2*n+2 # texture counters
    for i=3:length_pos-n
      # if i is a multiple of m we subtract m from the first and second coordinates, respectively.
      if ((i-2)%n) == 0
       push!(triangles,OBJTriangle([i,i-n+1,i+n],[t1,t1+1,t2],[i,i-n+1,i+n]))
       push!(triangles,OBJTriangle([i-n+1,i+1,i+n],[t1+1,t2+1,t2],[i-n+1,i+1,i+n]))
       t1 +=2 # textures increment twice as fast
       t2 += 2
       continue
      end

     # for most of the triangles, i is not a multiple of m, add m to 3rd coord.
     push!(triangles, OBJTriangle([i, i+1,i+n],[t1,t1+1,t2],[i, i+1,i+n]))
     push!(triangles, OBJTriangle([i+1,i+1+n,i+n],[t1+1,t2+1,t2],[i+1,i+1+n,i+n]))
     t1 += 1 # textures increment normally here
     t2 += 1
    end

    # top "ring"
    for i =length_pos-n+1:length_pos-1
      push!(triangles,OBJTriangle([i,i+1,2],[t1,t1+1,t1+1+n],[i,i+1,2]))
      t1 += 1
      t2 += 1
    end
    # missing bit of the top "ring"
    push!(triangles,OBJTriangle([length_pos,length_pos-n+1,2],[t1,t1+1,t1+1+n],
                                [length_pos, length_pos-n+1,2]))

   deleteat!(uvs, 1) # delete the first/last index of texture coords as it is unneeded
   deleteat!(uvs, length(uvs))
   OBJMesh(positions,uvs, normals, triangles)
end

"""
    estimate_normals(mesh::OBJMesh)
Estimates normals for the given mesh. Overwrites any existing normals and returns a new OBJMesh object.
"""
function estimate_normals(mesh::OBJMesh)
  normals = []
  # cerate a normal for every position
  for  x in mesh.positions
    push!(normals, Vec3(0,0,0))
  end
  # average all the normals for every triangle
  for x in mesh.triangles
    a = mesh.positions[x.positions[1]]
    b = mesh.positions[x.positions[2]]
    c = mesh.positions[x.positions[3]]
    # definition of computing a normal
    cr = cross(b-a, c-a)
    normals[x.positions[1]] += cr/norm(cr)
    normals[x.positions[2]] += cr/norm(cr)
    normals[x.positions[3]] += cr/norm(cr)
  end

  tmp = [] # create another array to normalize the normals
  for i in normals
    i = i/norm(i) # normalize
    push!(tmp, i)
  end

  normals = tmp # for the return value
  for x in mesh.triangles
    x.normals = x.positions # normals have same index as positions per the reference OBJs
  end
  OBJMesh(mesh.positions, mesh.uvs, normals, mesh.triangles)
end

end # module OBJMeshes

ext: jl


text:
import IntervalWavelets: trapezquad
using Base.Test

# Test trapezquad
x = dyadic_rationals( DaubSupport(-1,1), 3 )
@test trapezquad( x, ones(x) ) == 2
@test trapezquad( x, x ) == 0


text:
"""
$(TYPEDEF)

1D physical space with structured mesh

# Fields

$(FIELDS)
"""
struct PSpace1D{TR<:Real,TI<:Integer,TA<:AA} <: AbstractPhysicalSpace1D
    x0::TR
    x1::TR
    nx::TI
    x::TA
    dx::TA
end

function PSpace1D(X0::TR, X1::TR, NX::TI, NG = 0::Integer) where {TR,TI}
    TX = ifelse(TR == Float32, Float32, Float64)

    δ = (X1 - X0) / NX
    x = begin
        if NG > 0
            OffsetArray{TX}(undef, 1-NG:NX+NG)
        else
            Array{TX}(undef, NX)
        end
    end
    dx = similar(x)

    # uniform mesh
    for i in eachindex(x)
        x[i] = X0 + (i - 0.5) * δ
        dx[i] = δ
    end

    return PSpace1D{TR,TI,typeof(x)}(X0, X1, NX, x, dx)
end

PSpace1D() = PSpace1D(0, 1, 100)
PSpace1D(X0::T, X1::T) where {T} = PSpace1D(X0, X1, 100)

"""
$(TYPEDEF)

2D physical space with structured mesh

# Fields

$(FIELDS)
"""
struct PSpace2D{TR<:Real,TI<:Integer,TA<:AM{<:Real},TB<:AA{<:Real,4},TC,TD} <:
       AbstractPhysicalSpace2D
    x0::TR
    x1::TR
    nx::TI
    y0::TR
    y1::TR
    ny::TI
    x::TA
    y::TA
    dx::TA
    dy::TA
    vertices::TB
    areas::TC
    n::TD
end

function PSpace2D(
    X0::TR,
    X1::TR,
    NX::TI,
    Y0::TR,
    Y1::TR,
    NY::TI,
    NGX = 0::Integer,
    NGY = 0::Integer,
) where {TR,TI}
    TX = ifelse(TR == Float32, Float32, Float64)

    δx = (X1 - X0) / NX
    δy = (Y1 - Y0) / NY
    x = begin
        if NGX > 0 || NGY > 0
            OffsetArray{TX}(undef, 1-NGX:NX+NGX, 1-NGY:NY+NGY)
        else
            Array{TX}(undef, NX, NY)
        end
    end
    y = similar(x)
    dx = similar(x)
    dy = similar(x)
    for j in axes(x, 2)
        for i in axes(x, 1)
            x[i, j] = X0 + (i - 0.5) * δx
            y[i, j] = Y0 + (j - 0.5) * δy
            dx[i, j] = δx
            dy[i, j] = δy
        end
    end

    vertices = similar(x, axes(x)..., 4, 2)
    for j in axes(vertices, 2), i in axes(vertices, 1)
        vertices[i, j, 1, 1] = x[i, j] - 0.5 * dx[i, j]
        vertices[i, j, 2, 1] = x[i, j] + 0.5 * dx[i, j]
        vertices[i, j, 3, 1] = x[i, j] + 0.5 * dx[i, j]
        vertices[i, j, 4, 1] = x[i, j] - 0.5 * dx[i, j]

        vertices[i, j, 1, 2] = y[i, j] - 0.5 * dy[i, j]
        vertices[i, j, 2, 2] = y[i, j] - 0.5 * dy[i, j]
        vertices[i, j, 3, 2] = y[i, j] + 0.5 * dy[i, j]
        vertices[i, j, 4, 2] = y[i, j] + 0.5 * dy[i, j]
    end

    areas = [0.0 for i in axes(x, 1), j in axes(x, 2), k = 1:4]
    for j in axes(x, 2), i in axes(x, 1)
        areas[i, j, 1] = point_distance(vertices[i, j, 1, :], vertices[i, j, 2, :])
        areas[i, j, 2] = point_distance(vertices[i, j, 2, :], vertices[i, j, 3, :])
        areas[i, j, 3] = point_distance(vertices[i, j, 3, :], vertices[i, j, 4, :])
        areas[i, j, 4] = point_distance(vertices[i, j, 4, :], vertices[i, j, 1, :])
    end

    n = [zeros(2) for i in axes(x, 1), j in axes(x, 2), k = 1:4]
    for j in axes(x, 2), i in axes(x, 1)
        n1 = unit_normal(vertices[i, j, 1, :], vertices[i, j, 2, :])
        n1 .= ifelse(
            dot(
                n1,
                [x[i, j], y[i, j]] .- (vertices[i, j, 1, :] .+ vertices[i, j, 2, :]) ./ 2,
            ) <= 0,
            n1,
            -n1,
        )

        n2 = unit_normal(vertices[i, j, 2, :], vertices[i, j, 3, :])
        n2 .= ifelse(
            dot(
                n2,
                [x[i, j], y[i, j]] .- (vertices[i, j, 2, :] .+ vertices[i, j, 3, :]) ./ 2,
            ) <= 0,
            n2,
            -n2,
        )

        n3 = unit_normal(vertices[i, j, 3, :], vertices[i, j, 4, :])
        n3 .= ifelse(
            dot(
                n3,
                [x[i, j], y[i, j]] .- (vertices[i, j, 3, :] .+ vertices[i, j, 4, :]) ./ 2,
            ) <= 0,
            n3,
            -n3,
        )

        n4 = unit_normal(vertices[i, j, 4, :], vertices[i, j, 1, :])
        n4 .= ifelse(
            dot(
                n4,
                [x[i, j], y[i, j]] .- (vertices[i, j, 4, :] .+ vertices[i, j, 1, :]) ./ 2,
            ) <= 0,
            n4,
            -n4,
        )

        n[i, j, 1] .= n1
        n[i, j, 2] .= n2
        n[i, j, 3] .= n3
        n[i, j, 4] .= n4
    end

    return PSpace2D{TR,TI,typeof(x),typeof(vertices),typeof(areas),typeof(n)}(
        X0,
        X1,
        NX,
        Y0,
        Y1,
        NY,
        x,
        y,
        dx,
        dy,
        vertices,
        areas,
        n,
    )
end

PSpace2D() = PSpace2D(0, 1, 45, 0, 1, 45)
PSpace2D(X0::T, X1::T, Y0::T, Y1::T) where {T} = PSpace2D(X0, X1, 45, Y0, Y1, 45)

"""
$(TYPEDEF)

2D Circular space in polar coordinates

# Fields

$(FIELDS)
"""
struct CSpace2D{TR<:Real,TI<:Integer,TA<:AM{<:Real},TB<:AA{<:Real,4},TC,TD} <:
       AbstractPhysicalSpace2D
    r0::TR
    r1::TR
    nr::TI
    θ0::TR
    θ1::TR
    nθ::TI
    r::TA
    θ::TA
    x::TA
    y::TA
    dr::TA
    dθ::TA
    darc::TA
    vertices::TB
    areas::TC
    n::TD
end

function CSpace2D(
    R0::TR,
    R1::TR,
    NR::TI,
    θ0,
    θ1,
    Nθ::TI,
    NGR = 0::TI,
    NGθ = 0::TI,
) where {TR,TI<:Integer}
    TX = ifelse(TR == Float32, Float32, Float64)

    δr = (R1 - R0) / NR
    δθ = (θ1 - θ0) / Nθ
    r = begin
        if NGR > 0 || NGθ > 0
            OffsetArray{TX}(undef, 1-NGR:NR+NGR, 1-NGθ:Nθ+NGθ)
        else
            Array{TX}(undef, NR, Nθ)
        end
    end
    θ = similar(r)
    x = similar(r)
    y = similar(r)
    dr = similar(r)
    dθ = similar(r)
    darc = similar(r)

    for j in axes(r, 2)
        for i in axes(r, 1)
            r[i, j] = R0 + (i - 0.5) * δr
            θ[i, j] = θ0 + (j - 0.5) * δθ
            x[i, j] = r[i, j] * cos(θ[i, j])
            y[i, j] = r[i, j] * sin(θ[i, j])
            dr[i, j] = δr
            dθ[i, j] = δθ
            darc[i, j] = r[i, j] * dθ[i, j]
        end
    end

    vertices = similar(r, axes(r)..., 4, 2)
    for j in axes(vertices, 2), i in axes(vertices, 1)
        vertices[i, j, 1, 1] = (r[i, j] - 0.5 * dr[i, j]) * cos(θ[i, j] - 0.5 * dθ[i, j])
        vertices[i, j, 2, 1] = (r[i, j] + 0.5 * dr[i, j]) * cos(θ[i, j] - 0.5 * dθ[i, j])
        vertices[i, j, 3, 1] = (r[i, j] + 0.5 * dr[i, j]) * cos(θ[i, j] + 0.5 * dθ[i, j])
        vertices[i, j, 4, 1] = (r[i, j] - 0.5 * dr[i, j]) * cos(θ[i, j] + 0.5 * dθ[i, j])

        vertices[i, j, 1, 2] = (r[i, j] - 0.5 * dr[i, j]) * sin(θ[i, j] - 0.5 * dθ[i, j])
        vertices[i, j, 2, 2] = (r[i, j] + 0.5 * dr[i, j]) * sin(θ[i, j] - 0.5 * dθ[i, j])
        vertices[i, j, 3, 2] = (r[i, j] + 0.5 * dr[i, j]) * sin(θ[i, j] + 0.5 * dθ[i, j])
        vertices[i, j, 4, 2] = (r[i, j] - 0.5 * dr[i, j]) * sin(θ[i, j] + 0.5 * dθ[i, j])
    end

    areas = [0.0 for i in axes(x, 1), j in axes(x, 2), k = 1:4]
    for j in axes(x, 2), i in axes(x, 1)
        areas[i, j, 1] = point_distance(vertices[i, j, 1, :], vertices[i, j, 2, :])
        areas[i, j, 2] = point_distance(vertices[i, j, 2, :], vertices[i, j, 3, :])
        areas[i, j, 3] = point_distance(vertices[i, j, 3, :], vertices[i, j, 4, :])
        areas[i, j, 4] = point_distance(vertices[i, j, 4, :], vertices[i, j, 1, :])
    end

    n = [zeros(2) for i in axes(x, 1), j in axes(x, 2), k = 1:4]
    for j in axes(x, 2), i in axes(x, 1)
        n1 = unit_normal(vertices[i, j, 1, :], vertices[i, j, 2, :])
        n1 .= ifelse(
            dot(
                n1,
                [x[i, j], y[i, j]] .- (vertices[i, j, 1, :] .+ vertices[i, j, 2, :]) ./ 2,
            ) <= 0,
            n1,
            -n1,
        )

        n2 = unit_normal(vertices[i, j, 2, :], vertices[i, j, 3, :])
        n2 .= ifelse(
            dot(
                n2,
                [x[i, j], y[i, j]] .- (vertices[i, j, 2, :] .+ vertices[i, j, 3, :]) ./ 2,
            ) <= 0,
            n2,
            -n2,
        )

        n3 = unit_normal(vertices[i, j, 3, :], vertices[i, j, 4, :])
        n3 .= ifelse(
            dot(
                n3,
                [x[i, j], y[i, j]] .- (vertices[i, j, 3, :] .+ vertices[i, j, 4, :]) ./ 2,
            ) <= 0,
            n3,
            -n3,
        )

        n4 = unit_normal(vertices[i, j, 4, :], vertices[i, j, 1, :])
        n4 .= ifelse(
            dot(
                n4,
                [x[i, j], y[i, j]] .- (vertices[i, j, 4, :] .+ vertices[i, j, 1, :]) ./ 2,
            ) <= 0,
            n4,
            -n4,
        )

        n[i, j, 1] .= n1
        n[i, j, 2] .= n2
        n[i, j, 3] .= n3
        n[i, j, 4] .= n4
    end

    return CSpace2D{TR,TI,typeof(x),typeof(vertices),typeof(areas),typeof(n)}(
        R0,
        R1,
        NR,
        TX(θ0),
        TX(θ1),
        Nθ,
        r,
        θ,
        x,
        y,
        dr,
        dθ,
        darc,
        vertices,
        areas,
        n,
    )
end

"""
$(TYPEDEF)

3D physical space with structured mesh

  z
   |
   --- y
  /
x
     8 ----- 7
     /     /|
    /     / |
  5 ----- 6
   |     |  |
   |     | / 4
   |     |/
  2 ----- 3

# Fields

$(FIELDS)
"""
struct PSpace3D{TR<:Real,TI<:Integer,TA<:AA{<:Real,3},TB<:AA{<:Real,5},TC,TD} <:
       AbstractPhysicalSpace3D
    x0::TR
    x1::TR
    nx::TI
    y0::TR
    y1::TR
    ny::TI
    z0::TR
    z1::TR
    nz::TI
    x::TA
    y::TA
    z::TA
    dx::TA
    dy::TA
    dz::TA
    vertices::TB
    areas::TC
    n::TD
end

"""
$(SIGNATURES)

Generate uniform mesh
"""
function uniform_mesh(x0, xnum::Integer, dx)
    points = zeros(xnum)
    for i = 1:xnum
        points[i] = x0 + (i - 0.5) * dx
    end

    return points
end

"""
$(SIGNATURES)

Equivalent N-dimensional mesh generator as matlab
"""
ndgrid(v::AV) = copy(v)

"""
$(SIGNATURES)
"""
function ndgrid(v1::AV{T}, v2::AV{T}) where {T}
    m, n = length(v1), length(v2)
    v1 = reshape(v1, m, 1)
    v2 = reshape(v2, 1, n)

    return (repeat(v1, 1, n), repeat(v2, m, 1))
end

"""
$(SIGNATURES)
"""
function ndgrid(vs::AV{T}...) where {T}
    ndgrid_fill(a, v, s, snext) = begin
        for j = 1:length(a)
            a[j] = v[div(rem(j - 1, snext), s)+1]
        end
    end

    n = length(vs)
    sz = map(length, vs)
    out = ntuple(i -> Array{T}(undef, sz), n)
    s = 1
    for i = 1:n
        a = out[i]::Array
        v = vs[i]
        snext = s * size(a, i)
        ndgrid_fill(a, v, s, snext)
        s = snext
    end

    return out
end

"""
$(SIGNATURES)

Equivalent structured mesh generator as matlab...
"""
meshgrid(v::AV{T}) where {T} = meshgrid(v, v)

"""
$(SIGNATURES)
"""
function meshgrid(x::T, y::T) where {T<:AV{<:Real}}
    X = [i for j in y, i in x]
    Y = [j for j in y, i in x]

    return X, Y
end

"""
$(SIGNATURES)
"""
function meshgrid(x::T, y::T, z::T) where {T<:AV{<:Real}}
    X = [i for k in z, j in y, i in x]
    Y = [j for k in z, j in y, i in x]
    Z = [k for k in z, j in y, i in x]

    return X, Y, Z
end

"""
$(SIGNATURES)

Find the location index of a point in mesh
"""
function find_idx(x::AV{<:Real}, p::Real; mode = :nonuniform::Symbol)
    if mode == :uniform
        dx = x[2] - x[1]
        return Int(ceil((p - x[1] + 0.5 * dx) / dx)) # point location
    else
        return argmin(abs.(x .- p)) # center location
    end
end

# ------------------------------------------------------------
# Extended Base.show()
# ------------------------------------------------------------

function Base.show(io::IO, ps::PSpace1D{TR,TI,TA}) where {TR,TI,TA}
    print(
        io,
        "PhysicalSpace1D{$TR,$TI,$TA}\n",
        "domain: ($(ps.x0),$(ps.x1))\n",
        "resolution: $(ps.nx)\n",
        "ghost: $(1-firstindex(ps.x))\n",
    )
end

function Base.show(io::IO, ps::PSpace2D{TR,TI,TA}) where {TR,TI,TA}
    print(
        io,
        "PhysicalSpace2D{$TR,$TI,$TA}\n",
        "domain: ($(ps.x0),$(ps.x1)) × ($(ps.y0),$(ps.y1))\n",
        "resolution: $(ps.nx) × $(ps.ny)\n",
        "ghost in x: $(1-firstindex(ps.x[:, 1]))\n",
        "ghost in y: $(1-firstindex(ps.y[1, :]))\n",
    )
end

function Base.show(io::IO, ps::CSpace2D{TR,TI,TA}) where {TR,TI,TA}
    print(
        io,
        "CircularSpace2D{$TR,$TI,$TA}\n",
        "domain: ($(ps.r0),$(ps.r1)) × ($(ps.θ0),$(ps.θ1))\n",
        "resolution: $(ps.nr) × $(ps.nθ)\n",
        "ghost in r: $(1-firstindex(ps.x[:, 1]))\n",
        "ghost in θ: $(1-firstindex(ps.y[1, :]))\n",
    )
end


text:
#module HFUtilsTest

using  Test, Revise
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\utils\\CUDAGpuUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\distanceMetrics\\Housdorff\\mainHouseDorffKernel\\HFUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\test\\GPUtestUtils.jl")
using ..HFUtils
using ..CUDAGpuUtils,Cthulhu,BenchmarkTools , CUDA, StaticArrays

using ..HFUtils
using ..CUDAGpuUtils,Cthulhu,BenchmarkTools , CUDA, StaticArrays

@testset "clearMainShmem" begin

    testArr = CUDA.zeros(Bool,34,34,34);
    function testKernForClearShmem(testArrInn)
        resShmem =  @cuStaticSharedMem(Bool,(34,34,34))#+2 in order to get the one padding
        resShmem[threadIdxX()+1,threadIdxY()+1,1]= 1
        testArrInn[threadIdxX()+1,threadIdxY()+1,1]= 1
        resShmem[threadIdxX()+1,threadIdxY()+1,2]= 1
        testArrInn[threadIdxX()+1,threadIdxY()+1,2]= 1

         HFUtils.clearMainShmem( resShmem)
         HFUtils.clearMainShmem( testArrInn)
        return
    end
    @cuda threads=(32,32) blocks=1 testKernForClearShmem(testArr)
    @test  sum(testArr)==1024
    CUDA.reclaim()# just to destroy from gpu our dummy data

end # clearMainShmem

@testset "clearLocArr" begin

    testArr = CUDA.zeros(Bool,32);
    function testKernForClearArrA(testArrInn)
        if(threadIdxX()==UInt32(1) &&threadIdxY()==UInt32(1) )
        locArr= zeros(MVector{32,Bool})
        locArr[1]=1
        locArr[2]=1
        testArrInn[1] = locArr[1]
        testArrInn[2] = locArr[2]
        end
        return
    end

    @cuda threads=(32,32) blocks=1 testKernForClearArrA(testArr)
    @test  sum(testArr)==2

    testArr = CUDA.zeros(Bool,32);

    function testKernForClearArrB(testArrInn)
        @ifXY 1 1 begin
        locArr= zeros(MVector{32,Bool})
        locArr[1]=1
        locArr[2]=1
        clearLocArr(locArr)
        testArrInn[1] = locArr[1]
        testArrInn[2] = locArr[2]
        end
        return
    end

    @cuda threads=(32,32) blocks=1 testKernForClearArrB(testArr)
    @test  sum(testArr)==0

    CUDA.reclaim()# just to destroy from gpu our dummy data

end # clearLocArr

@testset "clearPadding" begin

    testArr = CUDA.zeros(Bool,34,34,34);

    function testclearPadding(testArrInn)
        resShmem =  @cuStaticSharedMem(Bool,(34,34,34))#+2 in order to get the one padding

        for z in 1:34
            resShmem[threadIdxX(),threadIdxY(),z ]=1
            resShmem[threadIdxX()+2,threadIdxY()+2,z ]=1
            resShmem[threadIdxX()+2,threadIdxY(),z ]=1
            resShmem[threadIdxX(),threadIdxY()+2,z ]=1
        end
        fillGlobalFromShmem(testArrInn,resShmem)

        # clearPadding(resShmem)

        # for z in 1:34
        #     testArrInn[threadIdxX(),threadIdxY(),z ]=resShmem[threadIdxX(),threadIdxY(),z ]
        #     testArrInn[threadIdxX()+2,threadIdxY()+2,z ]=  resShmem[threadIdxX()+2,threadIdxY()+2,z ]
        #     testArrInn[threadIdxX(),threadIdxY()+2,z ]=  resShmem[threadIdxX(),threadIdxY()+2,z ]
        #     testArrInn[threadIdxX()+2,threadIdxY(),z ]=  resShmem[threadIdxX()+2,threadIdxY(),z ]
        # end

        return
    end

    @cuda threads=(32,32) blocks=1 testclearPadding(testArr)
    @test (length(testArr)-(sum(testArr)+(4*34)+(8*32)  )) ==0

    testArrB = CUDA.zeros(Bool,34,34,34);

    function testclearPadding(testArrInn)
        resShmem =  @cuStaticSharedMem(Bool,(34,34,34))#+2 in order to get the one padding

        for z in 1:34
            resShmem[threadIdxX(),threadIdxY(),z ]=1
            resShmem[threadIdxX()+2,threadIdxY()+2,z ]=1
            resShmem[threadIdxX()+2,threadIdxY(),z ]=1
            resShmem[threadIdxX(),threadIdxY()+2,z ]=1
        end
sync_threads()
        clearPadding(resShmem)
        clearMainShmem(resShmem)
        sync_threads()

        fillGlobalFromShmem(testArrInn,resShmem)

        return
    end
    @cuda threads=(32,32) blocks=1 testclearPadding(testArrB)
    @test sum(testArrB) ==0

end#clearPadding

##############

# macro addArguments(x, ex)
#     return esc(:(if threadIdxX()==$x
#         $ex
#     end))

# end

# macro times3(ex)
#     return _times3(ex)
# end

# function _times3(ex)
#     # if ex.head == :call && ex.args[1] == :+
#     #     ex.args[1] = :*
#     # end
#     push!(ex.args,3)

#     return ex
# end

# function addd(aa,bb)
#     return aa+bb
# end

# a = 2; b = 3

# @times3 addd(a)

# function outer(bb)
#     aa=5
#     addd(bb)
# end

# using MacroTools

# ex = quote
#     struct someStr
#       x::Int
#       y
#     end
#   end

#   prettify(ex)

#   @capture(ex, struct T_ fields__ end)
#   T, fields

#   exB = quote
#     xi::Int = 2
#     xiB::Int = 2
#     xiC::Int = 2
#     xiD::Int = 2
#   end

#   MacroTools.prewalk(x -> @show(x) isa Symbol ? x : x, exB);

#   @capture(exB, Symbol__ )


text:
using SimString
using Faker
using BenchmarkTools
using DataStructures

################################# Benchmark Bulk addition #####################
db = DictDB(CharacterNGrams(3, " "));
Faker.seed(2020)
@time fake_names = [string(Faker.first_name(), " ", Faker.last_name()) for i in 1:100_000];

f(d, x) = append!(d, x)
@time f(db, fake_names)

################################ Simple Addition ###############################

db = DictDB(CharacterNGrams(2, " "));
push!(db, "foo");
push!(db, "bar");
push!(db, "fooo");

f(x, c, s, a, r) = search(x, c, s; α=a, ranked=r)
test = "foo";
col = db;
sim = Cosine();
a = 0.8;
r = true;

f(Cosine(),  db, "foo", 0.8, true)

@btime f($sim,  $col, $test, $a, $r)
@btime search(Cosine(), db, "foo"; α=0.8, ranked=true)

db2 = DictDB(CharacterNGrams(3, " "));
append!(db2, ["foo", "bar", "fooo", "foor"]) # also works via multiple dispatch on a vector

results = search(Cosine(), db, "foo"; α=0.8, ranked=true)  # yet to be implemented

bs = ["foo", "bar", "foo", "foo", "bar"]
SimString.extract_features(CharacterNGrams(3, " "), "prepress")
SimString.extract_features(WordNGrams(2, " ", " "), "You are a really really really cool dude.")

db = DictDB(WordNGrams(2, " ", " "))
push!(db, "You are a really really really cool dude.")


text:
#vector of vectors

Using CairoMakie

pointvectors = [Point2f.(1:100, cumsum(randn(100))) for i in 1:4]

series(pointvectors, markersize=5, color=:Set1)

max_stars_repo_name: Ellon-M/visualizations

max_issues_repo_name: Ellon-M/visualizations

max_forks_repo_name: Ellon-M/visualizations


text:
module Scanivalve

using Sockets
using AbstractDAQs

export AbstractDAQ, DSA3217
export close, numchans, listscan, listzero, listgain, listoffset, listdelta
export scan!, stopscan, isreading, samplesread, scanconfig, daqparam
export clearbuffer!
export daqread, daqstart, daqacquire, daqconfig, daqconfigdev
export daqzero, daqstop, daqaddinput, numchannels, daqchannels
export DAQConfig, iparameters, fparameters, sparameters, daqdevname
export daqdevip, daqdevmodel, daqdevserialnum, daqdevtag

abstract type AbstractScanivalve <: AbstractPressureScanner end

include("dsa3200packets.jl")
include("dsa3217.jl")

end


text:
### A Pluto.jl notebook ###
# v0.16.0

using Markdown
using InteractiveUtils

# ╔═╡ 5312be7e-edd8-11ea-34b0-7581fc4b7126
begin
	using Pkg
	cd(joinpath(dirname(@__FILE__),".."))
    Pkg.activate(pwd())
    using Distributions, LinearAlgebra, Plots, InteractiveUtils
	using PlutoUI
end

# ╔═╡ a813912a-edb3-11ea-3b13-23da723cb488
md"""
# Cellular Automaton - Langton's Loops
## General description
Langton's loops are a particular "species" of artificial life in a 2D cellular automaton created in 1984 by Christopher Langton. They consist of a loop of cells containing genetic information, which flows continuously around the loop and out along an "arm" (or pseudopod), which will become the daughter loop. The "genes" instruct it to make three left turns, completing the loop, which then disconnects from its parent.

A single cell has 8 possible states (0,1,...,7). Each of these values can be interpreted a shown below:

|state|role|color|description|
|-----|----|-----|-----------|
| 0 | background | black | empty state |
| 1 | core | blue | fill tube & 'conduct' |
| 2 | sheat | red | boundary container of the gene in the loop |
| 3 | - | green | support left turning; bonding two arms; generating new off-shoot; cap off-shoot |
| 4 | - | yellow | control left-turning & finishing a sprouted loop |
| 5 | - | pink| Disconnect parent from offspring |
| 6 | - | white | Point to where new sprout should start; guide sprout; finish sprout growth |
| 7 | - | cyan| Hold info. on straight growth of arm & offspring |

All cells update synchronously to a new set in function of their own present state and their symmetrical [von Neumann neighborhood](https://en.wikipedia.org/wiki/Von_Neumann_neighborhood) (using a rule table cf. rules.txt).

The rule is applied symmetrically, meaning that e.g. 4 neighbours in the states 3-2-1-0 is the same as 0-3-2-1 (and all other rotations thereof).

## Starting configuration
The initial configuration is shown in `./img/Langtonstart.png`. The numerically, this matches the array below. This array is also stored in `./data/Langtonstart.txt`.
```
022222222000000
217014014200000
202222220200000
272000021200000
212000021200000
202000021200000
272000021200000
212222221222220
207107107111112
022222222222220
```

$(PlutoUI.LocalResource("./Exercises/img/Langtonstart.png"))

## Rules
A rule e.g. '123456' is interpreted in the following way: 'Current-Top-Right-Bottom-Left-Next' i.e.  a cell currently in state 1 with neighbours 2,3,4 & 5 (and all possible rotations thereof) will become state 6 in the next iteration. The different rules can be found in `Langtonsrules.txt`.

## Problem solution
We split the problem in a series of subproblems:
* transforming the rule list (.txt) to someting usable (function)
* creating the standard layout
* identifying the neighbours of a cel
* visualising the result

___

The colors shown in the initial position image can created by using the following colormap:
```Julia
using Plots
mycmap = ColorGradient([RGBA(0/255,0/255,0/255),
    RGBA(0/255,0/255,255/255),
    RGBA(255/255,0/255,0/255),
    RGBA(0/255,255/255,0/255),
    RGBA(255/255,255/255,0/255),
    RGBA(255/255,0/255,255/255),
    RGBA(255/255,255/255,255/255),
    RGBA(0/255,255/255,255/255)]);

plt = heatmap(yflip=true,color=mycmap,size=(600,600), title="Langton loop")
```

An animated result is available in `./Exercises/img/Langton.gif`.

$(PlutoUI.LocalResource("./Exercises/img/Langton.gif"))
"""

# ╔═╡ b6e7f9a2-50eb-45e4-8a1a-3eefd591dc6a
md"""## FR 08 Sep"""

# ╔═╡ 8e6ae30a-e6e3-4d8b-b096-3db23ac01060
begin
	function regles(p::String)
		d = Dict()
		for rule in readlines(p)
			etat_actuel = parse(Int,rule[1])
			voisins = parse.(Int,split(rule[2:end-1],""))
			etat_futur = parse(Int,rule[end])

			println("etat_actuel: $(etat_actuel), voisins: $(voisins), next: $(etat_futur)")

			get!(d, etat_actuel, Dict())
			for v in [circshift(voisins,i) for i in 0:3]
				d[etat_actuel][v] = etat_futur
			end
		end

		d
	end

	function monde(p::String)
		m = zeros(Int,50,50)
		start = hcat(map(x->parse.(Int,x),split.(readlines(p),""))...)'
		m[20:20+size(start,1)-1, 20:20+size(start,2)-1] = start
		return m
	end

	function transition(etat::Int, voisins::Vector{Int}, regles::Dict)
		return regles[etat][voisins]
	end

	function mesvoisins(monde::Array, i::Int, j::Int)
		return [monde[i-1,j]; monde[i,j+1]; monde[i+1,j]; monde[i,j-1]]
	end

	function iteration!(monde_t::Array, monde_tnext::Array, regles::Dict)
		n,m = size(monde_t)
		for i = 2:n-1
			for j = 2:m-1
				monde_tnext[i,j] = transition(monde_t[i,j], mesvoisins(monde_t,i,j), regles)
			end
		end

	end
end

# ╔═╡ 2a14448f-e761-4b7a-b0c1-4808e1d17d95
R = regles("./Exercises/data/Langtonsrules.txt");

# ╔═╡ 548b40b4-4a5d-4a00-93d6-5bd5aebb1db7
M = monde("./Exercises/data/Langtonstart.txt");

# ╔═╡ 09c14ddd-27a0-4de5-8b8e-d187714e2290
transition(0,[2; 5; 2; 1], R)

# ╔═╡ fd6b0ccb-d0bf-4731-8520-362242ef2321
heatmap(M, yflip=true,size=(400,400), title="Langton départ")

# ╔═╡ 441f27a4-5819-4abf-9e6f-c5fb6a888e3d
begin
	mutable struct MonLangton
		monde::Array{Int64, 2}
		mondefutur::Array{Int64, 2}
		regles::Dict
		function MonLangton(fichiermonde::String, fichierregles::String)
			m = monde(fichiermonde);
			lesregles =  regles(fichierregles);
			return new(m, deepcopy(m), lesregles)
		end
	end

	Base.show(io::IO, L::MonLangton) = print(io, "Structure Langton (taille du monde: $(size(L.monde)))")

	function evolue!(L::MonLangton)
		iteration!(L.monde, L.mondefutur, L.regles)
		L.monde .= L.mondefutur
	end

	function visualise(L::MonLangton)
		heatmap(L.monde, yflip=true,size=(400,400), title="Langton")
	end

	LL = MonLangton("./Exercises/data/Langtonstart.txt", "./Exercises/data/Langtonsrules.txt")
end

# ╔═╡ f93794a7-feaf-454f-8c11-7b51818fcb52
begin
	evolue!(LL)
	visualise(LL)
end

# ╔═╡ f63722e3-561b-4125-a629-fc9eb0cdc7de
md"""## NL 08 Sep"""

# ╔═╡ ff900625-8fa9-4481-888d-adf467a7383b
md"""
* 2 matrices voor 'wereld'
* enkel gedeelte van de wereld bekijken (?)
* randeffect!
* permutatie regels
"""

# ╔═╡ 79a80023-2252-409e-a42d-4279254dd0f1
begin
	"""
		regellezer(p)

	lees textbestande van regels en maak een Dict
	"""
	function regellezer(p::String)
		d = Dict()

		for line in readlines(p)
			raw = split(line,"")
			current_state = parse(Int,raw[1])
			neighbors = parse.(Int,raw[2:end-1])
			future_state = parse(Int,raw[end])

			println("current_state= $(current_state), neighbors= $(neighbors), future_state=$(future_state)")
			get!(d, current_state, Dict())
			for neigh in [circshift(neighbors,i) for i in 0:3]
				d[current_state][neigh] = future_state
			end
		end

		return d
	end

	function langtonstart(p::String)
		start = hcat(map(x->parse.(Int,x),split.(readlines(p),""))...)'
		world = zeros(Int, 50,50)
		# place start in world
		world[20:20+size(start,1)-1, 20:20+size(start,2)-1] = start
		return world
	end

	function future(current_state::Int, neighbors::Vector{Int}, rules::Dict)
		return rules[current_state][neighbors]
	end

	function get_neigh(w::Array, i::Int, j::Int)
		return [w[i-1,j]; w[i,j+1]; w[i+1,j]; w[i, j-1]]
	end

	function single_iteration!(current_world::Array, future_world::Array, rules::Dict)
		n,m = size(current_world) # dimensions
		for i = 2:n-1
			for j = 2:m-1
				future_world[i,j] = future(current_world[i,j], get_neigh(current_world,i,j), rules)
			end
		end
	end

end

# ╔═╡ a7e38b3d-0f77-4d7a-850b-05a215dc9154
rules = regellezer("./Exercises/data/Langtonsrules.txt");

# ╔═╡ d1dc3a77-582d-4ebe-a885-fc9fb881108d
future(1, [7;0;0;2], rules)

# ╔═╡ a73898cf-93f3-4056-9e25-ba88edc15551
w = langtonstart("./Exercises/data/langtonstart.txt");

# ╔═╡ 4ef5b3be-c3d4-472f-b8e8-a28478fa17ba
get_neigh(w,20,24)

# ╔═╡ 51ee2ea1-31dd-4ab1-a60b-f7f6c0883556
single_iteration!(w,deepcopy(w),rules)

# ╔═╡ d0c186c3-2ea7-4de3-b18d-d74319aac6e8
heatmap(w,yflip=true,size=(400,400), title="Langton loop")

# ╔═╡ 4e8d36ee-3434-4689-831e-36ce58637d74
begin
	mutable struct MyLangton
		worldnow::Array{Int64,2}
		worldfuture::Array{Int64,2}
		rules:: Dict
		function MyLangton(worldpath::String, rulepath::String)
			world = langtonstart(worldpath);
			rules = regellezer(rulepath);
			return new(world, deepcopy(world), rules)
		end
	end

	Base.show(io::IO, L::MyLangton) = print(io, "MyLangton object (world size: $(size(L.worldnow))")

	function evolve!(L::MyLangton)
		single_iteration!(L.worldnow, L.worldfuture, L.rules)
		# dit is nog niet af!
		L.worldnow .= L.worldfuture
		return
	end

	function illustrate(L::MyLangton)
		heatmap(L.worldnow,yflip=true,size=(400,400), title="Langton loop")
	end
	Lang = MyLangton("./Exercises/data/langtonstart.txt","./Exercises/data/Langtonsrules.txt")
end

# ╔═╡ feee03a9-dc8f-4654-a152-4b5607edffa6
Lang

# ╔═╡ 1542e2e6-97e7-4c8b-aea7-5c514f82015d
begin
	evolve!(Lang)
	illustrate(Lang)
end

# ╔═╡ Cell order:
# ╠═5312be7e-edd8-11ea-34b0-7581fc4b7126
# ╟─a813912a-edb3-11ea-3b13-23da723cb488
# ╟─b6e7f9a2-50eb-45e4-8a1a-3eefd591dc6a
# ╠═8e6ae30a-e6e3-4d8b-b096-3db23ac01060
# ╠═2a14448f-e761-4b7a-b0c1-4808e1d17d95
# ╠═548b40b4-4a5d-4a00-93d6-5bd5aebb1db7
# ╠═09c14ddd-27a0-4de5-8b8e-d187714e2290
# ╠═fd6b0ccb-d0bf-4731-8520-362242ef2321
# ╠═441f27a4-5819-4abf-9e6f-c5fb6a888e3d
# ╠═f93794a7-feaf-454f-8c11-7b51818fcb52
# ╟─f63722e3-561b-4125-a629-fc9eb0cdc7de
# ╟─ff900625-8fa9-4481-888d-adf467a7383b
# ╠═79a80023-2252-409e-a42d-4279254dd0f1
# ╠═a7e38b3d-0f77-4d7a-850b-05a215dc9154
# ╠═d1dc3a77-582d-4ebe-a885-fc9fb881108d
# ╠═4ef5b3be-c3d4-472f-b8e8-a28478fa17ba
# ╠═a73898cf-93f3-4056-9e25-ba88edc15551
# ╠═51ee2ea1-31dd-4ab1-a60b-f7f6c0883556
# ╠═d0c186c3-2ea7-4de3-b18d-d74319aac6e8
# ╠═4e8d36ee-3434-4689-831e-36ce58637d74
# ╠═feee03a9-dc8f-4654-a152-4b5607edffa6
# ╠═1542e2e6-97e7-4c8b-aea7-5c514f82015d

max_stars_repo_path: Exercises/PS02 - Cellular Automaton.jl

max_issues_repo_path: Exercises/PS02 - Cellular Automaton.jl

max_forks_repo_path: Exercises/PS02 - Cellular Automaton.jl


text:
const _currency_data = Dict(
    :KYD => (Currency{:KYD}, 2,  136, "Cayman Islands Dollar"),
    :PYG => (Currency{:PYG}, 0,  600, "Guarani"),
    :AWG => (Currency{:AWG}, 2,  533, "Aruban Florin"),
    :LAK => (Currency{:LAK}, 2,  418, "Lao Kip"),
    :TRY => (Currency{:TRY}, 2,  949, "Turkish Lira"),
    :UYU => (Currency{:UYU}, 2,  858, "Peso Uruguayo"),
    :NGN => (Currency{:NGN}, 2,  566, "Naira"),
    :QAR => (Currency{:QAR}, 2,  634, "Qatari Rial"),
    :EUR => (Currency{:EUR}, 2,  978, "Euro"),
    :TTD => (Currency{:TTD}, 2,  780, "Trinidad and Tobago Dollar"),
    :UAH => (Currency{:UAH}, 2,  980, "Hryvnia"),
    :XPF => (Currency{:XPF}, 0,  953, "CFP Franc"),
    :JPY => (Currency{:JPY}, 0,  392, "Yen"),
    :THB => (Currency{:THB}, 2,  764, "Baht"),
    :BHD => (Currency{:BHD}, 3,   48, "Bahraini Dinar"),
    :PEN => (Currency{:PEN}, 2,  604, "Sol"),
    :SOS => (Currency{:SOS}, 2,  706, "Somali Shilling"),
    :TJS => (Currency{:TJS}, 2,  972, "Somoni"),
    :SHP => (Currency{:SHP}, 2,  654, "Saint Helena Pound"),
    :AOA => (Currency{:AOA}, 2,  973, "Kwanza"),
    :SGD => (Currency{:SGD}, 2,  702, "Singapore Dollar"),
    :CHF => (Currency{:CHF}, 2,  756, "Swiss Franc"),
    :IDR => (Currency{:IDR}, 2,  360, "Rupiah"),
    :SDG => (Currency{:SDG}, 2,  938, "Sudanese Pound"),
    :IQD => (Currency{:IQD}, 3,  368, "Iraqi Dinar"),
    :EGP => (Currency{:EGP}, 2,  818, "Egyptian Pound"),
    :SLL => (Currency{:SLL}, 2,  694, "Leone"),
    :UZS => (Currency{:UZS}, 2,  860, "Uzbekistan Sum"),
    :MAD => (Currency{:MAD}, 2,  504, "Moroccan Dirham"),
    :MYR => (Currency{:MYR}, 2,  458, "Malaysian Ringgit"),
    :MMK => (Currency{:MMK}, 2,  104, "Kyat"),
    :DZD => (Currency{:DZD}, 2,   12, "Algerian Dinar"),
    :CAD => (Currency{:CAD}, 2,  124, "Canadian Dollar"),
    :HKD => (Currency{:HKD}, 2,  344, "Hong Kong Dollar"),
    :MWK => (Currency{:MWK}, 2,  454, "Malawi Kwacha"),
    :INR => (Currency{:INR}, 2,  356, "Indian Rupee"),
    :TZS => (Currency{:TZS}, 2,  834, "Tanzanian Shilling"),
    :BTN => (Currency{:BTN}, 2,   64, "Ngultrum"),
    :LRD => (Currency{:LRD}, 2,  430, "Liberian Dollar"),
    :DKK => (Currency{:DKK}, 2,  208, "Danish Krone"),
    :NAD => (Currency{:NAD}, 2,  516, "Namibia Dollar"),
    :PKR => (Currency{:PKR}, 2,  586, "Pakistan Rupee"),
    :JMD => (Currency{:JMD}, 2,  388, "Jamaican Dollar"),
    :XOF => (Currency{:XOF}, 0,  952, "CFA Franc BCEAO"),
    :COP => (Currency{:COP}, 2,  170, "Colombian Peso"),
    :HNL => (Currency{:HNL}, 2,  340, "Lempira"),
    :KWD => (Currency{:KWD}, 3,  414, "Kuwaiti Dinar"),
    :GHS => (Currency{:GHS}, 2,  936, "Ghana Cedi"),
    :BAM => (Currency{:BAM}, 2,  977, "Convertible Mark"),
    :CUC => (Currency{:CUC}, 2,  931, "Peso Convertible"),
    :MXN => (Currency{:MXN}, 2,  484, "Mexican Peso"),
    :TND => (Currency{:TND}, 3,  788, "Tunisian Dinar"),
    :MZN => (Currency{:MZN}, 2,  943, "Mozambique Metical"),
    :BOB => (Currency{:BOB}, 2,   68, "Boliviano"),
    :DOP => (Currency{:DOP}, 2,  214, "Dominican Peso"),
    :ZMW => (Currency{:ZMW}, 2,  967, "Zambian Kwacha"),
    :MVR => (Currency{:MVR}, 2,  462, "Rufiyaa"),
    :BSD => (Currency{:BSD}, 2,   44, "Bahamian Dollar"),
    :KPW => (Currency{:KPW}, 2,  408, "North Korean Won"),
    :HUF => (Currency{:HUF}, 2,  348, "Forint"),
    :BIF => (Currency{:BIF}, 0,  108, "Burundi Franc"),
    :GNF => (Currency{:GNF}, 0,  324, "Guinean Franc"),
    :PAB => (Currency{:PAB}, 2,  590, "Balboa"),
    :VEF => (Currency{:VEF}, 2,  937, "Bolívar"),
    :BZD => (Currency{:BZD}, 2,   84, "Belize Dollar"),
    :HTG => (Currency{:HTG}, 2,  332, "Gourde"),
    :SAR => (Currency{:SAR}, 2,  682, "Saudi Riyal"),
    :GYD => (Currency{:GYD}, 2,  328, "Guyana Dollar"),
    :GMD => (Currency{:GMD}, 2,  270, "Dalasi"),
    :ALL => (Currency{:ALL}, 2,    8, "Lek"),
    :ZAR => (Currency{:ZAR}, 2,  710, "Rand"),
    :PHP => (Currency{:PHP}, 2,  608, "Philippine Piso"),
    :MRO => (Currency{:MRO}, 2,  478, "Ouguiya"),
    :AZN => (Currency{:AZN}, 2,  944, "Azerbaijan Manat"),
    :AED => (Currency{:AED}, 2,  784, "UAE Dirham"),
    :ETB => (Currency{:ETB}, 2,  230, "Ethiopian Birr"),
    :AUD => (Currency{:AUD}, 2,   36, "Australian Dollar"),
    :HRK => (Currency{:HRK}, 2,  191, "Kuna"),
    :VND => (Currency{:VND}, 0,  704, "Dong"),
    :MDL => (Currency{:MDL}, 2,  498, "Moldovan Leu"),
    :GTQ => (Currency{:GTQ}, 2,  320, "Quetzal"),
    :GEL => (Currency{:GEL}, 2,  981, "Lari"),
    :CZK => (Currency{:CZK}, 2,  203, "Czech Koruna"),
    :MOP => (Currency{:MOP}, 2,  446, "Pataca"),
    :PLN => (Currency{:PLN}, 2,  985, "Zloty"),
    :SSP => (Currency{:SSP}, 2,  728, "South Sudanese Pound"),
    :AFN => (Currency{:AFN}, 2,  971, "Afghani"),
    :SYP => (Currency{:SYP}, 2,  760, "Syrian Pound"),
    :STD => (Currency{:STD}, 2,  678, "Dobra"),
    :USD => (Currency{:USD}, 2,  840, "US Dollar"),
    :BND => (Currency{:BND}, 2,   96, "Brunei Dollar"),
    :CVE => (Currency{:CVE}, 2,  132, "Cabo Verde Escudo"),
    :ARS => (Currency{:ARS}, 2,   32, "Argentine Peso"),
    :CDF => (Currency{:CDF}, 2,  976, "Congolese Franc"),
    :PGK => (Currency{:PGK}, 2,  598, "Kina"),
    :RUB => (Currency{:RUB}, 2,  643, "Russian Ruble"),
    :MNT => (Currency{:MNT}, 2,  496, "Tugrik"),
    :RWF => (Currency{:RWF}, 0,  646, "Rwanda Franc"),
    :SZL => (Currency{:SZL}, 2,  748, "Lilangeni"),
    :GBP => (Currency{:GBP}, 2,  826, "Pound Sterling"),
    :WST => (Currency{:WST}, 2,  882, "Tala"),
    :BYN => (Currency{:BYN}, 2,  933, "Belarusian Ruble"),
    :BGN => (Currency{:BGN}, 2,  975, "Bulgarian Lev"),
    :JOD => (Currency{:JOD}, 3,  400, "Jordanian Dinar"),
    :YER => (Currency{:YER}, 2,  886, "Yemeni Rial"),
    :ZWL => (Currency{:ZWL}, 2,  932, "Zimbabwe Dollar"),
    :LYD => (Currency{:LYD}, 3,  434, "Libyan Dinar"),
    :TMT => (Currency{:TMT}, 2,  934, "Turkmenistan New Manat"),
    :FJD => (Currency{:FJD}, 2,  242, "Fiji Dollar"),
    :KHR => (Currency{:KHR}, 2,  116, "Riel"),
    :BMD => (Currency{:BMD}, 2,   60, "Bermudian Dollar"),
    :KZT => (Currency{:KZT}, 2,  398, "Tenge"),
    :XCD => (Currency{:XCD}, 2,  951, "East Caribbean Dollar"),
    :NPR => (Currency{:NPR}, 2,  524, "Nepalese Rupee"),
    :OMR => (Currency{:OMR}, 3,  512, "Rial Omani"),
    :CLP => (Currency{:CLP}, 0,  152, "Chilean Peso"),
    :ERN => (Currency{:ERN}, 2,  232, "Nakfa"),
    :TOP => (Currency{:TOP}, 2,  776, "Pa’anga"),
    :CUP => (Currency{:CUP}, 2,  192, "Cuban Peso"),
    :LBP => (Currency{:LBP}, 2,  422, "Lebanese Pound"),
    :SCR => (Currency{:SCR}, 2,  690, "Seychelles Rupee"),
    :NOK => (Currency{:NOK}, 2,  578, "Norwegian Krone"),
    :RON => (Currency{:RON}, 2,  946, "Romanian Leu"),
    :MUR => (Currency{:MUR}, 2,  480, "Mauritius Rupee"),
    :BDT => (Currency{:BDT}, 2,   50, "Taka"),
    :MGA => (Currency{:MGA}, 2,  969, "Malagasy Ariary"),
    :SBD => (Currency{:SBD}, 2,   90, "Solomon Islands Dollar"),
    :ISK => (Currency{:ISK}, 0,  352, "Iceland Krona"),
    :GIP => (Currency{:GIP}, 2,  292, "Gibraltar Pound"),
    :KES => (Currency{:KES}, 2,  404, "Kenyan Shilling"),
    :BWP => (Currency{:BWP}, 2,   72, "Pula"),
    :BBD => (Currency{:BBD}, 2,   52, "Barbados Dollar"),
    :KMF => (Currency{:KMF}, 0,  174, "Comorian Franc"),
    :XAF => (Currency{:XAF}, 0,  950, "CFA Franc BEAC"),
    :AMD => (Currency{:AMD}, 2,   51, "Armenian Dram"),
    :CNY => (Currency{:CNY}, 2,  156, "Yuan Renminbi"),
    :KRW => (Currency{:KRW}, 0,  410, "Won"),
    :SEK => (Currency{:SEK}, 2,  752, "Swedish Krona"),
    :BRL => (Currency{:BRL}, 2,  986, "Brazilian Real"),
    :CRC => (Currency{:CRC}, 2,  188, "Costa Rican Colon"),
    :SVC => (Currency{:SVC}, 2,  222, "El Salvador Colon"),
    :IRR => (Currency{:IRR}, 2,  364, "Iranian Rial"),
    :NZD => (Currency{:NZD}, 2,  554, "New Zealand Dollar"),
    :SRD => (Currency{:SRD}, 2,  968, "Surinam Dollar"),
    :NIO => (Currency{:NIO}, 2,  558, "Cordoba Oro"),
    :MKD => (Currency{:MKD}, 2,  807, "Denar"),
    :KGS => (Currency{:KGS}, 2,  417, "Som"),
    :DJF => (Currency{:DJF}, 0,  262, "Djibouti Franc"),
    :UGX => (Currency{:UGX}, 0,  800, "Uganda Shilling"),
    :VUV => (Currency{:VUV}, 0,  548, "Vatu"),
    :LKR => (Currency{:LKR}, 2,  144, "Sri Lanka Rupee"),
    :RSD => (Currency{:RSD}, 2,  941, "Serbian Dinar"),
    :ILS => (Currency{:ILS}, 2,  376, "New Israeli Sheqel"),
    :ANG => (Currency{:ANG}, 2,  532, "Netherlands Antillean Guilder"),
    :LSL => (Currency{:LSL}, 2,  426, "Loti"),
)

max_stars_repo_name: TotalVerb/Currencies

max_issues_repo_name: TotalVerb/Currencies

max_forks_repo_name: TotalVerb/Currencies


text:
################################################################################
#
#	ABSTRACT TYPE
#
#   Bond{L,N}
#   --> L is the label type
#   --> N is the dimension of the Bravais lattice that the bond is located in
#
#   FILE CONTAINS
#       - abstract type definition
#       - interface definition
#       - interface testing
#
################################################################################

################################################################################
#
#   ABSTRACT TYPE DEFINITION
#
################################################################################
abstract type AbstractBond{L,N} end

# export the type
export AbstractBond

# DOCSTRING
"""
    abstract type AbstractBond{L,N}

The abstract bond type that describes all bond implementations. It is parametric in two types,
- `L` the type of the bond label
- `N` an integer number specifying the number of periodic directions, it can wrap around

The abstract type is equipped with interface functions which are listed in the following. These interface functions have to be implemented by concrete subtypes / structs for the concrete type to be usable in all pre-implemented functions.
- `newBond` to create a new bond object of a passed concrete bond type
- `from` and `from!` to access the index of the bond origin site
- `to` and `to!` to access the index of the bond destination site
- `label` and `label!` to access the bond label
- `wrap` and `wrap!` to access the bond wrap, i.e. in which copy of the unitcell / lattice is points to

The interface functions throw adequate errors when called but not implemented for concrete types.
See specific documentation for the individual functions.

Furthermore, there is a function to test if the bond is periodic (i.e. wrapping around in any direction),
called `isPeriodic`, which does not have to be implemented for every concrete bond type.

# Examples

```julia-REPL
julia> AbstractBond{Int64,2} <: AbstractBond{L,N} where{L,N}
true
```

"""
AbstractBond

################################################################################
#
#	INTERFACING / ACCESSING BONDS
#	(functions have to be overwritten by concrete types)
#
################################################################################

# default constructor interface
# used for creation of new bonds
"""
    function newBond(
            :: Type{B},
            from    :: Integer,
            to      :: Integer,
            label   :: L,
            wrap    :: NTuple{N,<:Integer}
        ) :: B where {L,N,B<:AbstractBond{L,N}}

Interface function for creation of new `AbstractBond` object of a passed type `B` from passed site
indices `from` and `to` as well as passed label `label` of type `L` and a passed `wrap` as a `NTuple`.
Returns a new bond object of type `B`.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1))
...
```
"""
function newBond(
            :: Type{B},
            from    :: Integer,
            to      :: Integer,
            label   :: L,
            wrap    :: NTuple{N,<:Integer}
        ) :: B where {L,N,B<:AbstractBond{L,N}}

    # print an error because implementation for concrete type is missing
    error(  "not implemented function 'newBond' for concrete bond type " *
            string(B) * " with label type " * string(L) *
            " and wrap length " * string(N)   )
end

# export the from newBond interface
export newBond

# UIDs of sites between which the bond is located

# get from index / UID (Int64)
"""
    function from(
            b :: AbstractBond{L,N}
        ) :: Int64 where {L,N}

Interface function for obtaining the origin site index of a passed `AbstractBond` object `b`.
Returns an Int64 specifying the site index of the bonds origin site.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> from(b)
12
```
"""
function from(
            b :: AbstractBond{L,N}
        ) :: Int64 where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'from' for bond type " * string(typeof(b)))
end
# set from index / UID (Int64)
"""
    function from!(
            b :: AbstractBond{L,N},
            i :: Integer
        ) where {L,N}

Interface function for setting the origin site index of a passed `AbstractBond` object `b`
to a new value `i`. Returns nothing.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> from!(b, 14)

julia> from(b)
14
```
"""
function from!(
            b :: AbstractBond{L,N},
            i :: Integer
        ) where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'from!' for bond type " * string(typeof(b)))
end

# export the from index interface
export from, from!

# get to index / UID (Int64)
"""
    function to(
            b :: AbstractBond{L,N}
        ) :: Int64 where {L,N}

Interface function for obtaining the destination site index of a passed `AbstractBond` object `b`.
Returns an Int64 specifying the site index of the bonds destination site.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> to(b)
42
```
"""
function to(
            b :: AbstractBond{L,N}
        ) :: Int64 where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'to' for bond type " * string(typeof(b)))
end
# set to index / UID (Int64)
"""
    function to!(
            b :: AbstractBond{L,N},
            i :: Integer
        ) where {L,N}

Interface function for setting the destination site index of a passed `AbstractBond` object `b`
to a passed value `i`. Returns nothing.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> to!(b, 1)

julia> to(b)
1
```
"""
function to!(
            b :: AbstractBond{L,N},
            i :: Integer
        ) where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'to!' for bond type " * string(typeof(b)))
end

# export the to index interface
export to, to!

# get label
"""
    function label(
            b :: AbstractBond{L,N}
        ) :: L where {L,N}

Interface function for obtaining the label of a passed `AbstractBond` object `b`.
Returns an object of type `L`.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> label(b)
:x
```
"""
function label(
            b :: AbstractBond{L,N}
        ) :: L where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'label' for bond type " * string(typeof(b)))
end

# set label
"""
    function label!(
            b :: AbstractBond{L,N},
            l :: L
        ) where {L,N}

Interface function for setting the label of a passed `AbstractBond` object `b`
to a passed value `l` of label type `L`. Returns nothing.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> label!(b, :y)

julia> label(b)
:y
```
"""
function label!(
            b :: AbstractBond{L,N},
            l :: L
        ) where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'label!' for bond type " * string(typeof(b)))
end

# export the label interface
export label, label!

# get wrap
"""
    function wrap(
            b :: AbstractBond{L,N}
        ) :: NTuple{N,Int64} where {L,N}

Interface function for obtaining the wrap of a passed `AbstractBond` object `b`.
Returns an object of type `NTuple{N,Int64}` containing the information in which
copy of the unitcell or lattice the bond is pointing.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> wrap(b)
(0, 1)
```
"""
function wrap(
            b :: AbstractBond{L,N}
        ) :: NTuple{N,Int64} where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'wrap' for bond type " * string(typeof(b)))
end
# set wrap
"""
    function wrap!(
            b :: AbstractBond{L,N},
            w :: NTuple{N, <:Integer}
        ) where {L,N}

Interface function for setting the wrap of a passed `AbstractBond` object `b`
to a passed value `w` of label type `NTuple{N, <:Integer}`. Returns nothing.

This function has to be overwritten by a concrete type,
otherwise it will throw an error when used in further functions.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> wrap!(b, (0,0))

julia> wrap(b)
(0, 0)
```
"""
function wrap!(
            b :: AbstractBond{L,N},
            w :: NTuple{N, <:Integer}
        ) where {L,N}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'wrap!' for bond type " * string(typeof(b)))
end

# export the wrap interface
export wrap, wrap!

function saveBonds(
        bs :: Vector{B},
        fn :: AbstractString,
        group :: AbstractString = "bonds"
        ;
        append :: Bool = false
    ) where {LB,N,B<:AbstractBond{LB,N}}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'saveBonds' for bond type " * string(B))
end

function loadBonds(
        ::Type{B},
        fn :: AbstractString,
        group :: AbstractString = "bonds"
    ) where {LB,N,B<:AbstractBond{LB,N}}

    # print an error because implementation for concrete type is missing
    error("not implemented interface function 'loadBonds' for bond type " * string(B))
end

export saveBonds, loadBonds

# SIMILAR FUNCTION (can be overwritten but does not have to be overwritten)

# without new parameters
function similar(
            b :: B
        ) :: B where {L,N,B<:AbstractBond{L,N}}

    # return a new bond object
    return newBond(
        B,
        deepcopy(from(b)), deepcopy(to(b)),
        deepcopy(label(b)),
        deepcopy(wrap(b))
    )
end
# with new parameters
function similar(
            b       :: B,
            from    :: Integer,
            to      :: Integer,
            label   :: L,
            wrap    :: NTuple{N,<:Integer}
        ) :: B where {L,N,B<:AbstractBond{L,N}}

    # create a new bond object
    b_new = similar(b)
    # set parameters
    from!(b_new, from)
    to!(b_new, to)
    label!(b_new, label)
    wrap!(b_new, wrap)
    # return the new object
    return b_new
end

# export the similar interface
export similar

# ADDITIONAL FANCY SYNTAX
# does not have to be overwritten as it uses interface from above

# check if the bond is periodic
"""
    function isPeriodic(
            b :: AbstractBond{L,N}
        ) :: Bool where {L,N}

Function for determining if a passed `AbstractBond` object `b` is periodic
in any lattice / unitcell direction. Internally, the wrap is checked for non-zero elements
which indicate pointing into a different copy of the surrounding unitcell / lattice.
Returns a `Bool` indicating if there are non-zero wrap elements.

This function does not have to be overwritten by a concrete type.

# Examples

```julia-REPL
julia> b = newBond(Bond{Symbol,2}, 12, 42, :x, (0,1));

julia> isPeriodic(b)
true
```
"""
function isPeriodic(
            b :: AbstractBond{L,N}
        ) :: Bool where {L,N}

    # check all wraps explicitly
    for w in wrap(b)
        if w!=0
            # it is periodic if there is a single non-zero wrap
            return true
        end
    end
    # it is not periodic if there is no non-zero wrap
    return false
end

# export the isPeriodic interface
export isPeriodic

################################################################################
#
#	TESTING THE INTERFACE OF BONDS
#	(should never be overwritten by concrete types)
#
################################################################################

# TESTING THE BOND INTERFACE
function testInterface(
            ::Type{T}
        ) :: Bool where {T<:AbstractBond}

	# get the parameterless constructor
	B = Base.typename(T).wrapper

    # iterate over some standard wraps
    for w in [(1,), (0,0), (0,0,0), (1,2,0,0)]
    # iterate over some standard labels
    for l in ["t", 1, 1.0]
        # create a new bond
        bond = newBond(B{typeof(l), length(w)}, 1, 1, l, w)
        # test the interface getters
		from(bond)
		to(bond)
		label(bond)
		wrap(bond)
        # test the interface setters
		from!(bond, 1)
		to!(bond, 1)
		label!(bond, l)
		wrap!(bond, w)
    end
    end

	# return true to indicate the test passed
	return true
end

# export the test functions
export testInterface


text:
function build_xsd_rules(ast::ASTNode, rulenameprefix="")
    assign_rulenames(ast, rulenameprefix)
    rules = Vector{RuleSource}()
    build_xsd_lightxml_specific(rules)
    build_xsd_rule(ast, rules)
    rules
end

function build_xsd_rule(node::ASTNode, rules::Vector{RuleSource})

  if node.func in [:xsd]
    build_xsd_xsd(node, rules)
  elseif node.func in [:element]
    build_xsd_element(node, rules)
  elseif node.func in [:attribute]
    build_xsd_attribute(node, rules)
  elseif node.func in [:group, :attributeGroup, :complexType, :sequence, :extension]
    build_xsd_sequence(node, rules)
  elseif node.func in [:simpleType, :simpleContent, :complexContent, :restriction]
    build_xsd_call(node, rules)
  elseif node.func in [:pattern]
  build_xsd_pattern(node, rules)
  elseif node.func in [:choice, :substitutionGroup, :union]
    build_xsd_choice(node, rules)
  elseif node.func in [:quantifier]
    build_xsd_quantifier(node, rules)
  elseif node.func in [:optional]
    build_xsd_optional(node, rules)
  elseif node.func in [:enumeration]
    build_xsd_enumeration(node, rules)
  elseif node.func in [:list]
    build_xsd_list(node, rules)
  elseif node.func in [:any, :anyAttribute, :nothing]
    build_xsd_nothing(node, rules)
  elseif node.func in [:elementRef, :elementSub, :typeRef, :attributeRef, :groupRef, :attributeGroupRef]
    # do nothing
    @assert isempty(node.children)
  else
    error("Unexpected xml node function $(node.func)")
  end

  for child in node.children
    build_xsd_rule(child, rules)
  end

end

# # isolates code that is specific to the XML package used to build the XML
function build_xsd_lightxml_specific(rules::Vector{RuleSource})
  rule = RuleSource(:construct_element, [:name, :content,])
	# push!(rule.source, "construct_element(name::AbstractString, content::Array{Any}) = begin")
	# TODO for the moment, remove typing on arguments to rule since this breaks under Julia 4.0
	push!(rule.source, "begin")
	push!(rule.source, "  xmlelement = new_element(name)")
	push!(rule.source, "  for item in content")
	push!(rule.source, "    if typeof(item) <: Tuple{AbstractString,Array}")
	# 	push!(rule.source, "    if typeof(item) <: Main.XMLElement")			# LightXML specific # TODO remove explicit Main. when GT support this properly
	# 	push!(rule.source, "      add_child(xmlelement, item)")			# LightXML specific
	push!(rule.source, "      add_child(xmlelement, construct_element(item[1], item[2]))")			# LightXML specific
	push!(rule.source, "    elseif typeof(item) <: Tuple{AbstractString,AbstractString}")
	push!(rule.source, "      set_attribute(xmlelement, item[1], item[2])")			# LightXML specific
	push!(rule.source, "    elseif typeof(item) <: AbstractString")
	push!(rule.source, "      add_text(xmlelement, item)")			# LightXML specific
	push!(rule.source, "    else")
	push!(rule.source, "      @assert false")
	push!(rule.source, "    end")
	push!(rule.source, "  end")
	push!(rule.source, "  xmlelement")
	push!(rule.source, "end")
  push!(rules, rule)
end

function build_xsd_xsd(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  @assert length(node.refs)==1
  methodname = build_called_rulename(node)
  push!(rule.source, "  name, content = $(methodname)()")
  push!(rule.source, "  construct_element(name, content)")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_element(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  build_xsd_sequence_body(node, rule)
	push!(rule.source, "  (\"$(escape_string(node.args[:name]))\", content)")
	# push!(rule.source, "  construct_element(\"$(escape_string(node.args[:name]))\", content)")
	# ... appear to need Main to be explicit when construct_element is defined outside of generator
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_attribute(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  @assert length(node.children)==1
  methodname = build_called_rulename(node.children[1])
  push!(rule.source, "  content = $(methodname)()")
  push!(rule.source, "  @assert typeof(content)<:AbstractString")
  push!(rule.source, "  (\"$(escape_string(node.args[:name]))\", content)")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_sequence(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  build_xsd_sequence_body(node, rule)
  push!(rule.source, "  content")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_call(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  @assert length(node.children)==1
  methodname = build_called_rulename(node.children[1])
  push!(rule.source, "  $(methodname)()")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_pattern(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  push!(rule.source, "  choose(String, \"$(escape_string(node.args[:value]))\")")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_choice(node::ASTNode, rules::Vector{RuleSource})
  if isempty(node.children)
	  rule = build_rule_start(node)
    push!(rule.source, "  (Any)[]")
	  build_rule_end(rule, node)
    push!(rules, rule)
  else
    for child in node.children
		  rule = build_rule_start(node)
	    methodname = build_called_rulename(child)
	    push!(rule.source, "  $(methodname)()")
		  build_rule_end(rule, node)
      push!(rules, rule)
		end
  end
end

function build_xsd_quantifier(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  @assert haskey(node.args, :lowerbound)
  @assert haskey(node.args, :upperbound)
  @assert length(node.children)==1
  lowerbound = node.args[:lowerbound]
  upperbound = node.args[:upperbound]
  methodname = build_called_rulename(node.children[1])
	if (upperbound == Inf) || (lowerbound < upperbound)
		if (upperbound == Inf)
			push!(rule.source, "  r = reps($(methodname)(), $(lowerbound))")
		else
			push!(rule.source, "  r = reps($(methodname)(), $(lowerbound), $(upperbound))")
		end
	else
		push!(rule.source, "  r = [$(methodname)() for i in 1:$(lowerbound)]")
	end
  push!(rule.source, "  reduce((v,e)->[v; e], Any[], r)") # note: this flattens arrays in childcontent in a way that convert would not do
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_optional(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  @assert length(node.children)==1
  methodname = build_called_rulename(node.children[1])
  push!(rule.source, "  choose(Bool) ? $(methodname)() : Any[]")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_enumeration(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  @assert haskey(node.args, :value)
  push!(rule.source, "  \"$(escape_string(node.args[:value]))\"")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_list(node::ASTNode)
  rule = build_rule_start(node)
  @assert length(node.children)==1
  methodname = build_called_rulename(node.children[1], rules::Vector{RuleSource})
  push!(rule.source, "  content = plus($(methodname)())")
  push!(rule.source, "  join(content, \" \")")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_nothing(node::ASTNode, rules::Vector{RuleSource})
  rule = build_rule_start(node)
  push!(rule.source, "  (Any)[]")
  build_rule_end(rule, node)
  push!(rules, rule)
end

function build_xsd_sequence_body(parentnode::ASTNode, rule::RuleSource)
  push!(rule.source, "  content = (Any)[]")
  for node in parentnode.children
    methodname = build_called_rulename(node)
    if node.func in [:elementSub]
      push!(rule.source, "  childelement = $(methodname)()")
      push!(rule.source, "  childcontent = childelement[2]")
    else
      push!(rule.source, "  childcontent = $(methodname)()")
    end
    push!(rule.source, "  content = [content; childcontent]")  # note: this flattens arrays in childcontent in a way that push! would not do
  end
end


text:
"""
    struct JSONModel <: MetabolicModel
        json::Dict{String,Any}
        rxn_index::Dict{String,Int}
        rxns::Vector{Any}
        met_index::Dict{String,Int}
        mets::Vector{Any}
        gene_index::Dict{String,Int}
        genes::Vector{Any}
    end

A struct used to store the contents of a JSON model, i.e. a model read from a
file ending with `.json`. These model files typically store all the model data
in arrays of JSON objects (represented in Julia as vectors of dictionaries).

Usually, not all of the fields of the input JSON can be easily represented when
converting to other models, care should be taken to avoid losing information.

Direct work with the `json` structure is not very efficient; the model
structure therefore caches some of the internal structure in the extra fields.
The single-parameter [`JSONModel`](@ref) constructor creates these caches
correctly from the `json`. The model structure is designed as read-only, and
changes in `json` invalidate the cache.

# Example
````
model = load_json_model("some_model.json")
model.json # see the actual underlying JSON
reactions(model) # see the list of reactions
````
"""
struct JSONModel <: MetabolicModel
    json::Dict{String,Any}
    rxn_index::Dict{String,Int}
    rxns::Vector{Any}
    met_index::Dict{String,Int}
    mets::Vector{Any}
    gene_index::Dict{String,Int}
    genes::Vector{Any}
end

_json_rxn_name(r, i) = string(get(r, "id", "rxn$i"))
_json_met_name(m, i) = string(get(m, "id", "met$i"))
_json_gene_name(g, i) = string(get(g, "id", "gene$i"))

JSONModel(json::Dict{String,Any}) = begin
    rkey = _guesskey(keys(json), _constants.keynames.rxns)
    isnothing(rkey) && throw(DomainError(keys(json), "JSON model has no reaction keys"))
    rs = json[rkey]

    mkey = _guesskey(keys(json), _constants.keynames.mets)
    ms = json[mkey]
    isnothing(mkey) && throw(DomainError(keys(json), "JSON model has no metabolite keys"))

    gkey = _guesskey(keys(json), _constants.keynames.genes)
    gs = isnothing(gkey) ? [] : json[gkey]

    JSONModel(
        json,
        Dict(_json_rxn_name(r, i) => i for (i, r) in enumerate(rs)),
        rs,
        Dict(_json_met_name(m, i) => i for (i, m) in enumerate(ms)),
        ms,
        Dict(_json_gene_name(g, i) => i for (i, g) in enumerate(gs)),
        gs,
    )
end

function _parse_annotations(x)::Annotations
    Annotations([k => if typeof(v) == String
        [v]
    else
        convert(Vector{String}, v)
    end for (k, v) in x])
end

_parse_notes(x)::Notes = _parse_annotations(x)

"""
    reactions(model::JSONModel)

Extract reaction names (stored as `.id`) from JSON model.
"""
reactions(model::JSONModel) = [_json_rxn_name(r, i) for (i, r) in enumerate(model.rxns)]

"""
    metabolites(model::JSONModel)

Extract metabolite names (stored as `.id`) from JSON model.
"""
metabolites(model::JSONModel) = [_json_met_name(m, i) for (i, m) in enumerate(model.mets)]

"""
    genes(model::JSONModel)

Extract gene names from a JSON model.
"""
genes(model::JSONModel) = [_json_gene_name(g, i) for (i, g) in enumerate(model.genes)]

"""
    stoichiometry(model::JSONModel)

Get the stoichiometry. Assuming the information is stored in reaction object
under key `.metabolites`.
"""
function stoichiometry(model::JSONModel)
    rxn_ids = reactions(model)
    met_ids = metabolites(model)

    n_entries = 0
    for r in model.rxns
        for _ in r["metabolites"]
            n_entries += 1
        end
    end

    MI = Vector{Int}()
    RI = Vector{Int}()
    SV = Vector{Float64}()
    sizehint!(MI, n_entries)
    sizehint!(RI, n_entries)
    sizehint!(SV, n_entries)

    for (i, rid) in enumerate(rxn_ids)
        r = model.rxns[model.rxn_index[rid]]
        for (mid, coeff) in r["metabolites"]
            haskey(model.met_index, mid) || throw(
                DomainError(
                    met_id,
                    "Unknown metabolite found in stoichiometry of $(rxn_ids[i])",
                ),
            )

            push!(MI, model.met_index[mid])
            push!(RI, i)
            push!(SV, coeff)
        end
    end
    return SparseArrays.sparse(MI, RI, SV, length(met_ids), length(rxn_ids))
end

"""
    bounds(model::JSONModel)

Get the bounds for reactions, assuming the information is stored in
`.lower_bound` and `.upper_bound`.
"""
bounds(model::JSONModel) = (
    [get(rxn, "lower_bound", -_constants.default_reaction_bound) for rxn in model.rxns],
    [get(rxn, "upper_bound", _constants.default_reaction_bound) for rxn in model.rxns],
)

"""
    objective(model::JSONModel)

Collect `.objective_coefficient` keys from model reactions.
"""
objective(model::JSONModel) =
    sparse([float(get(rxn, "objective_coefficient", 0.0)) for rxn in model.rxns])

"""
    reaction_gene_associaton(model::JSONModel, rid::String)

Parses the `.gene_reaction_rule` from reactions.
"""
reaction_gene_association(model::JSONModel, rid::String) = _maybemap(
    _parse_grr,
    get(model.rxns[model.rxn_index[rid]], "gene_reaction_rule", nothing),
)

"""
    reaction_subsystem(model::JSONModel, rid::String)

Parses the `.subsystem` out from reactions.
"""
reaction_subsystem(model::JSONModel, rid::String) =
    get(model.rxns[model.rxn_index[rid]], "subsystem", nothing)

"""
    metabolite_formula(model::JSONModel, mid::String)

Parse and return the metabolite `.formula`
"""
metabolite_formula(model::JSONModel, mid::String) =
    _maybemap(_parse_formula, get(model.mets[model.met_index[mid]], "formula", nothing))

"""
    metabolite_charge(model::JSONModel, mid::String)

Return the metabolite `.charge`
"""
metabolite_charge(model::JSONModel, mid::String) =
    get(model.mets[model.met_index[mid]], "charge", 0)

"""
    metabolite_compartment(model::JSONModel, mid::String)

Return the metabolite `.compartment`
"""
metabolite_compartment(model::JSONModel, mid::String) =
    get(model.mets[model.met_index[mid]], "compartment", nothing)

"""
    gene_annotations(model::JSONModel, gid::String)::Annotations

Gene annotations from the [`JSONModel`](@ref).
"""
gene_annotations(model::JSONModel, gid::String)::Annotations = _maybemap(
    _parse_annotations,
    get(model.genes[model.gene_index[gid]], "annotation", nothing),
)

"""
    gene_notes(model::JSONModel, gid::String)::Notes

Gene notes from the [`JSONModel`](@ref).
"""
gene_notes(model::JSONModel, gid::String)::Notes =
    _maybemap(_parse_notes, get(model.genes[model.gene_index[gid]], "notes", nothing))

"""
    reaction_annotations(model::JSONModel, rid::String)::Annotations

Reaction annotations from the [`JSONModel`](@ref).
"""
reaction_annotations(model::JSONModel, rid::String)::Annotations = _maybemap(
    _parse_annotations,
    get(model.rxns[model.rxn_index[rid]], "annotation", nothing),
)

"""
    reaction_notes(model::JSONModel, rid::String)::Notes

Reaction notes from the [`JSONModel`](@ref).
"""
reaction_notes(model::JSONModel, rid::String)::Notes =
    _maybemap(_parse_notes, get(model.rxns[model.rxn_index[rid]], "notes", nothing))

"""
    metabolite_annotations(model::JSONModel, mid::String)::Annotations

Metabolite annotations from the [`JSONModel`](@ref).
"""
metabolite_annotations(model::JSONModel, mid::String)::Annotations = _maybemap(
    _parse_annotations,
    get(model.mets[model.met_index[mid]], "annotation", nothing),
)

"""
    metabolite_notes(model::JSONModel, mid::String)::Notes

Metabolite notes from the [`JSONModel`](@ref).
"""
metabolite_notes(model::JSONModel, mid::String)::Notes =
    _maybemap(_parse_notes, get(model.mets[model.met_index[mid]], "notes", nothing))

"""
    reaction_stoichiometry(model::JSONModel, rid::String)::Dict{String, Float64}

Return the stoichiometry of reaction with ID `rid`.
"""
reaction_stoichiometry(model::JSONModel, rid::String)::Dict{String,Float64} =
    model.rxns[model.rxn_index[rid]]["metabolites"]

"""
    Base.convert(::Type{JSONModel}, mm::MetabolicModel)

Convert any [`MetabolicModel`](@ref) to [`JSONModel`](@ref).
"""
function Base.convert(::Type{JSONModel}, mm::MetabolicModel)
    if typeof(mm) == JSONModel
        return mm
    end

    rxn_ids = reactions(mm)
    met_ids = metabolites(mm)
    gene_ids = genes(mm)
    S = stoichiometry(mm)
    lbs, ubs = bounds(mm)
    ocs = objective(mm)

    json = Dict{String,Any}()
    json["id"] = "model" # default

    #TODO: add notes, names and similar fun stuff when they are available

    json[first(_constants.keynames.genes)] = [
        Dict([
            "id" => gid,
            "annotation" => gene_annotations(mm, gid),
            "notes" => gene_notes(mm, gid),
        ],) for gid in gene_ids
    ]

    json[first(_constants.keynames.mets)] = [
        Dict([
            "id" => mid,
            "formula" => _maybemap(_unparse_formula, metabolite_formula(mm, mid)),
            "charge" => metabolite_charge(mm, mid),
            "compartment" => metabolite_compartment(mm, mid),
            "annotation" => metabolite_annotations(mm, mid),
            "notes" => metabolite_notes(mm, mid),
        ]) for mid in met_ids
    ]

    json[first(_constants.keynames.rxns)] = [
        begin
            res = Dict{String,Any}()
            res["id"] = rid
            res["subsystem"] = reaction_subsystem(mm, rid)
            res["annotation"] = reaction_annotations(mm, rid)
            res["notes"] = reaction_notes(mm, rid)

            grr = reaction_gene_association(mm, rid)
            if !isnothing(grr)
                res["gene_reaction_rule"] = _unparse_grr(String, grr)
            end

            res["lower_bound"] = lbs[ri]
            res["upper_bound"] = ubs[ri]
            res["objective_coefficient"] = ocs[ri]
            I, V = findnz(S[:, ri])
            res["metabolites"] =
                Dict{String,Float64}([met_ids[ii] => vv for (ii, vv) in zip(I, V)])
            res
        end for (ri, rid) in enumerate(rxn_ids)
    ]

    return JSONModel(json)
end