'use strict';

/*eslint-disable no-use-before-define*/

var common              = require('./common');
var YAMLException       = require('./exception');
var DEFAULT_FULL_SCHEMA = require('./schema/default_full');
var DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');

var _toString       = Object.prototype.toString;
var _hasOwnProperty = Object.prototype.hasOwnProperty;

var CHAR_TAB                  = 0x09; /* Tab */
var CHAR_LINE_FEED            = 0x0A; /* LF */
var CHAR_SPACE                = 0x20; /* Space */
var CHAR_EXCLAMATION          = 0x21; /* ! */
var CHAR_DOUBLE_QUOTE         = 0x22; /* " */
var CHAR_SHARP                = 0x23; /* # */
var CHAR_PERCENT              = 0x25; /* % */
var CHAR_AMPERSAND            = 0x26; /* & */
var CHAR_SINGLE_QUOTE         = 0x27; /* ' */
var CHAR_ASTERISK             = 0x2A; /* * */
var CHAR_COMMA                = 0x2C; /* , */
var CHAR_MINUS                = 0x2D; /* - */
var CHAR_COLON                = 0x3A; /* : */
var CHAR_GREATER_THAN         = 0x3E; /* > */
var CHAR_QUESTION             = 0x3F; /* ? */
var CHAR_COMMERCIAL_AT        = 0x40; /* @ */
var CHAR_LEFT_SQUARE_BRACKET  = 0x5B; /* [ */
var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */
var CHAR_GRAVE_ACCENT         = 0x60; /* ` */
var CHAR_LEFT_CURLY_BRACKET   = 0x7B; /* { */
var CHAR_VERTICAL_LINE        = 0x7C; /* | */
var CHAR_RIGHT_CURLY_BRACKET  = 0x7D; /* } */

var ESCAPE_SEQUENCES = {};

ESCAPE_SEQUENCES[0x00]   = '\\0';
ESCAPE_SEQUENCES[0x07]   = '\\a';
ESCAPE_SEQUENCES[0x08]   = '\\b';
ESCAPE_SEQUENCES[0x09]   = '\\t';
ESCAPE_SEQUENCES[0x0A]   = '\\n';
ESCAPE_SEQUENCES[0x0B]   = '\\v';
ESCAPE_SEQUENCES[0x0C]   = '\\f';
ESCAPE_SEQUENCES[0x0D]   = '\\r';
ESCAPE_SEQUENCES[0x1B]   = '\\e';
ESCAPE_SEQUENCES[0x22]   = '\\"';
ESCAPE_SEQUENCES[0x5C]   = '\\\\';
ESCAPE_SEQUENCES[0x85]   = '\\N';
ESCAPE_SEQUENCES[0xA0]   = '\\_';
ESCAPE_SEQUENCES[0x2028] = '\\L';
ESCAPE_SEQUENCES[0x2029] = '\\P';

var DEPRECATED_BOOLEANS_SYNTAX = [
  'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON',
  'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'
];

function compileStyleMap(schema, map) {
  var result, keys, index, length, tag, style, type;

  if (map === null) return {};

  result = {};
  keys = Object.keys(map);

  for (index = 0, length = keys.length; index < length; index += 1) {
    tag = keys[index];
    style = String(map[tag]);

    if (tag.slice(0, 2) === '!!') {
      tag = 'tag:yaml.org,2002:' + tag.slice(2);
    }
    type = schema.compiledTypeMap['fallback'][tag];

    if (type && _hasOwnProperty.call(type.styleAliases, style)) {
      style = type.styleAliases[style];
    }

    result[tag] = style;
  }

  return result;
}

function encodeHex(character) {
  var string, handle, length;

  string = character.toString(16).toUpperCase();

  if (character <= 0xFF) {
    handle = 'x';
    length = 2;
  } else if (character <= 0xFFFF) {
    handle = 'u';
    length = 4;
  } else if (character <= 0xFFFFFFFF) {
    handle = 'U';
    length = 8;
  } else {
    throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF');
  }

  return '\\' + handle + common.repeat('0', length - string.length) + string;
}

function State(options) {
  this.schema        = options['schema'] || DEFAULT_FULL_SCHEMA;
  this.indent        = Math.max(1, (options['indent'] || 2));
  this.noArrayIndent = options['noArrayIndent'] || false;
  this.skipInvalid   = options['skipInvalid'] || false;
  this.flowLevel     = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']);
  this.styleMap      = compileStyleMap(this.schema, options['styles'] || null);
  this.sortKeys      = options['sortKeys'] || false;
  this.lineWidth     = options['lineWidth'] || 80;
  this.noRefs        = options['noRefs'] || false;
  this.noCompatMode  = options['noCompatMode'] || false;
  this.condenseFlow  = options['condenseFlow'] || false;

  this.implicitTypes = this.schema.compiledImplicit;
  this.explicitTypes = this.schema.compiledExplicit;

  this.tag = null;
  this.result = '';

  this.duplicates = [];
  this.usedDuplicates = null;
}

// Indents every line in a string. Empty lines (\n only) are not indented.
function indentString(string, spaces) {
  var ind = common.repeat(' ', spaces),
      position = 0,
      next = -1,
      result = '',
      line,
      length = string.length;

  while (position < length) {
    next = string.indexOf('\n', position);
    if (next === -1) {
      line = string.slice(position);
      position = length;
    } else {
      line = string.slice(position, next + 1);
      position = next + 1;
    }

    if (line.length && line !== '\n') result += ind;

    result += line;
  }

  return result;
}

function generateNextLine(state, level) {
  return '\n' + common.repeat(' ', state.indent * level);
}

function testImplicitResolving(state, str) {
  var index, length, type;

  for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {
    type = state.implicitTypes[index];

    if (type.resolve(str)) {
      return true;
    }
  }

  return false;
}

// [33] s-white ::= s-space | s-tab
function isWhitespace(c) {
  return c === CHAR_SPACE || c === CHAR_TAB;
}

// Returns true if the character can be printed without escaping.
// From YAML 1.2: "any allowed characters known to be non-printable
// should also be escaped. [However,] This isn’t mandatory"
// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029.
function isPrintable(c) {
  return  (0x00020 <= c && c <= 0x00007E)
      || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029)
      || ((0x0E000 <= c && c <= 0x00FFFD) && c !== 0xFEFF /* BOM */)
      ||  (0x10000 <= c && c <= 0x10FFFF);
}

// Simplified test for values allowed after the first character in plain style.
function isPlainSafe(c) {
  // Uses a subset of nb-char - c-flow-indicator - ":" - "#"
  // where nb-char ::= c-printable - b-char - c-byte-order-mark.
  return isPrintable(c) && c !== 0xFEFF
    // - c-flow-indicator
    && c !== CHAR_COMMA
    && c !== CHAR_LEFT_SQUARE_BRACKET
    && c !== CHAR_RIGHT_SQUARE_BRACKET
    && c !== CHAR_LEFT_CURLY_BRACKET
    && c !== CHAR_RIGHT_CURLY_BRACKET
    // - ":" - "#"
    && c !== CHAR_COLON
    && c !== CHAR_SHARP;
}

// Simplified test for values allowed as the first character in plain style.
function isPlainSafeFirst(c) {
  // Uses a subset of ns-char - c-indicator
  // where ns-char = nb-char - s-white.
  return isPrintable(c) && c !== 0xFEFF
    && !isWhitespace(c) // - s-white
    // - (c-indicator ::=
    // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}”
    && c !== CHAR_MINUS
    && c !== CHAR_QUESTION
    && c !== CHAR_COLON
    && c !== CHAR_COMMA
    && c !== CHAR_LEFT_SQUARE_BRACKET
    && c !== CHAR_RIGHT_SQUARE_BRACKET
    && c !== CHAR_LEFT_CURLY_BRACKET
    && c !== CHAR_RIGHT_CURLY_BRACKET
    // | “#” | “&” | “*” | “!” | “|” | “>” | “'” | “"”
    && c !== CHAR_SHARP
    && c !== CHAR_AMPERSAND
    && c !== CHAR_ASTERISK
    && c !== CHAR_EXCLAMATION
    && c !== CHAR_VERTICAL_LINE
    && c !== CHAR_GREATER_THAN
    && c !== CHAR_SINGLE_QUOTE
    && c !== CHAR_DOUBLE_QUOTE
    // | “%” | “@” | “`”)
    && c !== CHAR_PERCENT
    && c !== CHAR_COMMERCIAL_AT
    && c !== CHAR_GRAVE_ACCENT;
}

