#ifndef MAIN_ROUTINES_CALLING_H
#define MAIN_ROUTINES_CALLING_H

#include <iostream>
#include <memory>
#include "mpi_funcs.h"
#include "config.h"
#include "utils.h"
#include "input_params.h"
#include "grid.h"
#include "io.h"
#include "main_routines_inversion_mode.h"
#include "model_optimization_routines.h"
#include "main_routines_earthquake_relocation.h"
#include "iterator_selector.h"
#include "iterator.h"
#include "iterator_legacy.h"
#include "iterator_level.h"
#include "source.h"
#include "receiver.h"
#include "kernel.h"
#include "model_update.h"
#include "lbfgs.h"
#include "objective_function_utils.h"
#include "timer.h"
#include "oneD_inversion.h"

// run forward-only or inversion mode
inline void run_forward_only_or_inversion(InputParams &IP, Grid &grid, IO_utils &io) {

    // for check if the current source is the first source
    bool first_src = true;

    if(myrank == 0)
        std::cout << "id_sim: " << id_sim << ", size of src_map: " << IP.src_map.size() << std::endl;

    // estimate running time
    Timer timer("Forward_or_inversion", true);

    // prepare objective_function file
    std::ofstream out_main; // close() is not mandatory
    prepare_header_line(IP, out_main);

    if (id_sim==0) {
        io.prepare_grid_inv_xdmf(0);

        //io.change_xdmf_obj(0); // change xmf file for next src
        io.change_group_name_for_model();

        // write out model info
        io.write_vel(grid, 0);
        io.write_xi( grid, 0);
        io.write_eta(grid, 0);
        //io.write_zeta(grid, i_inv); // TODO

        if (IP.get_verbose_output_level()){
            io.write_a(grid,   0);
            io.write_b(grid,   0);
            io.write_c(grid,   0);
            io.write_f(grid,   0);
            io.write_fun(grid, 0);
        }

        // // output model_parameters_inv_0000.dat
        // if (IP.get_if_output_model_dat())
        //     io.write_concerning_parameters(grid, 0, IP);
    }


    // output station correction file (only for teleseismic differential data)
    IP.write_station_correction_file(0);

    synchronize_all_world();

    /////////////////////
    // loop for inversion
    /////////////////////

    bool line_search_mode = false; // if true, run_simulation_one_step skips adjoint simulation and only calculates objective function value

    // objective function for all src
    CUSTOMREAL v_obj = 0.0, old_v_obj = 0.0;
    std::vector<CUSTOMREAL> v_obj_misfit(20, 0.0);

    for (int i_inv = 0; i_inv < IP.get_max_iter_inv(); i_inv++) {

        if(myrank == 0 && id_sim ==0){
            std::cout << "iteration " << i_inv << " starting ... " << std::endl;
        }

        old_v_obj = v_obj;

        // prepare inverstion iteration group in xdmf file
        io.prepare_grid_inv_xdmf(i_inv);

        ///////////////////////////////////////////////////////
        // run (forward and adjoint) simulation for each source
        ///////////////////////////////////////////////////////

        // run forward and adjoint simulation and calculate current objective function value and sensitivity kernel for all sources
        line_search_mode = false;
        // skip for the mode with sub-iteration
        if (i_inv > 0 && optim_method != GRADIENT_DESCENT) {
        } else {
            bool is_save_T = false;
            v_obj_misfit = run_simulation_one_step(IP, grid, io, i_inv, first_src, line_search_mode, is_save_T);
            v_obj = v_obj_misfit[0];
        }

        // wait for all processes to finish
        synchronize_all_world();

        // check if v_obj is nan
        if (std::isnan(v_obj)) {
            if (myrank == 0)
                std::cout << "v_obj is nan, stop inversion" << std::endl;
            // stop inversion
            break;
        }

        // output src rec file with the result arrival times
        if (IP.get_if_output_in_process_data() || i_inv == IP.get_max_iter_inv()-1 || i_inv==0) {
            IP.write_src_rec_file(i_inv,0);
        }

        ///////////////
        // model update
        ///////////////
        if(myrank == 0 && id_sim ==0)
            std::cout << "model update starting ... " << std::endl;

        if (IP.get_run_mode() == DO_INVERSION) {
            if (optim_method == GRADIENT_DESCENT)
                model_optimize(IP, grid, io, i_inv, v_obj, old_v_obj, first_src, out_main);
            else if (optim_method == HALVE_STEPPING_MODE)
                v_obj_misfit = model_optimize_halve_stepping(IP, grid, io, i_inv, v_obj, first_src, out_main);
            else if (optim_method == LBFGS_MODE) {
                bool found_next_step = model_optimize_lbfgs(IP, grid, io, i_inv, v_obj, first_src, out_main);
                if (!found_next_step)
                    goto end_of_inversion;
            }
        }

        // output station correction file (only for teleseismic differential data)
        IP.write_station_correction_file(i_inv + 1);

        // output objective function
        write_objective_function(IP, i_inv, v_obj_misfit, out_main, "model update");

        // since model is update. The written traveltime field should be discraded.
        // initialize is_T_written_into_file
        for (int i_src = 0; i_src < IP.n_src_this_sim_group; i_src++){
            const std::string name_sim_src = IP.get_src_name(i_src);

            if (proc_store_srcrec) // only proc_store_srcrec has the src_map object
                IP.src_map[name_sim_src].is_T_written_into_file = false;
        }

        // output updated model
        if (id_sim==0) {
            //io.change_xdmf_obj(0); // change xmf file for next src
            io.change_group_name_for_model();

            // write out model info
            if (IP.get_if_output_in_process() || i_inv >= IP.get_max_iter_inv() - 2){
                io.write_vel(grid, i_inv+1);
                io.write_xi( grid, i_inv+1);
                io.write_eta(grid, i_inv+1);
            }
            //io.write_zeta(grid, i_inv); // TODO

            if (IP.get_verbose_output_level()){
                io.write_a(grid,   i_inv+1);
                io.write_b(grid,   i_inv+1);
                io.write_c(grid,   i_inv+1);
                io.write_f(grid,   i_inv+1);
                io.write_fun(grid, i_inv+1);
            }

            // // output model_parameters_inv_0000.dat
            // if (IP.get_if_output_model_dat()
            // && (IP.get_if_output_in_process() || i_inv >= IP.get_max_iter_inv() - 2))
            //     io.write_concerning_parameters(grid, i_inv + 1, IP);

        }

        // writeout temporary xdmf file
        io.update_xdmf_file();

        // wait for all processes to finish
        synchronize_all_world();


        // estimate running time
        CUSTOMREAL time_elapsed = timer.get_t();
        if (id_sim == 0 && myrank == 0 && i_inv < IP.get_max_iter_inv()-1) {
            const time_t end_time_estimated = time_elapsed / (i_inv + 1) * (IP.get_max_iter_inv() - i_inv - 1) + timer.get_start();
            auto will_run_time = (int)(time_elapsed/(i_inv + 1) * (IP.get_max_iter_inv() - i_inv - 1));

            std::cout << std::endl;
            std::cout << "The program begins at " << timer.get_start_t() << std::endl;
            std::cout << "Iteration (" << i_inv + 1 << "/" << IP.get_max_iter_inv() << ") finished at " << time_elapsed << " seconds" << std::endl;
            std::cout << i_inv + 1 << " iterations run " << timer.get_t() << " seconds, the rest of " << IP.get_max_iter_inv() - i_inv - 1 << " iterations require " << will_run_time << " seconds." << std::endl;
            std::cout << "The program is estimated to stop at " << timer.get_utc_from_time_t(end_time_estimated) << std::endl;
            std::cout << std::endl;
        }

        // output current state of the model
        if (IP.get_if_output_final_model()) {
            // io.write_final_model(grid, IP);
            io.write_merged_model(grid, IP, "final_model.h5");
        }
        if (IP.get_if_output_middle_model()) {
            std::string tmp_fname = "middle_model_step_" + int2string_zero_fill(i_inv + 1) + ".h5";
            io.write_merged_model(grid, IP, tmp_fname);
        }


    } // end loop inverse

end_of_inversion:

    // close xdmf file
    io.finalize_data_output_file();

    timer.stop_timer();
    if (id_sim == 0 && myrank == 0) {
        std::cout << std::endl;
        std::cout << "The program begin at " << timer.get_start_t() << std::endl;
        std::cout << "Forward_or_inversion end at " << timer.get_end_t() << std::endl;
        std::cout << "It has run " << timer.get_elapsed_t() << " seconds in total." << std::endl;
        std::cout << std::endl;
    }
}


