// Copyright 2025 RnD Center "ELVEES", JSC

/*! \file
 *  \brief Тестирование функций расчета фильтров и фильтрации
 */

#include <elcore50-signal-lib/fft.h>

#include "argparser.h"

void print_usage() {
  printf("Usage: test_fir_filters [OPTIONS]\n");
  printf("Options description in strict order:\n");
  argparser_print_usage();
  printf("\t-m    Data location for filtration: {DDR, XYRAM}\n");
  printf("\t-w    FIR window type\n");
  printf("\t-p    Attenuation (in dB) for parametric windows\n");
  printf("\t-t    Length of the filter (filter order + 1). Should be an odd number\n");
  printf("\t-f    Filter type: LOW_PASS, HIGH_PASS, BAND_PASS, BAND_STOP\n");
  printf("\t-w0   Cutoff frequency or left bound of band for bandpass or bandstop filter\n");
  printf("\t-w1   Cutoff frequency or right bound of band for bandpass or bandstop filter\n");
  printf("\t     \t0 < w0 < w1 < 1\n");
  printf("\t-s   Size of the signal with noise\n");
  printf("\t-h   Print usage\n");
}

int main(int argc, char *argv[]) {
  if ((argc != 2) && (argc != 18) && (argc != 20) && (argc != 22)) {
    printf("Error: wrong number of parameters: %d!\n", argc);
    print_usage();
    return 1;
  }

  char *key_h = argv[1];
  if (!strcmp(key_h, "-h")) {
    print_usage();
    return 0;
  }

  char *infiles[MAX_FILES];
  char *outfiles[MAX_FILES];
  int8_t len = argparser(argc, argv, infiles, outfiles);
  if (len != 3) {
    printf("Error: Wrong amount of input files!\n");
    return 1;
  }

  int32_t taps, size;
  float w0 = 0.0, w1 = 0.0;
  float param = 0.0;
  int8_t filter_type;
  int8_t window;
  int8_t is_data_in_xyram;
  char *win;
  char *fil;
  char *mem_type;
  for (size_t i = 6; i < argc; i += 2) {
    char *key = argv[i];
    if (!strcmp(key, "-m")) {
      mem_type = argv[i + 1];
      if (strcmp(mem_type, "DDR") && strcmp(mem_type, "XYRAM")) {
        printf("Unrecognized program option: %s\n", mem_type);
        print_usage();
        return 1;
      }
      is_data_in_xyram = strcmp(mem_type, "XYRAM") ? 0 : 1;
    } else if (!strcmp(key, "-w")) {
      win = argv[i + 1];
      if (!strcmp(win, "hamming")) {
        window = HAMMING;
      } else if (!strcmp(win, "nuttall")) {
        window = NUTTALL;
      } else if (!strcmp(win, "blackman")) {
        window = BLACKMAN;
      } else if (!strcmp(win, "blackmanharris")) {
        window = BLACKMAN_HARRIS;
      } else if (!strcmp(win, "blackmannuttall")) {
        window = BLACKMAN_NUTTALL;
      } else if (!strcmp(win, "hann")) {
        window = HANN;
      } else if (!strcmp(win, "lanczos")) {
        window = LANCZOS;
      } else if (!strcmp(win, "bartlett")) {
        window = BARTLETT;
      } else if (!strcmp(win, "chebwin")) {
        window = CHEBWIN;
      } else {
        printf("Unrecognized program option: %s\n", argv[i + 1]);
        print_usage();
        return 1;
      }
    } else if (!strcmp(key, "-f")) {
      fil = argv[i + 1];
      if (!strcmp(fil, "LOWPASS")) {
        filter_type = LOWPASS;
      } else if (!strcmp(fil, "HIGHPASS")) {
        filter_type = HIGHPASS;
      } else if (!strcmp(fil, "BANDPASS")) {
        filter_type = BANDPASS;
      } else if (!strcmp(fil, "BANDSTOP")) {
        filter_type = BANDSTOP;
      } else {
        printf("Unrecognized program option: %s\n", argv[i + 1]);
        print_usage();
        return 1;
      }
    } else if (!strcmp(key, "-t")) {
      taps = atoi(argv[i + 1]);
      if (!(taps % 2)) {
        printf("Error: length of the filter should be an odd number!\n");
        print_usage();
        return 1;
      }
    } else if (!strcmp(key, "-p")) {
      param = atof(argv[i + 1]);
      if (param < 0.0) {
        printf("Error: incorrect variable value!\n");
        print_usage();
        return 1;
      }
    } else if (!strcmp(key, "-w0")) {
      w0 = atof(argv[i + 1]);
      if ((w0 == 0) || (w0 >= 1)) {
        printf("Error: incorrect variable value!\n");
        print_usage();
        return 1;
      }
    } else if (!strcmp(key, "-w1")) {
      w1 = atof(argv[i + 1]);
      if ((w1 <= w0) || (w1 >= 1)) {
        printf("Error: incorrect variable value!\n");
        print_usage();
        return 1;
      }
    } else if (!strcmp(key, "-s")) {
      size = atoi(argv[i + 1]);
    } else {
      printf("Unrecognized program option: %s\n", key);
      print_usage();
      return 1;
    }
  }

  if ((window == CHEBWIN) && (param <= 1e-2)) {
    printf("Error: incorrect variable value!\n");
    print_usage();
    return 1;
  }

  printf("\nTest %s FIR filter with %s window ", fil, win);
  if (is_data_in_xyram)
    printf("(XYRAM): ");
  else
    printf("(DDR): ");
  if (filter_type != HIGHPASS) printf("w0 = %f ", w0);
  if (filter_type != LOWPASS) printf("w1 = %f ", w1);
  if (window == CHEBWIN) printf("param = %f", param);
  printf("\n");

  int ticks_start, ticks_end;
  int instrs_start, instrs_end;
  int ticks_win_end, instrs_win_end;
  int ticks_filter_start, instrs_filter_start;

  float *h = memalign(taps * 2 * sizeof(float), taps * 2 * sizeof(float));
  ticks_counter(&ticks_start, &instrs_start);
  int retval = fir_window(h, taps - 1, w0, w1, filter_type, window, 1, param);
  if (retval) {
    print_error_message(retval);
    free(h);
    return 1;
  }
  ticks_counter(&ticks_win_end, &instrs_win_end);
  int32_t ticks_win = ticks_win_end - ticks_start;

  // Compare results for H function
  float *ref_h = malloc(taps * sizeof(float));
  if (read_data_from_file(ref_h, taps, outfiles[0])) {
    free(h);
    free(ref_h);
    return 1;
  }
  float norm = fabs(ref_h[0] - h[1]);
  for (size_t i = 0; i < taps; ++i) {
    float norm_i = fabs(ref_h[i] - h[2 * i + 1]);
    if (norm_i > norm) norm = norm_i;
  }
  free(ref_h);
  int16_t errors_count = !(norm < 1e-2);

  printf("         size |     ticks | status\n");
  printf("----------------------------------\n");
  printf("h func %6d | %9d | ", taps, ticks_win);
  if (errors_count) {
    printf("failed\n");
  } else {
    printf("passed\n");
  }
  printf("norm = %f\n", norm);

  char filename[65];
  snprintf(filename, 65, "test_fir_filter_%s_%s_%s_h.csv", win, fil, mem_type);
  FILE *res_h = fopen(filename, "w");
  if (res_h == NULL) {
    printf("Error: Can not open file %s!\n", filename);
    free(h);
    return 1;
  }
  fprintf(res_h, "%s,%s,%s\n", "h_func", "norm", "ticks");
  fprintf(res_h, "%f,%f,%d\n", h[2 * 0 + 1], norm, ticks_win);
  for (size_t i = 1; i < taps; ++i) {
    fprintf(res_h, "%f\n", h[2 * i + 1]);
  }
  fclose(res_h);

  float *src = memalign(size * 2 * sizeof(float), size * 2 * sizeof(float));
  if (read_data_from_file(src, size * 2, infiles[0])) {
    free(h);
    free(src);
    return (errors_count | 0x2);
  }

  float *dst = memalign(size * 2 * sizeof(float), size * 2 * sizeof(float));
  ticks_counter(&ticks_filter_start, &instrs_filter_start);
  retval = lfilter(h, NULL, taps, src, size, dst, is_data_in_xyram);
  if (retval) {
    print_error_message(retval);
    free(h);
    free(src);
    free(dst);
    return (errors_count | 0x2);
  }
  ticks_counter(&ticks_end, &instrs_end);
  int32_t ticks_filter = ticks_end - ticks_filter_start;
  free(h);
  free(src);

  // Compare results for filtration
  float *ref_dst = malloc(size * sizeof(float));
  if (read_data_from_file(ref_dst, size, outfiles[1])) {
    free(dst);
    free(ref_dst);
    return (errors_count | 0x2);
  }
  float filter_norm = fabs(ref_dst[0] - dst[1]);
  for (size_t i = 0; i < size; ++i) {
    float norm_i = fabs(ref_dst[i] - dst[2 * i + 1]);
    if (norm_i > filter_norm) filter_norm = norm_i;
  }
  free(ref_dst);
  errors_count += !(filter_norm < 1e-1) << 1;

  char filename_signal[65];
  snprintf(filename_signal, 65, "test_fir_filter_%s_%s_%s_signal.csv", win, fil, mem_type);
  FILE *res_y = fopen(filename_signal, "w");
  if (res_y == NULL) {
    printf("Error: Can not open file %s!\n", filename_signal);
    free(dst);
    return (errors_count | 0x2);
  }
  fprintf(res_y, "%s,%s,%s\n", "signal", "norm", "ticks");
  fprintf(res_y, "%f,%f,%d\n", dst[2 * 0 + 1], filter_norm, ticks_filter);
  for (size_t i = 1; i < size; ++i) {
    fprintf(res_y, "%f\n", dst[2 * i + 1]);
  }
  fclose(res_y);
  free(dst);

  printf("filter %6d | %9d | ", size, ticks_filter);
  if (errors_count & 0x2) {
    printf("failed\n");
  } else {
    printf("passed\n");
  }
  printf("norm = %f\n", filter_norm);

  return errors_count;
}
