text:
import Ember from 'ember-metal/core';
import EmberObject from 'ember-runtime/system/object';
import setProperties from 'ember-metal/set_properties';
import ObjectProxy from 'ember-runtime/system/object_proxy';
import { get } from 'ember-metal/property_get';
import { set } from 'ember-metal/property_set';
import { addObserver } from 'ember-metal/observer';
import { observer } from 'ember-metal/mixin';
import {
  sum,
  min,
  max,
  map,
  sort,
  setDiff,
  mapBy,
  filter,
  filterBy,
  uniq,
  union,
  intersect
} from 'ember-runtime/computed/reduce_computed_macros';

var obj;
QUnit.module('map', {
  setup() {
    obj = EmberObject.extend({
      mapped: map('array.@each.v', (item) => item.v),
      mappedObjects: map('arrayObjects.@each.v',  (item) => ({ name: item.v.name }))
    }).create({
      arrayObjects: Ember.A([
        { v: { name: 'Robert' } },
        { v: { name: 'Leanna' } }
      ]),

      array: Ember.A([
        { v: 1 },
        { v: 3 },
        { v: 2 },
        { v: 1 }
      ])
    });
  },

  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('map is readOnly', function() {
  QUnit.throws(function() {
    obj.set('mapped', 1);
  }, /Cannot set read-only property "mapped" on object:/);
});

QUnit.test('it maps simple properties', function() {
  deepEqual(obj.get('mapped'), [1, 3, 2, 1]);

  obj.get('array').pushObject({ v: 5 });

  deepEqual(obj.get('mapped'), [1, 3, 2, 1, 5]);

  obj.get('array').removeAt(3);

  deepEqual(obj.get('mapped'), [1, 3, 2, 5]);
});

QUnit.test('it maps simple unshifted properties', function() {
  var array = Ember.A();

  obj = EmberObject.extend({
    mapped: map('array', (item) => item.toUpperCase())
  }).create({
    array
  });

  array.unshiftObject('c');
  array.unshiftObject('b');
  array.unshiftObject('a');

  array.popObject();

  deepEqual(obj.get('mapped'), ['A', 'B'], 'properties unshifted in sequence are mapped correctly');
});

QUnit.test('it has the correct `this`', function() {
  obj = EmberObject.extend({
    mapped: map('array', function(item)  {
      equal(this, obj, 'should have correct context');
      return this.upperCase(item);
    }),
    upperCase(string) {
      return string.toUpperCase();
    }
  }).create({
    array: ['a', 'b', 'c']
  });

  deepEqual(obj.get('mapped'), ['A', 'B', 'C'], 'properties unshifted in sequence are mapped correctly');
});

QUnit.test('it passes the index to the callback', function() {
  var array = ['a', 'b', 'c'];

  obj = EmberObject.extend({
    mapped: map('array', (item, index) => index)
  }).create({
    array
  });

  deepEqual(obj.get('mapped'), [0, 1, 2], 'index is passed to callback correctly');
});

QUnit.test('it maps objects', function() {
  deepEqual(obj.get('mappedObjects'), [
    { name: 'Robert' },
    { name: 'Leanna' }
  ]);

  obj.get('arrayObjects').pushObject({
    v: { name: 'Eddard' }
  });

  deepEqual(obj.get('mappedObjects'), [
    { name: 'Robert' },
    { name: 'Leanna' },
    { name: 'Eddard' }
  ]);

  obj.get('arrayObjects').removeAt(1);

  deepEqual(obj.get('mappedObjects'), [
    { name: 'Robert' },
    { name: 'Eddard' }
  ]);

  set(obj.get('arrayObjects')[0], 'v', { name: 'Stannis' });

  deepEqual(obj.get('mappedObjects'), [
    { name: 'Stannis' },
    { name: 'Eddard' }
  ]);
});

QUnit.test('it maps unshifted objects with property observers', function() {
  var array = Ember.A();
  var cObj = { v: 'c' };

  obj = EmberObject.extend({
    mapped: map('array.@each.v', (item) => get(item, 'v').toUpperCase())
  }).create({
    array
  });

  array.unshiftObject(cObj);
  array.unshiftObject({ v: 'b' });
  array.unshiftObject({ v: 'a' });

  set(cObj, 'v', 'd');

  deepEqual(array.mapBy('v'), ['a', 'b', 'd'], 'precond - unmapped array is correct');
  deepEqual(obj.get('mapped'), ['A', 'B', 'D'], 'properties unshifted in sequence are mapped correctly');
});

QUnit.module('mapBy', {
  setup() {
    obj = EmberObject.extend({
      mapped: mapBy('array', 'v')
    }).create({
      array: Ember.A([
        { v: 1 },
        { v: 3 },
        { v: 2 },
        { v: 1 }
      ])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('mapBy is readOnly', function() {
  QUnit.throws(function() {
    obj.set('mapped', 1);
  }, /Cannot set read-only property "mapped" on object:/);
});

QUnit.test('it maps properties', function() {
  deepEqual(obj.get('mapped'), [1, 3, 2, 1]);

  obj.get('array').pushObject({ v: 5 });

  deepEqual(obj.get('mapped'), [1, 3, 2, 1, 5]);

  obj.get('array').removeAt(3);

  deepEqual(obj.get('mapped'), [1, 3, 2, 5]);
});

QUnit.test('it is observable', function() {
  var calls = 0;

  deepEqual(obj.get('mapped'), [1, 3, 2, 1]);

  addObserver(obj, 'mapped.@each', () => calls++);

  obj.get('array').pushObject({ v: 5 });

  equal(calls, 1, 'mapBy is observable');
});

QUnit.module('filter', {
  setup() {
    obj = EmberObject.extend({
      filtered: filter('array', (item) => item % 2 === 0)
    }).create({
      array: Ember.A([1, 2, 3, 4, 5, 6, 7, 8])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('filter is readOnly', function() {
  QUnit.throws(function() {
    obj.set('filtered', 1);
  }, /Cannot set read-only property "filtered" on object:/);
});

QUnit.test('it filters according to the specified filter function', function() {
  deepEqual(obj.get('filtered'), [2, 4, 6, 8], 'filter filters by the specified function');
});

QUnit.test('it passes the index to the callback', function() {
  obj = EmberObject.extend({
    filtered: filter('array', (item, index) => index === 1)
  }).create({
    array: ['a', 'b', 'c']
  });

  deepEqual(get(obj, 'filtered'), ['b'], 'index is passed to callback correctly');
});

QUnit.test('it has the correct `this`', function() {
  obj = EmberObject.extend({
    filtered: filter('array', function(item, index) {
      equal(this, obj);
      return this.isOne(index);
    }),
    isOne(value) {
      return value === 1;
    }
  }).create({
    array: ['a', 'b', 'c']
  });

  deepEqual(get(obj, 'filtered'), ['b'], 'index is passed to callback correctly');
});

QUnit.test('it passes the array to the callback', function() {
  obj = EmberObject.extend({
    filtered: filter('array',  (item, index, array) => index === get(array, 'length') - 2)
  }).create({
    array: Ember.A(['a', 'b', 'c'])
  });

  deepEqual(obj.get('filtered'), ['b'], 'array is passed to callback correctly');
});

QUnit.test('it caches properly', function() {
  var array = obj.get('array');

  var filtered = obj.get('filtered');
  ok(filtered === obj.get('filtered'));

  array.addObject(11);
  var newFiltered = obj.get('filtered');

  ok(filtered !== newFiltered);

  ok(obj.get('filtered') === newFiltered);
});

QUnit.test('it updates as the array is modified', function() {
  var array = obj.get('array');

  deepEqual(obj.get('filtered'), [2, 4, 6, 8], 'precond - filtered array is initially correct');

  array.addObject(11);
  deepEqual(obj.get('filtered'), [2, 4, 6, 8], 'objects not passing the filter are not added');

  array.addObject(12);
  deepEqual(obj.get('filtered'), [2, 4, 6, 8, 12], 'objects passing the filter are added');

  array.removeObject(3);
  array.removeObject(4);

  deepEqual(obj.get('filtered'), [2, 6, 8, 12], 'objects removed from the dependent array are removed from the computed array');
});

QUnit.test('the dependent array can be cleared one at a time', function() {
  var array = get(obj, 'array');

  deepEqual(obj.get('filtered'), [2, 4, 6, 8], 'precond - filtered array is initially correct');

  // clear 1-8 but in a random order
  array.removeObject(3);
  array.removeObject(1);
  array.removeObject(2);
  array.removeObject(4);
  array.removeObject(8);
  array.removeObject(6);
  array.removeObject(5);
  array.removeObject(7);

  deepEqual(obj.get('filtered'), [], 'filtered array cleared correctly');
});

QUnit.test('the dependent array can be `clear`ed directly (#3272)', function() {
  deepEqual(obj.get('filtered'), [2, 4, 6, 8], 'precond - filtered array is initially correct');

  obj.get('array').clear();

  deepEqual(obj.get('filtered'), [], 'filtered array cleared correctly');
});

QUnit.test('it updates as the array is replaced', function() {
  deepEqual(obj.get('filtered'), [2, 4, 6, 8], 'precond - filtered array is initially correct');

  obj.set('array', [20, 21, 22, 23, 24]);

  deepEqual(obj.get('filtered'), [20, 22, 24], 'computed array is updated when array is changed');
});

QUnit.module('filterBy', {
  setup() {
    obj = EmberObject.extend({
      a1s: filterBy('array', 'a', 1),
      as: filterBy('array', 'a'),
      bs: filterBy('array', 'b')
    }).create({
      array: Ember.A([
        { name: 'one', a: 1, b: false },
        { name: 'two', a: 2, b: false },
        { name: 'three', a: 1, b: true },
        { name: 'four', b: true }
      ])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('filterBy is readOnly', function() {
  QUnit.throws(function() {
    obj.set('as', 1);
  }, /Cannot set read-only property "as" on object:/);
});

QUnit.test('properties can be filtered by truthiness', function() {
  deepEqual(obj.get('as').mapBy('name'), ['one', 'two', 'three'], 'properties can be filtered by existence');
  deepEqual(obj.get('bs').mapBy('name'), ['three', 'four'], 'booleans can be filtered');

  set(obj.get('array')[0], 'a', undefined);
  set(obj.get('array')[3], 'a', true);

  set(obj.get('array')[0], 'b', true);
  set(obj.get('array')[3], 'b', false);

  deepEqual(obj.get('as').mapBy('name'), ['two', 'three', 'four'], 'arrays computed by filter property respond to property changes');
  deepEqual(obj.get('bs').mapBy('name'), ['one', 'three'], 'arrays computed by filtered property respond to property changes');

  obj.get('array').pushObject({ name: 'five', a: 6, b: true });

  deepEqual(obj.get('as').mapBy('name'), ['two', 'three', 'four', 'five'], 'arrays computed by filter property respond to added objects');
  deepEqual(obj.get('bs').mapBy('name'), ['one', 'three', 'five'], 'arrays computed by filtered property respond to added objects');

  obj.get('array').popObject();

  deepEqual(obj.get('as').mapBy('name'), ['two', 'three', 'four'], 'arrays computed by filter property respond to removed objects');
  deepEqual(obj.get('bs').mapBy('name'), ['one', 'three'], 'arrays computed by filtered property respond to removed objects');

  obj.set('array', [
    { name: 'six', a: 12, b: true }
  ]);

  deepEqual(obj.get('as').mapBy('name'), ['six'], 'arrays computed by filter property respond to array changes');
  deepEqual(obj.get('bs').mapBy('name'), ['six'], 'arrays computed by filtered property respond to array changes');
});

QUnit.test('properties can be filtered by values', function() {
  deepEqual(obj.get('a1s').mapBy('name'), ['one', 'three'], 'properties can be filtered by matching value');

  obj.get('array').pushObject({ name: 'five', a: 1 });

  deepEqual(obj.get('a1s').mapBy('name'), ['one', 'three', 'five'], 'arrays computed by matching value respond to added objects');

  obj.get('array').popObject();

  deepEqual(obj.get('a1s').mapBy('name'), ['one', 'three'], 'arrays computed by matching value respond to removed objects');

  set(obj.get('array')[1], 'a', 1);
  set(obj.get('array')[2], 'a', 2);

  deepEqual(obj.get('a1s').mapBy('name'), ['one', 'two'], 'arrays computed by matching value respond to modified properties');
});

QUnit.test('properties values can be replaced', function() {
  obj = EmberObject.extend({
    a1s: filterBy('array', 'a', 1),
    a1bs: filterBy('a1s', 'b')
  }).create({
    array: []
  });

  deepEqual(obj.get('a1bs').mapBy('name'), [], 'properties can be filtered by matching value');

  set(obj, 'array', [
    { name: 'item1', a: 1, b: true }
  ]);

  deepEqual(obj.get('a1bs').mapBy('name'), ['item1'], 'properties can be filtered by matching value');
});

[
  ['uniq', uniq],
  ['union', union]
].forEach((tuple) => {
  const [name, macro] = tuple;

  QUnit.module(`computed.${name}`, {
    setup() {
      obj = EmberObject.extend({
        union: macro('array', 'array2', 'array3')
      }).create({
        array: Ember.A([1, 2, 3, 4, 5, 6]),
        array2: Ember.A([4, 5, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9]),
        array3: Ember.A([1, 8, 10])
      });
    },
    teardown() {
      Ember.run(obj, 'destroy');
    }
  });

  QUnit.test(`${name} is readOnly`, function() {
    QUnit.throws(function() {
      obj.set('union', 1);
    }, /Cannot set read-only property "union" on object:/);
  });

  QUnit.test('does not include duplicates', function() {
    var array = obj.get('array');
    var array2 = obj.get('array2');

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], name + ' does not include duplicates');

    array.pushObject(8);

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], name + ' does not add existing items');

    array.pushObject(11);

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], name + ' adds new items');

    array2.removeAt(6); // remove 7

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], name + ' does not remove items that are still in the dependent array');

    array2.removeObject(7);

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 2, 3, 4, 5, 6, 8, 9, 10, 11], name + ' removes items when their last instance is gone');
  });

  QUnit.test('has set-union semantics', function() {
    var array = obj.get('array');

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], name + ' is initially correct');

    array.removeObject(6);

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'objects are not removed if they exist in other dependent arrays');

    array.clear();

    deepEqual(obj.get('union').sort((x, y) => x - y), [1, 4, 5, 6, 7, 8, 9, 10], 'objects are removed when they are no longer in any dependent array');
  });
});

QUnit.module('computed.intersect', {
  setup() {
    obj = EmberObject.extend({
      intersection: intersect('array', 'array2', 'array3')
    }).create({
      array: Ember.A([1, 2, 3, 4, 5, 6]),
      array2: Ember.A([3, 3, 3, 4, 5]),
      array3: Ember.A([3, 5, 6, 7, 8])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('intersect is readOnly', function() {
  QUnit.throws(function() {
    obj.set('intersection', 1);
  }, /Cannot set read-only property "intersection" on object:/);
});

QUnit.test('it has set-intersection semantics', function() {
  var array2 = obj.get('array2');
  var array3 = obj.get('array3');

  deepEqual(obj.get('intersection').sort((x, y) => x - y), [3, 5], 'intersection is initially correct');

  array2.shiftObject();

  deepEqual(obj.get('intersection').sort((x, y) => x - y), [3, 5], 'objects are not removed when they are still in all dependent arrays');

  array2.shiftObject();

  deepEqual(obj.get('intersection').sort((x, y) => x - y), [3, 5], 'objects are not removed when they are still in all dependent arrays');

  array2.shiftObject();

  deepEqual(obj.get('intersection'), [5], 'objects are removed once they are gone from all dependent arrays');

  array2.pushObject(1);

  deepEqual(obj.get('intersection'), [5], 'objects are not added as long as they are missing from any dependent array');

  array3.pushObject(1);

  deepEqual(obj.get('intersection').sort((x, y) => x - y), [1, 5], 'objects added once they belong to all dependent arrays');
});

QUnit.module('setDiff', {
  setup() {
    obj = EmberObject.extend({
      diff: setDiff('array', 'array2')
    }).create({
      array: Ember.A([1, 2, 3, 4, 5, 6, 7]),
      array2: Ember.A([3, 4, 5, 10])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('setDiff is readOnly', function() {
  QUnit.throws(function() {
    obj.set('diff', 1);
  }, /Cannot set read-only property "diff" on object:/);
});

QUnit.test('it throws an error if given fewer or more than two dependent properties', function() {
  throws(function () {
    EmberObject.extend({
      diff: setDiff('array')
    }).create({
      array: Ember.A([1, 2, 3, 4, 5, 6, 7]),
      array2: Ember.A([3, 4, 5])
    });
  }, /requires exactly two dependent arrays/, 'setDiff requires two dependent arrays');

  throws(function () {
    EmberObject.extend({
      diff: setDiff('array', 'array2', 'array3')
    }).create({
      array: Ember.A([1, 2, 3, 4, 5, 6, 7]),
      array2: Ember.A([3, 4, 5]),
      array3: Ember.A([7])
    });
  }, /requires exactly two dependent arrays/, 'setDiff requires two dependent arrays');
});

QUnit.test('it has set-diff semantics', function() {
  var array1 = obj.get('array');
  var array2 = obj.get('array2');

  deepEqual(obj.get('diff').sort((x, y) => x - y), [1, 2, 6, 7], 'set-diff is initially correct');

  array2.popObject();

  deepEqual(obj.get('diff').sort((x, y) => x - y), [1, 2, 6, 7], 'removing objects from the remove set has no effect if the object is not in the keep set');

  array2.shiftObject();

  deepEqual(obj.get('diff').sort((x, y) => x - y), [1, 2, 3, 6, 7], 'removing objects from the remove set adds them if they\'re in the keep set');

  array1.removeObject(3);

  deepEqual(obj.get('diff').sort((x, y) => x - y), [1, 2, 6, 7], 'removing objects from the keep array removes them from the computed array');

  array1.pushObject(5);

  deepEqual(obj.get('diff').sort((x, y) => x - y), [1, 2, 6, 7], 'objects added to the keep array that are in the remove array are not added to the computed array');

  array1.pushObject(22);

  deepEqual(obj.get('diff').sort((x, y) => x - y), [1, 2, 6, 7, 22], 'objects added to the keep array not in the remove array are added to the computed array');
});

function commonSortTests() {
  QUnit.test('arrays are initially sorted', function() {
    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Bran',
      'Robb'
    ], 'array is initially sorted');
  });

  QUnit.test('default sort order is correct', function() {
    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Bran',
      'Robb'
    ], 'array is initially sorted');
  });

  QUnit.test('changing the dependent array updates the sorted array', function() {
    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Bran',
      'Robb'
    ], 'precond - array is initially sorted');

    obj.set('items', [
      { fname: 'Roose', lname: 'Bolton' },
      { fname: 'Theon', lname: 'Greyjoy' },
      { fname: 'Ramsey', lname: 'Bolton' },
      { fname: 'Stannis', lname: 'Baratheon' }
    ]);

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Stannis',
      'Ramsey',
      'Roose',
      'Theon'
    ], 'changing dependent array updates sorted array');
  });

  QUnit.test('adding to the dependent array updates the sorted array', function() {
    var items = obj.get('items');

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Bran',
      'Robb'
    ], 'precond - array is initially sorted');

    items.pushObject({
      fname: 'Tyrion',
      lname: 'Lannister'
    });

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Tyrion',
      'Bran',
      'Robb'
    ], 'Adding to the dependent array updates the sorted array');
  });

  QUnit.test('removing from the dependent array updates the sorted array', function() {
    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Bran',
      'Robb'
    ], 'precond - array is initially sorted');

    obj.get('items').popObject();

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Robb'
    ], 'Removing from the dependent array updates the sorted array');
  });

  QUnit.test('distinct items may be sort-equal, although their relative order will not be guaranteed', function() {
    // We recreate jaime and "Cersei" here only for test stability: we want
    // their guid-ordering to be deterministic
    var jaimeInDisguise = {
      fname: 'Cersei',
      lname: 'Lannister',
      age: 34
    };

    var jaime = {
      fname: 'Jaime',
      lname: 'Lannister',
      age: 34
    };

    var items = obj.get('items');

    items.replace(0, 1, jaime);
    items.replace(1, 1, jaimeInDisguise);

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Bran',
      'Robb'
    ], 'precond - array is initially sorted');

    set(jaimeInDisguise, 'fname', 'Jaime');

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Jaime',
      'Jaime',
      'Bran',
      'Robb'
    ], 'sorted array is updated');

    set(jaimeInDisguise, 'fname', 'Cersei');

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Bran',
      'Robb'
    ], 'sorted array is updated');
  });

  QUnit.test('guid sort-order fallback with a search proxy is not confused by non-search ObjectProxys', function() {
    var tyrion = {
      fname: 'Tyrion',
      lname: 'Lannister'
    };

    var tyrionInDisguise = ObjectProxy.create({
      fname: 'Yollo',
      lname: '',
      content: tyrion
    });

    var items = obj.get('items');

    items.pushObject(tyrion);

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Cersei',
      'Jaime',
      'Tyrion',
      'Bran',
      'Robb'
    ]);

    items.pushObject(tyrionInDisguise);

    deepEqual(obj.get('sortedItems').mapBy('fname'), [
      'Yollo',
      'Cersei',
      'Jaime',
      'Tyrion',
      'Bran',
      'Robb'
    ]);
  });
}

