module MathOptInterface

"""
    ModelLike

Abstract supertype for objects that implement the "Model" interface for defining
an optimization problem.
"""
abstract type ModelLike end

# This allows to use `ModelLike`s in broadcast calls without the need to
# embed it in a `Ref`
Base.broadcastable(model::ModelLike) = Ref(model)

Base.show(io::IO, model::ModelLike) = Utilities.print_with_acronym(io, summary(model))

"""
    AbstractOptimizer

Abstract supertype for objects representing an instance of an optimization problem
tied to a particular solver. This is typically a solver's in-memory representation.
In addition to `ModelLike`, `AbstractOptimizer` objects let you solve the
model and query the solution.
"""
abstract type AbstractOptimizer <: ModelLike end

"""
    optimize!(optimizer::AbstractOptimizer)

Start the solution procedure.
"""
function optimize! end

"""
    write_to_file(model::ModelLike, filename::String)

Writes the current model data to the given file.
Supported file types depend on the model type.
"""
function write_to_file end

"""
    read_from_file(model::ModelLike, filename::String)

Read the file `filename` into the model `model`. If `model` is non-empty, this may
throw an error.

Supported file types depend on the model type.

### Note

Once the contents of the file are loaded into the model, users can query the variables via
`get(model, ListOfVariableIndices())`. However, some filetypes, such as LP files, do not
maintain an explicit ordering of the variables. Therefore, the returned list may be in an
arbitrary order. To avoid depending on the order of the indices, users should look up each
variable index by name: `get(model, VariableIndex, "name")`.
"""
function read_from_file end

"""
    is_empty(model::ModelLike)

Returns `false` if the `model` has any model attribute set or has any variables or constraints.
Note that an empty model can have optimizer attributes set.
"""
function is_empty end

"""
    empty!(model::ModelLike)

Empty the model, that is, remove all variables, constraints and model attributes but not optimizer attributes.
"""
function empty! end

"""
    copy_to(dest::ModelLike, src::ModelLike; copy_names=true, warn_attributes=true)

Copy the model from `src` into `dest`. The target `dest` is emptied, and all
previous indices to variables or constraints in `dest` are invalidated. Returns
a dictionary-like object that translates variable and constraint indices from
the `src` model to the corresponding indices in the `dest` model.

If `copy_names` is `false`, the `Name`, `VariableName` and `ConstraintName`
attributes are not copied even if they are set in `src`. If a constraint that
is copied from `src` is not supported by `dest` then an
[`UnsupportedConstraint`](@ref) error is thrown. Similarly, if a model, variable
or constraint attribute that is copied from `src` is not supported by `dest`
then an [`UnsupportedAttribute`](@ref) error is thrown. Unsupported *optimizer*
attributes are treated differently:

* If `warn_attributes` is `true`, a warning is displayed, otherwise,
* the attribute is silently ignored.

### Example

```julia
# Given empty `ModelLike` objects `src` and `dest`.

x = add_variable(src)

is_valid(src, x)   # true
is_valid(dest, x)  # false (`dest` has no variables)

index_map = copy_to(dest, src)
is_valid(dest, x) # false (unless index_map[x] == x)
is_valid(dest, index_map[x]) # true
```
"""
function copy_to end

include("error.jl")
include("indextypes.jl")
include("functions.jl")
include("sets.jl")
include("attributes.jl")
include("constraints.jl")
include("modifications.jl")
include("variables.jl")
include("nlp.jl")

# submodules
include("Utilities/Utilities.jl") # MOI.Utilities
include("Test/Test.jl")           # MOI.Test
include("Bridges/Bridges.jl")     # MOI.Bridges
include("Benchmarks/Benchmarks.jl")

end


const FaceMesh{Dim,T,Element} = Mesh{Dim,T,Element,<:FaceView{Element}}

coordinates(mesh::FaceMesh) = coordinates(getfield(mesh, :simplices))
faces(mesh::FaceMesh) = faces(getfield(mesh, :simplices))

function texturecoordinates(mesh::AbstractMesh)
    hasproperty(mesh, :uv) && return mesh.uv
    hasproperty(mesh, :uvw) && return mesh.uvw
    return nothing
end

function normals(mesh::AbstractMesh)
    hasproperty(mesh, :normals) && return mesh.normals
    return nothing
end

const GLTriangleElement = Triangle{3,Float32}
const GLTriangleFace = TriangleFace{GLIndex}
const PointWithUV{Dim,T} = PointMeta{Dim,T,Point{Dim,T},(:uv,),Tuple{Vec{2,T}}}
const PointWithNormal{Dim,T} = PointMeta{Dim,T,Point{Dim,T},(:normals,),Tuple{Vec{3,T}}}
const PointWithUVNormal{Dim,T} = PointMeta{Dim,T,Point{Dim,T},(:normals, :uv),
                                           Tuple{Vec{3,T},Vec{2,T}}}
const PointWithUVWNormal{Dim,T} = PointMeta{Dim,T,Point{Dim,T},(:normals, :uvw),
                                            Tuple{Vec{3,T},Vec{3,T}}}

"""
    TriangleMesh{Dim, T, PointType}

Abstract Mesh with triangle elements of eltype `T`.
"""
const TriangleMesh{Dim,T,PointType} = AbstractMesh{TriangleP{Dim,T,PointType}}

"""
    PlainMesh{Dim, T}

Triangle mesh with no meta information (just points + triangle faces)
"""
const PlainMesh{Dim,T} = TriangleMesh{Dim,T,Point{Dim,T}}
const GLPlainMesh{Dim} = PlainMesh{Dim,Float32}
const GLPlainMesh2D = GLPlainMesh{2}
const GLPlainMesh3D = GLPlainMesh{3}

"""
    UVMesh{Dim, T}

PlainMesh with texture coordinates meta at each point.
`uvmesh.uv isa AbstractVector{Vec2f}`
"""
const UVMesh{Dim,T} = TriangleMesh{Dim,T,PointWithUV{Dim,T}}
const GLUVMesh{Dim} = UVMesh{Dim,Float32}
const GLUVMesh2D = UVMesh{2}
const GLUVMesh3D = UVMesh{3}

"""
    NormalMesh{Dim, T}

PlainMesh with normals meta at each point.
`normalmesh.normals isa AbstractVector{Vec3f}`
"""
const NormalMesh{Dim,T} = TriangleMesh{Dim,T,PointWithNormal{Dim,T}}
const GLNormalMesh{Dim} = NormalMesh{Dim,Float32}
const GLNormalMesh2D = GLNormalMesh{2}
const GLNormalMesh3D = GLNormalMesh{3}

"""
    NormalUVMesh{Dim, T}

PlainMesh with normals and uv meta at each point.
`normalmesh.normals isa AbstractVector{Vec3f}`
`normalmesh.uv isa AbstractVector{Vec2f}`
"""
const NormalUVMesh{Dim,T} = TriangleMesh{Dim,T,PointWithUVNormal{Dim,T}}
const GLNormalUVMesh{Dim} = NormalUVMesh{Dim,Float32}
const GLNormalUVMesh2D = GLNormalUVMesh{2}
const GLNormalUVMesh3D = GLNormalUVMesh{3}

"""
    NormalUVWMesh{Dim, T}

PlainMesh with normals and uvw (texture coordinates in 3D) meta at each point.
`normalmesh.normals isa AbstractVector{Vec3f}`
`normalmesh.uvw isa AbstractVector{Vec3f}`
"""
const NormalUVWMesh{Dim,T} = TriangleMesh{Dim,T,PointWithUVWNormal{Dim,T}}
const GLNormalUVWMesh{Dim} = NormalUVWMesh{Dim,Float32}
const GLNormalUVWMesh2D = GLNormalUVWMesh{2}
const GLNormalUVWMesh3D = GLNormalUVWMesh{3}

function decompose_triangulate_fallback(primitive::Meshable; pointtype, facetype)
    positions = decompose(pointtype, primitive)
    faces = decompose(facetype, primitive)
    # If faces returns nothing for primitive, we try to triangulate!
    if faces === nothing
        # triangulation.jl
        faces = decompose(facetype, positions)
    end
    return positions, faces
end

"""
    mesh(primitive::GeometryPrimitive;
         pointtype=Point, facetype=GLTriangle,
         uv=nothing, normaltype=nothing)

Creates a mesh from `primitive`.

Uses the element types from the keyword arguments to create the attributes.
The attributes that have their type set to nothing are not added to the mesh.
Note, that this can be an `Int` or `Tuple{Int, Int}``, when the primitive is grid based.
It also only losely correlates to the number of vertices, depending on the algorithm used.
#TODO: find a better number here!
"""
function mesh(primitive::Meshable; pointtype=Point, facetype=GLTriangleFace, uv=nothing,
              normaltype=nothing)

    positions, faces = decompose_triangulate_fallback(primitive; pointtype=pointtype, facetype=facetype)

    # We want to preserve any existing attributes!
    attrs = attributes(primitive)
    # Make sure this doesn't contain position, we'll add position explicitely via meta!
    delete!(attrs, :position)

    if uv !== nothing
        # this may overwrite an existing :uv, but will only create a copy
        # if it has a different eltype, otherwise it should replace it
        # with exactly the same instance - which is what we want here
        attrs[:uv] = decompose(UV(uv), primitive)
    end

    if normaltype !== nothing
        primitive_normals = normals(primitive)
        if primitive_normals !== nothing
            attrs[:normals] = decompose(normaltype, primitive_normals)
        else
            # Normals not implemented for primitive, so we calculate them!
            n = normals(positions, faces; normaltype=normaltype)
            if n !== nothing # ok jeez, this is a 2d mesh which cant have normals
                attrs[:normals] = n
            end
        end
    end
    return Mesh(meta(positions; attrs...), faces)
end

"""
    mesh(polygon::AbstractVector{P}; pointtype=P, facetype=GLTriangleFace,
         normaltype=nothing)

Create a mesh from a polygon given as a vector of points, using triangulation.
"""
function mesh(polygon::AbstractVector{P}; pointtype=P, facetype=GLTriangleFace,
              normaltype=nothing) where {P<:AbstractPoint{2}}

    return mesh(Polygon(polygon); pointtype=pointtype, facetype=facetype,
                normaltype=normaltype)
end

function mesh(polygon::AbstractPolygon{Dim,T}; pointtype=Point{Dim,T},
              facetype=GLTriangleFace, normaltype=nothing) where {Dim,T}

    positions, faces = decompose_triangulate_fallback(polygon; pointtype=pointtype, facetype=facetype)

    if normaltype !== nothing
        n = normals(positions, faces; normaltype=normaltype)
        positions = meta(positions; normals=n)
    end
    return Mesh(positions, faces)
end

function triangle_mesh(primitive::Meshable{N}; nvertices=nothing) where {N}
    if nvertices !== nothing
        @warn("nvertices argument deprecated. Wrap primitive in `Tesselation(primitive, nvertices)`")
        primitive = Tesselation(primitive, nvertices)
    end
    return mesh(primitive; pointtype=Point{N,Float32}, facetype=GLTriangleFace)
end

function uv_mesh(primitive::Meshable{N,T}) where {N,T}
    return mesh(primitive; pointtype=Point{N,Float32}, uv=Vec2f, facetype=GLTriangleFace)
end

function uv_normal_mesh(primitive::Meshable{N}) where {N}
    return mesh(primitive; pointtype=Point{N,Float32}, uv=Vec2f, normaltype=Vec3f,
                facetype=GLTriangleFace)
end

function normal_mesh(points::AbstractVector{<:AbstractPoint},
                     faces::AbstractVector{<:AbstractFace})
    _points = decompose(Point3f, points)
    _faces = decompose(GLTriangleFace, faces)
    return Mesh(meta(_points; normals=normals(_points, _faces)), _faces)
end

