// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
//
// ## Description
// `StemmableNote` is an abstract interface for notes with optional stems.
// Examples of stemmable notes are `StaveNote` and `TabNote`

import { Vex } from './vex';
import { Flow } from './tables';
import { Stem } from './stem';
import { Glyph } from './glyph';
import { Note } from './note';

export class StemmableNote extends Note {
  constructor(note_struct) {
    super(note_struct);
    this.setAttribute('type', 'StemmableNote');
    this.stem = null;
    this.stemExtensionOverride = null;
    this.beam = null;
    this.renderFlag = true;
  }

  // Get and set the note's `Stem`
  getStem() { return this.stem; }
  setStem(stem) { this.stem = stem; return this; }

  // Builds and sets a new stem
  buildStem() {
    const stem = new Stem();
    this.setStem(stem);
    return this;
  }

  buildFlag() {
    const { glyph, beam } = this;
    const shouldRenderFlag = beam === null && this.renderFlag;

    if (glyph && glyph.flag && shouldRenderFlag) {
      const flagCode = this.getStemDirection() === Stem.DOWN
        ? glyph.code_flag_downstem
        : glyph.code_flag_upstem;

      this.flag = new Glyph(flagCode, this.render_options.glyph_font_scale);
    }
  }

  // Get the glyph associated with the top key of this note
  getTopGlyph() {
    if (this.getStemDirection() === Stem.DOWN) {
      return this.customGlyphs[this.customGlyphs.length - 1];
    } else {
      return this.customGlyphs[0];
    }
  }

  // Get the full length of stem
  getStemLength() {
    return Stem.HEIGHT + this.getStemExtension();
  }

  // Get the number of beams for this duration
  getBeamCount() {
    const glyph = this.getGlyph();

    if (glyph) {
      return glyph.beam_count;
    } else {
      return 0;
    }
  }

  // Get the minimum length of stem
  getStemMinumumLength() {
    const frac = Flow.durationToFraction(this.duration);
    let length = frac.value() <= 1 ? 0 : 20;
    // if note is flagged, cannot shorten beam
    switch (this.duration) {
      case '8':
        if (this.beam == null) length = 35;
        break;
      case '16':
        length = this.beam == null ? 35 : 25;
        break;
      case '32':
        length = this.beam == null ? 45 : 35;
        break;
      case '64':
        length = this.beam == null ? 50 : 40;
        break;
      case '128':
        length = this.beam == null ? 55 : 45;
        break;
      default:
        break;
    }
    return length;
  }

  // Get/set the direction of the stem
  getStemDirection() { return this.stem_direction; }
  setStemDirection(direction) {
    if (!direction) direction = Stem.UP;
    if (direction !== Stem.UP && direction !== Stem.DOWN) {
      throw new Vex.RERR('BadArgument', `Invalid stem direction: ${direction}`);
    }

    this.stem_direction = direction;
    if (this.stem) {
      this.stem.setDirection(direction);
      this.stem.setExtension(this.getStemExtension());
      const glyph = this.getTopGlyph() || this.getGlyph();
      this.stem.setOptions({
        stem_up_y_offset: glyph.stem_up_y_offset,
        stem_down_y_offset: glyph.stem_down_y_offset
      });
    }

    this.reset();
    if (this.flag && this.renderFlag) {
      this.buildFlag();
    }

    this.beam = null;
    if (this.preFormatted) {
      this.preFormat();
    }

    return this;
  }

  // Get the `x` coordinate of the stem
  getStemX() {
    const x_begin = this.getAbsoluteX() + this.x_shift;
    const x_end = this.getAbsoluteX() + this.x_shift + this.getGlyphWidth();
    const stem_x = this.stem_direction === Stem.DOWN ? x_begin : x_end;
    return stem_x;
  }

  // Get the `x` coordinate for the center of the glyph.
  // Used for `TabNote` stems and stemlets over rests
  getCenterGlyphX() {
    return this.getAbsoluteX() + this.x_shift + (this.getGlyphWidth() / 2);
  }

  // Get the stem extension for the current duration
  getStemExtension() {
    const glyph = this.getGlyph();

    if (this.stemExtensionOverride != null) {
      return this.stemExtensionOverride;
    }

    if (glyph) {
      return this.getStemDirection() === 1
        ? glyph.stem_up_extension
        : glyph.stem_down_extension;
    }

    return 0;
  }

  // Set the stem length to a specific. Will override the default length.
  setStemLength(height) {
    this.stemExtensionOverride = (height - Stem.HEIGHT);
    return this;
  }

  // Get the top and bottom `y` values of the stem.
  getStemExtents() {
    return this.stem.getExtents();
  }

  // Sets the current note's beam
  setBeam(beam) { this.beam = beam; return this; }

  // Get the `y` value for the top/bottom modifiers at a specific `textLine`
  getYForTopText(textLine) {
    const extents = this.getStemExtents();
    if (this.hasStem()) {
      return Math.min(
        this.stave.getYForTopText(textLine),
        extents.topY - (this.render_options.annotation_spacing * (textLine + 1))
      );
    } else {
      return this.stave.getYForTopText(textLine);
    }
  }

  getYForBottomText(textLine) {
    const extents = this.getStemExtents();
    if (this.hasStem()) {
      return Math.max(
        this.stave.getYForTopText(textLine),
        extents.baseY + (this.render_options.annotation_spacing * (textLine))
      );
    } else {
      return this.stave.getYForBottomText(textLine);
    }
  }

  hasFlag() {
    return this.renderFlag && Flow.getGlyphProps(this.duration).flag && !this.beam;
  }

  // Post format the note
  postFormat() {
    if (this.beam) this.beam.postFormat();

    this.postFormatted = true;

    return this;
  }

  // Render the stem onto the canvas
  drawStem(stem_struct) {
    this.checkContext();
    this.setRendered();

    this.setStem(new Stem(stem_struct));
    this.stem.setContext(this.context).draw();
  }
}