// Determines whether block indentation indicator is required.
function needIndentIndicator(string) {
  var leadingSpaceRe = /^\n* /;
  return leadingSpaceRe.test(string);
}

var STYLE_PLAIN   = 1,
    STYLE_SINGLE  = 2,
    STYLE_LITERAL = 3,
    STYLE_FOLDED  = 4,
    STYLE_DOUBLE  = 5;

// Determines which scalar styles are possible and returns the preferred style.
// lineWidth = -1 => no limit.
// Pre-conditions: str.length > 0.
// Post-conditions:
//    STYLE_PLAIN or STYLE_SINGLE => no \n are in the string.
//    STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1).
//    STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1).
function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) {
  var i;
  var char;
  var hasLineBreak = false;
  var hasFoldableLine = false; // only checked if shouldTrackWidth
  var shouldTrackWidth = lineWidth !== -1;
  var previousLineBreak = -1; // count the first line correctly
  var plain = isPlainSafeFirst(string.charCodeAt(0))
          && !isWhitespace(string.charCodeAt(string.length - 1));

  if (singleLineOnly) {
    // Case: no block styles.
    // Check for disallowed characters to rule out plain and single.
    for (i = 0; i < string.length; i++) {
      char = string.charCodeAt(i);
      if (!isPrintable(char)) {
        return STYLE_DOUBLE;
      }
      plain = plain && isPlainSafe(char);
    }
  } else {
    // Case: block styles permitted.
    for (i = 0; i < string.length; i++) {
      char = string.charCodeAt(i);
      if (char === CHAR_LINE_FEED) {
        hasLineBreak = true;
        // Check if any line can be folded.
        if (shouldTrackWidth) {
          hasFoldableLine = hasFoldableLine ||
            // Foldable line = too long, and not more-indented.
            (i - previousLineBreak - 1 > lineWidth &&
             string[previousLineBreak + 1] !== ' ');
          previousLineBreak = i;
        }
      } else if (!isPrintable(char)) {
        return STYLE_DOUBLE;
      }
      plain = plain && isPlainSafe(char);
    }
    // in case the end is missing a \n
    hasFoldableLine = hasFoldableLine || (shouldTrackWidth &&
      (i - previousLineBreak - 1 > lineWidth &&
       string[previousLineBreak + 1] !== ' '));
  }
  // Although every style can represent \n without escaping, prefer block styles
  // for multiline, since they're more readable and they don't add empty lines.
  // Also prefer folding a super-long line.
  if (!hasLineBreak && !hasFoldableLine) {
    // Strings interpretable as another type have to be quoted;
    // e.g. the string 'true' vs. the boolean true.
    return plain && !testAmbiguousType(string)
      ? STYLE_PLAIN : STYLE_SINGLE;
  }
  // Edge case: block indentation indicator can only have one digit.
  if (indentPerLevel > 9 && needIndentIndicator(string)) {
    return STYLE_DOUBLE;
  }
  // At this point we know block styles are valid.
  // Prefer literal style unless we want to fold.
  return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;
}

// Note: line breaking/folding is implemented for only the folded style.
// NB. We drop the last trailing newline (if any) of a returned block scalar
//  since the dumper adds its own newline. This always works:
//    • No ending newline => unaffected; already using strip "-" chomping.
//    • Ending newline    => removed then restored.
//  Importantly, this keeps the "+" chomp indicator from gaining an extra line.
function writeScalar(state, string, level, iskey) {
  state.dump = (function () {
    if (string.length === 0) {
      return "''";
    }
    if (!state.noCompatMode &&
        DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) {
      return "'" + string + "'";
    }

    var indent = state.indent * Math.max(1, level); // no 0-indent scalars
    // As indentation gets deeper, let the width decrease monotonically
    // to the lower bound min(state.lineWidth, 40).
    // Note that this implies
    //  state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound.
    //  state.lineWidth > 40 + state.indent: width decreases until the lower bound.
    // This behaves better than a constant minimum width which disallows narrower options,
    // or an indent threshold which causes the width to suddenly increase.
    var lineWidth = state.lineWidth === -1
      ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);

    // Without knowing if keys are implicit/explicit, assume implicit for safety.
    var singleLineOnly = iskey
      // No block styles in flow mode.
      || (state.flowLevel > -1 && level >= state.flowLevel);
    function testAmbiguity(string) {
      return testImplicitResolving(state, string);
    }

    switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)) {
      case STYLE_PLAIN:
        return string;
      case STYLE_SINGLE:
        return "'" + string.replace(/'/g, "''") + "'";
      case STYLE_LITERAL:
        return '|' + blockHeader(string, state.indent)
          + dropEndingNewline(indentString(string, indent));
      case STYLE_FOLDED:
        return '>' + blockHeader(string, state.indent)
          + dropEndingNewline(indentString(foldString(string, lineWidth), indent));
      case STYLE_DOUBLE:
        return '"' + escapeString(string, lineWidth) + '"';
      default:
        throw new YAMLException('impossible error: invalid scalar style');
    }
  }());
}

// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9.
function blockHeader(string, indentPerLevel) {
  var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : '';

  // note the special case: the string '\n' counts as a "trailing" empty line.
  var clip =          string[string.length - 1] === '\n';
  var keep = clip && (string[string.length - 2] === '\n' || string === '\n');
  var chomp = keep ? '+' : (clip ? '' : '-');

  return indentIndicator + chomp + '\n';
}

// (See the note for writeScalar.)
function dropEndingNewline(string) {
  return string[string.length - 1] === '\n' ? string.slice(0, -1) : string;
}

// Note: a long line without a suitable break point will exceed the width limit.
// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0.
function foldString(string, width) {
  // In folded style, $k$ consecutive newlines output as $k+1$ newlines—
  // unless they're before or after a more-indented line, or at the very
  // beginning or end, in which case $k$ maps to $k$.
  // Therefore, parse each chunk as newline(s) followed by a content line.
  var lineRe = /(\n+)([^\n]*)/g;

  // first line (possibly an empty line)
  var result = (function () {
    var nextLF = string.indexOf('\n');
    nextLF = nextLF !== -1 ? nextLF : string.length;
    lineRe.lastIndex = nextLF;
    return foldLine(string.slice(0, nextLF), width);
  }());
  // If we haven't reached the first content line yet, don't add an extra \n.
  var prevMoreIndented = string[0] === '\n' || string[0] === ' ';
  var moreIndented;

  // rest of the lines
  var match;
  while ((match = lineRe.exec(string))) {
    var prefix = match[1], line = match[2];
    moreIndented = (line[0] === ' ');
    result += prefix
      + (!prevMoreIndented && !moreIndented && line !== ''
        ? '\n' : '')
      + foldLine(line, width);
    prevMoreIndented = moreIndented;
  }

  return result;
}

// Greedy line breaking.
// Picks the longest line under the limit each time,
// otherwise settles for the shortest line over the limit.
// NB. More-indented lines *cannot* be folded, as that would add an extra \n.
function foldLine(line, width) {
  if (line === '' || line[0] === ' ') return line;

  // Since a more-indented line adds a \n, breaks can't be followed by a space.
  var breakRe = / [^ ]/g; // note: the match index will always be <= length-2.
  var match;
  // start is an inclusive index. end, curr, and next are exclusive.
  var start = 0, end, curr = 0, next = 0;
  var result = '';

  // Invariants: 0 <= start <= length-1.
  //   0 <= curr <= next <= max(0, length-2). curr - start <= width.
  // Inside the loop:
  //   A match implies length >= 2, so curr and next are <= length-2.
  while ((match = breakRe.exec(line))) {
    next = match.index;
    // maintain invariant: curr - start <= width
    if (next - start > width) {
      end = (curr > start) ? curr : next; // derive end <= length-2
      result += '\n' + line.slice(start, end);
      // skip the space that was output as \n
      start = end + 1;                    // derive start <= length-1
    }
    curr = next;
  }

  // By the invariants, start <= length-1, so there is something left over.
  // It is either the whole string or a part starting from non-whitespace.
  result += '\n';
  // Insert a break if the remainder is too long and there is a break available.
  if (line.length - start > width && curr > start) {
    result += line.slice(start, curr) + '\n' + line.slice(curr + 1);
  } else {
    result += line.slice(start);
  }

  return result.slice(1); // drop extra \n joiner
}

