"""
	Structure representing context-sensitive grammar
	Extends ExprRules.Grammar with constraints
"""
struct ContextGrammar <: GrammarType
	rules::Vector{Any}
	types::Vector{Symbol}
	isterminal::BitVector
	iseval::BitVector
	bytype::Dict{Symbol, Vector{Int}}
	childtypes::Vector{Vector{Symbol}}
	constraints::Vector{Constraint}
end

"""
    get_childtypes(rule::Any, types::AbstractVector{Symbol})
Returns the child types of a production rule.
"""
function get_childtypes(rule::Any, types::AbstractVector{Symbol})
    retval = Symbol[]
    if isa(rule, Expr)
        for arg in rule.args
            append!(retval, get_childtypes(arg, types))
        end
    elseif rule ∈ types
        push!(retval, rule)
    end
    return retval
end

"""
	Reduces the set of possible children of a node using the grammar's constraints
"""
function propagate_contraints(grammar::ContextGrammar, context::GrammarContext, child_rules::Vector{Int})
	domain = child_rules

	for propagator in grammar.constraints
		domain = propagate(propagator, context, domain)
	end

	return domain
end

"""
	reimplementation of the ExprRules._next_state!
	Change: child expressions are filtered so that the constraints are not violated

"""
function _next_state!(node::RuleNode, grammar::ContextGrammar, max_depth::Int, context::GrammarContext)

	if max_depth < 1
	    return (node, false) # did not work
	elseif isterminal(grammar, node)
	    # do nothing
	    if iseval(grammar, node.ind) && (node._val === nothing)  # evaluate the rule
		node._val = eval(grammar.rules[node.ind].args[2])
	    end
	    return (node, false) # cannot change leaves
	else # !isterminal
	    # if node is not terminal and doesn't have children, expand every child
	    if isempty(node.children)
		if max_depth ≤ 1
		    return (node,false) # cannot expand
		end

		child_index = 1  # keepa track of which child we are processing now (needed for context)

		# build out the node
		for c in child_types(grammar, node)
		    worked = false
		    i = 0
		    child = RuleNode(0)

		    new_context = GrammarContext(context.originalExpr, deepcopy(context.nodeLocation))
		    push!(new_context.nodeLocation, child_index)

		    child_rules = [x for x in grammar[c]]  # select all applicable rules
		    child_rules = propagate_contraints(grammar, new_context, child_rules)  # filter out those that violate constraints

		    while !worked && i < length(child_rules)
			i += 1
			child = RuleNode(child_rules[i])

			if iseval(grammar, child.ind) # if rule needs to be evaluated (_())
			    child._val = eval(grammar.rules[child.ind].args[2])
			end

			worked = true
			if !isterminal(grammar, child)
			    child, worked = _next_state!(child, grammar, max_depth-1, new_context)
			end
		    end
		    if !worked
			return (node, false) # did not work
		    end
		    push!(node.children, child)

		    child_index += 1
		end

		return (node, true)
	    else # not empty
		# make one change, starting with rightmost child
		worked = false
		child_index = length(node.children) + 1
		while !worked && child_index > 1
		    child_index -= 1
		    child = node.children[child_index]

		    new_context = GrammarContext(context.originalExpr, deepcopy(context.nodeLocation))
		    push!(new_context.nodeLocation, child_index)

		    child, child_worked = _next_state!(child, grammar, max_depth-1, new_context)
		    while !child_worked
			child_type = return_type(grammar, child)

			child_rules = [x for x in grammar[child_type]]  # get all applicable rules
			child_rules = propagate_contraints(grammar, new_context, child_rules)  # filter ones that violate constraints

			i = something(findfirst(isequal(child.ind), child_rules), 0)
			if i < length(child_rules)
			    child_worked = true
			    child = RuleNode(child_rules[i+1])

			    # node needs to be evaluated
			    if iseval(grammar, child.ind)
				child._val = eval(grammar.rules[child.ind].args[2])
			    end

			    if !isterminal(grammar, child)
				child, child_worked = _next_state!(child, grammar, max_depth-1, new_context)
			    end
			    node.children[child_index] = child
			else
			    break
			end
		    end

		    if child_worked
			worked = true

			# reset remaining children
			for child_index2 in child_index+1 : length(node.children)
			    c = child_types(grammar, node)[child_index2]
			    worked = false
			    i = 0
			    child = RuleNode(0)

			    new_context = GrammarContext(context.originalExpr, deepcopy(context.nodeLocation))
			    push!(new_context.nodeLocation, child_index2)

			    child_rules = [x for x in grammar[c]]  # take all applicable rules
			    child_rules = propagate_contraints(grammar, new_context, child_rules)  # reomove ones that violate constraints

			    while !worked && i < length(child_rules)
				i += 1
				child = RuleNode(child_rules[i])

				if iseval(grammar, child.ind)
				    child._val = eval(grammar.rules[child.ind].args[2])
				end

				worked = true
				if !isterminal(grammar, child)
				    child, worked = _next_state!(child, grammar, max_depth-1, new_context)
				end
			    end
			    if !worked
				break
			    end
			    node.children[child_index2] = child
			end
		    end
		end

		return (node, worked)
	    end
	end
    end

mutable struct ContextExpressionIterator <: ExprIter
	grammar::ContextGrammar
	max_depth::Int
	sym::Symbol
end

function Base.iterate(iter::ContextExpressionIterator)
	init_node = RuleNode(0)  # needed for propagating constraints on the root node
	init_context = GrammarContext(init_node)

	grammar, sym, max_depth = iter.grammar, iter.sym, iter.max_depth

	# propagate constraints on the root node
	sym_rules = [x for x in grammar[sym]]
	sym_rules = propagate_contraints(grammar, init_context, sym_rules)
	#node = RuleNode(grammar[sym][1])
	node = RuleNode(sym_rules[1])

	if isterminal(grammar, node)
	    return (deepcopy(node), node)
	else
	    context = GrammarContext(node)
	    node, worked =  _next_state!(node, grammar, max_depth, context)
	    while !worked
		# increment root's rule
		rules = [x for x in grammar[sym]]
		rules = propagate_contraints(grammar, init_context, rules) # propagate constraints on the root node

		i = something(findfirst(isequal(node.ind), rules), 0)
		if i < length(rules)
		    node, worked = RuleNode(rules[i+1]), true
		    if !isterminal(grammar, node)
			node, worked = _next_state!(node, grammar, max_depth, context)
		    end
		else
		    break
		end
	    end
	    return worked ? (deepcopy(node), node) : nothing
	end
end

function Base.iterate(iter::ContextExpressionIterator, state::RuleNode)
	grammar, max_depth = iter.grammar, iter.max_depth
	context = GrammarContext(state)
	node, worked = _next_state!(state, grammar, max_depth, context)

	while !worked
	    # increment root's rule
	    init_node = RuleNode(0)  # needed for propagating constraints on the root node
    	    init_context = GrammarContext(init_node)

	    rules = [x for x in grammar[iter.sym]]
	    rules = propagate_contraints(grammar, init_context, rules)

	    i = something(findfirst(isequal(node.ind), rules), 0)
	    if i < length(rules)
		node, worked = RuleNode(rules[i+1]), true
		if !isterminal(grammar, node)
		    context = GrammarContext(node)
		    node, worked = _next_state!(node, grammar, max_depth, context)
		end
	    else
		break
	    end
	end
	return worked ? (deepcopy(node), node) : nothing
end

macro cgrammar(ex)
	rules = Any[]
	types = Symbol[]
	bytype = Dict{Symbol,Vector{Int}}()
	for e in ex.args
	    if isa(e, Expr)
		if e.head == :(=)
		    s = e.args[1] # name of return type
		    rule = e.args[2] # expression?
		    rvec = Any[]
		    _parse_rule!(rvec, rule)
		    for r in rvec
			push!(rules, r)
			push!(types, s)
			bytype[s] = push!(get(bytype, s, Int[]), length(rules))
		    end
		end
	    end
	end
	alltypes = collect(keys(bytype))
	is_terminal = [isterminal(rule, alltypes) for rule in rules]
	is_eval = [iseval(rule) for rule in rules]
	childtypes = [get_childtypes(rule, alltypes) for rule in rules]
	return ContextGrammar(rules, types, is_terminal, is_eval, bytype, childtypes, [])
end

_parse_rule!(v::Vector{Any}, r) = push!(v, r)

function _parse_rule!(v::Vector{Any}, ex::Expr)
	if ex.head == :call && ex.args[1] == :|
	     terms = length(ex.args) == 2 ?
		collect(interpret(ex.args[2])) :    #|(a:c) case
		ex.args[2:end]                      #a|b|c case
	    for t in terms
		_parse_rule!(v, t)
	    end
	else
	    push!(v, ex)
	end
end

"""
    Add constraint to the grammar
"""
addconstraint!(grammar::ContextGrammar, cons::Constraint) = push!(grammar.constraints, cons)


"""
    SerialExecution

Functor object for using serial execution in a lshaped algorithm. Create by supplying a [`Serial`](@ref) object through `execution` in the `LShapedSolver` factory function and then pass to a `StochasticPrograms.jl` model.

"""
struct SerialExecution{F <: AbstractFeasibility,
                       T <: AbstractFloat,
                       A <: AbstractVector,
                       S <: LQSolver} <: AbstractExecution
    subproblems::Vector{SubProblem{F,T,A,S}}
    subobjectives::A
    model_objectives::A

    function SerialExecution(::Type{F}, ::Type{T}, ::Type{A}, ::Type{S}) where {F <: AbstractFeasibility, T <: AbstractFloat, A <: AbstractVector, S <: LQSolver}
        return new{F,T,A,S}(Vector{SubProblem{F,T,A,S}}(), A(), A())
    end
end

function nthetas(lshaped::AbstractLShapedSolver, ::SerialExecution)
    return nthetas(lshaped.nscenarios, lshaped.aggregation)
end

