text:
using Test
using PDFIO
using PDFIO.PD
using PDFIO.Cos
using PDFIO.Common
using ZipFile
using AbstractTrees

# Internal methods for testing only
using PDFIO.Cos: parse_indirect_ref, decode_ascii85, CosXString, parse_value
using PDFIO.PD: openssl_error
using PDFIO.Common: read_pkcs12

include("debugIO.jl")

pdftest_ver  = "0.0.7"
pdftest_link = "https://github.com/sambitdash/PDFTest/archive/v"*pdftest_ver

zipfile = "pdftest-"*pdftest_ver
pdftest_link *= ".zip"
zipfile *= ".zip"
pdftest_dir="PDFTest-"*pdftest_ver*"/"

if !isdir(pdftest_dir)
    isfile(zipfile) || download(pdftest_link, zipfile)
    r = ZipFile.Reader(zipfile)
    buf = Vector{UInt8}(undef, 64*1024)
    for f in r.files
        println("Filename: $(f.name)")
        if f.method == ZipFile.Store
            isdir(f.name) || mkdir(f.name)
        elseif f.method == ZipFile.Deflate
            isfile(f.name) ||
                write(f.name, read(f, Vector{UInt8}(undef, f.uncompressedsize)))
        end
    end
    close(r)
end

for (root, dirs, files) in walkdir(joinpath(@__DIR__, pdftest_dir, "fonts"))
    dest = joinpath(@__DIR__, "..", "data", "fonts")
    for file in files
        isfile(joinpath(dest, file)) && continue
        println("Copying $file to $dest")
        cp(joinpath(root, file), joinpath(dest, file), force=true)
    end
end

function testfiles(filename)
    name, ext = splitext(filename)
    return (name*".res", joinpath(@__DIR__, pdftest_dir, "templates", name*".txt"))
end

function local_testfiles(filename, filesdir="files")
    name, ext = splitext(filename)
    return (name*".res", joinpath(@__DIR__, pdftest_dir, "templates", name*".txt"),
            joinpath(@__DIR__, pdftest_dir, filesdir, filename))
end

