#=
This contains all of the pieces needed to add a new code gen target.
The following three functions must be provided.

1. get_external_language_elements() - file extensions, comment chars etc.
2. generate_external_code_layer(var, entities, terms, lorr, vors) - Turns symbolic expressions into code
3. generate_external_files(var, lhs_vol, lhs_surf, rhs_vol, rhs_surf) - Writes all files based on generated code

You will have access to anything in the Femshop module scope because this file will be included there.
=#

function get_external_language_elements()
    file_extension = ".cpp";
    comment_char = "//";
    block_comment = ["/*"; "*/"];

    return (file_extension, comment_char, block_comment);
end

#=
Translate the symbolic layer into code for the elemental assembly.
var - unknown variable or array of variables.
entities - leaf nodes of the computational graph. Type is SymEntity(see symexpression.jl) or number.
terms - array of additive terms, separated for convenience. [t1, t2, t3] for expression t1+t2+t3 (see note below)
lorr - LHS or RHS will have constant values LHS or RHS which are "lhs", "rhs"
vors - volume or surface will have values "volume" or "surface"

Note on terms: Each term is a SymExpression(see symexpression.jl) which is essentially a Julia Expr and list of entities.
The Expr holds the computational graph for each term. LHS and RHS are determined by separating terms that involve
unknowns from those that don't. Also, in well formed FEM problems each term should be multiplied by a test function,
and thus quadrature can be done for each term separately.
=#
function generate_external_code_layer(var, entities, terms, lorr, vors)
    # This can be divided up into smaller functions however you wish.
    # The whole body of code is stored in a string that will be inserted in the
    # appropriate function during file writing.
    code = "";

    # Allocate/Evaluate or fetch the values for each needed entity.
    # Note: This could be staged and interleaved with the calculation part for complex problems. TODO
    (prep_code, to_delete) = dendrotarget_prepare_needed_values(entities, var, lorr, vors);
    code *= prep_code;
    code *= "\n";

    # Form the final elemental calculation
    code *= dendrotarget_make_elemental_computation(terms, to_delete, var, lorr, vors);

    return code;
end

# Create all of the code files.
# The input are code strings generated above. They will be inserted into their appropriate files.
function generate_external_files(var, lhs_vol, lhs_surf, rhs_vol, rhs_surf)
    if codegen_params === nothing || length(codegen_params) < 5
        # default values (max_depth=6, wavelet_tol = 0.1, partition_tol = 0.3, solve_tol = 1e-6, max_iters = 100)
        params=(5, 1, 0.3, 0.000001, 100);
    else
        params = codegen_params;
    end
    # Again, this can be split up into smaller functions as you wish.
    dendro_utils_file();            # various math and other utils that will be filled as needed.
    dendro_main_file(var);          # The main file
    dendro_config_file(params);     # Dendro parameters and other configuration
    dendro_prob_file();             # Problem details: boundary conditions, initial conditions, etc.
    dendro_genfunction_file();      # Functions for coefficients, boundary conditions etc.
    dendro_bilinear_file(lhs_vol);  # The matrix part (feMatrix)
    dendro_linear_file(rhs_vol);    # The vector part (feVector)
    if Femshop.prob.time_dependent
        dendro_stepper_file();      # time stepper
    end
    dendro_output_file();           # Output code
    dendro_cmake_file();            # Writes a cmake file
    dendro_readme_file();           # readme
end

#########################################################
# code writing utilities
#########################################################

# numbers: 2 -> "2"
# strings: "thing" -> "\"thing\""
# arrays: [1 2; 3 4] -> "{{1,2},{3,4}}"
function cpp_gen_string(v)
    if typeof(v) == String
        return "\""*v*"\"";

    elseif typeof(v) <: Number
        return string(v);

    elseif typeof(v) <: Array
        if ndims(v) == 1 # "{a, b, c}"
            n = length(v);
            str = "{";
            for i=1:n
                str = str*cpp_gen_string(v[i]);
                if i < n
                    str = str*", ";
                end
            end
            str = str*"}";
        elseif ndims(v) == 2 # "{{a, b}, {c, d}}
            (n,m) = size(v);
            str = "{";
            for i=1:n
                for j=1:m
                    str = str*cpp_gen_string(v[i,j]);
                    if i*j < n*m
                        str = str*", ";
                    end
                end
            end
            str = str*"}";
        end
        return str;

    elseif typeof(v) == GenFunction
        return v.name;
    else
        return string(v);
    end
end

#= Generates:
std::function<returntype(argtypes)> name = [captures](args){
    (content)
};
# Note: content should be an array of lines to allow indentation
=#
function cpp_functional(indent, name, args, argtypes, ret, rettype, captures, content)
    lines = fill("", length(content)+2);
    inner_indent = indent*"    ";

    arg = string(argtypes[1])*" "*string(args[1]);
    argtype = string(argtypes[1]);
    for i=2:length(args)
        arg = arg*", "*string(argtypes[i])*" "*string(args[i]);
        argtype = argtype*", "*string(argtypes[i]);
    end
    ret = string(ret);

    lines[1] = indent*"std::function<"*rettype*"("*argtype*")> "*name*" = ["*captures*"]("*arg*"){";
    for i=1:length(content)
        lines[i+1] = inner_indent*content[i];
    end
    lines[end] = indent*"};";

    return lines;
end

#= Generates:
returntype name(args){
    (content)
}
# Note: content should be an array of lines to allow indentation
=#
function cpp_function_def(indent, name, args, argtypes, rettype, content)
    lines = fill("", length(content)+2);
    inner_indent = indent*"    ";

    arg = string(argtypes[1])*" "*string(args[1]);
    argtype = string(argtypes[1]);
    for i=2:length(args)
        arg = arg*", "*string(argtypes[i])*" "*string(args[i]);
        argtype = argtype*", "*string(argtypes[i]);
    end

    lines[1] = indent*rettype*" "*name*"("*arg*"){";
    for i=1:length(content)
        lines[i+1] = inner_indent*content[i];
    end
    lines[end] = indent*"}";

    return lines;
end

#= Generates:
for(iterator=range[1]; iterator<range[2]; iterator+=step){
    (content)
}
# Note: content should be an array of lines to allow indentation
=#
function cpp_for_loop(indent, iterator, range, step, content)
    lines = [];
    inner_indent = indent*"    ";
    push!(lines, indent*"for("*iterator*" = "*string(range[1])*";"*iterator*" < "*string(range[2])*";"*iterator*" += "*string(step)*"){");
    for i=1:length(content)
        push!(lines, inner_indent*content[i]);
    end
    push!(lines, indent*"}")

    return lines;
end

# changes symbol "a" to symbol "b" in expression ex
function dendro_swap_symbol(a, b, ex)
    if typeof(ex) == Symbol
        if ex === a
            return b;
        else
            return ex;
        end
    elseif typeof(ex) <: Number
        return ex;
    elseif typeof(ex) == Expr && length(ex.args) > 1
        swapped = copy(ex);
        for i=1:length(ex.args)
            swapped.args[i] = dendro_swap_symbol(a,b,ex.args[i]);
        end
        return swapped;
    else
        return ex;
    end
end

# changes the math operators in the expr
function dendro_change_math_ops(ex)
    if typeof(ex) == Expr
        if ex.head === :.
            # a broadcast operator, change to call
            ex.head = :call
            ex.args = [ex.args[1]; ex.args[2].args];
        end
        for i=1:length(ex.args)
            ex.args[i] = dendro_change_math_ops(ex.args[i]);
        end
    elseif typeof(ex) == Symbol
        if ex === :.+   ex = :+; end
        if ex === :.-   ex = :-; end
        if ex === :.*   ex = :*; end
        if ex === :./   ex = :/; end
        if (ex === :.^ || ex === :^)   ex = :pow; end
        if ex === :abs   ex = :fabs; end
    end
    return ex;
end

# builds a c++ functional for a constant
function dendro_number_to_function(name, val)
    indent = "";
    args = ["x"; "y"; "z"; "var"];
    argtypes = ["double"; "double"; "double"; "double*"];
    ret = "";
    rettype = "void";
    captures = "gridX_to_X,gridY_to_Y,gridZ_to_Z";
    content = ["var[0] = "*string(val)*";"];
    return cpp_functional(indent, name, args, argtypes, ret, rettype, captures, content);
end

function dendro_genfunction_to_string(genfun)
    newex = dendro_swap_symbol(:x, :(gridX_to_X(x)), genfun.expr); # swap x for gridX_to_X(x)
    newex = dendro_swap_symbol(:y, :(gridY_to_Y(y)), newex); # swap y for gridY_to_Y(y)
    newex = dendro_swap_symbol(:z, :(gridZ_to_Z(z)), newex); # swap z for gridZ_to_Z(z)
    newex = dendro_swap_symbol(:pi, :M_PI, newex); # swap pi for M_PI
    newex = dendro_change_math_ops(newex); # change operators to match C++
    s = string(newex);
    ns = replace(s, r"([\d)])([(A-Za-z])" => s"\1*\2"); # explicitly multiply with "*"
    return ns;
end

#######################################################
# Write code files

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

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