// Escapes a double-quoted string.
function escapeString(string) {
  var result = '';
  var char, nextChar;
  var escapeSeq;

  for (var i = 0; i < string.length; i++) {
    char = string.charCodeAt(i);
    // Check for surrogate pairs (reference Unicode 3.0 section "3.7 Surrogates").
    if (char >= 0xD800 && char <= 0xDBFF/* high surrogate */) {
      nextChar = string.charCodeAt(i + 1);
      if (nextChar >= 0xDC00 && nextChar <= 0xDFFF/* low surrogate */) {
        // Combine the surrogate pair and store it escaped.
        result += encodeHex((char - 0xD800) * 0x400 + nextChar - 0xDC00 + 0x10000);
        // Advance index one extra since we already used that char here.
        i++; continue;
      }
    }
    escapeSeq = ESCAPE_SEQUENCES[char];
    result += !escapeSeq && isPrintable(char)
      ? string[i]
      : escapeSeq || encodeHex(char);
  }

  return result;
}

function writeFlowSequence(state, level, object) {
  var _result = '',
      _tag    = state.tag,
      index,
      length;

  for (index = 0, length = object.length; index < length; index += 1) {
    // Write only valid elements.
    if (writeNode(state, level, object[index], false, false)) {
      if (index !== 0) _result += ',' + (!state.condenseFlow ? ' ' : '');
      _result += state.dump;
    }
  }

  state.tag = _tag;
  state.dump = '[' + _result + ']';
}

function writeBlockSequence(state, level, object, compact) {
  var _result = '',
      _tag    = state.tag,
      index,
      length;

  for (index = 0, length = object.length; index < length; index += 1) {
    // Write only valid elements.
    if (writeNode(state, level + 1, object[index], true, true)) {
      if (!compact || index !== 0) {
        _result += generateNextLine(state, level);
      }

      if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
        _result += '-';
      } else {
        _result += '- ';
      }

      _result += state.dump;
    }
  }

  state.tag = _tag;
  state.dump = _result || '[]'; // Empty sequence if no valid values.
}

function writeFlowMapping(state, level, object) {
  var _result       = '',
      _tag          = state.tag,
      objectKeyList = Object.keys(object),
      index,
      length,
      objectKey,
      objectValue,
      pairBuffer;

  for (index = 0, length = objectKeyList.length; index < length; index += 1) {
    pairBuffer = state.condenseFlow ? '"' : '';

    if (index !== 0) pairBuffer += ', ';

    objectKey = objectKeyList[index];
    objectValue = object[objectKey];

    if (!writeNode(state, level, objectKey, false, false)) {
      continue; // Skip this pair because of invalid key;
    }

    if (state.dump.length > 1024) pairBuffer += '? ';

    pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' ');

    if (!writeNode(state, level, objectValue, false, false)) {
      continue; // Skip this pair because of invalid value.
    }

    pairBuffer += state.dump;

    // Both key and value are valid.
    _result += pairBuffer;
  }

  state.tag = _tag;
  state.dump = '{' + _result + '}';
}

function writeBlockMapping(state, level, object, compact) {
  var _result       = '',
      _tag          = state.tag,
      objectKeyList = Object.keys(object),
      index,
      length,
      objectKey,
      objectValue,
      explicitPair,
      pairBuffer;

  // Allow sorting keys so that the output file is deterministic
  if (state.sortKeys === true) {
    // Default sorting
    objectKeyList.sort();
  } else if (typeof state.sortKeys === 'function') {
    // Custom sort function
    objectKeyList.sort(state.sortKeys);
  } else if (state.sortKeys) {
    // Something is wrong
    throw new YAMLException('sortKeys must be a boolean or a function');
  }

  for (index = 0, length = objectKeyList.length; index < length; index += 1) {
    pairBuffer = '';

    if (!compact || index !== 0) {
      pairBuffer += generateNextLine(state, level);
    }

    objectKey = objectKeyList[index];
    objectValue = object[objectKey];

    if (!writeNode(state, level + 1, objectKey, true, true, true)) {
      continue; // Skip this pair because of invalid key.
    }

    explicitPair = (state.tag !== null && state.tag !== '?') ||
                   (state.dump && state.dump.length > 1024);

    if (explicitPair) {
      if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
        pairBuffer += '?';
      } else {
        pairBuffer += '? ';
      }
    }

    pairBuffer += state.dump;

    if (explicitPair) {
      pairBuffer += generateNextLine(state, level);
    }

    if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
      continue; // Skip this pair because of invalid value.
    }

    if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
      pairBuffer += ':';
    } else {
      pairBuffer += ': ';
    }

    pairBuffer += state.dump;

    // Both key and value are valid.
    _result += pairBuffer;
  }

  state.tag = _tag;
  state.dump = _result || '{}'; // Empty mapping if no valid pairs.
}

function detectType(state, object, explicit) {
  var _result, typeList, index, length, type, style;

  typeList = explicit ? state.explicitTypes : state.implicitTypes;

  for (index = 0, length = typeList.length; index < length; index += 1) {
    type = typeList[index];

    if ((type.instanceOf  || type.predicate) &&
        (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) &&
        (!type.predicate  || type.predicate(object))) {

      state.tag = explicit ? type.tag : '?';

      if (type.represent) {
        style = state.styleMap[type.tag] || type.defaultStyle;

        if (_toString.call(type.represent) === '[object Function]') {
          _result = type.represent(object, style);
        } else if (_hasOwnProperty.call(type.represent, style)) {
          _result = type.represent[style](object, style);
        } else {
          throw new YAMLException('!<' + type.tag + '> tag resolver accepts not "' + style + '" style');
        }

        state.dump = _result;
      }

      return true;
    }
  }

  return false;
}

// Serializes `object` and writes it to global `result`.
// Returns true on success, or false on invalid object.
//
function writeNode(state, level, object, block, compact, iskey) {
  state.tag = null;
  state.dump = object;

  if (!detectType(state, object, false)) {
    detectType(state, object, true);
  }

  var type = _toString.call(state.dump);

  if (block) {
    block = (state.flowLevel < 0 || state.flowLevel > level);
  }

  var objectOrArray = type === '[object Object]' || type === '[object Array]',
      duplicateIndex,
      duplicate;

  if (objectOrArray) {
    duplicateIndex = state.duplicates.indexOf(object);
    duplicate = duplicateIndex !== -1;
  }

  if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) {
    compact = false;
  }

  if (duplicate && state.usedDuplicates[duplicateIndex]) {
    state.dump = '*ref_' + duplicateIndex;
  } else {
    if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
      state.usedDuplicates[duplicateIndex] = true;
    }
    if (type === '[object Object]') {
      if (block && (Object.keys(state.dump).length !== 0)) {
        writeBlockMapping(state, level, state.dump, compact);
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + state.dump;
        }
      } else {
        writeFlowMapping(state, level, state.dump);
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
        }
      }
    } else if (type === '[object Array]') {
      var arrayLevel = (state.noArrayIndent && (level > 0)) ? level - 1 : level;
      if (block && (state.dump.length !== 0)) {
        writeBlockSequence(state, arrayLevel, state.dump, compact);
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + state.dump;
        }
      } else {
        writeFlowSequence(state, arrayLevel, state.dump);
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
        }
      }
    } else if (type === '[object String]') {
      if (state.tag !== '?') {
        writeScalar(state, state.dump, level, iskey);
      }
    } else {
      if (state.skipInvalid) return false;
      throw new YAMLException('unacceptable kind of an object to dump ' + type);
    }

    if (state.tag !== null && state.tag !== '?') {
      state.dump = '!<' + state.tag + '> ' + state.dump;
    }
  }

  return true;
}