@testset "PDFIO tests" begin
    @testset "Miscellaneous" begin
        @test_throws ErrorException skipv(IOBuffer([UInt8(65), UInt8(66)]),
                                          UInt8(66))
        @test CDTextString(CosXString([UInt8('0'), UInt8('0'),
                                       UInt8('4'),UInt8('1')]))=="A"
        @test CDTextString(CosXString([UInt8('4'), UInt8('2'),
                                       UInt8('4'),UInt8('1')]))=="BA"
        @test CosFloat(CosInt(1)) == CosFloat(1f0)
        @test [CosFloat(1f0), CosInt(2)] == [CosFloat(1f0), CosFloat(2f0)]
        @test CDRect(CosArray(CosObject[
                               CosInt(0),
                               CosInt(0),
                               CosInt(640),
                               CosInt(480)])) == CDRect(0, 0, 640, 480)
        @test parse_indirect_ref(IOBuffer(b"10 0 R\n")) ==
            CosIndirectObjectRef(10, 0)
        @test string(parse_value(IOBuffer("% This is a comment\r\n"))) ==
                     "% This is a comment"
    end

    @testset "CDDate" begin
        @test string(CDDate("D:199812231952-08'30 "))==
            "D:19981223195200-08'30"
        @test_throws ErrorException CDDate("not a date")
        @test_throws ErrorException CDDate("D:209")
        @test CDDate("D:2009") == CDDate("D:20090101000000Z")
        @test CDDate("D:200902") == CDDate("D:20090201000000+00")
        @test CDDate("D:20090202") == CDDate("D:20090202000000-00")
        @test CDDate("D:2009020201") == CDDate("D:20090202010000+00'00")
        @test CDDate("D:200902020102") == CDDate("D:20090202010200+00'00")
        @test CDDate("D:20090202010203") == CDDate("D:20090202010203+00'00")
        @test CDDate("D:20090202010202+00'01") < CDDate("D:20090202010202") <
            CDDate("D:20090202010203") < CDDate("D:20090202010203-00'01")
        @test CDDate("D:20090202-01'01") > CDDate("D:20090202-00'01") >
            CDDate("D:20090202+00'01") > CDDate("D:20090202+01'01")
        @test isless(CDDate("D:2009020208+06"), CDDate("D:2009020204+01"))
        @test isequal(CDDate("D:2009020208+06"), CDDate("D:2009020204+02"))
        @test getUTCTime(CDDate("D:20190425173659+05'30")) ==
            CDDate("D:20190425120659Z")
    end
    @testset "Crypto APIs" begin
        @test_throws ErrorException openssl_error(0)
        @test openssl_error(1) === nothing
        cacerts   = joinpath(@__DIR__, "..", "data", "certs", "cacerts.pem")
        @testset "Self-sign certs" begin
            files = ["sample01.pdf", "sample02.pdf", "sample03.pdf", "sample04.pdf",
                     "sample05.pdf", "sample06.pdf", "sample07.pdf", "sample08.pdf"]
            isfile(cacerts) && rm(cacerts)
            doc = pdDocOpen(joinpath(@__DIR__, pdftest_dir, "DigSig", "sample01.pdf"))
            @test pdDocHasSignature(doc)
            r = pdDocValidateSignatures(doc, export_certs=true)
            pdDocClose(doc)
            @test r[1][:passed] == false
            @test isfile("sample01.pem")
            cp("sample01.pem", cacerts, force=true)
            for file in files
                doc = pdDocOpen(joinpath(@__DIR__, pdftest_dir, "DigSig", file))
                @test pdDocHasSignature(doc)
                r = pdDocValidateSignatures(doc)
                @test all([r[i][:passed] for i = 1:length(r)])
                pdDocClose(doc)
            end
        end
        @testset "Expired Certs" begin
            files = ["pdf-signer-tool-guide.pdf", "samplecertifiedpdf.pdf"]
            for file in files
                isfile(cacerts) && rm(cacerts)
                doc = pdDocOpen(joinpath(@__DIR__, pdftest_dir, "DigSig", file))
                @test pdDocHasSignature(doc)
                r = pdDocValidateSignatures(doc)
                @test all([r[i][:passed] for i = 1:length(r)])
                pdDocClose(doc)
            end
        end
        @testset "PDF 2.0 experimental" begin
            files = ["pades_example-1.pdf", "PAdES_SmartID.pdf",
                     "sbid_3rd_party_sign_pades.pdf", "sbid_authbased_signflow_pades.pdf"]
            for file in files
                isfile(cacerts) && rm(cacerts)
                doc = pdDocOpen(joinpath(@__DIR__, pdftest_dir, "DigSig", file))
                @test pdDocHasSignature(doc)
                r = pdDocValidateSignatures(doc)
                @test all([r[i][:passed] for i = 1:length(r)])
                pdDocClose(doc)
            end
        end

        @testset "PDF user password documents" begin
            files = ["dt-256-aes.pdf",              # V-5 R-6 supported in PDF 2.0
                     "dt-embed-protected.pdf",      # V-2 R-3
                     "dt-own-pass-same.pdf",        # V-2 R-3 - upw = opw = user
                     "dt.pdf",                      # V-2 R-3
                     "dt-protected.pdf",            # V-4 R-4
                     "dt-nopass.pdf",               # No encryption
                     "pdf-example-password.pdf",    # V-2 R-3
                     "pdf-example-encryption.pdf",  # V-2 R-3 No user password
                     "upw-password-opw-sample.pdf", # V-5 R-5
                     "samplesecured_256bitaes.pdf", # V-5 R-5
                     ]
            pws = Vector{UInt8}[b"user", b"user", b"user", b"user", b"user", b"", b"test", b"", b"password", b""]
            infos = []
            encrypted = []
            for i = 1:length(files)
                file = files[i]
                resname, template, filename = local_testfiles(file)
                doc = pdDocOpen(joinpath(@__DIR__, pdftest_dir, "encrypt", file), access=()->Base.SecretBuffer!(pws[i]))
                page = pdDocGetPage(doc, 1)
                push!(infos, pdDocGetInfo(doc))
                open(resname, "w") do io
                    pdPageExtractText(io, page)
                end
                push!(encrypted, cosDocIsEncrypted(doc.cosDoc))
                if i != 9
                    @test files_equal(resname, template)
                else
                    @test stat(resname).size == 0
                end
                pdDocClose(doc)
            end
            @test infos[1]["Producer"] == "SAMBox 1.1.57 (www.sejda.org)"
            @test infos[2]["Producer"] == "LibreOffice 6.0"
            @test infos[3]["Producer"] == "LibreOffice 6.0"
            @test infos[4]["Producer"] == "LibreOffice 6.0"
            @test infos[5]["Producer"] =="3-Heights(TM) PDF Security Shell 4.8.25.2 (http://www.pdf-tools.com)"
            @test infos[6]["Producer"] == "LibreOffice 6.0"
            @test all(encrypted[1:5])
        end
        @testset "PDF owner password documents" begin
            files = ["dt.pdf", "dt-own-pass-same.pdf", "upw-password-opw-sample.pdf"]
            opws = Vector{UInt8}[b"owner", b"user", b"sample"]
            for i = 1:length(files)
                file = files[i]
                resname, template, filename = local_testfiles(file)
                doc = pdDocOpen(joinpath(@__DIR__, pdftest_dir, "encrypt", file), access=()->Base.SecretBuffer!(opws[i]))
                page = pdDocGetPage(doc, 1)
                open(resname, "w") do io
                    pdPageExtractText(io, page)
                end
                if i != 3
                    @test files_equal(resname, template)
                else
                    @test stat(resname).size == 0
                end
                @test doc.cosDoc.secHandler.keys[cn"StdCF"][1] == 0xffffffff
                pdDocClose(doc)
            end
        end
        @testset "PDF Crypt filter" begin
            files = ["dt-att-protected.pdf"]
            opws = Vector{UInt8}[b"user1234"]
            for i = 1:length(files)
                file = files[i]
                doc = pdDocOpen(joinpath(@__DIR__, pdftest_dir, "encrypt", file), access=()->Base.SecretBuffer!(opws[i]))
                obj = cosDocGetObject(doc.cosDoc, CosIndirectObjectRef(43, 0))
                @test String(read(get(obj))) == "An embedded text file. "
                pdDocClose(doc)
            end
        end
        @testset "PKI Security Handlers" begin
            files = ["dt-embed-protected-cert.pdf",
                     "dt-all-aes-128.pdf",
                     "dt-cert-all.pdf",
                     "dt-rc-4-all.pdf"
                     ]
            infos = []
            encrypted = []
            pw = Base.SecretBuffer("password")
            p12path = joinpath(@__DIR__, pdftest_dir, "certs", "doc-crypt.p12")
            Base.shred!(pw) do pw
                for i = 1:length(files)
                    file = files[i]
                    resname, template, filename = local_testfiles(file)
                    path = joinpath(@__DIR__, pdftest_dir, "encrypt", file)
                    doc = pdDocOpen(path, access=()->read_pkcs12(p12path, pw))
                    page = pdDocGetPage(doc, 1)
                    push!(infos, pdDocGetInfo(doc))
                    open(resname, "w") do io
                        pdPageExtractText(io, page)
                    end
                    push!(encrypted, cosDocIsEncrypted(doc.cosDoc))
                    @test files_equal(resname, template)
                    pdDocClose(doc)
                end
            end
            @test infos[1]["Producer"] == "LibreOffice 6.0"
            @test infos[2]["Producer"] == "LibreOffice 6.0"
            @test infos[3]["Producer"] == "LibreOffice 6.0"
            @test infos[4]["Producer"] == "LibreOffice 6.0"
            @test all(encrypted)
        end
    end

    @testset "Test FlateDecode" begin
        @test begin
            resname, template, filename = local_testfiles("1.pdf")
            DEBUG && println(filename)
            doc = pdDocOpen(filename)
            DEBUG && println(pdDocGetCatalog(doc))
            cosDoc = pdDocGetCosDoc(doc)
            DEBUG && map(println, cosDoc.trailer)
            info = pdDocGetInfo(doc)
            @assert info["Producer"] == "LibreOffice 5.3" && info["Creator"] == "Writer"
            @assert pdDocGetPageCount(doc) == 2
            page = pdDocGetPage(doc, 1)
            @assert pdPageGetMediaBox(page) == pdPageGetCropBox(page)
            @assert pdPageIsEmpty(page) == false
            contents = pdPageGetContents(page)
            bufstm = get(contents)
            buf = read(bufstm)
            close(bufstm)
            @assert length(buf) == 18669
            @assert length(pdPageGetContentObjects(page).objs)==190
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end
    @testset "Document without Info" begin
        @test begin
            resname, template, filename = local_testfiles("1_noinfo.pdf")
            DEBUG && println(filename)
            doc = pdDocOpen(filename)
            DEBUG && println(pdDocGetCatalog(doc))
            cosDoc = pdDocGetCosDoc(doc)
            DEBUG && map(println, cosDoc.trailer)
            info = pdDocGetInfo(doc)
            @assert info === nothing
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Document with empty property" begin
        @test begin
            resname, template, filename = local_testfiles("empty_property.pdf")
            DEBUG && println(filename)
            doc = pdDocOpen(filename)
            DEBUG && println(pdDocGetCatalog(doc))
            cosDoc = pdDocGetCosDoc(doc)
            DEBUG && map(println, cosDoc.trailer)
            info = pdDocGetInfo(doc)
            @assert info == Dict(
                "Producer" => "Scribus PDF Library 1.3.3.13",
                "CreationDate" => CDDate("D:20090807192622"),
                "ModDate" => CDDate("D:20090807192622"),
                "Creator" => "Scribus 1.3.3.13",
                "Trapped" => cn"False")
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "PDF File with ObjectStreams" begin
        @test begin
            resname, template, filename = local_testfiles("pdf-17.pdf")
            DEBUG && println(filename)
            doc = pdDocOpen(filename)
            @assert pdDocGetPageCount(doc) == 1
            page = pdDocGetPage(doc, 1)
            rm = pdPageGetMediaBox(page)
            rc = pdPageGetCropBox(page)
            @assert rm == CDRect(0, 0, 612, 792)
            @assert rc == CDRect(0, 0, 612, 792)
            @assert pdPageIsEmpty(page) == false
            contents = pdPageGetContents(page)
            bufstm = get(contents)
            buf = read(bufstm)
            close(bufstm)
            @assert length(buf) == 1021
            @assert length(pdPageGetContentObjects(page).objs)==1
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "General File Opening 3" begin
        @test begin
            filename=pdftest_dir*"stillhq/3.pdf"
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            @assert pdDocGetPageCount(doc) == 30
            page = pdDocGetPage(doc, 1)
            @assert pdPageIsEmpty(page) == false
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Hybrid x-ref" begin
        @test begin
            filename="A1947-15.pdf"
            DEBUG && println(filename)
            resfile, template, filename = local_testfiles(filename)
            doc = pdDocOpen(filename)
            io = util_open(resfile, "w")
            try
                extract_text(io, doc)
            finally
                util_close(io)
                pdDocClose(doc)
            end
            @test files_equal(resfile, template)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Floating point CIDWidth" begin
        @test begin
            filename="sample-google-doc.pdf"
            DEBUG && println(filename)
            resfile, template, filename = local_testfiles(filename)
            doc = pdDocOpen(filename)
            io = util_open(resfile, "w")
            try
                extract_text(io, doc)
            finally
                util_close(io)
                pdDocClose(doc)
            end
            @test files_equal(resfile, template)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Non-standard CMap" begin
        @test begin
            filename="16-969_o7jp.pdf"
            DEBUG && println(filename)
            resfile, template, filename = local_testfiles(filename)
            doc = pdDocOpen(filename)
            io = util_open(resfile, "w")
            try
                extract_text(io, doc)
            finally
                util_close(io)
                pdDocClose(doc)
            end
            @test files_equal(resfile, template)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Corrupt File" begin
        @test begin
            resfile, template, filename = local_testfiles("A1947-14.pdf")
            DEBUG && println(filename)
            doc = pdDocOpen(filename)
            try
                npage= pdDocGetPageCount(doc)
                for i=1:npage
                    page = pdDocGetPage(doc, i)
                    @assert i == pdPageGetPageNumber(page)
                    if pdPageIsEmpty(page) == false
                        pdPageGetContentObjects(page)
                        pdPageExtractText(IOBuffer(), page)
                    end
                end
            finally
                pdDocClose(doc)
            end
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Test RunLengthDecode" begin
        @test begin
            filename=pdftest_dir*"stillhq/582.pdf"
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            info = pdDocGetInfo(doc)
            @assert info["Trapped"] == cn"False"
            @assert pdDocGetPageCount(doc) == 12
            obj=PDFIO.Cos.cosDocGetObject(doc.cosDoc,
                                          PDFIO.Cos.CosIndirectObjectRef(177, 0))
            stm=get(obj)
            data=read(stm)
            close(stm)
            @assert length(data) == 273
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Test ASCIIHexDecode" begin
        @test begin
            filename=pdftest_dir*"stillhq/325.pdf"
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            @assert pdDocGetPageCount(doc) == 1
            obj = cosDocGetObject(doc.cosDoc, CosIndirectObjectRef(7, 0))
            stm=get(obj)
            data=read(stm)
            close(stm)
            @assert length(data) == 121203
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Test ASCII85Decode" begin
        @test take!(decode_ascii85(IOBuffer("zzz!!!~>"))) == fill(0x0, 14)
        @test begin
            filename=pdftest_dir*"stillhq/388.pdf"
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            @assert pdDocGetPageCount(doc) == 1
            obj=PDFIO.Cos.cosDocGetObject(doc.cosDoc, PDFIO.Cos.CosIndirectObjectRef(9, 0))
            stm=get(obj)
            data=read(stm)
            close(stm)
            @assert length(data) == 38117
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "LZWDecode Filter" begin
        @test begin
            filename=pdftest_dir*"stillhq/589.pdf"
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            obj=PDFIO.Cos.cosDocGetObject(doc.cosDoc, PDFIO.Cos.CosIndirectObjectRef(70, 0))
            stm=get(obj)
            data=read(stm)
            close(stm)
            @assert length(data) == 768
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
        @test begin
            filename=pdftest_dir*"stillhq/339.pdf"
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            stm = get(cosDocGetObject(doc.cosDoc, CosIndirectObjectRef(4, 0)))
            buf = read(stm)
            util_close(stm)
            @assert length(buf) == 6636
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Content Array" begin
        @test begin
            filename=pdftest_dir*"stillhq/504.pdf"
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            page = pdDocGetPage(doc, 1)
            contents = pdPageGetContents(page)
            stm = get(contents)
            buf = read(stm)
            util_close(stm)
            @assert length(buf) == 5574
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Test read_string" begin
        @test begin
            DEBUG && PDFIO.Cos.parse_data(pdftest_dir*"files/page5.txt")
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Page label test with outlines" begin
        @test begin
            resfile, template, filename = local_testfiles("431.pdf", "stillhq")
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            @assert pdDocGetPageCount(doc) == 54
            @assert pdDocHasPageLabels(doc)
            @assert pdDocGetPageLabel(doc, 3) == "ii"
            @assert pdDocGetPageLabel(doc,  1) == "title"
            @assert pdDocGetPageLabel(doc,  3) == "ii"
            @assert pdDocGetPageLabel(doc, 46) == "42"
            @assert PDFIO.Cos.cosDocGetPageNumbers(doc.cosDoc, doc.catalog, "title") == range(1, length=1)
            @assert PDFIO.Cos.cosDocGetPageNumbers(doc.cosDoc, doc.catalog, "ii") == range(3, length=1)
            @assert PDFIO.Cos.cosDocGetPageNumbers(doc.cosDoc, doc.catalog, "42") == range(46, length=1)
            pdDocGetPageRange(doc, "iii")
            outline = pdDocGetOutline(doc)
            iobuf = IOBuffer()
            print_tree(iobuf, outline)
            write("431.toc.res", take!(iobuf))
            @assert files_equal("431.toc.res", pdftest_dir*"templates/431.toc")
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
        resfile, template, filename = local_testfiles("1.pdf")
        doc = pdDocOpen(filename)
        @test pdDocHasPageLabels(doc) == false
        @test_throws ErrorException(E_INVALID_PAGE_LABEL) pdDocGetPageRange(doc, "1")
        pdDocClose(doc)
    end

    @testset "Outlines" begin
        @test begin
            resfile, template, filename = local_testfiles("outline.pdf")
            DEBUG && println(filename)
            @assert isfile(filename)
            doc = pdDocOpen(filename)
            outline = pdDocGetOutline(doc)
            iobuf = IOBuffer()
            print_tree(iobuf, outline)
            write("outline.toc.res", take!(iobuf))
            @assert files_equal("outline.toc.res", pdftest_dir*"templates/outline.toc")
            @assert pdOutlineItemGetAttr(outline[1][1][1][1])[:Destination][2].pageno == 5
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Symbol Fonts test" begin
        @test begin
            filename="431.pdf"
            result, template_file = local_testfiles(filename)
            DEBUG && println(filename)
            @assert isfile(pdftest_dir*"stillhq/"*filename)
            doc = pdDocOpen(pdftest_dir*"stillhq/"*filename)
            (npage = pdDocGetPageCount(doc)) == 54
            try
                open(result, "w") do io
                    for i=1:npage
                        page = pdDocGetPage(doc, i)
                        if pdPageIsEmpty(page) == false
                            pdPageGetContentObjects(page)
                            pdPageExtractText(io, page)
                        end
                    end
                end
                @assert files_equal(result, template_file)
            finally
                pdDocClose(doc)
            end
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Font Flags test" begin
        @test begin
            resname, template, filename = local_testfiles("pdf-sample.pdf")
            doc = pdDocOpen(filename)
            (npage = pdDocGetPageCount(doc)) == 1
            page = pdDocGetPage(doc, 1)
            fonts = pdPageGetFonts(page)
            d = Dict{CosName, Tuple}()
            for (k, v) in fonts
                d[k] = (pdFontIsAllCap(v), pdFontIsBold(v),
                        pdFontIsFixedW(v), pdFontIsItalic(v),
                        pdFontIsSmallCap(v))
            end
            @assert d[cn"F2"]  == (false, false, false, false, false)
            @assert d[cn"TT2"] == (false, true,  false, false, false)
            @assert d[cn"TT8"] == (false, true,  false, true,  false)
            @assert d[cn"TT6"] == (false, false, false, false, false)
            @assert d[cn"TT4"] == (false, false, false, false, false)
            pdDocClose(doc)
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Forms XObjects Test" begin
        @test begin
            filename="Graphics-wpf.pdf"
            result, template_file = testfiles(filename)
            DEBUG && println(filename)
            isfile(filename) ||
                download("http://www.pdfsharp.net/wiki/GetFile.aspx?"*
                         "File=%2fGraphics-sample%2fGraphics-wpf.pdf",
                         filename)
            doc = pdDocOpen(filename)
            @assert (npage = pdDocGetPageCount(doc)) == 5
            try
                open(result, "w") do io
                    for i=npage:npage
                        page = pdDocGetPage(doc, i)
                        if pdPageIsEmpty(page) == false
                            pdPageGetContentObjects(page)
                            pdPageExtractText(io, page)
                        end
                    end
                end
                @test files_equal(result, template_file)
            finally
                 pdDocClose(doc)
            end
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Inline Image test" begin
        @test begin
            filename="Pratham-Sanskaran.pdf"
            result, template_file, src = local_testfiles(filename)
            DEBUG && println(src)
            doc = pdDocOpen(src)
            (npage = pdDocGetPageCount(doc)) == 3
            try
                open(result, "w") do io
                    for i=1:npage
                        page = pdDocGetPage(doc, i)
                        if pdPageIsEmpty(page) == false
                            pdPageGetContentObjects(page)
                            pdPageExtractText(io, page)
                        end
                    end
                end
                @test files_equal(result, template_file)
            finally
                 pdDocClose(doc)
            end
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "MacRomanEncoding Fonts test" begin
        @test begin
            filename="spec-2.pdf"
            result, template_file, src = local_testfiles(filename)
            DEBUG && println(src)
            doc = pdDocOpen(src)
            @assert (npage = pdDocGetPageCount(doc)) == 1
            try
                open(result, "w") do io
                    for i=1:npage
                        page = pdDocGetPage(doc, i)
                        if pdPageIsEmpty(page) == false
                            pdPageGetContentObjects(page)
                            pdPageExtractText(io, page)
                        end
                    end
                end
                @assert files_equal(result, template_file)
            finally
                pdDocClose(doc)
            end
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Text before header test" begin
        @test begin
            filename="spec-2c.pdf"
            result, template_file, src = local_testfiles(filename)
            DEBUG && println(src)
            doc = pdDocOpen(src)
            @assert (npage = pdDocGetPageCount(doc)) == 1
            try
                open(result, "w") do io
                    for i=1:npage
                        page = pdDocGetPage(doc, i)
                        if pdPageIsEmpty(page) == false
                            pdPageGetContentObjects(page)
                            pdPageExtractText(io, page)
                        end
                    end
                end
                @assert files_equal(result, template_file)
            finally
                pdDocClose(doc)
            end
            length(utilPrintOpenFiles()) == 0
        end
    end

    @testset "Attachment PDF" begin
        filename = "fileAttachment.pdf"
        result, template_file, src = local_testfiles(filename)
        DEBUG && println(src)
        doc = pdDocOpen(src)
        val = string(pdDocGetNamesDict(doc))
        @test val == "\n33 0 obj\n<<\n\t/EmbeddedFiles\t34 0 R\n\t/JavaScript\t35 0 R\n>>\nendobj\n\n" ||
            val == "\n33 0 obj\n<<\n\t/JavaScript\t35 0 R\n\t/EmbeddedFiles\t34 0 R\n>>\nendobj\n\n"
        pdDocClose(doc)
        @test length(utilPrintOpenFiles()) == 0
    end

    files=readdir(get_tempdir())
    @assert length(files) == 0