function dendro_main_file(var)
    file = add_generated_file(project_name*".cpp", dir="src");

    valvec = string(variables[1].symvar[1]);

    alloc_dof = "";
    set_dof = "";
    init_fun = "";
    for i=1:length(variables)
        for j=1:length(variables[i].symvar)
            nam = string(variables[i].symvar[j]);
            # if initial condition, use it, otherwise zero
            if length(prob.initial) >= i && !(prob.initial[i] === nothing)
                if typeof(prob.initial[i]) <: Array
                    init_fun = cpp_gen_string(prob.initial[i][j]);
                else
                    init_fun = cpp_gen_string(prob.initial[i]);
                end
            else
                init_fun = "zero_init"
            end
            alloc_dof = alloc_dof*"double * "*nam*"=octDA->getVecPointerToDof(uSolVecPtr,VAR::M_UI"*nam*", false,false);\n";
            set_dof = set_dof*"octDA->setVectorByFunction("*nam*","*init_fun*",false,false,1);\n";
            if prob.time_dependent
                alloc_dof = alloc_dof*"double * "*nam*"_next=octDA->getVecPointerToDof(uSolVecPtr,VAR::M_UI"*nam*"_NEXT, false,false);\n";
                set_dof = set_dof*"octDA->setVectorByFunction("*nam*"_next,"*init_fun*",false,false,1);\n";
            end
        end
    end
    for i=1:length(coefficients)
        for j=1:length(coefficients[i].symvar)
            nam = string(coefficients[i].symvar[j]);
            fun = cpp_gen_string(coefficients[i].value[j]);
            alloc_dof = alloc_dof*"double * "*nam*"=octDA->getVecPointerToDof(uSolVecPtr,VAR::M_UI"*nam*", false,false);\n";
            set_dof = set_dof*"octDA->setVectorByFunction("*nam*","*fun*",false,false,1);\n";
        end
    end

    # What to base the initial mesh refinement on
    if length(coefficients) > 0 && typeof(coefficients[1].value[1]) == GenFunction
        mesh_base = cpp_gen_string(coefficients[1].value[1]);
    elseif prob.time_dependent
        mesh_base = init_fun;
    else
        mesh_base = "zero_init";
    end

    solvepart = "";
    if prob.time_dependent
        solvepart = "
        //////////////will be generated/////////////////////////////////////////////
        #include \"Stepper.cpp\"
        ////////////////////////////////////////////////////////////////////////////
        ";
    else
        solvepart = "// This uses the generated RHS code to compute the RHS vector.
        rhsVec.computeVec(rhs,rhs,1.0);

        // Solve the linear system.
        lhsMat.cgSolve("*valvec*",rhs,solve_max_iters,solve_tol,0);";
    end

    # Just write the whole skeleton
    content = """
    #include "TreeNode.h"
    #include "mpi.h"
    #include "genPts_par.h"
    #include "sfcSort.h"
    #include "mesh.h"
    #include "dendro.h"
    #include "dendroIO.h"
    #include "octUtils.h"
    #include "functional"
    #include "fdCoefficient.h"
    #include "stencil.h"
    #include "rkTransport.h"
    #include "refel.h"
    #include "operators.h"
    #include "cg.h"
    #include <iostream>
    #include <sys/time.h>

    #include "linear_skel.h"
    #include "bilinear_skel.h"

    double rtclock();

    int main (int argc, char** argv){

        double t_setup, t_solve;

        MPI_Init(&argc, &argv);
        MPI_Comm comm = MPI_COMM_WORLD;

        int rank, npes;
        MPI_Comm_rank(comm, &rank);
        MPI_Comm_size(comm, &npes);

        if(!rank)
            t_setup = rtclock();

        m_uiMaxDepth = 4; // a default value, but should be set in config.cpp

        //////////////will be generated/////////////////////////////////////////////
        #include "Config.cpp"
        ////////////////////////////////////////////////////////////////////////////

        Point domain_min(0,0,0);
        Point domain_max(1,1,1);

        Point grid_min(0, 0, 0);
        Point grid_max((1u << m_uiMaxDepth), (1u << m_uiMaxDepth), (1u << m_uiMaxDepth));

        double Rg_x=(grid_max.x()-grid_min.x());
        double Rg_y=(grid_max.y()-grid_min.y());
        double Rg_z=(grid_max.z()-grid_min.z());

        double Rd_x=(domain_max.x()-domain_min.x());
        double Rd_y=(domain_max.y()-domain_min.y());
        double Rd_z=(domain_max.z()-domain_min.z());

        const Point d_min=domain_min;
        const Point d_max=domain_max;

        const Point g_min=grid_min;
        const Point g_max=grid_max;

        std::function<double(double)> gridX_to_X = [d_min,g_min,Rd_x,Rg_x](const double x){
            return d_min.x() + (x-g_min.x())*Rd_x/Rg_x;
        };

        std::function<double(double)> gridY_to_Y = [d_min,g_min,Rd_y,Rg_y](const double y){
            return d_min.y() + (y-g_min.y())*Rd_y/Rg_y;
        };

        std::function<double(double)> gridZ_to_Z = [d_min,g_min,Rd_z,Rg_z](const double z){
            return d_min.z() + (z-g_min.z())*Rd_z/Rg_z;
        };

        std::function<void(double,double,double,double*)> zero_init = [](const double x,const double y,const double z,double *var){
            var[0]=0;
        };

        //////////////will be generated/////////////////////////////////////////////
        #include "Genfunction.cpp"
        #include "Problem.cpp"
        /////////////////////////////////////////////////////////////////////////////

        if (!rank) {
             std::cout << YLW << "maxDepth: " << m_uiMaxDepth << NRM << std::endl;
             std::cout << YLW << "wavelet_tol: " << wavelet_tol << NRM << std::endl;
             std::cout << YLW << "partition_tol: " << partition_tol << NRM << std::endl;
             std::cout << YLW << "eleOrder: " << eOrder << NRM << std::endl;
        }

        _InitializeHcurve(m_uiDim);
        RefElement refEl(m_uiDim,eOrder);

        ot::DA* octDA=new ot::DA("""* mesh_base *""",1,comm,eOrder,wavelet_tol,100,partition_tol,ot::FEM_CG);

        // Variable info will also be generated, but for now assume a single scalar variable
        std::vector<double> uSolVec;
        octDA->createVector(uSolVec,false,false,DOF);
        double *uSolVecPtr=&(*(uSolVec.begin()));

        FemshopDendroSkeleton::LHSMat lhsMat(octDA,1);
        lhsMat.setProblemDimensions(domain_min,domain_max);
        lhsMat.setGlobalDofVec(uSolVecPtr);

        FemshopDendroSkeleton::RHSVec rhsVec(octDA,1);
        rhsVec.setProblemDimensions(domain_min,domain_max);
        rhsVec.setGlobalDofVec(uSolVecPtr);

        // This assumes some things
        lhsMat.setBdryFunction("""* cpp_gen_string(prob.bc_func[1,1][1]) *""");
        rhsVec.setBdryFunction("""* cpp_gen_string(prob.bc_func[1,1][1]) *""");

        // Allocate dofs
        """*alloc_dof*"""
        double * rhs=octDA->getVecPointerToDof(uSolVecPtr,VAR::M_UI_RHS, false,false); // linear part

        // Init dofs
        """*set_dof*"""
        octDA->setVectorByFunction(rhs,zero_init,false,false,1); // zeros

        // Solve
        if(!rank){
            t_setup = rtclock() - t_setup;
            t_solve = rtclock();
        }
        """*solvepart*"""

        if(!rank)
            t_solve = rtclock() - t_solve;

        if(!rank){
            std::cout<<"setup time: "<<t_setup<<std::endl;
            std::cout<<"solve time: "<<t_solve<<std::endl;
            std::ofstream logfile;
            logfile.open ("""*"\""*CodeGenerator.genFileName*"_timelog.txt\""*""", std::ios::out | std::ios::app);
            logfile<<"----------------------------------"<<std::endl;
            logfile<<"num. procs: "<<npes<<std::endl;
            logfile<<"setup time: "<<t_setup<<std::endl;
            logfile<<"solve time: "<<t_solve<<std::endl;
            logfile<<"----------------------------------"<<std::endl;
            logfile.close();
        }

        // Output
        //////////////will be generated/////////////////////////////////////////////
        #include "Output.cpp"
        ////////////////////////////////////////////////////////////////////////////

        octDA->destroyVector(uSolVec);

        if(!rank)
            std::cout<<" End of computation. "<<std::endl;

        delete octDA;

        MPI_Finalize();
        return 0;
    }

    double rtclock(){
        struct timezone Tzp;
        struct timeval Tp;
        int stat;
        stat = gettimeofday(&Tp, &Tzp);
        if(stat != 0){
            printf("Error returned from gettimeofday\n");
        }
        return (Tp.tv_sec + Tp.tv_usec * 1.0e-6);
    }
    """
    print(file, content);
end

# This is a class containing various utility functions
function dendro_utils_file()
    utilsfile = add_generated_file("Utils.cpp", dir="src");

    content = "
// Utilities

"
    println(utilsfile, content);
end

function dendro_config_file(dparams=(5, 1, 0.3, 0.000001, 100))
    file = add_generated_file("Config.cpp", dir="src");
    # dparams has (maxdepth, wavelet_tol, partition_tol, solve_tol, solve_max_iters)
    println(file, "m_uiMaxDepth = "*string(dparams[1])*";           // mesh refinement depth");
    println(file, "double wavelet_tol = "*string(dparams[2])*";     // tolerance for approximating functions(f) determines mesh fineness");
    println(file, "double partition_tol = "*string(dparams[3])*"; // load balancing parameter");
    println(file, "double solve_tol = "*string(dparams[4])*";            // tol used by cgsolve for stopping iterations");
    println(file, "unsigned int solve_max_iters = "*string(dparams[5])*"; // used by cgsolve");
    # From the config struct
    println(file, "unsigned int eOrder  = "*cpp_gen_string(config.basis_order_min)*";");

    println(file, "int config_dimension = "*cpp_gen_string(config.dimension)*";");

    println(file, "const char* config_solver = "*cpp_gen_string(config.solver_type)*";");
    println(file, "const char* config_trial_function = "*cpp_gen_string(config.trial_function)*";");
    println(file, "const char* config_test_function = "*cpp_gen_string(config.test_function)*";");
    println(file, "const char* config_elemental_nodes = "*cpp_gen_string(config.elemental_nodes)*";");
    println(file, "const char* config_quadrature= "*cpp_gen_string(config.quadrature)*";");
end

function dendro_prob_file()
    file = add_generated_file("Problem.cpp", dir="src");
    # variable and coefficient DOFs
    dofs = 0;
    varpart = "";
    varnames = "";
    for i=1:length(variables)
        for j=1:length(variables[i].symvar)
            varpart = varpart*"M_UI"*string(variables[i].symvar[j]);
            varnames = varnames*"\"m_ui"*string(variables[i].symvar[j])*"\", ";
            if i==1 && j==1
                varpart = varpart*"=0";
            end
            varpart = varpart*", ";
            if prob.time_dependent
                # Also add next versions for the next time step
                varpart = varpart*"M_UI"*string(variables[i].symvar[j])*"_NEXT, ";
                varnames = varnames*"\"m_ui"*string(variables[i].symvar[j])*"_next\", ";
                dofs = dofs + 1;
            end
            dofs = dofs+1;
        end
    end
    coefpart = "";
    coefnames = "";
    for i=1:length(coefficients)
        for j=1:length(coefficients[i].symvar)
            coefpart = coefpart*"M_UI"*string(coefficients[i].symvar[j])*", ";
            coefnames = coefnames*"\"m_ui"*string(coefficients[i].symvar[j])*"\", ";
            dofs = dofs+1;
        end
    end
    dofs += 1; # for linear part
    println(file, "enum VAR{"*varpart*coefpart*"M_UI_RHS}; // variables, coefficients, linear part");
    println(file, "const char * VAR_NAMES[]={"*varnames*coefnames*"\"m_uiRHS\"};");
    println(file, "const unsigned int DOF="*string(dofs)*";");

    # From the prob struct
    println(file, "const char* prob_bc_type[] = "*cpp_gen_string(prob.bc_type)*";");
    println(file, "int prob_bid[] = "*cpp_gen_string(prob.bid)*";");

    # for time dependent problems
    if prob.time_dependent
        println(file, "double tBegin = 0;");
        println(file, "double tEnd = "*string(prob.end_time)*";");
    end
end

# Functions for boundary conditions, coeficients, etc.
function dendro_genfunction_file()
    file = add_generated_file("Genfunction.cpp", dir="src");

    indent = "";
    args = ["x"; "y"; "z"; "var"];
    argtypes = ["double"; "double"; "double"; "double*"];
    rettype = "void";

    for i = 1:length(genfunctions)
        if true #isfunction(genfunctions[i])
            str = dendro_genfunction_to_string(genfunctions[i]);
            content = "var[0] = "*str*";";
            #lines = cpp_function_def(indent, genfunctions[i].name, args, argtypes, rettype, content);
            lines = ["std::function<void(double,double,double,double*)> "*genfunctions[i].name*" = [gridX_to_X, gridY_to_Y, gridZ_to_Z](const double x,const double y,const double z,double *var){", content, "};"];
            for j=1:length(lines)
                println(file, lines[j]);
            end
        end
    end
end

# Time stepper (Only added for time dependent problems)
function dendro_stepper_file()
    file = add_generated_file("Stepper.cpp", dir="src");
    varvec = string(variables[1].symvar[1]);
    varvec_next = string(variables[1].symvar[1])*"_next";
    content = "
double currentT = tBegin;
// Time stepper parameters
int Nsteps = "*string(50)*";
double dt = tEnd / Nsteps;
rhsVec.setDt(dt);
lhsMat.setDt(dt);

for(int ti=0; ti<Nsteps; ti++){
    int activeRank=octDA->getRankActive();
    if(!activeRank)
        std::cout<<\"step \"<<ti<<\" (t=\"<<currentT<<\")\"<<std::endl;

    rhsVec.computeVec(rhs,rhs,1.0);
    lhsMat.cgSolve("*varvec_next*",rhs,solve_max_iters,solve_tol,0);

    // Exchange ghosts and copy u_next to u
    double* _tmpvec=NULL;

    octDA->nodalVecToGhostedNodal(uSolVecPtr,_tmpvec,false,DOF);

    octDA->readFromGhostBegin(_tmpvec, DOF);
    octDA->readFromGhostEnd(_tmpvec, DOF);

    octDA->ghostedNodalToNodalVec(_tmpvec, uSolVecPtr,false,DOF);

    octDA->destroyVector(_tmpvec);

    octDA->copyVectors("*varvec*", "*varvec_next*", false, false, 1);

    currentT += dt;
}";

    print(file, content);
end

