import * as THREE from 'three';

const MOVE = 'G';
const M_COMMAND = 'M';
const FAN_SPEED = 'S';
const SPEED = 'F';
const EXTRUDER = 'E';
const POSITION_X = 'X';
const POSITION_Y = 'Y';
const POSITION_Z = 'Z';

export default class {
  constructor(settings) {
    this._gcode = '';
    this._currentValues = {};
    this._settings = settings;
    this._nozzlePosition = new THREE.Vector2(0, 0);
    this._extruder = 0.0;
    this._isRetracted = false;
    this._isFanOn = false;

    this.bottom = true;
  }

  _addGCode(command) {
    let str = '';

    let first = true;
    for (const action in command) {
      const value = command[action];
      const currentValue = this._currentValues[action];
      if (first) {
        str = action + value;

        first = false;
      } else if (currentValue !== value) {
        str += ` ${action}${value}`;

        this._currentValues[action] = value;
      }
    }

    this._gcode += `${str}\n`;
  }

  turnFanOn(fanSpeed) {
    this._isFanOn = true;

    const gcode = { [M_COMMAND]: 106 }
    if (typeof fanSpeed !== 'undefined') gcode[FAN_SPEED] = fanSpeed;

    this._addGCode(gcode);

    return this;
  }

  turnFanOff() {
    this._isFanOn = false;

    this._addGCode({ [M_COMMAND]: 107 });

    return this;
  }

  moveTo(x, y, layer) {
    const {
      layerHeight,
      travelSpeed
    } = this._settings;

    const z = layer * layerHeight + 0.2;
    const speed = travelSpeed * 60;

    this._addGCode({
      [MOVE]: 0,
      [POSITION_X]: x.toFixed(3),
      [POSITION_Y]: y.toFixed(3),
      [POSITION_Z]: z.toFixed(3),
      [SPEED]: speed.toFixed(3)
    });

    this._nozzlePosition.set(x, y);

    return this;
  }

  lineTo(x, y, layer, type) {
    const newNozzlePosition = new THREE.Vector2(x, y);

    const {
      layerHeight,
      nozzleDiameter,
      filamentThickness,
      travelSpeed
    } = this._settings;

    const profile = this._settings[(this.bottom ? 'bottom' : type)];

    let {
      speed,
      flowRate
    } = profile;

    speed *= 60;
    const z = layer * layerHeight + 0.2;

    const lineLength = this._nozzlePosition.distanceTo(newNozzlePosition);

    const filamentSurfaceArea = Math.pow((filamentThickness / 2), 2) * Math.PI;
    this._extruder += lineLength * nozzleDiameter * layerHeight / filamentSurfaceArea * flowRate;

    this._addGCode({
      [MOVE]: 1,
      [POSITION_X]: x.toFixed(3),
      [POSITION_Y]: y.toFixed(3),
      [POSITION_Z]: z.toFixed(3),
      [SPEED]: speed.toFixed(3),
      [EXTRUDER]: this._extruder.toFixed(3)
    });

    this._nozzlePosition.copy(newNozzlePosition);

    return this;
  }

  unRetract() {
    const {
      retraction: {
        enabled: retractionEnabled,
        minDistance: retractionMinDistance,
        speed: retractionSpeed
      }
    } = this._settings;

    if (this._isRetracted && retractionEnabled) {
      this._isRetracted = false;

      const speed = retractionSpeed * 60;

      if (this._extruder > retractionMinDistance) {
        this._addGCode({
          [MOVE]: 0,
          [EXTRUDER]: this._extruder.toFixed(3),
          [SPEED]: speed.toFixed(3)
        });
      }
    }

    return this;
  }

  retract() {
    const {
      retraction: {
        amount: retractionAmount,
        enabled: retractionEnabled,
        minDistance: retractionMinDistance,
        speed: retractionSpeed
      }
    } = this._settings;

    if (!this._isRetracted && retractionEnabled) {
      this._isRetracted = true;

      const speed = retractionSpeed * 60;

      if (this._extruder > retractionMinDistance && retractionEnabled) {
        this._addGCode({
          [MOVE]: 0,
          [EXTRUDER]: (this._extruder - retractionAmount).toFixed(3),
          [SPEED]: speed.toFixed(3)
        });
      }
    }

    return this;
  }

  getGCode() {
    return this._gcode;
  }
}
