using PyPlot

include("./topoprovision.jl")
include("./topodraw.jl")

## Increment Counter and Get Next Dir
function nextRun(dir, singleplot)
    # dir = joinpath(@__DIR__, "./$dir")
    if isfile("$(dir)/counter.txt") == false
        mkdir("$(dir)")
        writedlm("$(dir)/counter.txt", "1")
    end
    if singleplot
        c = Int(readdlm("$(dir)/counter.txt")[1])
        writedlm("$(dir)/counter.txt", c + 1)
        newdir = "$(dir)/$(c)"
        mkdir(newdir)
    else
        if Int(readdlm("$(dir)/counter.txt")[1]) == 1  # handling case when simulation last longer than a day
            c = Int(readdlm("$(dir)/counter.txt")[1])
            writedlm("$(dir)/counter.txt", c + 1)
            newdir = "$(dir)/$(c)"
            if isdir(newdir) == false
                mkdir(newdir)
            end
        else
            c = Int(readdlm("$(dir)/counter.txt")[1])-1
        end
        newdir = "$(dir)/$(c)"
    end

    return newdir
end

## Compute Weibull Probabilities
function weibullProbs(num; shape=.8, scale=.0001)
    w = Distributions.Weibull(shape, scale)
    probs = []
    for i in 1:num
        push!(probs, rand(w))
    end
    return probs
end

## get optical/IP topo
function ReadCrossLayerTopo(dir, topology, topology_index, verbose, expanded_spectrum; weibull_failure=false, IPfromFile=false, tofile=false)
    topodir =  "./data/topology/$(topology)"
    input_ip_nodes = readdlm("$(topodir)/IP_nodes.txt", header=true)[1]
    input_optical_nodes = readdlm("$(topodir)/optical_nodes.txt", header=true)[1]
    input_topology_o = readdlm("$(topodir)/optical_topo.txt", header=true)[1]
    ignore = ()
    fromNodesOptical = input_topology_o[:,1]
    toNodesOptical = input_topology_o[:,2]
    lengthOptical = input_topology_o[:,3]
    if isdir("$(dir)/$(topology)") == false
        mkdir("$(dir)/$(topology)")
    end

    if weibull_failure
        failureprobOptical = weibullProbs(length(lengthOptical), shape=.8, scale=.02)  # derive fiber cut probability from weibull
        ## draw initial weibull distribution CDF
        PyPlot.clf()
        sorted_failureprobOptical = sort(failureprobOptical)
        cdf = []
        for i in 1:length(sorted_failureprobOptical)
            push!(cdf, i/length(sorted_failureprobOptical))
        end
        PyPlot.plot(sorted_failureprobOptical, cdf, marker="P", linewidth=1, label="shape=.8, scale=.02")
        figname = "$(dir)/$(topology)/00_weibull_distribution.png"
        PyPlot.xlabel("Fiber Failure scenario probability")
        PyPlot.ylabel("CDF")
        PyPlot.legend(loc="best")
        PyPlot.xscale("log")
        PyPlot.savefig(figname)
    else
        failureprobOptical = input_topology_o[:,4]  # if read fiber cut probability from file
        ## draw initial weibull distribution CDF
        PyPlot.clf()
        sorted_failureprobOptical = sort(failureprobOptical)
        cdf = []
        for i in 1:length(sorted_failureprobOptical)
            push!(cdf, i/length(sorted_failureprobOptical))
        end
        PyPlot.plot(sorted_failureprobOptical, cdf, marker="P", linewidth=1, label="failure distribution from file")
        # PyPlot.plot(sorted_failureprobOptical.* 20, cdf, marker="P", linewidth=1, label="shifted")
        figname = "$(dir)/$(topology)/00_filefailure_distribution.png"
        PyPlot.xlabel("Fiber Failure scenario probability")
        PyPlot.ylabel("CDF")
        PyPlot.legend(loc="best")
        PyPlot.xscale("log")
        PyPlot.savefig(figname)
    end

    fiberlinks = []
    bidirect_links = []
    fiberlinkslength = []
    fiberlinksSpectrum = []
    fiberlinksFailure = []
    bidirect_fiberlinksFailure = []

    for i in 1:size(fromNodesOptical, 1)
        if (!(fromNodesOptical[i] in ignore) && !(toNodesOptical[i] in ignore))
            push!(fiberlinks, (Int(fromNodesOptical[i]), Int(toNodesOptical[i])))
            push!(fiberlinkslength, lengthOptical[i])
            push!(fiberlinksSpectrum, [])
            push!(fiberlinksFailure, failureprobOptical[i])
            if !in((Int(fromNodesOptical[i]), Int(toNodesOptical[i])), bidirect_links) && !in((Int(toNodesOptical[i]), Int(fromNodesOptical[i])), bidirect_links)
                push!(bidirect_links, (Int(fromNodesOptical[i]), Int(toNodesOptical[i])))
                push!(bidirect_fiberlinksFailure, failureprobOptical[i])
            end
        end
    end

    parsed_wavelength = []
    parsed_optical_route = []
    links_length = []
    if IPfromFile == true
        input_topology_ip = readdlm("$(topodir)/IP_topo_$(topology_index)/IP_topo_$(topology_index).txt", header=true)[1]
        if verbose println("Read IP layer topo from file: $(topodir)/IP_topo_$(topology_index)/IP_topo_$(topology_index).txt") end
        ignore = ()
        optical_route = input_topology_ip[:,5]
        wavelength = input_topology_ip[:,6]
        for x in 1:length(optical_route)
            current_wavelength = [parse(Int, ss) for ss in split(wavelength[x][2:end-1], ",")]  # example: [1,2,3,4,5,6,7]
            push!(parsed_wavelength, current_wavelength)
            current_optical_route = [parse(Int, ss) for ss in split(optical_route[x][2:end-1], ",")]
            push!(parsed_optical_route, current_optical_route)
            current_length = 0
            for j in 1:length(current_optical_route)
                current_length += lengthOptical[j]
                for t in 1:length(current_wavelength)
                    if current_wavelength[t] in fiberlinksSpectrum[current_optical_route[j]]
                        continue
                    else
                        push!(fiberlinksSpectrum[current_optical_route[j]],current_wavelength[t])
                    end
                end
            end
            push!(links_length, current_length)
        end
    else
        input_topology_ip = ProvisionIPTopology(topology, tofile=tofile)  # generate IP provisioning only no output to file
        optical_route = input_topology_ip[:,5]
        wavelength = input_topology_ip[:,6]
        for x in 1:length(optical_route)
            current_wavelength = [parse(Int, ss) for ss in split(wavelength[x][5:end-1], ", ")]  # example: Any[1, 2, 3, 4, 5, 6, 7]
            push!(parsed_wavelength, current_wavelength)
            current_optical_route = [parse(Int, ss) for ss in split(optical_route[x][5:end-1], ", ")]
            push!(parsed_optical_route, current_optical_route)
            current_length = 0
            for j in 1:length(current_optical_route)
                current_length += lengthOptical[j]
                for t in 1:length(current_wavelength)
                    if current_wavelength[t] in fiberlinksSpectrum[current_optical_route[j]]
                        continue
                    else
                        push!(fiberlinksSpectrum[current_optical_route[j]], current_wavelength[t])
                    end
                end
            end
            push!(links_length, current_length)
        end
    end

    fromNodes = input_topology_ip[:,1]
    toNodes = input_topology_ip[:,2]
    marker = input_topology_ip[:,3]
    capacity = input_topology_ip[:,4] .* 100  # we assume each wavelength is 100 Gbps
    failure_prob = input_topology_ip[:,7]

    links = []
    links_fiberroute = []
    links_wavelength = []
    links_hops = []
    # println("fromNodes ", fromNodes)

    for i in 1:size(fromNodes, 1)
        if (!(fromNodes[i] in ignore) && !(toNodes[i] in ignore))
            push!(links, (Int(fromNodes[i]), Int(toNodes[i]), Int(marker[i])))
            push!(links_fiberroute, parsed_optical_route[i])
            push!(links_wavelength, parsed_wavelength[i])
            push!(links_hops, length(parsed_optical_route[i]))
        end
    end

    # IP links are represented in uni-directional form
    IPTopo = Dict()
    IPTopo["nodes"] = input_ip_nodes
    IPTopo["links"] = links
    IPTopo["capacity"] = capacity
    IPTopo["link_probs"] = failure_prob
    IPTopo["link_fiberroute"] = links_fiberroute
    IPTopo["link_length"] = links_length
    IPTopo["link_wavelength"] = links_wavelength

    # optical fiber links are represented in uni-directional form
    OpticalTopo = Dict()
    OpticalTopo["nodes"] = input_optical_nodes
    OpticalTopo["links"] = fiberlinks  # unidirectional
    OpticalTopo["bidirect_links"] = bidirect_links
    OpticalTopo["fiber_length"] = fiberlinkslength
    OpticalTopo["fiber_spectrum"] = fiberlinksSpectrum
    OpticalTopo["fiber_probs"] = fiberlinksFailure
    OpticalTopo["bidirect_fiber_probs"] = bidirect_fiberlinksFailure
    OpticalTopo["capacity"] = [96+expanded_spectrum-length(x) for x in fiberlinksSpectrum]

    capacitycode = ones(length(fiberlinks), 96+expanded_spectrum)
    for fiber in 1:length(fiberlinksSpectrum)
        for slot in fiberlinksSpectrum[fiber]
            capacitycode[fiber, slot] = 0
        end
    end
    OpticalTopo["capacityCode"] = capacitycode  # 0 means occupied, 1 means available

    drawGraph(topology, IPTopo, topology_index, OpticalTopo, dir)

    return IPTopo, OpticalTopo
end

## different TM may have different sum demand, this function find the proper demand rescale if we want to consider a uniform constant demand time series
function FindDemandScale(topology, nodes_num, AllTraffic)
    demand_upscale = 1
    demand_downscale = 1
    sum_demands = []
    scaling_factor = []
    for traffic_num in 1:length(AllTraffic)
        initial_demand, flows = readDemand("$(topology)/demand", nodes_num, AllTraffic[traffic_num], demand_upscale, demand_downscale, false)  # read initial demand
        # println(sum(initial_demand))
        push!(sum_demands, sum(initial_demand))
    end

    min_demand = minimum(sum_demands)
    for i in 1:length(sum_demands)
        push!(scaling_factor, sum_demands[i]/min_demand)
    end

    return scaling_factor
end

## Read Demand
function readDemand(filename, num_nodes, num_demand, scale, downscale, rescale; matrix=true, sigfigs=1, zeroindex=false)
    filename = "./data/topology/$(filename)"
    input_demand = matrix ? ParseMatrix("$(filename).txt", num_nodes, num_demand) : IgnoreCycles(readdlm("$(filename)/$(num_demand).txt", header=true)[1], zeroindex=zeroindex)
    # println("input_demand ", input_demand)
    fromNodes = input_demand[:,1]
    toNodes = input_demand[:,2]
    flows = map(tup -> (Int(tup[2]), Int(toNodes[tup[1]])), enumerate(fromNodes))
    demand = convert(Array{Float64}, input_demand[:,3] ./ downscale .* scale)

    if rescale
        rescale_set = readdlm("$(filename)_rescale.txt", header=false)
        # println("rescale_set ", rescale_set)
        demand = demand ./ rescale_set[num_demand]
        println("Rescale demand $(num_demand) at $(rescale_set[num_demand]) -> $(sum(demand))")
    end

    return demand, flows
end

## IgnoreCycles
function IgnoreCycles(demand; zeroindex=false)
    z = [0 0 0;]
    for row in 1:size(demand,1)
        if demand[row, 1] != demand[row, 2]
            z = vcat(z, [demand[row,1] + zeroindex demand[row, 2] + zeroindex demand[row,3];])
            # z = vcat(z, transpose(demand[row,:]))
        end
    end
    return z[2:end,:]

end

## Parse Matrix
function ParseMatrix(filename, num_nodes, num_demand)
    ignore = ()
    start_range = 0
    end_range = Inf
    x = readdlm(filename)[num_demand,:]
    # println(x)
    m = zeros(length(x)-Int(sqrt(length(x))), 3)
    # m = zeros(length(x)-round(Int, sqrt(length(x))), 3)
    fromNode = 0
    count = 1
    for i in 0:(num_nodes^2-1)
        toNode = i%num_nodes + 1
        if toNode == 1
            fromNode += 1
        end
        if (fromNode != toNode && i >= start_range && i < end_range && !(fromNode in ignore) && !(toNode in ignore))
            m[count,1] = fromNode
            m[count,2] = toNode
            m[count,3] = x[i+1]
            count += 1
        end
    end
    ret = m
    for row in size(m,1):-1:1
        if (m[row,3] == 0)
            ret = ret[setdiff(1:end, row), :]
        end
    end
    return ret