function getDuplicateReferences(object, state) {
  var objects = [],
      duplicatesIndexes = [],
      index,
      length;

  inspectNode(object, objects, duplicatesIndexes);

  for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {
    state.duplicates.push(objects[duplicatesIndexes[index]]);
  }
  state.usedDuplicates = new Array(length);
}

function inspectNode(object, objects, duplicatesIndexes) {
  var objectKeyList,
      index,
      length;

  if (object !== null && typeof object === 'object') {
    index = objects.indexOf(object);
    if (index !== -1) {
      if (duplicatesIndexes.indexOf(index) === -1) {
        duplicatesIndexes.push(index);
      }
    } else {
      objects.push(object);

      if (Array.isArray(object)) {
        for (index = 0, length = object.length; index < length; index += 1) {
          inspectNode(object[index], objects, duplicatesIndexes);
        }
      } else {
        objectKeyList = Object.keys(object);

        for (index = 0, length = objectKeyList.length; index < length; index += 1) {
          inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
        }
      }
    }
  }
}

function dump(input, options) {
  options = options || {};

  var state = new State(options);

  if (!state.noRefs) getDuplicateReferences(input, state);

  if (writeNode(state, 0, input, true, true)) return state.dump + '\n';

  return '';
}

function safeDump(input, options) {
  return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
}

module.exports.dump     = dump;
module.exports.safeDump = safeDump;


#!/bin/sh
# This file is licensed under the BSD-3-Clause license.
# See the AUTHORS and LICENSE files for more information.

. ../spec_helper.sh
. ../../share/zfsnap/core.sh

# These include a TTL delimiter, and should be trimmed accordingly
ItsRetvalIs "TrimToTTL 'zpool@2011-04-05_02.06.00--1y'"                "1y"        0 # pool w/o child w/ snapshot
ItsRetvalIs "TrimToTTL 'z--pool@2011-04-05_02.06.00--1y4d5s'"          "1y4d5s"    0 # TTL delim in poolname w/ snapshot
ItsRetvalIs "TrimToTTL 'z--pool@prefix--2011-04-05_02.06.00--1y4d5s'"  "1y4d5s"    0 # TTL delim in poolname and prefix w/ snapshot
ItsRetvalIs "TrimToTTL 'var@1y--2011-04-05_02.06.00--8m5d32M'"         "8m5d32M"   0 # Prefix is in TTL format with TTL delim
ItsRetvalIs "TrimToTTL 'var@wtf--1y-2011-04-05_02.06.00--2w8d'"        "2w8d"      0 # Prefix is in TTL format with TTL delim
ItsRetvalIs "TrimToTTL 'zpool/child@2010-04-05_02.06.00--1m'"          "1m"        0 # w/ child w/ snapshot
ItsRetvalIs "TrimToTTL 'zpool/child/grandchild@2009-06-08_02.06.00--7y5h'" "7y5h"  0 # w/ grandchild w/ snapshot

# These don't contain a TTL delimiter, and should return an empty string
ItsRetvalIs "TrimToTTL ''"                                             ""          1 # empty
ItsRetvalIs "TrimToTTL 'z--pool'"                                      ""          1 # "pool" is not a valid TTL
ItsRetvalIs "TrimToTTL 'zpool_child'"                                  ""          1 # special character in poolname
ItsRetvalIs "TrimToTTL 'zpool/child'"                                  ""          1 # w/ child w/o snapshot
ItsRetvalIs "TrimToTTL 'zpool/child/grandchild'"                       ""          1 # w/ grandchild w/o snapshot

ExitTests


// -*- C++ -*-

// Copyright (C) 2004-2008 The Trustees of Indiana University.
// Copyright (C) 2007  Douglas Gregor <doug.gregor@gmail.com>
// Copyright (C) 2007  Matthias Troyer  <troyer@boost-consulting.com>

// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  Authors: Douglas Gregor
//           Andrew Lumsdaine
//           Matthias Troyer

//#define PBGL_PROCESS_GROUP_DEBUG

#ifndef BOOST_GRAPH_USE_MPI
#error "Parallel BGL files should not be included unless <boost/graph/use_mpi.hpp> has been included"
#endif

#include <boost/assert.hpp>
#include <algorithm>
#include <boost/graph/parallel/detail/untracked_pair.hpp>
#include <numeric>
#include <iterator>
#include <functional>
#include <vector>
#include <queue>
#include <stack>
#include <list>
#include <boost/graph/distributed/detail/tag_allocator.hpp>
#include <stdio.h>

// #define PBGL_PROCESS_GROUP_DEBUG

#ifdef PBGL_PROCESS_GROUP_DEBUG
#  include <iostream>
#endif

namespace boost { namespace graph { namespace distributed {

struct mpi_process_group::impl
{

  typedef mpi_process_group::message_header message_header;
  typedef mpi_process_group::outgoing_messages outgoing_messages;

  /**
   * Stores the incoming messages from a particular processor.
   *
   * @todo Evaluate whether we should use a deque instance, which
   * would reduce could reduce the cost of "receiving" messages and
     allow us to deallocate memory earlier, but increases the time
     spent in the synchronization step.
   */
  struct incoming_messages {
    incoming_messages();
    ~incoming_messages() {}

    std::vector<message_header> headers;
    buffer_type                 buffer;
    std::vector<std::vector<message_header>::iterator> next_header;
  };

  struct batch_request {
    MPI_Request request;
    buffer_type buffer;
  };

  // send once we have a certain number of messages or bytes in the buffer
  // these numbers need to be tuned, we keep them small at first for testing
  std::size_t batch_header_number;
  std::size_t batch_buffer_size;
  std::size_t batch_message_size;

  /**
   * The actual MPI communicator used to transmit data.
   */
  boost::mpi::communicator             comm;

  /**
   * The MPI communicator used to transmit out-of-band replies.
   */
  boost::mpi::communicator             oob_reply_comm;

  /// Outgoing message information, indexed by destination processor.
  std::vector<outgoing_messages> outgoing;

  /// Incoming message information, indexed by source processor.
  std::vector<incoming_messages> incoming;

  /// The numbers of processors that have entered a synchronization stage
  std::vector<int> processors_synchronizing_stage;

  /// The synchronization stage of a processor
  std::vector<int> synchronizing_stage;

  /// Number of processors still sending messages
  std::vector<int> synchronizing_unfinished;

  /// Number of batches sent since last synchronization stage
  std::vector<int> number_sent_batches;

  /// Number of batches received minus number of expected batches
  std::vector<int> number_received_batches;

  /// The context of the currently-executing trigger, or @c trc_none
  /// if no trigger is executing.
  trigger_receive_context trigger_context;

  /// Non-zero indicates that we're processing batches
  /// Increment this when processing patches,
  /// decrement it when you're done.
  int processing_batches;

  /**
   * Contains all of the active blocks corresponding to attached
   * distributed data structures.
   */
  blocks_type blocks;

  /// Whether we are currently synchronizing
  bool synchronizing;

  /// The MPI requests for posted sends of oob messages
  std::vector<MPI_Request> requests;

  /// The MPI buffers for posted irecvs of oob messages
  std::map<int,buffer_type> buffers;

  /// Queue for message batches received while already processing messages
  std::queue<std::pair<int,outgoing_messages> > new_batches;
  /// Maximum encountered size of the new_batches queue
  std::size_t max_received;

  /// The MPI requests and buffers for batchess being sent
  std::list<batch_request> sent_batches;
  /// Maximum encountered size of the sent_batches list
  std::size_t max_sent;

  /// Pre-allocated requests in a pool
  std::vector<batch_request> batch_pool;
  /// A stack controlling which batches are available
  std::stack<std::size_t> free_batches;

  void free_sent_batches();

  // Tag allocator
  detail::tag_allocator allocated_tags;

