/******************************************************************************
 * @internal
 * @file        rCFD_free.h
 * @copyright   (C) 2021
 *              Particulate Flow Modelling,
 *              Johannes Kepler University, Linz, Austria
 * @website     www.particulate-flow.at
 * @authors     Stefan Pirker
 *****************************************************************************/

#ifndef RCFD_FREE
#define RCFD_FREE

#include "rCFD_types.h"
#include "rCFD_macros.h"

void free_report(rCFD_type *rcfd);
void free_parallel(rCFD_type *rcfd);
void free_user(rCFD_type *rcfd);


void free_rcfd(rCFD_type *rcfd)
{
    free_report(rcfd);
    free_parallel(rcfd);
    free_user(rcfd);

    free(rcfd);
}

void free_shift(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->C2Cs != NULL){

        int i_state, i_phase, i_frame, i_node, i_layer;

        for(i_state = 0; i_state < rcfd->Solver_Dict->number_of_states; i_state++){

            if(rcfd->C2Cs[i_state] != NULL){

                for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

                    if(rcfd->C2Cs[i_state][i_phase] != NULL){

                        for(i_frame = 0; i_frame < rcfd->Solver_Dict->number_of_frames; i_frame++){

                            free(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts);
                            free(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_in);
                            free(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_out);

                            if(rcfd->C2Cs[i_state][i_phase][i_frame].shifts != NULL){

                                for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                                    free(rcfd->C2Cs[i_state][i_phase][i_frame].shifts[i_layer]);
                                }

                                free(rcfd->C2Cs[i_state][i_phase][i_frame].shifts);
                            }

                            if(rcfd->C2Cs[i_state][i_phase][i_frame].shifts_in != NULL){

                                for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                                    free(rcfd->C2Cs[i_state][i_phase][i_frame].shifts_in[i_layer]);
                                }

                                free(rcfd->C2Cs[i_state][i_phase][i_frame].shifts_in);
                            }

                            if(rcfd->C2Cs[i_state][i_phase][i_frame].shifts_out != NULL){

                                for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                                    free(rcfd->C2Cs[i_state][i_phase][i_frame].shifts_out[i_layer]);
                                }

                                free(rcfd->C2Cs[i_state][i_phase][i_frame].shifts_out);
                            }

                            free_i_2d(rcfd->C2Cs[i_state][i_phase][i_frame].island_offsets);
                            free_i_2d(rcfd->C2Cs[i_state][i_phase][i_frame].island_offsets_in);

                            if(myid == 0){

                                if(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_to_node_zero != NULL){

                                    for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                                        free(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_to_node_zero[i_layer]);
                                    }

                                    free(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_to_node_zero);
                                }

                                if(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_from_node_zero != NULL){

                                    for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                                        free(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_from_node_zero[i_layer]);
                                    }

                                    free(rcfd->C2Cs[i_state][i_phase][i_frame].number_of_shifts_from_node_zero);
                                }

                                if(rcfd->C2Cs[i_state][i_phase][i_frame].in2out != NULL){

                                    for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                                        if(rcfd->C2Cs[i_state][i_phase][i_frame].in2out[i_layer] != NULL){

                                            for(i_node = 0; i_node < compute_node_count; i_node++){

                                                free(rcfd->C2Cs[i_state][i_phase][i_frame].in2out[i_layer][i_node]);
                                            }

                                            free(rcfd->C2Cs[i_state][i_phase][i_frame].in2out[i_layer]);
                                        }
                                    }

                                    free(rcfd->C2Cs[i_state][i_phase][i_frame].in2out);
                                }
                            }
                        }

                        free(rcfd->C2Cs[i_state][i_phase]);
                    }
                }

                free(rcfd->C2Cs[i_state]);
            }
        }

        free(rcfd->C2Cs);
        rcfd->C2Cs = NULL;
    }
#endif
}

void free_balance(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Balance != NULL){

        int i_phase, i_data;

        for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

            if(rcfd->Balance[i_phase] != NULL){

                for(i_data = 0; i_data < rcfd->Phase_Dict[i_phase].number_of_data; i_data++){

                    free_r_2d(rcfd->Balance[i_phase][i_data].node2node_flux);

                    free_r_2d(rcfd->Balance[i_phase][i_data].node2node_data_flux);
                }

                free(rcfd->Balance[i_phase]);
            }
        }

        free(rcfd->Balance);
        rcfd->Balance = NULL;
    }
