/*

  This program is part of the TACLeBench benchmark suite.
  Version V 2.0

  Name: audiobeam

  Author: Eugene Weinstein

  Function: Audio beam former

  Source: StreamIt
          http://groups.csail.mit.edu/cag/streamit/

  Changes: no functional changes

  License: see license.txt

*/

/*
  Include section
*/

#include "audiobeam.h"
#include "audiobeamlibm.h"
#include "audiobeamlibmalloc.h"

/*
  Forward declaration of functions
*/

// Wasm loop bounds

__attribute__((import_module("__pragma"), import_name("loopbound"))) extern void
__pragma_loopbound(unsigned int min_bound, unsigned int max_bound);

void audiobeam_init();
int audiobeam_return();
__attribute__((noinline)) __attribute__((export_name("entrypoint"))) void
audiobeam_main(void);
__attribute__((noinline)) __attribute__((export_name("main"))) int main(void);
void
audiobeam_preprocess_delays(struct audiobeam_PreprocessedDelays prep_delays[],
                            float *delays);
float *audiobeam_parse_line(float *float_arr, int num_mic);
long int audiobeam_find_max_in_arr(float *arr, int size);
long int audiobeam_find_min_in_arr(float *arr, int size);
int audiobeam_wrapped_inc_offset(int i, int offset, int max_i);
int audiobeam_wrapped_dec_offset(int i, int offset, int max_i);
int audiobeam_wrapped_inc(int i, int max_i);
int audiobeam_wrapped_dec(int i, int max_i);
struct audiobeam_DataQueue *audiobeam_init_data_queue(int max_delay,
                                                      int num_mic);
struct audiobeam_Delays *audiobeam_init_delays(int num_angles, int num_mic);
void audiobeam_calc_distances(float *source_location,
                              float audiobeam_mic_locations[15][3],
                              float *distances, int num_mic);
void audiobeam_calc_delays(float *distances, float *delays, int sound_speed,
                           int sampling_rate, int num_mic);
void audiobeam_adjust_delays(float *delays, int num_mic);
float *audiobeam_calc_weights_lr(int num_mic);
float *audiobeam_calc_weights_left_only(int num_mic);
float audiobeam_calculate_energy(float *samples, int num_samples);
float audiobeam_do_beamforming(
    struct audiobeam_PreprocessedDelays preprocessed_delays[],
    float **sample_queue, int queue_head, long int max_delay, int num_mic,
    float *weights);
int audiobeam_process_signal(struct audiobeam_Delays *delays, int num_mic,
                             float sampling_rate, float **beamform_results,
                             struct audiobeam_DataQueue *queue, int num_beams,
                             int window, float *weights);
int audiobeam_calc_beamforming_result(struct audiobeam_Delays *delays,
                                      float **beamform_results, float *energies,
                                      struct audiobeam_DataQueue *queue,
                                      int num_beams, int window, int hamming);
void audiobeam_calc_single_pos(float source_location[3],
                               float audiobeam_mic_locations[15][3],
                               int hamming);

/*
  Declaration of global variables
*/

extern float audiobeam_input[5760];
extern float audiobeam_mic_locations[15][3];
extern float audiobeam_source_location[3];
extern float audiobeam_origin_location[3];
int audiobeam_input_pos;
int audiobeam_checksum;

/*
  Initialization- and return-value-related functions
*/

void
audiobeam_init() {
    audiobeam_input_pos = 0;
    audiobeam_checksum = 0;

    unsigned int i;
    unsigned char *p;
    volatile char bitmask = 0;

    /*
      Apply volatile XOR-bitmask to entire input array.
    */
    p = (unsigned char *) &audiobeam_input[0];
    __pragma_loopbound(23040, 23040);
    for (i = 0; i < sizeof(audiobeam_input); ++i, ++p)
        *p ^= bitmask;

    p = (unsigned char *) &audiobeam_mic_locations[0];
    __pragma_loopbound(180, 180);
    for (i = 0; i < sizeof(audiobeam_mic_locations); ++i, ++p)
        *p ^= bitmask;

    p = (unsigned char *) &audiobeam_source_location[0];
    __pragma_loopbound(12, 12);
    for (i = 0; i < sizeof(audiobeam_source_location); ++i, ++p)
        *p ^= bitmask;

    p = (unsigned char *) &audiobeam_origin_location[0];
    __pragma_loopbound(12, 12);
    for (i = 0; i < sizeof(audiobeam_origin_location); ++i, ++p)
        *p ^= bitmask;
}