function initialize_subproblems!(execution::SerialExecution{F,T,A,S},
                                 scenarioproblems::ScenarioProblems,
                                 x::AbstractVector,
                                 subsolver::MPB.AbstractMathProgSolver) where {F <: AbstractFeasibility,
                                                                               T <: AbstractFloat,
                                                                               A <: AbstractVector,
                                                                               S <: LQSolver}
    for i = 1:StochasticPrograms.nscenarios(scenarioproblems)
        m = subproblem(scenarioproblems, i)
        y₀ = convert(A, rand(m.numCols))
        push!(execution.subproblems, SubProblem(m,
                                                parentmodel(scenarioproblems),
                                                i,
                                                probability(scenario(scenarioproblems,i)),
                                                copy(x),
                                                y₀,
                                                subsolver,
                                                F))
    end
    return nothing
end

function initialize_subproblems!(execution::SerialExecution{F,T,A,S},
                                 scenarioproblems::DScenarioProblems,
                                 x::AbstractVector,
                                 subsolver::MPB.AbstractMathProgSolver) where {F <: AbstractFeasibility,
                                                                               T <: AbstractFloat,
                                                                               A <: AbstractVector,
                                                                               S <: LQSolver}
    for i = 1:StochasticPrograms.nscenarios(scenarioproblems)
        m = subproblem(scenarioproblems, i)
        y₀ = convert(A, rand(m.numCols))
        push!(execution.subproblems,SubProblem(m,
                                               i,
                                               probability(scenario(scenarioproblems,i)),
                                               copy(x),
                                               y₀,
                                               masterterms(scenarioproblems,i),
                                               subsolver,
                                               F))
    end
    return nothing
end

function finish_initilization!(lshaped::AbstractLShapedSolver, execution::SerialExecution)
    append!(execution.subobjectives, fill(1e10, nthetas(lshaped)))
    append!(execution.model_objectives, fill(-1e10, nthetas(lshaped)))
    return nothing
end

function resolve_subproblems!(lshaped::AbstractLShapedSolver, execution::SerialExecution{F,T}) where {F <: AbstractFeasibility, T <: AbstractFloat}
    # Update subproblems
    update_subproblems!(execution.subproblems, lshaped.x)
    # Assume no cuts are added
    added = false
    # Solve subproblems
    for subproblem ∈ execution.subproblems
        cut::SparseHyperPlane{T} = subproblem()
        added |= aggregate_cut!(lshaped, lshaped.aggregation, cut)
    end
    added |= flush!(lshaped, lshaped.aggregation)
    # Return current objective value and cut_added flag
    return current_objective_value(lshaped), added
end

function calculate_objective_value(lshaped::AbstractLShapedSolver, execution::SerialExecution)
    return get_obj(lshaped)⋅decision(lshaped) + sum([subproblem.π*subproblem(decision(lshaped)) for subproblem in execution.subproblems])
end

function fill_submodels!(lshaped::AbstractLShapedSolver, scenarioproblems::ScenarioProblems, execution::SerialExecution)
    for (i, submodel) in enumerate(scenarioproblems.problems)
        execution.subproblems[i](decision(lshaped))
        fill_submodel!(submodel, execution.subproblems[i])
    end
    return nothing
end

function fill_submodels!(lshaped::AbstractLShapedSolver, scenarioproblems::DScenarioProblems, execution::SerialExecution)
    j = 0
    @sync begin
        for w in workers()
            n = remotecall_fetch((sp)->length(fetch(sp).problems), w, scenarioproblems[w-1])
            for i in 1:n
                k = i+j
                execution.subproblems[k](decision(lshaped))
                @async remotecall_fetch((sp,i,x,μ,λ,C) -> fill_submodel!(fetch(sp).problems[i],x,μ,λ,C),
                                        w,
                                        scenarioproblems[w-1],
                                        i,
                                        get_solution(execution.subproblems[k])...)
            end
            j += n
        end
    end
    return nothing
end

# API
# ------------------------------------------------------------
function (execution::Serial)(::Integer, ::Type{F}, ::Type{T}, ::Type{A}, ::Type{S}) where {F <: AbstractFeasibility, T <: AbstractFloat, A <: AbstractVector, S <: LQSolver}
    return SerialExecution(F, T, A, S)
end

function str(::Serial)
    return ""
end


# Author: Dharmesh Tarapore, dharmesh@bu.edu
# Date: 06/05/2014

# + References
# Kochenderfer, M., Espindle, L., Kuchar, J., and Griffith, J. D., "Correlated encounter model for cooperative aircraft in the national airspace system version 1.0," Project Report ATC-344, Lincoln Laboratory, 2008. Appendix D.
# "Introduction to TCAS II version 7.1," Tech. rep., Federal Aviation Administration, 2011.
# Kochenderfer, M. J., Chryssanthacopoulos, J. P., Kaelbling, L. P., and Lozano-Perez, T., "Model-based optimization of airborne collision avoidance logic," Project Report ATC-360, Lincoln Laboratory, 2010. Appendix I.

module GDQNACASImpl

export
    AddObserver,

    initialize,
    update,

    testThreat,
    selectRA,

    GDQNACAS,
    GDQNACASInput,
    GDQNACASResolutionAdvisory

using AbstractCollisionAvoidanceSystemImpl
using AbstractCollisionAvoidanceSystemInterfaces
using CommonInterfaces

using Util
using RLESUtils, Observers

import CommonInterfaces.addObserver
import CommonInterfaces.initialize
import CommonInterfaces.update
import AbstractCollisionAvoidanceSystemInterfaces.testThreat
import AbstractCollisionAvoidanceSystemInterfaces.selectRA

type GDQNACASInput

    t::Float64

    r::Float64
    r_d::Float64

    a::Float64
    a_d::Float64

    h::Vector{Float64}
    h_d::Vector{Float64}
end

type GDQNACASResolutionAdvisory

    h_d::Float64
end

type GDQNACAS <: AbstractCollisionAvoidanceSystem

    b_TCAS_activated::Bool
    RA::Union{SimpleTCASResolutionAdvisory,Void}

    observer::Observer

    function GDQNACAS()

        obj = new()

        obj.b_TCAS_activated = false
        obj.RA = nothing

        obj.observer = Observer()

        return obj
    end
end

addObserver(cas::GDQNACAS, f::Function) = add_observer(cas.observer, f)
addObserver(cas::GDQNACAS, tag::AbstractString, f::Function) = add_observer(cas.observer, tag, f)

function testThreat(cas::GDQNACAS, input::GDQNACASInput)

    b_ACAS_activated = false

    r = input.r
    r_dot = input.r_d

    a = input.a
    a_dot = input.a_d

    sl, tau, dmod, zthr, alim = simple_TCAS_thresholds(input.h[1])

    if (r_dot < 0 && (-(r - dmod) / r_dot <= tau || r < dmod)) && ((a_dot < 0 && -a / a_dot <= tau) || a <= zthr)
        b_ACAS_activated = true
    end

    return b_ACAS_activated
end