function normal_mesh(primitive::Meshable{N}; nvertices=nothing) where {N}
    if nvertices !== nothing
        @warn("nvertices argument deprecated. Wrap primitive in `Tesselation(primitive, nvertices)`")
        primitive = Tesselation(primitive, nvertices)
    end
    return mesh(primitive; pointtype=Point{N,Float32}, normaltype=Vec3f,
                facetype=GLTriangleFace)
end

"""
    volume(triangle)

Calculate the signed volume of one tetrahedron. Be sure the orientation of your
surface is right.
"""
function volume(triangle::Triangle) where {VT,FT}
    v1, v2, v3 = triangle
    sig = sign(orthogonal_vector(v1, v2, v3) ⋅ v1)
    return sig * abs(v1 ⋅ (v2 × v3)) / 6
end

"""
    volume(mesh)

Calculate the signed volume of all tetrahedra. Be sure the orientation of your
surface is right.
"""
function volume(mesh::Mesh) where {VT,FT}
    return sum(volume, mesh)
end

function Base.merge(meshes::AbstractVector{<:Mesh})
    return if isempty(meshes)
        error("No meshes to merge")
    elseif length(meshes) == 1
        return meshes[1]
    else
        m1 = meshes[1]
        ps = copy(coordinates(m1))
        fs = copy(faces(m1))
        for mesh in Iterators.drop(meshes, 1)
            append!(fs, map(f -> f .+ length(ps), faces(mesh)))
            append!(ps, coordinates(mesh))
        end
        return Mesh(ps, fs)
    end
end

"""
    pointmeta(mesh::Mesh; meta_data...)

Attaches metadata to the coordinates of a mesh
"""
function pointmeta(mesh::Mesh; meta_data...)
    points = coordinates(mesh)
    attr = attributes(points)
    delete!(attr, :position) # position == metafree(points)
    # delete overlapping attributes so we can replace with `meta_data`
    foreach(k -> delete!(attr, k), keys(meta_data))
    return Mesh(meta(metafree(points); attr..., meta_data...), faces(mesh))
end

function pointmeta(mesh::Mesh, uv::UV)
    return pointmeta(mesh; uv=decompose(uv, mesh))
end

function pointmeta(mesh::Mesh, normal::Normal)
    return pointmeta(mesh; normals=decompose(normal, mesh))
end

"""
    pop_pointmeta(mesh::Mesh, property::Symbol)
Remove `property` from point metadata.
Returns the new mesh, and the property!
"""
function pop_pointmeta(mesh::Mesh, property::Symbol)
    points = coordinates(mesh)
    attr = attributes(points)
    delete!(attr, :position) # position == metafree(points)
    # delete overlapping attributes so we can replace with `meta_data`
    m = pop!(attr, property)
    return Mesh(meta(metafree(points); attr...), faces(mesh)), m
end

"""
    facemeta(mesh::Mesh; meta_data...)

Attaches metadata to the faces of a mesh
"""
function facemeta(mesh::Mesh; meta_data...)
    return Mesh(coordinates(mesh), meta(faces(mesh); meta_data...))
end

function attributes(hasmeta::Mesh)
    return Dict{Symbol,Any}((name => getproperty(hasmeta, name)
                             for name in propertynames(hasmeta)))
end


function modeljs(model, x, result::WebIO.Observable)
    code, weights = FluxJS.compile(model, x)
    code = join(split(code, "\n")[1:end-2],"\n") # remove fetch statement
    code = WebIO.JSString(code)

    JSExpr.@js (() -> begin
        $(code)
        JSExpr.@var weights = $weights.map(e -> begin
            tf.tensor(e)
        end)
        JSExpr.@var x = tf.tensor($x)
        model.setWeights(weights)
        window.model = model
        $result[] = model(x).dataSync()
    end)
end

function Base.take!(o::Channel,timeout::Int)
    taken = false
    @async begin
        sleep(timeout)
        !taken && put!(o, nothing)
    end
    s = take!(o)
    taken = true
    return s
end

function compare(res, out)
    out == nothing && return false
    resjs = Array{eltype(res),1}(undef, length(out))
    for i in keys(out)
        resjs[Main.parse(Int, i) + 1] = out[i]
    end
    return all(x -> x, abs.(res .- resjs) .< 10.0^(-5))
end

function testjs(w, model, x)
    s = Scope()
    r = Observable(s, "result", Dict())
    output = Channel{Any}(1)
    mjs = modeljs(model, x, r)
    onimport(s, mjs)
    on(r) do o
        put!(output, o)
    end
    Blink.body!(w, s)
    res = [data(model(x))...]
    @test compare(res, take!(output, 5))
end

loadseq(w) = nothing

function loadseq_(w, config, cb)
    s = Scope(imports=config["files"])
    obs = Observable(s, "loaded", false)
    onimport(s, JSExpr.@js ()->begin
        JSExpr.@var names = $(config["names"])
        JSExpr.@var modules = arguments
        names.forEach((e, i) -> begin
            window[e] = modules[i]
        end)
        $obs[] = true
    end)
    Blink.body!(w, s)

    b = Channel{Any}(1)
    on(obs) do x
        put!(b, 1)
    end
    s = take!(b, 3) # timeout of 3 secs
    s == nothing && throw("files not loading")
    cb()
end

function loadseq(w, config, args...)
    loadseq_(w, config, () -> loadseq(w, args...))
end

function setupWindow()
    w = Blink.Window(Dict(:show => false))
    libs = [
        "https://unpkg.com/bson@2.0.8/browser_build/bson.js",
        "//cdnjs.cloudflare.com/ajax/libs/tensorflow/0.11.7/tf.js"]
    fluxjs = [normpath("$(@__DIR__)/../lib/flux.js")]
    loadseq(w, Dict("files"=>libs, "names"=>["BSON", "tf"]), Dict("files"=>fluxjs, "names"=>["fluxjs"]))
    return w
end


"Default inference algorithm"
defalg(args...) = FailUnsat

"Default Ω to use"
# defΩ(args...) = SimpleΩ{Vector{Int}, Any}
defΩ(args...) = LinearΩ{Vector{Int}, UnitRange{Int64}, Vector{Any}}

"Default projection"
defΩProj(args...; OT = defΩ(args...)) = ΩProj{OT, idtype(OT)}

"Default callbacks"
defcb(args...) = donothing

"Single sample form from `x`"
function Base.rand(x::RandVar;
                   alg::SamplingAlgorithm = defalg(x),
                   ΩT = defΩ(alg),
                   kwargs...)
  first(rand(Random.GLOBAL_RNG, x, 1, alg; ΩT = ΩT, kwargs...))
end

function Base.rand(rng::AbstractRNG,
                   x::RandVar;
                   alg::SamplingAlgorithm = defalg(x),
                   ΩT::Type{OT} = defΩ(alg),
                   kwargs...) where {OT <: Ω}
  first(rand(rng, x, 1, alg; ΩT = ΩT, kwargs...))
end

function Base.rand(rng::AbstractRNG,
                   x::RandVar,
                   n::Integer,
                   alg::SamplingAlgorithm;
                   ΩT::Type{OT} = defΩ(alg),
                   kwargs...) where {OT <: Ω}
  logdensity = Omega.mem(logerr(indomainₛ(x)))
  ωsamples = rand(rng, ΩT, logdensity, n, alg; kwargs...)
  map(ω -> applynotrackerr(Omega.mem(x), ω), ωsamples)
end

function Base.rand(x::RandVar,
                   n::Integer;
                   alg::SamplingAlgorithm = defalg(x),
                   ΩT::Type{OT} = defΩ(alg),
                   kwargs...) where {OT <: Ω}
  rand(Random.GLOBAL_RNG, x, n, alg; ΩT = ΩT, kwargs...)
end

function Base.rand(rng::AbstractRNG,
                   x::RandVar,
                   n::Integer;
                   alg::SamplingAlgorithm = defalg(x),
                   ΩT::Type{OT} = defΩ(alg),
                   kwargs...) where {OT <: Ω}
  rand(rng, x, n, alg; ΩT = ΩT, kwargs...)
end

function Base.rand(x::RandVar,
                   n::Integer,
                   alg::SamplingAlgorithm;
                   ΩT::Type{OT} = defΩ(alg),
                   kwargs...) where {OT <: Ω}
  rand(Random.GLOBAL_RNG, x, n, alg; ΩT = ΩT, kwargs...)
end

# Condition (deprecate this?) x | y

Base.rand(x::RandVar, y::RandVar, n; kwargs...) = rand(Random.GLOBAL_RNG, cond(x, y), n; kwargs...)
Base.rand(x::RandVar, y::RandVar; kwargs...) = rand(Random.GLOBAL_RNG, cond(x, y); kwargs...)
Base.rand(rng::AbstractRNG, x::RandVar, y::RandVar, n; kwargs...) = rand(rng, cond(x, y), n; kwargs...)
Base.rand(rng::AbstractRNG, x::RandVar, y::RandVar; kwargs...) = rand(rng, cond(x, y); kwargs...)

# Return Tuple

Base.rand(x::UTuple{RandVar}, n::Integer; kwargs...) = rand(randtuple(x), n; kwargs...)
Base.rand(x::UTuple{RandVar}; kwargs...) = rand(randtuple(x); kwargs...)

Base.rand(x::UTuple{RandVar}, y::RandVar, n::Integer; kwargs...) = rand(randtuple(x), y, n; kwargs...)
Base.rand(x::UTuple{RandVar}, y::RandVar; kwargs...) = rand(randtuple(x), y; kwargs...)

# Return Ω object

"Returns Ω object, .e.g `x = normal(0, 1); rand(Ω, x > 0)`"
function ld(rng::AbstractRNG,
                   ΩT::Type{OT},
                   x::RandVar,
                   n::Integer,
                   alg::SamplingAlgorithm;
                   kwargs...) where {OT <: Ω}
  logdensity = Omega.mem(logerr(indomainₛ(x)))
  ωsamples = rand(rng, ΩT, logdensity, n, alg; kwargs...)
end

"Returns Ω object, e.g. `x = normal(0, 1); rand(Ω, cond(x, x >ₛ 2.0), 10)`"
Base.rand(rng::AbstractRNG, ::Type{Ω}, x::RandVar, n::Integer; alg::SamplingAlgorithm = defalg(x), kwargs...) =
  ld(rng, x, defΩ(alg), n, alg; kwargs...)

Base.rand(::Type{Ω}, x::RandVar, n::Integer; alg::SamplingAlgorithm = defalg(x), kwargs...) =
  ld(Random.GLOBAL_RNG, defΩ(alg), x, n, alg; kwargs...)

# Autorand

"`autorand(x::RandVar)` Single sample from `x`.  Inference `alg` and hyperparams, chosen automatically"
autorand(x::RandVar) = rand(x, 1000; alg = SSMH)[end]
Base.getindex(x::RandVar) = autorand(x)


"""

```
grid3D(solute,solute_atoms,mddf_result,output_file; dmin=1.5, ddax=5.0, step=0.5)
```

This function builds the grid of the 3D density function and fills an array of
mutable structures of type Atom, containing the position of the atoms of
grid, the closest atom to that position, and distance.

`solute` is a `ComplexMixtuers.Selection`, defining the solute. `solute_atoms` is the corresponding
vector of `PDBTools.Atom`s, and `mddf_result` is the result of a `mddf_result` calculation with
the correspondign solute.

`dmin` and `dmax` define the range of distance where the density grid will be built, and `step`
defines how fine the grid must be. Be aware that fine grids involve usually a very large (hundreds
of thousands points).

All parameters can be provides as keyword parameters.

### Example

```julia-repl
julia> using ComplexMixtures, PDBTools

julia> pdb = readPDB("./system.pdb");

julia> R = ComplexMixtures.load("./results.json");

julia> protein = select(pdb,"protein");

julia> solute = ComplexMixtures.Selection(protein,nmols=1);

julia> grid = ComplexMixtures.grid3D(solute=solute, solute_atoms=protein, mddf_result=R, output_file="grid.pdb");

```

`grid` will contain a vector of `Atom`s with the information of the MDDF at each grid point, and the
same data will be written in the `grid.pdb` file. This PDB file can be opened in VMD, for example, and contain
in the `beta` field the contribution of each protein residue to the MDDF at each point in space relative to the
protein, and in the `occupancy` field the distance to the protein. Examples of how this information can be
visualized are provided in the user guide of `ComplexMixtures`.

"""
grid3D(;
    solute = nothing,
    solute_atoms = nothing,
    mddf_result = nothing,
    output_file = nothing,
    dmin = 1.5,
    dmax = 5.0,
    step = 0.5,
) = grid3D(
    solute,
    solute_atoms,
    mddf_result,
    output_file,
    dmin = dmin,
    dmax = dmax,
    step = step,
)