// run earthquake relocation mode
inline void run_earthquake_relocation(InputParams& IP, Grid& grid, IO_utils& io) {

    Receiver recs;

    // calculate traveltime for each receiver (swapped from source) and write in output file
    calculate_traveltime_for_all_src_rec(IP, grid, io);

    // prepare output for iteration status
    std::ofstream out_main; // close() is not mandatory
    prepare_header_line(IP, out_main);

    // objective function and its gradient
    CUSTOMREAL v_obj      = 999999999.0;

    int        i_iter     = 0;

    std::vector<CUSTOMREAL> v_obj_misfit;

    // iterate
    while (true) {

        v_obj      = 0.0;

        // calculate gradient of objective function at sources
        v_obj_misfit = calculate_gradient_objective_function(IP, grid, io, i_iter);
        v_obj = v_obj_misfit[0];

        // update source location
        recs.update_source_location(IP, grid);

        synchronize_all_world();

        // check convergence
        bool finished = false;

        if (subdom_main && id_subdomain==0) {
            if (i_iter >= N_ITER_MAX_SRC_RELOC){
                std::cout << "Finished relocation because iteration number exceeds the maximum " << N_ITER_MAX_SRC_RELOC << std::endl;
                finished = true;
            }
            allreduce_bool_single_inplace_sim(finished); //LAND
        }

        synchronize_all_world();

        // check if all processes have finished
        broadcast_bool_inter_and_intra_sim(finished, 0);

        synchronize_all_world();

        // output location information
        if(id_sim == 0 && myrank == 0){
            // write objective function
            std::cout << "iteration: " << i_iter << ", objective function: "              << v_obj << std::endl;
        }

        // write objective functions
        write_objective_function(IP, i_iter, v_obj_misfit, out_main, "relocation");

        // write out new src_rec_file
        if (IP.get_if_output_in_process_data() || i_iter == N_ITER_MAX_SRC_RELOC-1 || i_iter==0){
            IP.write_src_rec_file(0,i_iter);
        }

        // modify the receiver's location for output
        IP.modify_swapped_source_location();


        if (finished)
            break;

        // new iteration
        i_iter++;
    }

    // modify the receiver's location
    IP.modify_swapped_source_location();
    // write out new src_rec_file
    IP.write_src_rec_file(0,i_iter);
    // close xdmf file
    io.finalize_data_output_file();

}