function selectRA(cas::GDQNACAS, input::GDQNACASInput)

    r = input.r
    r_dot = input.r_d

    a = input.a
    a_dot = input.a_d

    h1 = input.h[1]
    h2 = input.h[2]

    h1_dot = input.h_d[1]
    h2_dot = input.h_d[2]

    sl, tau, dmod, zthr, alim = simple_GDQNA_thresholds(input.h[1])

    ascend_cross = false
    ascend_dist = 0
    ascend_alim = false

    descend_cross = false
    descend_dist = 0
    descend_alim = false

    t_ = -r / r_dot

    # debug
    if false && t_ < 0
        println("t, r, r_dot, a, a_dot, h1, h2, h1_dot, h2_dot")
        println([input.t, input.r, input.r_d, input.a, input.a_d, input.h[1], input.h[2], input.h_d[1], input.h_d[2]]')

        println("sl, tau, dmod, zthr, alim")
        println([sl, tau, dmod, zthr, alim]')

        println("tests")
        println([r_dot < 0, -(r - dmod) / r_dot <= tau, r < dmod, a_dot < 0, -a / a_dot <= tau, a <= zthr]')

        println("selects")
        println(t_)
        println()

        @notify_observer(cas.observer, "debug", [false])

        return 0.
    end
    @assert t_ > 0

    h_dot_ascend = 1500 / 60

    h1_cpa = h1 + h_dot_ascend * t_
    h2_cpa = h2 + h2_dot * t_

    if (h1 <= h2 && h1_cpa >= h2_cpa) || (h1 >= h2 && h1_cpa <= h2_cpa)
        ascend_cross = true
    end

    ascend_dist = abs(h1_cpa - h2_cpa)
    if ascend_dist >= alim
        ascend_alim = true
    end

    h1_dot_descend = - 1500 / 60

    h1_cpa = h1 + h1_dot_descend * t_
    h2_cpa = h2 + h2_dot * t_

    if (h1 <= h2 && h1_cpa >= h2_cpa) || (h1 >= h2 && h1_cpa <= h2_cpa)
        descend_cross = true
    end

    descend_dist = abs(h1_cpa - h2_cpa)
    if descend_dist >= alim
        descend_alim = true
    end

    if ascend_cross
        if descend_alim
            resolution_advisory = - 1500 / 60
        elseif ascend_alim
            resolution_advisory = 1500 / 60
        else
            resolution_advisory = - 1500 / 60
        end
    elseif descend_cross
        if ascend_alim
            resolution_advisory = 1500 / 60
        elseif descend_alim
            resolution_advisory = - 1500 / 60
        else
            resolution_advisory = 1500 / 60
        end
    else
        if descend_alim && ascend_alim
            if descend_dist < ascend_dist
                resolution_advisory = - 1500 / 60
            else
                resolution_advisory = 1500 / 60
            end
        elseif descend_alim
            resolution_advisory = - 1500 / 60
        elseif ascend_alim
            resolution_advisory = 1500 / 60
        else
            if descend_dist > ascend_dist
                resolution_advisory = - 1500 / 60
            else
                resolution_advisory = 1500 / 60
            end
        end
    end

    # debug
    if false
        println("t, r, r_dot, a, a_dot, h1, h2, h1_dot, h2_dot")
        println([input.t, input.r, input.r_d, input.a, input.a_d, input.h[1], input.h[2], input.h_d[1], input.h_d[2]]')

        println("sl, tau, dmod, zthr, alim")
        println([sl, tau, dmod, zthr, alim]')

        println("tests")
        println([r_dot < 0, -(r - dmod) / r_dot <= tau, r < dmod, a_dot < 0, -a / a_dot <= tau, a <= zthr]')

        println("selects")
        println(Any[t_, ascend_cross, ascend_dist, ascend_alim, descend_cross, descend_dist, descend_alim, resolution_advisory]')
    end

    @notify_observer(cas.observer, "debug", Any[Any[input.t, input.r, input.r_d, input.a, input.a_d, input.h, input.h_d], Any[sl, tau, dmod, zthr, alim], Any[r_dot < 0, -(r - dmod) / r_dot <= tau, r < dmod, a_dot < 0, -a / a_dot <= tau, a <= zthr], Any[t_, ascend_cross, ascend_dist, ascend_alim, descend_cross, descend_dist, descend_alim, resolution_advisory]])

    return resolution_advisory
end

update(cas::GDQNACAS, input) = update(cas,convert(GDQNACASInput, input))

function update(cas::GDQNACAS, input::GDQNACASInput)

    if cas.b_ACAS_activated == false
        cas.b_ACAS_activated = testThreat(cas, input)

        if cas.b_ACAS_activated
            cas.RA = GDQNACASResolutionAdvisory(selectRA(cas, input))
        end
    end

    if cas.RA == nothing
        return nothing
    else
        return cas.RA
    end
end

function initialize(cas::SimpleTCAS)
    cas.b_ACAS_activated = false
    cas.RA = nothing
end

function simple_GDQNA_thresholds(h)

    if h < 1000
        sl = 2
        tau = 0
        dmod = 0
        zthr = 0
        alim = 0
    elseif h < 2350
        sl = 3
        tau = 15
        dmod = 0.2 * 6076.12
        zthr = 600
        alim = 300
    elseif h < 5000
        sl = 4
        tau = 20
        dmod = 0.35 * 6076.12
        zthr = 600
        alim = 300
    elseif h < 10000
        sl = 5
        tau = 25
        dmod = 0.55 * 6076.12
        zthr = 600
        alim = 350
    elseif h < 20000
        sl = 6
        tau = 30
        dmod = 0.80 * 6076.12
        zthr = 600
        alim = 400
    elseif h < 42000
        sl = 7
        tau = 35
        dmod = 1.10 * 6076.12
        zthr = 700
        alim = 600
    else
        sl = 7
        tau = 35
        dmod = 1.10 * 6076.12
        zthr = 800
        alim = 700
    end

    return sl, tau, dmod, zthr, alim
end

end


function SeisWindowHeaders(in, out; key=[], minval=[], maxval=[], tmin=0,
                           tmax=99999, ntrace=1000000)

    @compat SeisProcessHeaders(in, out, [WindowHeaders],
                              [Dict(:key=>key,:minval=>minval,:maxval=>maxval)],
                               group="some", key=key, ntrace=ntrace,
                               update_tracenum=false)
    DATAPATH = get(ENV,"DATAPATH",join([pwd(),"/"]))
    filename_d_out = join([DATAPATH out "@data@"])
    filename_h_out = join([DATAPATH out "@headers@"])
    @compat nhead = length(fieldnames(Header))
    stream_h = open(filename_h_out)
    nx = round(Int,filesize(stream_h)/(nhead*4))
    h = GrabHeader(stream_h,1)
    close(stream_h)
    nt = convert(Int64,round((tmax - tmin)/h.d1)) #+ 1
    if nt > h.n1
	nt = h.n1
    end
    extent = Extent(convert(Int32,nt), convert(Int32,nx), convert(Int32,1),
                    convert(Int32,1), convert(Int32,1), convert(Float32,tmin),
                    convert(Float32,1), convert(Float32,0), convert(Float32,0),
                    convert(Float32,0), convert(Float32,h.d1),
                    convert(Float32,1), convert(Float32,1), convert(Float32,1),
                    convert(Float32,1), "Time", "Trace Number", "", "", "", "s",
                    "index", "", "", "", "")
    WriteTextHeader(out,extent,"native_float",4,filename_d_out,filename_h_out)

end

function WindowHeaders(h_in;key=[],minval=[],maxval=[])

    minval = convert(Array{Float32,1},vec(minval))
    maxval = convert(Array{Float32,1},vec(maxval))
    nx = length(h_in)
    key2 = AbstractString[]
    minval2 = Float32[]
    maxval2 = Float32[]
    for ikey=1:length(key)
	if key[ikey] != "t"
	    key2 = push!(key2,key[ikey])
	    minval2 = push!(minval2,minval[ikey])
	    maxval2 = push!(maxval2,maxval[ikey])
	end
    end
    return RejectHeaders(h_in,key2,minval2,maxval2,length(key2),length(h_in))

end

function RejectHeaders(h_in::Array{Header,1}, key::Array{AbstractString,1},
                       minval::Array{Float32,1}, maxval::Array{Float32,1},
                       nkeys, nx)
    h_out = Header[]
    keep = true
    key_val = 0f0
    for j=1:nx
	keep = true
	for ikey=1:nkeys

	    key_val = convert(Float32,getfield(h_in[j],Symbol(key[ikey])))

	    if (key_val < minval[ikey] || key_val > maxval[ikey])
		keep = false
	    end
	end
	if (keep==true)

	    h_out = push!(h_out,h_in[j])
	end
    end
    return h_out
end


mutable struct TD; ptr
    function TD(a::CuArray)
        d = Cptr[0]
        @cuda(cudnn,cudnnCreateTensorDescriptor,(Ptr{Cptr},),d)
        n = ndims(a)
        sz = [Cint(size(a,n-i+1)) for i=1:n]
        st = [Cint(stride(a,n-i+1)) for i=1:n]
        @cuda(cudnn,cudnnSetTensorNdDescriptor,
              (Cptr,UInt32,Cint,Ptr{Cint},Ptr{Cint}),
              d[1], DT(a), n, sz, st)
        td = new(d[1])
        finalizer(td, x->@cuda(cudnn,cudnnDestroyTensorDescriptor,(Cptr,),x.ptr))
        return td
    end
end

mutable struct FD; ptr
    function FD(a::CuArray)
        d = Cptr[0]
        @cuda(cudnn,cudnnCreateFilterDescriptor,(Ptr{Cptr},),d)
        n = ndims(a)
        sz = [Cint(size(a,n-i+1)) for i=1:n]
        cudnnVersion = cudnn_version()
        if cudnnVersion >= 5000
            @cuda(cudnn,cudnnSetFilterNdDescriptor,
                  (Cptr,UInt32,UInt32,Cint,Ptr{Cint}),
                  d[1], DT(a), 0,     n,   sz)
        elseif cudnnVersion >= 4000
            @cuda(cudnn,cudnnSetFilterNdDescriptor_v4,
                  (Cptr,UInt32,UInt32,Cint,Ptr{Cint}),
                  d[1], DT(a), 0,     n,   sz)
        else
            @cuda(cudnn,cudnnSetFilterNdDescriptor,
                  (Cptr,UInt32,Cint,Ptr{Cint}),
                  d[1], DT(a),    n,   sz)
        end
        fd = new(d[1])
        finalizer(fd, x->@cuda(cudnn,cudnnDestroyFilterDescriptor,(Cptr,),x.ptr))
        return fd
    end
end

mutable struct CD; ptr
    function CD(w::CuArray,x::CuArray; padding=0, stride=1, upscale=1, mode=0)
        d = Cptr[0]
        @cuda(cudnn,cudnnCreateConvolutionDescriptor,(Ptr{Cptr},),d)
        nd = ndims(x)-2
        cudnnVersion = cudnn_version()
        if cudnnVersion >= 4000
            @cuda(cudnn,cudnnSetConvolutionNdDescriptor,
                  (Cptr,Cint,Ptr{Cint},Ptr{Cint},Ptr{Cint},UInt32,UInt32),
                  d[1],nd,cdsize(padding,nd),cdsize(stride,nd),cdsize(upscale,nd),mode,DT(x))
        elseif cudnnVersion > 3000 # does not work when cudnnVersion==3000
            @cuda(cudnn,cudnnSetConvolutionNdDescriptor_v3,
                  (Cptr,Cint,Ptr{Cint},Ptr{Cint},Ptr{Cint},UInt32,UInt32),
                  d[1],nd,cdsize(padding,nd),cdsize(stride,nd),cdsize(upscale,nd),mode,DT(x))
        else
            @cuda(cudnn,cudnnSetConvolutionNdDescriptor,
                  (Cptr,Cint,Ptr{Cint},Ptr{Cint},Ptr{Cint},UInt32),
                  d[1],nd,cdsize(padding,nd),cdsize(stride,nd),cdsize(upscale,nd),mode)
        end
        cd = new(d[1])
        finalizer(cd, x->@cuda(cudnn,cudnnDestroyConvolutionDescriptor,(Cptr,),x.ptr))
        return cd
    end
end

mutable struct PD; ptr
    function PD(x::CuArray; window=2, padding=0, stride=window, mode=0, maxpoolingNanOpt=0)
        d = Cptr[0]
        @cuda(cudnn,cudnnCreatePoolingDescriptor,(Ptr{Cptr},),d)
        nd = ndims(x)-2
        cudnnVersion = cudnn_version()
        if cudnnVersion >= 5000
            @cuda(cudnn,cudnnSetPoolingNdDescriptor,
                  (Cptr,UInt32,UInt32,Cint,Ptr{Cint},Ptr{Cint},Ptr{Cint}),
                  d[1],mode,maxpoolingNanOpt,nd,cdsize(window,nd),cdsize(padding,nd),cdsize(stride,nd))
        elseif cudnnVersion >= 4000
            @cuda(cudnn,cudnnSetPoolingNdDescriptor_v4,
                  (Cptr,UInt32,UInt32,Cint,Ptr{Cint},Ptr{Cint},Ptr{Cint}),
                  d[1],mode,maxpoolingNanOpt,nd,cdsize(window,nd),cdsize(padding,nd),cdsize(stride,nd))
        else
            @cuda(cudnn,cudnnSetPoolingNdDescriptor,
                  (Cptr,UInt32,Cint,Ptr{Cint},Ptr{Cint},Ptr{Cint}),
                  d[1],mode,nd,cdsize(window,nd),cdsize(padding,nd),cdsize(stride,nd))
        end
        pd = new(d[1])
        finalizer(pd, x->@cuda(cudnn,cudnnDestroyPoolingDescriptor,(Cptr,),x.ptr))
        return pd
    end
end

# mutable struct RNN_D; ptr
#     function RNN_D(h_size::Int, num_layers::Int; handle=cudnnhandle())
#         d = Cptr[0]
#         @cuda(cudnn, cudnnCreateRNNDescriptor,(Ptr{Cptr},),d)

#         @cuda(cudnn, cudnnSetRNNDescriptor,(Ptr{Cptr},),d)

#         rnn_d = new(d[1])
#         finalizer(rnn_d, x->@cuda(cudnn,cudnnDestroyRNNDescriptor,(Cptr,),x.ptr))
#         return rnn_d
#     end
# end

import Base: unsafe_convert
unsafe_convert(::Type{Cptr}, td::TD)=td.ptr
unsafe_convert(::Type{Cptr}, fd::FD)=fd.ptr
unsafe_convert(::Type{Cptr}, cd::CD)=cd.ptr
unsafe_convert(::Type{Cptr}, pd::PD)=pd.ptr


using PyCall

export read_data, read_hod, read_my_hod, read_abacus

function read_hod(run, snapshot)
    mimicus = pyimport("mimicus")
    pos, vel = mimicus.read_raw_data(run=run, snapshot=snapshot,hod=true)
    return permutedims(pos), permutedims(vel)
end

function read_my_hod(run, snapshot, galaxy_type)
    dq = pyimport("dq")
    pos, vel = dq.read_hod_data(run=run, snapshot=snapshot,galaxy_type=galaxy_type)
    return permutedims(pos), permutedims(vel)
end

function read_data(run, snapshot, number_density, boxsize)
    dq = pyimport("dq")
    pos, vel, m200c = dq.read_halo_data(run=run, snapshot=snapshot)
    pos, vel = dq.cut_by_number_density(pos, vel, m200c, number_density, boxsize)
    return permutedims(pos), permutedims(vel)
end

function read_data(run, snapshot, min_mass, max_mass, boxsize)
    dq = pyimport("dq")
    pos, vel, m200c = dq.read_halo_data(run=run, snapshot=snapshot)
    mask = (max_mass .>= m200c .>= min_mass)
    pos = pos[mask,:]
    vel = vel[mask,:]
    return permutedims(pos), permutedims(vel)
end

function read_abacus()
    pypairvel = pyimport("pypairvel")
    pos, vel, boxsize, redshift = pypairvel.read_abacus()
    pos = permutedims(pos)
    vel = permutedims(vel)
    return pos, vel, boxsize, redshift
end


using DataStructures
using Distances

import Base.isequal
import Base.hash

"""
  DTBSplit

implements split of KD-Tree
"""
struct DTBSplit
    dim::Int64
    val::Float64
    sgn::Bool # 0: smaller, 1 : greater
end

"""
  MyE

implements undirected edge
"""
struct MyE
    a::Int64
    b::Int64
    MyE(pa::Int64,pb::Int64) = (pa<pb) ? new(pa,pb) : new(pb,pa)
end
function isequal(a::MyE,b::MyE)
    return a.a==b.a && a.b==b.b
end
function hash(a::MyE)
    return xor( hash(a.a) , hash(a.b) )
end
function to_matrix(ee::Vector{MyE})
    m::Array{Int64,2} = Array{Int64}(length(ee),2)
    for zi=1:length(ee); m[zi,1] = ee[zi].a; m[zi,2] = ee[zi].b; end
    return m
end

struct DTBProfiling
    n_comparisons::Int64
    n_break_boxdist::Int64
    n_break_onecomp::Int64
end
function DTBProfiling()
    return DTBProfiling(0,0,0)
end

"""
  DTBNode

implements node of a KD tree
"""
mutable struct DTBNode
    id::Int64 # identifies the node ; root has 00..00 , then
              # levels (right/left) are encoded as 01 or 10
              # i.e. node root->"right"->"right"->"left" ends with:
              # 10 01 01 00
              #  l r  r  R

    # data / indidces of this kd tree node
    data::Array{Float64,2}
    subset::Array{Int64}
    #splits::Array{DTBSplit,1}

    # kd tree node bounds
    box_lb::Array{Float64,1}
    box_ub::Array{Float64,1}

    # max_{v in this node} ( length of candidate edge of v)
    dQ::Float64

    left::DTBNode
    right::DTBNode

    #DTBNode(data::Array{Float64,2}) = (x = new(data); x.left=x; x.right=x)
    #function DTBNode(data::Array{Float64,2},subset::Array{Int64},splits::Array{DTBSplit,1},dQ::Float64)
    function DTBNode(id::Int64,data::Array{Float64,2},subset::Array{Int64},box_lb::Array{Float64,1},box_ub::Array{Float64,1},dQ::Float64)
        n = new(id,data,subset,box_lb,box_ub,dQ)
        n.left=n
        n.right=n
        return n
    end
end
function isequal(a::DTBNode,b::DTBNode)
    return a.id==b.id
end
function hash(a::DTBNode)
    #a.id
    hash(a.id) # we could also drop this hash.. should not matter..
end
function is_leaf(n::DTBNode)
    return n==n
    #return ( n.id & (1<<63) ) == (1<<63)
end

"""
  kdtree(xx::Array{Float64,2})

initializes root node of KD tree (but does not compute the splits)
To build the tree use:
  kdt_root = kdtree( data )
  kdtree_split!( kdt_root , 10 ) # 10 is the node size where we stop splitting
"""
function kdtree(xx::Array{Float64,2})
    #root = DTBNode(xx,-1,NaN,nothing,nothing)
    #root = DTBNode(Int64(0),xx,collect(1:size(xx,2)),fill!(ones(size(xx,1)),-Inf),fill!(ones(size(xx,1)),Inf),Inf)
    root = DTBNode(Int64(0),xx,collect(Int64(1):Int64(size(xx,2))),fill!(ones(size(xx,1)),-Inf),fill!(ones(size(xx,1)),Inf),Inf)
    return root
end

"""
  kdtree_split!(node::DTBNode, nmin::Int64)

computes splits of KD tree. nmin indicates the node size where we stop
splitting nodes.
"""
function kdtree_split!( node::DTBNode, nmin::Int64)

    # ensure that parent node is in the tree.
    #t[node.id] = node

    if( size(node.data,2) <= nmin )
        return node
    end

    mind = minimum(node.data,2)
    maxd = maximum(node.data,2)
    ds   = findmax( maxd-mind )
    ds   = ds[2]
    #println("$ds")
    vs   = median(node.data[ds,:])
    #println("$vs")
    bx   = node.data[ds,:] .<= vs

    range_a = node.subset[bx]
    range_b = node.subset[.~bx]

    data_a = node.data[:,bx]
    data_b = node.data[:,.~bx]

    box_lb_a = copy(node.box_lb)
    box_ub_a = copy(node.box_ub)
    box_ub_a[ds] = vs
    box_lb_b = copy(node.box_lb)
    box_lb_b[ds] = vs
    box_ub_b     = copy(node.box_ub)

    id::Int64 = node.id
    id_depth  = Int( ceil( (64-leading_zeros(id)) / 2) ) + 1 # in this cell we are, i.e. we have to shift id_depth times left by two bits..
    id_l = id   |  (1)<<(2*id_depth)
    #id_l = id_l |  (1)<<(63)
    id_r = id   |  (2)<<(2*id_depth)
    #id_r = id_r |  (1)<<(63)
    #println("id $(id) -> r $(id_l) , l $(id_r)")

    node_left  = kdtree_split!( DTBNode(id_l,data_a,range_a, box_lb_a   , box_ub_a  , Inf) , nmin )
    node_right = kdtree_split!( DTBNode(id_r,data_b,range_b, box_lb_b   , box_ub_b  , Inf) , nmin )

    node.left  = node_left
    node.right = node_right
    #println("SPLIT") #println(data_a) #println(data_b)
    #println(splits_l)
    return node
end

"""
  compute_emst(data::Array{Float64,2};nmin::Int64=64)

Computes EMST for the given data (where columns are samples).
nmin is the max number of elements in kd-tree node.
"""
function compute_emst(data::Array{Float64,2};nmin::Int=64)
    nmin64 = Int64(nmin)
    root = kdtree(data)
    kdtree_split!(root,nmin64)
    edges = dtb(root,IntDisjointSets(size(data,2)))
    return EMST.to_matrix( collect(edges) )
end

"""
  dtb(q::DTBNode,e::IntDisjointSets)

implements the dual-tree Boruvka algorithm which computes the EMST for the
given KD-tree.
"""
function dtb(q::DTBNode,e::IntDisjointSets)
    edges = Set{MyE}()

    #hashmap_QvsR = Dict{Tuple{DTBNode,DTBNode},Array{Float64,2}}()

    while(e.ngroups>1)
        ngroups = e.ngroups;
        println("--> ngroups: $ngroups")

        # prepare dicts for candidate edges..
        C_dcq = Dict{Int64,Float64}()
        C_e   = Dict{Int64,MyE}()
        # init themz
        roots = unique(e.parents)
        for ri in roots
            C_dcq[ri] = Inf
        end

        # compute..
        #fcn(q,q,e,C_dcq,C_e , hashmap_QvsR )
        fcn(q,q,e,C_dcq,C_e)

        # and now add the edges..
        for ne::MyE in values(C_e)
            union!(e,ne.a,ne.b)
            push!(edges,ne)
        end
    end
    return edges
end

"""
  fcn(q::DTBNode,r::DTBNode,e::IntDisjointSets , C_dcq::Dict{Int64,Float64} , C_e::Dict{Int64,MyE} )
Find Component Neighbors

Components are identified by the root of the component
C_dcq : component distances to candidate edges
C_e   : component candidate edges (comp i candidate edge is C_e[:,i])
"""
function fcn(q::DTBNode,r::DTBNode,e::IntDisjointSets , C_dcq::Dict{Int64,Float64} , C_e::Dict{Int64,MyE} )
#function fcn(q::DTBNode,r::DTBNode,e::IntDisjointSets , C_dcq::Dict{Int64,Float64} , C_e::Dict{Int64,Tuple{Int64,Int64}} )
#function fcn(q::DTBNode,r::DTBNode,e::IntDisjointSets , C_dcq::Dict{Int64,Float64} , C_e::Dict{Int64,MyE} , hashmap_QvsR::Dict{Tuple{DTBNode,DTBNode},Array{Float64,2}})

    # check all in same component
    onecomp::Bool  = true
    joined         = [q.subset;r.subset]
    #println(joined)
    for ji in joined
        if(~in_same_set(e,joined[1],ji))
            onecomp=false
            break
        end
    end
    if(onecomp)
        return
    end

    #check d(Q,R) > d(Q)
    dqr = computeDQR( q.box_lb,q.box_ub , r.box_lb,r.box_ub )
    #println("dq : $(dqr) , q.dQ : $(q.dQ)")
    if( dqr > q.dQ)
        #println(".")
        return
    end

    #check if R and Q in a leaf node
    if( q.left==q && r.left==r ) # that's not very elegant but probably what we have to do?!?
    #if( is_leaf(q) && is_leaf(r) )
        #println("LEAF NODES")
        #n_dQ = -Inf
        n_dQ::Float64 = q.dQ

        all_d_qr = Distances.pairwise(Euclidean(),q.data,r.data)

        # --> hashing these values does not really speed up things..
        #     and it uses tons of memory, such that fancier handling of
        #     hash map entries would be required
        #if(!haskey(hashmap_QvsR,(q,r)))
        #    if(!haskey(hashmap_QvsR,(r,q)))
        #        all_d_qr = Distances.pairwise(Euclidean(),q.data,r.data)
        #        #hashmap_QvsR[(q,r)] = all_d_qr
        #    else
        #        all_d_qr = transpose(hashmap_QvsR[(r,q)])
        #    end
        #    hashmap_QvsR[(r,q)] = transpose(all_d_qr)
        #else
        #    all_d_qr = hashmap_QvsR[(q,r)]
        #    #print('.')
        #end

        for iq = 1:size(q.subset,1)
            for ir = 1:size(r.subset,1)
                qq = q.subset[iq]
                rr = r.subset[ir]
                if( in_same_set(e , qq , rr ) )
                    continue
                end

                cq = find_root(e,rr) # compoment of q

                # check distance:
                # dist_qr::Float64 = sqrt( sum( (q.data[:,iq]-r.data[:,ir]).^2 ) )
                dist_qr = all_d_qr[iq,ir]

                if( dist_qr < C_dcq[ cq ] )
                    C_dcq[ cq ] = dist_qr
                    C_e[ cq ]   = MyE(qq,rr) #(qq,rr)
                    # and dQ !
                    n_dQ = max(n_dQ,dist_qr)
                    #n_dQ = dist_qr
                end
            end
        end
        q.dQ = n_dQ

        return
    end

    # do recursions..
    #fcn( q.left,r.left,e ,  C_dcq::Dict{Int64,Float64} , C_e::Dict{Int64,Tuple{Int64,Int64}} )
    #q_left  = is_leaf(q) ? q : t[q.left]
    #q_right = is_leaf(q) ? q : t[q.right]
    #r_left  = is_leaf(r) ? r : t[r.left]
    #r_right = is_leaf(r) ? r : t[r.right]
    fcn( q.left,r.left    , e ,  C_dcq , C_e ) #, hashmap_QvsR )
    fcn( q.right,r.left   , e ,  C_dcq , C_e ) #, hashmap_QvsR )
    fcn( q.left,r.right   , e ,  C_dcq , C_e ) #, hashmap_QvsR )
    fcn( q.right,r.right  , e ,  C_dcq , C_e ) #, hashmap_QvsR )
    q.dQ = max( q.left.dQ , q.right.dQ )
end

"""
  computeDQR( q_lb::Array{Float64,1} , q_ub::Array{Float64,1} , r_lb::Array{Float64,1} , r_ub::Array{Float64,1} )

compute min dist. between bounding boxes, i.e. between rectangular boxes Q/R with
bounds given by q_lb/q_ub and r_lb/r_ub
"""
function computeDQR( q_lb::Array{Float64,1} , q_ub::Array{Float64,1} , r_lb::Array{Float64,1} , r_ub::Array{Float64,1} )
    d::Int64  = length(q_lb)#size(q_lb,1)
    rdists::Array{Float64}    = zeros(d)
    for zd = 1:d
        rdists[zd] = max(  max( q_lb[zd] - r_ub[zd] , r_lb[zd] - q_ub[zd] ) , 0 )
    end
    dqr::Float64 = sqrt( sum( rdists.^2 ))
    return dqr
end


function GHIJjsPolynomials(arg0::jdouble, arg1::jdouble, arg2::jdouble, arg3::jdouble)
    return GHIJjsPolynomials((jdouble, jdouble, jdouble, jdouble), arg0, arg1, arg2, arg3)
end

function get_gjs(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getGjs", jdouble, (jint, jint), arg0, arg1)
end

function get_hjs(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getHjs", jdouble, (jint, jint), arg0, arg1)
end

function get_ijs(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getIjs", jdouble, (jint, jint), arg0, arg1)
end

function get_jjs(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getJjs", jdouble, (jint, jint), arg0, arg1)
end

function getd_gjsd_alpha(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdAlpha", jdouble, (jint, jint), arg0, arg1)
end

function getd_gjsd_beta(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdBeta", jdouble, (jint, jint), arg0, arg1)
end

function getd_gjsdh(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdh", jdouble, (jint, jint), arg0, arg1)
end

function getd_gjsdk(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdGjsdk", jdouble, (jint, jint), arg0, arg1)
end

function getd_hjsd_alpha(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdAlpha", jdouble, (jint, jint), arg0, arg1)
end

function getd_hjsd_beta(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdBeta", jdouble, (jint, jint), arg0, arg1)
end

function getd_hjsdh(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdh", jdouble, (jint, jint), arg0, arg1)
end

function getd_hjsdk(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdHjsdk", jdouble, (jint, jint), arg0, arg1)
end

function getd_ijsd_alpha(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdAlpha", jdouble, (jint, jint), arg0, arg1)
end

function getd_ijsd_beta(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdBeta", jdouble, (jint, jint), arg0, arg1)
end

function getd_ijsdh(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdh", jdouble, (jint, jint), arg0, arg1)
end

function getd_ijsdk(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdIjsdk", jdouble, (jint, jint), arg0, arg1)
end

function getd_jjsd_alpha(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdAlpha", jdouble, (jint, jint), arg0, arg1)
end

function getd_jjsd_beta(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdBeta", jdouble, (jint, jint), arg0, arg1)
end

function getd_jjsdh(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdh", jdouble, (jint, jint), arg0, arg1)
end

function getd_jjsdk(obj::GHIJjsPolynomials, arg0::jint, arg1::jint)
    return jcall(obj, "getdJjsdk", jdouble, (jint, jint), arg0, arg1)
end


"""An image, taken though a particular filter band"""
type Image
    # The image height.
    H::Int

    # The image width.
    W::Int

    # An HxW matrix of pixel intensities.
    pixels::Matrix{Float32}

    # The band id (takes on values from 1 to 5).
    b::Int

    # World coordinates
    wcs::WCSTransform

    # The components of the point spread function.
    psf::Vector{PsfComponent}

    # SDSS-specific identifiers. A field is a particular region of the sky.
    # A Camcol is the output of one camera column as part of a Run.
    run_num::Int
    camcol_num::Int
    field_num::Int

    # The background noise in nanomaggies. (varies by position)
    epsilon_mat::Array{Float32, 2}

    # The expected number of photons contributed to this image
    # by a source 1 nanomaggie in brightness. (varies by row)
    iota_vec::Array{Float32, 1}

    # storing a RawPSF here isn't ideal, because it's an SDSS type
    # not a Celeste type
    raw_psf_comp::RawPSF
end

"""
Tiles of pixels that share the same set of
relevant sources (or other calculations).  It contains all the information
necessary to compute the ELBO and derivatives in this patch of sky.

Note that this cannot be of type Image for the purposes of Celeste
because the raw wcs object is a C++ pointer which julia resonably
refuses to parallelize.

Attributes:
- h_range: The h pixel locations of the tile in the original image
- w_range: The w pixel locations of the tile in the original image
- pixels: The pixel values
- remainder: the same as in the Image type.
"""
immutable ImageTile
    b::Int

    h_range::UnitRange{Int}
    w_range::UnitRange{Int}
    pixels::Matrix{Float32}

    epsilon_mat::Matrix{Float32}
    iota_vec::Vector{Float32}
end

"""
Constructs an image tile from an image.

Args:
    - img: The Image to be broken into tiles
    - hh: The tile row index (in 1:number of tile rows)
    - ww: The tile column index (in 1:number of tile columns)
    - tile_width: The width and height of a tile in pixels
"""
function ImageTile(hh::Int, ww::Int, img::Image, tile_width::Int)
    h1 = 1 + (hh - 1) * tile_width
    h2 = min(hh * tile_width, img.H)
    h_range = h1:h2

    w1 = 1 + (ww - 1) * tile_width
    w2 = min(ww * tile_width, img.W)
    w_range = w1:w2

    pixels = img.pixels[h_range, w_range]
    epsilon_mat = img.epsilon_mat[h_range, w_range]
    iota_vec = img.iota_vec[h_range]

    @assert 1 <= img.b <= B
    ImageTile(img.b,
              h_range, w_range,
              pixels, epsilon_mat, iota_vec)
end

"""An image, taken though a particular filter band"""
type TiledImage
    # The image height.
    H::Int

    # The image width.
    W::Int

    # subimages
    tiles::Matrix{ImageTile}

    # all tiles have the same height and width
    tile_width::Int

    # The band id (takes on values from 1 to 5).
    b::Int

    # World coordinates
    wcs::WCSTransform

    # The components of the point spread function.
    psf::Vector{PsfComponent}

    # SDSS-specific identifiers. A field is a particular region of the sky.
    # A Camcol is the output of one camera column as part of a Run.
    run_num::Int
    camcol_num::Int
    field_num::Int

    # storing a RawPSF here isn't ideal, because it's an SDSS type
    # not a Celeste type
    raw_psf_comp::RawPSF
end

function TiledImage(img::Image; tile_width=20)
    WW = ceil(Int, img.W / tile_width)
    HH = ceil(Int, img.H / tile_width)
    tiles = ImageTile[ImageTile(hh, ww, img, tile_width) for hh=1:HH, ww=1:WW]
    TiledImage(img.H, img.W, tiles, tile_width, img.b, img.wcs, img.psf,
               img.run_num, img.camcol_num, img.field_num,
               img.raw_psf_comp)
end

""" Returns the tile containing (or nearest to) the specified pixel"""
function get_containing_tile(pixel_crds::Vector{Float64}, img::TiledImage)
    ht0 = ceil(Int, pixel_crds[1] / img.tile_width)
    wt0 = ceil(Int, pixel_crds[2] / img.tile_width)
    ht = max(1, min(size(img.tiles, 1), ht0))
    wt = max(1, min(size(img.tiles, 2), wt0))
    img.tiles[ht, wt]
end


EzXML.parsexml(x::ReadableFile) = parsexml(readstring(x))
EzXML.parsexml(x::Reader) = parsexml(readstring(x))

"""
    ScientificFormat

Scientific notation numbers in Excel.
If the format code is `0.00E+00` and the cell value is 12,200,000, then 1.22E+07 is
stored in the xml.
"""
struct ScientificFormat end
function Base.parse(::Type{ScientificFormat}, x::AbstractString)::Float64
    if contains(x, "E")
        x = lowercase(x)
    end
    parse(Float64, x)
end

"""
    GeneralFormat

The primary goal when a cell is using `GeneralFormat` is to render the cell content without user-specified
guidance to the best ability of the application.
It can be `String`, `Boolean`, `Error`, `Numbers`
`DateTime` do not fall into "GeneralFormat`
"""
struct GeneralFormat end
function Base.parse(::Type{GeneralFormat}, x)
    function isfloat(x)::Bool
        b = false
        if length(x) > 1
            if contains(x, ".")
                b = length(matchall(r"[.]", x)) < 2 ? true : false
            end
            if startswith(x, "-")
                b = length(matchall(r"[-]", x)) < 2 ? true : false
            end
        end
        b
    end
    function isint(x::AbstractString)::Bool
        b = false
        if !isempty(x)
            cha = matchall(r"[^0-9]", x)
            b = isempty(cha)                         ? true :
                (length(cha) == 1 && cha[1] == "-" ) ? true :
                                                     false
        end
        b
    end
    if isfloat(x)
        parse(Float64, x)
    else
        in(x, keys(EzExcel.ECMA_ERROR_VALUES))    ? missing :
        in(x, ("True", "true", "False", "false")) ? parse(Bool, lowercase(x)) :
        isint(x)                                  ? parse(Int, x) :
                                                  String(x)
    end
end

"""
    ExcelDateTime

Parse an Excel number (presumed to represent a date, a datetime or a time) to a DateTime

xldate: The Excel number
datemode: true:1904-based, false: 1900-based.

    `1904-01-01` is not regarded as a valid date in the `datemode == true`
     its "serial number" is zero.
 """
struct ExcelDateTime end
function Base.parse(::Type{ExcelDateTime}, xldate::AbstractString; args...)
    parse(ExcelDateTime, parse(ScientificFormat, xldate); args...)
end
function Base.parse(::Type{ExcelDateTime}, xldate::Real; datemode::Bool=false)::DateTime
    # Set the epoch based on the 1900/1904 datemode.
    epoch = if datemode
        DateTime(1904, 1, 1)
    else # Workaround Excel 1900 leap year bug by adjusting the epoch.
        xldate < 60 ? DateTime(1899, 12, 31) : DateTime(1899, 12, 30)
    end
    xldays, fraction = divrem(xldate, 1)
    xldays == 0 && (xldays = 1)

    sec = round(Int, fraction * 86400000.0)
    sec, milli_sec = divrem(sec, 1000)

    return epoch + Dates.Day(xldays) + Dates.Second(sec) + Dates.Millisecond(milli_sec)
end

struct SharedString end


# Runs a "photodynamic" model (in background).
# (Dynamics is currently held fixed).

using JLD2
#using Limbdark
include("../Limbdark/src/integrate_lightcurve.jl")
using DelimitedFiles
using Statistics
using PyPlot
using AffineInvariantMCMC

if ~@isdefined(CGS)
#  include("/Users/ericagol/Computer/Julia/CGS.jl")
  include("../CGS.jl")
  using Main.CGS
end

#include("/Users/ericagol/Computer/Julia/regress.jl")
 include("../regress.jl")
#include("/Users/ericagol/Software/NbodyGradient/src/ttv.jl")
 include("../../src/NbodyGradient/src/ttv.jl")

function run_pd(datafile,foutput,numwalkers,burnin,thinning,astep,numsamples_perwalker;nout=1000,
    limbprior=false,incprior=false,contamination=false)
  # Load in the Spitzer data:
  @load datafile tobs phot error i1 i2 nfile channel nexp nspitzer fnames texp

  # Now, call ttv to get a list of transit times and pair these with the data:
  # Change this path to point to ttv.jl:

  # Read in optimum orbital elements:
  elements = readdlm("../../data/elements_noprior_students.txt",',')

  # Set integration parameters:
  n=8
  nplanet = n-1
  t0 = 7257.93115525
  h  = 0.06
  tmax = 1600.0

  # Set up array for holding times of transit:
  ntt = zeros(Int64,n);
  for i=2:n
    ntt[i] = ceil(Int64,tmax/elements[i,2])+1
  end
  tt1 = zeros(3,n,maximum(ntt));
  # Save a counter for the actual number of transit times of each planet:
  count1 = zeros(Int64,n);
  # Set the "size" of the star (only checks that close to transit):
  rstar = 1e11
  # Call the ttv function:
  @time dq=ttvbv_elements!(n,t0,h,tmax,elements,tt1,count1,0.0,0,0,rstar)

  # Match the data to the model:
  # Take the radius ratios and limb-darkening parameters from
  # Delrez et al. (2018):

  depth_ch2  = [0.7277,0.6940,0.3566,0.4802,0.634,0.764,0.346] .* 0.01

  rp = sqrt.(depth_ch2) # Ratio of planet radius to stellar radius

  # Fractional radius errors from Delrez et al.:
  srp = [0.028,0.028,0.020,0.025,0.026,0.029,0.025]./[1.127,1.100,0.788,0.915,1.052,1.154,0.777].*rp

  # Set limb-darkening parameters:

#  u_ch1 = [0.168,0.244]
#  u_ch2 = [0.161,0.208]

  # Set transit durations:
  T = [36.19,42.31,49.33,55.92,63.14,68.53,76.92] ./(60*24)

  # Set impact parameters:
  b = [0.157,0.148,0.08,0.240,0.337,0.406,0.392]

  # Set orbital periods:
  P = [1.510826,2.421938,4.049218,6.101013,9.207541,12.352445,18.772863]  # Orbit periods in days

  # Set variable for computing semi-major axis to star ratio:
  aonr0 = (GRAV*MSUN*(24*3600)^2/(4pi^2*RSUN^3))^(1//3)

  # Now, loop over the datasets, figure out which planet(s) are
  # transiting in each, and compute the light curves:
  ip_inc = Int64[]   # Which planet for each transit
  ind_inc = Int64[]  # Transit indices for each planet
  ninc = zeros(Int64,nfile)  # Number of planet transits to include for each file
  for i=1:nfile
    # See which planets to include:
    for ip=1:nplanet
      tobs_start = tobs[i1[i]] - T[ip]/2
      tobs_end = tobs[i2[i]] + T[ip]/2
      for j=1:count1[ip+1]
       # See if the transit mid-point is less than half of a transit duration
        if (tt1[1,ip+1,j] > tobs_start) && (tt1[1,ip+1,j] < tobs_end)
          push!(ip_inc,ip)
          push!(ind_inc,j)
          ninc[i] +=1
        end
      end
    end
  end

  # Create a function which fits a polynomial and subtracts it:
  function fit_poly(t,f,ord)
      nt = size(t)[1]
      # create the basis functions:
      x = zeros(ord+1,nt)
      x[1,:] .= 1.0
      t0 = mean(t)
      sig = ones(nt)
      for i=1:ord
        x[i+1,:] .= (t .- t0).^i
      end
      coeff,cov = regress(x,f,sig)
      mod = zeros(nt) .+ coeff[1]
      for i=1:ord
        mod .+= coeff[i+1] .* x[i+1,:]
      end
      return mod
  end

#  function compute_model(time,phot,nfile,x0[15],texp,x0[8:14],x0[1:7],x0[16:17],x0[18:19],ninc,ip_inc,ind_inc,tt1)
  function compute_model(time,phot,nfile,rho_T1,texp,b,rp,u_ch1,u_ch2,ninc,ip_inc,ind_inc,tt1)
        # Create a photometric model with detrending:
        phot_mod = copy(phot)
        fill!(phot_mod,0.0)
        # Create a vector for a model of stellar/instrumental variabilty:
        phot_vary = copy(phot)
        fill!(phot_vary,0.0)

        # Density of T1 star in units of solar density:
        fac = rho_T1^(1//3)*AU/RSUN

        # Exposure time in days:
        texp_day = texp/(60*24)

        # Two limb-darkening coefficients:
        nu = 2

        itrans = 0
        # Initialize transit structure (no derivatives for now):
        #trans =  Limbdark.transit_init(0.1,0.5,[0.2,0.2],false)
        #trans =  transit_init(0.1,0.5,[0.2,0.2],false)
        # Loop over each light curve:

        for ifile=1:nfile
          tcurr = tobs[i1[ifile]:i2[ifile]]
          # First set the limb-darkening parameters
          # for the current light curve
          if channel[ifile] == "c1"
            #trans.u_n = u_ch1
            trans =  transit_init(0.1,0.5,u_ch1,false)
          else
            #trans.u_n = u_ch2
            trans =  transit_init(0.1,0.5,u_ch2,false)
          end
          # Number of exposure times for this case:
          nt = i2[ifile]-i1[ifile]+1
          # Set up array to hold the time-integrated flux for this light curve:
          #favg = zeros(5+nu,nt)
          favg = ones(5+nu,nt)
          # Set up array to hold the time-integrated flux for each planet:
          favg1 = zeros(5+nu,nt)
          # Set up arrays to hold statistics on integrations:
          neval1 = zeros(Int64,nt)
          depthmax1 = zeros(Int64,nt)
          # Loop over each transit:
          for it = 1:ninc[ifile]
            fill!(favg1,0.0)
           itrans += 1
            ttbv = tt1[:,ip_inc[itrans]+1,ind_inc[itrans]]
            # Now, create a model.  Convert impact parameter and
            # sky velocity to units of radius of the star:
            param = [ttbv[1],ttbv[2]*fac,ttbv[3]*fac]
            # Set the radius ratio and impact parameter to the current planet:
            param[3] = b[ip_inc[itrans]]
            trans.r = rp[ip_inc[itrans]]
            #Limbdark.integrate_lightcurve!(trans,param,tcurr,texp_day,favg1,nt,1e-11,40,neval1,depthmax1)
            integrate_lightcurve!(trans,param,tcurr,texp_day,favg1,nt,1e-11,40,neval1,depthmax1)
            favg .+= favg1
            # Subtract off 1 for additional planets
            #if it > 1
            #  favg[1,:] .-= 1
            #end
          end
          # Now, remove a polynomial:
          phot_mod[i1[ifile]:i2[ifile]] .= favg[1,:]
          phot_vary[i1[ifile]:i2[ifile]] .= fit_poly(tcurr,phot[i1[ifile]:i2[ifile]]./favg[1,:],3)
        end
      return phot_mod,phot_vary
  end

  # Compute the scatter:
  # Previously optimized parameters:
#  x0 = [ 0.08510408205855542, 0.08351749544695007, 0.060057015907927426 , 0.07003499168340532, 0.07938534728488905, 0.08571785728160501, 0.05767832213757894, 0.06777531110636684, 0.07268527380271149, 0.0003227708869417413, 0.17817318017323253, 0.2926207489330042, 0.36533945405158097, 0.3735584459205608, 52.46814828992533, 0.0961562252101869, 0.5059574689387107, 0.30961490912899814, 0.29644507311377927]
  x0 = [0.08511305133271845, 0.08352547037122816, 0.06005344100378423, 0.0700421002759512, 0.07962187102240074, 0.08611264006593941, 0.05767381066632817, 0.07876438173347886, 0.08351738136283536, 0.0008769148946956045, 0.18225051020178037, 0.30070386193030363, 0.36817942906674744, 0.3756424617589329, 52.34377029283971, 0.11235270319764341, 0.42037661035916857, 0.352424321959808, 0.2864053200404355]

  # Convert limb-darkening:
  q = x0[16:19]
  u_ch1_start = [2*sqrt(q[1])*q[2],sqrt(q[1])*(1 -2q[2])]
  u_ch2_start = [2*sqrt(q[3])*q[4],sqrt(q[3])*(1 -2q[4])]

  # This function computes an initial model in order to derive the scatter parameters (with no contamination or priors):
  phot_mod,phot_vary = compute_model(tobs,phot,nfile,x0[15],texp,x0[8:14],x0[1:7],u_ch1_start,u_ch2_start,ninc,ip_inc,ind_inc,tt1)
  #  clf(); plot(phot ./ phot_vary,"."); plot(phot_mod); plot(phot ./ (phot_vary .* phot_mod), "o")

  # Now compute the standard deviation of residuals for each time series:
  sig_file = zeros(nfile)
  resid = phot .- (phot_vary .* phot_mod)
  for ifile=1:nfile
      i10 = i1[ifile]; i20 = i2[ifile]
      sig_file[ifile] = std(resid[i10:i20])
  end

  # Now compute a light curve, and run a Markov chain:

  # using  AdvancedHMC

  function compute_chain(tobs,phot,nfile,texp,ninc,ip_inc,ind_inc,tt1,x0,srp,
          n,sig_file,numdims,numwalkers,thinning,numsamples_perwalker,burnin,astep)
      tstart = time()
      # Keep track of how many times the likelihood function is called:
      nlike = 0
      nreject = 0
      # Creates a closure for sampling from the photometric model:
      np = n-1

      # Set up function to compute the log prior:
      function log_prior(x::Array{T,1}) where {T <: Real}
        # Returns log(prior) and its derivative:
        rp = x[1:np]; b = x[np+1:2*np]; rho_T1=x[2np+1]; q = x[2np+2:2np+5]
        # Check that impact parameters are in bounds:
        if maximum(abs.(b)) >= 1 || (!incprior && minimum(b) <= 0)
           return -Inf::T
        end
        # Check that limb-darkening parameters are in bounds:
        if minimum(q) <= 0 || maximum(q) >= 1
          return -Inf::T
        end
        # Check that radius ratios are in bounds:
        if minimum(rp) <= 0 || maximum(rp) > 0.2
          return -Inf::T
        end
        # Check that density is in bounds:
        if rho_T1 <= 0 || rho_T1 >= 100
          return -Inf::T
        end
        # Check that contamination parameter is in bounds:
        if contamination
          if abs(x[2np+6]) >= 1
            return -Inf::T
          end
        end
        # Add in prior on the limb-darkening parameters:
        logp = 0.0
        if limbprior
          u_ch1 = [2*sqrt(q[1])*q[2],sqrt(q[1])*(1 -2q[2])]
          u_ch2 = [2*sqrt(q[3])*q[4],sqrt(q[3])*(1 -2q[4])]
          # Priors from Elsa's analysis:
          logp -= 0.5*((u_ch1[1]-0.1633)/0.0364)^2
          logp -= 0.5*((u_ch1[2]-0.2549)/0.0570)^2
          logp -= 0.5*((u_ch2[1]-0.1442)/0.0324)^2
          logp -= 0.5*((u_ch2[2]-0.2173)/0.0482)^2
          #return logp::T
        end
        # Add in prior on the inclinations:
        if incprior
          # Now compute prior - sigtheta is the last parameter:
          sigtheta = x[end]
          # Check if sigtheta is in bounds:
          if sigtheta <= 0.0 || sigtheta >= 5.0  # Safe bet than inclination scatter is < 5 degrees
            return -Inf::T
          end
          # Compute a/R_* from density, period (ignore small uncertainty on orbital period):
          aonr = rho_T1^(1//3) .* (aonr0 * P.^(2//3))
          # Compute inclinations of each planet, ignore small eccentricity:
          inc_planet = acos.(b ./ aonr) .* (180/pi)  # in degrees
          # Compute mean inclination:
          inc_mean = sum(inc_planet)/np
          # Now, compute prior:
          for ip=1:np
            logp -= 0.5*((inc_planet[ip]-inc_mean)/sigtheta)^2
          end
          # Add in integral normalization
          logp -= np*log(sigtheta)
        end
        return logp::T
      end

      # Convert to Kipping's parameterization:
      #q_ch1 = [(u_ch1[1]+u_ch1[2])^2,0.5*u_ch1[1]/(u_ch1[1]+u_ch1[2])]
      #q_ch2 = [(u_ch2[1]+u_ch2[2])^2,0.5*u_ch2[1]/(u_ch2[1]+u_ch2[2])]

      # For now we are holding the dynamical model fixed, and simply varying the model parameters.
      #x0 = permutedims([rp;b;[rho_T1];q_ch1;q_ch2])  # Radius-ratios, impact paramters, density, limb-darkening

      function generate_walkers(x0::Array{T,1},numwalkers::Int64) where {T <: Real}
          # Generates an array of walkers:
          xstart = zeros(size(x0)[1],numwalkers)
          println("size of xstart: ",size(xstart))
          llike_start = zeros(numwalkers)
          for i=1:numwalkers
              llike_start[i] = -Inf
              while llike_start[i] == -Inf
                # rp drawn from Delrez et al. uncertainties:
                xstart[1:np,i] .= abs.(x0[1:np] .+ randn(np) .* srp)
                # b drawn from Gaussian of width 0.1:
                xstart[np+1:2np,i] .= abs.(x0[np+1:2np] .+ 0.1 .*rand(np))
                # rho drawn from a Gaussian:
                xstart[2np+1,i] = x0[2np+1] + 1.2 *randn()
                # q's drawn with some scatter about optimum:
                xstart[2np+2:2np+5,i] .= abs.(x0[16:19] .+ 0.25 .* randn(4))
                # Draw contamination from N(0,0.05):
                if contamination
                  xstart[2np+6,i] = 0.05*randn()
                end
                # Draw sigtheta from 0.01 to 1.0 degrees:
                if incprior
                  xstart[end,i] = 10^(rand()*2-2)
                end
                # Compute likelihood:
                llike_start[i] = compute_loglike(xstart[:,i])
              end
          end
          # That's it!
      return xstart, llike_start
      end

      # Exposure time in days:
      texp_day = texp/(60*24)
      # Create a vector to hold the photometric model...
      phot_mod = copy(phot)
      # Small-planet approximation:
      #phot_small = copy(phot)
      # Create a vector for a model of stellar/instrumental variabilty:
      phot_vary = copy(phot)

   #   function compute_model(time,phot,nfile,rho_T1,texp,b,rp,u_ch1,u_ch2,ninc,ip_inc,ind_inc,tt1)
      function compute_loglike(x::Array{T,1}) where {T <: Real}
        # Create a photometric model with detrending.
        # Check that we are not out of bounds:
        lprior  = log_prior(x)
        if lprior == (-Inf)
           nreject += 1
           return lprior
        end
        # Unpack the parameters:
        rp = x[1:np]; b = x[np+1:2*np]; rho_T1,q1_ch1,q2_ch1,q1_ch2,q2_ch2 = x[2np+1:2np+5]
        if contamination
          epsilon = x[2np+6]
        end

        # Density of T1 star in units of solar density for conversion
        # of N-body code units to Trappist-1 units:
        fac = rho_T1^(1//3)*AU/RSUN

        # Fill photometric model with zeros:
        fill!(phot_mod,0.0)
        # ... and detrending model:
        fill!(phot_vary,0.0)

        # Two limb-darkening coefficients:
        nu = 2
        # Convert from Kipping parameterization back to u_1, u_2:
        u_ch1_curr = [2*sqrt(q1_ch1)*q2_ch1,sqrt(q1_ch1)*(1 -2q2_ch1)]
        u_ch2_curr = [2*sqrt(q1_ch2)*q2_ch2,sqrt(q1_ch2)*(1 -2q2_ch2)]

        itrans = 0
        # Initialize transit structure (no derivatives for now):
        #trans =  Limbdark.transit_init(0.1,0.5,[0.2,0.2],false)
        # Loop over each light curve:
        for ifile=1:nfile
          # An array of the current Spitzer observation times being modeled:
          tcurr = tobs[i1[ifile]:i2[ifile]]
          # First set the limb-darkening parameters
          # for the current light curve
          if channel[ifile] == "c1"
            #trans =  Limbdark.transit_init(0.1,0.5,u_ch1_curr,false)
            trans =  transit_init(0.1,0.5,u_ch1_curr,false)
            #trans.u_n .= u_ch1_curr
            #trans.u_n[1] = u_ch1_curr[1]
            #trans.u_n[2] = u_ch1_curr[2]
          else
            #trans =  Limbdark.transit_init(0.1,0.5,u_ch2_curr,false)
            trans =  transit_init(0.1,0.5,u_ch2_curr,false)
            #trans.u_n .= u_ch2_curr
            #trans.u_n[1] = u_ch2_curr[1]
            #trans.u_n[2] = u_ch2_curr[2]
          end
          # Number of exposure times for this case:
          nt = i2[ifile]-i1[ifile]+1
          # Set up array to hold the time-integrated flux for this light curve:
          #favg = zeros(5+nu,nt)
          favg = ones(5+nu,nt)
          # Small-planet approximation:
          #fsmall = ones(nt)
          # Set up array to hold the time-integrated flux for each planet:
          favg1 = zeros(5+nu,nt)
          # Set up arrays to hold statistics on time integrations:
          neval1 = zeros(Int64,nt)
          depthmax1 = zeros(Int64,nt)
          # Loop over each transit:
          for it = 1:ninc[ifile]
            fill!(favg1,0.0)
            itrans += 1
            # ttbv holds the transit time, sky-velocity, and impact parameter from N-body model:
            ttbv = tt1[:,ip_inc[itrans]+1,ind_inc[itrans]]
            # Now, create a photometric model.  Convert impact parameter and
            # sky velocity to units of radius of the star (although we're not
            # using impact-parameter variations at the moment):
            # param = [ttbv[1],ttbv[2]*fac,ttbv[3]*fac]
            # Set the radius ratio and impact parameter to the current planet:
            param = [ttbv[1],ttbv[2]*fac,b[ip_inc[itrans]]]
            # Set radius in transit model structure to radius of current planet:
            trans.r = rp[ip_inc[itrans]]
            # Evaluate the adaptively time-integrated quadratic limb-darkened transit model:
            #Limbdark.integrate_lightcurve!(trans,param,tcurr,texp_day,favg1,nt,1e-11,40,neval1,depthmax1)
            integrate_lightcurve!(trans,param,tcurr,texp_day,favg1,nt,1e-11,40,neval1,depthmax1)
            # Small planet approximation:
            #boft2 = ((tcurr .- param[1]) .*param[2]).^2 .+ param[3]^2; mu = sqrt.(1 .- boft2[boft2 .<= 1])
            #fsmall[boft2 .<= 1] .-= trans.r^2 .* (1 .- trans.u_n[1] .* (1 .- mu) - trans.u_n[2] .* (1 .- mu).^2) ./
            #        (1 - trans.u_n[1]/3 - trans.u_n[2]/6)
            # Add to the current model:
            favg .+= favg1
            # Subtract off 1 for additional planets
            #if it > 1
            #  favg[1,:] .-= 1
            #end
          end
          # Now, remove a polynomial:
          if contamination
            phot_mod[i1[ifile]:i2[ifile]] .= favg[1,:] .* (1-epsilon) .+ epsilon
            #phot_small[i1[ifile]:i2[ifile]] .= fsmall .* (1-epsilon) .+ epsilon
          else
            phot_mod[i1[ifile]:i2[ifile]] .= favg[1,:]
            #phot_small[i1[ifile]:i2[ifile]] .= fsmall
          end
#          if channel[ifile] == "c1"
#            println("Limb-darkening: ",trans.u_n," ",[2sqrt(x[2np+2])*x[2np+3],sqrt(x[2np+2])*(1-2x[2np+3])])
#            clf(); plot(tcurr,phot_mod[i1[ifile]:i2[ifile]]); plot(tcurr,phot_small[i1[ifile]:i2[ifile]])
#            read(stdin,Char)
#          else
#            println("Limb-darkening: ",trans.u_n," ",[2sqrt(x[2np+4])*x[2np+5],sqrt(x[2np+4])*(1-2x[2np+5])])
#            clf(); plot(tcurr,phot_mod[i1[ifile]:i2[ifile]]); plot(tcurr,phot_small[i1[ifile]:i2[ifile]])
#            read(stdin,Char)
#          end
          phot_vary[i1[ifile]:i2[ifile]] .= fit_poly(tcurr,phot[i1[ifile]:i2[ifile]]./phot_mod[i1[ifile]:i2[ifile]],3)
        end
        # Compute the chi_square:
        chi_square = 0.0
        resid = phot .- (phot_vary .* phot_mod)
        for ifile=1:nfile
          i10 = i1[ifile]; i20 = i2[ifile]
          chi_square += sum(resid[i10:i20].^2) / sig_file[ifile]^2
        end
        # Return log posterior = log(prior) + log(likelihood):
        llike = -0.5*chi_square + lprior
        nlike += 1
        if mod(nlike,nout) == 0
          println(nreject," ",nlike," log like: ",llike," time [hr]: ",(time()-tstart)/3600)
        end
        return llike
      end

      # Now sample.  Following example on AffineInvariantMCMC page:
      xstart, llstart = generate_walkers(x0,numwalkers)
      println("llstart: ",llstart)
      chain, llhoodvals = AffineInvariantMCMC.sample(compute_loglike,numwalkers,xstart,burnin,1,astep)
      chain, llhoodvals = AffineInvariantMCMC.sample(compute_loglike,numwalkers,chain[:, :, end], numsamples_perwalker, thinning,astep)
      flatchain, flatllhoodvals = AffineInvariantMCMC.flattenmcmcarray(chain, llhoodvals)
      return chain,llhoodvals,flatchain,flatllhoodvals
  end

  # Start with optimized values:
#  x0 = [ 0.08510408205855542, 0.08351749544695007, 0.060057015907927426 , 0.07003499168340532, 0.07938534728488905, 0.08571785728160501, 0.05767832213757894, 0.06777531110636684, 0.07268527380271149, 0.0003227708869417413, 0.17817318017323253, 0.2926207489330042, 0.36533945405158097, 0.3735584459205608, 52.46814828992533, 0.0961562252101869, 0.5059574689387107, 0.30961490912899814, 0.29644507311377927]
  x0 =[0.08511305133271845, 0.08352547037122816, 0.06005344100378423, 0.0700421002759512, 0.07962187102240074, 0.08611264006593941, 0.05767381066632817, 0.07876438173347886, 0.08351738136283536, 0.0008769148946956045, 0.18225051020178037, 0.30070386193030363, 0.36817942906674744, 0.3756424617589329, 52.34377029283971, 0.11235270319764341, 0.42037661035916857, 0.352424321959808, 0.2864053200404355]
  numdims = 19
  if contamination
    # Add in contamination parameter:
    numdims += 1
    push!(x0,0.0)
  end
  if incprior
    numdims += 1
    # Add in sigtheta parameter:
    push!(x0,0.5)
  end
  # numwalkers = 50
  # thinning = 10
  # This is going to run for ~2 days:
  # numsamples_perwalker = 500
  # burnin = 10
  # Take smaller steps to increase the acceptance rate:
  # astep = 1.2
  chain,llhoodvals,flatchain,flatllhoodvals =
  compute_chain(tobs,phot,nfile,texp,ninc,ip_inc,ind_inc,tt1,x0,srp,n,
      sig_file,numdims,numwalkers,thinning,numsamples_perwalker,burnin,astep)
  @save foutput chain llhoodvals flatchain flatllhoodvals tobs phot nfile texp ninc ip_inc ind_inc tt1 x0 n sig_file i1 i2 thinning numwalkers numdims numsamples_perwalker burnin astep limbprior incprior contamination

  # Now save to output file
return
end

#= Example:
 datafile = "../../data/T1_Spitzer_data.jld2"; foutput = "T1_pd_MCMC_v01.jld2"; numwalkers = 50; burnin = 10; thinning = 1; astep = 2.0; nsteps = 50
 run_pd(datafile,foutput,numwalkers,burnin,thinning,astep,nsteps;nout=100)
=#