function grid3D(
    solute,
    solute_atoms,
    mddf_result,
    output_file;
    dmin = 1.5,
    dmax = 5.0,
    step = 0.5,
)

    if nothing in (solute, solute_atoms, mddf_result)
        error("grid3D requires `solute`, `solute_atoms` and `mddf_result` definitions.")
    end

    # Simple function to interpolate data
    interpolate(x₁, x₂, y₁, y₂, xₙ) = y₁ + (y₂ - y₁) / (x₂ - x₁) * (xₙ - x₁)

    # Maximum and minimum coordinates of the solute
    lims = maxmin(solute_atoms)
    n = @. ceil(Int, (lims.xlength + 2 * dmax) / step + 1)

    # Building the grid with the nearest solute atom information
    igrid = 0
    grid = PDBTools.Atom[]
    for ix = 1:n[1]
        x = lims.xmin[1] - dmax + step * (ix - 1)
        for iy = 1:n[2]
            y = lims.xmin[2] - dmax + step * (iy - 1)
            for iz = 1:n[3]
                z = lims.xmin[3] - dmax + step * (iz - 1)
                rgrid = -1
                iat, r = PDBTools.closest(x, y, z, solute_atoms)
                if (dmin < r < dmax)
                    if rgrid < 0 || r < rgrid
                        at = solute_atoms[iat]
                        # Get contribution of this atom to the MDDF
                        c = ComplexMixtures.contrib(
                            solute,
                            mddf_result.solute_atom,
                            [at.index],
                        )
                        # Interpolate c at the current distance
                        iright = findfirst(d -> d > r, mddf_result.d)
                        ileft = iright - 1
                        cᵣ = interpolate(
                            mddf_result.d[ileft],
                            mddf_result.d[iright],
                            c[ileft],
                            c[iright],
                            r,
                        )
                        if cᵣ > 0
                            gridpoint = Atom(
                                index = at.index,
                                index_pdb = at.index_pdb,
                                name = at.name,
                                chain = at.chain,
                                resname = at.resname,
                                resnum = at.resnum,
                                x = x,
                                y = y,
                                z = z,
                                occup = r,
                                beta = cᵣ,
                                model = at.model,
                                segname = at.segname,
                            )
                            if rgrid < 0
                                igrid += 1
                                push!(grid, gridpoint)
                            elseif r < rgrid
                                grid[igrid] = gridpoint
                            end
                            rgrid = r
                        end # cᵣ>0
                    end # rgrid
                end # dmin/dmax
            end #iz
        end #iy
    end #ix

    # Now will scale the density to be between 0 and 99.9 in the temperature
    # factor column, such that visualization is good enough
    bmin, bmax = +Inf, -Inf
    for gridpoint in grid
        bmin = min(bmin, gridpoint.beta)
        bmax = max(bmax, gridpoint.beta)
    end
    for gridpoint in grid
        gridpoint.beta = (gridpoint.beta - bmin) / (bmax - bmin)
    end

    if output_file != nothing
        writePDB(grid, output_file)
    end
    return grid
end


################################
## Helper methods for Sampler ##
################################

is_licensor(f :: Feature) = length(f) > 1 && f[1] == '+'
is_licensee(f :: Feature) = length(f) > 1 && f[1] == '-'

###Finds selector features to branch children
function find_features(lex)
    selectors = Feature[]
    licensors = Feature[]
    licensees = Feature[]
    for f in lex.features
        if is_selector(f)
            push!(selectors,f[3:length(f)])
        elseif is_licensor(f)
            push!(licensors,f[2:length(f)])
        elseif is_licensee(f)
            push!(licensees,f[2:length(f)])
        end
    end
    return (selectors, licensors, licensees)
end

#### Recursively builds a sample tree rooted at category C
function treeRootC_move(L, c, parent, prev_licensors)
    move_feat_mismatch = true
    move_feats = []
    id = ""
    selectors = []
    licensors = []
    licensees = []
    while move_feat_mismatch
        move_feats = copy(prev_licensors)
        id = sample(L.MG.lexitem_cond.dists[c])
        lex = L.lexicon[id]
        selectors, licensors, licensees = find_features(lex)
        if licensees != []
            for licensee in licensees
                if licensee in move_feats
                     deleteat!(move_feats ,findfirst(move_feats,licensee))
                else
                     break
                end
                move_feat_mismatch = false
            end
        end
        if selectors == []
            if move_feats != []
                move_feat_mismatch = true
            else
                move_feat_mismatch = false
            end
        else
            move_feat_mismatch = false
        end
    end
    node = TreeNode(id, parent)
    if length(selectors) > 0
        len = length(subroots)
        for sel in subroots[1:(len-1)]
            insert_child!(node, treeRootC(L,sel,node, []))
        end
        insert_child!(node, treeRootC(L,subroots[len],node, append!(move_feats,licensors)))
        return node
    else
        return node
    end
end

###################
##Forward Sampler##
###################

function samplerMGderivation_move(L::SampleGrammar{String, LogProb, SimpleCond})
    i = rand(1:length(L.selectees))
    c = L.selectees[i]
    root = EmptyTree{String}()
    d = treeRootC_move(L,c,root,[])
    prob = probDerivation(d, L)
    return SampleDerivation(d,prob)
end

####################
## Make a corpus  ##
####################

function getchain_fromderiv_move(d, L)
    chain = []
    if ! isterminal(d)
        lex = L.lexicon[d.data]
        selectors, licensors, licensees = find_features(lex)
        for i in 1:(length(selectors))
            f = selectors[i]
            if contains(f, "L")
                chain = append!(getchain_fromderiv_move(d.children[i], L), [lex])
            else
                chain = append!([lex], getchain_fromderiv_move(d.children[i], L))
            end
        end
    else
        lex = L.lexicon[d.data]
        chain = [lex]
    end
    return chain
end

function apply_move(chain)
    sent = []
    while chain != []
        selectors, licensors, licensees = find_features(chain[1])
        while licensors != []
            for i in (2:length(chain))
                next_selectors, next_licensors, next_licensees = find_features(chain[i])
                if next_licensees != []
                    if licensors[1] == next_licensees[1]
                        append!(sent, [chain[i]])
                        deleteat!(licensors,1)
                        deleteat!(chain, i )
                        break
                    end
                end
            end
        end
        push!(sent, chain[1])
        deleteat!(chain, 1)
    end
    return sent
end

function getsent_fromderiv_move(d, L)
    chain = apply_move(getchain_fromderiv_move(d, L))
    sent = ""
    for lex in chain
        sent = string(sent, lex.phon , " ")
    end
    return sent
end

function sample_corpus_move(corpus_size, L, result_dir)
    corpus = Array{Any}(corpus_size)
    for i in 1:corpus_size
        corpus[i] = samplerMGderivation_move(L)
    end
    name1 = string(result_dir,"/corpus.txt")
    name2 = string(result_dir,"/corpus_probs.txt")
    open(name1, "w") do f
        for deriv in corpus
            sent = getsent_fromderiv_move(deriv.d, L)
            write(f, sent)
            write(f, "\n")
        end
    end
    open(name2, "w") do f
        for deriv in corpus
            sent = getsent_fromderiv_move(deriv.d, L)
            prob = deriv.prob
            p = string(exp(prob))
            write(f, sent)
            write(f, string(";", p))
            write(f, "\n")
        end
    end
end


module Day23

using AdventOfCode2019

struct Computer
    task::Union{Task,Nothing}
    input::Union{Channel{Int},Nothing}
    output::Union{Channel{Int},Nothing}
    waiting::Union{Channel{Bool},Nothing}
    address::Int
end

function all_waiting(network::Dict{Int,Computer})
    for (k, v) in network
        k == 255 && continue
        if !isready(v.waiting) || isready(v.input)
            return false
        end
    end
    return true
end

function has_unprocessed_output(network::Dict{Int,Computer})
    for (k, v) in network
        k == 255 && continue
        if isready(v.output)
            return true
        end
    end
    return false
end

function initialize_network(program::Array{Int,1})
    network = Dict{Int,Computer}()
    for i = 0:49
        inp = Channel{Int}(100)
        out = Channel{Int}(100)
        waiting = Channel{Bool}(1)
        address = i
        t = @async AdventOfCode2019.IntCode.run_program!(copy(program), inp, out, nothing; waitingForInput = waiting)
        network[i] = Computer(t, inp, out, waiting, address)

        # Initialize computer by assigning its network address
        put!(network[i].input, i)
        put!(network[i].input, -1)
    end
    network[255] = Computer(nothing, Channel{Int}(100), Channel{Int}(100), Channel{Bool}(1), 255)
    return network
end

function shutdown(network::Dict{Int,Computer})
    for (k, v) in network
        close(v.input)
        close(v.output)
        close(v.waiting)
    end
end

function day23(input::String = readInput(joinpath(@__DIR__, "..", "data", "day23.txt")))
    program = parse.(Int, split(input, ','))
    return solve(program)
end

function solve(program::Array{Int,1})
    network = initialize_network(program)
    firstYChannel = Channel{Int}(1)
    lastYChannel = Channel{Int}(1)
    currentNATPacket = [-1, -1]
    @label resume
    while true
        read = false
        for (k, v) in network
            k == 255 && continue
            if isready(v.output)
                read = true
                a = take!(v.output)
                x = take!(v.output)
                y = take!(v.output)
                put!(network[a].input, x)
                put!(network[a].input, y)
            end
        end
        if !read
            for (k, v) in network
                k == 255 && continue
                put!(v.input, -1)
            end
        end
        while true
            if has_unprocessed_output(network)
                break
            elseif all_waiting(network)
                while isready(network[255].input)
                    currentNATPacket[1] = take!(network[255].input)
                    currentNATPacket[2] = take!(network[255].input)
                    if !isready(firstYChannel)
                        put!(firstYChannel, currentNATPacket[2])
                    end
                end
                put!(network[0].input, currentNATPacket[1])
                put!(network[0].input, currentNATPacket[2])
                if isready(lastYChannel)
                    if take!(lastYChannel) == currentNATPacket[2]
                        part1 = take!(firstYChannel)
                        close(firstYChannel)
                        close(lastYChannel)
                        shutdown(network)
                        return [part1, currentNATPacket[2]]
                    end
                end
                put!(lastYChannel, currentNATPacket[2])
                break
            end
            yield()
        end
    end
end

end # module


# obtain and record medline dataframe from internet based on Readcube-generated bib file
#
# HGow to use
# First, Generate ris file from Readcube, selecting ALL references
#
# You can obtain reference data from DataFrame by
# make_html_from_dataframe
#
# You can obtain reference data from (Array of) String or Int64 of pmid (String may be separated by comma) by
# make_html_from_pmid
#
# You can obtain reference data from gcsv file by
# make_html_from_csv
#
# bibfile_abs is path of Readcube-generated ris file
# pmid_list_file_abs is path of list of pmid_data
# pubmed_data_processed_file_abs is path of csv file