// run earthquake relocation mode
inline void run_inversion_and_relocation(InputParams& IP, Grid& grid, IO_utils& io) {

    Timer timer("Inv_and_reloc", true);

    /////////////////////
    // preparation of model update
    /////////////////////

    // for check if the current source is the first source
    bool first_src = true;

    if(myrank == 0)
        std::cout << "id_sim: " << id_sim << ", size of src_map: " << IP.src_map.size() << std::endl;

    // prepare objective_function file
    std::ofstream out_main; // close() is not mandatory
    prepare_header_line(IP, out_main);

    if (id_sim==0) {
        // prepare inverstion iteration group in xdmf file
        io.prepare_grid_inv_xdmf(0);

        //io.change_xdmf_obj(0); // change xmf file for next src
        io.change_group_name_for_model();

        // write out model info
        io.write_vel(grid, 0);
        io.write_xi( grid, 0);
        io.write_eta(grid, 0);
        //io.write_zeta(grid, i_inv); // TODO

        if (IP.get_verbose_output_level()){
            io.write_a(grid,   0);
            io.write_b(grid,   0);
            io.write_c(grid,   0);
            io.write_f(grid,   0);
            io.write_fun(grid, 0);
        }

        // // output model_parameters_inv_0000.dat
        // if (IP.get_if_output_model_dat())
        //     io.write_concerning_parameters(grid, 0, IP);
    }

    // output station correction file (only for teleseismic differential data)
    IP.write_station_correction_file(0);

    synchronize_all_world();

    bool line_search_mode = false; // if true, run_simulation_one_step skips adjoint simulation and only calculates objective function value

    /////////////////////
    // preparation of relocation
    /////////////////////

    Receiver recs;

    /////////////////////
    // initializae objective function
    /////////////////////

    CUSTOMREAL v_obj = 0.0,         old_v_obj = 10000000000.0;
    std::vector<CUSTOMREAL> v_obj_misfit(20, 0.0);

    if (inv_mode == ITERATIVE){

        /////////////////////
        // main loop for model update and relocation
        /////////////////////

        int model_update_step = 0;
        int relocation_step = 0;

        for (int i_loop = 0; i_loop < IP.get_max_loop_mode0(); i_loop++){
            /////////////////////
            // stage 1, update model parameters
            /////////////////////

            for (int one_loop_i_inv = 0; one_loop_i_inv < IP.get_model_update_N_iter(); one_loop_i_inv++){
                // the actural step index of model update
                int i_inv = i_loop * IP.get_model_update_N_iter() + one_loop_i_inv;

                if(myrank == 0 && id_sim ==0){
                    std::cout   << std::endl;
                    std::cout   << "loop " << i_loop+1 << ", model update iteration " << one_loop_i_inv+1
                                << " ( the " << i_inv+1 << "-th model update) starting ... " << std::endl;
                    std::cout   << std::endl;
                }


                // prepare inverstion iteration group in xdmf file
                io.prepare_grid_inv_xdmf(i_inv);

                ///////////////////////////////////////////////////////
                // run (forward and adjoint) simulation for each source
                ///////////////////////////////////////////////////////

                // run forward and adjoint simulation and calculate current objective function value and sensitivity kernel for all sources
                line_search_mode = false;
                // skip for the mode with sub-iteration
                if (i_inv > 0 && optim_method != GRADIENT_DESCENT) {
                } else {
                    bool is_save_T = false;
                    v_obj_misfit = run_simulation_one_step(IP, grid, io, i_inv, first_src, line_search_mode, is_save_T);
                    v_obj = v_obj_misfit[0];
                }

                // wait for all processes to finish
                synchronize_all_world();

                // check if v_obj is nan
                if (std::isnan(v_obj)) {
                    if (myrank == 0)
                        std::cout << "v_obj is nan, stop inversion" << std::endl;
                    // stop inversion
                    break;
                }

                ///////////////
                // model update
                ///////////////

                if (optim_method == GRADIENT_DESCENT)
                    model_optimize(IP, grid, io, i_inv, v_obj, old_v_obj, first_src, out_main);
                else if (optim_method == HALVE_STEPPING_MODE)
                    v_obj_misfit = model_optimize_halve_stepping(IP, grid, io, i_inv, v_obj, first_src, out_main);
                else if (optim_method == LBFGS_MODE) {
                    bool found_next_step = model_optimize_lbfgs(IP, grid, io, i_inv, v_obj, first_src, out_main);

                    if (!found_next_step)
                        break;
                }

                // define old_v_obj
                old_v_obj = v_obj;

                // output objective function
                write_objective_function(IP, i_inv, v_obj_misfit, out_main, "model update");

                // since model is update. The written traveltime field should be discraded.
                // initialize is_T_written_into_file
                for (int i_src = 0; i_src < IP.n_src_this_sim_group; i_src++){
                    const std::string name_sim_src = IP.get_src_name(i_src);

                    if (proc_store_srcrec) // only proc_store_srcrec has the src_map object
                        IP.src_map[name_sim_src].is_T_written_into_file = false;
                }

                // output updated model
                if (id_sim==0) {
                    //io.change_xdmf_obj(0); // change xmf file for next src
                    io.change_group_name_for_model();

                    // write out model info
                    if (IP.get_if_output_in_process() || i_inv >= IP.get_max_loop_mode0()*IP.get_model_update_N_iter() - 2){
                        io.write_vel(grid, i_inv+1);
                        io.write_xi( grid, i_inv+1);
                        io.write_eta(grid, i_inv+1);
                    }
                    //io.write_zeta(grid, i_inv); // TODO

                    if (IP.get_verbose_output_level()){
                        io.write_a(grid,   i_inv+1);
                        io.write_b(grid,   i_inv+1);
                        io.write_c(grid,   i_inv+1);
                        io.write_f(grid,   i_inv+1);
                        io.write_fun(grid, i_inv+1);
                    }

                    // // output model_parameters_inv_0000.dat
                    // if (IP.get_if_output_model_dat()
                    // && (IP.get_if_output_in_process() || i_inv >= IP.get_max_loop_mode0()*IP.get_model_update_N_iter() - 2))
                    //     io.write_concerning_parameters(grid, i_inv + 1, IP);

                } // end output updated model

                // writeout temporary xdmf file
                io.update_xdmf_file();

                // write src rec files
                if (IP.get_if_output_in_process_data()){
                    IP.write_src_rec_file(model_update_step,relocation_step);
                } else if (i_inv == IP.get_max_loop_mode0() * IP.get_model_update_N_iter()-1 || i_inv==0) {
                    IP.write_src_rec_file(model_update_step,relocation_step);
                }
                model_update_step += 1;

                // wait for all processes to finish
                synchronize_all_world();

                // output current state of the model
                if (IP.get_if_output_final_model()) {
                    // io.write_final_model(grid, IP);
                    io.write_merged_model(grid, IP, "final_model.h5");
                }
                if (IP.get_if_output_middle_model()) {
                    std::string tmp_fname = "middle_model_step_" + int2string_zero_fill(i_inv + 1) + ".h5";
                    io.write_merged_model(grid, IP, tmp_fname);
                }

            }   // end model update in one loop


            /////////////////////
            // stage 2, update earthquake locations
            /////////////////////

            if(myrank == 0 && id_sim ==0){
                std::cout   << std::endl;
                std::cout   << "loop " << i_loop+1 << ", computing traveltime field for relocation" << std::endl;
                std::cout   << std::endl;
            }

            // calculate traveltime for each receiver (swapped from source) and write in output file
            if (IP.get_relocation_N_iter() > 0) // we really do relocation
                calculate_traveltime_for_all_src_rec(IP, grid, io);


            // initilize all earthquakes
            if (proc_store_srcrec){
                for(auto iter = IP.rec_map.begin(); iter != IP.rec_map.end(); iter++){
                    iter->second.is_stop = false;
                }
            }

            // iterate
            for (int one_loop_i_iter = 0; one_loop_i_iter < IP.get_relocation_N_iter(); one_loop_i_iter++){
                int i_iter = i_loop * IP.get_relocation_N_iter() + one_loop_i_iter;

                if(myrank == 0 && id_sim ==0){
                    std::cout   << std::endl;
                    std::cout   << "loop " << i_loop+1 << ", relocation iteration " << one_loop_i_iter+1
                                << " ( the " << i_iter+1 << "-th relocation) starting ... " << std::endl;
                    std::cout   << std::endl;
                }


                // calculate gradient of objective function at sources
                v_obj_misfit = calculate_gradient_objective_function(IP, grid, io, i_iter);
                v_obj = v_obj_misfit[0];

                // update source location
                recs.update_source_location(IP, grid);

                synchronize_all_world();


                // output location information
                if(id_sim == 0 && myrank == 0){
                    std::cout << "objective function: "              << v_obj << std::endl;
                }

                // write objective functions
                write_objective_function(IP, i_iter, v_obj_misfit, out_main, "relocation");

                // write out new src_rec_file
                if (IP.get_if_output_in_process_data()){
                    IP.write_src_rec_file(model_update_step,relocation_step);
                } else if (i_iter == IP.get_max_loop_mode0() * IP.get_relocation_N_iter()-1 || i_iter==0) {
                    IP.write_src_rec_file(model_update_step,relocation_step);
                }

                // modify the receiver's location for output
                IP.modify_swapped_source_location();

                relocation_step += 1;
            } // end relocation loop

            grid.rejuvenate_abcf();     // (a,b/r^2,c/(r^2*cos^2),f/(r^2*cos)) -> (a,b,c,f)

            // estimate running time
            CUSTOMREAL time_elapsed = timer.get_t();
            if (id_sim == 0 && myrank == 0 && i_loop < IP.get_max_loop_mode0()-1) {
                const time_t end_time_estimated = time_elapsed / (i_loop + 1) * (IP.get_max_loop_mode0() - i_loop - 1) + timer.get_start();
                auto will_run_time = (int)(time_elapsed/(i_loop + 2) * (IP.get_max_loop_mode0() - i_loop - 1));

                std::cout << std::endl;
                std::cout << "The program begins at " << timer.get_start_t() << std::endl;
                std::cout << "Loop (" << i_loop + 1 << "/" << IP.get_max_loop_mode0() << ") finished at " << time_elapsed << " seconds" << std::endl;
                std::cout << i_loop + 1 << " loop run " << timer.get_t() << " seconds, the rest of " << IP.get_max_loop_mode0() - i_loop - 1 << " iterations require " << will_run_time << " seconds." << std::endl;
                std::cout << "The program is estimated to stop at " << timer.get_utc_from_time_t(end_time_estimated) << std::endl;
                std::cout << std::endl;
            }

        } // end loop for model update and relocation
    } else if (inv_mode == SIMULTANEOUS) {

        for (int i_loop = 0; i_loop < IP.get_max_loop_mode1(); i_loop++){

            if(myrank == 0 && id_sim ==0){
                std::cout   << std::endl;
                std::cout   << "loop " << i_loop+1 << ", model update and relocation starting ... " << std::endl;
                std::cout   << std::endl;
            }

            /////////////////////
            // stage 1, update model parameters
            /////////////////////


            // prepare inversion iteration group in xdmf file
            io.prepare_grid_inv_xdmf(i_loop);

                ///////////////////////////////////////////////////////
                // run (forward and adjoint) simulation for each source
                ///////////////////////////////////////////////////////

            // run forward and adjoint simulation and calculate current objective function value and sensitivity kernel for all sources
            line_search_mode = false;
            // skip for the mode with sub-iteration
            if (i_loop > 0 && optim_method != GRADIENT_DESCENT) {
            } else {
                bool is_save_T = true;
                v_obj_misfit = run_simulation_one_step(IP, grid, io, i_loop, first_src, line_search_mode, is_save_T);
                v_obj = v_obj_misfit[0];
            }

            // wait for all processes to finish
            synchronize_all_world();

            // check if v_obj is nan
            if (std::isnan(v_obj)) {
                if (myrank == 0)
                    std::cout << "v_obj is nan, stop inversion" << std::endl;

                // stop inversion
                break;
            }

                ///////////////
                // model update
                ///////////////

            if (optim_method == GRADIENT_DESCENT)
                model_optimize(IP, grid, io, i_loop, v_obj, old_v_obj, first_src, out_main);
            else if (optim_method == HALVE_STEPPING_MODE)
                v_obj_misfit = model_optimize_halve_stepping(IP, grid, io, i_loop, v_obj, first_src, out_main);
            else if (optim_method == LBFGS_MODE) {
                bool found_next_step = model_optimize_lbfgs(IP, grid, io, i_loop, v_obj, first_src, out_main);

                if (!found_next_step)
                    break;
            }


            // output objective function (model update part)
            write_objective_function(IP, i_loop, v_obj_misfit, out_main, "model update");

            /////////////////////
            // stage 2, update earthquake locations
            /////////////////////


            // initilize all earthquakes
            if (proc_store_srcrec){
                for(auto iter = IP.rec_map.begin(); iter != IP.rec_map.end(); iter++){
                    iter->second.is_stop = false;
                }
            }

            // calculate gradient of objective function at sources
            v_obj_misfit = calculate_gradient_objective_function(IP, grid, io, i_loop);

            // update source location
            recs.update_source_location(IP, grid);

            synchronize_all_world();

            // output objective function (relocation part)
            write_objective_function(IP, i_loop, v_obj_misfit, out_main, "relocation");


            // define old_v_obj
            old_v_obj   = v_obj;


            // since model is update. The written traveltime field should be discraded.
            // initialize is_T_written_into_file
            for (int i_src = 0; i_src < IP.n_src_this_sim_group; i_src++){
                const std::string name_sim_src = IP.get_src_name(i_src);

                if (proc_store_srcrec) // only proc_store_srcrec has the src_map object
                    IP.src_map[name_sim_src].is_T_written_into_file = false;
            }

            // output updated model
            if (id_sim==0) {
                //io.change_xdmf_obj(0); // change xmf file for next src
                io.change_group_name_for_model();

                // write out model info
                if (IP.get_if_output_in_process() || i_loop >= IP.get_max_loop_mode1() - 2){
                    io.write_vel(grid, i_loop+1);
                    io.write_xi( grid, i_loop+1);
                    io.write_eta(grid, i_loop+1);
                }
                //io.write_zeta(grid, i_inv); // TODO

                if (IP.get_verbose_output_level()){
                    io.write_a(grid,   i_loop+1);
                    io.write_b(grid,   i_loop+1);
                    io.write_c(grid,   i_loop+1);
                    io.write_f(grid,   i_loop+1);
                    io.write_fun(grid, i_loop+1);
                }

                // // output model_parameters_inv_0000.dat
                // if (IP.get_if_output_model_dat()
                // && (IP.get_if_output_in_process() || i_loop >= IP.get_max_loop_mode1() - 2))
                //     io.write_concerning_parameters(grid, i_loop + 1, IP);

            } // end output updated model

            // writeout temporary xdmf file
            io.update_xdmf_file();

            // write src rec files
            if (IP.get_if_output_in_process_data()){
                IP.write_src_rec_file(i_loop,i_loop);
            } else if (i_loop == IP.get_max_loop_mode1() -1 || i_loop==0) {
                IP.write_src_rec_file(i_loop,i_loop);
            }

            // modify the receiver's location for output
            IP.modify_swapped_source_location();

            // wait for all processes to finish
            synchronize_all_world();

            grid.rejuvenate_abcf();     // (a,b/r^2,c/(r^2*cos^2),f/(r^2*cos)) -> (a,b,c,f)

            // estimate running time
            CUSTOMREAL time_elapsed = timer.get_t();
            if (id_sim == 0 && myrank == 0 && i_loop < IP.get_max_loop_mode1()-1) {
                const time_t end_time_estimated = time_elapsed / (i_loop + 1) * (IP.get_max_loop_mode1() - i_loop - 1) + timer.get_start();
                auto will_run_time = (int)(time_elapsed/(i_loop + 2) * (IP.get_max_loop_mode1() - i_loop - 1));

                std::cout << std::endl;
                std::cout << "The program begins at " << timer.get_start_t() << std::endl;
                std::cout << "Loop (" << i_loop + 1 << "/" << IP.get_max_loop_mode1() << ") finished at " << time_elapsed << " seconds" << std::endl;
                std::cout << i_loop + 1 << " loop run " << timer.get_t() << " seconds, the rest of " << IP.get_max_loop_mode1() - i_loop - 1 << " iterations require " << will_run_time << " seconds." << std::endl;
                std::cout << "The program is estimated to stop at " << timer.get_utc_from_time_t(end_time_estimated) << std::endl;
                std::cout << std::endl;
            }

            // output current state of the model
            if (IP.get_if_output_final_model()) {
                // io.write_final_model(grid, IP);
                io.write_merged_model(grid, IP, "final_model.h5");
            }
            if (IP.get_if_output_middle_model()) {
                std::string tmp_fname = "middle_model_step_" + int2string_zero_fill(i_loop + 1) + ".h5";
                io.write_merged_model(grid, IP, tmp_fname);
            }
        } // end loop for model update and relocation

    } else {
        std::cout << "Error: inv_mode is not defined" << std::endl;
        exit(1);
    }
    // close xdmf file
    io.finalize_data_output_file();

    timer.stop_timer();
    if (id_sim == 0 && myrank == 0) {
        std::cout << std::endl;
        std::cout << "The program begin at " << timer.get_start_t();
        std::cout << "Inv_and_reloc end at " << timer.get_end_t();
        std::cout << "It has run " << timer.get_elapsed_t() << " seconds in total." << std::endl;
        std::cout << std::endl;
    }

}


