import Head from "next/head";
import { ChangeEventHandler, useState } from "react";

const calculateFrequency = (root: number, noteDescriptor: string) => {
  const match = noteDescriptor.match(
    /([a-gA-G])\s*(#?)(b?)\s*(-?\d+)([+-]\d+)?/
  );
  if (match == null) return undefined;

  const [value, note, sharp, flat, octave, cents] = match;
  const dists: { [key: string]: number | undefined } = {
    C: -9,
    D: -7,
    E: -5,
    F: -4,
    G: -2,
    A: 0,
    B: 2,
  };
  let distanceFromA = dists[note.toUpperCase()];
  if (distanceFromA == null) return;
  if (sharp) distanceFromA += 1;
  if (flat) distanceFromA -= 1;
  if (cents) distanceFromA += parseInt(cents) / 100;

  let octaveDistance = parseInt(octave) - 4;
  return root * Math.pow(2, octaveDistance + distanceFromA / 12);
};

const calculateNote = (root: number, frequency: number) => {
  const distanceFromA = 12 * Math.log2(frequency / root);
  const semis = Math.round(distanceFromA);
  const cents = Math.round((distanceFromA - semis) * 100);
  let octave = 4 + Math.floor(semis / 12);
  const octaveBoundSemis = ((semis % 12) + 12) % 12;
  if (octaveBoundSemis >= 3) octave += 1;
  const notes = [
    "A",
    "A#",
    "B",
    "C",
    "C#",
    "D",
    "D#",
    "E",
    "F",
    "F#",
    "G",
    "G#",
  ];
  const note = notes[octaveBoundSemis];

  let centsStr = "";
  if (cents <= -1) {
    centsStr = cents.toString().padStart(2, "0");
  } else if (cents >= 1) {
    centsStr = "+" + cents.toString().padStart(2, "0");
  }

  return `${note}${octave}${centsStr}`;
};

export default function Main() {
  const [root, setRoot] = useState(440);
  const [note, setNote] = useState("C5");
  const [frequency, setFrequency] = useState("523.251");

  const setRootValue: ChangeEventHandler<HTMLInputElement> = (e) => {
    const newRoot = parseFloat(e.target.value);
    if (!isNaN(newRoot)) setRoot(newRoot);
    const frequency = calculateFrequency(newRoot, note);
    if (frequency != null) setFrequency(frequency.toFixed(3));
  };

  const setNoteValue: ChangeEventHandler<HTMLInputElement> = (e) => {
    setNote(e.target.value);
    const frequency = calculateFrequency(root, e.target.value);
    if (frequency != null) setFrequency(frequency.toFixed(3));
  };

  const setFrequencyValue: ChangeEventHandler<HTMLInputElement> = (e) => {
    setFrequency(e.target.value);

    const newFrequency = parseFloat(e.target.value);
    if (!isNaN(newFrequency)) {
      const noteDescriptor = calculateNote(root, newFrequency);
      setNote(noteDescriptor);
    }
  };

  return (
    <>
      <Head>
        <title>12 tone equal temperament :)</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </Head>
      <main>
        <header>
          (by <a href="https://som.codes/">charlotte som</a>)
        </header>

        <section>
          {"if A4 is "}
          <input
            type="text"
            pattern="\d+(\.\d+)"
            value={root}
            placeholder="440"
            onChange={setRootValue}
          />
          {" Hz"}, then
        </section>
        <section>
          <input
            type="text"
            value={note}
            placeholder="C5"
            onChange={setNoteValue}
          />
          {" is "}
          <input
            type="text"
            pattern="\d+(\.\d+)"
            value={frequency}
            placeholder="523.251"
            onChange={setFrequencyValue}
          />
          {" Hz "}
          <aside>
            (period: {(1000 / parseFloat(frequency)).toFixed(4)} ms)
          </aside>
        </section>
      </main>
    </>
  );
}