#if new Reference style is needed, edit add_name_list_and_data_to_pmid_data or add_several_reference_string(pmid_data)
# in the module_Refrence_Tools
module module_Reference_Tools
    export obtain_medline_from_readcube_bib
    export make_html_from_pmid
    export make_html_from_csv
    export make_html_from_dataframe
    export custom_CSV_read
    export write_html

    using DataFrames
    using HTTP
    using CSV
    using Dates

# """
# sub funcyion of StringToIntpart
# """
    Base.@ccallable function LetterToInt(char::Char)::Int64
        x = Int(char)
        if ((x>=48) && (x<=57))
            return(x-48)
        else
            throw("invalid input; not number")
        end
        #vecx =  ['0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9']
        #return(findfirst((vecx .== char))-1)
    end

# """
# convert String to Integer; if cannot, throw error
# """
    Base.@ccallable function StringToIntpart(char::String)::Int64
        if (occursin(r"^-",char))
            signx = -1
            charx = replace(char, r"^-"=>"")
        else
            signx = 1
            charx = replace(char, r"^\+"=>"")
        end

        x1 = reverse(LetterToInt.(collect(charx)))

        fact = 1
        summed1 = 0
        for ix in 1:length(x1)
            summed1 = summed1 + fact * x1[ix]
            fact = fact*10
        end
        return(summed1*signx)
    end

    # """
    # #select item not included in old, from new
    # """
    Base.@ccallable function select_new(new::Array{String,1}, old::Array{String,1})::Array{String,1}
        boo = fill(false, length(new));
        for ix in 1:length(new)
            boo[ix] = !any(new[ix] .== old)
        end
        return(new[boo])
    end

# """
# #select item not included in old, from new (sumbol version)
# """
    Base.@ccallable function select_new(new::Array{Symbol,1}, old::Array{Symbol,1})::Array{Symbol,1}
        boo = fill(false, length(new));
        for ix in 1:length(new)
            boo[ix] = !any(new[ix] .== old)
        end
        return(new[boo])
    end

# """
# returns Array{String,1}
# each element is PMID concatenated with comma ("11253645,14522846,...""), with maximum unitnum
# """
    Base.@ccallable function join_by_unit(vec::Array{String,1}, unitnum::Int64, collapse::String=",")::Array{String,1}
        if (length(vec) == 0)
            return(fill("",1))
        end
        if (unitnum <= 0)
            return(fill("",1))
        end

        num = div(length(vec),unitnum)+1
        ret = fill("", num)
        for jx in 1:num
            rang = (unitnum*(jx-1)+1):min(unitnum*jx, length(vec))
            ret[jx] = join(vec[rang], collapse)
        end
        return(ret)
    end

# """
#  read CSV as all String
#  """
    Base.@ccallable function custom_CSV_read(file::String)::DataFrame
        temp = CSV.read(file;  missingstring = "this_is_missing_hahaha", limit = 2)
        types = fill(String, size(temp,2))
        return(CSV.read(file; missingstring = "this_is_missing_hahaha", types = types))
    end

# """
# delete first and last space and kaigyo   #
# """
    Base.@ccallable function delete_gomi_from_text(char::String)::String
        char = replace(char, r"^[ \n]*"=>"")
        char = replace(char, r"[ \n]*$"=>"")
        return(char)
    end

# """
# Base.@ccallable function keep_first(vec::Array{String,1})::Array{Bool, 1}
#     return is Array{Bool, 1}
# if a nth element is not equal to any of 1st to n-1th, return[n] is true
# """
    Base.@ccallable function keep_first(vec::Array{String,1})::Array{Bool, 1}
        if length(vec)<=1
            return(fill(true,size(vec)))
        end

        boo = fill(true, size(vec))
        for ix in 2:(length(vec))
            boo[ix] = all(vec[1:(ix-1)] .!= vec[ix])
        end
        return(boo)
    end

# """    Base.@ccallable function convert_readcube_bib(bibfile_abs::String, pmid_list_file_abs::String, unitnum::Int64=150, all_refresh::Bool=false)::Array{String,1}
# convert "readcube-exported bib file" to PMID Array, and write it  to "PMID_list_from_Julia.txt"
# returned value is new PMID list, concatenated with comma
# #
# bibfile_absis abs. path of bib file
# pmid_list_file_abs is abs. path of generated PMID Array file
# """
    Base.@ccallable function convert_readcube_bib(bibfile_abs::String, pmid_list_file_abs::String, unitnum::Int64=150, all_refresh::Bool=false)::Array{String,1}
        text = try
                    String.(split(read(bibfile_abs, String), "\n"));
                catch
                    fill("",0)
        end

        if (length(text)==0)
            return(fill("", 1))
        end

        text_old = try
                    String.(split(read(pmid_list_file_abs, String), ","));
                catch
                    fill("",0)
        end

        text_old_0 = copy(text_old)

        if (all_refresh)
            text_old = fill("",0)
        end

        #text = String.(split(read(bibfile_abs, String), "\n"));

        boo = fill(false, length(text));
        reg = r"pmid *= *\{.[^\{\}]*\}"

        # char = "pmid={28716377},"
        # occursin(reg,char)

        for ix in 1:length(text)
            boo[ix] = occursin(reg, text[ix]);
        end
        #length(boo[boo])
        text = text[boo]
        for ix in 1:length(text)
            text[ix] = replace(text[ix], r"pmid *= *\{"=>"")
            text[ix] = replace(text[ix], "},"=>"")
            text[ix] = replace(text[ix], " "=>"")
        end

        text = select_new(text, text_old) #only new PMID list are picked up

        if (all_refresh)
            text_old = select_new(text_old_0, text)
        end

        text_all = unique( [text_old; text] )

        if (length(text)==0)
            return(fill("", 1))
        end

        char1 =  join_by_unit(text, unitnum, ",")
        char2 = join(text_all, ",")

        try
            write(pmid_list_file_abs, char2);
        catch
            ;
        end
        return( char1 )
    end

    # """
    # Base.@ccallable function merge_dataframe(dfa::DataFrame, dfb::DataFrame)::DataFrame
    #
    #     merge two DataFrames
    #     both dfa and dfb column are left (if duplicated dfa column is left)
    #     """
    Base.@ccallable function merge_dataframe(dfa::DataFrame, dfb::DataFrame)::DataFrame
        df1 = copy(dfa)
        df2 = copy(dfb)
        boo = fill(true, size(df2, 1))

        for ix in 1:size(df2, 1)
            try
                boo[ix] = all(df2.PMID[ix] .!= df1.PMID)
            catch
                ;
            end
        end
        df2 = df2[boo, :]

        both = unique([names(df1); names(df2)])
        boo_n1 = fill(false, length(both))
        boo_n2 = fill(false, length(both))

        for ix in 1:length(both)
            boo_n1[ix] = all(both[ix] .!= names(df1))
            boo_n2[ix] = all(both[ix] .!= names(df2))
        end

        c_df1 = both[boo_n1]
        c_df2 = both[boo_n2]

        for ix in 1:length(c_df1)
            df1[c_df1[ix]] = fill("", size(df1,1))
        end
        for ix in 1:length(c_df2)
            df2[c_df2[ix]] = fill("", size(df2,1))
        end

        names(df1) == names(df2)

        return( copy(vcat(df1, df2)) )
     #only unincluded in df1

        # #return( join(df1, df2, on = :PMID, kind = :outer, makeuniwue) )
        #
        # data_vacant = DataFrame(fill(fill("", (size(df1,1) + size(df2,1))), length(both)), Symbol.(both))
        #
        # data_vacant
        #
        # if ((size(df1,1) != 0))
        #     for kx in names(df1)
        #         for jx in 1:size(df1,1)
        #             data_vacant[jx, kx] = df1[jx, kx] * ""
        #         end
        #     end
        # end
        #
        # if ((size(df2,1) != 0))
        #     for kx in names(df2)
        #         for ix in 1:size(df2,1)
        #             data_vacant[(size(df1,1) + jx), kx] = df2[jx, kx]
        #         end
        #     end
        # end
        #
        # #both are size 0
        # return(data_vacant)
    end

# """
# Base.@ccallable function get_pubmed_dataframe_from_pmid_list(char0::String)::DataFrame
#     obtain DataFrame from char0
#     char0 is String of single PMID or several PMID concatenated with comma
#
# Base.@ccallable function get_pubmed_dataframe_from_pmid_list(char::Array{String,1})::DataFrame
#     Array{String, 1} VERSION
#
#
# """
    Base.@ccallable function get_pubmed_dataframe_from_pmid_list(char0::String)::DataFrame
        if (char0 == r" *")
            #return DataFrame()
            return(data_vacant)
        end

        address = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=" * char0 * "&rettype=medline&retmode=text"
        http_message = replace(String(HTTP.request("GET", address; verbose=0)), "\r"=> "")
        http_message = replace(http_message, "\n      "=>" ")
        http_message = replace(http_message, "\""=>"")
        #http_message = replace(http_message, ","=>"\\,")

        vecx = String.(split(http_message, "\n\n"));
        vecx = vecx[occursin.("PMID- ", vecx)]
        vecx = delete_gomi_from_text.(vecx)

        dataframe_pmid = DataFrame()
        for ix in vecx
            vec2 = String.(split(ix, "\n"));
            vec2 = vec2[occursin.("-", SubString.(vec2,1,5))]

            df = DataFrame( Symbol("PMID") => fill("", 1), Symbol("OWN") => fill("", 1), Symbol("STAT") => fill("", 1), Symbol("DCOM") => fill("", 1), Symbol("LR") => fill("", 1), Symbol("IS") => fill("", 1), Symbol("VI") => fill("", 1), Symbol("IP") => fill("", 1), Symbol("DP") => fill("", 1), Symbol("TI") => fill("", 1), Symbol("PG") => fill("", 1), Symbol("LID") => fill("", 1), Symbol("AB") => fill("", 1), Symbol("CI") => fill("", 1), Symbol("FAU") => fill("", 1),Symbol("AU") => fill("", 1), Symbol("AD") => fill("", 1), Symbol("LA") => fill("", 1), Symbol("PT") => fill("", 1), Symbol("DEP") => fill("", 1), Symbol("PL") => fill("", 1), Symbol("TA") => fill("", 1), Symbol("JT") => fill("", 1), Symbol("JID") => fill("", 1), Symbol("RN") => fill("", 1), Symbol("SB") => fill("", 1), Symbol("MH") => fill("", 1), Symbol("OTO") => fill("", 1), Symbol("OT") => fill("", 1), Symbol("EDAT") => fill("", 1), Symbol("MHDA") => fill("", 1), Symbol("CRDT") => fill("", 1), Symbol("PHST") => fill("", 1), Symbol("AID") => fill("", 1), Symbol("PST") => fill("", 1), Symbol("SO")=> fill("", 1), Symbol("GR") => fill("", 1), Symbol("CIN") => fill("", 1), Symbol("PMC") => fill("", 1), Symbol("MID") => fill("", 1), Symbol("RF") => fill("", 1), Symbol("COIS") => fill("", 1), Symbol("SI") => fill("", 1), Symbol("AUID") => fill("", 1), Symbol("CN") => fill("", 1), Symbol("IR") => fill("", 1), Symbol("FIR") => fill("", 1), Symbol("EIN") => fill("", 1), Symbol("UOF") => fill("", 1), Symbol("EFR") => fill("", 1) )
            #if (length(vec2) != 0)
            if (length(vec2) != 0)
                vec0 = fill("", length(vec2))
                mat = [vec0 vec0]

                for jx in 1:length(vec2)
                    mat[jx,:] = ( String.(split(vec2[jx], "-",limit=2)) )
                end
                mat = delete_gomi_from_text.(mat)

                item = unique(mat[:,1])
                mat2 = [item fill("", length(item))]

                for kx in 1:length(item)
                    symb = Symbol(item[kx])
                    vecitem = (mat[:,2])[(mat[:,1]) .== item[kx]]
                    df[symb] = fill(join(vecitem, "\n"),1) #single row dataframe for ix
                end
            end
            dataframe_pmid = merge_dataframe(dataframe_pmid, df)
        end
        return(dataframe_pmid)
    end

    Base.@ccallable function get_pubmed_dataframe_from_pmid_list(char::Array{String,1})::DataFrame
        data_vacant = DataFrame( Symbol("PMID") => fill("", 0), Symbol("OWN") => fill("", 0), Symbol("STAT") => fill("", 0), Symbol("DCOM") => fill("", 0), Symbol("LR") => fill("", 0), Symbol("IS") => fill("", 0), Symbol("VI") => fill("", 0), Symbol("IP") => fill("", 0), Symbol("DP") => fill("", 0), Symbol("TI") => fill("", 0), Symbol("PG") => fill("", 0), Symbol("LID") => fill("", 0), Symbol("AB") => fill("", 0), Symbol("CI") => fill("", 0), Symbol("FAU") => fill("", 0),Symbol("AU") => fill("", 0), Symbol("AD") => fill("", 0), Symbol("LA") => fill("", 0), Symbol("PT") => fill("", 0), Symbol("DEP") => fill("", 0), Symbol("PL") => fill("", 0), Symbol("TA") => fill("", 0), Symbol("JT") => fill("", 0), Symbol("JID") => fill("", 0), Symbol("RN") => fill("", 0), Symbol("SB") => fill("", 0), Symbol("MH") => fill("", 0), Symbol("OTO") => fill("", 0), Symbol("OT") => fill("", 0), Symbol("EDAT") => fill("", 0), Symbol("MHDA") => fill("", 0), Symbol("CRDT") => fill("", 0), Symbol("PHST") => fill("", 0), Symbol("AID") => fill("", 0), Symbol("PST") => fill("", 0), Symbol("SO")=> fill("", 0), Symbol("GR") => fill("", 0), Symbol("CIN") => fill("", 0), Symbol("PMC") => fill("", 0), Symbol("MID") => fill("", 0), Symbol("RF") => fill("", 0), Symbol("COIS") => fill("", 0), Symbol("SI") => fill("", 0), Symbol("AUID") => fill("", 0), Symbol("CN") => fill("", 0), Symbol("IR") => fill("", 0), Symbol("FIR") => fill("", 0), Symbol("EIN") => fill("", 0), Symbol("UOF") => fill("", 0), Symbol("EFR") => fill("", 0) )

        dfret = data_vacant # DataFrame()
        for char0 in char
            dfret =  merge_dataframe(dfret, get_pubmed_dataframe_from_pmid_list(char0))
        end
        return(dfret)
    end