  impl(std::size_t num_headers, std::size_t buffers_size,
       communicator_type parent_comm);
  ~impl();

private:
  void set_batch_size(std::size_t header_num, std::size_t buffer_sz);
};

inline trigger_receive_context mpi_process_group::trigger_context() const
{
  return impl_->trigger_context;
}

template<typename T>
void
mpi_process_group::send_impl(int dest, int tag, const T& value,
                             mpl::true_ /*is_mpi_datatype*/) const
{
  BOOST_ASSERT(tag <  msg_reserved_first || tag > msg_reserved_last);

  impl::outgoing_messages& outgoing = impl_->outgoing[dest];

  // Start constructing the message header
  impl::message_header header;
  header.source = process_id(*this);
  header.tag = tag;
  header.offset = outgoing.buffer.size();

  boost::mpi::packed_oarchive oa(impl_->comm, outgoing.buffer);
  oa << value;

#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << "SEND: " << process_id(*this) << " -> " << dest << ", tag = "
            << tag << ", bytes = " << packed_size << std::endl;
#endif

  // Store the header
  header.bytes = outgoing.buffer.size() - header.offset;
  outgoing.headers.push_back(header);

  maybe_send_batch(dest);
}

template<typename T>
void
mpi_process_group::send_impl(int dest, int tag, const T& value,
                             mpl::false_ /*is_mpi_datatype*/) const
{
  BOOST_ASSERT(tag <  msg_reserved_first || tag > msg_reserved_last);

  impl::outgoing_messages& outgoing = impl_->outgoing[dest];

  // Start constructing the message header
  impl::message_header header;
  header.source = process_id(*this);
  header.tag = tag;
  header.offset = outgoing.buffer.size();

  // Serialize into the buffer
  boost::mpi::packed_oarchive out(impl_->comm, outgoing.buffer);
  out << value;

  // Store the header
  header.bytes = outgoing.buffer.size() - header.offset;
  outgoing.headers.push_back(header);
  maybe_send_batch(dest);

#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << "SEND: " << process_id(*this) << " -> " << dest << ", tag = "
            << tag << ", bytes = " << header.bytes << std::endl;
#endif
}

template<typename T>
inline void
send(const mpi_process_group& pg, mpi_process_group::process_id_type dest,
     int tag, const T& value)
{
  pg.send_impl(dest, pg.encode_tag(pg.my_block_number(), tag), value,
               boost::mpi::is_mpi_datatype<T>());
}

template<typename T>
typename enable_if<boost::mpi::is_mpi_datatype<T>, void>::type
send(const mpi_process_group& pg, mpi_process_group::process_id_type dest,
     int tag, const T values[], std::size_t n)
{
  pg.send_impl(dest, pg.encode_tag(pg.my_block_number(), tag),
                 boost::serialization::make_array(values,n),
                 boost::mpl::true_());
}

template<typename T>
typename disable_if<boost::mpi::is_mpi_datatype<T>, void>::type
mpi_process_group::
array_send_impl(int dest, int tag, const T values[], std::size_t n) const
{
  BOOST_ASSERT(tag <  msg_reserved_first || tag > msg_reserved_last);

  impl::outgoing_messages& outgoing = impl_->outgoing[dest];

  // Start constructing the message header
  impl::message_header header;
  header.source = process_id(*this);
  header.tag = tag;
  header.offset = outgoing.buffer.size();

  // Serialize into the buffer
  boost::mpi::packed_oarchive out(impl_->comm, outgoing.buffer);
  out << n;

  for (std::size_t i = 0; i < n; ++i)
    out << values[i];

  // Store the header
  header.bytes = outgoing.buffer.size() - header.offset;
  outgoing.headers.push_back(header);
  maybe_send_batch(dest);

#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << "SEND: " << process_id(*this) << " -> " << dest << ", tag = "
            << tag << ", bytes = " << header.bytes << std::endl;
#endif
}

template<typename T>
typename disable_if<boost::mpi::is_mpi_datatype<T>, void>::type
send(const mpi_process_group& pg, mpi_process_group::process_id_type dest,
     int tag, const T values[], std::size_t n)
{
  pg.array_send_impl(dest, pg.encode_tag(pg.my_block_number(), tag),
                     values, n);
}

template<typename InputIterator>
void
send(const mpi_process_group& pg, mpi_process_group::process_id_type dest,
     int tag, InputIterator first, InputIterator last)
{
  typedef typename std::iterator_traits<InputIterator>::value_type value_type;
  std::vector<value_type> values(first, last);
  if (values.empty()) send(pg, dest, tag, static_cast<value_type*>(0), 0);
  else send(pg, dest, tag, &values[0], values.size());
}

template<typename T>
bool
mpi_process_group::receive_impl(int source, int tag, T& value,
                                mpl::true_ /*is_mpi_datatype*/) const
{
#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << "RECV: " << process_id(*this) << " <- " << source << ", tag = "
            << tag << std::endl;
#endif

  impl::incoming_messages& incoming = impl_->incoming[source];

  // Find the next header with the right tag
  std::vector<impl::message_header>::iterator header =
    incoming.next_header[my_block_number()];
  while (header != incoming.headers.end() && header->tag != tag) ++header;

  // If no header is found, notify the caller
  if (header == incoming.headers.end()) return false;

  // Unpack the data
  if (header->bytes > 0) {
    boost::mpi::packed_iarchive ia(impl_->comm, incoming.buffer,
                                   archive::no_header, header->offset);
    ia >> value;
  }

  // Mark this message as received
  header->tag = -1;

  // Move the "next header" indicator to the next unreceived message
  while (incoming.next_header[my_block_number()] != incoming.headers.end()
         && incoming.next_header[my_block_number()]->tag == -1)
    ++incoming.next_header[my_block_number()];

  if (incoming.next_header[my_block_number()] == incoming.headers.end()) {
    bool finished = true;
    for (std::size_t i = 0; i < incoming.next_header.size() && finished; ++i) {
      if (incoming.next_header[i] != incoming.headers.end()) finished = false;
    }

    if (finished) {
      std::vector<impl::message_header> no_headers;
      incoming.headers.swap(no_headers);
      buffer_type empty_buffer;
      incoming.buffer.swap(empty_buffer);
      for (std::size_t i = 0; i < incoming.next_header.size(); ++i)
        incoming.next_header[i] = incoming.headers.end();
    }
  }

  return true;
}

template<typename T>
bool
mpi_process_group::receive_impl(int source, int tag, T& value,
                                mpl::false_ /*is_mpi_datatype*/) const
{
  impl::incoming_messages& incoming = impl_->incoming[source];

  // Find the next header with the right tag
  std::vector<impl::message_header>::iterator header =
    incoming.next_header[my_block_number()];
  while (header != incoming.headers.end() && header->tag != tag) ++header;

  // If no header is found, notify the caller
  if (header == incoming.headers.end()) return false;

  // Deserialize the data
  boost::mpi::packed_iarchive in(impl_->comm, incoming.buffer,
                                 archive::no_header, header->offset);
  in >> value;

  // Mark this message as received
  header->tag = -1;

  // Move the "next header" indicator to the next unreceived message
  while (incoming.next_header[my_block_number()] != incoming.headers.end()
         && incoming.next_header[my_block_number()]->tag == -1)
    ++incoming.next_header[my_block_number()];

  if (incoming.next_header[my_block_number()] == incoming.headers.end()) {
    bool finished = true;
    for (std::size_t i = 0; i < incoming.next_header.size() && finished; ++i) {
      if (incoming.next_header[i] != incoming.headers.end()) finished = false;
    }

    if (finished) {
      std::vector<impl::message_header> no_headers;
      incoming.headers.swap(no_headers);
      buffer_type empty_buffer;
      incoming.buffer.swap(empty_buffer);
      for (std::size_t i = 0; i < incoming.next_header.size(); ++i)
        incoming.next_header[i] = incoming.headers.end();
    }
  }

  return true;
}

template<typename T>
typename disable_if<boost::mpi::is_mpi_datatype<T>, bool>::type
mpi_process_group::
array_receive_impl(int source, int tag, T* values, std::size_t& n) const
{
  impl::incoming_messages& incoming = impl_->incoming[source];

  // Find the next header with the right tag
  std::vector<impl::message_header>::iterator header =
    incoming.next_header[my_block_number()];
  while (header != incoming.headers.end() && header->tag != tag) ++header;

  // If no header is found, notify the caller
  if (header == incoming.headers.end()) return false;

  // Deserialize the data
  boost::mpi::packed_iarchive in(impl_->comm, incoming.buffer,
                                 archive::no_header, header->offset);
  std::size_t num_sent;
  in >> num_sent;
  if (num_sent > n)
    std::cerr << "ERROR: Have " << num_sent << " items but only space for "
              << n << " items\n";

  for (std::size_t i = 0; i < num_sent; ++i)
    in >> values[i];
  n = num_sent;

  // Mark this message as received
  header->tag = -1;

  // Move the "next header" indicator to the next unreceived message
  while (incoming.next_header[my_block_number()] != incoming.headers.end()
         && incoming.next_header[my_block_number()]->tag == -1)
    ++incoming.next_header[my_block_number()];

  if (incoming.next_header[my_block_number()] == incoming.headers.end()) {
    bool finished = true;
    for (std::size_t i = 0; i < incoming.next_header.size() && finished; ++i) {
      if (incoming.next_header[i] != incoming.headers.end()) finished = false;
    }

    if (finished) {
      std::vector<impl::message_header> no_headers;
      incoming.headers.swap(no_headers);
      buffer_type empty_buffer;
      incoming.buffer.swap(empty_buffer);
      for (std::size_t i = 0; i < incoming.next_header.size(); ++i)
        incoming.next_header[i] = incoming.headers.end();
    }
  }

  return true;
}

// Construct triggers
template<typename Type, typename Handler>
void mpi_process_group::trigger(int tag, const Handler& handler)
{
  BOOST_ASSERT(block_num);
  install_trigger(tag,my_block_number(),shared_ptr<trigger_base>(
    new trigger_launcher<Type, Handler>(*this, tag, handler)));
}

template<typename Type, typename Handler>
void mpi_process_group::trigger_with_reply(int tag, const Handler& handler)
{
  BOOST_ASSERT(block_num);
  install_trigger(tag,my_block_number(),shared_ptr<trigger_base>(
    new reply_trigger_launcher<Type, Handler>(*this, tag, handler)));
}

template<typename Type, typename Handler>
void mpi_process_group::global_trigger(int tag, const Handler& handler,
      std::size_t sz)
{
  if (sz==0) // normal trigger
    install_trigger(tag,0,shared_ptr<trigger_base>(
      new global_trigger_launcher<Type, Handler>(*this, tag, handler)));
  else // trigger with irecv
    install_trigger(tag,0,shared_ptr<trigger_base>(
      new global_irecv_trigger_launcher<Type, Handler>(*this, tag, handler,sz)));

}

namespace detail {

template<typename Type>
void  do_oob_receive(mpi_process_group const& self,
    int source, int tag, Type& data, mpl::true_ /*is_mpi_datatype*/)
{
  using boost::mpi::get_mpi_datatype;

  //self.impl_->comm.recv(source,tag,data);
  MPI_Recv(&data, 1, get_mpi_datatype<Type>(data), source, tag, self.impl_->comm,
           MPI_STATUS_IGNORE);
}

template<typename Type>
void do_oob_receive(mpi_process_group const& self,
    int source, int tag, Type& data, mpl::false_ /*is_mpi_datatype*/)
{
  //  self.impl_->comm.recv(source,tag,data);
  // Receive the size of the data packet
  boost::mpi::status status;
  status = self.impl_->comm.probe(source, tag);

#if BOOST_VERSION >= 103600
  int size = status.count<boost::mpi::packed>().get();
#else
  int size;
  MPI_Status& mpi_status = status;
  MPI_Get_count(&mpi_status, MPI_PACKED, &size);
#endif

  // Receive the data packed itself
  boost::mpi::packed_iarchive in(self.impl_->comm);
  in.resize(size);
  MPI_Recv(in.address(), size, MPI_PACKED, source, tag, self.impl_->comm,
       MPI_STATUS_IGNORE);

  // Deserialize the data
  in >> data;
}

template<typename Type>
void do_oob_receive(mpi_process_group const& self, int source, int tag, Type& data)
{
  do_oob_receive(self, source, tag, data,
                           boost::mpi::is_mpi_datatype<Type>());
}

} // namespace detail

template<typename Type, typename Handler>
void
mpi_process_group::trigger_launcher<Type, Handler>::
receive(mpi_process_group const&, int source, int tag,
        trigger_receive_context context, int block) const
{
#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << (out_of_band? "OOB trigger" : "Trigger")
            << " receive from source " << source << " and tag " << tag
        << " in block " << (block == -1 ? self.my_block_number() : block) << std::endl;
#endif

  Type data;

  if (context == trc_out_of_band) {
    // Receive the message directly off the wire
    int realtag  = self.encode_tag(
      block == -1 ? self.my_block_number() : block, tag);
    detail::do_oob_receive(self,source,realtag,data);
  }
  else
    // Receive the message out of the local buffer
    boost::graph::distributed::receive(self, source, tag, data);

  // Pass the message off to the handler
  handler(source, tag, data, context);
}

template<typename Type, typename Handler>
void
mpi_process_group::reply_trigger_launcher<Type, Handler>::
receive(mpi_process_group const&, int source, int tag,
        trigger_receive_context context, int block) const
{
#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << (out_of_band? "OOB reply trigger" : "Reply trigger")
            << " receive from source " << source << " and tag " << tag
        << " in block " << (block == -1 ? self.my_block_number() : block) << std::endl;
#endif
  BOOST_ASSERT(context == trc_out_of_band);

  boost::parallel::detail::untracked_pair<int, Type> data;

  // Receive the message directly off the wire
  int realtag  = self.encode_tag(block == -1 ? self.my_block_number() : block,
                                 tag);
  detail::do_oob_receive(self, source, realtag, data);

  // Pass the message off to the handler and send the result back to
  // the source.
  send_oob(self, source, data.first,
           handler(source, tag, data.second, context), -2);
}

template<typename Type, typename Handler>
void
mpi_process_group::global_trigger_launcher<Type, Handler>::
receive(mpi_process_group const& self, int source, int tag,
        trigger_receive_context context, int block) const
{
#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << (out_of_band? "OOB trigger" : "Trigger")
            << " receive from source " << source << " and tag " << tag
        << " in block " << (block == -1 ? self.my_block_number() : block) << std::endl;
#endif

  Type data;

  if (context == trc_out_of_band) {
    // Receive the message directly off the wire
    int realtag  = self.encode_tag(
      block == -1 ? self.my_block_number() : block, tag);
    detail::do_oob_receive(self,source,realtag,data);
  }
  else
    // Receive the message out of the local buffer
    boost::graph::distributed::receive(self, source, tag, data);

  // Pass the message off to the handler
  handler(self, source, tag, data, context);
}

template<typename Type, typename Handler>
void
mpi_process_group::global_irecv_trigger_launcher<Type, Handler>::
receive(mpi_process_group const& self, int source, int tag,
        trigger_receive_context context, int block) const
{
#ifdef PBGL_PROCESS_GROUP_DEBUG
  std::cerr << (out_of_band? "OOB trigger" : "Trigger")
            << " receive from source " << source << " and tag " << tag
        << " in block " << (block == -1 ? self.my_block_number() : block) << std::endl;
#endif

  Type data;

  if (context == trc_out_of_band) {
    return;
  }
  BOOST_ASSERT (context == trc_irecv_out_of_band);

  // force posting of new MPI_Irecv, even though buffer is already allocated
  boost::mpi::packed_iarchive ia(self.impl_->comm,self.impl_->buffers[tag]);
  ia >> data;
  // Start a new receive
  prepare_receive(self,tag,true);
  // Pass the message off to the handler
  handler(self, source, tag, data, context);
}

template<typename Type, typename Handler>
void
mpi_process_group::global_irecv_trigger_launcher<Type, Handler>::
prepare_receive(mpi_process_group const& self, int tag, bool force) const
{
#ifdef PBGL_PROCESS_GROUP_DEBUG
 std::cerr << ("Posting Irecv for trigger")
      << " receive with tag " << tag << std::endl;
#endif
  if (self.impl_->buffers.find(tag) == self.impl_->buffers.end()) {
    self.impl_->buffers[tag].resize(buffer_size);
    force = true;
  }
  BOOST_ASSERT(static_cast<int>(self.impl_->buffers[tag].size()) >= buffer_size);

  //BOOST_MPL_ASSERT(mpl::not_<is_mpi_datatype<Type> >);
  if (force) {
    self.impl_->requests.push_back(MPI_Request());
    MPI_Request* request = &self.impl_->requests.back();
    MPI_Irecv(&self.impl_->buffers[tag].front(),buffer_size,
               MPI_PACKED,MPI_ANY_SOURCE,tag,self.impl_->comm,request);
  }
}

template<typename T>
inline mpi_process_group::process_id_type
receive(const mpi_process_group& pg, int tag, T& value)
{
  for (std::size_t source = 0; source < pg.impl_->incoming.size(); ++source) {
    if (pg.receive_impl(source, pg.encode_tag(pg.my_block_number(), tag),
                        value, boost::mpi::is_mpi_datatype<T>()))
      return source;
  }
  BOOST_ASSERT (false);
}

template<typename T>
typename
  enable_if<boost::mpi::is_mpi_datatype<T>,
            std::pair<mpi_process_group::process_id_type, std::size_t> >::type
receive(const mpi_process_group& pg, int tag, T values[], std::size_t n)
{
  for (std::size_t source = 0; source < pg.impl_->incoming.size(); ++source) {
    bool result =
      pg.receive_impl(source, pg.encode_tag(pg.my_block_number(), tag),
                 boost::serialization::make_array(values,n),
                 boost::mpl::true_());
    if (result)
      return std::make_pair(source, n);
  }
  BOOST_ASSERT(false);
}

template<typename T>
typename
  disable_if<boost::mpi::is_mpi_datatype<T>,
             std::pair<mpi_process_group::process_id_type, std::size_t> >::type
receive(const mpi_process_group& pg, int tag, T values[], std::size_t n)
{
  for (std::size_t source = 0; source < pg.impl_->incoming.size(); ++source) {
    if (pg.array_receive_impl(source, pg.encode_tag(pg.my_block_number(), tag),
                              values, n))
      return std::make_pair(source, n);
  }
  BOOST_ASSERT(false);
}

template<typename T>
mpi_process_group::process_id_type
receive(const mpi_process_group& pg,
        mpi_process_group::process_id_type source, int tag, T& value)
{
  if (pg.receive_impl(source, pg.encode_tag(pg.my_block_number(), tag),
                      value, boost::mpi::is_mpi_datatype<T>()))
    return source;
  else {
    fprintf(stderr,
            "Process %d failed to receive a message from process %d with tag %d in block %d.\n",
            process_id(pg), source, tag, pg.my_block_number());

    BOOST_ASSERT(false);
    abort();
  }
}

template<typename T>
typename
  enable_if<boost::mpi::is_mpi_datatype<T>,
            std::pair<mpi_process_group::process_id_type, std::size_t> >::type
receive(const mpi_process_group& pg, int source, int tag, T values[],
        std::size_t n)
{
  if (pg.receive_impl(source, pg.encode_tag(pg.my_block_number(), tag),
                      boost::serialization::make_array(values,n),
                      boost::mpl::true_()))
    return std::make_pair(source,n);
  else {
    fprintf(stderr,
            "Process %d failed to receive a message from process %d with tag %d in block %d.\n",
            process_id(pg), source, tag, pg.my_block_number());

    BOOST_ASSERT(false);
    abort();
  }
}

template<typename T>
typename
  disable_if<boost::mpi::is_mpi_datatype<T>,
             std::pair<mpi_process_group::process_id_type, std::size_t> >::type
receive(const mpi_process_group& pg, int source, int tag, T values[],
        std::size_t n)
{
  pg.array_receive_impl(source, pg.encode_tag(pg.my_block_number(), tag),
                        values, n);

  return std::make_pair(source, n);
}

template<typename T, typename BinaryOperation>
T*
all_reduce(const mpi_process_group& pg, T* first, T* last, T* out,
           BinaryOperation bin_op)
{
  synchronize(pg);

  bool inplace = first == out;

  if (inplace) out = new T [last-first];

  boost::mpi::all_reduce(boost::mpi::communicator(communicator(pg),
                                                  boost::mpi::comm_attach),
                         first, last-first, out, bin_op);

  if (inplace) {
    std::copy(out, out + (last-first), first);
    delete [] out;
    return last;
  }

  return out;
}

template<typename T>
void
broadcast(const mpi_process_group& pg, T& val,
          mpi_process_group::process_id_type root)
{
  // broadcast the seed
  boost::mpi::communicator comm(communicator(pg),boost::mpi::comm_attach);
  boost::mpi::broadcast(comm,val,root);
}

template<typename T, typename BinaryOperation>
T*
scan(const mpi_process_group& pg, T* first, T* last, T* out,
           BinaryOperation bin_op)
{
  synchronize(pg);

  bool inplace = first == out;

  if (inplace) out = new T [last-first];

  boost::mpi::scan(communicator(pg), first, last-first, out, bin_op);

  if (inplace) {
    std::copy(out, out + (last-first), first);
    delete [] out;
    return last;
  }

  return out;
}

template<typename InputIterator, typename T>
void
all_gather(const mpi_process_group& pg, InputIterator first,
           InputIterator last, std::vector<T>& out)
{
  synchronize(pg);

  // Stick a copy of the local values into a vector, so we can broadcast it
  std::vector<T> local_values(first, last);

  // Collect the number of vertices stored in each process
  int size = local_values.size();
  std::vector<int> sizes(num_processes(pg));
  int result = MPI_Allgather(&size, 1, MPI_INT,
                             &sizes[0], 1, MPI_INT,
                             communicator(pg));
  BOOST_ASSERT(result == MPI_SUCCESS);

  // Adjust sizes based on the number of bytes
  std::transform(sizes.begin(), sizes.end(), sizes.begin(),
                 std::bind2nd(std::multiplies<int>(), sizeof(T)));

  // Compute displacements
  std::vector<int> displacements;
  displacements.reserve(sizes.size() + 1);
  displacements.push_back(0);
  std::partial_sum(sizes.begin(), sizes.end(),
                   std::back_inserter(displacements));

  // Gather all of the values
  out.resize(displacements.back() / sizeof(T));
  if (!out.empty()) {
    result = MPI_Allgatherv(local_values.empty()? (void*)&local_values
                            /* local results */: (void*)&local_values[0],
                            local_values.size() * sizeof(T),
                            MPI_BYTE,
                            &out[0], &sizes[0], &displacements[0], MPI_BYTE,
                            communicator(pg));
  }
  BOOST_ASSERT(result == MPI_SUCCESS);
}

template<typename InputIterator>
mpi_process_group
process_subgroup(const mpi_process_group& pg,
                 InputIterator first, InputIterator last)
{
/*
  boost::mpi::group current_group = communicator(pg).group();
  boost::mpi::group new_group = current_group.include(first,last);
  boost::mpi::communicator new_comm(communicator(pg),new_group);
  return mpi_process_group(new_comm);
*/
  std::vector<int> ranks(first, last);

  MPI_Group current_group;
  int result = MPI_Comm_group(communicator(pg), &current_group);
  BOOST_ASSERT(result == MPI_SUCCESS);

  MPI_Group new_group;
  result = MPI_Group_incl(current_group, ranks.size(), &ranks[0], &new_group);
  BOOST_ASSERT(result == MPI_SUCCESS);

  MPI_Comm new_comm;
  result = MPI_Comm_create(communicator(pg), new_group, &new_comm);
  BOOST_ASSERT(result == MPI_SUCCESS);

  result = MPI_Group_free(&new_group);
  BOOST_ASSERT(result == MPI_SUCCESS);
  result = MPI_Group_free(&current_group);
  BOOST_ASSERT(result == MPI_SUCCESS);

  if (new_comm != MPI_COMM_NULL) {
    mpi_process_group result_pg(boost::mpi::communicator(new_comm,boost::mpi::comm_attach));
    result = MPI_Comm_free(&new_comm);
    BOOST_ASSERT(result == 0);
    return result_pg;
  } else {
    return mpi_process_group(mpi_process_group::create_empty());
  }

}

template<typename Receiver>
Receiver* mpi_process_group::get_receiver()
{
  return impl_->blocks[my_block_number()]->on_receive
           .template target<Receiver>();
}

template<typename T>
typename enable_if<boost::mpi::is_mpi_datatype<T> >::type
receive_oob(const mpi_process_group& pg,
            mpi_process_group::process_id_type source, int tag, T& value, int block)
{
  using boost::mpi::get_mpi_datatype;

  // Determine the actual message we expect to receive, and which
  // communicator it will come by.
  std::pair<boost::mpi::communicator, int> actual
    = pg.actual_communicator_and_tag(tag, block);

  // Post a non-blocking receive that waits until we complete this request.
  MPI_Request request;
  MPI_Irecv(&value, 1, get_mpi_datatype<T>(value),
            source, actual.second, actual.first, &request);

  int done = 0;
  do {
    MPI_Test(&request, &done, MPI_STATUS_IGNORE);
    if (!done)
      pg.poll(/*wait=*/false, block);
  } while (!done);
}

template<typename T>
typename disable_if<boost::mpi::is_mpi_datatype<T> >::type
receive_oob(const mpi_process_group& pg,
            mpi_process_group::process_id_type source, int tag, T& value, int block)
{
  // Determine the actual message we expect to receive, and which
  // communicator it will come by.
  std::pair<boost::mpi::communicator, int> actual
    = pg.actual_communicator_and_tag(tag, block);

  boost::optional<boost::mpi::status> status;
  do {
    status = actual.first.iprobe(source, actual.second);
    if (!status)
      pg.poll();
  } while (!status);

  //actual.first.recv(status->source(), status->tag(),value);

  // Allocate the receive buffer
  boost::mpi::packed_iarchive in(actual.first);

#if BOOST_VERSION >= 103600
  in.resize(status->count<boost::mpi::packed>().get());
#else
  int size;
  MPI_Status mpi_status = *status;
  MPI_Get_count(&mpi_status, MPI_PACKED, &size);
  in.resize(size);
#endif

  // Receive the message data
  MPI_Recv(in.address(), in.size(), MPI_PACKED,
           status->source(), status->tag(), actual.first, MPI_STATUS_IGNORE);

  // Unpack the message data
  in >> value;
}

template<typename SendT, typename ReplyT>
typename enable_if<boost::mpi::is_mpi_datatype<ReplyT> >::type
send_oob_with_reply(const mpi_process_group& pg,
                    mpi_process_group::process_id_type dest,
                    int tag, const SendT& send_value, ReplyT& reply_value,
                    int block)
{
  detail::tag_allocator::token reply_tag = pg.impl_->allocated_tags.get_tag();
  send_oob(pg, dest, tag, boost::parallel::detail::make_untracked_pair(
        (int)reply_tag, send_value), block);
  receive_oob(pg, dest, reply_tag, reply_value);
}

template<typename SendT, typename ReplyT>
typename disable_if<boost::mpi::is_mpi_datatype<ReplyT> >::type
send_oob_with_reply(const mpi_process_group& pg,
                    mpi_process_group::process_id_type dest,
                    int tag, const SendT& send_value, ReplyT& reply_value,
                    int block)
{
  detail::tag_allocator::token reply_tag = pg.impl_->allocated_tags.get_tag();
  send_oob(pg, dest, tag,
           boost::parallel::detail::make_untracked_pair((int)reply_tag,
                                                        send_value), block);
  receive_oob(pg, dest, reply_tag, reply_value);
}

} } } // end namespace boost::graph::distributed