int
audiobeam_return() {
    return (audiobeam_checksum + 1 != 0);
}

/*
  Algorithm core functions
*/

void
audiobeam_preprocess_delays(struct audiobeam_PreprocessedDelays prep_delays[],
                            float *delays) {
    int i;

    __pragma_loopbound(15, 15);
    for (i = 0; i < 15; i++) {
        prep_delays[i].delay = delays[i];
        prep_delays[i].high = (int) audiobeam_ceil(delays[i]);
        prep_delays[i].low = (int) audiobeam_floor(delays[i]);
        prep_delays[i].offset = delays[i] - prep_delays[i].low;
    }
}

float *
audiobeam_parse_line(float *float_arr, int num_mic) {
    int i;

    __pragma_loopbound(15, 15);
    for (i = 0; i < num_mic; i++)
        float_arr[i] = audiobeam_input[audiobeam_input_pos++];

    return float_arr;
}

long int
audiobeam_find_max_in_arr(float *arr, int size) {
    int i;
    float max = 0;

    __pragma_loopbound(15, 15);
    for (i = 0; i < size; i++) {
        if (arr[i] > max)
            max = arr[i];
    }

    return audiobeam_ceil(max);
}

long int
audiobeam_find_min_in_arr(float *arr, int size) {
    int i;
    float min = arr[0];

    __pragma_loopbound(15, 15);
    for (i = 0; i < size; i++) {
        if (arr[i] < min)
            min = arr[i];
    }

    return audiobeam_floor(min);
}

int
audiobeam_wrapped_inc_offset(int i, int offset, int max_i) {
    if (i + offset > max_i)
        return (i + offset - max_i - 1);
    else
        return (i + offset);
}

int
audiobeam_wrapped_dec_offset(int i, int offset, int max_i) {
    if (i - offset < 0)
        return (max_i - (offset - i) + 1);
    else
        return (i - offset);
}

int
audiobeam_wrapped_inc(int i, int max_i) {
    return audiobeam_wrapped_inc_offset(i, 1, max_i);
}

int
audiobeam_wrapped_dec(int i, int max_i) {
    return audiobeam_wrapped_dec_offset(i, 1, max_i);
}

struct audiobeam_DataQueue *
audiobeam_init_data_queue(int max_delay, int num_mic) {
    int i, j;

    struct audiobeam_DataQueue *queue;
    queue = (struct audiobeam_DataQueue *) audiobeam_malloc(
        sizeof(struct audiobeam_DataQueue));
    queue->sample_queue =
        (float **) audiobeam_malloc((max_delay + 1) * sizeof(float *));

    __pragma_loopbound(15, 15);
    for (i = 0; i < (max_delay + 1); i++) {
        (queue->sample_queue)[i] =
            (float *) audiobeam_malloc(num_mic * sizeof(float));
        __pragma_loopbound(15, 15);
        for (j = 0; j < num_mic; j++) {
            (queue->sample_queue)[i][j] = 0.0;   // Initialize values to 0
        }
    }

    queue->head = 0;
    queue->tail = 0;
    queue->full = 0;

    return queue;
}

struct audiobeam_Delays *
audiobeam_init_delays(int num_angles, int num_mic) {
    struct audiobeam_Delays *delays;
    int i;

    delays = (struct audiobeam_Delays *) audiobeam_malloc(
        sizeof(struct audiobeam_Delays));

    // Initialize the delays array
    delays->delay_values =
        (float **) audiobeam_malloc(num_angles * sizeof(float *));

    __pragma_loopbound(1, 1);
    for (i = 0; i < (num_angles); i++) {
        delays->delay_values[i] =
            (float *) audiobeam_malloc(num_mic * sizeof(float));
    }

    return delays;
}