# """
# Base.@ccallable function add_char_to_pubmed_csv(char::String, csvfile::String = "", returnonlynew::Bool = false)::DataFrame
#     #char is PMID1,PMID2,PMID3....,PMIDn
#     ## get PMIDx reference information and add to CSV file
#     #if returnonlynew is true, returns added database, else, return merged one
#
# Base.@ccallable function add_char_to_pubmed_csv(char::Array{String,1}, csvfile::String, returnonlynew::Bool = false)::DataFrame
#     Array version
# """
    Base.@ccallable function add_char_to_pubmed_csv(char::String, csvfile::String = "", returnonlynew::Bool = false)::DataFrame
        data_vacant = DataFrame( Symbol("PMID") => fill("", 0), Symbol("OWN") => fill("", 0), Symbol("STAT") => fill("", 0), Symbol("DCOM") => fill("", 0), Symbol("LR") => fill("", 0), Symbol("IS") => fill("", 0), Symbol("VI") => fill("", 0), Symbol("IP") => fill("", 0), Symbol("DP") => fill("", 0), Symbol("TI") => fill("", 0), Symbol("PG") => fill("", 0), Symbol("LID") => fill("", 0), Symbol("AB") => fill("", 0), Symbol("CI") => fill("", 0), Symbol("FAU") => fill("", 0),Symbol("AU") => fill("", 0), Symbol("AD") => fill("", 0), Symbol("LA") => fill("", 0), Symbol("PT") => fill("", 0), Symbol("DEP") => fill("", 0), Symbol("PL") => fill("", 0), Symbol("TA") => fill("", 0), Symbol("JT") => fill("", 0), Symbol("JID") => fill("", 0), Symbol("RN") => fill("", 0), Symbol("SB") => fill("", 0), Symbol("MH") => fill("", 0), Symbol("OTO") => fill("", 0), Symbol("OT") => fill("", 0), Symbol("EDAT") => fill("", 0), Symbol("MHDA") => fill("", 0), Symbol("CRDT") => fill("", 0), Symbol("PHST") => fill("", 0), Symbol("AID") => fill("", 0), Symbol("PST") => fill("", 0), Symbol("SO")=> fill("", 0), Symbol("GR") => fill("", 0), Symbol("CIN") => fill("", 0), Symbol("PMC") => fill("", 0), Symbol("MID") => fill("", 0), Symbol("RF") => fill("", 0), Symbol("COIS") => fill("", 0), Symbol("SI") => fill("", 0), Symbol("AUID") => fill("", 0), Symbol("CN") => fill("", 0), Symbol("IR") => fill("", 0), Symbol("FIR") => fill("", 0), Symbol("EIN") => fill("", 0), Symbol("UOF") => fill("", 0), Symbol("EFR") => fill("", 0) )

        pmid_data_old = try
            custom_CSV_read(csvfile);
            pmid_data_old = pmid_data_old[(pmid_data_old.PMID .!= ""), :]
        catch
            data_vacant #DataFrame()
        end

        pmid_data_new = try
            get_pubmed_dataframe_from_pmid_list(char)
        catch
            data_vacant #DataFrame()
        end

        pmid_data = merge_dataframe(pmid_data_old, pmid_data_new)

        try
            CSV.write(csvfile, pmid_data)
        catch
            ;
        end

        if (returnonlynew)
            return(pmid_data_new)
        end
        return(pmid_data)
    end

    #char is PMID1,PMID2,PMID3....,PMIDn
    ## get PMIDx reference information and add to CSV file
    #if returnonlynew is true, returns added database, else, return merged one
    Base.@ccallable function add_char_to_pubmed_csv(char::Array{String,1}, csvfile::String, returnonlynew::Bool = false)::DataFrame
        data_vacant = DataFrame( Symbol("PMID") => fill("", 0), Symbol("OWN") => fill("", 0), Symbol("STAT") => fill("", 0), Symbol("DCOM") => fill("", 0), Symbol("LR") => fill("", 0), Symbol("IS") => fill("", 0), Symbol("VI") => fill("", 0), Symbol("IP") => fill("", 0), Symbol("DP") => fill("", 0), Symbol("TI") => fill("", 0), Symbol("PG") => fill("", 0), Symbol("LID") => fill("", 0), Symbol("AB") => fill("", 0), Symbol("CI") => fill("", 0), Symbol("FAU") => fill("", 0),Symbol("AU") => fill("", 0), Symbol("AD") => fill("", 0), Symbol("LA") => fill("", 0), Symbol("PT") => fill("", 0), Symbol("DEP") => fill("", 0), Symbol("PL") => fill("", 0), Symbol("TA") => fill("", 0), Symbol("JT") => fill("", 0), Symbol("JID") => fill("", 0), Symbol("RN") => fill("", 0), Symbol("SB") => fill("", 0), Symbol("MH") => fill("", 0), Symbol("OTO") => fill("", 0), Symbol("OT") => fill("", 0), Symbol("EDAT") => fill("", 0), Symbol("MHDA") => fill("", 0), Symbol("CRDT") => fill("", 0), Symbol("PHST") => fill("", 0), Symbol("AID") => fill("", 0), Symbol("PST") => fill("", 0), Symbol("SO")=> fill("", 0), Symbol("GR") => fill("", 0), Symbol("CIN") => fill("", 0), Symbol("PMC") => fill("", 0), Symbol("MID") => fill("", 0), Symbol("RF") => fill("", 0), Symbol("COIS") => fill("", 0), Symbol("SI") => fill("", 0), Symbol("AUID") => fill("", 0), Symbol("CN") => fill("", 0), Symbol("IR") => fill("", 0), Symbol("FIR") => fill("", 0), Symbol("EIN") => fill("", 0), Symbol("UOF") => fill("", 0), Symbol("EFR") => fill("", 0) )

        pmid_data_old = try
            custom_CSV_read(csvfile)
        catch
            data_vacant #DataFrame()
        end

        pmid_data_new = try
            get_pubmed_dataframe_from_pmid_list(char)
        catch
            data_vacant
        end

        pmid_data = merge_dataframe(pmid_data_old, pmid_data_new)

        try
            CSV.write(csvfile, pmid_data)
        catch
            ;
        end

        if (returnonlynew)
            return(pmid_data_new)
        end
        return(pmid_data)
    end

    Base.@ccallable function two_string_vector_combination(veca::Array{String, 1}, vecb::Array{String, 1})::Array{String, 1}
        # veca = ["DEP_"; "DP_"; ""]
        # vecb = ["Year"; "Month"; "Monthname"; "Monthabbr"; "Day"]
        vecx = fill("", 0)
        for jx in 1:length(veca)
            vecx = [vecx; (veca[jx] .* vecb)]
        end
        return(vecx)
    end

    Base.@ccallable function return_year_monthx3_day_sub_sub(charx0::String)::Array{String, 1}
        charx = replace(charx0, "\n"=>" ");
        charx = replace(charx, r" +"=>" ");
        charx = replace(charx, ", "=>" ");
        charx = replace(charx, r"^ "=>"");
        charx = replace(charx, r" $"=>"");

        char2 = String.(split(charx, " "));
        if (length(char2) < 3)
            return(fill("", 5))
        end

        date_in = try
            Dates.Date(charx, "yyyy u dd");
        catch
            try
                Dates.Date(charx, "yyyy U dd");
            catch
                return(fill("", 5))
            end
        end

        if (typeof(date_in) == Nothing)
            return(fill("", 5))
        end
        return(
        [string(year(date_in)); string(month(date_in)); monthname(date_in); monthabbr(date_in); string(day(date_in)); ]
        )
    end

    Base.@ccallable function return_year_monthx3_day_sub(charx::String)::Array{String, 1}
        charx = replace(charx, "\n"=>" ");
        charx = replace(charx, r" +"=>" ");
        charx = replace(charx, ", "=>" ");
        charx = replace(charx, r"^ "=>"");
        charx = replace(charx, r" $"=>"");

        retval = return_year_monthx3_day_sub_sub(charx)

        if (fill("", 5) != retval)
            return(retval)
        end

        char2 = String.(split(charx, " "));
        retval = fill("", 5)

        if (length(char2) < 3)
            char2 = [char2; fill("", (3 - length(char2)))]
        end

        x = try
            StringToIntpart(char2[1]); #parse(Int64, char2[1])
        catch
            0
        end

        if (typeof(x) != Int64)
            x = 0
        end

        if ((year(today()) < x) || (x < 1900))
            return(fill("", 5))
        end
        retval[1] = string(x)

        allmonth = [monthname.(1:12); monthabbr.(1:12)]

        y = findfirst(allmonth .== char2[2])
        if (typeof(y) == Nothing)
            return(retval)
        end

        y = mod(y-1, 12) + 1
        retval[2] = string(y)
        retval[3] = monthname(y)
        retval[4] = monthabbr(y)

        return(retval)
    end

    # charx = "2015 Janz"
    # return_year_monthx3_day_sub(charx)

    #return [year; month; monthname; monthabbr; day; ]
    Base.@ccallable function return_year_monthx3_day(charx::String)::Array{String, 1}
        if (charx == "")
            return(fill("", 5))
        end

        try
            (date_in = Dates.Date(charx, "yyyymmdd"));
            return(
            [string(year(date_in)); string(month(date_in)); monthname(date_in); monthabbr(date_in); string(day(date_in)); ]
            )
        catch
            return(return_year_monthx3_day_sub(charx))
        end
    end

    Base.@ccallable function vector_of_vector_to_array(vec::Array{Array{String,1},1})::Array{Union{String,Missing},2}
        nrow = length(vec)
        leng = fill(0, nrow)
        for ix in 1:nrow
            leng[ix] = length(vec[ix])
        end
        ncol = maximum(leng)

        myarray = Array{Union{String,Missing},2}(missing, nrow, ncol)
        for ix in 1:nrow
            myarray[ix, 1:length(vec[ix])] = vec[ix]
        end
        return(myarray)
    end

    # Base.@ccallable function vector_of_vector_to_array(vec)
    #     mytype = typeof(vec[1][1])
    #     nrow = length(vec)
    #     leng = fill(0, nrow)
    #     for ix in 1:nrow
    #         leng[ix] = length(vec[ix])
    #     end
    #     ncol = maximum(leng)
    #
    #     myarray = Array{Union{mytype,Missing},2}(missing, nrow, ncol)
    #     for ix in 1:nrow
    #         myarray[ix, 1:length(vec[ix])] = vec[ix]
    #     end
    #     return(myarray)
    # end

    # """
    # return splitted String (String Format) of String
    # return( String.(split(char, sep)) )
    # """
    Base.@ccallable function mysplit(char::String, sep::String)::Array{String, 1}
        return( String.(split(char, sep)) )
    end

    #return splitted Vector{String} (String Format) by char
    Base.@ccallable function mysplit(char::Array{String, 1}, sep::String)::Array{Array{String,1},1}
        vecx = split.(char, sep)
        ret = fill(fill("",2), length(vecx))

        for ix in 1:length(char)
            ret[ix] = String.(vecx[ix])
        end
        return( ret )
    end

    #First split Strng by sep1 then split substring by sep2...
    Base.@ccallable function doublesplit(char::String, sep1::String, sep2::String)::Array{Array{String,1},1}
        vecx = mysplit(char, sep1)
        # return(typeof(vecx))
        ret = fill(fill("",2), length(vecx))

        for ix in 1:length(vecx)
            ret[ix] = mysplit(vecx[ix], sep2)
        end
        return( ret )
    end

    #First split Array{String, 1} by sep1 then split substring by sep2...
    Base.@ccallable function doublesplit(char::Array{String, 1}, sep1::String, sep2::String)::Array{Array{Array{String,1},1},1}
        # sep2num = maximum(length.(mysplit.(char, sep2)))
        # sep1num = maximum(length.(mysplit.(char, sep1)))
        ret = fill(fill(fill("",2),2), length(char))
        for jx in 1:length(char)
            ret[jx] = doublesplit(char[jx], sep1, sep2)
        end
        return( ret )
    end
    # mysplit("a b n", " ")
    # mysplit(["a b n"; "e r t t"; "gfgf hg"], " ")
    #length( fill(fill("",1000), 12) )
    # charx="19781203"
    # #charx="1978 Dec"
    # return_year_monthx3_day(charx)
    # charx = "2015 Jan 4"
    # return_year_monthx3_day(charx)