QUnit.module('sort - sortProperties', {
  setup() {
    obj = EmberObject.extend({
      sortedItems: sort('items', 'itemSorting')
    }).create({
      itemSorting: Ember.A(['lname', 'fname']),
      items: Ember.A([
        { fname: 'Jaime', lname: 'Lannister', age: 34 },
        { fname: 'Cersei', lname: 'Lannister', age: 34 },
        { fname: 'Robb', lname: 'Stark', age: 16 },
        { fname: 'Bran', lname: 'Stark', age: 8 }
      ])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('sort is readOnly', function() {
  QUnit.throws(function() {
    obj.set('sortedItems', 1);
  }, /Cannot set read-only property "sortedItems" on object:/);
});

commonSortTests();

QUnit.test('updating sort properties detaches observers for old sort properties', function() {
  var objectToRemove = obj.get('items')[3];

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ], 'precond - array is initially sorted');

  obj.set('itemSorting', Ember.A(['fname:desc']));

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Robb',
    'Jaime',
    'Cersei',
    'Bran'
  ], 'after updating sort properties array is updated');

  obj.get('items').removeObject(objectToRemove);

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Robb',
    'Jaime',
    'Cersei'
  ], 'after removing item array is updated');

  set(objectToRemove, 'lname', 'Updated-Stark');

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Robb',
    'Jaime',
    'Cersei'
  ], 'after changing removed item array is not updated');
});

