import { Curve, SettingControl } from '../types';
import fakeAudioBuffer from './fakeAudioBuffer';
import Ooura from "ooura";

interface SettingDefinition<T> {
  name: string;
  default: T;
  control: SettingControl;
}

type NumberSettingDefinition = SettingDefinition<number> & {
  min: number;
  max: number;
  exponential?: boolean;
}

type BooleanSettingDefinition = SettingDefinition<boolean> & {
  trueLabel: string;
  falseLabel: string;
}

type OptionSettingDefinition<T> = SettingDefinition<T> & {
  options: {[key: string]: T} | string[]
}

export interface ComplexArray {
  real: Float32Array;
  imag: Float32Array;
}

export type MapPhasorsFunction = (data: ComplexArray, sampleIndex: number) => ComplexArray;

const initStandardLibrary = (sampleRate: number, settings: {[key: string]: any}) => {
  let __settingIndex = 0;
  const __settings: {[key: string]: any} = {};
  const registerSetting = (definition: any) => {
    __settingIndex ++;
    __settings[__settingIndex] = definition;

    if (settings.hasOwnProperty(__settingIndex)) {
      return settings[__settingIndex];
    } else {
      return definition.default;
    }
  }
  const wavtool = {
    __settings,

    sampleRate,

    sin: (x: number) => Math.sin(x * 2 * Math.PI / sampleRate),

    saw: (x: number) => 2 * (((x % sampleRate) / sampleRate) - 0.5),
    square: (x: number) => wavtool.saw(x) > 0 ? 1 : -1,

    makeAudioBuffer: ({ length, numberOfChannels }: { length: number, numberOfChannels: number }) => fakeAudioBuffer({ length, numberOfChannels, sampleRate: sampleRate }),

    shortTimeFourierTransform: (data: Float32Array, fn: MapPhasorsFunction, start: number, end: number, bins: number, shift: number) => {
      const oo = new Ooura(bins);
      const hannWindow = oo.scalarArrayFactory();
      let scaling = 0;
      for(let i = 0; i < bins; i ++) {
        hannWindow[i] = Math.pow(Math.sin(Math.PI * i / bins), 2);
        scaling += hannWindow[i] * hannWindow[i];
      }
      scaling /= shift;

      const segment = oo.scalarArrayFactory();
      const real = oo.vectorArrayFactory();
      const imag = oo.vectorArrayFactory();
      const output = new Float32Array(end - start);
      for(let i = start; i < end; i += shift) {
        for(let j = 0; j < bins; j ++) {
          if(i + j >= end) {
            segment[j] = 0;
          } else {
            segment[j] = data[i + j] * hannWindow[j];
          }
        }
        oo.fft(segment.buffer, real.buffer, imag.buffer);
        const newPhasors = fn({ real, imag }, i);
        oo.ifft(segment.buffer, newPhasors.real.buffer, newPhasors.imag.buffer);
        for(let j = 0; j < Math.min(bins, end - i); j ++) {
          output[i + j - start] += (hannWindow[j] * segment[j]) / scaling;
        }
      }

      return output;
    },

    numberSetting: (settingDefinition: Partial<NumberSettingDefinition> = {}) => {
      const config = {
        name: 'Number Setting',
        curve: settingDefinition.exponential ? Curve.Exponential : Curve.Linear,
        control: SettingControl.Slider,
        min: 0,
        max: 100,
        default: ((settingDefinition.min || 0) + (settingDefinition.max || 100)) / 2,
        ...settingDefinition
      };
      return registerSetting(config);
    },

    frequencySetting: (settingDefinition: Partial<NumberSettingDefinition> = {}) => {
      const config = {
        name: 'Frequency',
        curve: settingDefinition.exponential === false ? Curve.Linear : Curve.Exponential,
        control: SettingControl.Slider,
        min: 20,
        max: 20000,
        default: Math.pow(2, ((Math.log(settingDefinition.min || 20) / Math.log(2)) + (Math.log(settingDefinition.max || 20000) / Math.log(2))) / 2),
        ...settingDefinition
      };
      return registerSetting(config);
    },

    booleanSetting: (settingDefinition: Partial<BooleanSettingDefinition> = {}) => {
      const config = {
        name: 'Boolean Setting',
        control: SettingControl.Select,
        options: {
          [settingDefinition.trueLabel || 'True']: true,
          [settingDefinition.falseLabel || 'False']: false
        },
        default: false,
        ...settingDefinition
      };
      return registerSetting(config);
    },

    optionSetting: <T>(settingDefinition: Partial<OptionSettingDefinition<T>> = {}) => {
      if (settingDefinition.options instanceof Array) {
        settingDefinition.options = settingDefinition.options.reduce((a, c) => ({ ...a, [c]: c }), {});
      }
      const config = {
        name: 'Option Setting',
        control: SettingControl.Select,
        options: {
          ['True']: true,
          ['False']: false
        },
        default: settingDefinition.options ? Object.values(settingDefinition.options)[0] : false,
        ...settingDefinition
      };
      return registerSetting(config);
    }
  }

  return wavtool;
};

export type StandardLibrary = ReturnType<typeof initStandardLibrary>;
export default initStandardLibrary;
