text: "use strict";
  /**
   * @module opcua.address_space
   * @class AddressSpace
   */
  
  const assert = require("node-opcua-assert").assert;
  const _ = require("underscore");
  const util = require("util");
  
  
  const BrowseDirection = require("node-opcua-data-model").BrowseDirection;
  
  
  const Variant = require("node-opcua-variant").Variant;
  const DataType = require("node-opcua-variant").DataType;
  
  const StatusCodes = require("node-opcua-status-code").StatusCodes;
  
  const UAVariable = require("./ua_variable").UAVariable;
  const Namespace = require("./namespace").Namespace;
  
  // Release 1.03 12 OPC Unified Architecture, Part 9
  // Two-state state machines
  // Most states defined in this standard are simple – i.e. they are either TRUE or FALSE. The
  // TwoStateVariableType is introduced specifically for this use case. More complex states are
  // modelled by using a StateMachineType defined in Part 5.
  // The TwoStateVariableType is derived from the StateVariableType.
  //
  // Attribute        Value
  // BrowseName       TwoStateVariableType
  // DataType         LocalizedText
  // ValueRank        -1 (-1 = Scalar)
  // IsAbstract       False
  //
  // Subtype of the StateVariableType defined in Part 5.
  // Note that a Reference to this subtype is not shown in the definition of the StateVariableType
  //
  // References      NodeClass BrowseName              DataType      TypeDefinition Modelling Rule
  // HasProperty     Variable  Id                      Boolean       PropertyType   Mandatory
  // HasProperty     Variable  TransitionTime          UtcTime       PropertyType   Optional
  // HasProperty     Variable  EffectiveTransitionTime UtcTime       PropertyType   Optional
  // HasProperty     Variable  TrueState               LocalizedText PropertyType   Optional
  // HasProperty     Variable  FalseState              LocalizedText PropertyType   Optional
  // HasTrueSubState StateMachine or
  //                 TwoStateVariableType
  //                                                  <StateIdentifier> Defined in Clause 5.4.2 Optional
  // HasFalseSubState StateMachine or
  //                  TwoStateVariableType
  //                                                  <StateIdentifier> Defined in Clause 5.4.3 Optional
  
  function _updateTransitionTime(node) {
      // TransitionTime specifies the time when the current state was entered.
      if (node.transitionTime) {
          node.transitionTime.setValueFromSource({dataType: DataType.DateTime, value: (new Date())})
      }
  }
  
  function _updateEffectiveTransitionTime(node,subStateNode) {
      if (node.effectiveTransitionTime) {
          //xx console.log("xxxx _updateEffectiveTransitionTime because subStateNode ",subStateNode.browseName.toString());
          node.effectiveTransitionTime.setValueFromSource({ dataType: DataType.DateTime,value: (new Date())})
      }
  }
  
  
  
  
  function _getEffectiveDisplayName(node) {
      const dataValue = node.id.readValue();
      if (dataValue.statusCode !== StatusCodes.Good) {
          return dataValue;
      }
      assert(dataValue.value.dataType === DataType.Boolean);
      const boolValue = dataValue.value.value;
  
      const humanReadableString = _getHumanReadableString(node);
  
      let subStateNodes;
      if (boolValue) {
          subStateNodes = node.findReferencesExAsObject("HasTrueSubState",BrowseDirection.Forward);
      } else {
          subStateNodes = node.findReferencesExAsObject("HasFalseSubState",BrowseDirection.Forward);
      }
      const states = subStateNodes.forEach(function(n) {
          // todo happen
      });
  
      return humanReadableString;
  }
  function _getHumanReadableString(node) {
  
      let dataValue = node.id.readValue();
      if (dataValue.statusCode !== StatusCodes.Good) {
          return dataValue;
      }
      assert(dataValue.value.dataType === DataType.Boolean);
      const boolValue = dataValue.value.value;
  
      // The Value Attribute of a TwoStateVariable contains the current state as a human readable name.
      // The EnabledState for example, might contain the name “Enabled” when TRUE and “Disabled” when FALSE.
  
      let valueAsLocalizedText;
  
      if (boolValue) {
          const _trueState = (node._trueState) ? node._trueState: "TRUE";
          valueAsLocalizedText = { dataType: "LocalizedText", value: { text: _trueState}};
  
      } else {
          const _falseState = (node._falseState) ? node._falseState: "FALSE";
          valueAsLocalizedText = { dataType: "LocalizedText", value: { text: _falseState}};
      }
      dataValue = dataValue.clone();
      dataValue.value =new Variant(valueAsLocalizedText);
      return dataValue;
  
  }
  
  
  function _install_TwoStateVariable_machinery(node,options) {
  
      assert(node.dataTypeObj.browseName.toString()=== "LocalizedText");
      assert(node.minimumSamplingInterval === 0);
      assert(node.typeDefinitionObj.browseName.toString() === "TwoStateVariableType");
      assert(node.dataTypeObj.browseName.toString() === "LocalizedText");
      assert(node.hasOwnProperty("valueRank") && (node.valueRank === -1 || node.valueRank === 0));
      assert(node.hasOwnProperty("id"));
      options = options || {};
      // promote node into a UATwoStateVariable
      Object.setPrototypeOf(node,UATwoStateVariable.prototype);
      node.initialize(options);
  }
  
  
  /***
   * @class UATwoStateVariable
   * @constructor
   * @extends UAVariable
   */
  function UATwoStateVariable() {
      // UAVariable.apply(this,arguments);
  }
  util.inherits(UATwoStateVariable,UAVariable);
  
  /**
   * @method initialize
   * @private
   * @param options
   */
  UATwoStateVariable.prototype.initialize = function(options) {
  
      const node = this;
  
      if (options.trueState) {
          assert(options.falseState);
          assert(typeof(options.trueState)==="string" );
          assert(typeof(options.falseState)==="string" );
          node._trueState  = options.trueState;
          node._falseState = options.falseState;
  
          if (node.falseState) {
              node.falseState.bindVariable({
                  get: function() {
                      const node = this;
                      return new Variant({
                          dataType: DataType.LocalizedText,
                          value: node._falseState
                      });
                  }
              },true);
  
          }
          if (node.trueState) {
              node.trueState.bindVariable({
                  get: function () {
                      const node = this;
                      return new Variant({
                          dataType: DataType.LocalizedText,
                          value: node._trueState
                      });
                  }
              }, true);
          }
      }
      node.id.setValueFromSource( {dataType: "Boolean", value: false} , StatusCodes.UncertainInitialValue);
  
      // handle isTrueSubStateOf
      if (options.isTrueSubStateOf) {
          node.addReference({ referenceType: "HasTrueSubState", isForward: false, nodeId: options.isTrueSubStateOf});
      }
  
      if (options.isFalseSubStateOf) {
          node.addReference({ referenceType: "HasFalseSubState", isForward: false, nodeId: options.isFalseSubStateOf});
      }
  
      if(node.effectiveTransitionTime) {
          // install "value_changed" event handler on SubState that are already defined
          const subStates = [].concat(node.getTrueSubStates(),node.getFalseSubStates());
          for(let subState of subStates) {
              subState.on("value_changed",_updateEffectiveTransitionTime.bind(null,node,subState));
          }
      }
  
      // it should be possible to define a trueState and falseState LocalizedText even if the trueState or FalseState node
      // is not exposed. Therefore we need to store their value into dedicated variables.
      node.id.on("value_changed",function() {
          node._internal_set_dataValue(_getHumanReadableString(node));
      });
      node._internal_set_dataValue(_getHumanReadableString(node));
  
      // todo : also set the effectiveDisplayName if present
  
      // from spec Part 5
      // Release 1.03 OPC Unified Architecture, Part 5
      // EffectiveDisplayName contains a human readable name for the current state of the state
      // machine after taking the state of any SubStateMachines in account. There is no rule specified
      // for which state or sub-state should be used. It is up to the Server and will depend on the
      // semantics of the StateMachineType
      //
      // EffectiveDisplayName will be constructed by adding the EnableSdtate
      // and the State of the addTrue state
      if (node.effectiveDisplayName) {
          node.id.on("value_changed",function() {
              node.effectiveDisplayName._internal_set_dataValue(_getEffectiveDisplayName(node));
          });
          node.effectiveDisplayName._internal_set_dataValue(_getEffectiveDisplayName(node));
      }
  };
  
  const resolveNodeId = require("node-opcua-nodeid").resolveNodeId;
  const sameNodeId = require("node-opcua-nodeid").sameNodeId;
  
  const hasTrueSubState_ReferenceTypeNodeId = resolveNodeId("HasTrueSubState");
  const hasFalseSubState_ReferenceTypeNodeId = resolveNodeId("HasFalseSubState");
  
  // TODO : shall we care about overloading the remove_backward_reference method ?
  // some TrueSubState and FalseSubState relationship may be added later
  // so we need a mechanism to keep adding the "value_changed" event handle on subStates that
  // will be defined later.
  // install change detection on sub State
  // this is useful to change the effective transitionTime
  // EffectiveTransitionTime specifies the time when the current state or one of its sub states was entered.
  // If, for example, a LevelAlarm is active and – while active – switches several times between High and
  // HighHigh, then the TransitionTime stays at the point in time where the Alarm became active whereas the
  // EffectiveTransitionTime changes with each shift of a sub state.
  UATwoStateVariable.prototype._add_backward_reference = function(reference) {
      const self = this;
      const _base_add_backward_reference = UAVariable.prototype._add_backward_reference;
      // call base method
      _base_add_backward_reference.call(self,reference);
  
      if ( reference.isForward &&
           ( sameNodeId(reference.referenceType,hasTrueSubState_ReferenceTypeNodeId) ||
               sameNodeId(reference.referenceType,hasFalseSubState_ReferenceTypeNodeId)) ) {
  
          const addressSpace = self.addressSpace;
          // add event handle
          const subState = addressSpace.findNode(reference.nodeId);
          subState.on("value_changed",_updateEffectiveTransitionTime.bind(null,self,subState));
      }
  };
  
  /**
   * @method setValue
   * @param boolValue {Boolean}
   */
  UATwoStateVariable.prototype.setValue = function TwoStateVariable_setValue(boolValue) {
  
      const node = this;
      assert(_.isBoolean(boolValue));
      const dataValue = node.id.readValue();
      const oldValue = dataValue.value.value;
      if (dataValue.statusCode === StatusCodes.Good && boolValue === oldValue) {
          return; // nothing to do
      }
      //
      node.id.setValueFromSource(new Variant({dataType: DataType.Boolean, value: boolValue}));
      _updateTransitionTime(node);
      _updateEffectiveTransitionTime(node,node);
  };
  
  /**
   * @method getValue
   * @return {Boolean}
   */
  UATwoStateVariable.prototype.getValue = function TwoStateVariable_getValue() {
  
      const node = this;
      const dataValue = node.id.readValue();
      assert(dataValue.statusCode === StatusCodes.Good);
      assert(dataValue.value.dataType === DataType.Boolean);
      return dataValue.value.value;
  };
  /**
   * @method getValueAsString
   * @return {string}
   */
  UATwoStateVariable.prototype.getValueAsString = function TwoStateVariable_getValue() {
      const node = this;
      const dataValue = node.readValue();
      assert(dataValue.statusCode === StatusCodes.Good);
      assert(dataValue.value.dataType === DataType.LocalizedText);
      return dataValue.value.value.text.toString();
  
  };
  exports.UATwoStateVariable = UATwoStateVariable;
  
  exports.install = function (AddressSpace) {
  
      assert(_.isUndefined(AddressSpace._install_TwoStateVariable_machinery ));
      AddressSpace._install_TwoStateVariable_machinery = _install_TwoStateVariable_machinery;
  
      /**
       *
       * @method addTwoStateVariable
       *
       * @param options
       * @param options.browseName  {String}
       * @param [options.description {String}]
       * @param [options.modellingRule {String}]
       * @param [options.minimumSamplingInterval {Number} =0]
       * @param options.componentOf {Node|NodeId}
       * @param options.propertyOf {Node|NodeId}
       * @param options.trueState {String}
       * @param options.falseState {String}
       * @param [options.isTrueSubStateOf {NodeId}]
       * @param [options.isFalseSubStateOf {NodeId}]
       * @param [options.modellingRule]
       * @return {UATwoStateVariable}
       *
       * Optionals can be EffectiveDisplayName, TransitionTime, EffectiveTransitionTime
       */
      AddressSpace.prototype.addTwoStateVariable   = function (options) {
  
          return this._resolveRequestedNamespace(options).addTwoStateVariable(options);
  
      };
      Namespace.prototype.addTwoStateVariable = function(options){
  
          const namespace = this;
          assert(options.browseName," a browseName is required");
          const addressSpace = namespace.addressSpace;
  
          const twoStateVariableType = addressSpace.findVariableType("TwoStateVariableType");
  
          options.optionals = options.optionals || [];
          if (options.trueState) {
              options.optionals.push("TrueState");
          }
          if (options.falseState) {
              options.optionals.push("FalseState");
          }
  
          // we want event based change...
          options.minimumSamplingInterval = 0;
  
          const node = twoStateVariableType.instantiate({
              browseName: options.browseName,
  
              nodeId: options.nodeId,
  
              description: options.description,
  
              organizedBy: options.organizedBy,
              componentOf: options.componentOf,
  
              modellingRule: options.modellingRule,
  
              minimumSamplingInterval: options.minimumSamplingInterval,
              optionals: options.optionals
          });
  
          _install_TwoStateVariable_machinery(node,options);
  
          return node;
      };
  };
  
  
  