// run 1D inversion mode
inline void run_1d_inversion(InputParams& IP, Grid& grid, IO_utils& io) {
    OneDInversion oneDInv(IP, grid);

    if(myrank == 0)
        std::cout << "id_sim: " << id_sim << ", size of src_map: " << IP.src_map.size() << std::endl;

    // estimate running time
    Timer timer("1D_inversion", true);

    // prepare objective_function file
    std::ofstream out_main; // close() is not mandatory
    prepare_header_line(IP, out_main);

    synchronize_all_world();

    /////////////////////
    // loop for inversion
    /////////////////////

    // objective function for all src
    std::vector<CUSTOMREAL> v_obj_misfit(20, 0.0);

    for (int i_inv = 0; i_inv < IP.get_max_iter_inv(); i_inv++) {
        if(myrank == 0 && id_sim ==0){
            std::cout << "iteration " << i_inv << " starting ... " << std::endl;
        }

        // prepare inverstion iteration group in xdmf file
        io.prepare_grid_inv_xdmf(i_inv);

        ///////////////////////////////////////////////////////
        // run (forward and adjoint) simulation for each source
        ///////////////////////////////////////////////////////
        v_obj_misfit = oneDInv.run_simulation_one_step_1dinv(IP, io, i_inv);

        // wait for all processes to finish
        synchronize_all_world();

        // check if v_obj is nan
        if (std::isnan(v_obj_misfit[0])) {
            if (myrank == 0)
                std::cout << "v_obj is nan, stop inversion" << std::endl;
            // stop inversion
            break;
        }

        // output src rec file with the result arrival times
        if (IP.get_if_output_in_process_data()){
            IP.write_src_rec_file(i_inv,0);
        } else if (i_inv == IP.get_max_iter_inv()-1 || i_inv==0) {
            IP.write_src_rec_file(i_inv,0);
        }

        ///////////////
        // model update
        ///////////////
        if(myrank == 0 && id_sim ==0)
            std::cout << "model update starting ... " << std::endl;
        oneDInv.model_optimize_1dinv(IP, grid, io, i_inv);

        // output objective function
        write_objective_function(IP, i_inv, v_obj_misfit, out_main, "1d inversion");

        // output updated model
        if (id_sim==0) {
            //io.change_xdmf_obj(0); // change xmf file for next src
            io.change_group_name_for_model();

            // write out model info
            if (IP.get_if_output_in_process() || i_inv >= IP.get_max_iter_inv() - 2){
                io.write_vel(grid, i_inv+1);
                io.write_xi( grid, i_inv+1);
                io.write_eta(grid, i_inv+1);
            }
        }

        // writeout temporary xdmf file
        io.update_xdmf_file();

        // wait for all processes to finish
        synchronize_all_world();

        // estimate running time
        CUSTOMREAL time_elapsed = timer.get_t();
        if (id_sim == 0 && myrank == 0 && i_inv < IP.get_max_iter_inv()-1) {
            const time_t end_time_estimated = time_elapsed / (i_inv + 1) * (IP.get_max_iter_inv() - i_inv - 1) + timer.get_start();
            auto will_run_time = (int)(time_elapsed/(i_inv + 1) * (IP.get_max_iter_inv() - i_inv - 1));

            std::cout << std::endl;
            std::cout << "The program begins at " << timer.get_start_t() << std::endl;
            std::cout << "Iteration (" << i_inv + 1 << "/" << IP.get_max_iter_inv() << ") finished at " << time_elapsed << " seconds" << std::endl;
            std::cout << i_inv + 1 << " iterations run " << timer.get_t() << " seconds, the rest of " << IP.get_max_iter_inv() - i_inv - 1 << " iterations require " << will_run_time << " seconds." << std::endl;
            std::cout << "The program is estimated to stop at " << timer.get_utc_from_time_t(end_time_estimated) << std::endl;
            std::cout << std::endl;
        }

        // output current state of the model
        if (IP.get_if_output_final_model()) {
            // io.write_final_model(grid, IP);
            io.write_merged_model(grid, IP, "final_model.h5");
        }
        if (IP.get_if_output_middle_model()) {
            std::string tmp_fname = "middle_model_step_" + int2string_zero_fill(i_inv + 1) + ".h5";
            io.write_merged_model(grid, IP, tmp_fname);
        }

        // wait for all processes to finish
        synchronize_all_world();
    } // end loop inverse

    // close xdmf file
    io.finalize_data_output_file();

    timer.stop_timer();
    if (id_sim == 0 && myrank == 0) {
        std::cout << std::endl;
        std::cout << "The program begin at " << timer.get_start_t() << std::endl;
        std::cout << "1d model inversion ended at " << timer.get_end_t() << std::endl;
        std::cout << "It has run " << timer.get_elapsed_t() << " seconds in total." << std::endl;
        std::cout << std::endl;
    }
}

#endif // MAIN_ROUTINES_CALLING_H