# This is the matrix assembly code.
# It requires code that was generated for this target.
function dendro_bilinear_file(code)
    file = add_generated_file("Bilinear.cpp", dir="src");
    skeleton_file = add_generated_file("bilinear_skel.cpp", dir="src");
    skeleton_headerfile = add_generated_file("bilinear_skel.h", dir="include");

    print(file, code);

    varpart = "";
    for i=1:length(variables)
        for j=1:length(variables[i].symvar)
            varpart = varpart*"M_UI"*string(variables[i].symvar[j]);
            if i==1 && j==1
                varpart = varpart*"=0";
            end
            varpart = varpart*", ";
            if prob.time_dependent
                # Also add next versions for the next time step
                varpart = varpart*"M_UI"*string(variables[i].symvar[j])*"_NEXT, ";
            end
        end
    end
    coefpart = "";
    for i=1:length(coefficients)
        for j=1:length(coefficients[i].symvar)
            coefpart = coefpart*"M_UI"*string(coefficients[i].symvar[j])*", ";
        end
    end

    content = """
    #include "bilinear_skel.h"

    FemshopDendroSkeleton::LHSMat::LHSMat(ot::DA* da,unsigned int dof) : feMatrix(da,dof)
    {
        const unsigned int nPe=m_uiOctDA->getNumNodesPerElement();
        imV1=new double[nPe];
        imV2=new double[nPe];

        Qx=new double[nPe];
        Qy=new double[nPe];
        Qz=new double[nPe];
    }

    FemshopDendroSkeleton::LHSMat::~LHSMat()
    {
        delete [] imV1;
        delete [] imV2;

        delete [] Qx;
        delete [] Qy;
        delete [] Qz;

        imV1=NULL;
        imV2=NULL;

        Qx=NULL;
        Qy=NULL;
        Qz=NULL;
    }

    void FemshopDendroSkeleton::LHSMat::elementalMatVec(const VECType* in,VECType* out, double*coords,double scale)
    {
        const RefElement* refEl=m_uiOctDA->getReferenceElement();

        const double * Q1d=refEl->getQ1d();
        const double * QT1d=refEl->getQT1d();
        const double * Dg=refEl->getDg1d();
        const double * DgT=refEl->getDgT1d();
        const double * W1d=refEl->getWgq();

        const unsigned int eleOrder=refEl->getOrder();
        const unsigned int nPe=(eleOrder+1)*(eleOrder+1)*(eleOrder+1);
        const unsigned int nrp=eleOrder+1;

        Point eleMin(coords[0*m_uiDim+0],coords[0*m_uiDim+1],coords[0*m_uiDim+2]);
        Point eleMax(coords[(nPe-1)*m_uiDim+0],coords[(nPe-1)*m_uiDim+1],coords[(nPe-1)*m_uiDim+2]);

        const double refElSz=refEl->getElementSz();

        const double szX=gridX_to_X(eleMax.x())-gridX_to_X(eleMin.x());
        const double szY=gridY_to_Y(eleMax.y())-gridY_to_Y(eleMin.y());
        const double szZ=gridZ_to_Z(eleMax.z())-gridZ_to_Z(eleMin.z());

        const double Jx = 1.0/(refElSz/(double (szX)));
        const double Jy = 1.0/(refElSz/(double (szY)));
        const double Jz = 1.0/(refElSz/(double (szZ)));

        //////////////will be generated/////////////////////////////////////////////
        #include "Bilinear.cpp"
        ////////////////////////////////////////////////////////////////////////////
    }

    void FemshopDendroSkeleton::LHSMat::setBdryFunction(std::function<void(double,double,double,double*)> bdry){
        bdry_function = bdry;
    }

    void FemshopDendroSkeleton::LHSMat::setGlobalDofVec(double* gdv){
        grandDofVecPtr = gdv;
    }

    bool FemshopDendroSkeleton::LHSMat::preMatVec(const VECType* in,VECType* out,double scale)
    {
        // apply boundary conditions.
        std::vector<unsigned int> bdyIndex;
        std::vector<double> bdyCoords;

        m_uiOctDA->getOctreeBoundaryNodeIndices(bdyIndex,bdyCoords);

        for(unsigned int i=0;i<bdyIndex.size();i++){

            out[bdyIndex[i]] = in[bdyIndex[i]]; // Dirichlet BC
        }

        return true;
    }

    bool FemshopDendroSkeleton::LHSMat::postMatVec(const VECType* in,VECType* out,double scale) {

        // apply boundary conditions.
        std::vector<unsigned int> bdyIndex;
        std::vector<double> bdyCoords;

        m_uiOctDA->getOctreeBoundaryNodeIndices(bdyIndex,bdyCoords);

        for(unsigned int i=0;i<bdyIndex.size();i++){

            out[bdyIndex[i]] = in[bdyIndex[i]]; // Dirichlet BC
        }

        return true;
    }

    double FemshopDendroSkeleton::LHSMat::gridX_to_X(double x)
    {
        double Rg_x=((1u<<m_uiMaxDepth)-0);
        return (((x)/(Rg_x))*((m_uiPtMax.x()-m_uiPtMin.x()))+m_uiPtMin.x());
    }

    double FemshopDendroSkeleton::LHSMat::gridY_to_Y(double y)
    {
        double Rg_y=((1u<<m_uiMaxDepth)-0);
        return (((y)/(Rg_y))*((m_uiPtMax.y()-m_uiPtMin.y()))+m_uiPtMin.y());
    }

    double FemshopDendroSkeleton::LHSMat::gridZ_to_Z(double z)
    {
        double Rg_z=((1u<<m_uiMaxDepth)-0);
        return (((z)/(Rg_z))*((m_uiPtMax.z()-m_uiPtMin.z()))+m_uiPtMin.z());
    }

    void FemshopDendroSkeleton::LHSMat::setDt(double newdt)
    {
        dt = newdt;
    }

    int FemshopDendroSkeleton::LHSMat::cgSolve(double * x ,double * b,int max_iter, double& tol,unsigned int var)
    {
        double resid,alpha,beta,rho,rho_1;
        int status=1; // 0 indicates it has solved the system within the specified max_iter, 1 otherwise.

        const unsigned int local_dof=m_uiOctDA->getLocalNodalSz();

        MPI_Comm globalComm=m_uiOctDA->getGlobalComm();

        if(m_uiOctDA->isActive())
        {

            int activeRank=m_uiOctDA->getRankActive();
            int activeNpes=m_uiOctDA->getNpesActive();

            MPI_Comm activeComm=m_uiOctDA->getCommActive();

            double* p;
            double* z;
            double* q;
            double* Ax;
            double* Ap;
            double* r0;
            double* r1;

            m_uiOctDA->createVector(p);
            m_uiOctDA->createVector(z);
            m_uiOctDA->createVector(q);

            m_uiOctDA->createVector(Ax);
            m_uiOctDA->createVector(Ap);
            m_uiOctDA->createVector(r0);
            m_uiOctDA->createVector(r1);

            double normb = normLInfty(b,local_dof,activeComm);
            par::Mpi_Bcast(&normb,1,0,activeComm);

            if(!activeRank)
                std::cout<<"normb = "<<normb<<std::endl;

            matVec(x,Ax);

            /*char fPrefix[256];
            sprintf(fPrefix,"%s_%d","cg",0);
            const char * varNames[]={"U"};
            const double * var[]={Ax};
            io::vtk::mesh2vtuFine(mesh,fPrefix,0,NULL,NULL,1,varNames,var);
            */
            for(unsigned int i=0;i<local_dof;i++)
            {
                r0[i]=b[i]-Ax[i];
                p[i]=r0[i];
            }

            if (normb == 0.0)
                normb = 1;

            double normr=normLInfty(r0,local_dof,activeComm);
            par::Mpi_Bcast(&normr,1,0,activeComm);
            if(!activeRank) std::cout<<"initial residual : "<<(normr/normb)<<std::endl;

            if ((resid = normr / normb) <= tol) {
                tol = resid;
                max_iter = 0;

                m_uiOctDA->destroyVector(p);
                m_uiOctDA->destroyVector(z);
                m_uiOctDA->destroyVector(q);

                m_uiOctDA->destroyVector(Ax);
                m_uiOctDA->destroyVector(Ap);
                m_uiOctDA->destroyVector(r0);
                m_uiOctDA->destroyVector(r1);

                status=0;
            }

            if(status!=0)
            {
                for(unsigned int i=1;i<=max_iter;i++)
                {
                    matVec(p,Ap);

                    alpha=(dot(r0,r0,local_dof,activeComm)/dot(p,Ap,local_dof,activeComm));
                    par::Mpi_Bcast(&alpha,1,0,activeComm);

                    //if(!activeRank) std::cout<<"rank: " <<activeRank<<" alpha: "<<alpha<<std::endl;
                    for(unsigned int e=0;e<local_dof;e++)
                    {
                        x[e]+=alpha*p[e];
                        r1[e]=r0[e]-alpha*Ap[e];
                    }

                    normr=normLInfty(r1,local_dof,activeComm);
                    par::Mpi_Bcast(&normr,1,0,activeComm);

                    if((!activeRank) && (i%10==0)) std::cout<<" iteration : "<<i<<" residual : "<<resid<<std::endl;

                    if ((resid = normr / normb) <= tol) {

                        if((!activeRank)) std::cout<<" iteration : "<<i<<" residual : "<<resid<<std::endl;
                        tol = resid;
                        m_uiOctDA->destroyVector(p);
                        m_uiOctDA->destroyVector(z);
                        m_uiOctDA->destroyVector(q);

                        m_uiOctDA->destroyVector(Ax);
                        m_uiOctDA->destroyVector(Ap);
                        m_uiOctDA->destroyVector(r0);
                        m_uiOctDA->destroyVector(r1);

                        status=0;
                        break;
                    }

                    beta=(dot(r1,r1,local_dof,activeComm)/dot(r0,r0,local_dof,activeComm));
                    par::Mpi_Bcast(&beta,1,0,activeComm);

                    //if(!activeRank) std::cout<<"<r_1,r_1> : "<<dot(r1+nodeLocalBegin,r1+nodeLocalBegin,local_dof,activeComm)<<" <r_0,r_0>: "<<dot(r0+nodeLocalBegin,r0+nodeLocalBegin,local_dof,activeComm)<<" beta "<<beta<<std::endl;

                    for(unsigned int e=0;e<local_dof;e++)
                    {
                        p[e]=r1[e]+beta*p[e];
                        r0[e]=r1[e];
                    }
                }

                if(status!=0)
                {
                    tol = resid;
                    m_uiOctDA->destroyVector(p);
                    m_uiOctDA->destroyVector(z);
                    m_uiOctDA->destroyVector(q);

                    m_uiOctDA->destroyVector(Ax);
                    m_uiOctDA->destroyVector(Ap);
                    m_uiOctDA->destroyVector(r0);
                    m_uiOctDA->destroyVector(r1);
                    status=1;
                }
            }
        }

        // bcast act as a barrier for active and inactive meshes.
        par::Mpi_Bcast(&tol,1,0,globalComm);
        return status;
    }
    """
    print(skeleton_file, content);

    content = """
    #ifndef DENDRO_5_0_BILINEAR_SKEL_H
    #define DENDRO_5_0_BILINEAR_SKEL_H

    #include "oda.h"
    #include "feMatrix.h"

    namespace FemshopDendroSkeleton
    {
        class LHSMat : public feMatrix<LHSMat>{

        private:
            // some additional work space variables to perform elemental MatVec
            double* imV1;
            double* imV2;
            double* Qx;
            double* Qy;
            double* Qz;
            // function for boundary
            std::function<void(double,double,double,double*)> bdry_function;
            // coefficient vectors
            double* grandDofVecPtr;
            """*"enum VAR{"*varpart*coefpart*"M_UI_RHS};"*"""
            // time step
            double dt;

        public:
            /**@brief: constructor*/
            LHSMat(ot::DA* da,unsigned int dof=1);

            /**@brief default destructor*/
            ~LHSMat();

            /**@biref elemental matvec*/
            virtual void elementalMatVec(const VECType* in,VECType* out, double*coords=NULL,double scale=1.0);

            /**@brief set boundary function*/
            void setBdryFunction(std::function<void(double,double,double,double*)> bdry);

            /**@brief set pointer to global dof vector*/
                void setGlobalDofVec(double* gdv);

            /**@brief things need to be performed before matvec (i.e. coords transform)*/
            bool preMatVec(const VECType* in,VECType* out,double scale=1.0);

            /**@brief things need to be performed after matvec (i.e. coords transform)*/
            bool postMatVec(const VECType* in,VECType* out,double scale=1.0);

            /**@brief octree grid x to domin x*/
            double gridX_to_X(double x);
            /**@brief octree grid y to domin y*/
            double gridY_to_Y(double y);
            /**@brief octree grid z to domin z*/
            double gridZ_to_Z(double z);

            void setDt(double dt);

            int cgSolve(double * x ,double * b,int max_iter, double& tol,unsigned int var=0);
        };
    }

    #endif //DENDRO_5_0_BILINEAR_SKEL_H
    """
    print(skeleton_headerfile, content);
end