QUnit.test('updating sort properties updates the sorted array', function() {
  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ], 'precond - array is initially sorted');

  obj.set('itemSorting', Ember.A(['fname:desc']));

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Robb',
    'Jaime',
    'Cersei',
    'Bran'
  ], 'after updating sort properties array is updated');
});

QUnit.test('updating sort properties invalidates the sorted array', function() {
  var sortProps = obj.get('itemSorting');

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ], 'precond - array is initially sorted');

  sortProps.clear();
  sortProps.pushObject('fname');

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Bran',
    'Cersei',
    'Jaime',
    'Robb'
  ], 'after updating sort properties array is updated');
});

QUnit.test('updating new sort properties invalidates the sorted array', function() {
  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ], 'precond - array is initially sorted');

  obj.set('itemSorting', Ember.A(['age:desc', 'fname:asc']));

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Robb',
    'Bran'
  ], 'precond - array is correct after item sorting is changed');

  set(obj.get('items')[1], 'age', 29);

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Jaime',
    'Cersei',
    'Robb',
    'Bran'
  ], 'after updating sort properties array is updated');
});

QUnit.test('sort direction defaults to ascending', function() {
  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ]);
});

QUnit.test('sort direction defaults to ascending (with sort property change)', function() {
  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ], 'precond - array is initially sorted');

  obj.set('itemSorting', Ember.A(['fname']));

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Bran',
    'Cersei',
    'Jaime',
    'Robb'
  ], 'sort direction defaults to ascending');
});

QUnit.test('updating an item\'s sort properties updates the sorted array', function() {
  var tyrionInDisguise = obj.get('items')[1];

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ], 'precond - array is initially sorted');

  set(tyrionInDisguise, 'fname', 'Tyrion');

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Jaime',
    'Tyrion',
    'Bran',
    'Robb'
  ], 'updating an item\'s sort properties updates the sorted array');
});

QUnit.test('updating several of an item\'s sort properties updated the sorted array', function() {
  var sansaInDisguise = obj.get('items')[1];

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Cersei',
    'Jaime',
    'Bran',
    'Robb'
  ], 'precond - array is initially sorted');

  setProperties(sansaInDisguise, {
    fname: 'Sansa',
    lname: 'Stark'
  });

  deepEqual(obj.get('sortedItems').mapBy('fname'), [
    'Jaime',
    'Bran',
    'Robb',
    'Sansa'
  ], 'updating an item\'s sort properties updates the sorted array');
});

QUnit.test('updating an item\'s sort properties does not error when binary search does a self compare (#3273)', function() {
  var jaime = {
    name: 'Jaime',
    status: 1
  };

  var cersei = {
    name: 'Cersei',
    status: 2
  };

  var obj = EmberObject.extend({
    sortProps: ['status'],
    sortedPeople: sort('people', 'sortProps')
  }).create({
    people: [jaime, cersei]
  });

  deepEqual(obj.get('sortedPeople'), [
    jaime,
    cersei
  ], 'precond - array is initially sorted');

  set(cersei, 'status', 3);

  deepEqual(obj.get('sortedPeople'), [
    jaime,
    cersei
  ], 'array is sorted correctly');

  set(cersei, 'status', 2);

  deepEqual(obj.get('sortedPeople'), [
    jaime,
    cersei
  ], 'array is sorted correctly');
});

QUnit.test('array observers do not leak', function() {
  var daria = { name: 'Daria' };
  var jane  = { name: 'Jane' };

  var sisters = [jane, daria];

  var sortProps = Ember.A(['name']);
  var jaime = EmberObject.extend({
    sortedPeople: sort('sisters', 'sortProps'),
    sortProps
  }).create({
    sisters
  });

  jaime.get('sortedPeople');
  Ember.run(jaime, 'destroy');

  try {
    sortProps.pushObject({
      name: 'Anna'
    });
    ok(true);
  } catch (e) {
    ok(false, e);
  }
});

QUnit.test('property paths in sort properties update the sorted array', function () {
  var jaime = {
    relatedObj: { status: 1, firstName: 'Jaime', lastName: 'Lannister' }
  };

  var cersei = {
    relatedObj: { status: 2, firstName: 'Cersei', lastName: 'Lannister' }
  };

  var sansa = EmberObject.create({
    relatedObj: { status: 3, firstName: 'Sansa', lastName: 'Stark' }
  });

  var obj = EmberObject.extend({
    sortProps: ['relatedObj.status'],
    sortedPeople: sort('people', 'sortProps')
  }).create({
    people: [jaime, cersei, sansa]
  });

  deepEqual(obj.get('sortedPeople'), [
    jaime,
    cersei,
    sansa
  ], 'precond - array is initially sorted');

  set(cersei, 'status', 3);

  deepEqual(obj.get('sortedPeople'), [
    jaime,
    cersei,
    sansa
  ], 'array is sorted correctly');

  set(cersei, 'status', 1);

  deepEqual(obj.get('sortedPeople'), [
    jaime,
    cersei,
    sansa
  ], 'array is sorted correctly');

  sansa.set('status', 1);

  deepEqual(obj.get('sortedPeople'), [jaime, cersei, sansa], 'array is sorted correctly');

  obj.set('sortProps', ['relatedObj.firstName']);

  deepEqual(obj.get('sortedPeople'), [cersei, jaime, sansa], 'array is sorted correctly');
});

function sortByLnameFname(a, b) {
  var lna = get(a, 'lname');
  var lnb = get(b, 'lname');

  if (lna !== lnb) {
    return lna > lnb ? 1 : -1;
  }

  return sortByFnameAsc(a, b);
}

function sortByFnameAsc(a, b) {
  var fna = get(a, 'fname');
  var fnb = get(b, 'fname');

  if (fna === fnb) {
    return 0;
  }
  return fna > fnb ? 1 : -1;
}