void
audiobeam_calc_distances(float *source_location,
                         float audiobeam_mic_locations[15][3], float *distances,
                         int num_mic) {
    int i;

    __pragma_loopbound(15, 15);
    for (i = 0; i < num_mic; i++) {
        distances[i] = (audiobeam_sqrt(
            (audiobeam_mic_locations[i][0] - source_location[0]) *
                (audiobeam_mic_locations[i][0] - source_location[0]) +
            (audiobeam_mic_locations[i][1] - source_location[1]) *
                (audiobeam_mic_locations[i][1] - source_location[1]) +
            (audiobeam_mic_locations[i][2] - source_location[2]) *
                (audiobeam_mic_locations[i][2] - source_location[2])));
    }
}

void
audiobeam_calc_delays(float *distances, float *delays, int sound_speed,
                      int sampling_rate, int num_mic) {
    int i;

    __pragma_loopbound(15, 15);
    for (i = 0; i < num_mic; i++)
        delays[i] = (distances[i] / sound_speed) * sampling_rate;
}

void
audiobeam_adjust_delays(float *delays, int num_mic) {
    int i;
    long int min_delay = audiobeam_find_min_in_arr(delays, num_mic) - 1;

    __pragma_loopbound(15, 15);
    for (i = 0; i < num_mic; i++)
        delays[i] -= min_delay;
}

float *
audiobeam_calc_weights_lr(int num_mic) {
    float *weights = (float *) audiobeam_malloc(num_mic * sizeof(float));
    int index = 0;
    int y, z;

    int half = num_mic / 4;

    __pragma_loopbound(0, 0);
    for (z = 1; z >= -1; z -= 2) {
        __pragma_loopbound(0, 0);
        for (y = 0; y < half; y++) {
            weights[index] =
                0.54 + 0.46 * audiobeam_cos(audiobeam_M_PI * y / half);
            index++;
        }
        __pragma_loopbound(0, 0);
        for (y = 0; y < half; y++) {
            weights[index] =
                0.54 + 0.46 * audiobeam_cos(audiobeam_M_PI * (-y) / half);
            index++;
        }
    }

    return weights;
}

float *
audiobeam_calc_weights_left_only(int num_mic) {
    float *weights = (float *) audiobeam_malloc(num_mic * sizeof(float));
    int index = 0;
    int y;

    int half = num_mic / 2;

    __pragma_loopbound(15, 15);
    for (y = -half; y <= half; y++) {
        weights[index] = 0.54 + 0.46 * audiobeam_cos(audiobeam_M_PI * y / half);
        index++;
    }

    return weights;
}

float
audiobeam_calculate_energy(float *samples, int num_samples) {
    int i;
    float sum = 0.0;

    __pragma_loopbound(0, 0);
    for (i = 0; i < num_samples; i++)
        sum += (samples[i] * samples[i]);

    return sum;
}

float
audiobeam_do_beamforming(
    struct audiobeam_PreprocessedDelays preprocessed_delays[],
    float **sample_queue, int queue_head, long int max_delay, int num_mic,
    float *weights) {
    int i;
    float sum = 0;
    int delay_floor;
    int delay_ceil;
    int low_index;
    int high_index;
    float interpolated_value;

    // add up all the num_mic delayed samples
    __pragma_loopbound(15, 15);
    for (i = 0; i < num_mic; i++) {
        delay_floor = preprocessed_delays[i].low;
        delay_ceil = preprocessed_delays[i].high;

        // Inline wrap around here
        // Low index gets index of sample right before desired sample
        low_index = queue_head + delay_floor;
        if (low_index > max_delay)
            low_index -= (max_delay + 1);

        // High index gets index of sample right after desired sample
        high_index = queue_head + delay_ceil;
        if (high_index > max_delay)
            high_index -= (max_delay + 1);

        // i gives the value of the microphone we want. However, since
        // the array only has microphones first_mic to last_mic, we
        // need to offset our index by first_mic

        interpolated_value =
            (((sample_queue[high_index][i] - sample_queue[low_index][i]) *
              (preprocessed_delays[i].offset)) +
             sample_queue[low_index][i]);

        // If we have microphone weights, multiply the value by the weight
        if (weights != 0)
            sum += (interpolated_value * weights[i]);
        else
            sum += interpolated_value;
    }

    return sum;
}