# This is the vector assembly code.
# It requires code that was generated for this target.
function dendro_linear_file(code)
    file = add_generated_file("Linear.cpp", dir="src");
    skeleton_file = add_generated_file("linear_skel.cpp", dir="src");
    skeleton_headerfile = add_generated_file("linear_skel.h", dir="include");

    print(file, code);

    varpart = "";
    for i=1:length(variables)
        for j=1:length(variables[i].symvar)
            varpart = varpart*"M_UI"*string(variables[i].symvar[j]);
            if i==1 && j==1
                varpart = varpart*"=0";
            end
            varpart = varpart*", ";
            if prob.time_dependent
                # Also add next versions for the next time step
                varpart = varpart*"M_UI"*string(variables[i].symvar[j])*"_NEXT, ";
            end
        end
    end
    coefpart = "";
    for i=1:length(coefficients)
        for j=1:length(coefficients[i].symvar)
            coefpart = coefpart*"M_UI"*string(coefficients[i].symvar[j])*", ";
        end
    end

    content = """
    #include "linear_skel.h"

    FemshopDendroSkeleton::RHSVec::RHSVec(ot::DA* da,unsigned int dof) : feVector(da,dof)
    {
        const unsigned int nPe=m_uiOctDA->getNumNodesPerElement();
        imV1=new double[nPe];
        imV2=new double[nPe];
    }

    FemshopDendroSkeleton::RHSVec::~RHSVec()
    {
        delete [] imV1;
        delete [] imV2;

        imV1=NULL;
        imV2=NULL;
    }

    void FemshopDendroSkeleton::RHSVec::elementalComputVec(const VECType* in,VECType* out, double*coords,double scale)
    {
        const RefElement* refEl=m_uiOctDA->getReferenceElement();
        const double * Q1d=refEl->getQ1d();
        const double * QT1d=refEl->getQT1d();
        const double * Dg=refEl->getDg1d();
        const double * W1d=refEl->getWgq();

        const unsigned int eleOrder=refEl->getOrder();
        const unsigned int nPe=(eleOrder+1)*(eleOrder+1)*(eleOrder+1);
        const unsigned int nrp=eleOrder+1;

        Point eleMin(coords[0*m_uiDim+0],coords[0*m_uiDim+1],coords[0*m_uiDim+2]);
        Point eleMax(coords[(nPe-1)*m_uiDim+0],coords[(nPe-1)*m_uiDim+1],coords[(nPe-1)*m_uiDim+2]);

        const double refElSz=refEl->getElementSz();

        const double szX=gridX_to_X(eleMax.x())-gridX_to_X(eleMin.x());
        const double szY=gridY_to_Y(eleMax.y())-gridY_to_Y(eleMin.y());
        const double szZ=gridZ_to_Z(eleMax.z())-gridZ_to_Z(eleMin.z());

        const double Jx = 1.0/(refElSz/(double (szX)));
        const double Jy = 1.0/(refElSz/(double (szY)));
        const double Jz = 1.0/(refElSz/(double (szZ)));

        double* Qx = {0};
        double* Qy = {0};
        double* Qz = {0};

        //////////////will be generated/////////////////////////////////////////////
        #include "Linear.cpp"
        ////////////////////////////////////////////////////////////////////////////
    }

    void FemshopDendroSkeleton::RHSVec::setBdryFunction(std::function<void(double,double,double,double*)> bdry){
        bdry_function = bdry;
    }

    void FemshopDendroSkeleton::RHSVec::setGlobalDofVec(double* gdv){
        grandDofVecPtr = gdv;
    }

    bool FemshopDendroSkeleton::RHSVec::preComputeVec(const VECType* in,VECType* out, double scale)
    {

        // apply boundary conditions.
        std::vector<unsigned int> bdyIndex;
        std::vector<double> bdyCoords;

        m_uiOctDA->getOctreeBoundaryNodeIndices(bdyIndex,bdyCoords);

        double x,y,z,val;
        for(unsigned int i=0;i<bdyIndex.size();i++){
            x = bdyCoords[i*3+0];
            y = bdyCoords[i*3+1];
            z = bdyCoords[i*3+2];
            bdry_function(x,y,z,&val);

            out[bdyIndex[i]] = val;
        }

        return true;
    }

    bool FemshopDendroSkeleton::RHSVec::postComputeVec(const VECType* in,VECType* out, double scale) {

        // apply boundary conditions.
        std::vector<unsigned int> bdyIndex;
        std::vector<double> bdyCoords;

        m_uiOctDA->getOctreeBoundaryNodeIndices(bdyIndex,bdyCoords);

        double x,y,z,val;
        for(unsigned int i=0;i<bdyIndex.size();i++){
            x = bdyCoords[i*3+0];
            y = bdyCoords[i*3+1];
            z = bdyCoords[i*3+2];
            bdry_function(x,y,z,&val);

            out[bdyIndex[i]] = val;
        }

        return true;
    }

    double FemshopDendroSkeleton::RHSVec::gridX_to_X(double x)
    {
        double Rg_x=((1u<<m_uiMaxDepth)-0);
        return (((x)/(Rg_x))*((m_uiPtMax.x()-m_uiPtMin.x()))+m_uiPtMin.x());
    }

    double FemshopDendroSkeleton::RHSVec::gridY_to_Y(double y)
    {
        double Rg_y=((1u<<m_uiMaxDepth)-0);
        return (((y)/(Rg_y))*((m_uiPtMax.y()-m_uiPtMin.y()))+m_uiPtMin.y());
    }

    double FemshopDendroSkeleton::RHSVec::gridZ_to_Z(double z)
    {
        double Rg_z=((1u<<m_uiMaxDepth)-0);
        return (((z)/(Rg_z))*((m_uiPtMax.z()-m_uiPtMin.z()))+m_uiPtMin.z());
    }

    void FemshopDendroSkeleton::RHSVec::setDt(double newdt)
    {
        dt = newdt;
    }

    void FemshopDendroSkeleton::RHSVec::getCorrectElementalValues(const VECType *in, VECType* out) {
        VECType* _in=NULL;

        m_uiOctDA->nodalVecToGhostedNodal(in,_in,false,m_uiDof);

        m_uiOctDA->readFromGhostBegin(_in, m_uiDof);

        m_uiOctDA->getElementNodalValues(_in, out, m_uiOctDA->curr(), m_uiDof);

        m_uiOctDA->readFromGhostEnd(_in, m_uiDof);

        m_uiOctDA->destroyVector(_in);

        return;
    }
    """
    print(skeleton_file, content);

    content = """
    #ifndef DENDRO_5_0_LINEAR_SKEL_H
    #define DENDRO_5_0_LINEAR_SKEL_H

    #include "oda.h"
    #include "feVector.h"

    namespace FemshopDendroSkeleton
    {
        class RHSVec : public feVector<RHSVec>{

        private:

            double * imV1;
            double * imV2;
            // function for boundary
            std::function<void(double,double,double,double*)> bdry_function;
            // coefficient vectors
            double* grandDofVecPtr;
            """*"enum VAR{"*varpart*coefpart*"M_UI_RHS};"*"""
            // time step
            double dt;

        public:
            RHSVec(ot::DA* da,unsigned int dof=1);
            ~RHSVec();

            /**@biref elemental compute vec for rhs*/
            virtual void elementalComputVec(const VECType* in,VECType* out, double*coords=NULL,double scale=1.0);

            /**@brief set boundary function*/
            void setBdryFunction(std::function<void(double,double,double,double*)> bdry);

            /**@brief set pointer to global dof vector*/
                void setGlobalDofVec(double* gdv);

            bool preComputeVec(const VECType* in,VECType* out, double scale=1.0);

            bool postComputeVec(const VECType* in,VECType* out, double scale=1.0);

            /**@brief octree grid x to domin x*/
            double gridX_to_X(double x);
            /**@brief octree grid y to domin y*/
            double gridY_to_Y(double y);
            /**@brief octree grid z to domin z*/
            double gridZ_to_Z(double z);

            void setDt(double dt);

            void getCorrectElementalValues(const VECType *in, VECType* out);
        };
    }

    #endif //DENDRO_5_0_LINEAR_SKEL_H
    """
    print(skeleton_headerfile, content);
end

# Right now this just tries to plot
function dendro_output_file()
    file = add_generated_file("Output.cpp", dir="src");

    if config.output_format == VTK
        println(file, "octDA->vecTopvtu(uSolVecPtr,\""*CodeGenerator.genFileName*"\",(char**)VAR_NAMES,false,false,DOF);");
    end
end

function dendro_cmake_file()
    file = add_generated_file("CMakeLists.txt", dir="", make_header_text=false);
    content = """
cmake_minimum_required(VERSION 2.8)
project("""*CodeGenerator.genFileName*""")

set("""*CodeGenerator.genFileName*"""_INC include/linear_skel.h
            include/bilinear_skel.h
        )

set("""*CodeGenerator.genFileName*"""_SRC src/linear_skel.cpp
            src/bilinear_skel.cpp
        )

set(SOURCE_FILES src/"""*CodeGenerator.genFileName*""".cpp \${"""*CodeGenerator.genFileName*"""_INC} \${"""*CodeGenerator.genFileName*"""_SRC})
add_executable("""*CodeGenerator.genFileName*""" \${SOURCE_FILES})
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/include)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/include/test)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/examples/include)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/FEM/include)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/ODE/include)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/LinAlg/include)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/IO/vtk/include)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CMAKE_SOURCE_DIR}/IO/zlib/inc)
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${MPI_INCLUDE_PATH})
target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${GSL_INCLUDE_DIRS})
if(WITH_CUDA)
    target_include_directories("""*CodeGenerator.genFileName*""" PRIVATE \${CUDA_INCLUDE_DIRS})
endif()
target_link_libraries("""*CodeGenerator.genFileName*""" dendro5 \${LAPACK_LIBRARIES} \${MPI_LIBRARIES} m)

""";
    print(file, content);
end

function dendro_readme_file()
    file = add_generated_file("README.txt", dir="", make_header_text=false);
    content = """
Basic instructions for compiling this generated code with dendro.
    1. Place this generated directory in the Dendro directory.

    2. Append the following line to the Dendro CMakeLists.txt file:
        add_subdirectory("""*uppercasefirst(CodeGenerator.genFileName)*""")

    3. Remake Dendro as usual. For example, to build to the directory "build",
       enter the Dendro directory and do the following.
        a. If it doesn't exist, make the build directory: "mkdir build"
        b. "cd build"
        c. "ccmake .."
        d. "make"
        e. "cd """*uppercasefirst(CodeGenerator.genFileName)*""" "

    4. Run the executable with "./"""*CodeGenerator.genFileName*""" "

    *Note: The directory will have an upper case first letter.
           The executable will match the name passed to generateFor().
"""
    print(file, content);
end

###########################################################################################################
# Symbolic to code layer generation

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

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

# Allocate, compute, or fetch all needed values
function dendrotarget_prepare_needed_values(entities, var, lorr, vors)
    to_delete = []; # arrays allocated with new that need deletion

    code = "";
    for i=1:length(entities)
        cname = CodeGenerator.make_coef_name(entities[i]);
        if CodeGenerator.is_test_function(entities[i])
            # Assign it a transpose quadrature matrix
            if length(entities[i].derivs) > 0
                xyzchar = ["x","y","z"];
                for di=1:length(entities[i].derivs)
                    code *= "// " * cname * " = TRQ"*string(entities[i].derivs[di])*"; // d/d"*xyzchar[entities[i].derivs[di]]*" of test function\n";
                end
            else
                code *= "// " * cname * " = refel.Q'; // test function.\n";
            end
        elseif CodeGenerator.is_unknown_var(entities[i], var) && lorr == LHS
            if length(entities[i].derivs) > 0
                xyzchar = ["x","y","z"];
                for di=1:length(entities[i].derivs)
                    code *= "// " * cname * " = RQ"*string(entities[i].derivs[di])*"; // d/d"*xyzchar[entities[i].derivs[di]]*" of trial function\n";
                end
            else
                code *= "// " * cname * " = refel.Q; // trial function.\n";
            end
        else
            # Is coefficient(number or function) or variable(array)?
            (ctype, cval) = CodeGenerator.get_coef_val(entities[i]);
            if ctype == -1
                # It was a special symbol like dt
            elseif ctype == 0
                # It was a number, do nothing?
            elseif ctype == 1 # a constant wrapped in a coefficient
                # This generates something like: coef_k_i = 4;
                if length(entities[i].derivs) > 0
                    code *= "double " * cname * " = 0.0; // NOTE: derivative applied to constant coefficient = 0\n";
                else
                    code *= "double " * cname * " = " * string(cval) * ";\n";
                end

            elseif ctype == 2 # a coefficient function
                if vors == "volume"
                    push!(to_delete, cname);
                    short_name = split(cname, "coef_")[end];
                    code *= "double* "*cname*" = new double[nPe];\n";
                    # WRONG-> code *= "m_uiOctDA->getElementNodalValues(m_uiOctDA->getVecPointerToDof(grandDofVecPtr, VAR::M_UI"*short_name*", false,false), "*cname*", m_uiOctDA->curr(), m_uiDof);\n";
                    code *= "FemshopDendroSkeleton::RHSVec::getCorrectElementalValues(m_uiOctDA->getVecPointerToDof(grandDofVecPtr, VAR::M_UI"*short_name*", false,false), "*cname*");\n";
                    # Apply any needed derivative operators. Interpolate at quadrature points.
                    if length(entities[i].derivs) > 0
                        println("coefficient derivatives not ready: "*string(entities[i]))
                        # xyzchar = ["x","y","z"];
                        # for di=1:length(entities[i].derivs)
                        #     code *= cname * " = RQ"*string(entities[i].derivs[di])*" * " * cname *
                        #             "; // Apply d/d"*xyzchar[entities[i].derivs[di]]*" and interpolate at quadrature points.\n";
                        # end
                    else
                        if lorr==LHS
                            code *= "//TODO: "*cname * " = refel.Q * " * cname * "; // Interpolate at quadrature points.\n";
                        end
                    end
                else
                    #TODO surface
                end

            elseif ctype == 3 # a known variable value
                # This should only occur for time dependent problems
                # Use the values from the previous time step
                if vors == "volume"
                    push!(to_delete, cname);
                    short_name = split(cname, "coef_")[end];
                    code *= "double* "*cname*" = new double[nPe];\n";
                    code *= "m_uiOctDA->getElementNodalValues(m_uiOctDA->getVecPointerToDof(grandDofVecPtr, VAR::M_UI"*short_name*", false,false), "*cname*", m_uiOctDA->curr(), m_uiDof);\n";
                else
                    #TODO surface
                end

            end
        end # if coefficient
    end # entity loop

    return (code, to_delete);