# """
# Base.@ccallable function add_name_list_and_data_to_pmid_data(pmid_data0::DataFrame)::DataFrame
#     add author name list and DEP&DP year, month, day
# """
    Base.@ccallable function add_name_list_and_data_to_pmid_data(pmid_data0::DataFrame)::DataFrame
        pmid_data = copy(pmid_data0)
        ##subroutine
        reg=r"[#\\$%&_{}<>^|~\\\\]<>"
        for jx in (names(pmid_data))
            vecx = (pmid_data[jx])
            for ix in 1:length(vecx)
                (pmid_data[jx])[ix] = replace((pmid_data[jx])[ix], reg=>"")
            end
        end

        rownum = size(pmid_data,1)

        veca = ["DEP_"; "DP_"; ""]
        vecb = ["Year"; "Month"; "Monthname"; "Monthabbr"; "Day"]
        vecx = two_string_vector_combination(veca, vecb)

        symb = Symbol.(vecx)

        arr1 = vector_of_vector_to_array(return_year_monthx3_day.(pmid_data.DEP))
        arr2 = vector_of_vector_to_array(return_year_monthx3_day.(pmid_data.DP))

        arr3 = vector_of_vector_to_array(return_year_monthx3_day.(pmid_data.DEP))
        arr4 = vector_of_vector_to_array(return_year_monthx3_day.(pmid_data.DP))

        arr = [arr1 arr2 arr4]
        #arr[12,:]

        for ix in 1:size(arr3, 1)
            if (arr3[ix, 1] == "")
                arr3[ix, 1:5] = arr2[ix, 1:5]
            end
            if (arr4[ix, 1] == "")
                arr4[ix, 1:5] = arr1[ix, 1:5]
            end
        end
        # arr3: DEP優先
        # arr4: DP優先  Use this

        for ix in 1:15
            pmid_data[symb[ix]] = arr[:,ix]
        end

        allfau = doublesplit(pmid_data.FAU, "\n", ",")

        # allfau[67]
        # pmid_data[67:67,:]
        # size(pmid_data,1)

        pmid_data.Firstname_Full = fill("",rownum)
        pmid_data.Firstname_0 = fill("",rownum)
        pmid_data.Firstname_1 = fill("",rownum)
        pmid_data.Firstname_2 = fill("",rownum)
        pmid_data.Firstname_3 = fill("",rownum)
        pmid_data.Firstname_4 = fill("",rownum)
        pmid_data.Firstname_5 = fill("",rownum)
        pmid_data.Lastname = fill("",rownum)

        for kx in 1:rownum
            firstname = fill("", length(allfau[kx]))
            fs0 = fill("", length(allfau[kx]))
            fs1 = fill("", length(allfau[kx]))
            fs2 = fill("", length(allfau[kx]))
            fs3 = fill("", length(allfau[kx]))
            fs4 = fill("", length(allfau[kx]))
            fs5 = fill("", length(allfau[kx]))
            lastname = fill("", length(allfau[kx]))

            for jx in 1:length(allfau[kx])
                firstname[jx] = try
                    allfau[kx][jx][2]
                catch
                    ""
                end
                firstname[jx] = replace(firstname[jx], r" +"=>" ")
                firstname[jx] = replace(firstname[jx], r"^ "=>"")
                firstname[jx] = replace(firstname[jx], r" $"=>"")

                fs0[jx] = replace(firstname[jx], r"[^A-Z-]"=>"") #A-B
                fs1[jx] = replace(fs0[jx], "-"=>"") #AB

                fs2[jx] = replace(fs0[jx], r"[A-Z]"=>function(char) return(char * " "); end)
                fs2[jx] = replace(fs2[jx], " -"=>"-")
                fs2[jx] = replace(fs2[jx], r" +$"=>"") #A Z-H

                fs3[jx] = replace(fs1[jx], r"[A-Z]"=>function(char) return(char * " "); end)
                fs3[jx] = replace(fs3[jx], r" +$"=>"") #A Z H

                fs4[jx] = replace(fs0[jx], r"[A-Z]"=>function(char) return(char * ". "); end)
                fs4[jx] = replace(fs4[jx], r" +$"=>"") #A. Z. -H.

                fs5[jx] = replace(fs1[jx], r"[A-Z]"=>function(char) return(char * ". "); end)
                fs5[jx] = replace(fs5[jx], r" +$"=>"") #A. Z. H.

                lastname[jx] = replace(allfau[kx][jx][1], r" +"=>" ")
                lastname[jx] = replace(lastname[jx], r"^ "=>"")
                lastname[jx] = replace(lastname[jx], r" $"=>"")
            end

            pmid_data.Firstname_Full[kx] = join(firstname , "\n")
            pmid_data.Firstname_0[kx] = join(fs0 , "\n")
            pmid_data.Firstname_1[kx] = join(fs1 , "\n")
            pmid_data.Firstname_2[kx] = join(fs2 , "\n")
            pmid_data.Firstname_3[kx] = join(fs3 , "\n")
            pmid_data.Firstname_4[kx] = join(fs4 , "\n")
            pmid_data.Firstname_5[kx] = join(fs5 , "\n")
            pmid_data.Lastname[kx] = join(lastname , "\n")
        end

        return(pmid_data)
    end

# """
#     #authorstyle = ["#Lastname"; " "; "#Firstname_1"]
#     #Lastname and #Firstname_XXX are replaced with variables
#     Firstname_Full is full version
#     Firstname_0 is A-B style
#     Firstname_1 is AB
#     Firstname_2 is A Z-H
#     Firstname_3 is A Z H
#     Firstname_4 is A. Z. -H.
#     Firstname_5 is A. Z. H
#
#     #get author list for eachrow
# """
    Base.@ccallable function get_each_author(pmid_data0::DataFrame, authorstyle::Array{String, 1} = ["#Lastname"; " "; "#Firstname_1"])::Array{String, 1}
        pmid_data = copy(pmid_data0)
        rownum = size(pmid_data, 1)
        ret = fill("", rownum)

        for kx in 1:rownum
            vecvec = fill(fill("",1), length(authorstyle))
            boox = fill(true, length(authorstyle))

            for jx in 1:length(authorstyle)
                if (occursin(r"^ *#", authorstyle[jx]))
                    vecvec[jx] = String.(split( (pmid_data[kx:kx, :])[Symbol(replace(authorstyle[jx], r"^ *#"=>""))][1], "\n") )
                    boox[jx] = false
                end
            end

            num = length.(vecvec)
            maxnum = maximum(num)
            for jx in 1:length(authorstyle)
                if (boox[jx])
                    vecvec[jx] = fill(authorstyle[jx], maxnum)
                end
            end

            char = fill("", maxnum)
            for jx in 1:length(authorstyle)
                char = char .* vecvec[jx]
            end

            ret[kx]= join(char, "\n")
        end
        return(ret)
    end