end

## Tunnels routing
function getTunnels(IPtopo, flows, k, verbose, scenarios; edge_disjoint=1)  # 1 means k shortest path, 2 means IPedge disjoint, 3 means fiber disjoint
    nodes = IPtopo["nodes"]
    edges = IPtopo["links"]
    capacities = IPtopo["capacity"]
    fiberroute = IPtopo["link_fiberroute"]
    num_edges = length(edges)
    # print("num_edges:", num_edges)
    # print(edges)
    num_nodes = length(nodes)
    num_flows = length(flows)
    paral_edges = []
    # count number of paralle links for adding dummy links/nodes
    dummy_nodes = 0
    for l in 1:num_edges
        if edges[l][3] > 1  # parallel IP links
            dummy_nodes += 1
            push!(paral_edges, edges[l])
        end
    end

    graph = LightGraphs.SimpleDiGraph(num_nodes + dummy_nodes)  # considering parallel links
    distances = Inf*ones(num_nodes+dummy_nodes, num_nodes+dummy_nodes)

    # println("paral_edges: ", length(paral_edges), paral_edges)

    for i in 1:num_edges
        if edges[i][3] == 1
            LightGraphs.add_edge!(graph, edges[i][1], edges[i][2])
            if edge_disjoint == 5  # weight KSP
                distances[edges[i][1], edges[i][2]] = 1 / capacities[i]  # weight edge, more capacity means less distance
                distances[edges[i][2], edges[i][1]] = 1 / capacities[i]  # weight edge, more capacity means less distance
            else
                distances[edges[i][1], edges[i][2]] = 1
                distances[edges[i][2], edges[i][1]] = 1
            end
        else
            e = findfirst(x -> x == (edges[i][1], edges[i][2], edges[i][3]), paral_edges)
            # println("parallel link: ", e, paral_edges[e])
            LightGraphs.add_edge!(graph, edges[i][1], num_nodes+e)
            LightGraphs.add_edge!(graph, num_nodes+e, edges[i][2])
            distances[edges[i][1], num_nodes+e] = 0.5
            distances[num_nodes+e, edges[i][2]] = 0.5
            # distances[edges[i][2], num_nodes+e] = 0.5
            # distances[num_nodes+e, edges[i][1]] = 0.5
        end
    end

    T = []
    Tf = []
    ti = 1  # global tunnel index
    max_k = 1

    # KSP routing
    if edge_disjoint == 1
        printstyled("KSP tunnel routing\n", color=:blue)
        for f in 1:num_flows
            tf = []
            curr_k = 0

            state = LightGraphs.yen_k_shortest_paths(graph, flows[f][1], flows[f][2], distances, k)
            paths = state.paths
            edges_used = []
            fiber_used = []
            for i in 1:k
                t = []
                traversed_fiber = []
                path = i <= size(paths,1) ? paths[i] : []
                jump_flag = 0
                for n in 2:size(path,1)
                    if path[n] > num_nodes   # use parallel links
                        e = findfirst(x -> x == (path[n-1], path[n+1], paral_edges[path[n]-num_nodes][3]), edges)
                        jump_flag = 1
                        push!(t, e)
                    elseif jump_flag == 0
                        e = findfirst(x -> x == (path[n-1], path[n], 1), edges)
                        push!(t, e)
                        for b in 1:length(fiberroute[e])
                            if in(fiberroute[e][b], traversed_fiber)
                                continue
                            else
                                push!(traversed_fiber, fiberroute[e][b])
                            end
                        end
                        f = edges[e]
                    elseif jump_flag == 1
                        jump_flag = 0
                    end
                end

                if length(t) == 0 break end

                push!(T, t)
                push!(tf, ti)
                ti += 1
                curr_k += 1

            end
            push!(Tf, tf)
            if verbose println("K-shortest tunnel routing: $(paths)") end
        end

    # IPedge disjoint routing
    elseif edge_disjoint == 2
        printstyled("IP disjoint tunnel routing\n", color=:blue)
        for f in 1:num_flows
            tf = []
            disjoint_graph = deepcopy(graph)
            for i in 1:k
                state = LightGraphs.yen_k_shortest_paths(disjoint_graph, flows[f][1], flows[f][2], distances, 1)
                paths = state.paths
                # println("paths ", paths)
                t = []
                path = 0 < size(paths,1) ? paths[1] : []  # because we use KSP k=1
                # println("path ", path)
                for n in 2:size(path,1)
                    e = findfirst(x -> x == (path[n-1], path[n], 1), edges)
                    push!(t, e)
                    remove_status = LightGraphs.rem_edge!(disjoint_graph, path[n-1], path[n])
                    # println("remove edge $(path[n-1])-$(path[n]), $(remove_status)")
                    distances[path[n-1], path[n]] = Inf
                end

                if length(t) == 0 break end

                push!(T, t)
                push!(tf, ti)
                ti += 1
            end
            push!(Tf, tf)
        end

    # fiber disjoint routing
    elseif edge_disjoint == 3
        printstyled("fiber disjoint tunnel routing\n", color=:blue)
        for f in 1:num_flows
            tf = []
            disjoint_graph = deepcopy(graph)
            for i in 1:k
                state = LightGraphs.yen_k_shortest_paths(disjoint_graph, flows[f][1], flows[f][2], distances, 1)
                paths = state.paths
                # println("paths ", paths)
                t = []
                path = 0 < size(paths,1) ? paths[1] : []  # because we use KSP k=1
                # println("path ", path)
                for n in 2:size(path,1)
                    e = findfirst(x -> x == (path[n-1], path[n], 1), edges)
                    push!(t, e)
                    # remove all IP edges on related b fiber
                    for b in 1:length(fiberroute[e])
                        for ee in 1:num_edges
                            if fiberroute[e][b] in fiberroute[ee]
                                remove_status = LightGraphs.rem_edge!(disjoint_graph, edges[ee][1], edges[ee][2])
                                # println("remove edge $(edges[ee][1])-$(edges[ee][2]), $(remove_status)")
                                distances[edges[ee][1], edges[ee][2]] = Inf
                            end
                        end
                    end
                    # the reverse path fiber should also be removed because fiber are bidirectional
                    re = findfirst(x -> x == (path[n], path[n-1], 1), edges)
                    for b in 1:length(fiberroute[re])
                        for ee in 1:num_edges
                            if fiberroute[re][b] in fiberroute[ee]
                                remove_status = LightGraphs.rem_edge!(disjoint_graph, edges[ee][1], edges[ee][2])
                                # println("remove edge $(edges[ee][1])-$(edges[ee][2]), $(remove_status)")
                                distances[edges[ee][1], edges[ee][2]] = Inf
                            end
                        end
                    end
                end

                if length(t) == 0 break end

                push!(T, t)
                push!(tf, ti)
                ti += 1
            end
            push!(Tf, tf)
        end

    # failure aware routing, in real life tunnels should be properly routed such that failure will disrupt all tunnels of a flow
    elseif edge_disjoint == 4 || edge_disjoint == 5
        printstyled("failure aware tunnel routing\n", color=:blue)
        nscenarios = length(scenarios)
        for f in 1:num_flows
            accumulate_k = 0
            tf = []

            # failure scenario aware tunnel routing
            for s in 1:nscenarios
                disjoint_graph = deepcopy(graph)
                for q in 1:length(scenarios[s])
                    if scenarios[s][q] == 0
                        remove_status = LightGraphs.rem_edge!(disjoint_graph, edges[q][1], edges[q][2])
                        distances[edges[q][1], edges[q][2]] = Inf
                    end
                end
                state = LightGraphs.yen_k_shortest_paths(disjoint_graph, flows[f][1], flows[f][2], distances, 1)
                paths = state.paths
                t = []
                path = 0 < size(paths,1) ? paths[1] : []  # because we use KSP k=1

                for n in 2:size(path,1)
                    e = findfirst(x -> x == (path[n-1], path[n], 1), edges)
                    push!(t, e)
                end

                if length(t) == 0 break end

                if !in(t, T) && accumulate_k < k
                    push!(T, t)
                    push!(tf, ti)
                    ti += 1
                    accumulate_k += 1
                end
            end

            # if tunnel number is smaller than k, then use KSP to fill it
            if accumulate_k < k
                state = LightGraphs.yen_k_shortest_paths(graph, flows[f][1], flows[f][2], distances, k)
                paths = state.paths

                for i in 1:k
                    t = []
                    traversed_fiber = []
                    path = i <= size(paths,1) ? paths[i] : []
                    jump_flag = 0
                    for n in 2:size(path,1)
                        if path[n] > num_nodes   # use parallel links
                            e = findfirst(x -> x == (path[n-1], path[n+1], paral_edges[path[n]-num_nodes][3]), edges)
                            jump_flag = 1
                            push!(t, e)
                        elseif jump_flag == 0
                            e = findfirst(x -> x == (path[n-1], path[n], 1), edges)
                            push!(t, e)
                            for b in 1:length(fiberroute[e])
                                if in(fiberroute[e][b], traversed_fiber)
                                    continue
                                else
                                    push!(traversed_fiber, fiberroute[e][b])
                                end
                            end
                            f = edges[e]
                        elseif jump_flag == 1
                            jump_flag = 0
                        end
                    end

                    if length(t) == 0 break end

                    if !in(t, T) && accumulate_k < k
                        push!(T, t)
                        push!(tf, ti)
                        ti += 1
                        accumulate_k += 1
                    end
                end
            end
            push!(Tf, tf)
        end
    end
    return T, Tf, max_k, graph
end

## parse tunnels from input files (format 1)
function parseTunnels(allocs_tunnels_paths, links)
    println("Reading tunnels from: $(allocs_tunnels_paths)")
    allocs_tunnels = readdlm(allocs_tunnels_paths)

    num_lines = size(allocs_tunnels,1)
    flows = []
    T = [[]]
    Tf = []
    tf = []
    max_t_paths = 0
    num_flows = 0

    for i in 1:num_lines
        # Reading flows
        if allocs_tunnels[i, 1] == "Flow:"
            flow_str = allocs_tunnels[i, 2][2:end-1]
            f1, f2 = split(flow_str, ",")
            f1, f2 = parse(Int64, f1) + 1, parse(Int64, f2) + 1
            flow = (f1, f2)

            push!(flows, flow)
            if num_flows > 0
                push!(Tf, tf)
            end

            num_flows = num_flows + 1
            tf = []

        # Reading tunnels and allocations
        elseif allocs_tunnels[i, 2] == ""
            _hops = split(allocs_tunnels[i, 1], ";")[1:end-1]
            t = []
            for _hop in _hops
                _hop_str = _hop[2:end-1]
                _h1, _h2 = split(_hop_str, ",")
                _h1, _h2 = parse(Int64, _h1) + 1, parse(Int64, _h2) + 1
                _hop_int = (_h1, _h2)

                if _h1 != 0 && _h2 != 0
                    e = findfirst(x -> (x[1], x[2]) == _hop_int, links)
                    if e !== nothing
                        push!(t, e)
                    end
                end
            end

            if !in(t, T)
                push!(T, t)
            end

            _t_idx = findfirst(x -> x == t, T)
            push!(tf, _t_idx)
            max_t_paths = max(max_t_paths, size(tf, 1))
        end
    end

    push!(Tf, tf)

    Tf_adjusted = []
    t_empty = findfirst(x -> x == [], T)

    for f in 1:size(flows, 1)
        tf = Tf[f]
        t = T[f]
        if length(tf) == max_t_paths
            push!(Tf_adjusted, tf)
        else
            missing_tunnels = max_t_paths - length(tf)

            for i in 1:missing_tunnels
                push!(tf, t_empty)
            end
            push!(Tf_adjusted, tf)
        end
    end

    return T, Tf_adjusted, flows
end

