# backtick_javascript: true

module ::PackUnpack
  %x{
    var directives = [
      // Integer
      'C',
      'S',
      'L',
      'Q',
      'J',

      'c',
      's',
      'l',
      'q',
      'j',

      'n',
      'N',
      'v',
      'V',

      'U',
      'w',

      // Float
      'D',
      'd',
      'F',
      'f',
      'E',
      'e',
      'G',
      'g',

      // String
      'A',
      'a',
      'Z',
      'B',
      'b',
      'H',
      'h',
      'u',
      'M',
      'm',

      'P',
      'p',

      // Misc
      '@',
      'X',
      'x'
    ];

    var modifiers = [
      '!', // ignored
      '_', // ignored
      '>', // big endian
      '<'  // little endian
    ];

    self.eachDirectiveAndCount = function(format, callback) {
      var currentDirective,
          currentCount,
          currentModifiers,
          countSpecified;

      function reset() {
        currentDirective = null;
        currentCount = 0;
        currentModifiers = [];
        countSpecified = false;
      }

      reset();

      function yieldAndReset() {
        if (currentDirective == null) {
          reset();
          return;
        }

        var directiveSupportsModifiers = /[sSiIlLqQjJ]/.test(currentDirective);

        if (!directiveSupportsModifiers && currentModifiers.length > 0) {
          #{::Kernel.raise ::ArgumentError, "'#{`currentModifiers[0]`}' allowed only after types sSiIlLqQjJ"}
        }

        if (currentModifiers.indexOf('<') !== -1 && currentModifiers.indexOf('>') !== -1) {
          #{::Kernel.raise ::RangeError, "Can't use both '<' and '>'"}
        }

        if (!countSpecified) {
          currentCount = 1;
        }

        if (currentModifiers.indexOf('>') !== -1) {
          currentDirective = currentDirective + '>';
        }

        callback(currentDirective, currentCount);

        reset();
      }

      for (var i = 0; i < format.length; i++) {
        var currentChar = format[i];

        if (directives.indexOf(currentChar) !== -1) {
          // Directive char always resets current state
          yieldAndReset();
          currentDirective = currentChar;
        } else if (currentDirective) {
          if (/\d/.test(currentChar)) {
            // Count can be represented as a sequence of digits
            currentCount = currentCount * 10 + parseInt(currentChar, 10);
            countSpecified = true;
          } else if (currentChar === '*' && countSpecified === false) {
            // Count can be represented by a star character
            currentCount = Infinity;
            countSpecified = true;
          } else if (modifiers.indexOf(currentChar) !== -1 && countSpecified === false) {
            // Directives can be specified only after directive and before count
            currentModifiers.push(currentChar);
          } else {
            yieldAndReset();
          }
        }
      }

      yieldAndReset();
    }
  }
end
