#!/bin/sh
# This script is used to generate models.hpp.

cat << EOF
// AUTOMATICALLY GENERATED FILE
//
// This file is automatically generated using models.hpp.sh
// to facilitate adding new models to the code.
// Edit the code in the models.hpp.sh instead.

#define UNIQUE_ID __COUNTER__

EOF

MODELS=$(find models/*."${PRECISION}".hpp | sed 's/^.*\///;s/\.\(float\|double\)\.hpp$//' | sort | uniq)
for MODEL in $MODELS
do
cat models/"${MODEL}"."${PRECISION}".hpp
done

cat << EOF

#ifndef __OPENCL_VERSION__
struct Models {
    Size Nv = 0;
    std::vector<Size> model_ids;
    std::vector<Size> model_offsets;
    std::vector<Real> model_params;

    Models() {}
    Models(const std::vector<Real>& input) : Models(input.size(), input.data()) {}
    Models(Size size, const Real* const input) { add_models(size, input); }

    Size size() const {
        return model_ids.size();
    }

    void buffer(OpenCLWrapper* cl) {
        const Size model_count = size();
        model_ids_buffer = cl->bufferR(sizeof(Size) * model_count, model_ids.data());
        model_offsets_buffer = cl->bufferR(sizeof(Size) * model_count, model_offsets.data());
        model_params_buffer = cl->bufferR(sizeof(Real) * model_params.size(), model_params.data());
    }

    std::set<Size> get_set_of_model_ids() const {
        return {model_ids.data(), model_ids.data() + size()};
    }

    void add_model(Size id, Size Nv_, Size Np, Size Np_prev, const Real* const input) {
        model_ids.push_back(id);

        if(model_offsets.size() == 0) {
            model_offsets.push_back(0);
        } else {
            model_offsets.push_back(model_offsets.back() + Np_prev);
        }

        for(Size ip=0; ip<Np; ip++) {
            model_params.push_back(input[ip]);
        }

        if(Nv_ > Nv) {
            Nv = Nv_;
        }
    }

    void add_models(Size size, const Real* const input) {
        Size Np = 0;
        for(Size i=0; i<size;) {
            switch (Size(input[i++] + 0.5)) {
EOF
for MODEL in $MODELS
do
cat << EOF
                case Model_${MODEL}_id:
                    add_model(
                            Model_${MODEL}_id,
                            Model_${MODEL}_Nv,
                            Model_${MODEL}_Np,
                            Np,
                            input + i
                    );
                    Np = Model_${MODEL}_Np;
                    i += Np;
                    break;;
EOF
done
cat << EOF
                default: ASSERT(0); break;;
            }
        }
    }

    static std::string list_all() {
        std::string s;
EOF
for MODEL in $MODELS
do
cat << EOF
        s += Model_${MODEL}_info;
        s += "\n  id: ";
        s += std::to_string(Model_${MODEL}_id);
        s += "\n";
EOF
done
cat << EOF
        return s;
    }

    void step(OpenCLWrapper* cl, Size model_id, struct States inhom,
              struct States weights, struct States states_old,
              struct States states_new, const Real dt) const {
        const std::vector<Size> work_size = {states_old.Nz, states_old.Ny, states_old.Nx};
        switch (model_id) {
EOF
for MODEL in $MODELS
do
cat << EOF
            case Model_${MODEL}_id:
                cl->execute(
                    "Model_${MODEL}_kernel",
                    work_size,
                    size(),
                    model_ids_buffer,
                    model_offsets_buffer,
                    model_params_buffer,
                    inhom.buffer,      StatesIdx{STATES_UNPACK(inhom)},
                    weights.buffer,    StatesIdx{STATES_UNPACK(weights)},
                    states_old.buffer, StatesIdx{STATES_UNPACK(states_old)},
                    states_new.buffer, StatesIdx{STATES_UNPACK(states_new)},
                    dt
                ); break;;
EOF
done
cat << EOF
            default: ASSERT(0); break;;
        }
    }

    private:
    cl_mem model_ids_buffer = NULL;
    cl_mem model_offsets_buffer = NULL;
    cl_mem model_params_buffer = NULL;
};
#endif
EOF
