import { Pattern, midiToFreq, getFrequency } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs';

const c = midiToFreq(36);

const circlePos = (cx, cy, radius, angle) => {
  angle = angle * Math.PI * 2;
  const x = Math.sin(angle) * radius + cx;
  const y = Math.cos(angle) * radius + cy;
  return [x, y];
};

const freq2angle = (freq, root) => {
  return 0.5 - (Math.log2(freq / root) % 1);
};

export function pitchwheel({
  haps,
  ctx,
  id,
  hapcircles = 1,
  circle = 0,
  edo = 12,
  root = c,
  thickness = 3,
  hapRadius = 6,
  mode = 'flake',
  margin = 10,
} = {}) {
  const connectdots = mode === 'polygon';
  const centerlines = mode === 'flake';
  const w = ctx.canvas.width;
  const h = ctx.canvas.height;
  ctx.clearRect(0, 0, w, h);
  const color = getTheme().foreground;

  const size = Math.min(w, h);
  const radius = size / 2 - thickness / 2 - hapRadius - margin;
  const centerX = w / 2;
  const centerY = h / 2;

  if (id) {
    haps = haps.filter((hap) => hap.hasTag(id));
  }
  ctx.strokeStyle = color;
  ctx.fillStyle = color;
  ctx.globalAlpha = 1;
  ctx.lineWidth = thickness;

  if (circle) {
    ctx.beginPath();
    ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
    ctx.stroke();
  }

  if (edo) {
    edo = haps.length >= 1 && haps[0].value && haps[0].value.edo ? haps[0].value.edo : edo;
    root = haps.length >= 1 && haps[0].value && haps[0].value.root ? haps[0].value.root : root;
    const degreeIndexes =
      haps.length >= 1 && haps[0].value && haps[0].value.degreeIndexes ? haps[0].value.degreeIndexes : null;
    const intLabels = haps.length >= 1 && haps[0].value && haps[0].value.intLabels ? haps[0].value.intLabels : null;
    ctx.font = '20px sans-serif';
    const label = `${edo} EDO`;
    // Draw EDO label:
    ctx.fillText(label, centerX + radius - ctx.measureText(label).width + 15, centerY + radius);
    Array.from({ length: edo }, (_, i) => {
      const angle = freq2angle(root * Math.pow(2, i / edo), root);

      const [x, y] = circlePos(centerX, centerY, radius, angle);
      ctx.beginPath();
      // Draw interval label for degree i when it exists:
      if (degreeIndexes === null || degreeIndexes.includes(i)) {
        ctx.globalAlpha = 1;
        ctx.arc(x, y, hapRadius, 0, 2 * Math.PI);
        if (intLabels !== null) {
          const degree = degreeIndexes.indexOf(i);
          if (intLabels[degree]) {
            if (angle < 0.32 && angle > 0.125) {
              ctx.fillText(intLabels[degree], x - 34, y);
            } else {
              if (angle < 0.1 && angle > -1.125) {
                ctx.fillText(intLabels[degree], x - 7, y - 12);
              } else {
                ctx.fillText(intLabels[degree], x + 9, y);
              }
            }
          }
        }
      } else {
        ctx.globalAlpha = 0.15;
        ctx.arc(x, y, hapRadius, 0, 2 * Math.PI);
      }
      ctx.fill();
    });
    ctx.stroke();
  }

  let shape = [];
  ctx.lineWidth = hapRadius;
  haps.forEach((hap) => {
    let freq;
    try {
      freq = getFrequency(hap);
    } catch (err) {
      return;
    }
    const angle = freq2angle(freq, root);
    const [x, y] = circlePos(centerX, centerY, radius, angle);
    const hapColor = hap.value.color || color;
    ctx.strokeStyle = hapColor;
    ctx.fillStyle = hapColor;
    const { velocity = 1, gain = 1 } = hap.value || {};
    const alpha = velocity * gain;
    ctx.globalAlpha = alpha;
    shape.push([x, y, angle, hapColor, alpha]);
    ctx.beginPath();
    if (hapcircles) {
      ctx.moveTo(x + hapRadius, y);
      ctx.arc(x, y, hapRadius, 0, 2 * Math.PI);
      ctx.fill();
    }
    if (centerlines) {
      ctx.moveTo(centerX, centerY);
      ctx.lineTo(x, y);
    }
    ctx.stroke();
  });

  ctx.strokeStyle = color;
  ctx.globalAlpha = 1;
  if (connectdots && shape.length) {
    shape = shape.sort((a, b) => a[2] - b[2]);
    ctx.beginPath();
    ctx.moveTo(shape[0][0], shape[0][1]);
    shape.forEach(([x, y, _, color, alpha]) => {
      ctx.strokeStyle = color;
      ctx.globalAlpha = alpha;
      ctx.lineTo(x, y);
    });
    ctx.lineTo(shape[0][0], shape[0][1]);
    ctx.stroke();
  }

  return;
}

/**
 * Renders a pitch circle to visualize frequencies within one octave
 * @name pitchwheel
 * @tags visualization
 * @param {number} hapcircles
 * @param {number} circle
 * @param {number} edo
 * @param {string} root
 * @param {number} thickness
 * @param {number} hapRadius
 * @param {string} mode
 * @param {number} margin
 * @example
 * n("0 .. 12").scale("C:chromatic")
 * .s("sawtooth")
 * .lpf(500)
 * ._pitchwheel()
 */
Pattern.prototype.pitchwheel = function (options = {}) {
  let { ctx = getDrawContext(), id = 1 } = options;
  return this.tag(id).onPaint((_, time, haps) =>
    pitchwheel({
      ...options,
      time,
      ctx,
      haps: haps.filter((hap) => hap.isActive(time)),
      id,
    }),
  );
};