# """
#     ##get author List
#     #max_all #max author number expressed without et al
#     #etal_num #author number written when et al is used
#     #authorstyle: authorstyle
#     #sep_vec
#     #[1] #sep between authors
#     #[2] #sep between last two authors
#     #[3] #sep between only TWO auhtors
#     #[4] #sep between author and other part
#     #[5] #et. al
# """
    Base.@ccallable function get_author_list(pmid_data0::DataFrame, max_all::Int64 = 1, etal_num::Int64 = 1,
        authorstyle::Array{String, 1} = ["#Lastname"; " "; "#Firstname_1"],
        sep_vec::Array{String, 1} = [", "; ", and"; " and "; ". "; ", et. al. "])::Array{String, 1}

        pmid_data = copy(pmid_data0)
        sepauthor = sep_vec[1]
        seplast2 = sep_vec[2]
        sep2 = sep_vec[3]
        sepafter = sep_vec[4]
        etal = sep_vec[5]

        #########Base.@ccallable function(pmid_data)
        author_list = get_each_author(pmid_data, authorstyle)

        max_all2 = max_all
        etal_num2 = min(etal_num, max_all2)

        if (max_all2 <= 1)
            for ix in 1:length(author_list)
                vec2 = String.(split(author_list[ix], "\n"))

                if (length(vec2) == 1)
                    author_list[ix] = author_list[ix] * sepafter
                else
                    author_list[ix] = vec2[1] * etal
                end
            end
            return(author_list);
        end

        if (max_all2 == 2)
            for ix in 1:length(author_list)
                vec2 = String.(split(author_list[ix], "\n"))

                if (length(vec2) == 1)
                    author_list[ix] = author_list[ix] * sepafter
                elseif (length(vec2) == 2)
                    author_list[ix] = join(vec2, sep2) * sepafter
                else
                    author_list[ix] = join(vec2[1:2], sepauthor) * etal
                end
            end
            return(author_list);
        end

        for ix in 1:length(author_list)
            vec2 = String.(split(author_list[ix], "\n"))

            if (length(vec2) == 1)
                author_list[ix] = author_list[ix] * sepafter
            elseif (length(vec2) == 2)
                author_list[ix] = join(vec2, sep2) * sepafter
            elseif (length(vec2) <= max_all2)
                author_list[ix] = join(vec2[1:(length(vec2)-1)], sepauthor) * seplast2 * vec2[length(vec2)] * sepafter
            else
                author_list[ix] = join(vec2[1:etal_num2], sepauthor) * etal
            end

            # char = author_list[ix]
            # loc = findlast("\n", char)
            # char = SubString( char, 1:( minimum(loc)-1 ) ) * seplast2 * SubString( char, ( maximum(loc)+1 ):lastindex(char) )
            # author_list[ix] = replace(char, "\n"=>sepauthor)
        end
        return(author_list);
        ######end of Base.@ccallable function
    end

    # """
    #     ##make a reference style
    #     #max_all #max author number expressed without et al
    #     #etal_num #author number written when et al is used
    #     #authorstyle: authorstyle
    #
    #     #sep_vec
    #     #[1] #sep between authors
    #     #[2] #sep between last two authors
    #     #[3] #sep between only TWO auhtors
    #     #[4] #sep between author and other part
    #     #[5] #et. al
    #
    #     #refstyle
    #     "%author_list" is replaced with author list Array{String}
    #     #TI etc is MEDLINE item
    # """
    Base.@ccallable function add_single_reference_string(pmid_data0::DataFrame, refname::String, max_all::Int64 = 1, etal_num::Int64 = 1,
        authorstyle::Array{String, 1} = ["#Lastname"; " "; "#Firstname_1"],
        sep_vec::Array{String, 1} = [", "; ", and"; " and "; ". "; ", et. al. "],
        refstyle = ["%author_list"; "#TI"; " "; "#TA"; " "; "#Year"; " <b>"; "#VI"; "</b>: "; "#PG"; ". "]
        )::DataFrame
        pmid_data = copy(pmid_data0)
        author_list = get_author_list(pmid_data, max_all, etal_num, authorstyle, sep_vec)

        rownum = size(pmid_data, 1)
        ret = fill("", rownum)

        vecvec = fill(fill("", rownum), length(refstyle))

        for jx in 1:length(refstyle)
            if (refstyle[jx] == "%author_list")
                vecvec[jx] = author_list
            elseif (occursin(r"^ *#", refstyle[jx]))
                vecvec[jx] = pmid_data[Symbol(replace(refstyle[jx], r"^ *#"=>""))]
            else
                reg=r"[#\\$%&_{}<>^|~\\\\]<>"
                charx = replace(refstyle[jx], reg=>"")
                vecvec[jx] = fill(charx, rownum)
            end
            ret = ret .* vecvec[jx]
        end

        ret2 = copy(ret)

        for ix in 1:length(ret2)
            ret2[ix] = replace(ret2[ix], "<b>"=>"\\textbf{")
            ret2[ix] = replace(ret2[ix], "<i>"=>"\\textit{")
            ret2[ix] = replace(ret2[ix], "</b>"=>"}")
            ret2[ix] = replace(ret2[ix], "</i>"=>"}")
        end

        if (refname != "")
            refname3 = Symbol("Ref_" * refname * "_html")
            refname4 = Symbol("Ref_" * refname * "_tex")

            if (all(refname3 .!= names(pmid_data)))
                pmid_data[refname3] = ret
            end
            if (all(refname4 .!= names(pmid_data)))
                pmid_data[refname4] = ret2
            end
        end
        # val = fill(fill("",1),2)
        # val[1] = ret
        # val[2] = ret2
        return(pmid_data)
    end
# """
# Base.@ccallable function add_several_reference_string(pmid_data0::DataFrame)::DataFrame
# add several reference stylesheet to  pmid_data0
# """
    Base.@ccallable function add_several_reference_string(pmid_data0::DataFrame)::DataFrame
        pmid_data = copy(pmid_data0)
        max_all = 1 #max author number expressed without et al
        etal_num = 1 #author number written when et al is used

        authorstyle = ["#Lastname"; " "; "#Firstname_1"] #style of author
        sep_vec = [", "; ", and "; " and "; ". "; ", et. al. "]

        refstyle = ["%author_list"; "#TA"; " "; "#Year"; " <b>"; "#VI"; "</b>: "; "#PG"; ". "]

        #get reference string
        pmid_data = add_single_reference_string(pmid_data, "0", max_all, etal_num, authorstyle, sep_vec, refstyle)

        max_all = 6
        etal_num = 3

        authorstyle = ["#Lastname"; " "; "#Firstname_1"] #style of author
        sep_vec = [", "; ", and "; " and "; ". "; ", et. al. "]

        refstyle = ["%author_list"; "#TI"; " "; "#TA"; " "; "#Year"; " <b>"; "#VI"; "</b>: "; "#PG"; ". "]

        pmid_data = add_single_reference_string(pmid_data, "Lancet", max_all, etal_num, authorstyle, sep_vec, refstyle)

        return(pmid_data)
    end

# """
# Base.@ccallable function add_PMID_to_list_and_write_to_csv(char0::String, pmid_list_file_abs::String, pubmed_data_generated_file_abs::String)::DataFrame
# """
    Base.@ccallable function add_PMID_to_list_and_write_to_csv(char0::String, pmid_list_file_abs::String, pubmed_data_generated_file_abs::String)::DataFrame
        char = replace(char0, " "=>"")
        char = replace(char, "\t"=>"")
        char = replace(char, "\n"=>",")
        char = replace(char, r",+"=>",")

        char = replace(char, "?term="=>"")
        char = replace(char, r"https://www-ncbi-nlm-nih-gov.*/pubmed/"=>"")
        char = replace(char, "https://www.ncbi.nlm.nih.gov/pubmed/"=>"")

        if (occursin(r"[^0-9,]", char))
            return(DataFrame( Symbol("PMID") => fill("", 0), Symbol("OWN") => fill("", 0), Symbol("STAT") => fill("", 0), Symbol("DCOM") => fill("", 0), Symbol("LR") => fill("", 0), Symbol("IS") => fill("", 0), Symbol("VI") => fill("", 0), Symbol("IP") => fill("", 0), Symbol("DP") => fill("", 0), Symbol("TI") => fill("", 0), Symbol("PG") => fill("", 0), Symbol("LID") => fill("", 0), Symbol("AB") => fill("", 0), Symbol("CI") => fill("", 0), Symbol("FAU") => fill("", 0),Symbol("AU") => fill("", 0), Symbol("AD") => fill("", 0), Symbol("LA") => fill("", 0), Symbol("PT") => fill("", 0), Symbol("DEP") => fill("", 0), Symbol("PL") => fill("", 0), Symbol("TA") => fill("", 0), Symbol("JT") => fill("", 0), Symbol("JID") => fill("", 0), Symbol("RN") => fill("", 0), Symbol("SB") => fill("", 0), Symbol("MH") => fill("", 0), Symbol("OTO") => fill("", 0), Symbol("OT") => fill("", 0), Symbol("EDAT") => fill("", 0), Symbol("MHDA") => fill("", 0), Symbol("CRDT") => fill("", 0), Symbol("PHST") => fill("", 0), Symbol("AID") => fill("", 0), Symbol("PST") => fill("", 0), Symbol("SO")=> fill("", 0), Symbol("GR") => fill("", 0), Symbol("CIN") => fill("", 0), Symbol("PMC") => fill("", 0), Symbol("MID") => fill("", 0), Symbol("RF") => fill("", 0), Symbol("COIS") => fill("", 0), Symbol("SI") => fill("", 0), Symbol("AUID") => fill("", 0), Symbol("CN") => fill("", 0), Symbol("IR") => fill("", 0), Symbol("FIR") => fill("", 0), Symbol("EIN") => fill("", 0), Symbol("UOF") => fill("", 0), Symbol("EFR") => fill("", 0) ))
        end

        text = String.(split(char, ","));

        text_old = try
                    String.(split(read(pmid_list_file_abs, String), ","));
                catch
                    fill("",0)
        end

        text_all = unique([text_old; select_new(text, text_old)])
        #text = String.(split(read(bibfile_abs, String), "\n"));

        char2 = join(text_all, ",")

        try
            write(pmid_list_file_abs, char2);
        catch
            ;
        end

        pmid_data = add_char_to_pubmed_csv(char, pubmed_data_generated_file_abs, true)
        pmid_data = add_name_list_and_data_to_pmid_data(pmid_data)
        pmid_data = add_several_reference_string(pmid_data)
        return(copy(pmid_data))
    end

    Base.@ccallable function add_PMID_to_list_and_write_to_csv(char0::Array{String, 1}, pmid_list_file_abs::String, pubmed_data_generated_file_abs::String)::DataFrame
        subdata = DataFrame( Symbol("PMID") => fill("", 0), Symbol("OWN") => fill("", 0), Symbol("STAT") => fill("", 0), Symbol("DCOM") => fill("", 0), Symbol("LR") => fill("", 0), Symbol("IS") => fill("", 0), Symbol("VI") => fill("", 0), Symbol("IP") => fill("", 0), Symbol("DP") => fill("", 0), Symbol("TI") => fill("", 0), Symbol("PG") => fill("", 0), Symbol("LID") => fill("", 0), Symbol("AB") => fill("", 0), Symbol("CI") => fill("", 0), Symbol("FAU") => fill("", 0),Symbol("AU") => fill("", 0), Symbol("AD") => fill("", 0), Symbol("LA") => fill("", 0), Symbol("PT") => fill("", 0), Symbol("DEP") => fill("", 0), Symbol("PL") => fill("", 0), Symbol("TA") => fill("", 0), Symbol("JT") => fill("", 0), Symbol("JID") => fill("", 0), Symbol("RN") => fill("", 0), Symbol("SB") => fill("", 0), Symbol("MH") => fill("", 0), Symbol("OTO") => fill("", 0), Symbol("OT") => fill("", 0), Symbol("EDAT") => fill("", 0), Symbol("MHDA") => fill("", 0), Symbol("CRDT") => fill("", 0), Symbol("PHST") => fill("", 0), Symbol("AID") => fill("", 0), Symbol("PST") => fill("", 0), Symbol("SO")=> fill("", 0), Symbol("GR") => fill("", 0), Symbol("CIN") => fill("", 0), Symbol("PMC") => fill("", 0), Symbol("MID") => fill("", 0), Symbol("RF") => fill("", 0), Symbol("COIS") => fill("", 0), Symbol("SI") => fill("", 0), Symbol("AUID") => fill("", 0), Symbol("CN") => fill("", 0), Symbol("IR") => fill("", 0), Symbol("FIR") => fill("", 0), Symbol("EIN") => fill("", 0), Symbol("UOF") => fill("", 0), Symbol("EFR") => fill("", 0) )

        for ix in char0
            subdata = merge_dataframe(subdata, add_PMID_to_list_and_write_to_csv(ix, pmid_list_file_abs, pubmed_data_generated_file_abs))
        end
        return(copy(subdata))
    end

    Base.@ccallable function get_pmid_from_URL(char::String)::String
        char = replace(char, "?term="=>"")
        char = replace(char, r"https://www-ncbi-nlm-nih-gov.*/pubmed/"=>"")
        char = replace(char, "https://www.ncbi.nlm.nih.gov/pubmed/"=>"")
        return(char)
    end #of Base.@ccallable function

    #write html file and return String
    Base.@ccallable function write_html(htmltitle::String, htmlbody::String, outputfile::String = "", iopen::Bool = false)::String
        char1 = "<!DOCTYPE html>  <html lang=\"ja\"> \n<head> <meta charset=\"UTF-8\">\n<title> " * htmltitle * " </title>\n<style> </style>\n</head>\n<body>\n" * htmlbody * "\n</body>\n</html>"

        try
            write(outputfile, char1)
        catch
            ;
        end
        if (iopen)
            try
                run(`explorer $outputfile`)
            catch
                ;
            end
        end
        return(char1)
    end

    Base.@ccallable function char_from_0_to_9XX(x::Int64)::Char
        return(Char(0x30+x))
    end

    Base.@ccallable function Int64ToString(x::Int64)::String
        if (x < 0)
            char0 = "-"
            x2 = -x
        else
            char0 = ""
            x2 = x
        end
        return( char0 * join( char_from_0_to_9XX.(reverse(digits(x2))) ) )
    end