end

for (root, dirs, files) in walkdir(joinpath(@__DIR__, pdftest_dir, "fonts"))
    dest = joinpath(@__DIR__, "..", "data", "fonts")
    for file in files
        println("Removing $file from $dest")
        isfile(joinpath(dest, file)) && rm(joinpath(dest, file))
    end
end

if isfile("pvt/pvttests.jl")
    include("pvt/pvttests.jl")
end


text:
mutable struct Bayes_IPM_ensemble
	path::String #ensemble models and popped-out posterior samples serialised here
	models::Vector{Model_Record} #ensemble keeps paths to serialised models and their likelihood tuples rather than keeping the models in memory

	log_Li::Vector{AbstractFloat} #likelihood of lowest-ranked model at iterate i
	log_Xi::Vector{AbstractFloat} #amt of prior mass included in ensemble contour at Li
	log_wi::Vector{AbstractFloat} #width of prior mass covered in this iterate
	log_Liwi::Vector{AbstractFloat} #evidentiary weight of the iterate
	log_Zi::Vector{AbstractFloat} #ensemble evidence
	Hi::Vector{AbstractFloat} #ensemble information

	obs_array::Matrix{Integer} #observations
	obs_lengths::Vector{Integer}

	source_priors::Vector{Vector{Dirichlet{AbstractFloat}}} #source pwm priors
	mix_prior::Tuple{BitMatrix,AbstractFloat} #prior on %age of observations that any given source contributes to

	bg_scores::Matrix{AbstractFloat} #precalculated background HMM scores

	sample_posterior::Bool
	retained_posterior_samples::Vector{Model_Record} #list of posterior sample records

	model_counter::Integer

	naive_lh::AbstractFloat #the likelihood of the background model without any sources