## parse tunnels from input files (format 2)
function parsePaths(filename, links, flows; zeroindex=false)
    filename = joinpath(@__DIR__, "./data/$filename")
    nflows = length(flows)
    x = readdlm(filename)
    T = []
    Tf = [[] for i=1:nflows]
    tf = []
    fromNode = 0
    toNode = 0
    num_flow = 0
    tindex = 1
    max_paths = 0
    paths = Matrix(undef, nflows, nflows)
    for row in 1:size(x,1)
        if "->" in x[row,:]
            max_paths = max(max_paths, size(tf, 1))
            if fromNode != 0 && num_flow != 0
                #add tunnel if not first run
                paths[fromNode, toNode] = tf
                Tf[num_flow] = tf
            end
            if x[row,1] isa Number
                fromNode = x[row, 1] + zeroindex
                toNode = x[row, 3] + zeroindex
            else
                fromNode = parse(Int, replace(x[row, 1], "h" => "")) + zeroindex
                toNode = parse(Int, replace(x[row, 3], "h" => "")) + zeroindex
            end
            num_flow = findfirst(x -> x == (fromNode, toNode), flows)
            if num_flow === nothing
                num_flow = 0
            end
            tf = []
        else
            t = []
            #parse each tunnel into edges
            for col in 2:size(x,2)
                if occursin("]", x[row,col])
                    break
                end
                r = replace(x[row,col][2:end-2], "s" => "")
                stringtup = split(r, ",")
                #find edge in edge matrix
                e = (parse(Int,stringtup[1]) + zeroindex, parse(Int,stringtup[2]) + zeroindex)
                index = findfirst(x -> x == e, links)
                push!(t, index)
            end
            #create new tunnel and add index to that flows tunnels
            if num_flow != 0
                push!(T, t)
                push!(tf, tindex)
                tindex += 1
            end
        end
    end

    #add last tunnel
    if num_flow != 0
        Tf[num_flow] = tf
        paths[fromNode, toNode] = tf
    end
    push!(T, [])

    for f in 1:size(Tf,1)
        for t in 1:max_paths
            try Tf[f][t]
            catch
                push!(Tf[f], tindex)
            end
        end
    end
    return T, Tf, max_paths
end


module LangrethsRules

using LinearAlgebra

export TimeOrderedGreenFunction, TimeOrderedConvolution
export greater, lesser

include("common.jl")
include("rules.jl")

end # module


#=
Dimensionality Reduction methods. Each method needs
1) Type with fields necessary for algorithm
2) function "reduction" to operate on sort with type field defined above
3) any other necessary functions for alignment algorithm
=#

export ReductionNone

function reductionprepare(r::Reduction,sort::Sorting)
    nothing
end

#=
No Dimensionality Reduction
=#
mutable struct ReductionNone <: Reduction
end

#=
Maximum Difference

Gibson et al 2010
=#
#=
type ReductionMD <: Reduction
    mydims::Int64
    maximum_difference::Array{Int64,1}
    local_difference::Array{Float64,1}
    spike_old::Array{Float64,1}
    Dc::Array{Int64,1}
end

ReductionMD()=ReductionMD(10,4)

ReductionMD(dims::Int64)=ReductionMD(10,dims)

ReductionMD(N::Int64,dims::Int64)=ReductionMD(dims,zeros(Int64,N),zeros(Float64,N),zeros(Float64,N),zeros(Int64,dims))

function reductionprepare(r::ReductionMD,sort::Sorting)

    max3ind=zeros(Int64,3)
    for i=1:length(sort.fullfeature)
        sort.r.local_difference[i]=abs(sort.r.spike_old[i]-sort.fullfeature[i])

        if sort.r.local_difference[i]>max3ind[1]
            if sort.r.local_difference[i]>max3ind[2]
                if sort.r.local_difference[i]>max3ind[3]
                    max3ind[1]=max3ind[2]
                    max3ind[2]=max3ind[3]
                    max3ind[3]=i
                end
                max3ind[1]=max3ind[2]
                max3ind[2]=i
            end
            max3ind[1]=i
        end
    end

    sort.r.maximum_difference[max3ind]+=1
    sort.r.spike_old[:]=sort.fullfeature[:]

    for i in max3ind
        for j=1:length(sort.r.Dc)
            if sort.r.maximum_difference[i]>sort.r.Dc[j]
                if i != sort.dims[j]
                    sort.dims[j]=i
                end
                sort.r.Dc[j]=sort.r.maximum_difference[i]
                break
            end
        end
    end
    nothing
end
=#
#=
Lilliefor's Test
=#
#=
type ReductionLT <: Reduction
end
=#
#=
Hartigan's Dip
=#
#=
type ReductionHD <: Reduction
end
=#
#=
Uniform Sampling
=#
#=
type ReductionUS <: Reduction
end
=#


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

@doc doc"""
    GenericDensity{F<:Function} <: AbstractDensity

Constructors:

    GenericDensity(f)

Turns the density function `f` into a BAT-compatible [`AbstractDensity)(@ref).
`f(v)` must return either a [`LogDVal`](@ref) (recommended) or a
[`LinDVal`](@ref).

It must be safe to execute `f` in parallel on multiple threads and
processes.
"""
struct GenericDensity{F<:Function} <: AbstractDensity
    f::F
end

Base.convert(::Type{GenericDensity}, f::Function) = GenericDensity(f)
Base.convert(::Type{AbstractDensity}, f::Function) = GenericDensity(f)

Base.parent(density::GenericDensity) = density.f

function eval_logval_unchecked(density::GenericDensity, v::Any)
    logvalof(density.f(v))
end


module HHPC

# Stdlib Requirements
using Logging
using Printf
using Random
using Statistics

# Non-stdlib essentials
using StaticArrays

# Open-loop requirements
using Graphs

# POMDP Stuff
using POMDPs
using POMDPModelTools
using POMDPModels
using POMDPPolicies
using POMDPSimulators
using LocalFunctionApproximation
using LocalApproximationValueIteration

# For saving and loading
using JLD2
using FileIO

# CMSSPs submodule
using CMSSPs.CMSSPModel

# Open-loop exports
export
    GraphTracker,
    metadatatype,
    bookkeepingtype,
    open_loop_plan!

# Closed-loop exports
export
    ModalHorizonPolicy,
    ModalFinInfHorPolicy,
    ModalStateAugmented,
    ModalAction,
    ModalFinHorMDP,
    ModalInfHorMDP,
    set_horizon_limit!,
    compute_terminalcost_localapprox!,
    finite_horizon_VI_localapprox!,
    infinite_horizon_VI_localapprox,
    compute_min_value_per_horizon_localapprox!,
    horizon_weighted_value,
    inf_hor_value,
    horizon_weighted_actionvalue,
    get_best_intramodal_action

# HHPC framework
# Requirements
# ensure that mode-switch MDP available?
export
    HHPCSolver,
    set_start_state!,
    set_open_loop_samples!

# Utils
export
    save_localapproxvi_policy_to_jld2,
    load_localapproxvi_policy_from_jld2,
    load_modal_horizon_policy_localapprox,
    save_modal_horizon_policy_localapprox,
    log2space_symmetric,
    polyspace_symmetric

# General functions that need to be implemented
export
    # Local
    expected_reward,
    get_relative_state,
    get_mdp,
    # Global
    update_vertices_with_context!,
    generate_goal_vertex_set!,
    generate_next_valid_modes,
    generate_bridge_vertex_set!,
    update_next_target!,
    # HHPC
    simulate_cmssp!,
    update_context_set!,
    display_context_future

# Local reqs
function get_relative_state end
function expected_reward end
function get_mdp end
# Global reqs
function update_vertices_with_context! end
function generate_goal_vertex_set! end
function generate_next_valid_modes end
function generate_bridge_vertex_set! end
function update_next_target! end
# HHPC reqs
function simulate_cmssp! end
function update_context_set! end
function get_bridging_action end
function display_context_future end

include("global_layer.jl")
include("local_layer.jl")
include("hhpc_framework.jl")
include("utils.jl")

end


export iauSp00
"""
The TIO locator s', positioning the Terrestrial Intermediate Origin
on the equator of the Celestial Intermediate Pole.

This function is part of the International Astronomical Union's
SOFA (Standards Of Fundamental Astronomy) software collection.

Status:  canonical model.

Given:
   date1,date2  double    TT as a 2-part Julian Date (Note 1)

Returned (function value):
               double    the TIO locator s' in radians (Note 2)

Notes:

   1. The TT date date1+date2 is a Julian Date, apportioned in any
   convenient way between the two arguments.  For example,
   JD(TT)=2450123.7 could be expressed in any of these ways,
   among others:

         date1          date2

      2450123.7           0.0       (JD method)
      2451545.0       -1421.3       (J2000 method)
      2400000.5       50123.2       (MJD method)
      2450123.5           0.2       (date & time method)

   The JD method is the most natural and convenient to use in
   cases where the loss of several decimal digits of resolution
   is acceptable.  The J2000 method is best matched to the way
   the argument is handled internally and will deliver the
   optimum resolution.  The MJD method and the date & time methods
   are both good compromises between resolution and convenience.

   2. The TIO locator s' is obtained from polar motion observations by
   numerical integration, and so is in essence unpredictable.
   However, it is dominated by a secular drift of about
   47 microarcseconds per century, which is the approximation
   evaluated by the present function.

Reference:

   McCarthy, D. D., Petit, G. (eds.), IERS Conventions (2003),
   IERS Technical Note No. 32, BKG (2004)

This revision:  2013 June 18

SOFA release 2018-01-30

Copyright (C) 2018 IAU SOFA Board.  See notes at end.
"""

# double iauSp00(double date1, double date2)

function iauSp00(date1::Real, date2::Real)
   return ccall((:iauSp00, libsofa_c), Cdouble,
               (Cdouble, Cdouble),
               convert(Float64, date1), convert(Float64, date2))
end


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

"""
$(TYPEDSIGNATURES)

Real from ASCII files all observed/measured data from a set of experiments.
It can read data for one or more proteins.
The entalphy values are *scaled* by a factor given by the argument `scalfactor`,
 which defaults to 0.004184 (Cal/mol to kJ/mol).

# Arguments

- `inpdir`: directory containing the input data
- `proteinnames`: array of strings containing the names of proteins
- `scalfactor`=0.004184: scaling factor for enthalpy, defaults
                        to 0.004184 (Cal/mol to kJ/mol)
- `discninitrows`=0: number of initial rows of the data set to discard. This is used
                    to remove some initial data often affected by strong
                    instrument noise which could bias the fitting process.

"""
function readallexperiments(inpdir::String,proteinnames::Vector{String} ;
                            scalfactor::Float64=0.004184 ,
                            discninitrows::Integer=0)
    ## swith units, from Cal/mol to kJ/mol by multiplying by 0.004184
    @assert discninitrows>=0
    starow = discninitrows+1
    #@show discninitrows

    println("Reading data from directory: $inpdir")
    println(" Scaling enthalpy values by a factor $scalfactor")

    numprot = length(proteinnames)
    data = Dict()

    for p=1:numprot

        data[proteinnames[p]] = Dict()
        data[proteinnames[p]]["sdscon"] = Float64[]
        data[proteinnames[p]]["procon"] = Float64[]
        data[proteinnames[p]]["enout"] = Float64[]
        data[proteinnames[p]]["idxdata"] = UnitRange{Int64}[]

        sdscon = data[proteinnames[p]]["sdscon"]
        procon = data[proteinnames[p]]["procon"]
        enout = data[proteinnames[p]]["enout"]
        idxdata = data[proteinnames[p]]["idxdata"]

        # get a list of files in inpdir
        fllist = readdir(joinpath(inpdir,proteinnames[p]))
        # @show fllist
        # @show occursin(proteinnames[p],fllist[1])
        # @show endswith(fllist[1],".DAT")
        # select only files starting with
        checkfile(x) = (occursin(proteinnames[p],x) && ( endswith(x,".DAT") || endswith(x,".dat") ) )
        fllist = filter(checkfile,fllist)
        println(" Input file list: ")

        startind = 1
        for fl in fllist
            println("  $fl")

            seflname = joinpath(inpdir,proteinnames[p]*"/"*fl)
            csds1,cpro1,ceout1 = readsingleexperiment(seflname)

            # discard some initial rows (strongly affected by instrument error)
            csds1 = csds1[starow:end]
            cpro1 = cpro1[starow:end]
            ceout1 = ceout1[starow:end]

            # add to dictionary
            lendata = length(ceout1)
            push!(sdscon,csds1...)
            push!(procon,cpro1...)
            push!(enout,ceout1...)
            # indices of individual experiments
            push!(idxdata,startind:(startind+lendata-1))
            startind += lendata
        end

        ## scale enthalpy
        enout .*= scalfactor

    end

    return data
end

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

