
import { ipcMain } from 'electron';
import { blackout } from '../patterns/blackout';
import { Pattern, PatternOutput, RenderUpdate } from '../patterns/proto';
import { TestPattern } from '../patterns/test';
import rust, { BeatTrackerHandle, MovingHeadState, OutputHandle, TrackerConfig } from 'rust_native_module';
import { ChaserPattern } from '../patterns/chaser';
import { HSLRandomMovementPattern } from '../patterns/hslRandom';

export type AppState = {
  patterns: { [key: string]: PatternOutput },
  selectedPattern: string | null,

  beatProgress: number | null,
  graphData: {
    bassFiltered: Array<number>,
    autoCorrelated: Array<number>,
  } | null,
  trackerConfig: TrackerConfig,
};

class Backend {

  beatTracker: BeatTrackerHandle;
  dmxOutput: OutputHandle;

  patterns: Map<string, Pattern>;
  // patternOutputs: Map<string, PatternOutput>;
  // selectedPattern: string;

  state: AppState;

  constructor() {

    // beat tracking

    let beatTracker = rust.getBeatTracker();

    if (beatTracker.type !== 'success') {
      throw new Error("could not initialize beat tracking");
    }

    this.beatTracker = beatTracker.value;
    ipcMain.on('beat-tracking', async (_, arg) => {
      if (arg === 'tap') {
        this.beatTracker.tap();
      }
    });

    // output

    let dmxOutput = rust.openOutput("/dev/ttyUSB0");

    if (dmxOutput.type !== 'success') {
      throw new Error("could not open DMX output");
    }

    this.dmxOutput = dmxOutput.value;

    // patterns

    this.patterns = new Map();
    this.patterns.set("test", new TestPattern());
    this.patterns.set("chaser", new ChaserPattern());
    this.patterns.set("hslRandom", new HSLRandomMovementPattern());

    this.state = {
      patterns: {},
      selectedPattern: null,
      beatProgress: null,
      graphData: null,
      trackerConfig: {
        mode: "auto",
        acThreshold: 1000,
        zeroCrossingBeatDelay: 50,
      }
    }

    ipcMain.on('pattern-select', async (_, arg) => {
      this.state.selectedPattern = arg;
    });

    ipcMain.on('update-delay', async (_, delay) => {
      this.state.trackerConfig.zeroCrossingBeatDelay = Math.floor(delay);
      this.beatTracker.setConfig(this.state.trackerConfig);
    });

    ipcMain.on('manual-mode', async (_, is_manual) => {
      this.state.trackerConfig.mode = is_manual ? "manual" : "auto";
      this.beatTracker.setConfig(this.state.trackerConfig);
    });

    let update: RenderUpdate = {
      absolute: 0,
      beatRelative: this.state.beatProgress,
      bassVolume: 0,
    }

    for (let [patternId, pattern] of this.patterns.entries()) {
      let patternOutput = pattern.render(update);
      this.state.patterns[patternId] = patternOutput;
    }

    ipcMain.handle('poll', async (_) => {
      return this.getState();
    });

  }

  getState(): AppState {
    return this.state;
  }

  update() {

    let beatProgress = this.beatTracker.getProgress();

    if (beatProgress.type == 'some') {
      this.state.beatProgress = beatProgress.value;
    } else {
      this.state.beatProgress = null;
    }

    this.state.graphData = this.beatTracker.getGraphPoints();

    let date = new Date();
    let update: RenderUpdate = {
      absolute: date.getTime() / 1000,
      beatRelative: this.state.beatProgress,
      bassVolume: this.beatTracker.getCurrentBass(),
    }

    // render all patterns and write selected pattern to DMX

    let output: Array<MovingHeadState> = blackout;

    for (let [patternId, pattern] of this.patterns.entries()) {
      let patternOutput = pattern.render(update);
      this.state.patterns[patternId] = patternOutput;

      if (patternId === this.state.selectedPattern && patternOutput) {
        output = patternOutput;
      }
    }

    this.dmxOutput.set(output);
  }

  close() {
    this.dmxOutput.close();
  }

}

export default Backend;