end

function dendrotarget_make_elemental_computation(terms, to_delete, var, lorr, vors)
    # Here is where I make some assumption about the form of the expression.
    # Since it was expanded by the parser it should look like a series of terms: t1 + t2 + t3...
    # Where each term is multiplied by one test function component, and if LHS, involves one unknown component.
    # The submatrix modified by a term is determined by these, so go through the terms and divide them
    # into their submatrix expressions.
    # Each term will look something like
    # LHS: test_part * diagm(weight_part .* coef_part) * trial_part
    # RHS: test_part * (weight_part .* coef_part)

    # find some useful numbers
    dofsper = 0;
    offset_ind = [0];
    if typeof(var) <:Array
        offset_ind = zeros(Int, varcount);
        dofsper = length(var[1].symvar);
        for i=2:length(var)
            offset_ind[i] = dofsper;
            dofsper = dofsper + length(var[i].symvar);
        end
    else
        dofsper = length(var.symvar);
    end

    # This will hold the full code string
    code = "";
    # If temp storage was needed for multiple terms, allocate
    temp_alloc = "";
    dealloc = "";
    # The pieces of the code
    preloop_code = "";
    loop_code = "";
    postloop_code = "";

    # Separate the factors of each term into test, trial, coef and form the calculation
    if dofsper > 1
        printerr("dendro not ready for multi dofs per node. ");
        # Submatrices or subvectors for each component
        if lorr == LHS
            submatrices = Array{String, 2}(undef, dofsper, dofsper);
        else # RHS
            submatrices = Array{String, 1}(undef, dofsper);
        end
        for smi=1:length(submatrices)
            submatrices[smi] = "";
        end

        if typeof(var) <: Array
            term_number = 0;
            for vi=1:length(var) # variables
                # Process the terms for this variable
                for ci=1:length(terms[vi]) # components
                    for i=1:length(terms[vi][ci])
                        term_number += 1;
                        (trial_code, coef_code, test_code, test_ind, trial_ind) = dendrotarget_generate_term_calculation(terms[vi][ci][i], term_number, var, lorr);
                        temp_alloc *= "double* out_"*string(term_number)*" = new double[nPe];\n";
                        push!(to_delete, "out_"*string(term_number));

                        # println(terms)
                        # println(terms[vi])
                        # println(terms[vi][ci])
                        # println(terms[vi][ci][i])
                        # println(term_result * " : "*string(test_ind)*", "*string(trial_ind))

                        # Find the appropriate submatrix for this term
                        submati = offset_ind[vi] + test_ind;
                        submatj = trial_ind;
                        if lorr == LHS
                            submat_ind = submati + dofsper * (submatj-1);
                        else
                            submat_ind = submati;
                        end

                        preloop_code *= trial_code * "\n";
                        loop_code *= coef_code * "\n";
                        postloop_code *= test_code * "\n";
                    end
                end

            end # vi

        else # only one variable
            # Process the terms for this variable

            for ci=1:length(terms) # components
                for i=1:length(terms[ci])
                    term_number += 1;
                    (trial_code, coef_code, test_code, test_ind, trial_ind) = dendrotarget_generate_term_calculation(terms[ci][i], term_number, var, lorr);
                    temp_alloc *= "double* out_"*string(term_number)*" = new double[nPe];\n";
                    push!(to_delete, "out_"*string(term_number));

                    # Find the appropriate submatrix for this term
                    if lorr == LHS
                        submat_ind = test_ind + dofsper * (trial_ind-1);
                    else
                        submat_ind = test_ind;
                    end

                    preloop_code *= trial_code * "\n";
                    loop_code *= coef_code * "\n";
                    postloop_code *= test_code * "\n";
                end
            end

        end

    else # one dof
        terms = terms[1];
        #process each term
        for i=1:length(terms)
            (trial_code, coef_code, test_code, test_ind, trial_ind) = dendrotarget_generate_term_calculation(terms[i], i, var, lorr);
            temp_alloc *= "double* out_"*string(i)*" = new double[nPe];\n";
            push!(to_delete, "out_"*string(i));

            preloop_code *= string(trial_code) * "\n";
            loop_code *= string(coef_code) * "\n";
            postloop_code *= string(test_code) * "\n";
        end
    end

    for i=1:length(to_delete)
        dealloc *= "delete [] "*to_delete[i]*";\n";
    end

    # Put the pieces together
    code = "// Allocate temporary storage for each term.\n"
    code *= temp_alloc;
    if lorr == RHS && length(loop_code) > 0
        code *= "double* rhscoefvec = new double[nPe];\n";
        dealloc *= "delete [] rhscoefvec;\n";
    end
    code *= "\n";

    if lorr==LHS
        code *= "// Trial function factors.\n"
    end
    code *= preloop_code;
    code *= "// Multiply quadrature weights, geometric factors and coefficients.\n"
    code *=
"for(unsigned int k=0;k<(eleOrder+1);k++){
    for(unsigned int j=0;j<(eleOrder+1);j++){
        for(unsigned int i=0;i<(eleOrder+1);i++){
";
    code *= loop_code;
    code *= "
        }
    }
}
\n";
    code *= "// multiply by test functions\n"
    code *= postloop_code;

    combine_loop = "out_1[i]";
    for i=2:length(terms)
        combine_loop *= "+out_"*string(i)*"[i]";
    end
    code *=
"
// Combine all of the terms into the output.
for(unsigned int i=0;i<nPe;i++){
    out[i]="*combine_loop*";
}\n";

    code *= dealloc;

    return code;
end

function dendrotarget_generate_term_calculation(term, thisterm, var, lorr)
    test_code = "";
    trial_code = "";
    coef_code = "";
    test_deriv = 0;
    trial_deriv = 0;

    # The output of this term will be stored in a temporary array "out_n"
    out_name = "out_"*string(thisterm);

    # extract each of the factors.
    (test_part, trial_part, coef_part, test_ind, trial_ind) = CodeGenerator.separate_factors(term, var);

    # strip off all negatives, combine and apply the result somewhere convenient
    neg = false;
    if typeof(test_part) == Expr && test_part.args[1] === :- && length(test_part.args) == 2
        neg = !neg;
        test_part = test_part.args[2];
    end
    if typeof(trial_part) == Expr && trial_part.args[1] === :- && length(trial_part.args) == 2
        neg = !neg;
        trial_part = trial_part.args[2];
    end
    if typeof(coef_part) == Expr && coef_part.args[1] === :- && length(coef_part.args) == 2
        neg = !neg;
        coef_part = coef_part.args[2];
    end

    # Turn each part into a code string
    # First the test function
    if !(test_part === nothing)
        test_code = "// Multiply by test function factor: "*string(test_part)*"\n";
        if length(test_part.derivs) > 0
            test_deriv = test_part.derivs[1];
            if test_part.derivs[1] == 1
                test_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,DgT,"*out_name*",imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,QT1d,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,QT1d,imV2,"*out_name*");\n";
            elseif test_part.derivs[1] == 2
                test_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,QT1d,"*out_name*",imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,DgT,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,QT1d,imV2,"*out_name*");\n";
            elseif test_part.derivs[1] == 3
                test_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,QT1d,"*out_name*",imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,QT1d,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,DgT,imV2,"*out_name*");\n";
            elseif test_part.derivs[1] == 4
                # This will eventually be ??
                printerr("Derivative index problem in "*string(test_part));
            else
                printerr("Derivative index problem in "*string(test_part));
            end
        else # no derivatives
            test_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,QT1d,"*out_name*",imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,QT1d,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,QT1d,imV2,"*out_name*");\n";
        end
    else
        # There was no test function on this term. This shouldn't happen
        printerr("No test function found for term: "*string(term)*", expect an error.");
        return (-1, -1, -1, -1, -1, -1);
    end

    # Then the trial function
    if !(trial_part === nothing) && lorr == LHS
        trial_code = "// Multiply by trial function factor: "*string(trial_part)*"\n";
        if length(trial_part.derivs) > 0
            trial_deriv = trial_part.derivs[1];
            if trial_part.derivs[1] == 1
                trial_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,Dg,in,imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,Q1d,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,Q1d,imV2,"*out_name*");\n";
            elseif trial_part.derivs[1] == 2
                trial_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,Q1d,in,imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,Dg,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,Q1d,imV2,"*out_name*");\n";
            elseif trial_part.derivs[1] == 3
                trial_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,Q1d,in,imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,Q1d,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,Dg,imV2,"*out_name*");\n";
            elseif trial_part.derivs[1] == 4
                # This will eventually be ??
                printerr("Derivative index problem in "*string(trial_part));
            else
                printerr("Derivative index problem in "*string(trial_part));
            end
        else # no derivatives
            trial_code *=
"DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,Q1d,in,imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,Q1d,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,Q1d,imV2,"*out_name*");\n";
        end
    else
        # There was no trial function on this term.
    end

    # build weight/coefficient parts
    idxstr = "[i]";
    weight_code = "W1d[i]";
    if config.dimension == 2
        idxstr = "[j*nrp+i]";
        weight_code = "W1d[i]*W1d[j]";
    elseif config.dimension == 3
        idxstr = "[(k*nrp+j)*nrp+i]";
        weight_code = "W1d[i]*W1d[j]*W1d[k]";
    end

    # If term is negative, apply it here
    if neg
        weight_code = "-"*weight_code;
    end

    # Multiply by coefficients inside integral if needed
    # need to change the index for rhs
    if lorr == RHS
        idxstr = "[coefi]";
    end
    if !(coef_part === nothing) || (lorr == RHS && !(trial_part === nothing))
        if !(coef_part === nothing)
            coef_string = string(dendro_change_math_ops(CodeGenerator.replace_entities_with_symbols(coef_part, index="coefi")));
        else
            coef_string = "";
        end
        # for time dependent parts there can be variable parts on RHS
        if lorr == RHS && !(trial_part === nothing)
            if length(coef_string)>0
                coef_string = coef_string * " * " * string(dendro_change_math_ops(CodeGenerator.replace_entities_with_symbols(trial_part, index="coefi")));
            else
                coef_string = string(dendro_change_math_ops(CodeGenerator.replace_entities_with_symbols(trial_part, index="coefi")));
            end
        end

        if lorr == LHS
            weight_code = weight_code*" * "*coef_string;
        else
            rhscoef_code =
"for(int coefi=0; coefi<nPe; coefi++){
    rhscoefvec[coefi] = "*coef_string*";
}
";
        end

    end

    # build the inner weight/coef loop
    idxstr = "[(k*nrp+j)*nrp+i]";
    jacobian_factors = ["Jx*Jy*Jz"  "Jy*Jz"     "Jx*Jz"     "Jx*Jy";
                        "Jy*Jz"     "Jy*Jz/Jx"  "Jz"        "Jy";
                        "Jx*Jz"     "Jz"        "Jx*Jz/Jy"  "Jy";
                        "Jx*Jy"     "Jy"        "Jx"        "Jx*Jy/Jz"];
    coef_code = out_name*idxstr*"*=(("*jacobian_factors[test_deriv+1, trial_deriv+1]*")*"*weight_code*");";

    # If there was no trial part, it's an RHS and we need to finish the quadrature with this
    if lorr == RHS
        trial_code = rhscoef_code*
"// For RHS, multiply by refel.Q to interpolate to quadrature points.
DENDRO_TENSOR_IIAX_APPLY_ELEM(nrp,Q1d,rhscoefvec,imV1);
DENDRO_TENSOR_IAIX_APPLY_ELEM(nrp,Q1d,imV1,imV2);
DENDRO_TENSOR_AIIX_APPLY_ELEM(nrp,Q1d,imV2,"*out_name*");\n";
    end

    return (trial_code, coef_code, test_code, test_ind, trial_ind);
end


using Brownian
using Test

@testset "Brownian" begin

t = LinRange(0, 1, 5)
p = BrownianMotion(t)

rand(p)
rand([p, p])

@test true

end


#!/usr/bin/env julia

using MPI
using PencilArrays
using Random
using Test

import ArrayInterface:
    ArrayInterface,
    StaticInt,
    StaticBool,
    contiguous_axis,
    contiguous_axis_indicator,
    contiguous_batch_size,
    dense_dims,
    stride_rank

