/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");

const GENERIC_VARIABLES_VIEW_SETTINGS = {
  searchEnabled: false,
  editableValueTooltip: "",
  editableNameTooltip: "",
  preventDisableOnChange: true,
  preventDescriptorModifiers: false,
  eval: () => {}
};

/**
 * Functions handling the audio node inspector UI.
 */

var PropertiesView = {

  /**
   * Initialization function called when the tool starts up.
   */
  initialize: function () {
    this._onEval = this._onEval.bind(this);
    this._onNodeSet = this._onNodeSet.bind(this);

    window.on(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
    this._propsView = new VariablesView($("#properties-content"), GENERIC_VARIABLES_VIEW_SETTINGS);
    this._propsView.eval = this._onEval;
  },

  /**
   * Destruction function called when the tool cleans up.
   */
  destroy: function () {
    window.off(EVENTS.UI_INSPECTOR_NODE_SET, this._onNodeSet);
    this._propsView = null;
  },

  /**
   * Empties out the props view.
   */
  resetUI: function () {
    this._propsView.empty();
    this._currentNode = null;
  },

  /**
   * Internally sets the current audio node and rebuilds appropriate
   * views.
   */
  _setAudioNode: function (node) {
    this._currentNode = node;
    if (this._currentNode) {
      this._buildPropertiesView();
    }
  },

  /**
   * Reconstructs the `Properties` tab in the inspector
   * with the `this._currentNode` as it's source.
   */
  _buildPropertiesView: Task.async(function* () {
    let propsView = this._propsView;
    let node = this._currentNode;
    propsView.empty();

    let audioParamsScope = propsView.addScope("AudioParams");
    let props = yield node.getParams();

    // Disable AudioParams VariableView expansion
    // when there are no props i.e. AudioDestinationNode
    this._togglePropertiesView(!!props.length);

    props.forEach(({ param, value, flags }) => {
      let descriptor = {
        value: value,
        writable: !flags || !flags.readonly,
      };
      let item = audioParamsScope.addItem(param, descriptor);

      // No items should currently display a dropdown
      item.twisty = false;
    });

    audioParamsScope.expanded = true;

    window.emit(EVENTS.UI_PROPERTIES_TAB_RENDERED, node.id);
  }),

  /**
   * Toggles the display of the "empty" properties view when
   * node has no properties to display.
   */
  _togglePropertiesView: function (show) {
    let propsView = $("#properties-content");
    let emptyView = $("#properties-empty");
    (show ? propsView : emptyView).removeAttribute("hidden");
    (show ? emptyView : propsView).setAttribute("hidden", "true");
  },

  /**
   * Returns the scope for AudioParams in the
   * VariablesView.
   *
   * @return Scope
   */
  _getAudioPropertiesScope: function () {
    return this._propsView.getScopeAtIndex(0);
  },

  /**
   * Event handlers
   */

  /**
   * Called when the inspector view determines a node is selected.
   */
  _onNodeSet: function (_, id) {
    this._setAudioNode(gAudioNodes.get(id));
  },

  /**
   * Executed when an audio prop is changed in the UI.
   */
  _onEval: Task.async(function* (variable, value) {
    let ownerScope = variable.ownerView;
    let node = this._currentNode;
    let propName = variable.name;
    let error;

    if (!variable._initialDescriptor.writable) {
      error = new Error("Variable " + propName + " is not writable.");
    } else {
      // Cast value to proper type
      try {
        let number = parseFloat(value);
        if (!isNaN(number)) {
          value = number;
        } else {
          value = JSON.parse(value);
        }
        error = yield node.actor.setParam(propName, value);
      }
      catch (e) {
        error = e;
      }
    }

    // TODO figure out how to handle and display set prop errors
    // and enable `test/brorwser_wa_properties-view-edit.js`
    // Bug 994258
    if (!error) {
      ownerScope.get(propName).setGrip(value);
      window.emit(EVENTS.UI_SET_PARAM, node.id, propName, value);
    } else {
      window.emit(EVENTS.UI_SET_PARAM_ERROR, node.id, propName, value);
    }
  })
};