const FFT_SIZE = 8192;
const BLOCK_SIZE = 2048;

const FADE_RATE = BLOCK_SIZE / 50000;
const RECOVER_RATE = BLOCK_SIZE / 200;

const EQ_BAND_MODES = {
  'EQ Band': BiQuadFilterMode.EQBand,
  'Low Pass': BiQuadFilterMode.LowPass,
  'High Pass': BiQuadFilterMode.HighPass,
  'Band Pass': BiQuadFilterMode.BandPass,
  'Notch': BiQuadFilterMode.Notch,
  'Low Shelf': BiQuadFilterMode.LowShelf,
  'High Shelf': BiQuadFilterMode.HighShelf,
}

const EQBand = ({ input, output, analysis, frequency, q, adaptiveQ, effectiveQ, gain, mode, interfaceVisible }) => {
  const filterLeft = useBiQuadFilter();
  const filterRight = useBiQuadFilter();

  const adjustedQ = useMonoSignal(q.length);

  if (adaptiveQ && mode === 'EQ Band') {
    for (let i = 0; i < q.length; i ++) {
      adjustedQ[i] = q[i] + (Math.abs(Math.log(gain[i])));
    }
  } else {
    adjustedQ.set(q);
  }

  effectiveQ[0] = adjustedQ[0];

  output[0].set(filterLeft(input[0], EQ_BAND_MODES[mode], frequency, adjustedQ, gain));
  output[1].set(filterRight(input[1], EQ_BAND_MODES[mode], frequency, adjustedQ, gain));

  const lastAnalysisResult = useMonoSignal(analysis.length);
  const nextAnalysisResult = useMonoSignal(analysis.length);
  const fft = useFFT(FFT_SIZE, BLOCK_SIZE);
  const stepIndex = useVal(0);

  if (!interfaceVisible) return;

  for (let i = 0; i < output[0].length; i ++) {
    fft.receiveSample((output[0][i] + output[1][i]) / sampleRate);
    stepIndex.current ++;
    if (fft.isProcessingStep()) {
      fft.updateSpectrum();
      stepIndex.current = 0;
      lastAnalysisResult.set(nextAnalysisResult);
      nextAnalysisResult.set(fft.getSpectrumOutput());
    }
  }

  for (let a = 0; a < analysis.length; a ++) {
    const progress = (stepIndex.current / BLOCK_SIZE);
    const value = (
      (lastAnalysisResult[a] * (1 - progress)) +
      (nextAnalysisResult[a] * progress)
    );
    const faded = analysis[a] * (1-FADE_RATE);
    analysis[a] = Math.max(
      faded,
      faded + (RECOVER_RATE * value)
    )
  }
}

const Spectrum = (ctx, { analysis, frequency, effectiveQ, gain, mode }) => {
  const w = ctx.canvas.width;
  const h = ctx.canvas.height;
  const focusH = h - 14;
  // const imageData = ctx.getImageData(0, 0, w, h);
  ctx.clearRect(0, 0, w, h);
  // ctx.putImageData(imageData, 1, -1);
  ctx.fillStyle = '#000000'
  ctx.fillRect(0, 0, w, h);

  ctx.strokeStyle = '#444';
  ctx.lineWidth = 1;
  renderSpectrumGrid(ctx, w, focusH);

  const maxDB = 24;
  const minDB = -24;

  for (let i = maxDB; i >= minDB; i -= 12) {
    ctx.beginPath();
    const y = ((i - maxDB) / (minDB - maxDB)) * focusH;
    ctx.moveTo(0, y);
    ctx.lineTo(w, y);
    ctx.stroke();
    ctx.closePath();
  }

  ctx.fillStyle = '#666';
  ctx.font = `10px Arial`;
  labelSpectrumGrid(ctx, w, h);

  ctx.lineWidth = 1;
  ctx.strokeStyle = '#ffffff44';
  ctx.fillStyle = '#ffffff11';
  ctx.beginPath();
  ctx.moveTo(-w,h);

  traceSpectrum(ctx, analysis, FFT_SIZE, sampleRate, w, focusH, 'line');

  ctx.lineTo(w * 2,h);
  ctx.stroke();
  ctx.fill();
  ctx.closePath();

  ctx.lineWidth = 2;
  ctx.strokeStyle = blue500;
  ctx.fillStyle = `${blue500}44`;
  ctx.beginPath();

  const transferFunction = getBiQuadFilterTransferFunction(
    sampleRate,
    EQ_BAND_MODES[mode],
    frequency,
    effectiveQ[0],
    multiplierToDb(gain)
  );

  const zeroY = ((0 - maxDB) / (minDB - maxDB)) * focusH;
  ctx.moveTo(-w, zeroY);
  let x, y;

  for (let i = 0; i < transferFunction.length; i ++) {
    x = i / (transferFunction.length - 1) * w;
    y = (transferFunction[i][1] - maxDB) / (minDB - maxDB) * focusH;

    ctx.lineTo(x, y);
  }

  ctx.lineTo(w * 2, zeroY);

  ctx.stroke();
  ctx.fill();
  ctx.closePath();
}

this.constructDevice = () => constructPerBufferDevice({
  implementation: EQBand,
  inputs: {
    input: stereo('In'),
    q: numberSetting('Q', { defaultValue: Math.sqrt(.5), min: .1, max: 18, curve: Curve.Log2 }),
    frequency: frequencySetting('Frequency', 1000),
    gain: biQuadFilterDbSetting('Gain', { defaultValue: 0, min: -24, max: 24 })
  },
  constants: {
    adaptiveQ: booleanConstant('Adaptive Q', true),
    mode: enumConstant('Mode', Object.keys(EQ_BAND_MODES), 'EQ Band')
  },
  outputs: {
    output: stereo('Out')
  },
  sharedArrays: {
    analysis: FFT_SIZE / 2,
    effectiveQ: 1,
  },
  layout: themed(DeviceLayoutTheme.Skeuomorphic, col([
    pinnable(canvas(
      'spectrum',
      Spectrum,
      344,
      150
    )),
    row([
      titled.checkbox('adaptiveQ', 'Adapt.'),
      titled.knob('q'),
      titled.knob('frequency', 'Freq'),
      titled.knob('gain'),
      titled.selector('mode')
    ])
  ]))
});