"""
$(TYPEDSIGNATURES)

Real from ASCII file the observed/measured data from an experiment.
"""
function readsingleexperiment(singlefl::String)
    ## Only column 3 (Xt), 4 (Mt) and 6 (NDH) are relevant
    #
    # Xt is the concentration of SDS in the cell (in mM). This
    # is initially 0, and increases during the experiment.
    #
    # Mt is the protein concentration. This is initially the
    # value stated in the header of the file (in mM), and
    # decreases slightly during the experiment (as it is
    # diluted with the SDS).
    #
    # NDH is the energy output of that single injection in J/mol..
    #

    # REMARK:
    # For DH, INJV, XMt and NDH an initial row with a zero should be added.
    # Xt and Mt are initially one row longer than the other columns, and
    #   this is corrected for by the additional zeros.

    data,header = readdlm(singlefl,header=true)
    header = vec(header)

    idxXt = findall(x->x=="Xt",header)[1]
    idxMt = findall(x->x=="Mt",header)[1]
    @assert idxXt<idxMt

    ## DH	INJV	Xt	Mt	XMt	NDH
    # the last row contains only Xt and Mt at the beginning...
    data2 = data[1:end-1,:]
    lasttXtMt = data[end,:]

    idx=findall(x->x=="Xt",header)[1]
    # Xt in the last row of data is in the second column
    sdscon = convert.(Float64,[data2[:,idx]; lasttXtMt[2]])

    idx=findall(x->x=="Mt",header)[1]
    # Xt in the last row of data is in the third column
    procon = convert.(Float64,[data2[:,idx]; lasttXtMt[3]])

    idx=findall(x->x=="NDH",header)[1]
    enout = convert.(Float64,[0.0; data2[:,idx]])

    return sdscon,procon,enout
end

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


module PGEN
using BitIntegers
import Mmap: mmap
import Base: unsafe_load
export Pgen, iterator, n_samples, n_variants, get_genotypes, get_genotypes!
export alt_allele_dosage, alt_allele_dosage!, ref_allele_dosage, ref_allele_dosage!
BitIntegers.@define_integers 24
const variant_type_lengths = Dict(
    0x00 => (4, 1), 0x01 => (4, 2), 0x02 => (4, 3), 0x03 => (4, 4),
    0x04 => (8, 1), 0x05 => (8, 2), 0x06 => (8, 3), 0x07 => (8, 4)
)
const bytes_to_UInt = Dict(0x01 => UInt8, 0x02 => UInt16, 0x03 => UInt24, 0x04 => UInt32, 0x08 => UInt64)
const mask_map = [0x01, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0xff]

@inline ceil_int(x::Integer, y::Integer) = (x ÷ y) + (x % y != 0)
include("uleb128.jl")
include("internal_structs.jl")
include("structs.jl")
include("header.jl")
include("difflist.jl")
include("iterator.jl")
include("genotype.jl")
include("dosage.jl")
datadir(parts...) = joinpath(@__DIR__, "..", "data", parts...)
end


module RecursiveSparseBlocks

export SparseMatrixRSB

include("../deps/deps.jl")
include("constants.jl")

const RSBCooIdx = Cint
const RSBNnzIdx = Cint
const RSBType   = Cchar
const RSBFlags  = Cint
const RSBErr    = Cint
const RSBChar   = Cchar
const RSBBlkIdx = Cint
const RSBTrans  = RSBFlags

function __init__()
    # init library with default options
    err = ccall((:rsb_lib_init, librsb), RSBErr,
                (Ptr{Void},), C_NULL)

    if err != 0
        error("rsb_lib_init failed with code $(err)")
    end
end

type SparseMatrixRSB{T} <: AbstractSparseMatrix{T, Cint}
    ptr::Ptr{Void}
end

function rsb_mtx_free(A::SparseMatrixRSB)
    ccall((:rsb_mtx_free, librsb), Ptr{Void}, (Ptr{Void},), A.ptr)
end

function check_rsb_error(err::RSBErr)
    if err != RSB_ERR_NO_ERROR
        buflen = 5000
        buf = Array{UInt8}(buflen)
        ccall((:rsb_strerror_r, librsb), RSBErr, (RSBErr, Ptr{RSBChar}, Csize_t),
              err, buf, buflen)
        i = findfirst(0x0, buf)
        if i == 0
            error("SparseMatrixRSB operation failed with code $(err)")
        else
            error(String(buf[1:i-1]))
        end
    end
end

@inline function rsb_type_code{T}(::Type{T})
    typ = T == Float32    ? 'S' :
          T == Float64    ? 'D' :
          T == Complex64  ? 'C' :
          T == Complex128 ? 'Z' :
          error("librsb does not support matrices of type $T")
    return typ
end

function SparseMatrixRSB{T}(A::SparseMatrixCSC{T})
    typ = rsb_type_code(T)
    m, n = size(A)
    nnz = length(A.nzval)

    IA = Vector{RSBCooIdx}(A.rowval)
    CP = Vector{RSBCooIdx}(A.colptr)

    # make zero based
    IA .-= 1
    CP .-= 1

    err = Ref{RSBErr}()
    ptr = ccall((:rsb_mtx_alloc_from_csc_const, librsb), Ptr{Void},
                (Ptr{Void}, Ptr{RSBCooIdx}, Ptr{RSBCooIdx}, RSBNnzIdx, RSBType,
                 RSBCooIdx, RSBCooIdx, RSBBlkIdx, RSBBlkIdx, RSBFlags,
                 Ptr{RSBErr}),
                A.nzval, IA, CP, nnz, typ, m, n,
                RSB_DEFAULT_ROW_BLOCKING, RSB_DEFAULT_COL_BLOCKING,
                RSB_FLAG_DEFAULT_RSB_MATRIX_FLAGS, err)
    check_rsb_error(err.x)

    ret = SparseMatrixRSB{T}(ptr)
    finalizer(ret, rsb_mtx_free)
    return ret
end

function SparseMatrixRSB{T}(I_, J_, V::Vector{T}, m::Integer, n::Integer)
    typ = rsb_type_code(T)
    nnz = length(V)
    I = Vector{RSBCooIdx}(I_)
    J = Vector{RSBCooIdx}(J_)

    # make zero based
    I .-= 1
    J .-= 1

    err = Ref{RSBErr}()
    ptr = ccall((:rsb_mtx_alloc_from_coo_const, librsb), Ptr{Void},
                (Ptr{Void}, Ptr{RSBCooIdx}, Ptr{RSBCooIdx}, RSBNnzIdx, RSBType,
                 RSBCooIdx, RSBCooIdx, RSBBlkIdx, RSBBlkIdx, RSBFlags,
                 Ptr{RSBErr}),
                V, I, J, nnz, typ, m, n,
                RSB_DEFAULT_ROW_BLOCKING, RSB_DEFAULT_COL_BLOCKING,
                RSB_FLAG_DEFAULT_RSB_MATRIX_FLAGS, err)
    check_rsb_error(err.x)

    ret = SparseMatrixRSB{T}(ptr)
    finalizer(ret, rsb_mtx_free)
    return ret
end

function Base.convert{T}(::Type{SparseMatrixCSC}, A::SparseMatrixRSB{T})
    I, J, V = findnz(A)
    m, n = size(A)
    return sparse(I, J, V, m, n)
end

function Base.findnz{T}(A::SparseMatrixRSB{T})
    N = nnz(A)
    V = Array{T}(N)
    I = Array{RSBCooIdx}(N)
    J = Array{RSBCooIdx}(N)

    err = ccall((:rsb_mtx_get_coo, librsb), RSBErr,
                (Ptr{Void}, Ptr{Void}, Ptr{RSBCooIdx}, Ptr{RSBCooIdx},
                 RSBFlags),
                A.ptr, V, I, J, RSB_FLAG_FORTRAN_INDICES_INTERFACE)
    check_rsb_error(err)

    return (I, J, V)
end

function Base.copy{T}(A::SparseMatrixRSB{T})
    typ = rsb_type_code(T)
    Bptr = Ref{Ptr{Void}}()
    err = ccall((:rsb_mtx_clone, librsb), RSBErr,
                (Ptr{Ptr{Void}}, RSBType, RSBTrans, Ptr{Void}, Ptr{Void}, RSBFlags),
                BPtr, typ, RSB_TRANSPOSITION_N, C_NULL, A.ptr,
                RSB_FLAG_DEFAULT_RSB_MATRIX_FLAGS)
    check_rsb_error(err)

    ret = SparseMatrixRSB{T}(Bptr.x)
    finalizer(ret, rsb_mtx_free)
    return ret
end

function Base.eltype{T}(A::SparseMatrixRSB{T})
    return T
end

function Base.show(io::IO, ::MIME"text/plain", A::SparseMatrixRSB)
    m, n = size(A)
    print(io, m, "×", n, " sparse RSB matrix with ", nnz(A), " ", eltype(A),
          " stored entries")
    if nnz(A) != 0
        print(io, ":")
        show(io, A)
    end
end

Base.show(io::IO, S::SparseMatrixRSB) = Base.show(convert(IOContext, io),
                                                  S::SparseMatrixRSB)

function Base.show(io::IOContext, S::SparseMatrixRSB)
    # Taken mostly from SparseMatrixCSC show function

    if nnz(S) == 0
        return show(io, MIME("text/plain"), S)
    end

    limit::Bool = get(io, :limit, false)
    if limit
        rows = displaysize(io)[1]
        half_screen_rows = div(rows - 8, 2)
    else
        half_screen_rows = typemax(Int)
    end
    pad = ndigits(max(size(S)...))
    k = 0
    sep = "\n  "
    if !haskey(io, :compact)
        io = IOContext(io, :compact => true)
    end

    I, J, V = findnz(S)
    k = 0
    for (i, j, v) in zip(I, J, V)
        if k < half_screen_rows || k > nnz(S)-half_screen_rows
            print(io, sep, '[',
                  rpad(i, pad), ", ",
                  lpad(j, pad), "]  =  ")
            Base.show(io, v)
        elseif k == half_screen_rows
            print(io, sep, '\u22ee')
        end
        k += 1
    end
end

function Base.nnz(A::SparseMatrixRSB)
    out = Ref{RSBNnzIdx}()
    err = ccall((:rsb_mtx_get_info, librsb), RSBErr,
                (Ptr{Void}, Cint, Ptr{Void}),
                A.ptr, RSB_MIF_MATRIX_NNZ__TO__RSB_NNZ_INDEX_T, out)
    check_rsb_error(err)
    return Int(out.x)
end

function rsb_get_nrows(A::SparseMatrixRSB)
    out = Ref{RSBCooIdx}()
    err = ccall((:rsb_mtx_get_info, librsb), RSBErr,
                (Ptr{Void}, Cint, Ptr{Void}),
                A.ptr, RSB_MIF_MATRIX_ROWS__TO__RSB_COO_INDEX_T, out)
    check_rsb_error(err)
    return out.x
end

function rsb_get_ncols(A::SparseMatrixRSB)
    out = Ref{RSBCooIdx}()
    err = ccall((:rsb_mtx_get_info, librsb), RSBErr,
                (Ptr{Void}, Cint, Ptr{Void}),
                A.ptr, RSB_MIF_MATRIX_COLS__TO__RSB_COO_INDEX_T, out)
    check_rsb_error(err)
    return out.x
end

function Base.size(A::SparseMatrixRSB)
    return (Int(rsb_get_nrows(A)),
            Int(rsb_get_ncols(A)))
end

function Base.size(A::SparseMatrixRSB, k::Integer)
    if k == 1
        return Int(rsb_get_nrows(A))
    elseif k == 2
        return Int(rsb_get_ncols(A))
    else
        return 1
    end
end

function Base.getindex{T}(A::SparseMatrixRSB{T}, i::Integer, j::Integer)
    yptr = Ref{T}()
    iptr = Ref{RSBCooIdx}(i)
    jptr = Ref{RSBCooIdx}(j)
    err = ccall((:rsb_mtx_get_vals, librsb), RSBErr,
                (Ptr{Void}, Ptr{Void}, Ptr{Void}, Ptr{Void}, RSBNnzIdx,
                 RSBFlags),
                A.ptr, yptr, iptr, jptr, 1, RSB_FLAG_FORTRAN_INDICES_INTERFACE)
    if err == -1
        return zero(T)
    end
    check_rsb_error(err)
    return yptr.x
end

function Base.setindex!{T}(A::SparseMatrixRSB{T}, value, i::Integer, j::Integer)
    yptr = Ref{T}(value)
    iptr = Ref{RSBCooIdx}(i)
    jptr = Ref{RSBCooIdx}(j)
    err = ccall((:rsb_mtx_set_vals, librsb), RSBErr,
                (Ptr{Void}, Ptr{Void}, Ptr{Void}, Ptr{Void}, RSBNnzIdx,
                 RSBFlags),
                A.ptr, yptr, iptr, jptr, 1, RSB_FLAG_FORTRAN_INDICES_INTERFACE)
    check_rsb_error(err)
    return yptr.x
end

function Base.diag{T}(A::SparseMatrixRSB{T})
    v = Vector{T}(min(size(A)...))
    err = ccall((:rsb_mtx_get_vec, librsb), RSBErr,
                (Ptr{Void}, Ptr{Void}, Cint),
                A.ptr, v, RSB_EXTF_DIAG)
    check_rsb_error(err)
    return v
end