/*
 * Copyright (c) 2014, salesforce.com, inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *    Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *    following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *    the following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 *    Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or
 *    promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package com.sforce.dataset.loader.file.listener;

import java.util.LinkedHashMap;

public class FileListenerSettings {
 public LinkedHashMap<String,FileListener> fileListeners;
}


var arrayAggregator = require('./_arrayAggregator'),
    baseAggregator = require('./_baseAggregator'),
    baseIteratee = require('./_baseIteratee'),
    isArray = require('./isArray');

/**
 * Creates a function like `_.groupBy`.
 *
 * @private
 * @param {Function} setter The function to set accumulator values.
 * @param {Function} [initializer] The accumulator object initializer.
 * @returns {Function} Returns the new aggregator function.
 */
function createAggregator(setter, initializer) {
  return function(collection, iteratee) {
    var func = isArray(collection) ? arrayAggregator : baseAggregator,
        accumulator = initializer ? initializer() : {};

    return func(collection, setter, baseIteratee(iteratee, 2), accumulator);
  };
}

module.exports = createAggregator;


/*
 * Copyright 2020 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netflix.spinnaker.orca.peering

/**
 * Interface that custom peerers should implement (and expose as a bean)
 */
interface CustomPeerer {
  /**
   * Let the custom peerer initialize itself
   *
   * @param srcDb source/foreign (read-only) database, use srcDb.runQuery to get the jooq context to perform queries on
   * @param destDb destination/local database
   * @param peerId the id of the peer we are peering
   */
  fun init(srcDb: SqlRawAccess, destDb: SqlRawAccess, peerId: String)