#endif
}

void free_norms(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Norms != NULL){

        free(rcfd->Norms->norm);

        free(rcfd->Norms);
        rcfd->Norms = NULL;
    }
#endif
}

void free_rec(rCFD_type *rcfd)
{
    if(rcfd->Rec != NULL){

#ifdef RCFD_LIB_ISLANDS
        free(rcfd->Rec->global_frame_of_island);

        free(rcfd->Rec->prev_global_frame_of_island);
#endif

        free_i_4d(rcfd->Rec->jumps);
		
        free(rcfd->Rec->frame_id);
		
        if((rcfd->Solver_Dict->mode == RCFD_MODE_RUN) && (myid == 0)){

            free(rcfd->Rec->frame_id_means);
			free(rcfd->Rec->frame_id_vars);

			free_r_2d(rcfd->Rec->frame_id_database);
        }

        free(rcfd->Rec);
        rcfd->Rec = NULL;
    }
}

void free_tracer(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Tracer != NULL){

        free(rcfd->Tracer->monitor_counter);

        free(rcfd->Tracer->number_of_shifts);

        if(rcfd->Tracer->shifts != NULL){

            int i_phase;

            for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

                free(rcfd->Tracer->shifts[i_phase]);
            }

            free(rcfd->Tracer->shifts);
        }

        free(rcfd->Tracer);
        rcfd->Tracer = NULL;
    }
#endif
}

void free_topo(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Topo != NULL){

        int i_phase, i_cell, i_layer;

        if(rcfd->Topo->Cell != NULL){

            for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                free_r_2d(_C(i_layer).x);

                free(_C(i_layer).volume);

                free(_C(i_layer).grid_spacing);

                free(_C(i_layer).number_of_neigh_cells);
                free(_C(i_layer).number_of_next_neigh_cells);

                if(_C(i_layer).neigh_cell != NULL){

                    for(i_cell = 0; i_cell < _CELL_DICT(i_layer).number_of_cells; i_cell++){

                        free(_C(i_layer).neigh_cell[i_cell]);
                    }
                }

                if(_C(i_layer).next_neigh_cell != NULL){

                    for(i_cell = 0; i_cell < _CELL_DICT(i_layer).number_of_cells; i_cell++){

                        free(_C(i_layer).next_neigh_cell[i_cell]);
                    }
                }

                free(_C(i_layer).neigh_cell);
                free(_C(i_layer).next_neigh_cell);

                free_r_2d(_C(i_layer).average_velocity);

                free_r_2d(_C(i_layer).crossing_time);

                free(_C(i_layer).hit_by_other_cell);

                free(_C(i_layer).island_id);

                free(_C(i_layer).weight_after_shift);

                free(_C(i_layer).weight_after_swap);

                free_r_3d(_C(i_layer).vof);

                free_r_2d(_C(i_layer).vof_changed);

                if(_C(i_layer).data != NULL){

                    for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

                        free_r_2d(_C(i_layer).data[i_phase]);
                    }

                    free(_C(i_layer).data);
                }

                if(_C(i_layer).data_shift != NULL){

                    for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

                        free_r_2d(_C(i_layer).data_shift[i_phase]);
                    }

                    free(_C(i_layer).data_shift);
                }

                if(_C(i_layer).data_swap != NULL){

                    for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

                        free_r_2d(_C(i_layer).data_swap[i_phase]);
                    }

                    free(_C(i_layer).data_swap);
                }

                free_r_3d(_C(i_layer).local_diff);

                free(_C(i_layer).drift_exchange);

                free_r_3d(_C(i_layer).cfd_displ);

                free_r_3d(_C(i_layer).cfd_diff);

                free(_C(i_layer).tmp);

                free_r_2d(_C(i_layer).user);

                free_r_3d(_C(i_layer).rec_user);

                free(_C(i_layer).marked);

                free(_C(i_layer).parent_cell);

                free(_C(i_layer).number_of_children);

                if(_C(i_layer).child_index != NULL){

                    for(i_cell = 0; i_cell < _CELL_DICT(i_layer).number_of_cells; i_cell++){

                        free(_C(i_layer).child_index[i_cell]);
                    }

                    free(_C(i_layer).child_index);
                }

                free(_C(i_layer).type);

                free(_C(i_layer).number_of_nodes);

                free_i_2d(_C(i_layer).nodes);

                free(_C(i_layer).number_of_poly_faces);

                free_i_2d(_C(i_layer).number_of_poly_face_nodes);

                free(_C(i_layer).poly_nodes);
            }

            free(rcfd->Topo->Cell);
        }

        if(rcfd->Topo->Face != NULL){

            for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                free(_F(i_layer).c0);

                free(_F(i_layer).c1);

                free_r_2d(_F(i_layer).area);

                if(rcfd->Solver_Dict->data_drifting_on){

                    free_r_2d(_F(i_layer).local_drift_adaption);
                }
            }

            free(rcfd->Topo->Face);
        }

        if(rcfd->Topo->Node != NULL){

            for(i_layer = 0; i_layer < rcfd->Solver_Dict->number_of_layers; i_layer++){

                free_r_2d(_N(i_layer).x);
            }
        }

        free(rcfd->Topo);
        rcfd->Topo = NULL;
    }