# """
# Base.@ccallable function make_html_from_dataframe(pmid_data0::DataFrame, writehtml::Bool = true)::String
#
# make html file of reference and show it from dataframe
# """
    Base.@ccallable function make_html_from_dataframe(pmid_data0::DataFrame, writehtml::Bool = true)::String
        pmid_data= copy(pmid_data0)
        # pmid_data.FAU

        boo1 = fill(false, length(names(pmid_data)))
        boo2 = fill(false, length(names(pmid_data)))
        for ix in 1:length(names(pmid_data))
            boo1[ix] = occursin(r"Ref_.*_html", String.(names(pmid_data))[ix])
            boo2[ix] = occursin("PMID", String.(names(pmid_data))[ix])
        end

        boo = boo1.|boo2
        boo1x = boo1[boo]
        boo2x = boo2[boo]

        #sum(Int64.(boo))
        pmid_data = pmid_data[boo]
        newname = String.(names(pmid_data))
        newname2 = fill("", length(newname))

        for ix in 1:length(newname)
            newname[ix] = replace(newname[ix], "_html" => ")")
            newname[ix] = replace(newname[ix], "Ref_" => "Ref (type ")
        end

        newname[boo1x] = "\n<h2>" .* newname[boo1x] .* "</h2>\n"
        newname[boo2x] = "\n<h1> " .* newname[boo2x] .* ": "
        newname2[boo2x] .= "</h1>\n"

        output = fill("", (size(pmid_data, 1)))

        for ix in 1:size(pmid_data, 1)
            tempx = fill("", (size(pmid_data, 2)))
            for jx in 1:size(pmid_data, 2)
                tempx[jx] = newname[jx] * (pmid_data[ix, jx]) * newname2[jx]
            end
            output[ix] = join(tempx)*"\n"
        end
        ret = join(output)

        if (writehtml)
            write_html("Reference List", ret, "test.html", true)
        end
        return(ret)
        #write_html("test", ret, "test.html", true)
    end #of Base.@ccallable function

    # """
    # Base.@ccallable function make_html_from_pmid(char1::String, writehtml::Bool = true)::String
    # Base.@ccallable function make_html_from_pmid(charx::Array{String,1}, writehtml::Bool = true)::Strin
    # Base.@ccallable function make_html_from_pmid(int::Int64, writehtml::Bool = true)::String
    # ase.@ccallable Base.@ccallable function make_html_from_pmid(int::Array{Int64,1}, writehtml::Bool = true)::String
    #
    # make html filefrom PMID and show it from dataframe
    # """
    Base.@ccallable function make_html_from_pmid(char1::String, writehtml::Bool = true)::String
        char = String.(split(char1, ","))
        charx = get_pmid_from_URL.(char)
        #charx = get_pmid_from_URL(char1)

        pmid_list_file_abs = "PMID_list_from_Julia.txt"
        pubmed_data_generated_file_abs = ""
        #for known pmid_data
        newdata= add_PMID_to_list_and_write_to_csv(charx, pmid_list_file_abs, pubmed_data_generated_file_abs)
        # newdata.FAU

        ret = make_html_from_dataframe(newdata, writehtml)
        return(ret)
        #write_html("test", ret, "test.html", true)
    end #of Base.@ccallable function

    Base.@ccallable function make_html_from_pmid(charx::Array{String,1}, writehtml::Bool = true)::String
        ret = ""
        for ix in charx
            ret = ret * make_html_from_pmid(ix, false) * "\n\n"
        end
        if (writehtml)
            write_html("Reference List", ret, "test.html", true)
        end
        return(ret)
    end

    Base.@ccallable function make_html_from_pmid(int::Int64, writehtml::Bool = true)::String
        charx = Int64ToString(int)
        ret = make_html_from_pmid(charx, writehtml)
        return( ret )
    end

    Base.@ccallable function make_html_from_pmid(int::Array{Int64,1}, writehtml::Bool = true)::String
        charx = Int64ToString.(int)
        ret = make_html_from_pmid(charx, writehtml)
        return( ret )
    end

# """
# Base.@ccallable function make_html_from_csv(csvfile::String, writehtml::Bool = true)::String
# make html file from generated CSV and show it from dataframe
# """
    Base.@ccallable function make_html_from_csv(csvfile::String, writehtml::Bool = true)::String
        try
            pmid_data = custom_CSV_read(csvfile)
            ret = make_html_from_dataframe(pmid_data, writehtml)
            return(ret)
        catch
            return("")
        end
    end #of Base.@ccallable function

# """
# Base.@ccallable function obtain_medline_from_readcube_bib(bibfile_abs::String, pmid_list_file_abs::String, pubmed_data_processed_file_abs::String)::DataFrame
#
#     # obtain and record medline dataframe from internet
#     # pmid_list_file_abs is list of pmid_data
#     # pubmed_data_processed_file_abs is csv file
# """
    Base.@ccallable function obtain_medline_from_readcube_bib(bibfile_abs::String, pmid_list_file_abs::String, pubmed_data_processed_file_abs::String)::DataFrame
        unitnum = 150;
        #obtain PMID strings joined by "," as char
        char = convert_readcube_bib(bibfile_abs, pmid_list_file_abs, unitnum, true)
        pmid_data = add_char_to_pubmed_csv(char, "")

        pmid_data = add_name_list_and_data_to_pmid_data(pmid_data)
        pmid_data = add_several_reference_string(pmid_data)

        try
            CSV.write(pubmed_data_processed_file_abs, pmid_data)
        catch
            throw("file not found: ")
        end
        return(pmid_data)
    end #of Base.@ccallable function

end  # module


"""
    PolyharmonicSpline <: Kernel

TODO
"""
struct PolyharmonicSpline <: Kernel
    # TODO
end


abstract InferenceAlgorithm{P}
abstract Sampler{T<:InferenceAlgorithm}

## Fallback functions

Base.run(spl :: Sampler) = error("[sample]: unmanaged inference algorithm: $(typeof(spl))")

assume(spl, distr :: Distribution) =
  error("[assume]: unmanaged inference algorithm: $(typeof(spl))")

observe(spl, weight :: Float64) =
  error("[observe]: unmanaged inference algorithm: $(typeof(spl))")

predict(spl, var_name :: Symbol, value) =
  error("[predict]: unmanaged inference algorithm: $(typeof(spl))")

## Default functions
function sample(model::Function, alg :: InferenceAlgorithm)
  global sampler = ParticleSampler{typeof(alg)}(model, alg);
  Base.run(sampler)
end

assume(spl :: Sampler, distr :: Distribution)  = rand(current_trace(), distr)
observe(spl :: Sampler, score :: Float64)      = produce(score)

function predict(spl :: Sampler, v_name :: Symbol, value)
  task = current_task()
  if haskey(task.storage, :turing_predicts)
    predicts = task.storage[:turing_predicts]
  else
    predicts = Dict{Symbol,Any}()
  end
  predicts[v_name] = value
  task.storage[:turing_predicts] = predicts
end


function AngularCoordinates()
    return AngularCoordinates(())
end

function AngularCoordinates(arg0::FieldRotation)
    return AngularCoordinates((FieldRotation,), arg0)
end

function AngularCoordinates(arg0::PVCoordinates, arg1::PVCoordinates)
    return AngularCoordinates((PVCoordinates, PVCoordinates), arg0, arg1)
end

function AngularCoordinates(arg0::PVCoordinates, arg1::PVCoordinates, arg2::PVCoordinates, arg3::PVCoordinates, arg4::jdouble)
    return AngularCoordinates((PVCoordinates, PVCoordinates, PVCoordinates, PVCoordinates, jdouble), arg0, arg1, arg2, arg3, arg4)
end

function AngularCoordinates(arg0::Rotation, arg1::Vector3D)
    return AngularCoordinates((Rotation, Vector3D), arg0, arg1)
end

function AngularCoordinates(arg0::Rotation, arg1::Vector3D, arg2::Vector3D)
    return AngularCoordinates((Rotation, Vector3D, Vector3D), arg0, arg1, arg2)
end

function add_offset(obj::AngularCoordinates, arg0::AngularCoordinates)
    return jcall(obj, "addOffset", AngularCoordinates, (AngularCoordinates,), arg0)
end

function apply_to(obj::AngularCoordinates, arg0::FieldPVCoordinates)
    return jcall(obj, "applyTo", FieldPVCoordinates, (FieldPVCoordinates,), arg0)
end

function apply_to(obj::AngularCoordinates, arg0::PVCoordinates)
    return jcall(obj, "applyTo", PVCoordinates, (PVCoordinates,), arg0)
end

function apply_to(obj::AngularCoordinates, arg0::TimeStampedFieldPVCoordinates)
    return jcall(obj, "applyTo", TimeStampedFieldPVCoordinates, (TimeStampedFieldPVCoordinates,), arg0)
end

function apply_to(obj::AngularCoordinates, arg0::TimeStampedPVCoordinates)
    return jcall(obj, "applyTo", TimeStampedPVCoordinates, (TimeStampedPVCoordinates,), arg0)
end

function create_from_modified_rodrigues(::Type{AngularCoordinates}, arg0::Vector{Vector{jdouble}})
    return jcall(AngularCoordinates, "createFromModifiedRodrigues", AngularCoordinates, (Vector{Vector{jdouble}},), arg0)
end

function estimate_rate(::Type{AngularCoordinates}, arg0::Rotation, arg1::Rotation, arg2::jdouble)
    return jcall(AngularCoordinates, "estimateRate", Vector3D, (Rotation, Rotation, jdouble), arg0, arg1, arg2)
end

function get_modified_rodrigues(obj::AngularCoordinates, arg0::jdouble)
    return jcall(obj, "getModifiedRodrigues", Vector{Vector{jdouble}}, (jdouble,), arg0)
end

function get_rotation(obj::AngularCoordinates)
    return jcall(obj, "getRotation", Rotation, ())
end

function get_rotation_acceleration(obj::AngularCoordinates)
    return jcall(obj, "getRotationAcceleration", Vector3D, ())
end

function get_rotation_rate(obj::AngularCoordinates)
    return jcall(obj, "getRotationRate", Vector3D, ())
end

function revert(obj::AngularCoordinates)
    return jcall(obj, "revert", AngularCoordinates, ())
end

function shifted_by(obj::AngularCoordinates, arg0::jdouble)
    return jcall(obj, "shiftedBy", AngularCoordinates, (jdouble,), arg0)
end

function subtract_offset(obj::AngularCoordinates, arg0::AngularCoordinates)
    return jcall(obj, "subtractOffset", AngularCoordinates, (AngularCoordinates,), arg0)
end

function to_derivative_structure_rotation(obj::AngularCoordinates, arg0::jint)
    return jcall(obj, "toDerivativeStructureRotation", FieldRotation, (jint,), arg0)
end

function to_univariate_derivative1_rotation(obj::AngularCoordinates)
    return jcall(obj, "toUnivariateDerivative1Rotation", FieldRotation, ())
end