struct DummyArray{T,N} <: AbstractArray{T,N}
    dims :: Dims{N}
    DummyArray{T}(::UndefInitializer, dims) where {T} = new{T,length(dims)}(dims)
end

Base.size(x::DummyArray) = x.dims
Base.getindex(::DummyArray{T}, ind...) where {T} = zero(T)
Base.strides(x::DummyArray) = Base.size_to_strides(1, size(x)...)

function non_dense_array(::Type{T}, dims) where {T}
    # Only the first dimension is dense: (True, False, False, ...).
    N = length(dims)
    dims_parent = ntuple(d -> (d - 1) + dims[d], Val(N))
    up = view(Array{T}(undef, dims_parent), Base.OneTo.(dims)...)
    @assert dense_dims(up) === ntuple(d -> StaticBool(d == 1), Val(ndims(up)))
    up
end

function non_contiguous_array(::Type{T}, dims) where {T}
    N = length(dims)
    dims_parent = (2, dims...)  # we take the slice [1, :, :, ...]
    up = view(Array{T}(undef, dims_parent), 1, ntuple(d -> Colon(), Val(N))...)
    @assert contiguous_axis(up) === StaticInt(-1)
    @assert size(up) == dims
    @assert ArrayInterface.size(up) == dims
    up
end

function test_array_interface(p::Pencil)
    # Test different kinds of parent arrays
    dims_mem = size_local(p, MemoryOrder())
    up_regular = Array{Float64}(undef, dims_mem)
    up_noncontig = non_contiguous_array(Float64, dims_mem)
    up_nondense = non_dense_array(Float64, dims_mem)
    up_dummy = DummyArray{Float64}(undef, dims_mem)

    parents = (
        up_regular,
        up_noncontig,
        up_nondense,
        up_dummy,
    )

    @testset "Parent $(typeof(up))" for up in parents
        u = PencilArray(p, up)

        @test ArrayInterface.parent_type(u) === typeof(up)
        @test ArrayInterface.known_length(u) === missing
        @test !ArrayInterface.can_change_size(u)
        @test ArrayInterface.ismutable(u)
        @test ArrayInterface.can_setindex(u)
        @test ArrayInterface.aos_to_soa(u) === u
        @test ArrayInterface.fast_scalar_indexing(u)
        @test !ArrayInterface.isstructured(u)

        # Compare outputs with equivalent PermutedDimsArray
        iperm = inv(permutation(u))
        vp = PermutedDimsArray(up, iperm)

        functions = (
            contiguous_axis, contiguous_axis_indicator,
            contiguous_batch_size, stride_rank, dense_dims,
            ArrayInterface.size, ArrayInterface.strides, ArrayInterface.offsets,
        )

        for f in functions
            @inferred f(u)
            @test f(u) === f(vp)
        end
    end

    nothing
end

MPI.Init()
Nxyz = (11, 21, 32)
comm = MPI.COMM_WORLD
Nproc = MPI.Comm_size(comm)
myrank = MPI.Comm_rank(comm)

let dev_null = @static Sys.iswindows() ? "nul" : "/dev/null"
    MPI.Comm_rank(comm) == 0 || redirect_stdout(open(dev_null, "w"))
end

rng = MersenneTwister(42 + myrank)

topo = MPITopology(comm, Val(2))

pen1 = Pencil(topo, Nxyz, (2, 3))
pen2 = Pencil(pen1, decomp_dims=(1, 3), permute=Permutation(2, 1, 3))
pen3 = Pencil(pen2, decomp_dims=(1, 2), permute=Permutation(3, 2, 1))
pens = (pen1, pen2, pen3)

@testset "ArrayInterface -- Pencil$(decomposition(p))" for p in pens
    test_array_interface(p)
end


using InteractiveDynamics, DynamicalBilliards, GLMakie

# For 3b1b style
# BLUE = "#7BC3DC"
# BROWN = "#8D6238"

PURPLE = "#9134F1"
TEAL =  "#25D5DB"
GREEN = "#49C544"
RED = "#C32D2D"

TEALGREEN = "#20D2A0"
GREY = "#9a9a9a"

COLORS = to_color.([PURPLE, TEALGREEN, RED, GREY])
CGRAD = cgrad(COLORS)

# Overwrite default color of obstacles to white (to fit with black background)
InteractiveDynamics.obcolor(::Obstacle) = RGBf0(1, 1, 1)
InteractiveDynamics.obfill(::Obstacle) = RGBAf0(1, 1, 1, 0.4)
InteractiveDynamics.oblw(::Obstacle) = 4

videodir(args...) = projectdir("video", args...)

MAXRES = (1500, 900) # depends on monitor
MAXPS = 1.8

include("set_black_theme.jl")


# This file was generated, do not modify it. # hide
v = categorical([1, 2, 3, 1, 2, 3, 1, 2, 3], ordered=true)

levels(v)


struct Analysis{F} <: AbstractElement
    f::F
    kwargs::NamedTuple
end

Analysis(f; kwargs...) = Analysis(f, values(kwargs))

Spec(a::Analysis) = Spec{Any}(analyses=(a,))

(a::Analysis)(; kwargs...) = Analysis(a.f, merge(a.kwargs, values(kwargs)))

(a::Analysis)(args...; kwargs...) = a.f(args...; merge(a.kwargs, values(kwargs))...)


function show_args(args)
  @show args
end

include("run_pd_hyak.jl")
datafile = "../../data/T1_Spitzer_data.jld2"
#foutput = "T1_pd_MCMC_run_05.jld2"
foutput = string("T1_pd_MCMC_run_001_",show_args(ARGS)[1],".jld2")
numwalkers = 50
burnin = 1
thinning = 10
astep = 2.0
nsteps = 40000
run_pd(datafile,foutput,numwalkers,burnin,thinning,astep,nsteps;nout=10000,incprior=true)


using GPR

function experimentVarInt(config, id, eid, mechanism, varargs...)
    testdfs = config["datasets"][2]  # Each thread operates on its own dataset -> no races
    testdf = testdfs.df[id][shuffle(1:nrow(testdfs.df[id]))[1:config["testsamples"]], :]
    xtest_future_true = [CState(x) for x in testdf.sfuture]
    # Add noise to the dataset
    applynoise!(testdf, config["Σ"], eid, config["Δtsim"], varargs...)
    xtest_old = [CState(x) for x in testdf.sold]
    predictedstates = Vector{CState{Float64, length(mechanism.bodies)}}()
    for i in 1:length(xtest_old)
        predictedstate = predictdynamics(mechanism, xtest_old[i], config["simsteps"])
        push!(predictedstates, predictedstate)
    end
    return predictedstates, xtest_future_true
end

function experimentVarIntP1(config, id)
    mechanism = simplependulum2D(1, Δt=0.01, threadlock = config["mechanismlock"])[2]
    return experimentVarInt(config, id, "P1", mechanism, mechanism.bodies[1].shape.rh[2])
end

function experimentVarIntP2(config, id)
    mechanism = doublependulum2D(1, Δt=0.01, threadlock = config["mechanismlock"])[2]
    return experimentVarInt(config, id, "P2", mechanism, mechanism.bodies[1].shape.xyz[3], mechanism.bodies[2].shape.xyz[3])
end

function experimentVarIntCP(config, id)
    mechanism = cartpole(1, Δt=0.01, threadlock = config["mechanismlock"])[2]
    return experimentVarInt(config, id, "CP", mechanism, mechanism.bodies[2].shape.rh[2])
end

function experimentVarIntFB(config, id)
    mechanism = fourbar(1, Δt=0.01, threadlock = config["mechanismlock"])[2]
    return experimentVarInt(config, id, "FB", mechanism, mechanism.bodies[1].shape.xyz[3])
end


using ImagineProcedures, ImagineInterface
using Test

rt = rigtemplate("ocpi-2")
isigs = rt[1:2]

#returns 2 ImagineSignals
f = x -> isigs
sg = SignalGenerator(f, (0,))

@test isequal(description(sg), "")
@test func(sg) == f
@test func_args(sg) == (0,)
@test call!(sg) == isigs

af = x->x[1]
ip = ImagineProcedure("p", sg, af)
@test isa(repr(ip), AbstractString)
@test isequal(description(ip), "p")
@test ImagineProcedures.signal_generator(ip) == sg
@test ImagineProcedures.analyze_function(ip) == af
@test outputs(ip) == isigs
@test process(ip) == isigs[1]


import CategoricalArrays: recode!

"""
    recode!(dat, col, recode_lookup, typ)
This function performs the re-coding specified by the `recode_lookup`
dictionary and modifies the `dat` argument in place. Note that we must
specify the type of the column using the `typ` argument. This should be
the type of recoded values.
"""

function recode!(dat::DataTable, col::Symbol, recode_lookup::Dict, typ::DataType)
    tmp = dat[col]
    n = size(dat, 1)
    dat[col] = NullableArray{typ, 1}(n)
    for i = 1:n
        if tmp[i] ∈ keys(recode_lookup)
            dat[i, col] = recode_lookup[tmp[i]]
        else
            dat[i, col] = tmp[i]
        end
    end
end

"""
    clean_string(x)
Given a single string `x`, this function returns a copy of the string
where all non-alphabetic characters have been replaced with a blank space.
The exception is the hyphen, which is not removed or altered.
"""
function clean_string(x::T) where {T <: AbstractString}
    xlwr = lowercase(x)
    xcln = replace(xlwr, r"[^-a-z]", " ")     # keep only letters and hyphens
    return xcln
end

function cut_stopwords(x_arr, stopwords)
    res = [z for z in x_arr if z ∉ stopwords]
    return res
end

function clean_all_strings(x::NullableArray{T, 1}, stopwords::Array{String, 1}) where {T <: AbstractString}
    n = length(x)
    res = Array{String,1}(n)
    for i = 1:n
        xi_cln = clean_string(x[i].value)
        res[i] = join(cut_stopwords(split(xi_cln), stopwords), " ")
    end
    res
end

function clean_all_strings(x::Array{T, 1}, stopwords::Array{String, 1}) where {T <: AbstractString}
    n = length(x)
    res = Array{String,1}(n)
    for i = 1:n
        xi_cln = clean_string(x[i])
        res[i] = join(cut_stopwords(split(xi_cln), stopwords), " ")
    end
    res
end

"""
    document_frequency(x)
Give a vector of strings, `x`, this function return a dictionary where the
keys are the unique words in the entire vector `x` and the values are integers
representing the number of documents that contained the given word.
"""
function document_frequency(x::NullableArray{String, 1})
    n = length(x)
    res = Dict{String, Int}()
    for i = 1:n
        unq_words = unique(split(x[i].value))
        for word in unq_words
            res[word] = get(res, word, 0) + 1
        end
    end
    res
end

function document_frequency(x::Array{String, 1})
    n = length(x)
    res = Dict{String, Int}()
    for i = 1:n
        unq_words = unique(split(x[i]))
        for word in unq_words
            res[word] = get(res, word, 0) + 1
        end
    end
    res
end

"""
    tf_idf_datatable(x, stopwords)
Given a vector (of strings, typically), `x`, this function returns a
DataTable where columns contain the TF/IDF values for the words appearing
in the original vector `x`. In particular, the rows in the output DataTable
correspond to the indices of the input vector `x`. And the columns correspond
to a sorted list of unique terms in the the vocabulary of the input vector (after
the stop words have been removed).
"""
function tf_idf_datatable(x::AbstractArray, stopwords::Array{String,1})
     x_cln = clean_all_strings(x, stopwords)
     doc_freq = document_frequency(x_cln)
     unq_words = collect(keys(doc_freq))
     sort!(unq_words)

     n = length(x)
     p = length(unq_words)

     word_idx = Dict{String, Int}()
     for j = 1:p
         word_idx[unq_words[j]] = j
     end

     mat = zeros(n, p)
     for i = 1:n
         word_cnts = countmap(split(x_cln[i]))
         for (word, cnt) in word_cnts
             j = word_idx[word]
             mat[i, j] = cnt * log(n / doc_freq[word])
         end
     end
     res = convert(DataTable, mat)
     col_names = map(Symbol, unq_words)
     names!(res, col_names)
     res
 end


@testset "phylogenetic_tree(::String)" begin
    tree = phylogenetic_tree("(A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5);")
    names = [get_prop(tree.graph, x, :name) for x in 1:6]
    branch_lengths = [get_prop(tree.graph, e, :branch_length) for
                        e in collect(edges(bfs_tree(tree.graph, 1)))[1:end]]
    @test names == ["root", "A", "B", "E", "C", "D"]
    @test branch_lengths == [0.1, 0.2, 0.5, 0.3, 0.4]
end