#endif
}

void free_dump_dict(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Dump_Dict != NULL){

        if(rcfd->Dump_Dict->dump_cell_data){

            int i_phase;

            for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

                free(rcfd->Dump_Dict->dump_cell_data[i_phase]);
            }

            free(rcfd->Dump_Dict->dump_cell_data);
        }

        free(rcfd->Dump_Dict->dump_user_data);
        free(rcfd->Dump_Dict->dump_rec_user_data);

        free(rcfd->Dump_Dict->probe_layer);
        free(rcfd->Dump_Dict->probe_cell);
        free(rcfd->Dump_Dict->probe_type);
        free(rcfd->Dump_Dict->probe_phase);
        free(rcfd->Dump_Dict->probe_data);
        free(rcfd->Dump_Dict->probe_value);
        free_r_2d(rcfd->Dump_Dict->probe_x);

        free(rcfd->Dump_Dict);
        rcfd->Dump_Dict = NULL;
    }
#endif
}

void free_topo_dict(rCFD_type *rcfd)
{
    if(rcfd->Topo_Dict != NULL){

#if RP_NODE
        free(rcfd->Topo_Dict->Cell_Dict);

        free(rcfd->Topo_Dict->Face_Dict);

        free(rcfd->Topo_Dict->Node_Dict);
#endif
        free(rcfd->Topo_Dict);
        rcfd->Topo_Dict = NULL;
    }
}

void free_balance_dict(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Balance_Dict != NULL){

        int i_phase;

        for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

            free(rcfd->Balance_Dict[i_phase]);
        }

        free(rcfd->Balance_Dict);
        rcfd->Balance_Dict = NULL;
    }
#endif
}

void free_data_dict(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Data_Dict != NULL){

        int i_phase, i_data;

        for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

            if(rcfd->Data_Dict[i_phase] != NULL){

                for(i_data = 0; i_data < rcfd->Phase_Dict[i_phase].number_of_data; i_data++){

                    free(rcfd->Data_Dict[i_phase][i_data].drift_velocity);

                    free(rcfd->Data_Dict[i_phase][i_data].user);
                }

                free(rcfd->Data_Dict[i_phase]);
            }
        }

        free(rcfd->Data_Dict);
        rcfd->Data_Dict = NULL;
    }
#endif
}

void free_rec_dict(rCFD_type *rcfd)
{
    free(rcfd->Rec_Dict);
    rcfd->Rec_Dict = NULL;
}

void free_norm_dict(rCFD_type *rcfd)
{
#if RP_NODE
    free(rcfd->Norm_Dict);
    rcfd->Norm_Dict = NULL;
#endif
}

void free_calibration_dict(rCFD_type *rcfd)
{
#if RP_NODE
    free(rcfd->Calibration_Dict);
    rcfd->Calibration_Dict = NULL;
#endif
}