function Base.full{T}(A::SparseMatrixRSB{T})
    B = zeros(T, size(A))
    I, J, V = findnz(A)
    for (i, j, v) in zip(I, J, V)
        B[i, j] = v
    end
    return B
end

# Sparse matrix by vector multiplication
for (f, transA) in ((:A_mul_B!,  RSB_TRANSPOSITION_N),
                    (:At_mul_B!, RSB_TRANSPOSITION_T),
                    (:Ac_mul_B!, RSB_TRANSPOSITION_C))
    @eval begin
        function Base.$f{T}(y::Vector{T}, A::SparseMatrixRSB{T}, x::Vector{T})
            m, n = size(A)
            if $transA == RSB_TRANSPOSITION_N
                @assert length(x) == n
                @assert length(y) == m
            else
                @assert length(x) == m
                @assert length(y) == n
            end

            alpha = Ref{T}(one(T))
            beta  = Ref{T}(zero(T))
            err = ccall((:rsb_spmv, librsb), RSBErr,
                        (RSBTrans, Ptr{Void}, Ptr{Void}, Ptr{Void}, RSBCooIdx,
                         Ptr{Void}, Ptr{Void}, RSBCooIdx),
                        $transA, alpha, A.ptr, x, 1, beta, y, 1)
            check_rsb_error(err)
            return y
        end
    end
end

# Sparse matrix by dense matrix multiplication
for (f, transA) in ((:A_mul_B!,  RSB_TRANSPOSITION_N),
                    (:At_mul_B!, RSB_TRANSPOSITION_T),
                    (:Ac_mul_B!, RSB_TRANSPOSITION_C))
    @eval begin
        function Base.$f{T}(C::Matrix{T}, A::SparseMatrixRSB{T}, B::Matrix{T})
            ma, na = size(A)
            mb, nb = size(B)
            mc, nc = size(C)

            if $transA == RSB_TRANSPOSITION_N
                @assert na == mb
                @assert (mc, nc) == (ma, nb)
            else
                @assert ma == mb
                @assert (mc, nc) == (na, nb)
            end

            alpha = Ref{T}(one(T))
            beta  = Ref{T}(zero(T))
            err = ccall((:rsb_spmm, librsb), RSBErr,
                        (RSBTrans, Ptr{Void}, Ptr{Void}, RSBCooIdx, RSBFlags,
                         Ptr{Void}, RSBNnzIdx, Ptr{Void}, Ptr{Void}, RSBNnzIdx),
                        $transA, alpha, A.ptr, nb,
                        RSB_FLAG_WANT_COLUMN_MAJOR_ORDER, B, mb, beta, C, na)
            check_rsb_error(err)
            return C
        end
    end
end

# TODO:
# get column and row sums
# convert{S, T}(::Type{SparseMatrixRSB{S}}, ::SparseMatrixRSB{T})
# transpose / transpose!
# conj / conj!
# A_ldiv_B! / trisolve (BLAS_?ussv, BLAS_?ussm)
# + (I think we just insert elements with the right flag set if we want to add)

end


import Pkg; Pkg.activate(".")
include("../required.jl")

coords = (left = -145.0, right = -50.0, bottom = 20.0, top = 75.0)

# Test loading variables
# lc_vars = landcover(1:10, resolution = 5.0)
# lc_vars = landcover(1:10, resolution = 10.0)
# lc_vars = map(x -> landcover(x, resolution = 10.0)[coords], 1:10)

# # Test loading variables
# lc_vars = SimpleSDMPredictor(Copernicus, LandCover, 1:10; resolution = 5.0)
# lc_vars = SimpleSDMPredictor(Copernicus, LandCover, 1:10)
# lc_vars = map(x -> SimpleSDMPredictor(Copernicus, LandCover, x)[coords], 1:10)

# # Check compatibility
# lc1 = SimpleSDMPredictor(Copernicus, LandCover, 1)
# wc1 = SimpleSDMPredictor(WorldClim, BioClim, 1)
# SimpleSDMLayers._layers_are_compatible(lc1, wc1) # compatible

# # lc1 = SimpleSDMPredictor(Copernicus, LandCover, 1; coords...)
# lc1 = SimpleSDMPredictor(Copernicus, LandCover, 1)[coords]
# lc1 = lc1[top = coords.top - stride(lc1, 2)]
# wc1 = SimpleSDMPredictor(WorldClim, BioClim, 1; coords...)
# SimpleSDMLayers._layers_are_compatible(lc1, wc1) # not compatible

## Export values to compare
lc_path = joinpath("assets", "landcover")
function nothingtomissing!(df::DataFrame)
    allowmissing!(df)
    for col in eachcol(df)
        replace!(col, nothing => missing)
    end
end
function valuesdf(layers::Vector{T}) where {T <: SimpleSDMLayer}
    df = DataFrame(layers)
    select!(df, Not([:longitude, :latitude]))
    rename!(df, Symbol.("lc", 1:10))
    nothingtomissing!(df)
    return df
end

# lc_vars = map(x -> SimpleSDMPredictor(Copernicus, LandCover, x)[coords], 1:10)
# lc_vars = [l[top = coords.top - stride(l, 2)] for l in lc_vars]
lc_vars = SimpleSDMPredictor(Copernicus, LandCover, 1:10)
lc_vars = SimpleSDMPredictor(Copernicus, LandCover, 1:10; coords...)
lc1 = copy(lc_vars)[1]
wc1 = SimpleSDMPredictor(WorldClim, BioClim, 1; coords...)

# Step 1: SimpleSDMPredictor, as before
df1 = valuesdf(lc_vars)
# CSV.write(joinpath(lc_path, "landcover_values_df1.csv"), df1)
df1 = CSV.read(joinpath(lc_path, "landcover_values_df1.csv"), DataFrame)

# Step 2: SimpleSDMPredictor, with integer as default and range as overload
SimpleSDMPredictor(Copernicus, LandCover)
SimpleSDMPredictor(Copernicus, LandCover, 1)
SimpleSDMPredictor(Copernicus, LandCover, 1:10)
df2 = valuesdf(lc_vars)
# CSV.write(joinpath(lc_path, "landcover_values_df2.csv"), df2)
df2 = CSV.read(joinpath(lc_path, "landcover_values_df2.csv"), DataFrame)
isequal(df1, df2) # true
# confirmed by comparison

# Step 3. load with geotiff instead of custom ArchGDAL call
layer = 1
resolution = 10.0
path = joinpath("assets", "landcover")

# List files in path
lc_files = readdir(path)
# Filter for selected resolution
filter!(x -> occursin.("$(Int(resolution))m.tif", x), lc_files)
# Create path for selected layer only
p = joinpath.(path, lc_files)[layer]
# Use geotiff instead
geotiff(SimpleSDMPredictor, p)
ArchGDAL.getgeotransform(d)
wctransform = ArchGDAL.getgeotransform(ArchGDAL.read(joinpath(ENV["SDMLAYERS_PATH"], "WorldClim", "BioClim", "10", "wc2.1_10m_bio_1.tif")))

gdalwarp -te -180.0 -60.0 180.0 80.0 assets/landcover/lc_bare_10m.tif assets/landcover/lc_bare_10m2.tif

p2 = replace(p, ".tif" => "2.tif")
d2 = ArchGDAL.read(p2)
ArchGDAL.getgeotransform(d2)
landcover_layers2 = geotiff(SimpleSDMPredictor, p2)

l3 = geotiff(SimpleSDMPredictor, p2; coords...)
SimpleSDMLayers._layers_are_compatible(l3, l1) # not compatible
SimpleSDMLayers._layers_are_compatible(l3, wc1) # true, compatible with WorldClim!!
isequal(l3.grid, l1.grid) # grids are equal!!

# Export values
lc_files = readdir(path)
filter!(x -> occursin.("$(Int(resolution))m.tif", x), lc_files)
ps = joinpath.(path, lc_files)
lc_vars = geotiff.(SimpleSDMPredictor, ps)
lc_vars = geotiff.(SimpleSDMPredictor, ps; coords...)
lc_vars = convert.(Float32, lc_vars)
df3 = valuesdf(lc_vars)
# CSV.write(joinpath(lc_path, "landcover_values_df1.csv"), df3)
# CSV.write(joinpath(lc_path, "landcover_values_df3.csv"), df3)
df3 = CSV.read(joinpath(lc_path, "landcover_values_df3.csv"), DataFrame)
isequal(df1, df3) # true
# confirmed by comparison

# Step 4: Update function
SimpleSDMLayers._layers_are_compatible(lc1, wc1)
df4 = valuesdf(lc_vars)
# CSV.write(joinpath(lc_path, "landcover_values_df1.csv"), df4)
# CSV.write(joinpath(lc_path, "landcover_values_df4.csv"), df4)
df4 = CSV.read(joinpath(lc_path, "landcover_values_df4.csv"), DataFrame)
isequal(df1, df4) # true
# confirmed by comparison

# Step 5: stack
stack_path = joinpath("assets", "landcover", "landcover.tif")
geotiff(stack_path, lc_vars)
lc_vars2 = [geotiff(SimpleSDMPredictor, stack_path, i) for i in 1:10]
lc_vars2 = replace.(lc_vars2, NaN => nothing)
isequal(lc_vars, lc_vars2)
isequal(lc_vars[1], lc_vars2[1])
isequal(lc_vars[1].grid, lc_vars2[1].grid)
isequal(size(lc_vars[1]), size(lc_vars2[1].grid)) # not same size...
# so dimensions problem with geotiff writing and/or reading call too...

geotiff("test.tif", lc_vars[1])
test1 = geotiff(SimpleSDMPredictor, "test.tif")
isequal(size(lc_vars[1]), test1) # not equal
dtest1 = ArchGDAL.read("test.tif") # dimensions are good in tif file
ArchGDAL.getgeotransform(dtest1) # possibly are problem with left coordinate?


#!/usr/bin/env julia
include("secirc9717eurshort.jl")
sc9717plall(sc9717sex(2))
savefig("../../images/secirc9717eurshort_kv.svg")
close()
sc9717plall(sc9717sex(1))
savefig("../../images/secirc9717eurshort_m.svg")


module SynthControl

using Dates, DataFrames, Optim, LinearAlgebra, RecipesBase, Parameters
import CSV

export TreatmentPanel, SynthControlModel, fit!, isfitted, load_brexit, load_germany, load_basque, load_smoking

include("treatmentpanel.jl")
include("synthcontrolmodels.jl")
include("plotrecipes.jl")
include("load_data.jl")

end # module


using Poisson2D
using Test
using LinearAlgebra

@testset "Direct Solve" begin
    @test include("direct_test1.jl")
end


################################################################################
# MIT License
#
# Copyright (c) 2021 Hajime Nakagami<nakagami@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
################################################################################
using Decimals

function dpb_bit_to_int64(dpd::UInt64, mask::UInt64)::UInt64
    (dpd & mask) != 0 ? Int64(1) : Int64(0)
end

function dpd_to_int64(dpd::UInt)::UInt64
    # Convert DPD encodined value to int (0-999)
    # dpd: DPD encoded value. 10bit unsigned int

    b = zeros(UInt64, 10)
    b[10] = dpdBitToInt(dpd, 0x0200)
    b[9] = dpdBitToInt(dpd, 0x0100)
    b[8] = dpdBitToInt(dpd, 0x0080)
    b[7] = dpdBitToInt(dpd, 0x0040)
    b[6] = dpdBitToInt(dpd, 0x0020)
    b[5] = dpdBitToInt(dpd, 0x0010)
    b[4] = dpdBitToInt(dpd, 0x0008)
    b[3] = dpdBitToInt(dpd, 0x0004)
    b[2] = dpdBitToInt(dpd, 0x0002)
    b[1] = dpdBitToInt(dpd, 0x0001)

    b = zeros(UInt64, 3)
    if b[4] == 0
        d[3] = b[10]*4 + b[9]*2 + b[8]
        d[2] = b[7]*4 + b[6]*2 + b[5]
        d[1] = b[3]*4 + b[2]*2 + b[1]
    elseif b[4] == 1 && b[3] == 0 && b[2] == 0
        d[3] = b[10]*4 + b[9]*2 + b[8]
        d[2] = b[7]*4 + b[6]*2 + b[5]
        d[1] = 8 + b[1]
    elseif b[4] == 1 && b[3] == 0 && b[2] == 1
        d[3] = b[10]*4 + b[9]*2 + b[8]
        d[2] = 8 + b[5]
        d[1] = b[7]*4 + b[6]*2 + b[1]
    elseif b[4] == 1 && b[3] == 1 && b[2] == 0
        d[3] = 8 + b[8]
        d[2] = b[7]*4 + b[6]*2 + b[5]
        d[1] = b[10]*4 + b[9]*2 + b[1]
    elseif b[7] == 0 && b[6] == 0 && b[4] == 1 && b[3] == 1 && b[2] == 1
        d[3] = 8 + b[8]
        d[2] = 8 + b[5]
        d[1] = b[10]*4 + b[9]*2 + b[1]
    elseif b[7] == 0 && b[6] == 1 && b[4] == 1 && b[3] == 1 && b[2] == 1
        d[3] = 8 + b[8]
        d[2] = b[10]*4 + b[9]*2 + b[5]
        d[1] = 8 + b[1]
    elseif b[7] == 1 && b[6] == 0 && b[4] == 1 && b[3] == 1 && b[2] == 1
        d[3] = b[10]*4 + b[9]*2 + b[8]
        d[2] = 8 + b[5]
        d[1] = 8 + b[1]
    elseif b[7] == 1 && b[6] == 1 && b[4] == 1 && b[3] == 1 && b[2] == 1
        d[3] = 8 + b[8]
        d[2] = 8 + b[5]
        d[1] = 8 + b[1]
    else
        throw(DomainError(plugin_name, "Invalid DPD encoding"))
    end

    int64(d[3])*100 + int64(d[2])*10 + int64(d[1])