@testset "phylogenetic_tree(::AbstractHLAData)" begin
    fasta_path = joinpath(@__DIR__, "data", "test.fasta")
    hla_types = rand(HLAType, 5)
    hla_data = HLAData("test", fasta_path, hla_types, missing, missing)

    @test Escape.phylogenetic_tree(hla_data) isa PhylogeneticTree
end

@testset "add_to_graph!(::AbstractMetaGraph, ::Int, ::String)" begin
    graph = MetaDiGraph()
    add_vertex!(graph)

    Escape.add_to_graph!(graph, 1, "(A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5)")
    names = [get_prop(graph, x, :name) for x in [3,4,5,6,7]]
    branch_lengths = [get_prop(graph, e, :branch_length) for
                        e in collect(edges(graph))[2:end]]

    @test names == ["A", "B", "E", "C", "D"]
    @test branch_lengths == [0.1, 0.2, 0.5, 0.3, 0.4]

end

@testset "extract_name(::String)" begin
    @test ismissing(Escape.extract_name(""))
    @test ismissing(Escape.extract_name("(A:3, B:1)"))
    @test ismissing(Escape.extract_name("(A:3, B:1):3"))
    @test ismissing(Escape.extract_name("(,)"))
    @test Escape.extract_name("B:5") == "B"
    @test Escape.extract_name("(A:3, B:1)E") == "E"
    @test Escape.extract_name("(A:3, B:1)E:5") == "E"
    @test Escape.extract_name("(,)E") == "E"
    @test Escape.extract_name("(,B)E:3") == "E"
end

@testset "extract_length(::String)" begin
    @test ismissing(Escape.extract_length(""))
    @test ismissing(Escape.extract_length("(A:3, B:1)"))
    @test ismissing(Escape.extract_length("(,)"))
    @test ismissing(Escape.extract_length("(A:3, B:1)E"))
    @test ismissing(Escape.extract_length("(,)E"))
    @test Escape.extract_length("(A:3, B:1):3") == 3.0
    @test Escape.extract_length("B:5") == 5.0
    @test Escape.extract_length("(A:3, B:1)E:5") == 5.0
    @test Escape.extract_length("(,B)E:3") == 3.0
end

@testset "newick_component(::String)" begin
    components = ["A:0.1", "B:0.2", "(C:0.3,D:0.4)E:0.5"]
    @test Escape.newick_components("(A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5);") == components
    @test Escape.newick_components("(,)") == ["", ""]
    @test Escape.newick_components("(,B)E:3") == ["", "B"]
end

@testset "newick_string(::PhylogeneticTree)" begin
    newick = ["(A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5);", "(,);",
              "((B:0.2,(C:0.3,D:0.4)E:0.5)A:0.1);",
              "(B:6.0,(A:5.0,C:3.0,E:4.0):5.0,D:11.0);"]
    for n in newick
        @test Escape.newick_string(phylogenetic_tree(n)) == n
    end
end

@testset "leaves(::PhylogeneticTree)" begin
    fasta_path = joinpath(@__DIR__, "data", "phylogeny.fasta")
    hla_data = HLAData("test", fasta_path, rand(HLAType, 6), missing, missing)
    tree = phylogenetic_tree(hla_data)

    @test leaves(tree) isa Vector{Int}
end

@testset "isleaf(v::Int, tree::Phylogenetic)" begin
    fasta_path = joinpath(@__DIR__, "data", "phylogeny.fasta")
    hla_data = HLAData("test", fasta_path, rand(HLAType, 6), missing, missing)
    tree = phylogenetic_tree(hla_data)

    @test isleaf(leaves(tree)[1], tree)
    @test !isleaf(90, tree)
end

@testset "get_property(::PhylogeneticTree, ::Int, ::Symbol)" begin
    fasta_path = joinpath(@__DIR__, "data", "phylogeny.fasta")
    hla_data = HLAData("test", fasta_path, rand(HLAType, 6), missing, missing)
    tree = phylogenetic_tree(hla_data)
    tree_leaves = Escape.leaves(tree)
    @test get_property(tree, 1, :name) == "root"
    @test get_property(tree, tree_leaves[1], :name) ==
        get_prop(tree.graph, tree_leaves[1], :name)
    @test get_property(tree, tree_leaves[1], :name) ==
        get_prop(tree.graph, tree_leaves[1], :name)
    @test ismissing(get_property(tree, 100, :name))
    @test ismissing(get_property(tree, 1, :nonexisting))
end

@testset "set_property!(::PhylogeneticTree, ::Int, ::Symbol)" begin
    fasta_path = joinpath(@__DIR__, "data", "phylogeny.fasta")
    hla_data = HLAData("test", fasta_path, rand(HLAType, 6), missing, missing)
    tree = phylogenetic_tree(hla_data)

    @test get_property(tree, 1, :name) == "root"
    set_property!(tree, 1, :name, "new name")
    @test get_property(tree, 1, :name) == "new name"
end

@testset "annotate!(::PhylogeneticTree, ::HLAData, ::Replacement)" begin
    fasta_path = joinpath(@__DIR__, "data", "test.fasta")
    r = Replacement("test", 2, 'S', false)
    hla_data = HLAData("test", fasta_path, rand(HLAType, 5), missing, missing)
    tree = phylogenetic_tree(hla_data)

    annotation = Dict("1" => "0", "2" => "0", "3" => "1", "4" => "1", "5" => "1")
    Escape.annotate!(tree, hla_data, r)

    for k in keys(annotation)
        v = filter(x -> get_property(tree, x, :name) == k, leaves(tree))[1]
        @test get_property(tree, v, :state) == annotation[k]
    end
end

@testset "matching(::PhylogeneticTree, ::HLAData)" begin
    fasta_path = joinpath(@__DIR__, "data", "phylogeny.fasta")
    hla_data = HLAData("test", fasta_path, rand(HLAType, 6), missing, missing)
    tree = phylogenetic_tree(hla_data)

    @test Escape.matching(tree, hla_data)

    set_property!(tree, leaves(tree)[2], :name, "test")
    @test Escape.matching(tree, hla_data) isa ErrorException

    short_hla_data = HLAData("short", joinpath(@__DIR__, "data", "test.fasta"),
                             rand(HLAType, 5), missing, missing)
    @test Escape.matching(tree, short_hla_data) isa DimensionMismatch
end

@testset "load PhylogeneticTree from .jls" begin
    tree = phylogenetic_tree("(A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5);")
    path, io = mktemp()

    serialize(path * ".jls", tree)
    loaded_tree = deserialize(path * ".jls")
    close(io)
    rm(path)

    @test loaded_tree isa PhylogeneticTree
end


m = BabyPOMDP()

for step in stepthrough(m, Starve(), "s,a,o", max_steps=1)
    gfx = render(m, step)
    show(stdout, MIME("text/plain"), gfx)
end


include("field_operations.jl")
include("spherical_harmonics.jl")
using Functors

struct Grid
    dx::AbstractFloat
    sz::Any
    origin::Any
    spherical_harmonics::Any
    r::Any
    dV::AbstractFloat
end
@functor Grid

"""
    Grid(dx::AbstractFloat, rmax::AbstractFloat; dims = 3, rank_max = 1)
"""
function Grid(dx::AbstractFloat, rmax::AbstractFloat; dims = 3, rank_max = 2)
    dV = dx^dims

    nrmax = round(Int, rmax / dx)
    origin = (nrmax + 1) * ones(dims)
    sz = fill(2nrmax + 1, dims)

    r, spherical_harmonics =
        make_spherical_harmonics(dx, sz, origin, rank_max; dims)
    Grid(dx, sz, origin, spherical_harmonics, r, dV)
end

"""
    Grid(
        dx::AbstractFloat,
        sz::Union{AbstractVector,Tuple};
        origin = nothing,
        rank_max = 1
        )
"""
function Grid(
    dx::AbstractFloat,
    sz::Union{AbstractVector,Tuple};
    origin = nothing,
    rank_max = 1,
)
    dims = length(sz)
    dV = dx^dims
    if origin === nothing
        origin = (sz .+ 1.0) / 2
    end
    r, spherical_harmonics =
        make_spherical_harmonics(dx, sz, origin, rank_max; dims)
    Grid(dx, sz, origin, spherical_harmonics, r, dV)
end

GRID_ALIASES = (
    x = x -> x.spherical_harmonics[1][1],
    y = x -> x.spherical_harmonics[1][2],
    z = x -> x.spherical_harmonics[1][3],
)

function Base.getproperty(grid::Grid, v::Symbol)
    v in GRID_ALIASES ? GRID_ALIASES[v](grid) : getfield(grid, v)
end

"""
    Field(; grid = nothing, rank = nothing, radfunc = nothing)
"""
function Field(; grid = nothing, rank = nothing, radfunc = nothing)
    if radfunc !== nothing
        r = radfunc.(grid.r)
        r = join([r])
        return rank == 0 ? r : field_prod(r, grid.spherical_harmonics[rank])
    end

    zeros(grid.sz..., 2rank + 1)
end

struct Props

    nranks::Any
    ranks::Any
    parities::Any
    slices::Any
end

function Props(ranks)
    Props(; ranks)
end
function Props(; nranks = nothing, ranks = nothing)
    if ranks === nothing
        ranks = vcat([fill(i - 1, n) for (i, n) in enumerate(nranks)]...)
    elseif nranks === nothing
        nranks = zeros(Int, maximum(ranks) + 1)
        for x in ranks
            nranks[x+1] += 1
        end
    end
    ends = Int.(cumsum(2 .* ranks .+ 1))
    slices = [(i == 1 ? 1 : ends[i-1] + 1):ends[i] for i in eachindex(ends)]

    # starts = [1, (1 .+ cumsum(nranks))...]
    # grouped_slices = [slices[starts[i]:starts[i+1]-1] for i = 1:length(nranks)]
    parities = (-1) .^ ranks
    Props(nranks, ranks, parities, slices)
end

"""
    (field::AbstractArray)(rvec, grid::Grid)

Makes field objects callable. Returns interpolated tensor value of the tensor field at position rvec.
"""
function Base.get(fields::AbstractArray, props::Props, i::Int)
    fields[:, :, :, props.slices[i]]
end

function Base.get(field::AbstractArray, grid::Grid, rvec::AbstractVector)
    sum([
        w * getindex.(eachslice(field, dims = 4), ix...)
        for (ix, w) in nearest(grid.dx, grid.origin, rvec)
    ])
end
function Base.get(
    fields::AbstractArray,
    props::Props,
    i::Int,
    rvec::AbstractVector,
)
    field = get(fields, props, i)
    get(field, props.grid, rvec)
end
function Base.get(fields::AbstractArray, props::Props, rvec::AbstractVector)
    get(fields, props, 1, rvec)
end

"""
    (field::AbstractArray)(i::Int)

Makes field objects callable. Returns ith component array (2d/3d) of tensor field.
"""
function (field::AbstractArray)(i::Int)
    selectdim(field, dim(field), i)
end

function nearest(dx, origin, rvec)
    ix = origin .+ rvec / dx
    ixfloor = floor.(Int, ix)
    er = ix - ixfloor
    [
        (ixfloor + [x, y, z], prod(ones(3) - abs.([x, y, z] - er)))
        for x = 0:1, y = 0:1, z = 0:1
    ]
end

"""
    put_point_source!(
        field::AbstractArray,
        grid::Grid,
        rvec::AbstractVector,
        val::AbstractVector)

"""
function put_point_source!(
    field::AbstractArray,
    grid::Grid,
    rvec::AbstractVector,
    val::AbstractVector,
)
    for (ix, w) in nearest(grid.dx, grid.origin, rvec)
        for (c, vi) in zip(1:size(field)[end], val)
            field[ix..., c] += w / grid.dV * vi
        end
    end
end

function put_point_source!(f, grid, rvecs::AbstractMatrix, vals::AbstractMatrix)
    for (val, rvec) in zip(eachcol(vals), eachcol(rvecs))
        put_point_source!(f, grid, rvec, val)
    end
end

"""
    field_rank(x::AbstractArray)
"""
field_rank(x::AbstractArray) = (size(x)[end] - 1) ÷ 2


abstract type TypeOfUnknown end

struct RandomStateType <: TypeOfUnknown end

struct TupleType <: TypeOfUnknown
    elements :: Array{TypeOfUnknown, 1}
end

struct ArrayType <: TypeOfUnknown
    shape :: Nullable{Array{Nullable{Int64}, 1}}
    eltype :: Nullable{DataType}
end

struct OpaqueType <: TypeOfUnknown
end

array_typeof(arr::Number) = ArrayType([], eltype(arr))
array_typeof(arr::AbstractArray) = ArrayType(collect(size(arr)), eltype(arr))

abstract type ArrayExpr end