void free_tracer_dict(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Tracer_Dict){
        free(rcfd->Tracer_Dict->random_walk);

        free(rcfd->Tracer_Dict->random_walk_velocity_ratio);

        free(rcfd->Tracer_Dict);
        rcfd->Tracer_Dict = NULL;
    }
#endif

}

void free_phase_dict(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->Phase_Dict != NULL){

        int i_phase;

        for(i_phase = 0; i_phase < rcfd->Solver_Dict->number_of_phases; i_phase++){

            free(rcfd->Phase_Dict[i_phase].user);
        }

        free(rcfd->Phase_Dict);
        rcfd->Phase_Dict    = NULL;
    }
#endif
}

void free_solver(rCFD_type *rcfd)
{
    if(rcfd->Solver != NULL){

        free(rcfd->Solver->timestep_width);

        free(rcfd->Solver);
        rcfd->Solver = NULL;
    }
}

void free_file_dict(rCFD_type *rcfd)
{
#if RP_NODE
    if(rcfd->File_Dict != NULL){

        free(rcfd->File_Dict->tracer_start_position_filename);
        free(rcfd->File_Dict->C2C_filename);
        free(rcfd->File_Dict->Prep_Transscript_filename);
        free(rcfd->File_Dict->Run_Transscript_filename);
        free(rcfd->File_Dict->Norm_filename);
        free(rcfd->File_Dict->vof_filename);
        free(rcfd->File_Dict->local_diff_filename);
        free(rcfd->File_Dict->rec_user_filename);
        free(rcfd->File_Dict->Jump_filename);
        free(rcfd->File_Dict->Matrix_filename);
        free(rcfd->File_Dict->Balance_filename);
        free(rcfd->File_Dict->Rec_Frames_filename);
        free(rcfd->File_Dict->Topo_filename);
        free(rcfd->File_Dict->Dump_filename);
        free(rcfd->File_Dict->Probe_filename);

        free(rcfd->File_Dict);
        rcfd->File_Dict = NULL;
    }
#endif
}

void free_solver_dict(rCFD_type *rcfd)
{
    free(rcfd->Solver_Dict);
    rcfd->Solver_Dict = NULL;
}

void free_report(rCFD_type *rcfd)
{
    if(rcfd->Report != NULL){

        int i;

        if(rcfd->Report->file_is_open) {

            fclose(rcfd->Report->report_file);
            rcfd->Report->report_file = NULL;
            rcfd->Report->file_is_open = 0;
        }

        if(rcfd->Report->file0_is_open) {

#if defined(RCFD_LIB_MPI)
            MPI_File_close(&rcfd->Report->report_file0);
#else
            fclose(rcfd->Report->report_file0);
            rcfd->Report->report_file0 = NULL;
#endif
            rcfd->Report->file0_is_open = 0;
        }

        free_i_2d(rcfd->Report->number_of_reports);

        for(i = 0; i < RCFD_TIMER_MAX; ++i){

            unit_timer_destroy(rcfd->Report->timers[i]);
        }

        free(rcfd->Report);
        rcfd->Report = NULL;
    }
}

void free_parallel(rCFD_type *rcfd)
{
    free(rcfd->Parallel);
    rcfd->Parallel = NULL;
}

void free_user(rCFD_type *rcfd)
{
    free(rcfd->rCFD_UDF);
    rcfd->rCFD_UDF = NULL;
}


void free_all(rCFD_type *rcfd)
{
    rcfd->initialized   = 0;

    free_shift(rcfd);

    parallel_free_all(rcfd);

    free_balance(rcfd);

    free_norms(rcfd);

    free_rec(rcfd);

    free_tracer(rcfd);

    free_dump_dict(rcfd);

    free_topo(rcfd);

    free_topo_dict(rcfd);

    free_balance_dict(rcfd);

    free_data_dict(rcfd);

    free_rec_dict(rcfd);

    free_norm_dict(rcfd);

    free_calibration_dict(rcfd);

    free_tracer_dict(rcfd);

    free_phase_dict(rcfd);

    free_solver(rcfd);

    free_solver_dict(rcfd);

    free_file_dict(rcfd);
}

#endif