end

function calc_significand(prefix::Int64, dpd_bits::BigInt, numBits::Int)::BigInt
    # prefix: High bits integer value
    # dpdBits: dpd encoded bits
    # numBits: bit length of dpd_bits
    # https://en.wikipedia.org/wiki/Decimal128_floating-point_format#Densely_packed_decimal_significand_field
    num_segments = div(numBits, 10)
    segments::Vector{BigInt} = []

    for _ in 1:num_segments
        segments.append(dpd_bits & 0b1111111111)
        dpd_bits >>= 10
    end

    v = prefix
    for dpd in segments
        v = v + 1000 + dpd_to_int64(dpd)
    end
    v
end

function decimal128_to_sign_digits_exponent(b::Vector{UInt8})::Union{Decimal, Tuple{Int, BigInt, Int32}}
    # https://en.wikipedia.org/wiki/Decimal128_floating-point_format
    sign::Int = 0
    digits::BigInt = 0
    exponent::Int32 = 0

    prefix::Int64 = 0

    if (b[0] & 0x80) == 0x80
        sign = 1
    end
    cf = (UInt32(b[0]&0x7f) << 10) + UInt32(b[1]<<2) + UInt32(b[2]>>6)
    if (cf & 0x1F000) == 0x1F000
        if sign == 1
            return Deicmal(-NaN)
        else
            return Decimal(Nan)
        end
    elseif (cf & 0x1F000) == 0x1E000
        if sign == 1
            return Decimal(Inf)
        else
            return Decimal(-Inf)
        end
    elseif (cf & 0x18000) == 0x00000
        exponent = int32(0x0000 + (cf & 0x00fff))
        prefix = int64((cf >> 12) & 0x07)
    elseif (cf & 0x18000) == 0x08000
        exponent = int32(0x1000 + (cf & 0x00fff))
        prefix = Int64((cf >> 12) & 0x07)
    elseif (cf & 0x18000) == 0x10000
        exponent = Int32(0x2000 + (cf & 0x00fff))
        prefix = Int64((cf >> 12) & 0x07)
    elseif (cf & 0x1e000) == 0x18000
        exponent = int32(0x0000 + (cf & 0x00fff))
        prefix = Int64(8 + (cf>>12)&0x01)
    elseif (cf & 0x1e000) == 0x1a000
        exponent = Int32(0x1000 + (cf & 0x00fff))
        prefix = Int64(8 + (cf>>12)&0x01)
    elseif (cf & 0x1e000) == 0x1c000
        exponent = int32(0x2000 + (cf & 0x00fff))
        prefix = int64(8 + (cf>>12)&0x01)
    else
        throw(DomainError(b, "decimal128 value error"))
    end
    exponent -= 6176

    dbd_bits &= big"0x3fffffffffffffffffffffffffff"
    digits = calc_significand(prefix, dpd_bits, 110)

    (sign, digits, exponent)
end

function decimal_fixed_to_decimal(b::Vector{UInt8}, scale::Int32)::Decimal
    v = decimal128_to_sign_digits_exponent(b)
    if isa(v, Decimal)
        return v
    end
    sign, digits, _ = v
    if sign != 0
        digits *= -1
    end

    Decimal(digits, scale)
end

function decimal64_to_decimal(b::Vector{UInt8})::Decimal
    # https://en.wikipedia.org/wiki/Decimal64_floating-point_format
    prefix::Int64 = 0
    sign::Int = 0
    if (b[0] & 0x80) == 0x80
        sign = 1
    end
    cf = (UInt32(b[0]) >> 2) & 0x1f
    exponent = ((Int32(b[0]) & 3) << 6) + ((Int32(b[1]) >> 2) & 0x3f)

    dpd_bits = reinterpret(BigInt, b)
    dpd_bits &= big"0x3ffffffffffff"

    if cf == 0x1f
        if sign == 1
            return Deicmal(-NaN)
        else
            return Deicmal(NaN)
        end
    elseif cf == 0x1e
        if sign == 1
            return Decimal(-Inf)
        else
            return Decimal(Inf)
        end
    elseif (cf & 0x18) == 0x00
        exponent = 0x000 + exponent
        prefix = Int64(cf & 0x07)
    elseif (cf & 0x18) == 0x08
        exponent = 0x100 + exponent
        prefix = Int64(cf & 0x07)
    elseif (cf & 0x18) == 0x10
        exponent = 0x200 + exponent
        prefix = Int64(cf & 0x07)
    elseif (cf & 0x1e) == 0x18
        exponent = 0x000 + exponent
        prefix = Int64(8 + cf&1)
    elseif (cf & 0x1e) == 0x1a
        exponent = 0x100 + exponent
        prefix = Int64(8 + cf&1)
    elseif (cf & 0x1e) == 0x1c
        exponent = 0x200 + exponent
        prefix = Int64(8 + cf&1)
    else
        throw(DomainError(b, "decimal64 value error"))
    end
    digits = calc_significand(prefix, dpd_bits, 50)
    exponent -= 398

    if sign != 0
        digits *= big"-1"
    end

    Decimal(digits, eponent)
end

function decimal128_to_decimal(b::Vector{UInt8})::Decimal
    # https://en.wikipedia.org/wiki/Decimal64_floating-point_format
    v = decimal128_to_sign_digits_exponent(b)
    if isa(v, Decimal)
        return v
    end
    sign, _, exponent = v
    if sign != 0
        digits *= -1
    end

    Decimal(digits, exponent)
end


using MarketData
using Test

@testset "TimeSeries readwrite parses csv file correctly" begin

    @testset "1d values array works" begin
        @test cl |> values isa Array{Float64,1}
    end

    @testset "2d values array works" begin
        @test ohlc |> values isa Array{Float64,2}
    end

    @testset "timestamp parses to correct type" begin
        @test cl |> timestamp        isa Vector{Date}
        @test datetime1 |> timestamp isa Vector{DateTime}
    end

    @testset "meta field is correctly constructed" begin
        @test AAPL |> meta  == "AAPL"
        @test BA |> meta    == "BA"
        @test CAT |> meta   == "CAT"
        @test DELL |> meta  == "DELL"
        @test EBAY |> meta  == "EBAY"
        @test F |> meta     == "F"
        @test GE |> meta    == "GE"
        @test mdata |> meta == "Apple"
    end
end

@testset "const objects have expected length" begin

    @testset "test objects" begin
        @test length(datetime1) == 5
        @test length(op)        == 500
        @test length(cl)        == 500
        @test length(ohlc)      == 500
        @test length(ohlcv)     == 500
        @test length(mdata)     == 500
    end

    @testset "historical data sets" begin
        @test length(AAPL) == 8336
        @test length(BA)   == 13090
        @test length(CAT)  == 13090
        @test length(DELL) == 6353
        @test length(EBAY) == 3842
        @test length(F)    == 10491
        @test length(GE)   == 13090
    end
end


# Types

const D4 = NTuple{4, Cdouble}

export Float256
struct Float256 <: AbstractFloat
    d4::D4
end

# Functions

function Base.tryparse(::Type{Float256}, str::String)
    r = tryparse(BigFloat, str)
    r === nothing && return r
    Float256(r)
end

export @f256_str
macro f256_str(str::String)
    :(Float256(BigFloat($str)))
end

# function Base.show(io::IO, x::Float256)
#     len = 80
#     buf = Vector{Cchar}(undef, len)
#     prec = 64
#     ccall((:c_qd_swrite, libqd), Cvoid, (Ref{D4}, Cint, Ptr{Cchar}, Cint),
#           x.d4, prec, buf, len)
#     print(io, unsafe_string(pointer(buf)))
# end

Base.show(io::IO, x::Float256) = show(io, BigFloat(x))

# function Float256(x::Float64)
#     r = Ref{D4}()
#     ccall((:c_qd_copy_d, libqd), Cvoid, (Cdouble, Ref{D4}), x, r)
#     Float256(r[])
# end
#
# function Float256(x::Float128)
#     r = Ref{D4}()
#     ccall((:c_qd_copy_dd, libqd), Cvoid, (Ref{D2}, Ref{D4}), x.d2, r)
#     Float256(r[])
# end

Float256(x::Float64) = Float256((x, 0.0, 0.0, 0.0))

Float256(x::Float128) = Float256((x.d2[1], x.d2[2], 0.0, 0.0))

function Float256(x::Union{Bool, Int8, Int16, Int32, UInt8, UInt16, UInt32,
                           Float16, Float32})
    Float256(Float64(x))
end

function Float256(x::Union{Int64, UInt64})
    hi = x >> 32 << 32
    lo = x - hi
    Float256(Float64(hi)) + Float256(Float64(lo))
end

function Float256(x::Rational)
    Float256(numerator(x)) / Float256(denominator(x))
end

function Float256(x::BigFloat)
    r1 = Float256(Float64(x))
    x -= BigFloat(r1)
    r2 = Float256(Float64(x))
    x -= BigFloat(r2)
    r3 = Float256(Float64(x))
    x -= BigFloat(r3)
    r4 = Float256(Float64(x))
    # x -= BigFloat(r4)
    r1 + r2 + r3 + r4
end

Base.Float64(x::Float256) = x.d4[1]

Float128(x::Float256) = Float128((x.d4[1], x.d4[2]))

Base.BigFloat(x::Float256) =
    big(x.d4[1]) + big(x.d4[2]) + big(x.d4[3]) + big(x.d4[4])

Base.big(::Type{Float256}) = BigFloat

Base.promote_rule(::Type{Float256}, ::Type{Float16}) = Float256
Base.promote_rule(::Type{Float256}, ::Type{Float32}) = Float256
Base.promote_rule(::Type{Float256}, ::Type{Float64}) = Float256
Base.promote_rule(::Type{Float256}, ::Type{Float128}) = Float256
Base.promote_rule(::Type{Float256}, ::Type{BigFloat}) = BigFloat
Base.promote_rule(::Type{Float256}, ::Type{I}) where {I <: Integer} = Float256
Base.promote_rule(::Type{Float256}, ::Type{BigInt}) = BigFloat

# Base.zero(::Type{Float256}) = c256_0
# Base.one(::Type{Float256}) = c256_1
# Base.eps(::Type{Float256}) = c256_eps
Base.zero(::Type{Float256}) = Float256(0.0)
Base.one(::Type{Float256}) = Float256(1.0)
Base.eps(::Type{Float256}) = Float256(2^-208)

# function Float256(::Irrational{:π})
#     r = Ref{D4}()
#     ccall((:c_qd_pi, libqd), Cvoid, (Ref{D4},), r)
#     Float256(r[])
# end
Float256(::Irrational{:ℯ}) = c256_e
Float256(::Irrational{:π}) = c256_pi

Base. +(x::Float256) = x

# function Base. -(x::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_neg, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
#     Float256(r[])
# end

function Base. -(a::Float256)
    Float256((- a.d4[1], - a.d4[2], - a.d4[3], - a.d4[4]))
end

# function Base.abs(x::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_abs, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
#     Float256(r[])
# end
Base.abs(a::Float256) = a.d4[1] < 0.0 ? -a : a

Base.inv(x::Float256) = one(Float64) / x