int
audiobeam_process_signal(struct audiobeam_Delays *delays, int num_mic,
                         float sampling_rate, float **beamform_results,
                         struct audiobeam_DataQueue *queue, int num_beams,
                         int window, float *weights) {
    int i, j;
    float time_index = 0;
    float time_index_inc = (1.0 / sampling_rate);

    float value;

    int done = 0;

    struct audiobeam_PreprocessedDelays preprocessed_delays[15];

    audiobeam_preprocess_delays(preprocessed_delays, delays->delay_values[0]);

    __pragma_loopbound(13, 13);
    for (i = 0; i < delays->max_delay - 1; i++) {
        if (audiobeam_input_pos < 5760)
            audiobeam_parse_line((queue->sample_queue)[queue->head], 15);
        else
            return -1;
        queue->head = audiobeam_wrapped_inc(queue->head, delays->max_delay);
    }
    __pragma_loopbound(371, 371);
    for (i = 0; (i < window) || (window < 0); i++) {
        if (audiobeam_input_pos < 5760)
            audiobeam_parse_line((queue->sample_queue)[queue->head], 15);
        else {
            done = 1;
            break;
        }

        __pragma_loopbound(1, 1);
        for (j = 0; j < num_beams; j++) {
            value = audiobeam_do_beamforming(
                preprocessed_delays, (queue->sample_queue),
                audiobeam_wrapped_inc(queue->head, delays->max_delay),
                delays->max_delay, num_mic, weights);

            value = value / num_mic;

            if (beamform_results != 0)
                beamform_results[j][i] = value;
        }

        queue->tail = queue->head;
        queue->head = audiobeam_wrapped_inc(queue->head, delays->max_delay);

        time_index += time_index_inc;
    }

    return (done);
}

int
audiobeam_calc_beamforming_result(struct audiobeam_Delays *delays,
                                  float **beamform_results, float *energies,
                                  struct audiobeam_DataQueue *queue,
                                  int num_beams, int window, int hamming) {
    int i;
    int done;
    float *weights = 0;

    if (hamming) {
        if ((15 % 2) == 1)
            weights = audiobeam_calc_weights_left_only(15);
        else
            weights = audiobeam_calc_weights_lr(15);
    }

    done = audiobeam_process_signal(delays, 15, 16000, beamform_results, queue,
                                    num_beams, window, weights);

    if (beamform_results != 0) {
        __pragma_loopbound(1, 1);
        for (i = 0; i < num_beams; i++)
            energies[i] =
                audiobeam_calculate_energy(beamform_results[i], window);
    }
    return done;
}

void
audiobeam_calc_single_pos(float source_location[3],
                          float audiobeam_mic_locations[15][3], int hamming) {
    float mic_distances[15];
    struct audiobeam_Delays *delays = audiobeam_init_delays(1, 15);
    struct audiobeam_DataQueue *queue;

    float **beamform_results;
    float *energies;

    beamform_results = (float **) audiobeam_malloc(1 * sizeof(float *));
    beamform_results[0] = (float *) audiobeam_malloc(384 * sizeof(float));
    energies = (float *) audiobeam_malloc(1 * sizeof(float *));

    // Calculate distances from source to each of mics
    audiobeam_calc_distances(source_location, audiobeam_mic_locations,
                             mic_distances, 15);

    audiobeam_calc_delays(mic_distances, delays->delay_values[0], 342, 16000,
                          15);

    audiobeam_adjust_delays(delays->delay_values[0], 15);

    delays->max_delay = audiobeam_find_max_in_arr(delays->delay_values[0], 15);

    queue = audiobeam_init_data_queue(delays->max_delay, 15);

    audiobeam_calc_beamforming_result(delays, beamform_results, energies, queue,
                                      1, -1, hamming);

    audiobeam_checksum += beamform_results[0][0] * 1000;
}

/*
  Main functions
*/

__attribute__((noinline)) __attribute__((export_name("entrypoint"))) void
audiobeam_main(void) {
    char hamming = 1;
    audiobeam_calc_single_pos(audiobeam_source_location,
                              audiobeam_mic_locations, hamming);
}

__attribute__((noinline)) __attribute__((export_name("main"))) int
main(void) {
    audiobeam_init();
    audiobeam_main();

    return (audiobeam_return());
}