  /**
   * doPeer function will be called AFTER all default peering actions are completed for this agent cycle
   *
   * @return true if peering completed successfully, false otherwise
   */
  fun doPeer(): Boolean
}


# SPDX-License-Identifier: GPL-2.0+
all:
	( cd ../../../..; tools/testing/selftests/rcutorture/bin/kvm.sh --duration 10 --configs TREE01 )


package Paws::Connect::ListSecurityProfiles;
  use Moose;
  has InstanceId => (is => 'ro', isa => 'Str', traits => ['ParamInURI'], uri_name => 'InstanceId', required => 1);
  has MaxResults => (is => 'ro', isa => 'Int', traits => ['ParamInQuery'], query_name => 'maxResults');
  has NextToken => (is => 'ro', isa => 'Str', traits => ['ParamInQuery'], query_name => 'nextToken');

  use MooseX::ClassAttribute;

  class_has _api_call => (isa => 'Str', is => 'ro', default => 'ListSecurityProfiles');
  class_has _api_uri  => (isa => 'Str', is => 'ro', default => '/security-profiles-summary/{InstanceId}');
  class_has _api_method  => (isa => 'Str', is => 'ro', default => 'GET');
  class_has _returns => (isa => 'Str', is => 'ro', default => 'Paws::Connect::ListSecurityProfilesResponse');