# function Base. +(x::Float256, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_add, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{D4}), x.d4, y.d4, r)
#     Float256(r[])
# end
#
# function Base. +(x::Float128, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_add_dd_qd, libqd), Cvoid, (Ref{D2}, Ref{D4}, Ref{D4}),
#           x.d2, y.d4, r)
#     Float256(r[])
# end
#
# function Base. +(x::Float64, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_add_d_qd, libqd), Cvoid, (Cdouble, Ref{D4}, Ref{D4}),
#           x, y.d4, r)
#     Float256(r[])
# end
#
# function Base. +(x::Float256, y::Float128)
#     r = Ref{D4}()
#     ccall((:c_qd_add_qd_dd, libqd), Cvoid, (Ref{D4}, Ref{D2}, Ref{D4}),
#           x.d4, y.d2, r)
#     Float256(r[])
# end
#
# function Base. +(x::Float256, y::Float64)
#     r = Ref{D4}()
#     ccall((:c_qd_add_qd_d, libqd), Cvoid, (Ref{D4}, Cdouble, Ref{D4}),
#           x.d4, y, r)
#     Float256(r[])
# end

function Base. +(a::Float256, b::Float256)
    s0, t0 = two_sum(a.d4[1], b.d4[1])
    s1, t1 = two_sum(a.d4[2], b.d4[2])
    s2, t2 = two_sum(a.d4[3], b.d4[3])
    s3, t3 = two_sum(a.d4[4], b.d4[4])

    s1, t0 = two_sum(s1, t0)
    s2, t0, t1 = three_sum(s2, t0, t1)
    s3, t0, t2 = three_sum2(s3, t0, t2)
    t0 = t0 + t1 + t3

    s0, s1, s2, s3, t0 = renorm(s0, s1, s2, s3, t0)
    Float256((s0, s1, s2, s3))
end

function Base. +(a::Float256, b::Float64)
    c0, e = two_sum(a.d4[1], b)
    c1, e = two_sum(a.d4[2], e)
    c2, e = two_sum(a.d4[3], e)
    c3, e = two_sum(a.d4[4], e)

    c0, c1, c2, c3, e = renorm(c0, c1, c2, c3, e)

    Float256((c0, c1, c2, c3))
end

function Base. +(a::Float256, b::Float128)
    s0, t0 = two_sum(a.d4[1], b.d2[1])
    s1, t1 = two_sum(a.d4[2], b.d2[2])

    s1, t0 = two_sum(s1, t0)

    s2 = a.d4[3]
    s2, t0, t1 = three_sum(s2, t0, t1)

    s3, t0 = two_sum(t0, a.d4[4])
    t0 += t1

    s0, s1, s2, s3, t0 = renorm(s0, s1, s2, s3, t0)
    Float256((s0, s1, s2, s3))
end

Base. +(a::Float128, b::Float256) = b + a
Base. +(a::Float64, b::Float256) = b + a

# function Base. -(x::Float256, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_sub, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{D4}), x.d4, y.d4, r)
#     Float256(r[])
# end
#
# function Base. -(x::Float128, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_sub_dd_qd, libqd), Cvoid, (Ref{D2}, Ref{D4}, Ref{D4}),
#           x.d2, y.d4, r)
#     Float256(r[])
# end
#
# function Base. -(x::Float64, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_sub_d_qd, libqd), Cvoid, (Cdouble, Ref{D4}, Ref{D4}),
#           x, y.d4, r)
#     Float256(r[])
# end
#
# function Base. -(x::Float256, y::Float128)
#     r = Ref{D4}()
#     ccall((:c_qd_sub_qd_dd, libqd), Cvoid, (Ref{D4}, Ref{D2}, Ref{D4}),
#           x.d4, y.d2, r)
#     Float256(r[])
# end
#
# function Base. -(x::Float256, y::Float64)
#     r = Ref{D4}()
#     ccall((:c_qd_sub_qd_d, libqd), Cvoid, (Ref{D4}, Cdouble, Ref{D4}),
#           x.d4, y, r)
#     Float256(r[])
# end

Base. -(a::Float256, b::Float256) = a + (- b)
Base. -(a::Float256, b::Float64) = a + (- b)
Base. -(a::Float256, b::Float128) = a + (- b)
Base. -(a::Float128, b::Float256) = a + (- b)
Base. -(a::Float64, b::Float256) = a + (- b)

# function Base. *(x::Float256, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_mul, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{D4}), x.d4, y.d4, r)
#     Float256(r[])
# end
#
# # This function as well as its counterpart below are much less
# # accurate than expected. Their accuracy is that of Float128, not
# # Float256. We therefore disable them.
# # function Base. *(x::Float128, y::Float256)
# #     r = Ref{D4}()
# #     ccall((:c_qd_mul_dd_qd, libqd), Cvoid, (Ref{D2}, Ref{D4}, Ref{D4}),
# #           x.d2, y.d4, r)
# #     Float256(r[])
# # end
#
# function Base. *(x::Float64, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_mul_d_qd, libqd), Cvoid, (Cdouble, Ref{D4}, Ref{D4}),
#           x, y.d4, r)
#     Float256(r[])
# end
#
# # function Base. *(x::Float256, y::Float128)
# #     r = Ref{D4}()
# #     ccall((:c_qd_mul_qd_dd, libqd), Cvoid, (Ref{D4}, Ref{D2}, Ref{D4}),
# #           x.d4, y.d2, r)
# #     Float256(r[])
# # end
#
# function Base. *(x::Float256, y::Float64)
#     r = Ref{D4}()
#     ccall((:c_qd_mul_qd_d, libqd), Cvoid, (Ref{D4}, Cdouble, Ref{D4}),
#           x.d4, y, r)
#     Float256(r[])
# end

function Base. *(a::Float256, b::Float256)
    # sloppy_mul
    # quad-double * quad-double
    # a0 * b0                    0
    #      a0 * b1               1
    #      a1 * b0               2
    #           a0 * b2          3
    #           a1 * b1          4
    #           a2 * b0          5
    #                a0 * b3     6
    #                a1 * b2     7
    #                a2 * b1     8
    #                a3 * b0     9

    p0, q0 = two_prod(a.d4[1], b.d4[1])

    p1, q1 = two_prod(a.d4[1], b.d4[2])
    p2, q2 = two_prod(a.d4[2], b.d4[1])

    p3, q3 = two_prod(a.d4[1], b.d4[3])
    p4, q4 = two_prod(a.d4[2], b.d4[2])
    p5, q5 = two_prod(a.d4[3], b.d4[1])

    # Start Accumulation
    p1, p2, q0 = three_sum(p1, p2, q0)

    # Six-Three Sum  of p2, q1, q2, p3, p4, p5.
    p2, q1, q2 = three_sum(p2, q1, q2)
    p3, p4, p5 = three_sum(p3, p4, p5)
    # compute (s0, s1, s2) = (p2, q1, q2) + (p3, p4, p5).
    s0, t0 = two_sum(p2, p3)
    s1, t1 = two_sum(q1, p4)
    s2 = q2 + p5
    s1, t0 = two_sum(s1, t0)
    s2 += t0 + t1

    # O(eps^3) order terms
    s1 += a.d4[1] * b.d4[4] + a.d4[2] * b.d4[3] + a.d4[3] * b.d4[2] +
        a.d4[4] * b.d4[1] + q0 + q3 + q4 + q5
    p0, p1, s0, s1, s2 = renorm(p0, p1, s0, s1, s2)
    return Float256((p0, p1, s0, s1))
end

# This function is much less accurate than expected. Their accuracy is
# that of Float128, not Float256. We therefore disable it.
# function Base. *(a::Float256, b::Float128)
#     # quad-double * double-double
#     # a0 * b0                        0
#     #      a0 * b1                   1
#     #      a1 * b0                   2
#     #           a1 * b1              3
#     #           a2 * b0              4
#     #                a2 * b1         5
#     #                a3 * b0         6
#     #                     a3 * b1    7
#
#     p0, q0 = two_prod(a.d4[1], b.d2[1])
#     p1, q1 = two_prod(a.d4[1], b.d2[2])
#     p2, q2 = two_prod(a.d4[2], b.d2[1])
#     p3, q3 = two_prod(a.d4[2], b.d2[2])
#     p4, q4 = two_prod(a.d4[3], b.d2[1])
#
#     p1, p2, q0 = three_sum(p1, p2, q0)
#
#     # Five-Three-Sum
#     p2, p3, p4 = three_sum(p2, p3, p4)
#     q1, q2 = two_sum(q1, q2)
#     s0, t0 = two_sum(p2, q1)
#     s1, t1 = two_sum(p3, q2)
#     s1, t0 = two_sum(s1, t0)
#     s2 = t0 + t1 + p4
#     p2 = s0
#
#     p3 = a.d4[3] * b.d2[1] + a.d4[4] * b.d2[2] + q3 + q4
#     p3, q0, s1 = three_sum2(p3, q0, s1)
#     p4 = q0 + s2
#
#     p0, p1, p2, p3, p4 = renorm(p0, p1, p2, p3, p4)
#     Float256((p0, p1, p2, p3))
# end

# This is an adaptation from the *(::Float256, ::Float256) case,
# manually partially evaluated to avoid operations involving zeros.
function Base. *(a::Float256, b::Float128)
    # sloppy_mul
    # quad-double * quad-double
    # a0 * b0                    0
    #      a0 * b1               1
    #      a1 * b0               2
    #           a1 * b1          4
    #           a2 * b0          5
    #                a2 * b1     8
    #                a3 * b0     9

    p0, q0 = two_prod(a.d4[1], b.d2[1])

    p1, q1 = two_prod(a.d4[1], b.d2[2])
    p2, q2 = two_prod(a.d4[2], b.d2[1])

    p4, q4 = two_prod(a.d4[2], b.d2[2])
    p5, q5 = two_prod(a.d4[3], b.d2[1])

    # Start Accumulation
    p1, p2, q0 = three_sum(p1, p2, q0)

    # Six-Three Sum  of p2, q1, q2, p3, p4, p5.
    p2, q1, q2 = three_sum(p2, q1, q2)
    p3, p4 = two_sum(p4, p5)
    # compute (s0, s1, s2) = (p2, q1, q2) + (p3, p4, p5).
    s0, t0 = two_sum(p2, p3)
    s1, t1 = two_sum(q1, p4)
    s2 = q2
    s1, t0 = two_sum(s1, t0)
    s2 += t0 + t1

    # O(eps^3) order terms
    s1 += a.d4[3] * b.d2[2] + a.d4[4] * b.d2[1] + q0 + q4 + q5
    p0, p1, s0, s1, s2 = renorm(p0, p1, s0, s1, s2)
    return Float256((p0, p1, s0, s1))
end

function Base. *(a::Float256, b::Float64)
    p0, q0 = two_prod(a.d4[1], b)
    p1, q1 = two_prod(a.d4[2], b)
    p2, q2 = two_prod(a.d4[3], b)
    p3 = a.d4[4] * b

    s0 = p0

    s1, s2 = two_sum(q0, p1)

    s2, q1, p2 = three_sum(s2, q1, p2)

    q1, q2, p3 = three_sum2(q1, q2, p3)
    s3 = q1

    s4 = q2 + p2

    s0, s1, s2, s3, s4 = renorm(s0, s1, s2, s3, s4)
    Float256((s0, s1, s2, s3))
end

Base. *(a::Float64, b::Float256) = b * a
Base. *(a::Float128, b::Float256) = b * a

function Base. /(x::Float256, y::Float256)
    r = Ref{D4}()
    ccall((:c_qd_div, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{D4}), x.d4, y.d4, r)
    Float256(r[])
end

# function Base. /(x::Float128, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_div_dd_qd, libqd), Cvoid, (Ref{D2}, Ref{D4}, Ref{D4}),
#           x.d2, y.d4, r)
#     Float256(r[])
# end
#
# function Base. /(x::Float64, y::Float256)
#     r = Ref{D4}()
#     ccall((:c_qd_div_d_qd, libqd), Cvoid, (Cdouble, Ref{D4}, Ref{D4}),
#           x, y.d4, r)
#     Float256(r[])
# end
#
# function Base. /(x::Float256, y::Float128)
#     r = Ref{D4}()
#     ccall((:c_qd_div_qd_dd, libqd), Cvoid, (Ref{D4}, Ref{D2}, Ref{D4}),
#           x.d4, y.d2, r)
#     Float256(r[])
# end
#
# function Base. /(x::Float256, y::Float64)
#     r = Ref{D4}()
#     ccall((:c_qd_div_qd_d, libqd), Cvoid, (Ref{D4}, Cdouble, Ref{D4}),
#           x.d4, y, r)
#     Float256(r[])
# end

Base. \(x::Float256, y::Float256) = y / x
# Base. \(x::Float128, y::Float256) = y / x
# Base. \(x::Float64, y::Float256) = y / x
# Base. \(x::Float256, y::Float128) = y / x
# Base. \(x::Float256, y::Float64) = y / x

function Base.cmp(x::Float256, y::Float256)
    r = Ref{Cint}()
    ccall((:c_qd_comp, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{Cint}),
          x.d4, y.d4, r)
    Int(r[])
end