end

####Bayes_IPM_ensemble FUNCTIONS
Bayes_IPM_ensemble(path::String, no_models::Integer, source_priors::Vector{Vector{Dirichlet{AbstractFloat}}}, mix_prior::Tuple{BitMatrix,AbstractFloat}, bg_scores::Matrix{AbstractFloat}, obs::Array{Integer}, source_length_limits; posterior_switch::Bool=true) =
Bayes_IPM_ensemble(
	path,
	assemble_IPMs(path, no_models, source_priors, mix_prior, bg_scores, obs, source_length_limits),
	[-Inf], #L0 = 0
	[0], #ie exp(0) = all of the prior is covered
	[-Inf], #w0 = 0
	[-Inf], #Liwi0 = 0
	[-1e300], #Z0 = 0
	[0], #H0 = 0,
	obs,
	[findfirst(iszero,obs[:,o])-1 for o in 1:size(obs)[2]],
	source_priors,
	mix_prior,
	bg_scores, #precalculated background score
	posterior_switch,
	Vector{String}(),
	no_models+1,
	IPM_likelihood(init_logPWM_sources(source_priors, source_length_limits), obs, [findfirst(iszero,obs[:,o])-1 for o in 1:size(obs)[2]], bg_scores, falses(size(obs)[2],length(source_priors))))

Bayes_IPM_ensemble(worker_pool::Vector{Integer}, path::String, no_models::Integer, source_priors::Vector{Vector{Dirichlet{AbstractFloat}}}, mix_prior::Tuple{BitMatrix,AbstractFloat}, bg_scores::Matrix{AbstractFloat}, obs::Array{Integer}, source_length_limits; posterior_switch::Bool=true) =
Bayes_IPM_ensemble(
	path,
	distributed_IPM_assembly(worker_pool, path, no_models, source_priors, mix_prior, bg_scores, obs, source_length_limits),
	[-Inf], #L0 = 0
	[0], #ie exp(0) = all of the prior is covered
	[-Inf], #w0 = 0
	[-Inf], #Liwi0 = 0
	[-1e300], #Z0 = 0
	[0], #H0 = 0,
	obs,
	[findfirst(iszero,obs[:,o])-1 for o in 1:size(obs)[2]],
	source_priors,
	mix_prior,
	bg_scores, #precalculated background score
	posterior_switch,
	Vector{String}(),
	no_models+1,
	IPM_likelihood(init_logPWM_sources(source_priors, source_length_limits), obs, [findfirst(iszero,obs[:,o])-1 for o in 1:size(obs)[2]], bg_scores, falses(size(obs)[2],length(source_priors))))

function assemble_IPMs(path::String, no_models::Integer, source_priors::Vector{Vector{Dirichlet{AbstractFloat}}}, mix_prior::Tuple{BitMatrix,AbstractFloat}, bg_scores::AbstractArray{AbstractFloat}, obs::AbstractArray{Integer}, source_length_limits::UnitRange{Integer})
	ensemble_records = Vector{Model_Record}()
	!isdir(path) && mkpath(path)

	@assert size(obs)[2]==size(bg_scores)[2]

	@showprogress 1 "Assembling IPM ensemble..." for model_no in 1:no_models
		model_path = string(path,'/',model_no)
		if !isfile(model_path)
			model = ICA_PWM_model(string(model_no), source_priors, mix_prior, bg_scores, obs, source_length_limits)
			serialize(model_path, model) #save the model to the ensemble directory
			push!(ensemble_records, Model_Record(model_path,model.log_Li))
		else #interrupted assembly pick up from where we left off
			model = deserialize(model_path)
			push!(ensemble_records, Model_Record(model_path,model.log_Li))
		end
	end

	return ensemble_records
end

function distributed_IPM_assembly(worker_pool::Vector{Integer}, path::String, no_models::Integer, source_priors::Vector{Vector{Dirichlet{AbstractFloat}}}, mix_prior::Tuple{BitMatrix,AbstractFloat}, bg_scores::AbstractArray{AbstractFloat}, obs::AbstractArray{Integer}, source_length_limits::UnitRange{Integer})
	ensemble_records = Vector{Model_Record}()
	!isdir(path) && mkpath(path)

	@assert size(obs)[2]==size(bg_scores)[2]

    model_chan= RemoteChannel(()->Channel{ICA_PWM_model}(length(worker_pool)))
    job_chan = RemoteChannel(()->Channel{Union{Tuple,Nothing}}(1))
	put!(job_chan,(source_priors, mix_prior, bg_scores, obs, source_length_limits))

    for worker in worker_pool
        remote_do(worker_assemble, worker, job_chan, model_chan)
	end

	assembly_progress=Progress(no_models, desc="Assembling IPM ensemble...")

	model_counter=check_assembly!(ensemble_records, path, no_models, assembly_progress)

	while model_counter <= no_models
		wait(model_chan)
		candidate=take!(model_chan)
		model = ICA_PWM_model(string(model_counter), candidate.sources, candidate.informed_sources, candidate.source_length_limits, candidate.mix_matrix, candidate.log_Li, candidate.flags)
		model_path=string(path,'/',model_counter)
		serialize(model_path,model)
		push!(ensemble_records, Model_Record(model_path,model.log_Li))
		model_counter+=1
		next!(assembly_progress)
	end

	take!(job_chan),put!(job_chan,nothing)

	return ensemble_records
end
				function check_assembly!(ensemble_records::Vector{Model_Record}, path::String, no_models::Integer, assembly_progress::Progress)
					counter=1
					while counter <= no_models
						model_path=string(path,'/',counter)
						if isfile(model_path)
							model=deserialize(model_path)
							push!(ensemble_records, Model_Record(model_path,model.log_Li))
							counter+=1
							next!(assembly_progress)
						else
							return counter
						end
					end
					return counter
				end

				function worker_assemble(job_chan::RemoteChannel, models_chan::RemoteChannel) #NEEDS REWRITING, TOO SLOW IN DISTRIBUTED
					wait(job_chan)
					params=fetch(job_chan)
					while isready(job_chan)
						model=ICA_PWM_model(string(myid()),params...)
						put!(models_chan,model)
					end
				end

ext: jl


text:
@testset "Callbacks" begin
    problem = MultivariateProblems.UnconstrainedProblems.examples["Rosenbrock"]

    f = MVP.objective(problem)
    g! = MVP.gradient(problem)
    h! = MVP.hessian(problem)
    initial_x = problem.initial_x
    d2 = OnceDifferentiable(f, g!, initial_x)
    d3 = TwiceDifferentiable(f, g!, h!, initial_x)

    for method in (NelderMead(), SimulatedAnnealing())
        ot_run = false
        cb = tr -> begin
            @test tr[end].iteration % 3 == 0
            ot_run = true
            false
        end
        options = Optim.Options(callback = cb, show_every=3, store_trace=true)
        optimize(f, initial_x, method, options)
        @test ot_run == true

        os_run = false
        cb = os -> begin
            @test os.iteration % 3 == 0
            os_run = true
            false
        end
        options = Optim.Options(callback = cb, show_every=3)
        optimize(f, initial_x, method, options)
        @test os_run == true

        # Test early stopping by callbacks
        options = Optim.Options(callback = x -> x.iteration == 5 ? true : false)
        optimize(f, zeros(2), NelderMead(), options)
    end

    for method in (BFGS(),
                   ConjugateGradient(),
                   GradientDescent(),
                   MomentumGradientDescent())
        ot_run = false
        cb = tr -> begin
            @test tr[end].iteration % 3 == 0
            ot_run = true
            false
        end
        options = Optim.Options(callback = cb, show_every=3, store_trace=true)

        optimize(d2, initial_x, method, options)
        @test ot_run == true

        os_run = false
        cb = os -> begin
            @test os.iteration % 3 == 0
            os_run = true
            false
        end
        options = Optim.Options(callback = cb, show_every=3)
        optimize(d2, initial_x, method, options)
        @test os_run == true
    end

    for method in (Newton(),)
        ot_run = false
        cb = tr -> begin
            @test tr[end].iteration % 3 == 0
            ot_run = true
            false
        end
        options = Optim.Options(callback = cb, show_every=3, store_trace=true)
        optimize(d3, initial_x, method, options)
        @test ot_run == true

        os_run = false
        cb = os -> begin
            @test os.iteration % 3 == 0
            os_run = true
            false
        end
        options = Optim.Options(callback = cb, show_every=3)
        optimize(d3, initial_x, method, options)
        @test os_run == true
    end
    res = optimize(x->x^2, -5, 5, callback=_->true)
    @test res.iterations == 0