struct Unknown <: ArrayExpr
    typ :: TypeOfUnknown

    # Technically, this object does not need a name, but:
    # 1. It makes debugging easier
    # 2. Julia automatically caches objects, so, say, two `Unknown(OpaqueType())`
    #    created seaparetly would have the same object ID, which would ruin the whole
    #    expression graph processing.
    name :: Symbol

    Unknown(typ; tag=nothing) = new(typ, tag === nothing ? gensym() : gensym(tag))
end

struct LazyArray <: ArrayExpr
    value :: Any
end

struct CallNode <: ArrayExpr
    func :: Symbol
    args :: Array{ArrayExpr, 1}
    typ :: TypeOfUnknown
end

map_accum(func, state, aexpr) = func(state, aexpr)

function map_accum(func, state, aexpr::CallNode)
    new_args = []
    for arg in aexpr.args
        state, new_arg = func(state, arg)
        push!(new_args, new_arg)
    end
    state, CallNode(aexpr.func, new_args, aexpr.typ)
end

expr_type(aexpr::Unknown) = aexpr.typ
expr_type(aexpr::LazyArray) = array_typeof(aexpr.value)
expr_type(aexpr::CallNode) = aexpr.typ

is_leaf(aexpr::ArrayExpr) = true
is_leaf(aexpr::CallNode) = false

is_node(aexpr::ArrayExpr) = false
is_node(aexpr::CallNode) = true

children(aexpr::ArrayExpr) = []
children(aexpr::CallNode) = aexpr.args


using Test
using Word2Vec

include("inputprocessing_tests.jl")
include("huffman_tests.jl")
include("contextdata_tests.jl")
# include("network_tests.jl")


using Revise
using BenchmarkTools
using LarSurf
using Statistics
using SparseArrays
# x = 1

# paper
# Calculation of lar model (141 ms on HP) is faster than reading LARmodel from
# file (923 ms on HP)

block_size = [16,16,16]
# block_size = [64,64,64]
# pure computation
LarSurf.set_param(larmodel_allow_memory=false, larmodel_allow_write_files=false; larmodel_allow_read_files=false)
bm = @benchmark LarSurf.get_larmodel(block_size)
println("Mean time: $(mean(bm.times) * 10^-9) [s]")
# mean time [16,16,16]
# 141 ms with JLD
# 124 with JLD2
# mean time [64,64,64]
# 20.67 s with JLD2

# calculate and write
LarSurf.set_param(larmodel_allow_memory=false, larmodel_allow_write_files=true; larmodel_allow_read_files=false)
bm = @benchmark LarSurf.get_larmodel(block_size)
println("Mean time: $(mean(bm.times) * 10^-9) [s]")
# mean time [16,16,16]
# 923 ms with JLD
# 130 with JLD2
# mean time [64,64,64]
# 21.92 s with JLD2

# just read
LarSurf.set_param(larmodel_allow_memory=false, larmodel_allow_write_files=false; larmodel_allow_read_files=true)
bm = @benchmark LarSurf.get_larmodel(block_size)
println("Mean time: $(mean(bm.times) * 10^-9) [s]")
# mean time [16,16,16]
# 983 ms with JLD
# 134 with JLD2
# mean time [64,64,64]
# 20.86 s with JLD2

# Pure computation
LarSurf.set_param(larmodel_allow_memory=false, larmodel_allow_write_files=false; larmodel_allow_read_files=false)
LarSurf.set_param(boundary_allow_memory=false, boundary_allow_write_files=false; boundary_allow_read_files=false)
bm = @benchmark LarSurf.get_boundary3(block_size, false)
println("Mean time: $(mean(bm.times) * 10^-9) [s]")
# mean 16
# 214 ms

# Computation and write
LarSurf.set_param(larmodel_allow_memory=false, larmodel_allow_write_files=false; larmodel_allow_read_files=false)
LarSurf.set_param(boundary_allow_memory=false, boundary_allow_write_files=true; boundary_allow_read_files=false)
bm = @benchmark LarSurf.get_boundary3(block_size, false)
println("Mean time: $(mean(bm.times) * 10^-9) [s]")
# mean 16
# 270 ms

# Computation and read
LarSurf.set_param(larmodel_allow_memory=false, larmodel_allow_write_files=false; larmodel_allow_read_files=false)
LarSurf.set_param(boundary_allow_memory=false, boundary_allow_write_files=false; boundary_allow_read_files=true)
bm = @benchmark LarSurf.get_boundary3(block_size, false)
println("Mean time: $(mean(bm.times) * 10^-9) [s]")
# mean 16
# 139 ms
#


function check_yml_params(d)
  species_keys = ["migration threshold", "number of genes", "number of phenotypes", "abiotic phenotypes", "biotic phenotypes", "migration phenotype", "migration threshold", "ploidy", "epistasis matrix", "pleiotropy matrix", "growth rate", "expression array", "selection coefficient", "mutation probabilities", "mutation magnitudes", "N", "vision radius", "check fraction", "environmental noise", "optimal phenotype values", "optimal phenotypes", "age", "recombination", "initial energy", "bottleneck function", "bottleneck times", "reproduction start age", "reproduction end age"]
  model_keys = ["generations", "space", "metric", "periodic", "resources", "interactions", "food sources", "seed"]
  for (k, sp) in d["species"]
    @assert haskey(sp, "name") "Species name field missing."
    for kk in species_keys
      @assert haskey(sp, kk) "Species $(sp["name"]) does not have field $(kk)."
    end
  end
  for kk in model_keys
    @assert haskey(d["model"], kk) "Model parameter $kk is missing."
  end
end

function check_param_shapes(d)
  nspecies = length(d["species"])

  for species in 1:nspecies
    spacesize = prod(d["model"]["space"])
    dd = d["species"][species]
    # Size of optimal phenotypes
    for opt in dd["optimal phenotype values"]
      @assert length(opt) == length(dd["abiotic phenotypes"]) * spacesize "Not as many optimal phenotypes as abiotic phenotypes. Species: $species"
      # for mat in opt
      #   @assert length(mat) == spacesize "Optimal phenotpyes per trait is not the same size as the number of sites"
      # end
    end
    # Biotic and abiotic phenotypes are Array
    @assert typeof(dd["abiotic phenotypes"]) <: AbstractArray "Abiotic phenotypes should be array"
    @assert typeof(dd["biotic phenotypes"]) <: AbstractArray "Biotic phenotypes should be array"
    # Ploidy
    @assert dd["ploidy"] < 3  "Ploidy more than 2 is not implemented"
    # epistasis matrix
    # @assert size(dd["epistasis matrix"], 2) % dd["ploidy"] == 0 "Number of columns in epistasisMat are not correct. They should a factor of ploidy"
    @assert length(dd["epistasis matrix"])  == (dd["ploidy"] * dd["number of genes"])^2 "epistasis matrix does not have correct number of elements in species $species."
    # name is string
    @assert typeof(dd["name"]) <: AbstractString "name should be a string"
    # age is integer
    @assert typeof(dd["age"]) <: Int "Age of species $species should be integer."
    @assert typeof(dd["reproduction start age"]) <: Int "reproduction start age of species $species should be integer."
    @assert typeof(dd["reproduction end age"]) <: Int "reproduction end age of species $species should be integer."
    # Check fraction is float
    @assert typeof(dd["check fraction"]) <: Real "Check fraction of species $species should be number."
    # recombination is Bool
    @assert typeof(dd["recombination"]) <: Real "recombination of species $species should be type numeric"
    @assert typeof(dd["initial energy"]) <: Real "Initial energy should be a number"
    # bottleneck should be nothing or array/string
    @assert typeof(dd["bottleneck function"]) <: AbstractString || isnothing(dd["bottleneck function"])
    if !isnothing(dd["bottleneck function"])
      @assert isfile(dd["bottleneck function"]) "bottleneck function points to a nonexistant file"
    end
    @assert typeof(dd["bottleneck times"]) <: AbstractArray{Int} || isnothing(dd["bottleneck times"])
  end
  # Space should be 2D
  if !isnothing(d["model"]["space"]) && d["model"]["space"] != "nothing"
    @assert length(d["model"]["space"]) == 2 "Space should be only 2D"
  end
  # Resources
  @assert typeof(d["model"]["resources"]) <: AbstractArray{Int} "Resources should be array of Integers"
  @assert length(d["model"]["resources"]) == prod(d["model"]["space"]) "Resources should be as long as number of sites"
  # # Area
  # @assert typeof(d["model"]["area"]) <: AbstractArray "Area should be array"
  # @assert eltype(d["model"]["area"]) <: Real "Area elements should be numbers"
  # @assert length(d["model"]["area"]) == prod(d["model"]["space"]) "Area should be as long as number of sites"
  # Interactions
  @assert typeof(d["model"]["interactions"]) <: AbstractArray "Interactions should be an array"
  @assert eltype(d["model"]["interactions"]) <: AbstractFloat "Elements of interactions should be floating numbers"
  @assert length(d["model"]["interactions"]) == nspecies*nspecies "Interactions should be as many as number of species times number of species"
  # Food sources
  @assert typeof(d["model"]["food sources"]) <: AbstractArray "Food sources should be array"
  @assert eltype(d["model"]["food sources"]) <: AbstractFloat "Elements of food sources should be floating numbers"
  @assert length(d["model"]["food sources"]) == nspecies*nspecies "Food sources should be as many as number of species times number of species"
end

function reformat_params!(d)
  nspecies = length(d["species"])

  # Fix formats and shapes
  for species in 1:nspecies
    # 1. Reshape and convert pleiotropy matrix
    pmat = d["species"][species]["pleiotropy matrix"]
    ntraits = d["species"][species]["number of phenotypes"]
    ngenes = d["species"][species]["number of genes"] * d["species"][species]["ploidy"]
    d["species"][species]["pleiotropy matrix"] = Bool.(reshape(pmat, ntraits, ngenes))
    # 2. Reshape epistasis matrix
    d["species"][species]["epistasis matrix"] = reshape(d["species"][species]["epistasis matrix"], ngenes, ngenes)
    # 3. add key ngenes
    d["species"][species]["ngenes"] = ngenes
    # 4. reshape optimal phenotype values
    spacesize = prod(d["model"]["space"])
    optphens = d["species"][species]["optimal phenotype values"]
    output = [Array{Float64, 2}[] for i in  1:length(optphens)]
    for optind in 1:length(optphens)
      start = -spacesize + 1
      for phen in 1:length(d["species"][species]["abiotic phenotypes"])
        first = start + (phen * spacesize)
        last = first + spacesize - 1
        push!(output[optind], reshape(optphens[optind][first:last], Tuple(d["model"]["space"])...))
      end
    end
    d["species"][species]["optimal phenotype values"] = output
    # Check fraction to float
    d["species"][species]["check fraction"] = Float64(d["species"][species]["check fraction"])
    # include bottleneck function
    if !isnothing(d["species"][species]["bottleneck function"])
      include(d["species"][species]["bottleneck function"])
      # reshape bottleneck times
      d["species"][species]["bottleneck times"] = Bool.(reshape(d["species"][species]["bottleneck times"], prod(d["model"]["space"]), d["model"]["generations"]))
    else
      d["species"][species]["bottleneck times"] = Bool.(zeros(Int, prod(d["model"]["space"]), d["model"]["generations"]))
    end
  end

  # Metric to Symbol
  d["model"]["metric"] = Symbol(d["model"]["metric"])

  # "nothing" to nothing for seed and space
  if d["model"]["seed"] == "nothing"
    d["model"]["seed"] = nothing
  end
  if d["model"]["space"] == "nothing"
    d["model"]["space"] = nothing
  end

  # space to tuple
  if !isnothing(d["model"]["space"])
    d["model"]["space"] = Tuple(d["model"]["space"])
  end

  # reshape and convert resources
  d["model"]["resources"] = reshape(d["model"]["resources"], d["model"]["space"]...)
  # # reshape and convert area
  # d["model"]["area"] = reshape(d["model"]["area"], d["model"]["space"]...)
  # reshape and convert interactions
  d["model"]["interactions"] = reshape(d["model"]["interactions"], nspecies, nspecies)
  # reshape and convert food sources
  d["model"]["food sources"] = reshape(d["model"]["food sources"], nspecies, nspecies)
end


function FirstOrderConverter(arg0::SecondOrderODE)
    return FirstOrderConverter((SecondOrderODE,), arg0)
end

function compute_derivatives(obj::FirstOrderConverter, arg0::jdouble, arg1::Vector{jdouble})
    return jcall(obj, "computeDerivatives", Vector{jdouble}, (jdouble, Vector{jdouble}), arg0, arg1)
end

function get_dimension(obj::FirstOrderConverter)
    return jcall(obj, "getDimension", jint, ())
end

function init(obj::OrdinaryDifferentialEquation, arg0::jdouble, arg1::Vector{jdouble}, arg2::jdouble)
    return jcall(obj, "init", void, (jdouble, Vector{jdouble}, jdouble), arg0, arg1, arg2)
end