QUnit.module('sort - sort function', {
  setup() {
    obj = EmberObject.extend({
      sortedItems: sort('items.@each.fname', sortByLnameFname)
    }).create({
      items: Ember.A([
        { fname: 'Jaime', lname: 'Lannister', age: 34 },
        { fname: 'Cersei', lname: 'Lannister', age: 34 },
        { fname: 'Robb', lname: 'Stark', age: 16 },
        { fname: 'Bran', lname: 'Stark', age: 8 }
      ])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('sort has correct `this`', function() {
  var obj = EmberObject.extend({
    sortedItems: sort('items.@each.fname', function(a, b) {
      equal(this, obj, 'expected the object to be `this`');
      return this.sortByLastName(a, b);
    }),
    sortByLastName(a, b) {
      return sortByFnameAsc(a, b);
    }
  }).create({
    items: Ember.A([
      { fname: 'Jaime', lname: 'Lannister', age: 34 },
      { fname: 'Cersei', lname: 'Lannister', age: 34 },
      { fname: 'Robb', lname: 'Stark', age: 16 },
      { fname: 'Bran', lname: 'Stark', age: 8 }
    ])
  });

  obj.get('sortedItems');
});

QUnit.test('sort (with function) is readOnly', function() {
  QUnit.throws(function() {
    obj.set('sortedItems', 1);
  }, /Cannot set read-only property "sortedItems" on object:/);
});

commonSortTests();

QUnit.test('changing item properties specified via @each triggers a resort of the modified item', function() {
  var items = get(obj, 'items');

  var tyrionInDisguise = items[1];

  deepEqual(obj.get('sortedItems').mapBy('fname'), ['Cersei', 'Jaime', 'Bran', 'Robb'], 'precond - array is initially sorted');

  set(tyrionInDisguise, 'fname', 'Tyrion');

  deepEqual(obj.get('sortedItems').mapBy('fname'), ['Jaime', 'Tyrion', 'Bran', 'Robb'], 'updating a specified property on an item resorts it');
});

QUnit.test('changing item properties not specified via @each does not trigger a resort', function() {
  var items = obj.get('items');
  var cersei = items[1];

  deepEqual(obj.get('sortedItems').mapBy('fname'), ['Cersei', 'Jaime', 'Bran', 'Robb'], 'precond - array is initially sorted');

  set(cersei, 'lname', 'Stark'); // plot twist! (possibly not canon)

  // The array has become unsorted.  If your sort function is sensitive to
  // properties, they *must* be specified as dependent item property keys or
  // we'll be doing binary searches on unsorted arrays.
  deepEqual(obj.get('sortedItems').mapBy('fname'), ['Cersei', 'Jaime', 'Bran', 'Robb'], 'updating an unspecified property on an item does not resort it');
});

QUnit.module('sort - stability', {
  setup() {
    obj = EmberObject.extend({
      sortProps: ['count', 'name'],
      sortedItems: sort('items', 'sortProps')
    }).create({
      items: [
        { name: 'A', count: 1 },
        { name: 'B', count: 1 },
        { name: 'C', count: 1 },
        { name: 'D', count: 1 }
      ]
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('sorts correctly as only one property changes', function() {
  deepEqual(obj.get('sortedItems').mapBy('name'), ['A', 'B', 'C', 'D'], 'initial');

  set(obj.get('items')[3], 'count', 2);

  deepEqual(obj.get('sortedItems').mapBy('name'), ['A', 'B', 'C', 'D'], 'final');
});

QUnit.module('sort - concurrency', {
  setup() {
    obj = EmberObject.extend({
      sortProps: ['count'],
      sortedItems: sort('items', 'sortProps'),
      customSortedItems: sort('items.@each.count', (a, b) => a.count - b.count)
    }).create({
      items: Ember.A([
        { name: 'A', count: 1 },
        { name: 'B', count: 2 },
        { name: 'C', count: 3 },
        { name: 'D', count: 4 }
      ])
    });
  },

  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('sorts correctly after mutation to the sort properties', function() {
  var sorted = obj.get('sortedItems');
  deepEqual(sorted.mapBy('name'), ['A', 'B', 'C', 'D'], 'initial');

  set(obj.get('items')[1], 'count', 5);
  set(obj.get('items')[2], 'count', 6);

  deepEqual(obj.get('sortedItems').mapBy('name'), ['A', 'D', 'B', 'C'], 'final');
});

QUnit.test('sort correctl after mutation to the sor ', function() {
  deepEqual(obj.get('customSortedItems').mapBy('name'), ['A', 'B', 'C', 'D'], 'initial');

  set(obj.get('items')[1], 'count', 5);
  set(obj.get('items')[2], 'count', 6);

  deepEqual(obj.get('customSortedItems').mapBy('name'), ['A', 'D', 'B', 'C'], 'final');

  deepEqual(obj.get('sortedItems').mapBy('name'), ['A', 'D', 'B', 'C'], 'final');
});

QUnit.module('max', {
  setup() {
    obj = EmberObject.extend({
      max: max('items')
    }).create({
      items: Ember.A([1, 2, 3])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('max is readOnly', function() {
  QUnit.throws(function() {
    obj.set('max', 1);
  }, /Cannot set read-only property "max" on object:/);
});

QUnit.test('max tracks the max number as objects are added', function() {
  equal(obj.get('max'), 3, 'precond - max is initially correct');

  var items = obj.get('items');

  items.pushObject(5);

  equal(obj.get('max'), 5, 'max updates when a larger number is added');

  items.pushObject(2);

  equal(obj.get('max'), 5, 'max does not update when a smaller number is added');
});

QUnit.test('max recomputes when the current max is removed', function() {
  equal(obj.get('max'), 3, 'precond - max is initially correct');

  obj.get('items').removeObject(2);

  equal(obj.get('max'), 3, 'max is unchanged when a non-max item is removed');

  obj.get('items').removeObject(3);

  equal(obj.get('max'), 1, 'max is recomputed when the current max is removed');
});

QUnit.module('min', {
  setup() {
    obj = EmberObject.extend({
      min: min('items')
    }).create({
      items: Ember.A([1, 2, 3])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('min is readOnly', function() {
  QUnit.throws(function() {
    obj.set('min', 1);
  }, /Cannot set read-only property "min" on object:/);
});

QUnit.test('min tracks the min number as objects are added', function() {
  equal(obj.get('min'), 1, 'precond - min is initially correct');

  obj.get('items').pushObject(-2);

  equal(obj.get('min'), -2, 'min updates when a smaller number is added');

  obj.get('items').pushObject(2);

  equal(obj.get('min'), -2, 'min does not update when a larger number is added');
});

QUnit.test('min recomputes when the current min is removed', function() {
  var items = obj.get('items');

  equal(obj.get('min'), 1, 'precond - min is initially correct');

  items.removeObject(2);

  equal(obj.get('min'), 1, 'min is unchanged when a non-min item is removed');

  items.removeObject(1);

  equal(obj.get('min'), 3, 'min is recomputed when the current min is removed');
});

QUnit.module('Ember.arrayComputed - mixed sugar', {
  setup() {
    obj = EmberObject.extend({
      lannisters: filterBy('items', 'lname', 'Lannister'),
      lannisterSorting: Ember.A(['fname']),
      sortedLannisters: sort('lannisters', 'lannisterSorting'),

      starks: filterBy('items', 'lname', 'Stark'),
      starkAges: mapBy('starks', 'age'),
      oldestStarkAge: max('starkAges')
    }).create({
      items: Ember.A([
        { fname: 'Jaime', lname: 'Lannister', age: 34 },
        { fname: 'Cersei', lname: 'Lannister', age: 34 },
        { fname: 'Robb', lname: 'Stark', age: 16 },
        { fname: 'Bran', lname: 'Stark', age: 8 }
      ])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('filtering and sorting can be combined', function() {
  var items = obj.get('items');

  deepEqual(obj.get('sortedLannisters').mapBy('fname'), ['Cersei', 'Jaime'], 'precond - array is initially filtered and sorted');

  items.pushObject({ fname: 'Tywin',   lname: 'Lannister' });
  items.pushObject({ fname: 'Lyanna',  lname: 'Stark' });
  items.pushObject({ fname: 'Gerion',  lname: 'Lannister' });

  deepEqual(obj.get('sortedLannisters').mapBy('fname'), ['Cersei', 'Gerion', 'Jaime', 'Tywin'], 'updates propagate to array');
});

QUnit.test('filtering, sorting and reduce (max) can be combined', function() {
  var items = obj.get('items');

  equal(16, obj.get('oldestStarkAge'), 'precond - end of chain is initially correct');

  items.pushObject({ fname: 'Rickon', lname: 'Stark', age: 5 });

  equal(16, obj.get('oldestStarkAge'), 'chain is updated correctly');

  items.pushObject({ fname: 'Eddard', lname: 'Stark', age: 35 });

  equal(35, obj.get('oldestStarkAge'), 'chain is updated correctly');
});

function todo(name, priority) {
  return EmberObject.create({ name: name, priority: priority });
}

function priorityComparator(todoA, todoB) {
  var pa = parseInt(get(todoA, 'priority'), 10);
  var pb = parseInt(get(todoB, 'priority'), 10);

  return pa - pb;
}

function evenPriorities(todo) {
  var p = parseInt(get(todo, 'priority'), 10);

  return p % 2 === 0;
}

QUnit.module('Ember.arrayComputed - chains', {
  setup() {
    obj = EmberObject.extend({
      sorted: sort('todos.@each.priority', priorityComparator),
      filtered: filter('sorted.@each.priority', evenPriorities)
    }).create({
      todos: Ember.A([
        todo('E', 4),
        todo('D', 3),
        todo('C', 2),
        todo('B', 1),
        todo('A', 0)
      ])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('it can filter and sort when both depend on the same item property', function() {
  deepEqual(obj.get('todos').mapBy('name'), ['E', 'D', 'C', 'B', 'A'], 'precond - todos initially correct');
  deepEqual(obj.get('sorted').mapBy('name'), ['A', 'B', 'C', 'D', 'E'], 'precond - sorted initially correct');
  deepEqual(obj.get('filtered').mapBy('name'), ['A', 'C', 'E'], 'precond - filtered initially correct');

  set(obj.get('todos')[1], 'priority', 6);

  deepEqual(obj.get('todos').mapBy('name'), ['E', 'D', 'C', 'B', 'A'], 'precond - todos remain correct');
  deepEqual(obj.get('sorted').mapBy('name'), ['A', 'B', 'C', 'E', 'D'], 'precond - sorted updated correctly');
  deepEqual(obj.get('filtered').mapBy('name'), ['A', 'C', 'E', 'D'], 'filtered updated correctly');
});

var userFnCalls;
QUnit.module('Chaining array and reduced CPs', {
  setup() {
    userFnCalls = 0;
    obj = EmberObject.extend({
      mapped: mapBy('array', 'v'),
      max: max('mapped'),
      maxDidChange: observer('max', () => userFnCalls++)
    }).create({
      array: Ember.A([
        { v: 1 },
        { v: 3 },
        { v: 2 },
        { v: 1 }
      ])
    });
  },
  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('it computes interdependent array computed properties', function() {
  equal(obj.get('max'), 3, 'sanity - it properly computes the maximum value');

  var calls = 0;

  addObserver(obj, 'max', () => calls++);

  obj.get('array').pushObject({ v: 5 });

  equal(obj.get('max'), 5, 'maximum value is updated correctly');
  equal(userFnCalls, 1, 'object defined observers fire');
  equal(calls, 1, 'runtime created observers fire');
});

QUnit.module('sum', {
  setup() {
    obj = EmberObject.extend({
      total: sum('array')
    }).create({
      array: Ember.A([1, 2, 3])
    });
  },

  teardown() {
    Ember.run(obj, 'destroy');
  }
});

QUnit.test('sum is readOnly', function() {
  QUnit.throws(function() {
    obj.set('total', 1);
  }, /Cannot set read-only property "total" on object:/);
});
QUnit.test('sums the values in the dependentKey', function() {
  equal(obj.get('total'), 6, 'sums the values');
});

QUnit.test('if the dependentKey is neither an array nor object, it will return `0`', () => {
  set(obj, 'array', null);
  equal(get(obj, 'total'), 0, 'returns 0');

  set(obj, 'array', undefined);
  equal(get(obj, 'total'), 0, 'returns 0');

  set(obj, 'array', 'not an array');
  equal(get(obj, 'total'), 0, 'returns 0');
});

QUnit.test('updates when array is modified', function() {
  obj.get('array').pushObject(1);

  equal(obj.get('total'), 7, 'recomputed when elements are added');

  obj.get('array').popObject();

  equal(obj.get('total'), 6, 'recomputes when elements are removed');
});


text:
#include "renderdoccmd.h"
#include <app/renderdoc_app.h>
#include <replay/version.h>
#include <string>

// normally this is in the renderdoc core library, but it's needed for the 'unknown enum' path,
// so we implement it here using ostringstream. It's not great, but this is a very uncommon path -
// either for invalid values or for when a new enum is added and the code isn't updated
template <>
std::string DoStringise(const uint32_t &el)
{
  std::ostringstream oss;
  oss << el;
  return oss.str();
}

#include <replay/renderdoc_tostr.inl>

using std::string;
using std::wstring;

bool usingKillSignal = false;
volatile bool killSignal = false;

rdcarray<rdcstr> convertArgs(const std::vector<std::string> &args)
{
  rdcarray<rdcstr> ret;
  ret.reserve(args.size());
  for(size_t i = 0; i < args.size(); i++)
    ret.push_back(args[i]);
  return ret;
}

void DisplayRendererPreview(IReplayController *renderer, uint32_t width, uint32_t height,
                            uint32_t numLoops)
{
  if(renderer == NULL)
    return;

  rdcarray<TextureDescription> texs = renderer->GetTextures();

  TextureDisplay d;
  d.mip = 0;
  d.sampleIdx = ~0U;
  d.overlay = DebugOverlay::NoOverlay;
  d.typeHint = CompType::Typeless;
  d.customShaderId = ResourceId();
  d.hdrMultiplier = -1.0f;
  d.linearDisplayAsGamma = true;
  d.flipY = false;
  d.rangeMin = 0.0f;
  d.rangeMax = 1.0f;
  d.scale = 1.0f;
  d.xOffset = 0.0f;
  d.yOffset = 0.0f;
  d.sliceFace = 0;
  d.rawOutput = false;
  d.red = d.green = d.blue = true;
  d.alpha = false;

  for(const TextureDescription &desc : texs)
  {
    if(desc.creationFlags & TextureCategory::SwapBuffer)
    {
      d.resourceId = desc.resourceId;
      break;
    }
  }

  rdcarray<DrawcallDescription> draws = renderer->GetDrawcalls();

  if(!draws.empty() && draws.back().flags & DrawFlags::Present)
  {
    ResourceId id = draws.back().copyDestination;
    if(id != ResourceId())
      d.resourceId = id;
  }

  DisplayRendererPreview(renderer, d, width, height, numLoops);
}

std::map<std::string, Command *> commands;
std::map<std::string, std::string> aliases;

void add_command(const std::string &name, Command *cmd)
{
  commands[name] = cmd;
}

void add_alias(const std::string &alias, const std::string &command)
{
  aliases[alias] = command;
}

static void clean_up()
{
  for(auto it = commands.begin(); it != commands.end(); ++it)
    delete it->second;
}

static int command_usage(std::string command = "")
{
  if(!command.empty())
    std::cerr << command << " is not a valid command." << std::endl << std::endl;

  std::cerr << "Usage: renderdoccmd <command> [args ...]" << std::endl;
  std::cerr << "Command line tool for capture & replay with RenderDoc." << std::endl << std::endl;

  std::cerr << "Command can be one of:" << std::endl;

  size_t max_width = 0;
  for(auto it = commands.begin(); it != commands.end(); ++it)
  {
    if(it->second->IsInternalOnly())
      continue;

    max_width = std::max(max_width, it->first.length());
  }

  for(auto it = commands.begin(); it != commands.end(); ++it)
  {
    if(it->second->IsInternalOnly())
      continue;

    std::cerr << "  " << it->first;
    for(size_t n = it->first.length(); n < max_width + 4; n++)
      std::cerr << ' ';
    std::cerr << it->second->Description() << std::endl;
  }
  std::cerr << std::endl;

  std::cerr << "To see details of any command, see 'renderdoccmd <command> --help'" << std::endl
            << std::endl;

  std::cerr << "For more information, see <https://renderdoc.org/>." << std::endl;

  return 2;
}

static std::vector<std::string> version_lines;

struct VersionCommand : public Command
{
  VersionCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser) {}
  virtual const char *Description() { return "Print version information"; }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    std::cout << "renderdoccmd " << (sizeof(uintptr_t) == sizeof(uint64_t) ? "x64" : "x86")
              << " v" MAJOR_MINOR_VERSION_STRING << " built from " << GitVersionHash << std::endl;

#if defined(DISTRIBUTION_VERSION)
    std::cout << "Packaged for " << DISTRIBUTION_NAME << " (" << DISTRIBUTION_VERSION << ") - "
              << DISTRIBUTION_CONTACT << std::endl;
#endif

    for(size_t i = 0; i < version_lines.size(); i++)
      std::cout << version_lines[i] << std::endl;

    std::cout << std::endl;

    return 0;
  }
};

void add_version_line(const std::string &str)
{
  version_lines.push_back(str);
}

struct HelpCommand : public Command
{
  HelpCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser) {}
  virtual const char *Description() { return "Print this help message"; }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    command_usage();
    return 0;
  }
};

struct ThumbCommand : public Command
{
  ThumbCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.set_footer("<filename.rdc>");
    parser.add<string>("out", 'o', "The output filename to save the file to", true, "filename.jpg");
    parser.add<string>("format", 'f',
                       "The format of the output file. If empty, detected from filename", false, "",
                       cmdline::oneof<string>("jpg", "png", "bmp", "tga"));
    parser.add<uint32_t>(
        "max-size", 's',
        "The maximum dimension of the thumbnail. Default is 0, which is unlimited.", false, 0);
  }
  virtual const char *Description() { return "Saves a capture's embedded thumbnail to disk."; }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    std::vector<std::string> rest = parser.rest();
    if(rest.empty())
    {
      std::cerr << "Error: thumb command requires a capture filename." << std::endl
                << std::endl
                << parser.usage();
      return 0;
    }

    string filename = rest[0];

    rest.erase(rest.begin());

    RENDERDOC_InitGlobalEnv(m_Env, convertArgs(rest));

    string outfile = parser.get<string>("out");

    string format = parser.get<string>("format");

    uint32_t maxsize = parser.get<uint32_t>("max-size");

    FileType type = FileType::JPG;

    if(format == "png")
    {
      type = FileType::PNG;
    }
    else if(format == "tga")
    {
      type = FileType::TGA;
    }
    else if(format == "bmp")
    {
      type = FileType::BMP;
    }
    else
    {
      const char *dot = strrchr(outfile.c_str(), '.');

      if(dot != NULL && strstr(dot, "png"))
        type = FileType::PNG;
      else if(dot != NULL && strstr(dot, "tga"))
        type = FileType::TGA;
      else if(dot != NULL && strstr(dot, "bmp"))
        type = FileType::BMP;
      else if(dot != NULL && strstr(dot, "jpg"))
        type = FileType::JPG;
      else
        std::cerr << "Couldn't guess format from '" << outfile << "', defaulting to jpg."
                  << std::endl;
    }

    bytebuf buf;

    ICaptureFile *file = RENDERDOC_OpenCaptureFile();
    ReplayStatus st = file->OpenFile(filename.c_str(), "rdc", NULL);
    if(st == ReplayStatus::Succeeded)
    {
      buf = file->GetThumbnail(type, maxsize).data;
    }
    else
    {
      std::cerr << "Couldn't open '" << filename << "': " << ToStr(st) << std::endl;
    }
    file->Shutdown();

    if(buf.empty())
    {
      std::cerr << "Couldn't fetch the thumbnail in '" << filename << "'" << std::endl;
    }
    else
    {
      FILE *f = fopen(outfile.c_str(), "wb");

      if(!f)
      {
        std::cerr << "Couldn't open destination file '" << outfile << "'" << std::endl;
      }
      else
      {
        fwrite(buf.data(), 1, buf.size(), f);
        fclose(f);

        std::cout << "Wrote thumbnail from '" << filename << "' to '" << outfile << "'." << std::endl;
      }
    }

    return 0;
  }
};

struct CaptureCommand : public Command
{
  CaptureCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.set_footer("<executable> [program arguments]");
    parser.stop_at_rest(true);
  }
  virtual const char *Description() { return "Launches the given executable to capture."; }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return true; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &opts)
  {
    if(parser.rest().empty())
    {
      std::cerr << "Error: capture command requires an executable to launch." << std::endl
                << std::endl
                << parser.usage();
      return 0;
    }

    std::string executable = parser.rest()[0];
    std::string workingDir = parser.get<string>("working-dir");
    std::string cmdLine;
    std::string logFile = parser.get<string>("capture-file");

    for(size_t i = 1; i < parser.rest().size(); i++)
    {
      if(!cmdLine.empty())
        cmdLine += ' ';

      cmdLine += EscapeArgument(parser.rest()[i]);
    }

    RENDERDOC_InitGlobalEnv(m_Env, rdcarray<rdcstr>());

    std::cout << "Launching '" << executable << "'";

    if(!cmdLine.empty())
      std::cout << " with params: " << cmdLine;

    std::cout << std::endl;

    rdcarray<EnvironmentModification> env;

    ExecuteResult res = RENDERDOC_ExecuteAndInject(
        executable.c_str(), workingDir.empty() ? "" : workingDir.c_str(),
        cmdLine.empty() ? "" : cmdLine.c_str(), env, logFile.empty() ? "" : logFile.c_str(), opts,
        parser.exist("wait-for-exit"));

    if(res.status != ReplayStatus::Succeeded)
    {
      std::cerr << "Failed to create & inject: " << ToStr(res.status) << std::endl;
      return (int)res.status;
    }

    if(parser.exist("wait-for-exit"))
    {
      std::cerr << "'" << executable << "' finished executing." << std::endl;
      res.ident = 0;
    }
    else
    {
      std::cerr << "Launched as ID " << res.ident << std::endl;
    }

    return res.ident;
  }

  std::string EscapeArgument(const std::string &arg)
  {
    // nothing to escape or quote
    if(arg.find_first_of(" \t\r\n\"") == std::string::npos)
      return arg;

    // return arg in quotes, with any quotation marks escaped
    std::string ret = arg;

    size_t i = ret.find('\"');
    while(i != std::string::npos)
    {
      ret.insert(ret.begin() + i, '\\');

      i = ret.find('\"', i + 2);
    }

    return '"' + ret + '"';
  }
};

struct InjectCommand : public Command
{
  InjectCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.add<uint32_t>("PID", 0, "The process ID of the process to inject.", true);
  }
  virtual const char *Description() { return "Injects RenderDoc into a given running process."; }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return true; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &opts)
  {
    uint32_t PID = parser.get<uint32_t>("PID");
    std::string workingDir = parser.get<string>("working-dir");
    std::string logFile = parser.get<string>("capture-file");

    std::cout << "Injecting into PID " << PID << std::endl;

    rdcarray<EnvironmentModification> env;

    RENDERDOC_InitGlobalEnv(m_Env, convertArgs(parser.rest()));

    ExecuteResult res = RENDERDOC_InjectIntoProcess(
        PID, env, logFile.empty() ? "" : logFile.c_str(), opts, parser.exist("wait-for-exit"));

    if(res.status != ReplayStatus::Succeeded)
    {
      std::cerr << "Failed to inject: " << ToStr(res.status) << std::endl;
      return (int)res.status;
    }

    if(parser.exist("wait-for-exit"))
    {
      std::cerr << PID << " finished executing." << std::endl;
      res.ident = 0;
    }
    else
    {
      std::cerr << "Launched as ID " << res.ident << std::endl;
    }

    return res.ident;
  }
};

struct RemoteServerCommand : public Command
{
  RemoteServerCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.add("daemon", 'd', "Go into the background.");
    parser.add<string>(
        "host", 'h', "The interface to listen on. By default listens on all interfaces", false, "");
    parser.add<uint32_t>("port", 'p', "The port to listen on.", false,
                         RENDERDOC_GetDefaultRemoteServerPort());
    parser.add("preview", 'v', "Display a preview window when a replay is active.");
  }
  virtual const char *Description()
  {
    return "Start up a server listening as a host for remote replays.";
  }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    string host = parser.get<string>("host");
    uint32_t port = parser.get<uint32_t>("port");

    RENDERDOC_InitGlobalEnv(m_Env, convertArgs(parser.rest()));

    std::cerr << "Spawning a replay host listening on " << (host.empty() ? "*" : host) << ":"
              << port << "..." << std::endl;

    if(parser.exist("daemon"))
    {
      std::cerr << "Detaching." << std::endl;
      Daemonise();
    }

    usingKillSignal = true;

    // by default have a do-nothing callback that creates no windows
    RENDERDOC_PreviewWindowCallback previewWindow;

    // if the user asked for a preview, then call to the platform-specific preview function
    if(parser.exist("preview"))
      previewWindow = &DisplayRemoteServerPreview;

    // OR if the platform-specific preview function always has a window, then return it anyway.
    if(DisplayRemoteServerPreview(false, {}).system != WindowingSystem::Unknown)
      previewWindow = &DisplayRemoteServerPreview;

    RENDERDOC_BecomeRemoteServer(host.empty() ? NULL : host.c_str(), port,
                                 []() { return killSignal; }, previewWindow);

    std::cerr << std::endl << "Cleaning up from replay hosting." << std::endl;

    return 0;
  }
};

struct ReplayCommand : public Command
{
  ReplayCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.set_footer("<capture.rdc>");
    parser.add<uint32_t>("width", 'w', "The preview window width.", false, 1280);
    parser.add<uint32_t>("height", 'h', "The preview window height.", false, 720);
    parser.add<uint32_t>("loops", 'l', "How many times to loop the replay, or 0 for indefinite.",
                         false, 0);
    parser.add<string>("remote-host", 0,
                       "Instead of replaying locally, replay on this host over the network.", false);
    parser.add<uint32_t>("remote-port", 0, "If --remote-host is set, use this port.", false,
                         RENDERDOC_GetDefaultRemoteServerPort());
  }
  virtual const char *Description()
  {
    return "Replay the log file and show the backbuffer on a preview window.";
  }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    std::vector<std::string> rest = parser.rest();
    if(rest.empty())
    {
      std::cerr << "Error: capture command requires a filename to load." << std::endl
                << std::endl
                << parser.usage();
      return 0;
    }

    string filename = rest[0];

    rest.erase(rest.begin());

    RENDERDOC_InitGlobalEnv(m_Env, convertArgs(rest));

    if(parser.exist("remote-host"))
    {
      std::cout << "Replaying '" << filename << "' on " << parser.get<string>("remote-host") << ":"
                << parser.get<uint32_t>("remote-port") << "." << std::endl;

      IRemoteServer *remote = NULL;
      ReplayStatus status = RENDERDOC_CreateRemoteServerConnection(
          parser.get<string>("remote-host").c_str(), parser.get<uint32_t>("remote-port"), &remote);

      if(remote == NULL || status != ReplayStatus::Succeeded)
      {
        std::cerr << "Error: " << ToStr(status) << " - Couldn't connect to "
                  << parser.get<string>("remote-host") << ":" << parser.get<uint32_t>("remote-port")
                  << "." << std::endl;
        std::cerr << "       Have you run renderdoccmd remoteserver on '"
                  << parser.get<string>("remote-host") << "'?" << std::endl;
        return 1;
      }

      std::cerr << "Copying capture file to remote server" << std::endl;

      rdcstr remotePath = remote->CopyCaptureToRemote(filename.c_str(), NULL);

      IReplayController *renderer = NULL;
      std::tie(status, renderer) = remote->OpenCapture(~0U, remotePath.c_str(), NULL);

      if(status == ReplayStatus::Succeeded)
      {
        DisplayRendererPreview(renderer, parser.get<uint32_t>("width"),
                               parser.get<uint32_t>("height"), parser.get<uint32_t>("loops"));

        remote->CloseCapture(renderer);
      }
      else
      {
        std::cerr << "Couldn't load and replay '" << filename << "': " << ToStr(status) << std::endl;
      }

      remote->ShutdownConnection();
    }
    else
    {
      std::cout << "Replaying '" << filename << "' locally.." << std::endl;

      ICaptureFile *file = RENDERDOC_OpenCaptureFile();

      if(file->OpenFile(filename.c_str(), "rdc", NULL) != ReplayStatus::Succeeded)
      {
        std::cerr << "Couldn't load '" << filename << "'." << std::endl;
        return 1;
      }

      IReplayController *renderer = NULL;
      ReplayStatus status = ReplayStatus::InternalError;
      std::tie(status, renderer) = file->OpenCapture(NULL);

      file->Shutdown();

      if(status == ReplayStatus::Succeeded)
      {
        DisplayRendererPreview(renderer, parser.get<uint32_t>("width"),
                               parser.get<uint32_t>("height"), parser.get<uint32_t>("loops"));

        renderer->Shutdown();
      }
      else
      {
        std::cerr << "Couldn't load and replay '" << filename << "': " << ToStr(status) << std::endl;
        return 1;
      }
    }
    return 0;
  }
};

struct formats_reader
{
  formats_reader()
  {
    ICaptureFile *tmp = RENDERDOC_OpenCaptureFile();

    for(const CaptureFileFormat &f : tmp->GetCaptureFileFormats())
    {
      exts.push_back(f.extension);
      names.push_back(f.name);
    }

    tmp->Shutdown();
  }
  std::string operator()(const std::string &s)
  {
    if(std::find(exts.begin(), exts.end(), s) == exts.end())
      throw cmdline::cmdline_error("'" + s + "' is not one of the accepted values");
    return s;
  }
  std::string description() const
  {
    std::string ret = "Options are:";
    for(size_t i = 0; i < exts.size(); i++)
      ret += "\n  * " + exts[i] + " - " + names[i];
    return ret;
  }

private:
  std::vector<std::string> exts;
  std::vector<std::string> names;
};

struct ConvertCommand : public Command
{
  rdcarray<CaptureFileFormat> m_Formats;

  ConvertCommand(const GlobalEnvironment &env) : Command(env)
  {
    ICaptureFile *tmp = RENDERDOC_OpenCaptureFile();

    m_Formats = tmp->GetCaptureFileFormats();

    tmp->Shutdown();
  }

  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.add<string>("filename", 'f', "The file to convert from.", false);
    parser.add<string>("output", 'o', "The file to convert to.", false);
    parser.add<string>("input-format", 'i', "The format of the input file.", false, "",
                       formats_reader());
    parser.add<string>("convert-format", 'c', "The format of the output file.", false, "",
                       formats_reader());
    parser.add("list-formats", '\0', "Print a list of target formats.");
    parser.stop_at_rest(true);
  }
  virtual const char *Description() { return "Convert between capture formats."; }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    if(parser.exist("list-formats"))
    {
      std::cout << "Available formats:" << std::endl;
      for(CaptureFileFormat f : m_Formats)
        std::cout << "'" << (std::string)f.extension << "': " << (std::string)f.name << std::endl
                  << " * " << (std::string)f.description << std::endl
                  << std::endl;
      return 0;
    }

    std::string infile = parser.get<string>("filename");
    std::string outfile = parser.get<string>("output");

    if(infile.empty())
    {
      std::cerr << "Need an input filename (-f)." << std::endl << std::endl;
      std::cerr << parser.usage() << std::endl;
      return 1;
    }

    if(outfile.empty())
    {
      std::cerr << "Need an output filename (-o)." << std::endl << std::endl;
      std::cerr << parser.usage() << std::endl;
      return 1;
    }

    std::string infmt = parser.get<string>("input-format");
    std::string outfmt = parser.get<string>("convert-format");

    // sort the formats by the length of the extension, so we check the longest ones first. This
    // means that .zip.xml will get chosen before just .xml
    std::sort(m_Formats.begin(), m_Formats.end(),
              [](const CaptureFileFormat &a, const CaptureFileFormat &b) {
                return a.extension.size() > b.extension.size();
              });

    if(infmt.empty())
    {
      // try to guess the format by looking for the extension in the filename
      for(CaptureFileFormat f : m_Formats)
      {
        string extension = ".";
        extension += f.extension;

        if(infile.find(extension.c_str()) != string::npos)
        {
          infmt = f.extension;
          break;
        }
      }
    }

    if(infmt.empty())
    {
      std::cerr << "Couldn't guess input format from filename." << std::endl << std::endl;
      std::cerr << parser.usage() << std::endl;
      return 1;
    }

    if(outfmt.empty())
    {
      // try to guess the format by looking for the extension in the filename
      for(CaptureFileFormat f : m_Formats)
      {
        string extension = ".";
        extension += f.extension;

        if(outfile.find(extension.c_str()) != string::npos)
        {
          outfmt = f.extension;
          break;
        }
      }
    }

    if(outfmt.empty())
    {
      std::cerr << "Couldn't guess output format from filename." << std::endl << std::endl;
      std::cerr << parser.usage() << std::endl;
      return 1;
    }

    ICaptureFile *file = RENDERDOC_OpenCaptureFile();

    ReplayStatus st = file->OpenFile(infile.c_str(), infmt.c_str(), NULL);

    if(st != ReplayStatus::Succeeded)
    {
      std::cerr << "Couldn't load '" << infile << "' as '" << infmt << "': " << ToStr(st)
                << std::endl;
      return 1;
    }

    st = file->Convert(outfile.c_str(), outfmt.c_str(), NULL, NULL);

    if(st != ReplayStatus::Succeeded)
    {
      std::cerr << "Couldn't convert '" << infile << "' to '" << outfile << "' as '" << outfmt
                << "': " << ToStr(st) << std::endl;
      return 1;
    }

    std::cout << "Converted '" << infile << "' to '" << outfile << "'" << std::endl;

    return 0;
  }
};

struct TestCommand : public Command
{
  TestCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.set_footer(
#if PYTHON_VERSION_MINOR > 0
        "<unit|functional>"
#else
        "<unit>"
#endif
        " [... parameters to test framework ...]");
    parser.add("help", '\0', "print this message");
    parser.stop_at_rest(true);
  }
  virtual const char *Description() { return "Run internal tests such as unit tests."; }
  virtual bool HandlesUsageManually() { return true; }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    std::vector<std::string> rest = parser.rest();

    if(rest.empty())
    {
      std::cerr << "First argument must specify a test framework" << std::endl << std::endl;
      std::cerr << parser.usage() << std::endl;
      return 1;
    }

    std::string mode = rest[0];
    rest.erase(rest.begin());

    if(parser.exist("help"))
      rest.push_back("--help");

    if(mode == "unit")
      return RENDERDOC_RunUnitTests("renderdoccmd test unit", convertArgs(rest));
#if PYTHON_VERSION_MINOR > 0
    else if(mode == "functional")
      return RENDERDOC_RunFunctionalTests(PYTHON_VERSION_MINOR, convertArgs(rest));
#endif

    std::cerr << "Unsupported test frame work '" << mode << "'" << std::endl << std::endl;
    std::cerr << parser.usage() << std::endl;
    return 1;
  }
};

struct CapAltBitCommand : public Command
{
  CapAltBitCommand(const GlobalEnvironment &env) : Command(env) {}
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.add<uint32_t>("pid", 0, "");
    parser.add<string>("capfile", 0, "");
    parser.add<string>("debuglog", 0, "");
    parser.add<string>("capopts", 0, "");
    parser.stop_at_rest(true);
  }
  virtual const char *Description() { return "Internal use only!"; }
  virtual bool IsInternalOnly() { return true; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    CaptureOptions cmdopts;
    cmdopts.DecodeFromString(parser.get<string>("capopts"));

    RENDERDOC_InitGlobalEnv(m_Env, rdcarray<rdcstr>());

    std::vector<std::string> rest = parser.rest();

    if(rest.size() % 3 != 0)
    {
      std::cerr << "Invalid generated capaltbit command rest.size() == " << rest.size() << std::endl;
      return 0;
    }

    int numEnvs = int(rest.size() / 3);

    rdcarray<EnvironmentModification> env;
    env.reserve(numEnvs);

    for(int i = 0; i < numEnvs; i++)
    {
      string typeString = rest[i * 3 + 0];

      EnvMod type = EnvMod::Set;
      EnvSep sep = EnvSep::NoSep;

      if(typeString == "+env-replace")
      {
        type = EnvMod::Set;
        sep = EnvSep::NoSep;
      }
      else if(typeString == "+env-append-platform")
      {
        type = EnvMod::Append;
        sep = EnvSep::Platform;
      }
      else if(typeString == "+env-append-semicolon")
      {
        type = EnvMod::Append;
        sep = EnvSep::SemiColon;
      }
      else if(typeString == "+env-append-colon")
      {
        type = EnvMod::Append;
        sep = EnvSep::Colon;
      }
      else if(typeString == "+env-append")
      {
        type = EnvMod::Append;
        sep = EnvSep::NoSep;
      }
      else if(typeString == "+env-prepend-platform")
      {
        type = EnvMod::Prepend;
        sep = EnvSep::Platform;
      }
      else if(typeString == "+env-prepend-semicolon")
      {
        type = EnvMod::Prepend;
        sep = EnvSep::SemiColon;
      }
      else if(typeString == "+env-prepend-colon")
      {
        type = EnvMod::Prepend;
        sep = EnvSep::Colon;
      }
      else if(typeString == "+env-prepend")
      {
        type = EnvMod::Prepend;
        sep = EnvSep::NoSep;
      }
      else
      {
        std::cerr << "Invalid generated capaltbit env '" << rest[i * 3 + 0] << std::endl;
        return 0;
      }

      env.push_back(
          EnvironmentModification(type, sep, rest[i * 3 + 1].c_str(), rest[i * 3 + 2].c_str()));
    }

    string debuglog = parser.get<string>("debuglog");

    RENDERDOC_SetDebugLogFile(debuglog.c_str());

    ExecuteResult result = RENDERDOC_InjectIntoProcess(
        parser.get<uint32_t>("pid"), env, parser.get<string>("capfile").c_str(), cmdopts, false);

    if(result.status == ReplayStatus::Succeeded)
      return result.ident;

    return (int)result.status;
  }
};

struct EmbeddedSectionCommand : public Command
{
  bool m_Extract = false;
  EmbeddedSectionCommand(const GlobalEnvironment &env, bool extract) : Command(env)
  {
    m_Extract = extract;
  }
  virtual void AddOptions(cmdline::parser &parser)
  {
    parser.set_footer("<capture.rdc>");
    parser.add<std::string>("section", 's', "The embedded section name.");
    parser.add<std::string>("file", 'f', m_Extract ? "The file to write the section contents to."
                                                   : "The file to read the section contents from.");
    parser.add("no-clobber", 'n', m_Extract ? "Don't overwrite the file if it already exists."
                                            : "Don't overwrite the section if it already exists.");

    if(!m_Extract)
    {
      parser.add("lz4", 0, "Use LZ4 to compress the data.");
      parser.add("zstd", 0, "Use Zstandard to compress the data.");
    }

    parser.add("list-sections", 0, "Print a list of known sections.");
  }
  virtual const char *Description()
  {
    if(m_Extract)
      return "Extract an arbitrary section of data from a capture.";
    else
      return "Inject an arbitrary section of data into a capture.";
  }
  virtual bool IsInternalOnly() { return false; }
  virtual bool IsCaptureCommand() { return false; }
  virtual int Execute(cmdline::parser &parser, const CaptureOptions &)
  {
    if(parser.exist("list-sections"))
    {
      std::cout << "Known sections:" << std::endl;
      for(SectionType s : values<SectionType>())
        std::cout << ToStr(s) << std::endl;
      return 0;
    }

    std::vector<std::string> rest = parser.rest();
    if(rest.empty())
    {
      std::cerr << "Error: this command requires a filename to load." << std::endl
                << std::endl
                << parser.usage();
      return 0;
    }

    std::string rdc = rest[0];

    rest.erase(rest.begin());

    RENDERDOC_InitGlobalEnv(m_Env, convertArgs(rest));

    std::string file = parser.get<std::string>("file");
    std::string section = parser.get<std::string>("section");
    bool noclobber = parser.exist("no-clobber");
    bool lz4 = !m_Extract && parser.exist("lz4");
    bool zstd = !m_Extract && parser.exist("zstd");

    if(zstd && lz4)
    {
      std::cerr << "Can't compress with Zstandard and lz4 - ignoring lz4." << std::endl;
      lz4 = false;
    }

    ICaptureFile *capfile = RENDERDOC_OpenCaptureFile();

    ReplayStatus status = capfile->OpenFile(rdc.c_str(), "", NULL);

    if(status != ReplayStatus::Succeeded)
    {
      capfile->Shutdown();
      std::cerr << "Couldn't load '" << rdc << "': " << ToStr(status) << std::endl;
      return 1;
    }

    if(m_Extract)
    {
      int idx = capfile->FindSectionByName(section.c_str());

      if(idx < 0)
      {
        std::cerr << "'" << rdc << "' has no section called '" << section << "'" << std::endl;
        std::cerr << "Available sections are:" << std::endl;

        int num = capfile->GetSectionCount();

        for(int i = 0; i < num; i++)
          std::cerr << "    " << capfile->GetSectionProperties(i).name.c_str() << std::endl;

        capfile->Shutdown();
        return 1;
      }

      FILE *f = NULL;

      if(noclobber)
      {
        bool exists = false;
        f = fopen(file.c_str(), "rb");
        if(f)
        {
          exists = true;
          fclose(f);
          f = NULL;
        }

        if(exists)
        {
          capfile->Shutdown();
          std::cerr << "Refusing to overwrite '" << file << "'" << std::endl;
          return 1;
        }
      }

      f = fopen(file.c_str(), "wb");

      if(!f)
      {
        capfile->Shutdown();
        std::cerr << "Couldn't open destination file '" << file << "'" << std::endl;
        return 1;
      }
      else
      {
        bytebuf blob = capfile->GetSectionContents(idx);

        capfile->Shutdown();

        fwrite(blob.data(), 1, blob.size(), f);
        fclose(f);

        std::cout << "Wrote '" << section << "' from '" << rdc << "' to '" << file << "'."
                  << std::endl;
      }
    }
    else    // insert/embed
    {
      int idx = capfile->FindSectionByName(section.c_str());

      if(idx >= 0)
      {
        if(noclobber)
        {
          capfile->Shutdown();
          std::cerr << "Refusing to overwrite section '" << section << "' in '" << rdc << "'"
                    << std::endl;
          return 1;
        }
        else
        {
          std::cout << "Overwriting section '" << section << "' in '" << rdc << "'" << std::endl;
        }
      }

      FILE *f = fopen(file.c_str(), "rb");

      if(!f)
      {
        capfile->Shutdown();
        std::cerr << "Couldn't open source file '" << file << "'" << std::endl;
        return 1;
      }

      bytebuf blob;

      fseek(f, 0, SEEK_END);
      int len = ftell(f);
      fseek(f, 0, SEEK_SET);

      if(len < 0)
      {
        len = 0;
        std::cerr << "I/O error reading from '" << file << "'" << std::endl;
      }

      blob.resize((size_t)len);
      size_t read = fread(blob.data(), 1, (size_t)len, f);

      if(read != (size_t)len)
        std::cerr << "I/O error reading from '" << file << "'" << std::endl;

      fclose(f);

      SectionProperties props;
      props.name = section;

      for(SectionType s : values<SectionType>())
      {
        if(ToStr(s) == section)
        {
          props.type = s;
          break;
        }
      }

      if(zstd)
        props.flags |= SectionFlags::ZstdCompressed;
      if(lz4)
        props.flags |= SectionFlags::LZ4Compressed;

      capfile->WriteSection(props, blob);

      capfile->Shutdown();

      std::cout << "Wrote '" << section << "' from '" << file << "' to '" << rdc << "'." << std::endl;
    }

    return 0;
  }
};

REPLAY_PROGRAM_MARKER()

int renderdoccmd(const GlobalEnvironment &env, std::vector<std::string> &argv)
{
  try
  {
    // add basic commands, and common aliases
    add_command("version", new VersionCommand(env));

    add_alias("--version", "version");
    add_alias("-v", "version");
    // for windows
    add_alias("/version", "version");
    add_alias("/v", "version");

    add_command("help", new HelpCommand(env));

    add_alias("--help", "help");
    add_alias("-h", "help");
    add_alias("-?", "help");

    // for windows
    add_alias("/help", "help");
    add_alias("/h", "help");
    add_alias("/?", "help");

    // add platform agnostic commands
    add_command("thumb", new ThumbCommand(env));
    add_command("capture", new CaptureCommand(env));
    add_command("inject", new InjectCommand(env));
    add_command("remoteserver", new RemoteServerCommand(env));
    add_command("replay", new ReplayCommand(env));
    add_command("capaltbit", new CapAltBitCommand(env));
    add_command("test", new TestCommand(env));
    add_command("convert", new ConvertCommand(env));
    add_command("embed", new EmbeddedSectionCommand(env, false));
    add_command("extract", new EmbeddedSectionCommand(env, true));

    if(argv.size() <= 1)
    {
      int ret = command_usage();
      clean_up();
      return ret;
    }

    // std::string programName = argv[0];

    argv.erase(argv.begin());

    std::string command = *argv.begin();

    argv.erase(argv.begin());

    auto it = commands.find(command);

    if(it == commands.end())
    {
      auto a = aliases.find(command);
      if(a != aliases.end())
        it = commands.find(a->second);
    }

    if(it == commands.end())
    {
      int ret = command_usage(command);
      clean_up();
      return ret;
    }

    cmdline::parser cmd;

    cmd.set_program_name("renderdoccmd");
    cmd.set_header(command);

    it->second->AddOptions(cmd);

    if(it->second->IsCaptureCommand())
    {
      cmd.add<string>("working-dir", 'd', "Set the working directory of the program, if launched.",
                      false);
      cmd.add<string>("capture-file", 'c',
                      "Set the filename template for new captures. Frame number will be "
                      "automatically appended.",
                      false);
      cmd.add("wait-for-exit", 'w', "Wait for the target program to exit, before returning.");

      // CaptureOptions
      cmd.add("opt-disallow-vsync", 0,
              "Capturing Option: Disallow the application from enabling vsync.");
      cmd.add("opt-disallow-fullscreen", 0,
              "Capturing Option: Disallow the application from enabling fullscreen.");
      cmd.add("opt-api-validation", 0,
              "Capturing Option: Record API debugging events and messages.");
      cmd.add("opt-api-validation-unmute", 0,
              "Capturing Option: Unmutes API debugging output from --opt-api-validation.");
      cmd.add("opt-capture-callstacks", 0,
              "Capturing Option: Capture CPU callstacks for API events.");
      cmd.add("opt-capture-callstacks-only-draws", 0,
              "Capturing Option: When capturing CPU callstacks, only capture them from drawcalls.");
      cmd.add<int>("opt-delay-for-debugger", 0,
                   "Capturing Option: Specify a delay in seconds to wait for a debugger to attach.",
                   false, 0, cmdline::range(0, 10000));
      cmd.add("opt-verify-buffer-access", 0,
              "Capturing Option: Verify any writes to mapped buffers, by bounds checking, and "
              "initialise buffers to invalid value if uninitialised.");
      cmd.add("opt-hook-children", 0,
              "Capturing Option: Hooks any system API calls that create child processes.");
      cmd.add("opt-ref-all-resources", 0,
              "Capturing Option: Include all live resources, not just those used by a frame.");
      cmd.add("opt-capture-all-cmd-lists", 0,
              "Capturing Option: In D3D11, record all command lists from application start.");
    }

    cmd.parse_check(argv, true);

    CaptureOptions opts;
    RENDERDOC_GetDefaultCaptureOptions(&opts);

    if(it->second->IsCaptureCommand())
    {
      if(cmd.exist("opt-disallow-vsync"))
        opts.allowVSync = false;
      if(cmd.exist("opt-disallow-fullscreen"))
        opts.allowFullscreen = false;
      if(cmd.exist("opt-api-validation"))
        opts.apiValidation = true;
      if(cmd.exist("opt-api-validation-unmute"))
        opts.debugOutputMute = false;
      if(cmd.exist("opt-capture-callstacks"))
        opts.captureCallstacks = true;
      if(cmd.exist("opt-capture-callstacks-only-draws"))
        opts.captureCallstacksOnlyDraws = true;
      if(cmd.exist("opt-verify-buffer-access"))
        opts.verifyBufferAccess = true;
      if(cmd.exist("opt-hook-children"))
        opts.hookIntoChildren = true;
      if(cmd.exist("opt-ref-all-resources"))
        opts.refAllResources = true;
      if(cmd.exist("opt-capture-all-cmd-lists"))
        opts.captureAllCmdLists = true;

      opts.delayForDebugger = (uint32_t)cmd.get<int>("opt-delay-for-debugger");
    }

    if(!it->second->HandlesUsageManually() && cmd.exist("help"))
    {
      std::cerr << cmd.usage() << std::endl;
      clean_up();
      return 0;
    }

    int ret = it->second->Execute(cmd, opts);
    clean_up();
    return ret;
  }
  catch(std::exception &e)
  {
    fprintf(stderr, "Unexpected exception: %s\n", e.what());

    exit(1);
  }
}

int renderdoccmd(const GlobalEnvironment &env, int argc, char **c_argv)
{
  std::vector<std::string> argv;
  argv.resize(argc);
  for(int i = 0; i < argc; i++)
    argv[i] = c_argv[i];

  return renderdoccmd(env, argv);
}

redpajama_set_name: RedPajamaGithub


text:
ACCEPTED

#### According to
Index Fungorum

#### Published in
null

#### Original name
Euryachora rosicola Av.-Saccá

### Remarks
null


text:
using CodeModel.Graphs;

namespace CodeModel.Primitives
{
    public class ContainedInLink : Link
    {
    }
}


text:
SYNONYM

#### According to
The Catalogue of Life, 3rd January 2011

#### Published in
Aust. J. Bot. 31(5): 467 (1983)

#### Original name
Parmelia arapilensis Elix & P. M. Armstr.

### Remarks
null


text:
<!DOCTYPE html>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>El Niño - aktualizacja 1</title>
  <meta name="description" content="Ponieważ El Niño 15/16 rozwija się zgodnie z przewidywaniami, można pokusić się o kilka prognoz na koniec roku.">

  <link rel="stylesheet" href="https://doskonaleszare.github.io/css/tufte.css">

  <link rel="canonical" href="https://doskonaleszare.github.io/articles/15/el-nino-aktualizacja-1">
  <link rel="alternate" type="application/rss+xml" title="Doskonale Szare" href="https://doskonaleszare.github.io/feed.xml" />

  <meta name="twitter:card" content="summary"></meta>
  <meta name="twitter:site"    content="@doskonaleszare">
  <meta name="twitter:creator" content="@doskonaleszare">
  <meta name="twitter:title"   content="El Niño - aktualizacja 1">

  <meta name="twitter:description" content="Ponieważ El Niño 15/16 rozwija się zgodnie z przewidywaniami, można pokusić się o kilka prognoz na koniec roku.">

Doskonale Szare

		O blogu

El Niño - aktualizacja 1

July 25, 2015

            ← poprzednia notka

	|

            następna notka →

Ponieważ El Niño 15/16 rozwija się zgodnie z przewidywaniami, można pokusić się o kilka prognoz na koniec roku.

Nie będą one specjalnie oryginalne , bo akurat teraz najłatwiej przewidywać dalszą ewolucję ENSO: jesteśmy we właściwym miejscu cyklu sezonowego, El Niño jest właściwego, "wschodniopacyficznego" typu, tropikalna atmosfera odpowiada we właściwy sposób (czego zabrakło w zeszłym roku), kolejna fala Kelvina jest już w drodze, a pod powierzchnią oceanu jest dużo bardzo ciepłej wody.

Indeks NINO3.4 osiągnął w poprzednim tygodniu +1,7°C, i można spodziewać się przekroczenia progu +2,0°C jesienią. Poprzednim razem pisałem, że modele sugerują "co najmniej średnie" El Niño. Obecne prognozy mówią już o "co najmniej silnym, a być może i rekordowym" El Niño.

Wiązka prognoz indeksu ENSO 3.4 wygenerowana przez NMME.

Wiązka prognoz indeksu ENSO 3.4 wygenerowana przez GloSea5 Met Office.

Wiązka prognoz indeksu ENSO 3.4 wygenerowana przez POAMA BOM.

Wiązka prognoz indeksu ENSO 3.4 wygenerowana przez SFS ECMWF.

Oczywiście planeta to nie tylko tropikalny Pacyfik, ale biorąc pod uwagę statystyczną korelację pomiędzy ENSO a anomalią temperatury globalnej można z dużym prawdopodobieństwem oczekiwać że:

1

Druga połowa roku będzie co najmniej tak ciepła jak pierwsza. W konsekwencji, rok 2015 będzie nie tylko kolejnym najcieplejszym w historii obserwacji, ale nowy rekord zostanie pobity ze sporym marginesem. Powiedziałbym, że w tym momencie rekord jest niemal gwarantowany, przy czym "niemal" wynika tylko z tego, że zawsze może w tropikach wybuchnąć duży wulkan.

Zmiany temperatury globalnej w analizach NOAA, NASA i Met Office. Krzywe przesunięte są o 0,1°C w celu zwiększenia czytelności, wartość 2015 jest szacowana na podstawie średniej z połowy roku.

2

Co najmniej 2 z najbliższych 6 miesięcy będzie globalnie rekordowo ciepłych (największe na to szanse mają lipiec, listopad i grudzień).

3

W analizach satelitarnych temperatury dolnej troposfery 2015 nie pobije rekordu…

4

…ale pobije go w roku 2016.

5

W analizach temperatury globalnej 2016 będzie cieplejszy niż (obecnie rekordowy) 2014.

6

2016 będzie cieplejszy niż 2015.

7

Popularne wśród denialistów prognozy globalnego ochłodzenia będą pod koniec roku wyglądać jeszcze bardziej komicznie.

    Podaj dalej →
    Twitter
    Facebook
    Google+

Please enable JavaScript to view the comments powered by Disqus.

    El Niño - aktualizacja 1 - July 25, 2015 - Doskonale Szare

© 2020   DOSKONALE SZARE

Theme: Tufte theme


text:
import React from 'react';
import Button from 'material-ui/Button';
import Dialog, {
    DialogActions,
    DialogContent,
    DialogContentText,
    DialogTitle,
} from 'material-ui/Dialog';
import { Link, NavLink } from 'react-router-dom'
import Slide from 'material-ui/transitions/Slide';

function Transition(props) {
    return <Slide direction="up" {...props} />;
}

class PopupDialog extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            open: false,
        };
        this.handleClickOpen = this.handleClickOpen.bind(this)
        this.handleClose = this.handleClose.bind(this)

    }

    componentDidMount() {
        console.log('popup mounted', this.props)
        { this.props.user.avatar === "" ? this.handleClickOpen() : undefined }
    }

    handleClickOpen() {
        this.setState({ open: true });
    };

    handleClose() {
        this.setState({ open: false });
    };

    render() {
        return (
            <div>
                {/* <Button onClick={this.handleClickOpen}>Slide in alert dialog</Button> */}
                <Dialog
                    open={this.state.open}
                    transition={Transition}
                    keepMounted
                    onClose={this.handleClose}

                >
                    <DialogContent style={{ fontSize: '48' }}>
                        Hello, {this.props.user.username}!
                    </DialogContent>
                    <DialogContent>
                        <DialogContentText style={{ fontSize: '24' }}>
                            Thanks for joing the community, to get the best experience possible please go update your profile!
            </DialogContentText>
                    </DialogContent>
                    <DialogActions>
                        <Link to={'/settings'}><Button onClick={this.handleClose} style={{ color: `#42d1a8`, fontSize: '18' }}>
                            I would love too!
                        </Button></Link>
                        <Button onClick={this.handleClose} style={{ color: `#42d1a8`, fontSize: '12' }}>
                            I'm camera shy...
            </Button>
                    </DialogActions>
                </Dialog>
            </div>
        );
    }
}

export default PopupDialog;


text:
using namespace Dcdr::Server;

Scene::Scene(
        const std::string& sceneFileName,
        uint32_t sceneFileId,
        const std::string& name,
        uint16_t width,
        uint16_t height) :
    sceneFileName_(sceneFileName),
    sceneFileId_(sceneFileId),
    name_(name),
    width_(width),
    height_(height) {}

uint16_t Scene::get_width() const
{
    return width_;
}

uint16_t Scene::get_height() const
{
    return height_;
}

const std::string& Scene::get_scene_file_name() const
{
    return sceneFileName_;
}

uint32_t Scene::get_scene_file_id() const
{
    return sceneFileId_;
}

const std::string& Scene::get_name() const
{
    return name_;
}