end

max_stars_repo_name: ethansaxenian/RosettaDecode

max_issues_repo_name: ethansaxenian/RosettaDecode

max_forks_repo_name: ethansaxenian/RosettaDecode


text:
# plot/z-test.jl

using Test

@test jim(:test)


text:
type TimerOutput <: AbstractTimerOutput
    start_time::UInt64
    sections::Dict{SectionName, TimeData}
    start_times::Dict{SectionName, UInt64}
    last_section::SectionName
end

sections(to::TimerOutput) = to.sections

function TimerOutput()
    sections = Dict{SectionName, TimeData}()
    start_time = time_ns()
    start_times = Dict{SectionName, UInt64}()
    return TimerOutput(start_time, sections, start_times, "")
end

function reset_timer!(to::TimerOutput)
    to.sections = Dict{SectionName, TimeData}()
    to.start_time = time_ns()
    return to
end

function timer_expr(to::Symbol, label, ex::Expr)
    quote
        time_section($(esc(to)), $(esc(label))) do
            $(esc(ex))
        end
    end
end

function tottimed(to::TimerOutput)
    s = UInt64(0)
    for time_data in values(to.sections)
        s += time_data.tottime
    end
    return s
end

function time_section(f::Function, to::TimerOutput, label)
    if !(typeof(label) <: AbstractString)
        throw(ArgumentError("section did not get evaluated to a string"))
    end
    time_data = get!(sections(to), label, TimeData(0, 0))
    elapsedtime = time_ns()
    local val
    try
        val = f()
    finally
        elapsedtime = time_ns() - elapsedtime
        time_data.tottime += elapsedtime
        time_data.ncalls += 1
    end
    return val
end

function enter_section(to::TimerOutput, label::AbstractString)
    start_time = time_ns()
    to.last_section = label
    to.start_times[label] = start_time
end

function exit_section(to::TimerOutput, label::AbstractString = to.last_section)
    end_time = time_ns()
    start_time = to.start_times[label]
    time_data = get!(sections(to), label, TimeData(0, 0))
    time_data.tottime += end_time - start_time
    time_data.ncalls += 1
end

function print_sections(io::IO, to::TimerOutput, since_start)
    keys_v = collect(keys(sections(to)))
    values_v = collect(values(sections(to)))
    tot_timed = tottimed(to)
    for i in reverse(sortperm(values_v))
        section = keys_v[i]
        time_data = values_v[i]
        print_section(io, section, time_data, since_start, tot_timed)
    end
end


text:
#
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

using FMIImport: FMU3, fmi3ModelDescription
using FMIImport: fmi3Float32, fmi3Float64, fmi3Int8, fmi3Int16, fmi3Int32, fmi3Int64, fmi3Boolean, fmi3String, fmi3Binary, fmi3UInt8, fmi3UInt16, fmi3UInt32, fmi3UInt64, fmi3Byte
using FMIImport: fmi3Clock, fmi3FMUState
using FMIImport: fmi3CallbackLogger, fmi3CallbackIntermediateUpdate, fmi3CallbackClockUpdate

# wrapper functions on the model description
function fmi3GetModelName(fmu::FMU3)
    fmi3GetModelName(fmu.modelDescription)
end
function fmi3GetInstantiationToken(fmu::FMU3)
    fmi3GetInstantiationToken(fmu.modelDescription)
end
function fmi3GetGenerationTool(fmu::FMU3)
    fmi3GetGenerationTool(fmu.modelDescription)
end
function fmi3GetGenerationDateAndTime(fmu::FMU3)
    fmi3GetGenerationDateAndTime(fmu.modelDescription)
end
function fmi3GetVariableNamingConvention(fmu::FMU3)
    fmi3GetVariableNamingConvention(fmu.modelDescription)
end

function fmi3CanGetSetState(fmu::FMU3)
    fmi3CanGetSetState(fmu.modelDescription)
end
function fmi3CanSerializeFMUstate(fmu::FMU3)
    fmi3CanSerializeFMUstate(fmu.modelDescription)
end
function fmi3ProvidesDirectionalDerivatives(fmu::FMU3)
    fmi3ProvidesDirectionalDerivatives(fmu.modelDescription)
end
function fmi3ProvidesAdjointDerivatives(fmu::FMU3)
    fmi3ProvidesAdjointDerivatives(fmu.modelDescription)
end
function fmi3IsCoSimulation(fmu::FMU3)
    fmi3IsCoSimulation(fmu.modelDescription)
end
function fmi3IsModelExchange(fmu::FMU3)
    fmi3IsModelExchange(fmu.modelDescription)
end
function fmi3IsScheduledExecution(fmu::FMU3)
    fmi3IsScheduledExecution(fmu.modelDescription)
end

"""
Returns an array of ValueReferences coresponding to the variable names.
"""
function fmi3String2ValueReference(md::fmi3ModelDescription, names::Array{String})
    vr = Array{fmi3ValueReference}(undef,0)
    for name in names
        reference = fmi3String2ValueReference(md, name)
        if reference === nothing
            @warn "Value reference for variable '$name' not found, skipping."
        else
            push!(vr, reference)
        end
    end
    vr
end

"""
Returns the ValueReference coresponding to the variable name.
"""
function fmi3String2ValueReference(md::fmi3ModelDescription, name::String)
    reference = nothing
    if haskey(md.stringValueReferences, name)
        reference = md.stringValueReferences[name]
    else
        @warn "No variable named '$name' found."
    end
    reference
end

function fmi3String2ValueReference(fmu::FMU3, name::Union{String, Array{String}})
    fmi3String2ValueReference(fmu.modelDescription, name)
end

"""
Returns an array of variable names matching a fmi3ValueReference.
"""
function fmi3ValueReference2String(md::fmi3ModelDescription, reference::fmi3ValueReference)
    [k for (k,v) in md.stringValueReferences if v == reference]
end
function fmi3ValueReference2String(md::fmi3ModelDescription, reference::Int64)
    fmi3ValueReference2String(md, fmi3ValueReference(reference))
end

function fmi3ValueReference2String(fmu::FMU3, reference::Union{fmi3ValueReference, Int64})
    fmi3ValueReference2String(fmu.modelDescription, reference)
end

"""
Source: FMISpec3.0, Version D5ef1c1:: 2.3.1. Super State: FMU State Setable

Create a new instance of the given fmu, adds a logger if logginOn == true.

Returns the instance of a new FMU component.

For more information call ?fmi3InstantiateModelExchange
"""
function fmi3InstantiateModelExchange!(fmu::FMU3; visible::Bool = false, loggingOn::Bool = false)

    ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{Cvoid}, Ptr{Cchar}, Cuint, Ptr{Cchar}))

    compAddr = fmi3InstantiateModelExchange(fmu.cInstantiateModelExchange, fmu.instanceName, fmu.modelDescription.instantiationToken, fmu.fmuResourceLocation, fmi3Boolean(visible), fmi3Boolean(loggingOn), fmu.instanceEnvironment, ptrLogger)

    if compAddr == Ptr{Cvoid}(C_NULL)
        @error "fmi3InstantiateModelExchange!(...): Instantiation failed!"
        return nothing
    end
    previous_z  = zeros(fmi3Float64, fmi3GetEventIndicators(fmu.modelDescription))
    rootsFound  = zeros(fmi3Int32, fmi3GetEventIndicators(fmu.modelDescription))
    stateEvent  = fmi3False
    timeEvent   = fmi3False
    stepEvent   = fmi3False
    component = fmi3Component(compAddr, fmu, previous_z, rootsFound, stateEvent, timeEvent, stepEvent)
    push!(fmu.components, component)
    component
end

