goog.module('grrUi.forms.bytesFormDirective');
goog.module.declareLegacyNamespace();



/**
 * Encodes given bytes string to a hex-encoded string.
 * See https://mathiasbynens.be/notes/javascript-escapes#hexadecimal
 * for details.
 *
 * @param {string} bytes Bytes string.
 * @return {string} Hex-encoded string. Only characters with codes
 *     below than 32 and greater than 126 get encoded
 */
exports.bytesToHexEncodedString = function(bytes) {
  var segmentStart = 0;
  var str = [];

  var blen = bytes.length;
  for (var i = 0; i < blen; i += 1) {
    var c = bytes.charCodeAt(i);

    if (c < 32 || c > 126) {
      var segmentLength = i - segmentStart;
      if (segmentLength > 0) {
        str.push(bytes.substr(segmentStart, segmentLength));
      }
      segmentStart = i + 1;

      var hc = c.toString(16);
      if (hc.length == 1) {
        hc = '0' + hc;
      }
      str.push('\\x' + hc);
    }
  }

  segmentLength = blen - segmentStart;
  if (segmentLength > 0) {
    str.push(bytes.substr(segmentStart, segmentLength));
  }

  return str.join('');
};
var bytesToHexEncodedString = exports.bytesToHexEncodedString;



/**
 * Decodes given hex-encoded string into a bytes string.
 *
 * @param {string} str Hex-encoded string. Not every character has
 *     to be hex-encoded. Not encoded characters are preserved as they
 *     are.
 * @return {string} Bytes string.
 */
exports.hexEncodedStringToBytes = function(str) {
  return str.replace(/\\x([0-9A-Fa-f]{2})/g, function() {
    return String.fromCharCode(parseInt(arguments[1], 16));
  });
};
var hexEncodedStringToBytes = exports.hexEncodedStringToBytes;


/**
 * Checks if the string is a byte string: i.e. that it has no unicode
 * characters.
 *
 * @param {string} str String to be checked.
 * @return {boolean} True, if the string is a byte string, false otherwise.
 */
exports.isByteString = function(str) {
  var blen = str.length;
  for (var i = 0; i < blen; i += 1) {
    var c = str.charCodeAt(i);
    if (c >= 256) {
      return false;
    }
  }

  return true;
};
var isByteString = exports.isByteString;


/**
 * Controller for BytesFormDirective.
 * @unrestricted
 */
const BytesFormController = class {
  /**
   * @param {!angular.Scope} $scope
   * @param {!angular.$window} $window
   * @ngInject
   */
  constructor($scope, $window) {
    /** @private {!angular.Scope} */
    this.scope_ = $scope;

    /** @private {!angular.$window} */
    this.window_ = $window;

    /** @export {string} */
    this.valueString = '';

    this.scope_.$watch('value.value', this.onValueChange_.bind(this));
    this.scope_.$watch(
        'controller.valueString', this.onValueStringChange_.bind(this));
  }

  /**
   * Handles changes in the value bound to be edited by this directive.
   * As the value may be changed in onValueStringChange handler (i.e. when
   * user types a symbol), we're only changing this.valueString if
   * the bytes sequence it represents is different from newValue.
   *
   * @param {string} newValue New time value.
   * @private
   */
  onValueChange_(newValue) {
    if (angular.isString(newValue)) {
      try {
        var base64DecodedBytes = this.window_.atob(newValue);
        if (base64DecodedBytes != hexEncodedStringToBytes(this.valueString)) {
          this.valueString = bytesToHexEncodedString(base64DecodedBytes);
        }
      } catch (err) {
        this.valueString = '';
      }
    }
  }

  /**
   * Handles changes in string representation of the time value being edited.
   * Called when user types or deletes a symbol. Updates the actual value
   * that's bound to this directive via "value" binding.
   *
   * @param {string} newValue New string from the text input.
   * @private
   */
  onValueStringChange_(newValue) {
    if (angular.isUndefined(newValue)) {
      return;
    }

    if (!isByteString(newValue)) {
      // Annotate the value with 'validationError' annotation.
      // These annotations are meant to be used by the higher-level
      // UI components to disable submit buttons and show errors
      // summary.
      this.scope_['value']['validationError'] =
          'Unicode characters are not allowed in a byte string.';
      return;
    } else {
      delete this.scope_['value']['validationError'];
    }

    this.scope_['value']['value'] =
        this.window_.btoa(hexEncodedStringToBytes(newValue));
  }
};



/**
 * BytesFormDirective renders RDFBytes values..
 *
 * @return {!angular.Directive} Directive definition object.
 */
exports.BytesFormDirective = function() {
  return {
    restrict: 'E',
    scope: {value: '=', metadata: '='},
    templateUrl: '/static/angular-components/forms/bytes-form.html',
    controller: BytesFormController,
    controllerAs: 'controller'
  };
};


/**
 * Name of the directive in Angular.
 *
 * @const
 * @export
 */
exports.BytesFormDirective.directive_name = 'grrFormBytes';

/**
 * Semantic types corresponding to this directive.
 *
 * @const
 * @export
 */
exports.BytesFormDirective.semantic_type = 'RDFBytes';