function Base.cmp(x::Float64, y::Float256)
    r = Ref{Cint}()
    ccall((:c_qd_comp_d_qd, libqd), Cvoid, (Cdouble, Ref{D4}, Ref{Cint}),
          x, y.d4, r)
    Int(r[])
end

function Base.cmp(x::Float256, y::Float64)
    r = Ref{Cint}()
    ccall((:c_qd_comp_qd_d, libqd), Cvoid, (Ref{D4}, Cdouble, Ref{Cint}),
          x.d4, y, r)
    Int(r[])
end

# Base. ==(x::Float256, y::Float256) = cmp(x, y) == 0
# Base. ==(x::Float64, y::Float256) = cmp(x, y) == 0
# Base. ==(x::Float256, y::Float64) = cmp(x, y) == 0
#
# Base. !=(x::Float256, y::Float256) = cmp(x, y) != 0
# Base. !=(x::Float64, y::Float256) = cmp(x, y) != 0
# Base. !=(x::Float256, y::Float64) = cmp(x, y) != 0
#
# Base. <(x::Float256, y::Float256) = cmp(x, y) < 0
# Base. <(x::Float64, y::Float256) = cmp(x, y) < 0
# Base. <(x::Float256, y::Float64) = cmp(x, y) < 0
#
# Base. <=(x::Float256, y::Float256) = cmp(x, y) <= 0
# Base. <=(x::Float64, y::Float256) = cmp(x, y) <= 0
# Base. <=(x::Float256, y::Float64) = cmp(x, y) <= 0
#
# Base. >(x::Float256, y::Float256) = cmp(x, y) > 0
# Base. >(x::Float64, y::Float256) = cmp(x, y) > 0
# Base. >(x::Float256, y::Float64) = cmp(x, y) > 0
#
# Base. >=(x::Float256, y::Float256) = cmp(x, y) >= 0
# Base. >=(x::Float64, y::Float256) = cmp(x, y) >= 0
# Base. >=(x::Float256, y::Float64) = cmp(x, y) >= 0

Base. ==(a::Float256, b::Float256) =
    a.d4[1] == b.d4[1] && a.d4[2] == b.d4[2] &&
    a.d4[3] == b.d4[3] && a.d4[4] == b.d4[4]
Base. ==(a::Float128, b::Float256) = b == a
Base. ==(a::Float64, b::Float256) = b == a
Base. ==(a::Float256, b::Float128) =
    a.d4[1] == b.d2[1] && a.d4[2] == b.d2[2] && a.d4[3] == 0.0 && a.d4[4] == 0.0
Base. ==(a::Float256, b::Float64) =
    a.d4[1] == b && a.d4[2] == 0.0 && a.d4[3] == 0.0 && a.d4[4] == 0.0

Base. !=(a::Float256, b::Float256) = !(a == b)
Base. !=(a::Float128, b::Float256) = !(a == b)
Base. !=(a::Float64, b::Float256) = !(a == b)
Base. !=(a::Float256, b::Float128) = !(a == b)
Base. !=(a::Float256, b::Float64) = !(a == b)

Base. <(a::Float256, b::Float256) =
    a.d4[1] < b.d4[1] ||
    (a.d4[1] == b.d4[1] && (a.d4[2] < b.d4[2] ||
                            (a.d4[2] == b.d4[2] && (a.d4[3] < b.d4[3] ||
                                                    (a.d4[3] == b.d4[3] &&
                                                     a.d4[4] < b.d4[4])))))
Base. <(a::Float128, b::Float256) = b > a
Base. <(a::Float64, b::Float256) = b > a
Base. <(a::Float256, b::Float128) =
    a.d4[1] < b.d2[1] ||
    (a.d4[1] == b.d2[1] && (a.d4[2] < b.d2[2] ||
                            (a.d4[2] == b.d2[2] && a.d4[3] < 0.0)))
Base. <(a::Float256, b::Float64) =
    a.d4[1] < b || (a.d4[1] == b && a.d4[2] < 0.0)

Base. <=(a::Float256, b::Float256) =
    a.d4[1] < b.d4[1] ||
    (a.d4[1] == b.d4[1] && (a.d4[2] < b.d4[2] ||
                            (a.d4[2] == b.d4[2] && (a.d4[3] < b.d4[3] ||
                                                    (a.d4[3] == b.d4[3] &&
                                                     a.d4[4] <= b.d4[4])))))
Base. <=(a::Float128, b::Float256) = b >= a
Base. <=(a::Float64, b::Float256) = b >= a
Base. <=(a::Float256, b::Float128) =
    a.d4[1] < b.d2[1] ||
    (a.d4[1] == b.d2[1] && (a.d4[2] < b.d2[2] ||
                            (a.d4[2] == b.d2[2] && a.d4[3] <= 0.0)))
Base. <=(a::Float256, b::Float64) =
    a.d4[1] < b || (a.d4[1] == b && a.d4[2] <= 0.0)

Base. >(a::Float256, b::Float256) =
    a.d4[1] > b.d4[1] ||
    (a.d4[1] == b.d4[1] && (a.d4[2] > b.d4[2] ||
                            (a.d4[2] == b.d4[2] && (a.d4[3] > b.d4[3] ||
                                                    (a.d4[3] == b.d4[3] &&
                                                     a.d4[4] > b.d4[4])))))
Base. >(a::Float128, b::Float256) = b < a
Base. >(a::Float64, b::Float256) = b < a
Base. >(a::Float256, b::Float128) =
    a.d4[1] > b.d2[1] ||
    (a.d4[1] == b.d2[1] && (a.d4[2] > b.d2[2] ||
                            (a.d4[2] == b.d2[2] && a.d4[3] > 0.0)))
Base. >(a::Float256, b::Float64) =
    a.d4[1] > b || (a.d4[1] == b && a.d4[2] > 0.0)

Base. >=(a::Float256, b::Float256) =
    a.d4[1] > b.d4[1] ||
    (a.d4[1] == b.d4[1] && (a.d4[2] > b.d4[2] ||
                            (a.d4[2] == b.d4[2] && (a.d4[3] > b.d4[3] ||
                                                    (a.d4[3] == b.d4[3] &&
                                                     a.d4[4] >= b.d4[4])))))
Base. >=(a::Float128, b::Float256) = b <= a
Base. >=(a::Float64, b::Float256) = b <= a
Base. >=(a::Float256, b::Float128) =
    a.d4[1] > b.d2[1] ||
    (a.d4[1] == b.d2[1] && (a.d4[2] > b.d2[2] ||
                            (a.d4[2] == b.d2[2] && a.d4[3] >= 0.0)))
Base. >=(a::Float256, b::Float64) =
    a.d4[1] > b || (a.d4[1] == b && a.d4[2] >= 0.0)

function Base.ceil(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_ceil, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.floor(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_floor, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

# Note: This breaks ties the wrong way
function Base.round(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_nint, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.trunc(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_aint, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base. ^(x::Float256, i::Integer)
    r = Ref{D4}()
    ccall((:c_qd_npwr, libqd), Cvoid, (Ref{D4}, Cint, Ref{D4}), x.d4, i, r)
    Float256(r[])
end

# function Base.literal_pow(::typeof(^), x::Float256, ::Val{2})
#     r = Ref{D4}()
#     ccall((:c_qd_sqr, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
#     Float256(r[])
# end

function Base.literal_pow(::typeof(^), a::Float256, ::Val{2})
    # quad-double ^ 2  = (x0 + x1 + x2 + x3) ^ 2
    #                  = x0 ^ 2 + 2 x0 * x1 + (2 x0 * x2 + x1 ^ 2)
    #                             + (2 x0 * x3 + 2 x1 * x2)

    p0, q0 = two_sqr(a.d4[1])
    p1, q1 = two_prod(2.0 * a.d4[1], a.d4[2])
    p2, q2 = two_prod(2.0 * a.d4[1], a.d4[3])
    p3, q3 = two_sqr(a.d4[2])

    p1, q0 = two_sum(q0, p1)

    q0, q1 = two_sum(q0, q1)
    p2, p3 = two_sum(p2, p3)

    s0, t0 = two_sum(q0, p2)
    s1, t1 = two_sum(q1, p3)

    s1, t0 = two_sum(s1, t0)
    t0 += t1

    s1, t0 = quick_two_sum(s1, t0)
    p2, t1 = quick_two_sum(s0, s1)
    p3, q0 = quick_two_sum(t1, t0)

    p4 = 2.0 * a.d4[1] * a.d4[4]
    p5 = 2.0 * a.d4[2] * a.d4[3]

    p4, p5 = two_sum(p4, p5)
    q2, q3 = two_sum(q2, q3)

    t0, t1 = two_sum(p4, q2)
    t1 = t1 + p5 + q3

    p3, p4 = two_sum(p3, t0)
    p4 = p4 + q0 + t1

    p0, p1, p2, p3, p4 = renorm(p0, p1, p2, p3, p4)
    Float256((p0, p1, p2, p3))
end

function Base.sqrt(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_sqrt, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

export nroot
function nroot(x::Float256, i::Integer)
    r = Ref{D4}()
    ccall((:c_qd_nroot, libqd), Cvoid, (Ref{D4}, Cint, Ref{D4}), x.d4, i, r)
    Float256(r[])
end

function Base.exp(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_exp, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

Base.exp10(x::Float256) = exp(c256_log10 * x)

Base.exp2(x::Float256) = exp(c256_log2 * x)

function Base.log(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_log, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.log10(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_log10, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

Base.log2(x::Float256) = c256_1_log2 * log(x)

function Base.sin(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_sin, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.cos(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_cos, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.tan(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_tan, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.asin(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_asin, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.acos(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_acos, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.atan(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_atan, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.atan(x::Float256, y::Float256)
    r = Ref{D4}()
    ccall((:c_qd_atan2, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{D4}),
          x.d4, y.d4, r)
    Float256(r[])
end

Base.hypot(x::Float256, y::Float256) = sqrt(x^2 + y^2)

function Base.sinh(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_sinh, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.cosh(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_cosh, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.tanh(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_tanh, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.asinh(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_asinh, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.acosh(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_acosh, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.atanh(x::Float256)
    r = Ref{D4}()
    ccall((:c_qd_atanh, libqd), Cvoid, (Ref{D4}, Ref{D4}), x.d4, r)
    Float256(r[])
end

function Base.sincos(x::Float256)
    r = Ref{D4}()
    s = Ref{D4}()
    ccall((:c_qd_sincos, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{D4}), x.d4, r, s)
    Float256(r[]), Float256(s[])
end

export sincosh
function sincosh(x::Float256)
    r = Ref{D4}()
    s = Ref{D4}()
    ccall((:c_qd_sincosh, libqd), Cvoid, (Ref{D4}, Ref{D4}, Ref{D4}),
          x.d4, r, s)
    Float256(r[]), Float256(s[])
end

function Base.rand(::Type{Float256})
    r = Ref{D4}()
    ccall((:c_qd_rand, libqd), Cvoid, (Ref{D4},), r)
    Float256(r[])
end

function Base.rand(::Type{Float256}, dims::Dims)
    r = Array{Float256}(undef, dims)
    for i in LinearIndices(r)
        r[i] = rand(Float256)
    end
    r
end

Base.rand(::Type{Float256}, dims::Integer...) = rand(Float256, Dims(dims))

# Constants

# const c256_0 = Float256(0.0)
# const c256_1 = Float256(1.0)
# const c256_eps = Float256(2^-208)

const c256_pi = begin
    r = Ref{D4}()
    ccall((:c_qd_pi, libqd), Cvoid, (Ref{D4},), r)
    Float256(r[])
end
const c256_e = exp(Float256(1.0))
const c256_log10 = log(Float256(10.0))
const c256_log2 = log(Float256(2.0))
const c256_1_log2 = inv(c256_log2)


using WGLMakie

x = collect(-4π:0.1:4π)
y = Base.sin.(x)

function plot_d18o(dist, d18o) # Create menu
    fig = Figure()

    colormenu =  Menu(fig, options=[:red, :blue, :black])
    legendmenu = Toggle(fig, active=true)
    fig[1, 1] = vgrid!(
        Label(fig, "Color", width=nothing),
        colormenu,
        Label(fig, "Legend", width=nothing),
        legendmenu,
        width=200, tellheight=false
    )

    ax = Axis(fig[1, 2], size=(800, 400),
            xlabel="Testlabel", ylabel="some quantity (kgm s⁻²)")
    plot = lines!(ax, dist, d18o, c=:black, label="testlegend")

    on(colormenu.selection) do s
        plot.color = s
    end
    # connect!(plot.legend, legendmenu.active)

    fig
end

plot_d18o(range(1,100), cumsum(randn(100)))

include("sinreg.jl")