1;

### main pod documentation begin ###

=head1 NAME

Paws::Connect::ListSecurityProfiles - Arguments for method ListSecurityProfiles on L<Paws::Connect>

=head1 DESCRIPTION

This class represents the parameters used for calling the method ListSecurityProfiles on the
L<Amazon Connect Service|Paws::Connect> service. Use the attributes of this class
as arguments to method ListSecurityProfiles.

You shouldn't make instances of this class. Each attribute should be used as a named argument in the call to ListSecurityProfiles.

=head1 SYNOPSIS

    my $connect = Paws->service('Connect');
    my $ListSecurityProfilesResponse = $connect->ListSecurityProfiles(
      InstanceId => 'MyInstanceId',
      MaxResults => 1,                # OPTIONAL
      NextToken  => 'MyNextToken',    # OPTIONAL
    );

    # Results:
    my $NextToken = $ListSecurityProfilesResponse->NextToken;
    my $SecurityProfileSummaryList =
      $ListSecurityProfilesResponse->SecurityProfileSummaryList;

    # Returns a L<Paws::Connect::ListSecurityProfilesResponse> object.

Values for attributes that are native types (Int, String, Float, etc) can passed as-is (scalar values). Values for complex Types (objects) can be passed as a HashRef. The keys and values of the hashref will be used to instance the underlying object.
For the AWS API documentation, see L<https://docs.aws.amazon.com/goto/WebAPI/connect/ListSecurityProfiles>

=head1 ATTRIBUTES

=head2 B<REQUIRED> InstanceId => Str

The identifier of the Amazon Connect instance.

=head2 MaxResults => Int

The maximimum number of results to return per page.

=head2 NextToken => Str

The token for the next set of results. Use the value returned in the
previous response in the next request to retrieve the next set of
results.

=head1 SEE ALSO

This class forms part of L<Paws>, documenting arguments for method ListSecurityProfiles in L<Paws::Connect>

=head1 BUGS and CONTRIBUTIONS

The source code is located here: L<https://github.com/pplu/aws-sdk-perl>

Please report bugs to: L<https://github.com/pplu/aws-sdk-perl/issues>

=cut