"""
Source: FMISpec3.0, Version D5ef1c1:: 2.3.1. Super State: FMU State Setable

Create a new instance of the given fmu, adds a logger if logginOn == true.

Returns the instance of a new FMU component.

For more information call ?fmi3InstantiateCoSimulation
"""
function fmi3InstantiateCoSimulation!(fmu::FMU3; visible::Bool = false, loggingOn::Bool = false, eventModeUsed::Bool = false, ptrIntermediateUpdate=nothing)

    ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{Cvoid}, Ptr{Cchar}, Cuint, Ptr{Cchar}))
    if ptrIntermediateUpdate === nothing
        ptrIntermediateUpdate = @cfunction(fmi3CallbackIntermediateUpdate, Cvoid, (Ptr{Cvoid}, fmi3Float64, fmi3Boolean, fmi3Boolean, fmi3Boolean, fmi3Boolean, Ptr{fmi3Boolean}, Ptr{fmi3Float64}))
    end
    if fmu.modelDescription.CShasEventMode
        mode = eventModeUsed
    else
        mode = false
    end

    compAddr = fmi3InstantiateCoSimulation(fmu.cInstantiateCoSimulation, fmu.instanceName, fmu.modelDescription.instantiationToken, fmu.fmuResourceLocation, fmi3Boolean(visible), fmi3Boolean(loggingOn),
                                            fmi3Boolean(mode), fmi3Boolean(fmu.modelDescription.CScanReturnEarlyAfterIntermediateUpdate), fmu.modelDescription.intermediateUpdateValueReferences, Csize_t(length(fmu.modelDescription.intermediateUpdateValueReferences)), fmu.instanceEnvironment, ptrLogger, ptrIntermediateUpdate)

    if compAddr == Ptr{Cvoid}(C_NULL)
        @error "fmi3InstantiateCoSimulation!(...): Instantiation failed!"
        return nothing
    end

    component = fmi3Component(compAddr, fmu)
    push!(fmu.components, component)
    component
end

# TODO not tested
"""
Source: FMISpec3.0, Version D5ef1c1:: 2.3.1. Super State: FMU State Setable

Create a new instance of the given fmu, adds a logger if logginOn == true.

Returns the instance of a new FMU component.

For more information call ?fmi3InstantiateScheduledExecution
"""
function fmi3InstantiateScheduledExecution!(fmu::FMU3, ptrlockPreemption::Ptr{Cvoid}, ptrunlockPreemption::Ptr{Cvoid}; visible::Bool = false, loggingOn::Bool = false)

    ptrLogger = @cfunction(fmi3CallbackLogger, Cvoid, (Ptr{Cvoid}, Ptr{Cchar}, Cuint, Ptr{Cchar}))
    ptrClockUpdate = @cfunction(fmi3CallbackClockUpdate, Cvoid, (Ptr{Cvoid}, ))

    compAddr = fmi3InstantiateScheduledExecution(fmu.cInstantiateScheduledExecution, fmu.instanceName, fmu.modelDescription.instantiationToken, fmu.fmuResourceLocation, fmi3Boolean(visible), fmi3Boolean(loggingOn), fmu.instanceEnvironment, ptrLogger, ptrClockUpdate, ptrlockPreemption, ptrunlockPreemption)

    if compAddr == Ptr{Cvoid}(C_NULL)
        @error "fmi3InstantiateScheduledExecution!(...): Instantiation failed!"
        return nothing
    end

    component = fmi3Component(compAddr, fmu)
    push!(fmu.components, component)
    component
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.4. Inquire Version Number of Header Files

Returns the version of the FMI Standard used in this FMU.

For more information call ?fmi3GetVersion
"""
function fmi3GetVersion(fmu::FMU3)
    fmi3GetVersion(fmu.cGetVersion)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.1. Super State: FMU State Setable

Sets debug logging for the FMU.

For more information call ?fmi3SetDebugLogging
"""
function fmi3SetDebugLogging(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetDebugLogging(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.2. State: Instantiated

FMU enters Initialization mode.

For more information call ?fmi3EnterInitializationMode
"""
function fmi3EnterInitializationMode(fmu::FMU3, startTime::Real = 0.0, stopTime::Real = startTime; tolerance::Real = 0.0)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3EnterInitializationMode(fmu.components[end], startTime, stopTime; tolerance = tolerance)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.3. State: Initialization Mode

FMU exits Initialization mode.

For more information call ?fmi3ExitInitializationMode
"""
function fmi3ExitInitializationMode(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3ExitInitializationMode(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.4. Super State: Initialized

Informs FMU that simulation run is terminated.

For more information call ?fmi3Terminate
"""
function fmi3Terminate(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3Terminate(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.1. Super State: FMU State Setable

Resets FMU.

For more information call ?fmi3Reset
"""
function fmi3Reset(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3Reset(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Float32 variables.

For more information call ?fmi3GetFloat32
"""
function fmi3GetFloat32(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetFloat32(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Float32 variables.

For more information call ?fmi3GetFloat32!
"""
function fmi3GetFloat32!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Float32}, fmi3Float32})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetFloat32!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Float32 variables.

For more information call ?fmi3SetFloat32
"""
function fmi3SetFloat32(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Float32}, fmi3Float32})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetFloat32(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Float64 variables.

For more information call ?fmi3GetFloat64
"""
function fmi3GetFloat64(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetFloat64(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Float64 variables.

For more information call ?fmi3GetFloat64!
"""
function fmi3GetFloat64!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Float64}, fmi3Float64})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetFloat64!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Float64 variables.

For more information call ?fmi3SetFloat64
"""
function fmi3SetFloat64(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Float64}, fmi3Float64})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetFloat64(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int8 variables.

For more information call ?fmi3GetInt8
"""
function fmi3GetInt8(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt8(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int8 variables.

For more information call ?fmi3GetInt8!
"""
function fmi3GetInt8!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int8}, fmi3Int8})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt8!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Int8 variables.

For more information call ?fmi3SetInt8
"""
function fmi3SetInt8(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int8}, fmi3Int8})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetInt8(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt8 variables.

For more information call ?fmi3GetUInt8
"""
function fmi3GetUInt8(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt8(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt8 variables.

For more information call ?fmi3GetUInt8!
"""
function fmi3GetUInt8!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt8}, fmi3UInt8})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt8!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3UInt8 variables.

For more information call ?fmi3SetUInt8
"""
function fmi3SetUInt8(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt8}, fmi3UInt8})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetUInt8(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int16 variables.

For more information call ?fmi3GetInt16
"""
function fmi3GetInt16(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt16(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int16 variables.

For more information call ?fmi3GetInt16!
"""
function fmi3GetInt16!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int16}, fmi3Int16})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt16!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Int16 variables.

For more information call ?fmi3SetInt16
"""
function fmi3SetInt16(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int16}, fmi3Int16})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetInt16(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt16 variables.

For more information call ?fmi3GetUInt16
"""
function fmi3GetUInt16(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt16(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt16 variables.

For more information call ?fmi3GetUInt16!
"""
function fmi3GetUInt16!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt16}, fmi3UInt16})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt16!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3UInt16 variables.

For more information call ?fmi3SetUInt16
"""
function fmi3SetUInt16(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt16}, fmi3UInt16})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetUInt16(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int32 variables.

For more information call ?fmi3GetInt32
"""
function fmi3GetInt32(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt32(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int32 variables.

For more information call ?fmi3GetInt32!
"""
function fmi3GetInt32!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int32}, fmi3Int32})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt32!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Int32 variables.

For more information call ?fmi3SetInt32
"""
function fmi3SetInt32(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int32}, fmi3Int32})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetInt32(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt32 variables.

For more information call ?fmi3GetUInt32
"""
function fmi3GetUInt32(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt32(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt32 variables.

For more information call ?fmi3GetUInt32!
"""
function fmi3GetUInt32!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt32}, fmi3UInt32})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt32!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3UInt32 variables.

For more information call ?fmi3SetUInt32
"""
function fmi3SetUInt32(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt32}, fmi3UInt32})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetUInt32(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int64 variables.

For more information call ?fmi3GetInt64
"""
function fmi3GetInt64(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt64(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Int64 variables.

For more information call ?fmi3GetInt64!
"""
function fmi3GetInt64!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int64}, fmi3Int64})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetInt64!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Int64 variables.

For more information call ?fmi3SetInt64
"""
function fmi3SetInt64(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Int64}, fmi3Int64})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetInt64(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt64 variables.

For more information call ?fmi3GetUInt64
"""
function fmi3GetUInt64(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt64(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3UInt64 variables.

For more information call ?fmi3GetUInt64!
"""
function fmi3GetUInt64!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt64}, fmi3UInt64})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetUInt64!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3UInt64 variables.

For more information call ?fmi3SetUInt64
"""
function fmi3SetUInt64(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3UInt64}, fmi3UInt64})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetUInt64(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Boolean variables.

For more information call ?fmi3GetBoolean
"""
function fmi3GetBoolean(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetBoolean(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Boolean variables.

For more information call ?fmi3GetBoolean!
"""
function fmi3GetBoolean!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{Bool}, Bool, Array{fmi3Boolean}})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetBoolean!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Boolean variables.

For more information call ?fmi3SetBoolean
"""
function fmi3SetBoolean(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{Bool}, Bool})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetBoolean(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3String variables.

For more information call ?fmi3GetString
"""
function fmi3GetString(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetString(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3String variables.

For more information call ?fmi3GetString!
"""
function fmi3GetString!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{String}, String})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetString!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3String variables.

For more information call ?fmi3SetString
"""
function fmi3SetString(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{String}, String})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetString(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Binary variables.

For more information call ?fmi3GetBinary
"""
function fmi3GetBinary(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetBinary(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Binary variables.

For more information call ?fmi3GetBinary!
"""
function fmi3GetBinary!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Binary}, fmi3Binary})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetBinary!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Binary variables.

For more information call ?fmi3SetBinary
"""
function fmi3SetBinary(fmu::FMU3, vr::fmi3ValueReferenceFormat, valueSizes::Union{Array{Csize_t}, Csize_t}, values::Union{Array{fmi3Binary}, fmi3Binary})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetBinary(fmu.components[end], vr, valueSizes, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Clock variables.

For more information call ?fmi3GetClock
"""
function fmi3GetClock(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetClock(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Get the values of an array of fmi3Clock variables.

For more information call ?fmi3GetClock!
"""
function fmi3GetClock!(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Clock}, fmi3Clock})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetClock!(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.2. Getting and Setting Variable Values

Set the values of an array of fmi3Clock variables.

For more information call ?fmi3SetClock
"""
function fmi3SetClock(fmu::FMU3, vr::fmi3ValueReferenceFormat, values::Union{Array{fmi3Clock}, fmi3Clock})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetClock(fmu.components[end], vr, values)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.4. Getting and Setting the Complete FMU State

Get the pointer to the current FMU state.

For more information call ?fmi3GetFMUState
"""
function fmi3GetFMUState(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetFMUState(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.4. Getting and Setting the Complete FMU State

Set the FMU to the given fmi3FMUstate.

For more information call ?fmi3SetFMUState
"""
function fmi3SetFMUState(fmu::FMU3, state::fmi3FMUState)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SetFMUState(fmu.components[end], state)
end

"""
function fmi3FreeFMUState(c::fmi3Component, FMUstate::Ref{fmi3FMUState})

Free the allocated memory for the FMU state.

For more information call ?fmi3FreeFMUState
"""
function fmi3FreeFMUState(fmu::FMU3, state::fmi3FMUState)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    stateRef = Ref(state)
    fmi3FreeFMUState(fmu.components[end], stateRef)
    state = stateRef[]
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.4. Getting and Setting the Complete FMU State

Returns the size of a byte vector the FMU can be stored in.

For more information call ?fmi3SerzializedFMUStateSize
"""
function fmi3SerializedFMUStateSize(fmu::FMU3, state::fmi3FMUState)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SerializedFMUStateSize(fmu.components[end], state)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.4. Getting and Setting the Complete FMU State

Serialize the data in the FMU state pointer.

For more information call ?fmi3SerializeFMUState
"""
function fmi3SerializeFMUState(fmu::FMU3, state::fmi3FMUState)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SerializeFMUState(fmu.components[end], state)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.6.4. Getting and Setting the Complete FMU State

Deserialize the data in the serializedState fmi3Byte field.

For more information call ?fmi3DeSerializeFMUState
"""
function fmi3DeSerializeFMUState(fmu::FMU3, serializedState::Array{fmi3Byte})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3DeSerializeFMUState(fmu.components[end], serializedState)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.11. Getting Partial Derivatives

Retrieves directional derivatives.

For more information call ?fmi3GetDirectionalDerivative
"""
function fmi3GetDirectionalDerivative(fmu::FMU3,
                                      unknowns::fmi3ValueReference,
                                      knowns::fmi3ValueReference,
                                      seed::fmi3Float64 = 1.0)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetDirectionalDerivative(fmu.components[end], unknowns, knowns, seed)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.11. Getting Partial Derivatives

Retrieves directional derivatives.

For more information call ?fmi3GetDirectionalDerivative
"""
function fmi3GetDirectionalDerivative(fmu::FMU3,
                                      unknowns::Array{fmi3ValueReference},
                                      knowns::Array{fmi3ValueReference},
                                      seed::Array{fmi3Float64} = Array{fmi3Float64}([]))
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetDirectionalDerivative(fmu.components[end], unknowns, knowns, seed)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.11. Getting Partial Derivatives

Retrieves directional derivatives in-place.

For more information call ?fmi3GetDirectionalDerivative
"""
function fmi3GetDirectionalDerivative!(fmu::FMU3,
    unknowns::Array{fmi3ValueReference},
    knowns::Array{fmi3ValueReference},
    sensitivity::Array{fmi3Float64},
    seed::Array{fmi3Float64} = Array{fmi3Float64}([]))
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetDirectionalDerivative!(fmu.components[end], unknowns, knowns, sensitivity, seed)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.11. Getting Partial Derivatives

Retrieves adjoint derivatives.

For more information call ?fmi3GetAdjointDerivative
"""
function fmi3GetAdjointDerivative(fmu::FMU3,
                                      unknowns::fmi3ValueReference,
                                      knowns::fmi3ValueReference,
                                      seed::fmi3Float64 = 1.0)

    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetAdjointDerivative(fmu.components[end], unknowns, knowns, seed)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.11. Getting Partial Derivatives

Retrieves adjoint derivatives.

For more information call ?fmi3GetAdjointDerivative
"""
function fmi3GetAdjointDerivative(fmu::FMU3,
                                      unknowns::Array{fmi3ValueReference},
                                      knowns::Array{fmi3ValueReference},
                                      seed::Array{fmi3Float64} = Array{fmi3Float64}([]))

    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetAdjointDerivative(fmu.components[end], unknowns, knowns, seed)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.11. Getting Partial Derivatives

Retrieves adjoint derivatives.

For more information call ?fmi3GetAdjointDerivative
"""
function fmi3GetAdjointDerivative!(fmu::FMU3,
    unknowns::Array{fmi3ValueReference},
    knowns::Array{fmi3ValueReference},
    sensitivity::Array{fmi3Float64},
    seed::Array{fmi3Float64} = Array{fmi3Float64}([]))

    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetAdjointDerivative!(fmu.components[end], unknowns, knowns, sensitivity, seed)
end
"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.12. Getting Derivatives of Continuous Outputs

Retrieves the n-th derivative of output values.

vr defines the value references of the variables
the array order specifies the corresponding order of derivation of the variables

For more information call ?fmi3GetOutputDerivatives
"""
function fmi3GetOutputDerivatives(fmu::FMU3, vr::fmi3ValueReferenceFormat, order::Array{Integer})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetOutputDerivatives(fmu.components[end], vr, order)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.12. Getting Derivatives of Continuous Outputs

Retrieves the n-th derivative of output values.

vr defines the value references of the variables
the array order specifies the corresponding order of derivation of the variables

For more information call ?fmi3GetOutputDerivatives
"""
function fmi3GetOutputDerivatives(fmu::FMU3, vr::fmi3ValueReference, order::Integer)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetOutputDerivatives(fmu.components[end], vr, order)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.2. State: Instantiated

If the importer needs to change structural parameters, it must move the FMU into Configuration Mode using fmi3EnterConfigurationMode.
For more information call ?fmi3EnterConfigurationMode
"""
function fmi3EnterConfigurationMode(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3EnterConfigurationMode(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.2. State: Instantiated

This function returns the number of continuous states.
This function can only be called in Model Exchange.
For more information call ?fmi3GetNumberOfContinuousStates
"""
function fmi3GetNumberOfContinuousStates(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetNumberOfContinuousStates(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.2. State: Instantiated

This function returns the number of event indicators.
This function can only be called in Model Exchange.
For more information call ?fmi3GetNumberOfEventIndicators
"""
function fmi3GetNumberOfEventIndicators(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetNumberOfEventIndicators(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.10. Dependencies of Variables

The number of dependencies of a given variable, which may change if structural parameters are changed, can be retrieved by calling the following function:
For more information call ?fmi3GetNumberOfVariableDependencies
"""
function fmi3GetNumberOfVariableDependencies(fmu::FMU3, vr::Union{fmi3ValueReference, String})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetNumberOfVariableDependencies(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.3. State: Initialization Mode

Return the states at the current time instant.
For more information call ?fmi3GetContinuousStates
"""
function fmi3GetContinuousStates(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetContinuousStates(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.2.10. Dependencies of Variables

The dependencies (of type dependenciesKind) can be retrieved by calling the function fmi3GetVariableDependencies.
For more information call ?fmi3GetVariableDependencies
"""
function fmi3GetVariableDependencies(fmu::FMU3, vr::Union{fmi3ValueReference, String})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetVariableDependencies(fmu.components[end], vr)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.3. State: Initialization Mode

Return the nominal values of the continuous states.

For more information call ?fmi3GetNominalsOfContinuousStates
"""
function fmi3GetNominalsOfContinuousStates(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetNominalsOfContinuousStates(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.3. State: Initialization Mode

This function is called to trigger the evaluation of fdisc to compute the current values of discrete states from previous values.
The FMU signals the support of fmi3EvaluateDiscreteStates via the capability flag providesEvaluateDiscreteStates.

For more information call ?fmi3EvaluateDiscreteStates
"""
function fmi3EvaluateDiscreteStates(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3EvaluateDiscreteStates(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.5. State: Event Mode

This function is called to signal a converged solution at the current super-dense time instant. fmi3UpdateDiscreteStates must be called at least once per super-dense time instant.

For more information call ?fmi3UpdateDiscreteStates
"""
function fmi3UpdateDiscreteStates(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3UpdateDiscreteStates(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.5. State: Event Mode

The model enters Continuous-Time Mode.

For more information call ?fmi3EnterContinuousTimeMode
"""
function fmi3EnterContinuousTimeMode(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3EnterContinuousTimeMode(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.5. State: Event Mode

This function must be called to change from Event Mode into Step Mode in Co-Simulation.

For more information call ?fmi3EnterStepMode
"""
function fmi3EnterStepMode(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3EnterStepMode(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 2.3.6. State: Configuration Mode

Exits the Configuration Mode and returns to state Instantiated.

For more information call ?fmi3ExitConfigurationMode
"""
function fmi3ExitConfigurationMode(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3ExitConfigurationMode(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 3.2.1. State: Continuous-Time Mode

Set independent variable time and reinitialize chaching of variables that depend on time.

For more information call ?fmi3SetTime
"""
function fmi3SetTime(fmu::FMU3, time::Real)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmu.t = time
    fmi3SetTime(fmu.components[end], fmi3Float64(time))
end

"""
Source: FMISpec3.0, Version D5ef1c1: 3.2.1. State: Continuous-Time Mode

Set a new (continuous) state vector and reinitialize chaching of variables that depend on states.

For more information call ?fmi3SetContinuousStates
"""
function fmi3SetContinuousStates(fmu::FMU3, x::Union{Array{Float32}, Array{Float64}})
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    nx = Csize_t(length(x))
    fmu.x = x
    fmi3SetContinuousStates(fmu.components[end], Array{fmi3Float64}(x), nx)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 3.2.1. State: Continuous-Time Mode

Compute state derivatives at the current time instant and for the current states.

For more information call ?fmi3GetContinuousStateDerivatives
"""
function  fmi3GetContinuousStateDerivatives(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetContinuousStateDerivatives(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 3.2.1. State: Continuous-Time Mode

Returns the event indicators of the FMU.

For more information call ?fmi3GetEventIndicators
"""
function fmi3GetEventIndicators(fmu::FMU3)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetEventIndicators(fmu.components[end])
end

"""
Source: FMISpec3.0, Version D5ef1c1: 3.2.1. State: Continuous-Time Mode

This function must be called by the environment after every completed step
If enterEventMode == fmi3True, the event mode must be entered
If terminateSimulation == fmi3True, the simulation shall be terminated

For more information call ?fmi3CompletedIntegratorStep
"""
function fmi3CompletedIntegratorStep(fmu::FMU3,
                                     noSetFMUStatePriorToCurrentPoint::fmi3Boolean)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3CompletedIntegratorStep(fmu.components[end], noSetFMUStatePriorToCurrentPoint)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 3.2.1. State: Continuous-Time Mode

The model enters Event Mode.

For more information call ?fmi3EnterEventMode
"""
function fmi3EnterEventMode(fmu::FMU3, stepEvent::Bool, stateEvent::Bool, rootsFound::Array{fmi3Int32}, nEventIndicators::Integer, timeEvent::Bool)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3EnterEventMode(fmu.components[end], stepEvent, stateEvent, rootsFound, nEventIndicators, timeEvent)
end

"""
Source: FMISpec3.0, Version D5ef1c1: 4.2.1. State: Step Mode

The computation of a time step is started.

For more information call ?fmi3DoStep
"""
function fmi3DoStep(fmu::FMU3, currentCommunicationPoint::Real, communicationStepSize::Real, noSetFMUStatePriorToCurrentPoint::Bool, eventEncountered::fmi3Boolean, terminateSimulation::fmi3Boolean, earlyReturn::fmi3Boolean, lastSuccessfulTime::fmi3Float64)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    refeventEncountered = Ref(eventEncountered)
    refterminateSimulation = Ref(terminateSimulation)
    refearlyReturn = Ref(earlyReturn)
    reflastSuccessfulTime = Ref(lastSuccessfulTime)
    fmi3DoStep(fmu.components[end], fmi3Float64(currentCommunicationPoint), fmi3Float64(communicationStepSize), fmi3Boolean(noSetFMUStatePriorToCurrentPoint), refeventEncountered, refterminateSimulation, refearlyReturn, reflastSuccessfulTime)
    eventEncountered = refeventEncountered[]
    terminateSimulation = refterminateSimulation[]
    earlyReturn = refearlyReturn[]
    lastSuccessfulTime = reflastSuccessfulTime[]
end

"""
Starts a simulation of the fmu instance for the matching fmu type. If both types are available, CS is preferred over ME.
"""
function fmi3Simulate(fmu::FMU3, t_start::Real = 0.0, t_stop::Real = 1.0;
                      recordValues::fmi3ValueReferenceFormat = nothing, saveat=[], setup=true)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3Simulate(fmu.components[end], t_start, t_stop;
                 recordValues=recordValues, saveat=saveat, setup=setup)
end
"""
Starts a simulation of a FMU in CS-mode.
"""
function fmi3SimulateCS(fmu::FMU3, t_start::Real, t_stop::Real;
                        recordValues::fmi3ValueReferenceFormat = nothing, saveat=[], setup=true)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SimulateCS(fmu.components[end], t_start, t_stop;
                   recordValues=recordValues, saveat=saveat, setup=setup)
end

"""
Starts a simulation of a FMU in ME-mode.
"""
function fmi3SimulateME(fmu::FMU3, t_start::Real, t_stop::Real; kwargs...)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3SimulateME(fmu.components[end], t_start, t_stop; kwargs...)
end

"""
Returns the start/default value for a given value reference.

TODO: Add this command in the documentation.
"""
function fmi3GetStartValue(fmu::FMU3, vr::fmi3ValueReferenceFormat)
    @assert length(fmu.components) > 0 ["No FMU instance allocated, have you already called fmiInstantiate?"]

    fmi3GetStartValue(fmu.components[end], vr)
end


text:
# returns iostream for debugging purposes for now, not in use
function rinex_read(filename::String)
    open(filename) do iostream
        rinex_header = rinex_read_header(iostream)
    end
end

function rinex_read(iostream::IOStream)
    rinex_header = rinex_read_header!(RINEXHeader(), iostream)
    rinex_data = rinex_read_data!(
        DataFrame(NavMessageID=String[], NavigationMessage=RINEX.AbstractNavMessageFile[]),
        iostream)
    return rinex_header, rinex_data
end

function rinex_read_header!(header::RINEXHeader, iostream::IOStream)
    header = readlines(IOBuffer(readuntil(iostream, EOH, keep = true)))

    # # RINEX VERSION / TYPE
    # file_version = parse.(Float64, header[6:20])
    # file_type = header[21:40]
    # satellite_system = header[41:60]

    return header
    # return num_header_lines, file_version, file_type, satellite_system
end

function rinex_read_data!(data::DataFrame, iostream::IOStream)
    rxndata_raw = readlines(iostream)
    num_of_entries = length(rxndata_raw) ÷ 8
    for i = 1:num_of_entries
        chunk = rxndata_raw[1+(i-1)*8:8*i] #get 8 lines chunks
        @inbounds gpsnavmsg = GPSNavMessageFile(
            parse(Int, chunk[1][2:3]),
            DateTime(
                parse(Int, chunk[1][5:8]),
                parse(Int, chunk[1][10:11]),
                parse(Int, chunk[1][13:14]),
                parse(Int, chunk[1][16:17]),
                parse(Int, chunk[1][19:20]),
                parse(Int, chunk[1][22:23])),
            parse(Float64, chunk[1][24:42]),
            parse(Float64, chunk[1][43:61]),
            parse(Float64, chunk[1][62:80]),
            parse(Float64, chunk[2][5:23]),
            parse(Float64, chunk[2][24:42]),
            parse(Float64, chunk[2][43:61]),
            parse(Float64, chunk[2][62:80]),
            parse(Float64, chunk[3][5:23]),
            parse(Float64, chunk[3][24:42]),
            parse(Float64, chunk[3][43:61]),
            parse(Float64, chunk[3][62:80]),
            parse(Float64, chunk[4][5:23]),
            parse(Float64, chunk[4][24:42]),
            parse(Float64, chunk[4][43:61]),
            parse(Float64, chunk[4][62:80]),
            parse(Float64, chunk[5][5:23]),
            parse(Float64, chunk[5][24:42]),
            parse(Float64, chunk[5][43:61]),
            parse(Float64, chunk[5][62:80]),
            parse(Float64, chunk[6][5:23]),
            parse(Float64, chunk[6][24:42]),
            parse(Float64, chunk[6][43:61]),
            parse(Float64, chunk[6][62:80]),
            parse(Float64, chunk[7][5:23]),
            parse(Float64, chunk[7][24:42]),
            parse(Float64, chunk[7][43:61]),
            parse(Float64, chunk[7][62:80]),
            parse(Float64, chunk[8][5:23]),
            parse(Float64, chunk[8][24:42])
        )
        push!(data, ("G$(gpsnavmsg.prn) $(gpsnavmsg.toc)", gpsnavmsg))
    end
    return data
end