├── .gitignore ├── .npmignore ├── src ├── Prediction.js ├── OrientedBoundingBox.js ├── Point.js ├── index.js ├── Constants.js ├── Rect.js ├── Learner.js ├── InstanceLearner.js ├── Stroke.js ├── Instance.js ├── Gesture.js ├── GestureStore.js └── Utils.js ├── NOTICE ├── gulpfile.js ├── package.json ├── bower.json ├── LICENSE ├── trainer ├── library.css ├── index.html └── library.js ├── README.md ├── gestrec.min.js └── gestrec.js /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | node_modules 3 | .DS_Store 4 | .idea -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | trainer/ 3 | bower.json 4 | .DS_Store -------------------------------------------------------------------------------- /src/Prediction.js: -------------------------------------------------------------------------------- 1 | function Prediction(label, predictionScore) { 2 | this.name = label; 3 | this.score = predictionScore; 4 | } 5 | 6 | Prediction.prototype.toString = function() { 7 | return this.name; 8 | }; 9 | 10 | module.exports = Prediction; 11 | -------------------------------------------------------------------------------- /src/OrientedBoundingBox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An oriented bounding box 3 | */ 4 | module.exports = function OrientedBoundingBox(angle, cx, cy, w, h) { 5 | this.orientation = angle; 6 | this.width = w; 7 | this.height = h; 8 | this.centerX = cx; 9 | this.centerY = cy; 10 | var ratio = w / h; 11 | this.squareness = ratio > 1 ? (1/ratio) : ratio; 12 | }; -------------------------------------------------------------------------------- /src/Point.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A timed point of a gesture stroke. Multiple points form a stroke. 3 | */ 4 | module.exports = function Point(x, y, t) { 5 | if (x instanceof Object) { 6 | var o = x; 7 | this.x = o.x; 8 | this.y = o.y; 9 | this.timestamp = o.t; 10 | } else { 11 | this.x = x; 12 | this.y = y; 13 | this.timestamp = t; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Constants: require('./Constants'), 3 | Gesture: require('./Gesture'), 4 | GestureStore: require('./GestureStore'), 5 | Instance: require('./Instance'), 6 | InstanceLearner: require('./InstanceLearner'), 7 | Learner: require('./Learner'), 8 | OrientedBoundingBox: require('./OrientedBoundingBox'), 9 | Point: require('./Point'), 10 | Prediction: require('./Prediction'), 11 | Rect: require('./Rect'), 12 | Stroke: require('./Stroke'), 13 | Utils: require('./Utils') 14 | }; -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ========================================================================= 2 | == NOTICE file corresponding to the section 4 d of == 3 | == the Apache License, Version 2.0, == 4 | == in this case for the Android-specific code. == 5 | ========================================================================= 6 | 7 | Android Gesture Code 8 | Copyright 2008-2009 The Android Open Source Project 9 | This product includes software derived from code developed as part of 10 | The Android Open Source Project (http://source.android.com). 11 | 12 | https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/gesture/ 13 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | browserify = require('browserify'), 3 | buffer = require('vinyl-buffer'), 4 | rename = require('gulp-rename'), 5 | source = require('vinyl-source-stream'), 6 | uglify = require('gulp-uglify'); 7 | 8 | function browser() { 9 | return browserify({ 10 | entries: ['./src/'], 11 | standalone: 'gestrec', 12 | debug: true, 13 | cache: {}, packageCache: {} 14 | }); 15 | } 16 | 17 | function build() { 18 | return browser().bundle() 19 | .pipe(source('gestrec.js')) 20 | .pipe(buffer()) 21 | .pipe(gulp.dest('.')) 22 | // This will minify and rename to gestrec.min.js 23 | .pipe(uglify()) 24 | .pipe(rename({ extname: '.min.js' })) 25 | .pipe(gulp.dest('.')); 26 | } 27 | 28 | gulp.task('build', function() { build(); }); 29 | 30 | gulp.task('default', ['build']); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gestrec", 3 | "version": "1.0.0", 4 | "description": "JavaScript Gesture Recognizer using the Protractor algorithm.", 5 | "keywords": [ 6 | "gesture", 7 | "recognizer", 8 | "sketch", 9 | "touch", 10 | "javascript", 11 | "protractor" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "http://github.com/uwdata/gestrec.git" 16 | }, 17 | "homepage": "http://uwdata.github.io/gestrec/", 18 | "author": { 19 | "name": "UW Interactive Data Lab", 20 | "url": "http://idl.cs.washington.edu" 21 | }, 22 | "license": "BSD-3-Clause", 23 | "devDependencies": { 24 | "browserify": "^9.0.8", 25 | "gulp": "^3.8.11", 26 | "gulp-rename": "^1.2.2", 27 | "gulp-uglify": "^1.2.0", 28 | "vinyl-buffer": "^1.0.0", 29 | "vinyl-source-stream": "^1.1.0" 30 | }, 31 | "main": "src/index.js" 32 | } 33 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gestrec", 3 | "version": "1.0.0", 4 | "description": "JavaScript Gesture Recognizer using the Protractor algorithm.", 5 | "keywords": [ 6 | "gesture", 7 | "recognizer", 8 | "sketch", 9 | "touch", 10 | "javascript", 11 | "protractor" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "http://github.com/uwdata/gestrec.git" 16 | }, 17 | "homepage": "http://uwdata.github.io/gestrec/", 18 | "author": { 19 | "name": "UW Interactive Data Lab", 20 | "homepage": "http://idl.cs.washington.edu" 21 | }, 22 | "license": "BSD-3-Clause", 23 | "main": "gestrec.js", 24 | "scripts": [ 25 | "gestrec.js" 26 | ], 27 | "ignore": [ 28 | ".DS_Store", 29 | ".git", 30 | ".gitignore", 31 | ".npmignore", 32 | ".spmignore", 33 | ".travis.yml", 34 | "gulpfile.js", 35 | "node_modules", 36 | "package.json", 37 | "src" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/Constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Constants. 3 | */ 4 | module.exports = { 5 | // ignore sequence information 6 | SEQUENCE_INVARIANT: 1, 7 | // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed 8 | SEQUENCE_SENSITIVE: 2, 9 | 10 | // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures 11 | ORIENTATION_INVARIANT: 1, 12 | // at most 2 directions can be recognized 13 | ORIENTATION_SENSITIVE: 2, 14 | // at most 4 directions can be recognized 15 | ORIENTATION_SENSITIVE_4: 4, 16 | // at most 8 directions can be recognized 17 | ORIENTATION_SENSITIVE_8: 8, 18 | 19 | SEQUENCE_SAMPLE_SIZE: 16, 20 | 21 | PATCH_SAMPLE_SIZE: 16, 22 | 23 | ORIENTATIONS: [ 24 | 0, 25 | (Math.PI / 4), 26 | (Math.PI / 2), 27 | (Math.PI * 3 / 4), 28 | Math.PI, 29 | -0, 30 | (-Math.PI / 4), 31 | (-Math.PI / 2), 32 | (-Math.PI * 3 / 4), 33 | -Math.PI 34 | ] 35 | }; -------------------------------------------------------------------------------- /src/Rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rectangle object. 3 | */ 4 | function Rect(l, t, r, b) { 5 | this.set(l, t, r, b); 6 | } 7 | 8 | Rect.prototype.clone = function() { 9 | return new Rect(this.left, this.top, this.right, this.bottom); 10 | }; 11 | 12 | Rect.prototype.centerX = function() { 13 | return (this.left + this.right) / 2; 14 | }; 15 | 16 | Rect.prototype.centerY = function() { 17 | return (this.top + this.bottom) / 2; 18 | }; 19 | 20 | Rect.prototype.width = function() { 21 | return this.right - this.left; 22 | }; 23 | 24 | Rect.prototype.height = function() { 25 | return this.bottom - this.top; 26 | }; 27 | 28 | Rect.prototype.set = function(l, t, r, b) { 29 | this.top = t; 30 | this.left = l; 31 | this.bottom = b; 32 | this.right = r; 33 | }; 34 | 35 | Rect.prototype.unionPoint = function(x, y) { 36 | if (x < this.left) this.left = x; 37 | if (x > this.right) this.right = x; 38 | if (y < this.top) this.top = y; 39 | if (y > this.bottom) this.bottom = y; 40 | }; 41 | 42 | Rect.prototype.union = function(r) { 43 | if (r.left < this.left) this.left = r.left; 44 | if (r.right > this.right) this.right = r.right; 45 | if (r.top < this.top) this.top = r.top; 46 | if (r.bottom > this.bottom) this.bottom = r.bottom; 47 | }; 48 | 49 | module.exports = Rect; 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, University of Washington Interactive Data Lab. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the University of Washington Interactive Data Lab 15 | nor the names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/Learner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The abstract class of a gesture learner 3 | */ 4 | function Learner() { 5 | this.mInstances = []; 6 | } 7 | 8 | /** 9 | * Add an instance to the learner 10 | * 11 | * @param instance 12 | */ 13 | Learner.prototype.addInstance = function(instance) { 14 | this.mInstances.push(instance); 15 | }; 16 | 17 | /** 18 | * Retrieve all the instances 19 | * 20 | * @return instances 21 | */ 22 | Learner.prototype.getInstances = function() { 23 | return this.mInstances; 24 | }; 25 | 26 | /** 27 | * Remove an instance based on its id 28 | * 29 | * @param id 30 | */ 31 | Learner.prototype.removeInstance = function(id) { 32 | instances = this.mInstances; 33 | var count = instances.length; 34 | for (var i = 0; i < count; i++) { 35 | var instance = instances[i]; 36 | if (id === instance.id) { 37 | instances.splice(i, 1); 38 | return; 39 | } 40 | } 41 | }; 42 | 43 | /** 44 | * Remove all the instances of a category 45 | * 46 | * @param name the category name 47 | */ 48 | Learner.prototype.removeInstances = function(name) { 49 | var toDelete = []; 50 | var instances = this.mInstances; 51 | var count = instances.length; 52 | for (var i = 0; i < count; ++i) { 53 | var instance = instances[i]; 54 | // the label can be null, as specified in Instance 55 | if ((instance.label == null && name == null) 56 | || (instance.label != null && instance.label === name)) { 57 | toDelete.push(i); 58 | } 59 | } 60 | for (i=toDelete.length-1; i>=0; --i) { 61 | instances.splice(toDelete[i], 1); 62 | } 63 | }; 64 | 65 | Learner.prototype.classify = function(sequenceType, orientationType, vector) { 66 | }; 67 | 68 | module.exports = Learner; -------------------------------------------------------------------------------- /src/InstanceLearner.js: -------------------------------------------------------------------------------- 1 | var Prediction = require('./Prediction'); 2 | var Constants = require('./Constants'); 3 | var Learner = require('./Learner'); 4 | var Utils = require('./Utils'); 5 | 6 | /** 7 | * An implementation of an instance-based learner 8 | */ 9 | function InstanceLearner() { 10 | Learner.call(this); 11 | } 12 | 13 | InstanceLearner.prototype = new Learner(); 14 | 15 | InstanceLearner.compare = function(object1, object2) { 16 | var score1 = object1.score; 17 | var score2 = object2.score; 18 | if (score1 > score2) { 19 | return -1; 20 | } else if (score1 < score2) { 21 | return 1; 22 | } else { 23 | return 0; 24 | } 25 | }; 26 | 27 | InstanceLearner.prototype.classify = function(sequenceType, orientationType, vector) { 28 | var predictions = []; 29 | var instances = this.getInstances(); 30 | var count = instances.length; 31 | var label2score = {}; 32 | 33 | for (var i=0; i score) { 52 | label2score[sample.label] = weight; 53 | } 54 | } 55 | 56 | for (var name in label2score) { 57 | var score = label2score[name]; 58 | predictions.push(new Prediction(name, score)); 59 | } 60 | 61 | return predictions.sort(InstanceLearner.compare); 62 | }; 63 | 64 | module.exports = InstanceLearner; 65 | -------------------------------------------------------------------------------- /src/Stroke.js: -------------------------------------------------------------------------------- 1 | var Rect = require('./Rect'); 2 | var Point = require('./Point'); 3 | 4 | /** 5 | * A gesture stroke started on a touch down and ended on a touch up. A stroke 6 | * consists of a sequence of timed points. One or multiple strokes form a gesture. 7 | */ 8 | function Stroke(points) { 9 | if (points == null) return; 10 | 11 | var count = points.length; 12 | var tmpPoints = Array(count*2); 13 | var times = Array(count); 14 | var bx = null; 15 | var len = 0; 16 | var index = 0; 17 | 18 | for (var i=0; i>1] 61 | }); 62 | } 63 | return points; 64 | }; 65 | 66 | Stroke.fromJSON = function(json) { 67 | var points = []; 68 | for (var i=0; i .remove, 129 | .library .instance:hover > .remove { 130 | visibility: visible; 131 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gestrec 2 | 3 | A JavaScript implementation of the Protractor gesture recognizer. 4 | 5 | This package provides a JavaScript version of Yang Li's [Protractor](http://yangl.org/pdf/protractor-chi2010.pdf) gesture recognizer. The code is a close port of the [Java version](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/gesture) that ships as part of the Google Android distribution. 6 | 7 | ## Usage 8 | 9 | The gesture recognizer can be used both server-side and client-side. For use in node.js, simply `npm install gestrec` or include gestrec as a dependency in your package.json file. For use on the client, simply import gestrec.min.js in the browser. 10 | 11 | ### Example 12 | 13 | ```javascript 14 | var gestrec = require('gestrec'); 15 | var store = new gestrec.GestureStore(); 16 | 17 | // a stroke is a collection of timestamped points 18 | var stroke = new gestrec.Stroke([ 19 | // gestrec.Point( x, y, t) 20 | new gestrec.Point( 2, 5, 1), 21 | new gestrec.Point( 2, 9, 2) 22 | ]); 23 | // a gesture is a collection of one or more strokes 24 | var gesture = new gestrec.Gesture([stroke]); 25 | 26 | // build a recognizer by providing gesture training examples 27 | // repeat to provide multiple examples for each named gesture class 28 | store.addGesture("line_down", gesture); 29 | 30 | // gestures can be read from JSON-style objects 31 | var down = gestrec.Gesture.fromJSON([[{x:4, y:1, t:1}, {x:4, y:3, t:2}]]), 32 | left = gestrec.Gesture.fromJSON([[{x:4, y:1, t:1}, {x:8, y:1, t:2}]]); 33 | 34 | // recognize a matching (high-scoring) gesture 35 | var prediction = store.recognize(down); 36 | console.log(JSON.stringify(prediction)); 37 | // [{"name":"line_down","score":67108864}] 38 | 39 | // recognize a non-matching (low-scoring) gesture 40 | prediction = store.recognize(left); 41 | console.log(JSON.stringify(prediction)); 42 | // [{"name":"line_down","score":0.6366197723675814}] 43 | ``` 44 | 45 | ### Gesture Training Application 46 | 47 | A gesture training application is available online at [http://uwdata.github.io/gestrec/](http://uwdata.github.io/gestrec/). 48 | 49 | The application is intended to work with both mouse and touch input. The code for the training application is included in this repository under the `trainer` folder. Simply open the `index.html` file in a browser to run locally. 50 | 51 | ## Build Process 52 | 53 | We use the [gulp](http://gulpjs.com/) build system along with [browserify](http://browserify.org/) to build gestrec.min.js. 54 | 55 | 1. Install gulp, as needed. Follow [step 1 on the Gulp Getting Started guide](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md). 56 | 2. Run `npm install` in the gestures folder to install dependencies. 57 | 3. Run `gulp`. 58 | 59 | -------------------------------------------------------------------------------- /src/Gesture.js: -------------------------------------------------------------------------------- 1 | var Rect = require('./Rect'); 2 | var Stroke = require('./Stroke'); 3 | 4 | /** 5 | * A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes. 6 | * Each stroke is a sequence of timed points. A user-defined gesture can be recognized by 7 | * a GestureLibrary. 8 | */ 9 | function Gesture(strokes) { 10 | this.mBoundingBox = new Rect(); 11 | this.mGestureID = Gesture.GESTURE_ID_BASE + (++Gesture.GESTURE_COUNT); 12 | this.mStrokes = []; 13 | if (!strokes) return; 14 | for (var i=0; i 2 | 3 | 4 | Gesture Designer 5 | 6 | 7 | 8 | 9 | 65 | 66 | 67 |
68 |
69 | Mode: 70 | 71 | 72 | 73 |
74 | 75 |
76 |
77 |
78 |
79 |
80 | 90 |
91 | 92 | 289 | -------------------------------------------------------------------------------- /trainer/library.js: -------------------------------------------------------------------------------- 1 | // A library managing a set of gesture classes and training examples. 2 | // Also supports rendering of library contents to a web page. 3 | var GestureLibrary = (function(d3, gestrec) { 4 | 5 | function GestureLibrary(el) { 6 | this._el = el; 7 | this._id = 1; 8 | this._store = new gestrec.GestureStore(); 9 | this._names = []; 10 | this._thumbsize = 100; 11 | this._selected = null; 12 | this._listeners = {}; 13 | } 14 | 15 | var proto = GestureLibrary.prototype; 16 | 17 | // Add an event listener. 18 | proto.on = function(type, listener) { 19 | this._listeners[type] = listener; 20 | return this; 21 | }; 22 | 23 | // Fire an event with the given event type. 24 | proto.fire = function(type) { 25 | try { 26 | if (this._listeners[type]) this._listeners[type](); 27 | } catch (err) { 28 | console.error(err); 29 | } 30 | return this; 31 | }; 32 | 33 | // Set the currently selected gesture class. 34 | proto.setSelected = function(name) { 35 | this._selected = name; 36 | d3.select(this._el) 37 | .selectAll("div.entry") 38 | .classed("selected", function(n) { return name === n.name; }); 39 | }; 40 | 41 | // Lookup the gesture class matching the given name. 42 | proto._lookupName = function(name) { 43 | var n = this._names; 44 | for (var i=0; i= 0) { 66 | throw new Error("Name already in use: " + newname); 67 | } 68 | 69 | var idx = this._lookupName(oldname); 70 | if (idx < 0) throw new Error("Unrecognized name: " + oldname); 71 | this._names[idx].name = newname; 72 | this.setSelected(newname); 73 | }; 74 | 75 | // Remove the gesture class with the given name. 76 | proto._nameRemove = function(name) { 77 | var idx = this._lookupName(name); 78 | if (idx < 0) throw new Error("Unrecognized name: " + name); 79 | this._names.splice(idx, 1); 80 | this.fire("remove"); 81 | if (this._selected === name) { 82 | if (this._names.length > idx) { 83 | this.setSelected(this._names[idx].name); 84 | } else if (this._names.length > 0) { 85 | this.setSelected(this._names[idx-1].name); 86 | } else { 87 | this.addEntry(); 88 | } 89 | } 90 | }; 91 | 92 | // Perform gesture recognition for the input gesture. 93 | proto.recognize = function(gesture) { 94 | return this._store.recognize(gesture); 95 | }; 96 | 97 | // Add a gesture as an example for the named gesture class. 98 | proto.addGesture = function(name, gesture) { 99 | if (gesture == null) { 100 | gesture = name; 101 | name = this._selected; 102 | } 103 | this._nameCheck(name); 104 | this._store.addGesture(name, gesture); 105 | return this; 106 | }; 107 | 108 | // Remove a gesture as an example for the named gesture class. 109 | proto.removeGesture = function(name, gesture) { 110 | this._store.removeGesture(name, gesture); 111 | return this; 112 | }; 113 | 114 | // Add a new gesture class entry. 115 | proto.addEntry = function() { 116 | var name = "Gesture " + (this._id++); 117 | this._nameCheck(name); 118 | return this.fire("addentry"); 119 | }; 120 | 121 | // Remove the named gesture class entry. 122 | proto.removeEntry = function(name) { 123 | this._nameRemove(name); 124 | this._store.removeEntry(name); 125 | return this; 126 | }; 127 | 128 | // Rename a gesture class entry. 129 | proto.renameEntry = function(oldname, newname) { 130 | this._nameUpdate(oldname, newname); 131 | var gestures = this._store.getGestures(oldname) || []; 132 | this._store.removeEntry(oldname); 133 | for (var i=0; in;++n){var o=this.mStrokes[n];t.mStrokes.push(o.clone())}return t},r.prototype.getStrokes=function(){return this.mStrokes},r.prototype.getStrokesCount=function(){return this.mStrokes.length},r.prototype.addStroke=function(t){this.mStrokes.push(t),this.mBoundingBox.union(t.boundingBox)},r.prototype.getLength=function(){for(var t=0,e=this.mStrokes,n=e.length,r=0;n>r;++r)t+=e[r].length;return t},r.prototype.getBoundingBox=function(){return this.mBoundingBox},r.prototype.setID=function(t){this.mGestureID=t},r.prototype.getID=function(){return this.mGestureID},r.prototype.toJSON=function(){for(var t=[],e=this.mStrokes.length,n=0;e>n;++n)t.push(this.mStrokes[n].toJSON());return t},r.fromJSON=function(t){for(var e=new r,n=0;nr;r++)e+=t[r]*t[r];for(var o=Math.sqrt(e),r=0;n>r;r++)t[r]/=o},r.createInstance=function(t,e,n,i){var s,a;return t===o.SEQUENCE_SENSITIVE?(s=r.temporalSampler(e,n),a=new r(n.getID(),s,i),a.normalize()):(s=r.spatialSampler(n),a=new r(n.getID(),s,i)),a},r.spatialSampler=function(t){return i.spatialSampling(t,o.PATCH_SAMPLE_SIZE,!1)},r.temporalSampler=function(t,e){var n=i.temporalSampling(e.getStrokes()[0],o.SEQUENCE_SAMPLE_SIZE),r=i.computeCentroid(n),s=Math.atan2(n[1]-r[1],n[0]-r[0]),a=-s;if(t!=o.ORIENTATION_INVARIANT)for(var u=o.ORIENTATIONS.length,h=0;u>h;h++){var c=o.ORIENTATIONS[h]-s;Math.abs(c)r?-1:r>n?1:0},r.prototype.classify=function(t,e,n){for(var s=[],u=this.getInstances(),h=u.length,c={},f=0;h>f;++f){var p=u[f];if(p.vector.length==n.length){var l;l=t==i.SEQUENCE_SENSITIVE?a.minimumCosineDistance(p.vector,n,e):a.squaredEuclideanDistance(p.vector,n);var m;m=0==l?Number.MAX_VALUE:1/l;var g=c[p.label];(null==g||m>g)&&(c[p.label]=m)}}for(var v in c){var g=c[v];s.push(new o(v,g))}return s.sort(r.compare)},e.exports=r},{"./Constants":2,"./Learner":7,"./Prediction":10,"./Utils":13}],7:[function(t,e,n){function r(){this.mInstances=[]}r.prototype.addInstance=function(t){this.mInstances.push(t)},r.prototype.getInstances=function(){return this.mInstances},r.prototype.removeInstance=function(t){instances=this.mInstances;for(var e=instances.length,n=0;e>n;n++){var r=instances[n];if(t===r.id)return void instances.splice(n,1)}},r.prototype.removeInstances=function(t){for(var e=[],n=this.mInstances,r=n.length,o=0;r>o;++o){var i=n[o];(null==i.label&&null==t||null!=i.label&&i.label===t)&&e.push(o)}for(o=e.length-1;o>=0;--o)n.splice(e[o],1)},r.prototype.classify=function(t,e,n){},e.exports=r},{}],8:[function(t,e,n){e.exports=function(t,e,n,r,o){this.orientation=t,this.width=r,this.height=o,this.centerX=e,this.centerY=n;var i=r/o;this.squareness=i>1?1/i:i}},{}],9:[function(t,e,n){e.exports=function(t,e,n){if(t instanceof Object){var r=t;this.x=r.x,this.y=r.y,this.timestamp=r.t}else this.x=t,this.y=e,this.timestamp=n}},{}],10:[function(t,e,n){function r(t,e){this.name=t,this.score=e}r.prototype.toString=function(){return this.name},e.exports=r},{}],11:[function(t,e,n){function r(t,e,n,r){this.set(t,e,n,r)}r.prototype.clone=function(){return new r(this.left,this.top,this.right,this.bottom)},r.prototype.centerX=function(){return(this.left+this.right)/2},r.prototype.centerY=function(){return(this.top+this.bottom)/2},r.prototype.width=function(){return this.right-this.left},r.prototype.height=function(){return this.bottom-this.top},r.prototype.set=function(t,e,n,r){this.top=e,this.left=t,this.bottom=r,this.right=n},r.prototype.unionPoint=function(t,e){tthis.right&&(this.right=t),ethis.bottom&&(this.bottom=e)},r.prototype.union=function(t){t.leftthis.right&&(this.right=t.right),t.topthis.bottom&&(this.bottom=t.bottom)},e.exports=r},{}],12:[function(t,e,n){function r(t){if(null!=t){for(var e=t.length,n=Array(2*e),r=Array(e),i=null,s=0,a=0,u=0;e>u;++u){var h=t[u];if(n[2*u]=h.x,n[2*u+1]=h.y,r[a]=h.timestamp,null==i)i=new o(h.x,h.y,h.x,h.y),s=0;else{var c=h.x-n[2*(u-1)],f=h.y-n[2*(u-1)+1];s+=Math.sqrt(c*c+f*f),i.unionPoint(h.x,h.y)}a++}this.timestamps=r,this.points=n,this.boundingBox=i,this.length=s}}var o=t("./Rect"),i=t("./Point");r.prototype.clone=function(){var t=new r;return t.boundingBox=this.boundingBox.clone(),t.length=this.length,t.points=this.points.slice(),t.timestamps=this.timestamps.slice(),t},r.prototype.toJSON=function(){for(var t=[],e=this.points.length,n=0;e>n;n+=2)t.push({x:this.points[n],y:this.points[n+1],t:this.timestamps[n>>1]});return t},r.fromJSON=function(t){for(var e=[],n=0;nn;++n)e[n]=0;return e},o.spatialSampling=function(t,e,n){var r,i=e-1,s=o.zeroes(e*e),a=t.getBoundingBox(),u=a.width(),h=a.height(),c=i/u,f=i/h;if(n)r=f>c?c:f,c=r,f=r;else{var p=u/h;p>1&&(aspectRation=1/p),pc?c:f,c=r,f=r):c>f?(r=f*o.NONUNIFORM_SCALE,c>r&&(c=r)):(r=c*o.NONUNIFORM_SCALE,f>r&&(f=r))}for(var l,m,g,v=-a.centerX(),S=-a.centerY(),d=i/2,I=i/2,N=t.getStrokes(),y=N.length,E=0;y>E;E++){var M=N[E],O=M.points;l=O.length;for(var x=Array(l),T=0;l>T;T+=2)x[T]=(O[T]+v)*c+d,x[T+1]=(O[T+1]+S)*f+I;for(var A=-1,C=-1,T=0;l>T;T+=2){var B=x[T]<0?0:x[T],_=x[T+1]<0?0:x[T+1];if(B>i&&(B=i),_>i&&(_=i),o.plot(B,_,s,e),-1!=A){if(A>B){m=Math.ceil(B);for(var b=(C-_)/(A-B);A>m;)g=b*(m-B)+_,plot(m,g,s,e),m++}else if(B>A){m=Math.ceil(A);for(var b=(C-_)/(A-B);B>m;)g=b*(m-B)+_,plot(m,g,s,e),m++}if(C>_){g=Math.ceil(_);for(var L=(A-B)/(C-_);C>g;)m=L*(g-_)+B,plot(m,g,s,e),g++}else if(_>C){g=Math.ceil(C);for(var L=(A-B)/(C-_);_>g;)m=L*(g-_)+B,plot(m,g,s,e),g++}}A=B,C=_}}return s},o.plot=function(t,e,n,r){t=0>t?0:t,e=0>e?0:e;var o=Math.floor(t),i=Math.ceil(t),s=Math.floor(e),a=Math.ceil(e);if(t===o&&e===s){var u=a*r+i;n[u]<1&&(n[u]=1)}else{var h=Math.pow(o-t,2),c=Math.pow(s-e,2),f=Math.pow(i-t,2),p=Math.pow(a-e,2),l=Math.sqrt(h+c),m=Math.sqrt(f+c),g=Math.sqrt(h+p),v=Math.sqrt(f+p),S=l+m+g+v,d=l/S,u=s*r+o;d>n[u]&&(n[u]=d),d=m/S,u=s*r+i,d>n[u]&&(n[u]=d),d=g/S,u=a*r+o,d>n[u]&&(n[u]=d),d=v/S,u=a*r+i,d>n[u]&&(n[u]=d)}},o.temporalSampling=function(t,e){var n=t.length/(e-1),r=2*e,o=Array(r),i=0,s=t.points,a=s[0],u=s[1],h=0,c=Number.MIN_VALUE,f=Number.MIN_VALUE;o[h]=a,h++,o[h]=u,h++;for(var p=0,l=s.length/2;l>p;){if(c==Number.MIN_VALUE){if(p++,p>=l)break;c=s[2*p],f=s[2*p+1]}var m=c-a,g=f-u,v=Math.sqrt(m*m+g*g);if(i+v>=n){var S=(n-i)/v,d=a+S*m,I=u+S*g;o[h]=d,h++,o[h]=I,h++,a=d,u=I,i=0}else a=c,u=f,c=Number.MIN_VALUE,f=Number.MIN_VALUE,i+=v}for(p=h;r>p;p+=2)o[p]=a,o[p+1]=u;return o},o.computeCentroid=function(t){for(var e=0,n=0,r=t.length,o=0;r>o;++o)e+=t[o],o++,n+=t[o];return[2*e/r,2*n/r]},o.computeCoVariance=function(t){for(var e=[[0,0],[0,0]],n=t.length,r=0;n>r;++r){var o=t[r];r++;var i=t[r];e[0][0]+=o*o,e[0][1]+=o*i,e[1][0]=e[0][1],e[1][1]+=i*i}return e[0][0]/=n/2,e[0][1]/=n/2,e[1][0]/=n/2,e[1][1]/=n/2,e},o.computeTotalLength=function(t){for(var e=0,n=t.length-4,r=0;n>r;r+=2){var o=t[r+2]-t[r],i=t[r+3]-t[r+1];e+=Math.sqrt(o*o+i*i)}return e},o.computeStraightness=function(t){var e=o.computeTotalLength(t),n=t[2]-t[0],r=t[3]-t[1];return Math.sqrt(n*n+r*r)/e},o.computeStraightness=function(t,e){var n=t[2]-t[0],r=t[3]-t[1];return Math.sqrt(n*n+r*r)/e},o.squaredEuclideanDistance=function(t,e){for(var n=0,r=t.length,o=0;r>o;++o){var i=t[o]-e[o];n+=i*i}return n/r},o.cosineDistance=function(t,e){for(var n=0,r=t.length,o=0;r>o;++o)n+=t[o]*e[o];return Math.acos(n)},o.minimumCosineDistance=function(t,e,n){for(var r=t.length,o=0,i=0,s=0;r>s;s+=2)o+=t[s]*e[s]+t[s+1]*e[s+1],i+=t[s]*e[s+1]-t[s+1]*e[s];if(0!=o){var a=i/o,u=Math.atan(a);if(n>2&&Math.abs(u)>=Math.PI/n)return Math.acos(o);var h=Math.cos(u),c=h*a;return Math.acos(o*h+i*c)}return Math.PI/2},o.computeOrientedBoundingBoxPoints=function(t){for(var e=t.length,n=Array(2*e),r=0;e>r;r++){var i=t[r],s=2*r;n[s]=i.x,n[s+1]=i.y}var a=o.computeCentroid(n);return o.computeOrientedBoundingBoxFull(n,a)},o.computeOrientedBoundingBox=function(t){for(var e=t.length,n=Array(e),r=0;e>r;r++)n[r]=t[r];var i=o.computeCentroid(n);return o.computeOrientedBoundingBoxFull(n,i)},o.computeOrientedBoundingBoxFull=function(t,e){o.translate(t,-e[0],-e[1]);var n,i=o.computeCoVariance(t),s=o.computeOrientation(i);0==s[0]&&0==s[1]?n=-Math.PI/2:(n=Math.atan2(s[1],s[0]),o.rotate(t,-n));for(var a=Number.MAX_VALUE,u=Number.MAX_VALUE,h=Number.MIN_VALUE,c=Number.MIN_VALUE,f=t.length,p=0;f>p;p++)t[p]h&&(h=t[p]),p++,t[p]c&&(c=t[p]);return new r(180*n/Math.PI,e[0],e[1],h-a,c-u)},o.computeOrientation=function(t){var e=[0,0];(0==t[0][1]||0==t[1][0])&&(e[0]=1,e[1]=0);var n=-t[0][0]-t[1][1],r=t[0][0]*t[1][1]-t[0][1]*t[1][0],o=n/2,i=Math.sqrt(Math.pow(o,2)-r),s=-o+i,a=-o-i;if(s==a)e[0]=0,e[1]=0;else{var u=s>a?s:a;e[0]=1,e[1]=(u-t[0][0])/t[0][1]}return e},o.rotate=function(t,e){for(var n=Math.cos(e),r=Math.sin(e),o=t.length,i=0;o>i;i+=2){var s=t[i]*n-t[i+1]*r,a=t[i]*r+t[i+1]*n;t[i]=s,t[i+1]=a}return t},o.translate=function(t,e,n){for(var r=t.length,o=0;r>o;o+=2)t[o]+=e,t[o+1]+=n;return t},o.scale=function(t,e,n){for(var r=t.length,o=0;r>o;o+=2)t[o]*=e,t[o+1]*=n;return t},e.exports=o},{"./OrientedBoundingBox":8}]},{},[1])(1)}); -------------------------------------------------------------------------------- /src/Utils.js: -------------------------------------------------------------------------------- 1 | var OrientedBoundingBox = require('./OrientedBoundingBox'); 2 | 3 | /** 4 | * Utility functions for gesture processing & analysis, including methods for: 5 | *
    6 | *
  • feature extraction (e.g., samplers and those for calculating bounding 7 | * boxes and gesture path lengths); 8 | *
  • geometric transformation (e.g., translation, rotation and scaling); 9 | *
  • gesture similarity comparison (e.g., calculating Euclidean or Cosine 10 | * distances between two gestures). 11 | *
12 | */ 13 | var Utils = {}; 14 | 15 | Utils.SCALING_THRESHOLD = 0.26; 16 | Utils.NONUNIFORM_SCALE = Math.sqrt(2); 17 | 18 | Utils.zeroes = function(n) { 19 | var array = Array(n); 20 | for (var i=0; i 1) { 54 | aspectRation = 1 / aspectRatio; 55 | } 56 | if (aspectRatio < Utils.SCALING_THRESHOLD) { 57 | scale = sx < sy ? sx : sy; 58 | sx = scale; 59 | sy = scale; 60 | } else { 61 | if (sx > sy) { 62 | scale = sy * Utils.NONUNIFORM_SCALE; 63 | if (scale < sx) { sx = scale; } 64 | } else { 65 | scale = sx * Utils.NONUNIFORM_SCALE; 66 | if (scale < sy) { sy = scale; } 67 | } 68 | } 69 | } 70 | 71 | var preDx = -rect.centerX(); 72 | var preDy = -rect.centerY(); 73 | var postDx = targetPatchSize / 2; 74 | var postDy = targetPatchSize / 2; 75 | var strokes = gesture.getStrokes(); 76 | var count = strokes.length; 77 | var size; // int 78 | var xpos; 79 | var ypos; 80 | 81 | for (var index=0; index < count; index++) { 82 | var stroke = strokes[index]; 83 | var strokepoints = stroke.points; 84 | size = strokepoints.length; 85 | var pts = Array(size); 86 | for (var i=0; i < size; i += 2) { 87 | pts[i] = (strokepoints[i] + preDx) * sx + postDx; 88 | pts[i + 1] = (strokepoints[i + 1] + preDy) * sy + postDy; 89 | } 90 | var segmentEndX = -1; 91 | var segmentEndY = -1; 92 | for (var i=0; i < size; i += 2) { 93 | var segmentStartX = pts[i] < 0 ? 0 : pts[i]; 94 | var segmentStartY = pts[i + 1] < 0 ? 0 : pts[i + 1]; 95 | if (segmentStartX > targetPatchSize) { 96 | segmentStartX = targetPatchSize; 97 | } 98 | if (segmentStartY > targetPatchSize) { 99 | segmentStartY = targetPatchSize; 100 | } 101 | Utils.plot(segmentStartX, segmentStartY, sample, bitmapSize); 102 | if (segmentEndX != -1) { 103 | // Evaluate horizontally 104 | if (segmentEndX > segmentStartX) { 105 | xpos = Math.ceil(segmentStartX); 106 | var slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); 107 | while (xpos < segmentEndX) { 108 | ypos = slope * (xpos - segmentStartX) + segmentStartY; 109 | plot(xpos, ypos, sample, bitmapSize); 110 | xpos++; 111 | } 112 | } else if (segmentEndX < segmentStartX) { 113 | xpos = Math.ceil(segmentEndX); 114 | var slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); 115 | while (xpos < segmentStartX) { 116 | ypos = slope * (xpos - segmentStartX) + segmentStartY; 117 | plot(xpos, ypos, sample, bitmapSize); 118 | xpos++; 119 | } 120 | } 121 | // Evaluate vertically 122 | if (segmentEndY > segmentStartY) { 123 | ypos = Math.ceil(segmentStartY); 124 | var invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); 125 | while (ypos < segmentEndY) { 126 | xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; 127 | plot(xpos, ypos, sample, bitmapSize); 128 | ypos++; 129 | } 130 | } else if (segmentEndY < segmentStartY) { 131 | ypos = Math.ceil(segmentEndY); 132 | var invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); 133 | while (ypos < segmentStartY) { 134 | xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; 135 | plot(xpos, ypos, sample, bitmapSize); 136 | ypos++; 137 | } 138 | } 139 | } 140 | segmentEndX = segmentStartX; 141 | segmentEndY = segmentStartY; 142 | } 143 | } 144 | return sample; 145 | }; 146 | 147 | Utils.plot = function(x, y, sample, sampleSize) { 148 | x = x < 0 ? 0 : x; 149 | y = y < 0 ? 0 : y; 150 | var xFloor = Math.floor(x); 151 | var xCeiling = Math.ceil(x); 152 | var yFloor = Math.floor(y); 153 | var yCeiling = Math.ceil(y); 154 | 155 | // if it's an integer 156 | if (x === xFloor && y === yFloor) { 157 | var index = yCeiling * sampleSize + xCeiling; 158 | if (sample[index] < 1) { 159 | sample[index] = 1; 160 | } 161 | } else { 162 | var xFloorSq = Math.pow(xFloor - x, 2); 163 | var yFloorSq = Math.pow(yFloor - y, 2); 164 | var xCeilingSq = Math.pow(xCeiling - x, 2); 165 | var yCeilingSq = Math.pow(yCeiling - y, 2); 166 | var topLeft = Math.sqrt(xFloorSq + yFloorSq); 167 | var topRight = Math.sqrt(xCeilingSq + yFloorSq); 168 | var btmLeft = Math.sqrt(xFloorSq + yCeilingSq); 169 | var btmRight = Math.sqrt(xCeilingSq + yCeilingSq); 170 | var sum = topLeft + topRight + btmLeft + btmRight; 171 | 172 | var value = topLeft / sum; 173 | var index = yFloor * sampleSize + xFloor; 174 | if (value > sample[index]) { 175 | sample[index] = value; 176 | } 177 | 178 | value = topRight / sum; 179 | index = yFloor * sampleSize + xCeiling; 180 | if (value > sample[index]) { 181 | sample[index] = value; 182 | } 183 | 184 | value = btmLeft / sum; 185 | index = yCeiling * sampleSize + xFloor; 186 | if (value > sample[index]) { 187 | sample[index] = value; 188 | } 189 | 190 | value = btmRight / sum; 191 | index = yCeiling * sampleSize + xCeiling; 192 | if (value > sample[index]) { 193 | sample[index] = value; 194 | } 195 | } 196 | }; 197 | 198 | /** 199 | * Samples a stroke temporally into a given number of evenly-distributed 200 | * points. 201 | * 202 | * @param stroke the gesture stroke to be sampled 203 | * @param numPoints the number of points 204 | * @return the sampled points in the form of [x1, y1, x2, y2, ..., xn, yn] 205 | */ 206 | Utils.temporalSampling = function(stroke, numPoints) { 207 | var increment = stroke.length / (numPoints - 1); 208 | var vectorLength = numPoints * 2; 209 | var vector = Array(vectorLength); 210 | var distanceSoFar = 0; 211 | var pts = stroke.points; 212 | var lstPointX = pts[0]; 213 | var lstPointY = pts[1]; 214 | var index = 0; 215 | var currentPointX = Number.MIN_VALUE; 216 | var currentPointY = Number.MIN_VALUE; 217 | vector[index] = lstPointX; 218 | index++; 219 | vector[index] = lstPointY; 220 | index++; 221 | var i = 0; 222 | var count = pts.length / 2; 223 | while (i < count) { 224 | if (currentPointX == Number.MIN_VALUE) { 225 | i++; 226 | if (i >= count) { 227 | break; 228 | } 229 | currentPointX = pts[i * 2]; 230 | currentPointY = pts[i * 2 + 1]; 231 | } 232 | var deltaX = currentPointX - lstPointX; 233 | var deltaY = currentPointY - lstPointY; 234 | var distance = Math.sqrt(deltaX*deltaX + deltaY*deltaY); 235 | if (distanceSoFar + distance >= increment) { 236 | var ratio = (increment - distanceSoFar) / distance; 237 | var nx = lstPointX + ratio * deltaX; 238 | var ny = lstPointY + ratio * deltaY; 239 | vector[index] = nx; 240 | index++; 241 | vector[index] = ny; 242 | index++; 243 | lstPointX = nx; 244 | lstPointY = ny; 245 | distanceSoFar = 0; 246 | } else { 247 | lstPointX = currentPointX; 248 | lstPointY = currentPointY; 249 | currentPointX = Number.MIN_VALUE; 250 | currentPointY = Number.MIN_VALUE; 251 | distanceSoFar += distance; 252 | } 253 | } 254 | 255 | for (i = index; i < vectorLength; i += 2) { 256 | vector[i] = lstPointX; 257 | vector[i + 1] = lstPointY; 258 | } 259 | return vector; 260 | }; 261 | 262 | /** 263 | * Calculates the centroid of a set of points. 264 | * 265 | * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn] 266 | * @return the centroid 267 | */ 268 | Utils.computeCentroid = function(points) { 269 | var centerX = 0; 270 | var centerY = 0; 271 | var count = points.length; 272 | for (var i=0; i 2 && Math.abs(angle) >= Math.PI / numOrientations) { 385 | return Math.acos(a); 386 | } else { 387 | var cosine = Math.cos(angle); 388 | var sine = cosine * tan; 389 | return Math.acos(a * cosine + b * sine); 390 | } 391 | } else { 392 | return Math.PI / 2; 393 | } 394 | }; 395 | 396 | /** 397 | * Computes an oriented, minimum bounding box of a set of points. 398 | * 399 | * @param originalPoints 400 | * @return an oriented bounding box 401 | */ 402 | Utils.computeOrientedBoundingBoxPoints = function(originalPoints) { 403 | var count = originalPoints.length; 404 | var points = Array(count * 2); 405 | for (var i = 0; i < count; i++) { 406 | var point = originalPoints[i]; 407 | var index = i * 2; 408 | points[index] = point.x; 409 | points[index + 1] = point.y; 410 | } 411 | var meanVector = Utils.computeCentroid(points); 412 | return Utils.computeOrientedBoundingBoxFull(points, meanVector); 413 | }; 414 | 415 | /** 416 | * Computes an oriented, minimum bounding box of a set of points. 417 | * 418 | * @param originalPoints 419 | * @return an oriented bounding box 420 | */ 421 | Utils.computeOrientedBoundingBox = function(originalPoints) { 422 | var size = originalPoints.length; 423 | var points = Array(size); 424 | for (var i = 0; i < size; i++) { 425 | points[i] = originalPoints[i]; 426 | } 427 | var meanVector = Utils.computeCentroid(points); 428 | return Utils.computeOrientedBoundingBoxFull(points, meanVector); 429 | }; 430 | 431 | Utils.computeOrientedBoundingBoxFull = function(points, centroid) { 432 | Utils.translate(points, -centroid[0], -centroid[1]); 433 | 434 | var array = Utils.computeCoVariance(points); 435 | var targetVector = Utils.computeOrientation(array); 436 | 437 | var angle; 438 | if (targetVector[0] == 0 && targetVector[1] == 0) { 439 | angle = -Math.PI/2; 440 | } else { // -PI maxx) { 455 | maxx = points[i]; 456 | } 457 | i++; 458 | if (points[i] < miny) { 459 | miny = points[i]; 460 | } 461 | if (points[i] > maxy) { 462 | maxy = points[i]; 463 | } 464 | } 465 | 466 | return new OrientedBoundingBox( 467 | (angle * 180 / Math.PI), 468 | centroid[0], 469 | centroid[1], 470 | maxx - minx, 471 | maxy - miny 472 | ); 473 | }; 474 | 475 | Utils.computeOrientation = function(covarianceMatrix) { 476 | var targetVector = [0, 0]; 477 | if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) { 478 | targetVector[0] = 1; 479 | targetVector[1] = 0; 480 | } 481 | 482 | var a = -covarianceMatrix[0][0] - covarianceMatrix[1][1]; 483 | var b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1] 484 | * covarianceMatrix[1][0]; 485 | var value = a / 2; 486 | var rightside = Math.sqrt(Math.pow(value, 2) - b); 487 | var lambda1 = -value + rightside; 488 | var lambda2 = -value - rightside; 489 | if (lambda1 == lambda2) { 490 | targetVector[0] = 0; 491 | targetVector[1] = 0; 492 | } else { 493 | var lambda = lambda1 > lambda2 ? lambda1 : lambda2; 494 | targetVector[0] = 1; 495 | targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1]; 496 | } 497 | return targetVector; 498 | }; 499 | 500 | Utils.rotate = function(points, angle) { 501 | var cos = Math.cos(angle); 502 | var sin = Math.sin(angle); 503 | var size = points.length; 504 | for (var i = 0; i < size; i += 2) { 505 | var x = points[i] * cos - points[i + 1] * sin; 506 | var y = points[i] * sin + points[i + 1] * cos; 507 | points[i] = x; 508 | points[i + 1] = y; 509 | } 510 | return points; 511 | }; 512 | 513 | Utils.translate = function(points, dx, dy) { 514 | var size = points.length; 515 | for (var i = 0; i < size; i += 2) { 516 | points[i] += dx; 517 | points[i + 1] += dy; 518 | } 519 | return points; 520 | }; 521 | 522 | Utils.scale = function(points, sx, sy) { 523 | var size = points.length; 524 | for (var i = 0; i < size; i += 2) { 525 | points[i] *= sx; 526 | points[i + 1] *= sy; 527 | } 528 | return points; 529 | }; 530 | 531 | module.exports = Utils; -------------------------------------------------------------------------------- /gestrec.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.gestrec = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o score2) { 457 | return -1; 458 | } else if (score1 < score2) { 459 | return 1; 460 | } else { 461 | return 0; 462 | } 463 | }; 464 | 465 | InstanceLearner.prototype.classify = function(sequenceType, orientationType, vector) { 466 | var predictions = []; 467 | var instances = this.getInstances(); 468 | var count = instances.length; 469 | var label2score = {}; 470 | 471 | for (var i=0; i score) { 490 | label2score[sample.label] = weight; 491 | } 492 | } 493 | 494 | for (var name in label2score) { 495 | var score = label2score[name]; 496 | predictions.push(new Prediction(name, score)); 497 | } 498 | 499 | return predictions.sort(InstanceLearner.compare); 500 | }; 501 | 502 | module.exports = InstanceLearner; 503 | 504 | },{"./Constants":2,"./Learner":7,"./Prediction":10,"./Utils":13}],7:[function(require,module,exports){ 505 | /** 506 | * The abstract class of a gesture learner 507 | */ 508 | function Learner() { 509 | this.mInstances = []; 510 | } 511 | 512 | /** 513 | * Add an instance to the learner 514 | * 515 | * @param instance 516 | */ 517 | Learner.prototype.addInstance = function(instance) { 518 | this.mInstances.push(instance); 519 | }; 520 | 521 | /** 522 | * Retrieve all the instances 523 | * 524 | * @return instances 525 | */ 526 | Learner.prototype.getInstances = function() { 527 | return this.mInstances; 528 | }; 529 | 530 | /** 531 | * Remove an instance based on its id 532 | * 533 | * @param id 534 | */ 535 | Learner.prototype.removeInstance = function(id) { 536 | instances = this.mInstances; 537 | var count = instances.length; 538 | for (var i = 0; i < count; i++) { 539 | var instance = instances[i]; 540 | if (id === instance.id) { 541 | instances.splice(i, 1); 542 | return; 543 | } 544 | } 545 | }; 546 | 547 | /** 548 | * Remove all the instances of a category 549 | * 550 | * @param name the category name 551 | */ 552 | Learner.prototype.removeInstances = function(name) { 553 | var toDelete = []; 554 | var instances = this.mInstances; 555 | var count = instances.length; 556 | for (var i = 0; i < count; ++i) { 557 | var instance = instances[i]; 558 | // the label can be null, as specified in Instance 559 | if ((instance.label == null && name == null) 560 | || (instance.label != null && instance.label === name)) { 561 | toDelete.push(i); 562 | } 563 | } 564 | for (i=toDelete.length-1; i>=0; --i) { 565 | instances.splice(toDelete[i], 1); 566 | } 567 | }; 568 | 569 | Learner.prototype.classify = function(sequenceType, orientationType, vector) { 570 | }; 571 | 572 | module.exports = Learner; 573 | },{}],8:[function(require,module,exports){ 574 | /** 575 | * An oriented bounding box 576 | */ 577 | module.exports = function OrientedBoundingBox(angle, cx, cy, w, h) { 578 | this.orientation = angle; 579 | this.width = w; 580 | this.height = h; 581 | this.centerX = cx; 582 | this.centerY = cy; 583 | var ratio = w / h; 584 | this.squareness = ratio > 1 ? (1/ratio) : ratio; 585 | }; 586 | },{}],9:[function(require,module,exports){ 587 | /** 588 | * A timed point of a gesture stroke. Multiple points form a stroke. 589 | */ 590 | module.exports = function Point(x, y, t) { 591 | if (x instanceof Object) { 592 | var o = x; 593 | this.x = o.x; 594 | this.y = o.y; 595 | this.timestamp = o.t; 596 | } else { 597 | this.x = x; 598 | this.y = y; 599 | this.timestamp = t; 600 | } 601 | }; 602 | 603 | },{}],10:[function(require,module,exports){ 604 | function Prediction(label, predictionScore) { 605 | this.name = label; 606 | this.score = predictionScore; 607 | } 608 | 609 | Prediction.prototype.toString = function() { 610 | return this.name; 611 | }; 612 | 613 | module.exports = Prediction; 614 | 615 | },{}],11:[function(require,module,exports){ 616 | /** 617 | * Rectangle object. 618 | */ 619 | function Rect(l, t, r, b) { 620 | this.set(l, t, r, b); 621 | } 622 | 623 | Rect.prototype.clone = function() { 624 | return new Rect(this.left, this.top, this.right, this.bottom); 625 | }; 626 | 627 | Rect.prototype.centerX = function() { 628 | return (this.left + this.right) / 2; 629 | }; 630 | 631 | Rect.prototype.centerY = function() { 632 | return (this.top + this.bottom) / 2; 633 | }; 634 | 635 | Rect.prototype.width = function() { 636 | return this.right - this.left; 637 | }; 638 | 639 | Rect.prototype.height = function() { 640 | return this.bottom - this.top; 641 | }; 642 | 643 | Rect.prototype.set = function(l, t, r, b) { 644 | this.top = t; 645 | this.left = l; 646 | this.bottom = b; 647 | this.right = r; 648 | }; 649 | 650 | Rect.prototype.unionPoint = function(x, y) { 651 | if (x < this.left) this.left = x; 652 | if (x > this.right) this.right = x; 653 | if (y < this.top) this.top = y; 654 | if (y > this.bottom) this.bottom = y; 655 | }; 656 | 657 | Rect.prototype.union = function(r) { 658 | if (r.left < this.left) this.left = r.left; 659 | if (r.right > this.right) this.right = r.right; 660 | if (r.top < this.top) this.top = r.top; 661 | if (r.bottom > this.bottom) this.bottom = r.bottom; 662 | }; 663 | 664 | module.exports = Rect; 665 | 666 | },{}],12:[function(require,module,exports){ 667 | var Rect = require('./Rect'); 668 | var Point = require('./Point'); 669 | 670 | /** 671 | * A gesture stroke started on a touch down and ended on a touch up. A stroke 672 | * consists of a sequence of timed points. One or multiple strokes form a gesture. 673 | */ 674 | function Stroke(points) { 675 | if (points == null) return; 676 | 677 | var count = points.length; 678 | var tmpPoints = Array(count*2); 679 | var times = Array(count); 680 | var bx = null; 681 | var len = 0; 682 | var index = 0; 683 | 684 | for (var i=0; i>1] 727 | }); 728 | } 729 | return points; 730 | }; 731 | 732 | Stroke.fromJSON = function(json) { 733 | var points = []; 734 | for (var i=0; i 748 | *
  • feature extraction (e.g., samplers and those for calculating bounding 749 | * boxes and gesture path lengths); 750 | *
  • geometric transformation (e.g., translation, rotation and scaling); 751 | *
  • gesture similarity comparison (e.g., calculating Euclidean or Cosine 752 | * distances between two gestures). 753 | * 754 | */ 755 | var Utils = {}; 756 | 757 | Utils.SCALING_THRESHOLD = 0.26; 758 | Utils.NONUNIFORM_SCALE = Math.sqrt(2); 759 | 760 | Utils.zeroes = function(n) { 761 | var array = Array(n); 762 | for (var i=0; i 1) { 796 | aspectRation = 1 / aspectRatio; 797 | } 798 | if (aspectRatio < Utils.SCALING_THRESHOLD) { 799 | scale = sx < sy ? sx : sy; 800 | sx = scale; 801 | sy = scale; 802 | } else { 803 | if (sx > sy) { 804 | scale = sy * Utils.NONUNIFORM_SCALE; 805 | if (scale < sx) { sx = scale; } 806 | } else { 807 | scale = sx * Utils.NONUNIFORM_SCALE; 808 | if (scale < sy) { sy = scale; } 809 | } 810 | } 811 | } 812 | 813 | var preDx = -rect.centerX(); 814 | var preDy = -rect.centerY(); 815 | var postDx = targetPatchSize / 2; 816 | var postDy = targetPatchSize / 2; 817 | var strokes = gesture.getStrokes(); 818 | var count = strokes.length; 819 | var size; // int 820 | var xpos; 821 | var ypos; 822 | 823 | for (var index=0; index < count; index++) { 824 | var stroke = strokes[index]; 825 | var strokepoints = stroke.points; 826 | size = strokepoints.length; 827 | var pts = Array(size); 828 | for (var i=0; i < size; i += 2) { 829 | pts[i] = (strokepoints[i] + preDx) * sx + postDx; 830 | pts[i + 1] = (strokepoints[i + 1] + preDy) * sy + postDy; 831 | } 832 | var segmentEndX = -1; 833 | var segmentEndY = -1; 834 | for (var i=0; i < size; i += 2) { 835 | var segmentStartX = pts[i] < 0 ? 0 : pts[i]; 836 | var segmentStartY = pts[i + 1] < 0 ? 0 : pts[i + 1]; 837 | if (segmentStartX > targetPatchSize) { 838 | segmentStartX = targetPatchSize; 839 | } 840 | if (segmentStartY > targetPatchSize) { 841 | segmentStartY = targetPatchSize; 842 | } 843 | Utils.plot(segmentStartX, segmentStartY, sample, bitmapSize); 844 | if (segmentEndX != -1) { 845 | // Evaluate horizontally 846 | if (segmentEndX > segmentStartX) { 847 | xpos = Math.ceil(segmentStartX); 848 | var slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); 849 | while (xpos < segmentEndX) { 850 | ypos = slope * (xpos - segmentStartX) + segmentStartY; 851 | plot(xpos, ypos, sample, bitmapSize); 852 | xpos++; 853 | } 854 | } else if (segmentEndX < segmentStartX) { 855 | xpos = Math.ceil(segmentEndX); 856 | var slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); 857 | while (xpos < segmentStartX) { 858 | ypos = slope * (xpos - segmentStartX) + segmentStartY; 859 | plot(xpos, ypos, sample, bitmapSize); 860 | xpos++; 861 | } 862 | } 863 | // Evaluate vertically 864 | if (segmentEndY > segmentStartY) { 865 | ypos = Math.ceil(segmentStartY); 866 | var invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); 867 | while (ypos < segmentEndY) { 868 | xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; 869 | plot(xpos, ypos, sample, bitmapSize); 870 | ypos++; 871 | } 872 | } else if (segmentEndY < segmentStartY) { 873 | ypos = Math.ceil(segmentEndY); 874 | var invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); 875 | while (ypos < segmentStartY) { 876 | xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; 877 | plot(xpos, ypos, sample, bitmapSize); 878 | ypos++; 879 | } 880 | } 881 | } 882 | segmentEndX = segmentStartX; 883 | segmentEndY = segmentStartY; 884 | } 885 | } 886 | return sample; 887 | }; 888 | 889 | Utils.plot = function(x, y, sample, sampleSize) { 890 | x = x < 0 ? 0 : x; 891 | y = y < 0 ? 0 : y; 892 | var xFloor = Math.floor(x); 893 | var xCeiling = Math.ceil(x); 894 | var yFloor = Math.floor(y); 895 | var yCeiling = Math.ceil(y); 896 | 897 | // if it's an integer 898 | if (x === xFloor && y === yFloor) { 899 | var index = yCeiling * sampleSize + xCeiling; 900 | if (sample[index] < 1) { 901 | sample[index] = 1; 902 | } 903 | } else { 904 | var xFloorSq = Math.pow(xFloor - x, 2); 905 | var yFloorSq = Math.pow(yFloor - y, 2); 906 | var xCeilingSq = Math.pow(xCeiling - x, 2); 907 | var yCeilingSq = Math.pow(yCeiling - y, 2); 908 | var topLeft = Math.sqrt(xFloorSq + yFloorSq); 909 | var topRight = Math.sqrt(xCeilingSq + yFloorSq); 910 | var btmLeft = Math.sqrt(xFloorSq + yCeilingSq); 911 | var btmRight = Math.sqrt(xCeilingSq + yCeilingSq); 912 | var sum = topLeft + topRight + btmLeft + btmRight; 913 | 914 | var value = topLeft / sum; 915 | var index = yFloor * sampleSize + xFloor; 916 | if (value > sample[index]) { 917 | sample[index] = value; 918 | } 919 | 920 | value = topRight / sum; 921 | index = yFloor * sampleSize + xCeiling; 922 | if (value > sample[index]) { 923 | sample[index] = value; 924 | } 925 | 926 | value = btmLeft / sum; 927 | index = yCeiling * sampleSize + xFloor; 928 | if (value > sample[index]) { 929 | sample[index] = value; 930 | } 931 | 932 | value = btmRight / sum; 933 | index = yCeiling * sampleSize + xCeiling; 934 | if (value > sample[index]) { 935 | sample[index] = value; 936 | } 937 | } 938 | }; 939 | 940 | /** 941 | * Samples a stroke temporally into a given number of evenly-distributed 942 | * points. 943 | * 944 | * @param stroke the gesture stroke to be sampled 945 | * @param numPoints the number of points 946 | * @return the sampled points in the form of [x1, y1, x2, y2, ..., xn, yn] 947 | */ 948 | Utils.temporalSampling = function(stroke, numPoints) { 949 | var increment = stroke.length / (numPoints - 1); 950 | var vectorLength = numPoints * 2; 951 | var vector = Array(vectorLength); 952 | var distanceSoFar = 0; 953 | var pts = stroke.points; 954 | var lstPointX = pts[0]; 955 | var lstPointY = pts[1]; 956 | var index = 0; 957 | var currentPointX = Number.MIN_VALUE; 958 | var currentPointY = Number.MIN_VALUE; 959 | vector[index] = lstPointX; 960 | index++; 961 | vector[index] = lstPointY; 962 | index++; 963 | var i = 0; 964 | var count = pts.length / 2; 965 | while (i < count) { 966 | if (currentPointX == Number.MIN_VALUE) { 967 | i++; 968 | if (i >= count) { 969 | break; 970 | } 971 | currentPointX = pts[i * 2]; 972 | currentPointY = pts[i * 2 + 1]; 973 | } 974 | var deltaX = currentPointX - lstPointX; 975 | var deltaY = currentPointY - lstPointY; 976 | var distance = Math.sqrt(deltaX*deltaX + deltaY*deltaY); 977 | if (distanceSoFar + distance >= increment) { 978 | var ratio = (increment - distanceSoFar) / distance; 979 | var nx = lstPointX + ratio * deltaX; 980 | var ny = lstPointY + ratio * deltaY; 981 | vector[index] = nx; 982 | index++; 983 | vector[index] = ny; 984 | index++; 985 | lstPointX = nx; 986 | lstPointY = ny; 987 | distanceSoFar = 0; 988 | } else { 989 | lstPointX = currentPointX; 990 | lstPointY = currentPointY; 991 | currentPointX = Number.MIN_VALUE; 992 | currentPointY = Number.MIN_VALUE; 993 | distanceSoFar += distance; 994 | } 995 | } 996 | 997 | for (i = index; i < vectorLength; i += 2) { 998 | vector[i] = lstPointX; 999 | vector[i + 1] = lstPointY; 1000 | } 1001 | return vector; 1002 | }; 1003 | 1004 | /** 1005 | * Calculates the centroid of a set of points. 1006 | * 1007 | * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn] 1008 | * @return the centroid 1009 | */ 1010 | Utils.computeCentroid = function(points) { 1011 | var centerX = 0; 1012 | var centerY = 0; 1013 | var count = points.length; 1014 | for (var i=0; i 2 && Math.abs(angle) >= Math.PI / numOrientations) { 1127 | return Math.acos(a); 1128 | } else { 1129 | var cosine = Math.cos(angle); 1130 | var sine = cosine * tan; 1131 | return Math.acos(a * cosine + b * sine); 1132 | } 1133 | } else { 1134 | return Math.PI / 2; 1135 | } 1136 | }; 1137 | 1138 | /** 1139 | * Computes an oriented, minimum bounding box of a set of points. 1140 | * 1141 | * @param originalPoints 1142 | * @return an oriented bounding box 1143 | */ 1144 | Utils.computeOrientedBoundingBoxPoints = function(originalPoints) { 1145 | var count = originalPoints.length; 1146 | var points = Array(count * 2); 1147 | for (var i = 0; i < count; i++) { 1148 | var point = originalPoints[i]; 1149 | var index = i * 2; 1150 | points[index] = point.x; 1151 | points[index + 1] = point.y; 1152 | } 1153 | var meanVector = Utils.computeCentroid(points); 1154 | return Utils.computeOrientedBoundingBoxFull(points, meanVector); 1155 | }; 1156 | 1157 | /** 1158 | * Computes an oriented, minimum bounding box of a set of points. 1159 | * 1160 | * @param originalPoints 1161 | * @return an oriented bounding box 1162 | */ 1163 | Utils.computeOrientedBoundingBox = function(originalPoints) { 1164 | var size = originalPoints.length; 1165 | var points = Array(size); 1166 | for (var i = 0; i < size; i++) { 1167 | points[i] = originalPoints[i]; 1168 | } 1169 | var meanVector = Utils.computeCentroid(points); 1170 | return Utils.computeOrientedBoundingBoxFull(points, meanVector); 1171 | }; 1172 | 1173 | Utils.computeOrientedBoundingBoxFull = function(points, centroid) { 1174 | Utils.translate(points, -centroid[0], -centroid[1]); 1175 | 1176 | var array = Utils.computeCoVariance(points); 1177 | var targetVector = Utils.computeOrientation(array); 1178 | 1179 | var angle; 1180 | if (targetVector[0] == 0 && targetVector[1] == 0) { 1181 | angle = -Math.PI/2; 1182 | } else { // -PI maxx) { 1197 | maxx = points[i]; 1198 | } 1199 | i++; 1200 | if (points[i] < miny) { 1201 | miny = points[i]; 1202 | } 1203 | if (points[i] > maxy) { 1204 | maxy = points[i]; 1205 | } 1206 | } 1207 | 1208 | return new OrientedBoundingBox( 1209 | (angle * 180 / Math.PI), 1210 | centroid[0], 1211 | centroid[1], 1212 | maxx - minx, 1213 | maxy - miny 1214 | ); 1215 | }; 1216 | 1217 | Utils.computeOrientation = function(covarianceMatrix) { 1218 | var targetVector = [0, 0]; 1219 | if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) { 1220 | targetVector[0] = 1; 1221 | targetVector[1] = 0; 1222 | } 1223 | 1224 | var a = -covarianceMatrix[0][0] - covarianceMatrix[1][1]; 1225 | var b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1] 1226 | * covarianceMatrix[1][0]; 1227 | var value = a / 2; 1228 | var rightside = Math.sqrt(Math.pow(value, 2) - b); 1229 | var lambda1 = -value + rightside; 1230 | var lambda2 = -value - rightside; 1231 | if (lambda1 == lambda2) { 1232 | targetVector[0] = 0; 1233 | targetVector[1] = 0; 1234 | } else { 1235 | var lambda = lambda1 > lambda2 ? lambda1 : lambda2; 1236 | targetVector[0] = 1; 1237 | targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1]; 1238 | } 1239 | return targetVector; 1240 | }; 1241 | 1242 | Utils.rotate = function(points, angle) { 1243 | var cos = Math.cos(angle); 1244 | var sin = Math.sin(angle); 1245 | var size = points.length; 1246 | for (var i = 0; i < size; i += 2) { 1247 | var x = points[i] * cos - points[i + 1] * sin; 1248 | var y = points[i] * sin + points[i + 1] * cos; 1249 | points[i] = x; 1250 | points[i + 1] = y; 1251 | } 1252 | return points; 1253 | }; 1254 | 1255 | Utils.translate = function(points, dx, dy) { 1256 | var size = points.length; 1257 | for (var i = 0; i < size; i += 2) { 1258 | points[i] += dx; 1259 | points[i + 1] += dy; 1260 | } 1261 | return points; 1262 | }; 1263 | 1264 | Utils.scale = function(points, sx, sy) { 1265 | var size = points.length; 1266 | for (var i = 0; i < size; i += 2) { 1267 | points[i] *= sx; 1268 | points[i + 1] *= sy; 1269 | } 1270 | return points; 1271 | }; 1272 | 1273 | module.exports = Utils; 1274 | },{"./OrientedBoundingBox":8}]},{},[1])(1) 1275 | }); 1276 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMiLCJzcmMvQ29uc3RhbnRzLmpzIiwic3JjL0dlc3R1cmUuanMiLCJzcmMvR2VzdHVyZVN0b3JlLmpzIiwic3JjL0luc3RhbmNlLmpzIiwic3JjL0luc3RhbmNlTGVhcm5lci5qcyIsInNyYy9MZWFybmVyLmpzIiwic3JjL09yaWVudGVkQm91bmRpbmdCb3guanMiLCJzcmMvUG9pbnQuanMiLCJzcmMvUHJlZGljdGlvbi5qcyIsInNyYy9SZWN0LmpzIiwic3JjL1N0cm9rZS5qcyIsInNyYy9VdGlscy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDYkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNsQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDM0hBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDckxBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaEVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNYQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNmQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDakRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMxRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIm1vZHVsZS5leHBvcnRzID0ge1xuICBDb25zdGFudHM6ICAgICAgICAgICByZXF1aXJlKCcuL0NvbnN0YW50cycpLFxuICBHZXN0dXJlOiAgICAgICAgICAgICByZXF1aXJlKCcuL0dlc3R1cmUnKSxcbiAgR2VzdHVyZVN0b3JlOiAgICAgICAgcmVxdWlyZSgnLi9HZXN0dXJlU3RvcmUnKSxcbiAgSW5zdGFuY2U6ICAgICAgICAgICAgcmVxdWlyZSgnLi9JbnN0YW5jZScpLFxuICBJbnN0YW5jZUxlYXJuZXI6ICAgICByZXF1aXJlKCcuL0luc3RhbmNlTGVhcm5lcicpLFxuICBMZWFybmVyOiAgICAgICAgICAgICByZXF1aXJlKCcuL0xlYXJuZXInKSxcbiAgT3JpZW50ZWRCb3VuZGluZ0JveDogcmVxdWlyZSgnLi9PcmllbnRlZEJvdW5kaW5nQm94JyksXG4gIFBvaW50OiAgICAgICAgICAgICAgIHJlcXVpcmUoJy4vUG9pbnQnKSxcbiAgUHJlZGljdGlvbjogICAgICAgICAgcmVxdWlyZSgnLi9QcmVkaWN0aW9uJyksXG4gIFJlY3Q6ICAgICAgICAgICAgICAgIHJlcXVpcmUoJy4vUmVjdCcpLFxuICBTdHJva2U6ICAgICAgICAgICAgICByZXF1aXJlKCcuL1N0cm9rZScpLFxuICBVdGlsczogICAgICAgICAgICAgICByZXF1aXJlKCcuL1V0aWxzJylcbn07IiwiLyoqXG4gKiBDb25zdGFudHMuXG4gKi9cbm1vZHVsZS5leHBvcnRzID0ge1xuICAvLyBpZ25vcmUgc2VxdWVuY2UgaW5mb3JtYXRpb25cbiAgU0VRVUVOQ0VfSU5WQVJJQU5UOiAxLFxuICAvLyB3aGVuIFNFUVVFTkNFX1NFTlNJVElWRSBpcyB1c2VkLCBvbmx5IHNpbmdsZSBzdHJva2UgZ2VzdHVyZXMgYXJlIGN1cnJlbnRseSBhbGxvd2VkXG4gIFNFUVVFTkNFX1NFTlNJVElWRTogMixcblxuICAvLyBPUklFTlRBVElPTl9TRU5TSVRJVkUgYW5kIE9SSUVOVEFUSU9OX0lOVkFSSUFOVCBhcmUgb25seSBmb3IgU0VRVUVOQ0VfU0VOU0lUSVZFIGdlc3R1cmVzXG4gIE9SSUVOVEFUSU9OX0lOVkFSSUFOVDogMSxcbiAgLy8gYXQgbW9zdCAyIGRpcmVjdGlvbnMgY2FuIGJlIHJlY29nbml6ZWRcbiAgT1JJRU5UQVRJT05fU0VOU0lUSVZFOiAyLFxuICAvLyBhdCBtb3N0IDQgZGlyZWN0aW9ucyBjYW4gYmUgcmVjb2duaXplZFxuICBPUklFTlRBVElPTl9TRU5TSVRJVkVfNDogNCxcbiAgLy8gYXQgbW9zdCA4IGRpcmVjdGlvbnMgY2FuIGJlIHJlY29nbml6ZWRcbiAgT1JJRU5UQVRJT05fU0VOU0lUSVZFXzg6IDgsXG5cbiAgU0VRVUVOQ0VfU0FNUExFX1NJWkU6IDE2LFxuXG4gIFBBVENIX1NBTVBMRV9TSVpFOiAxNixcblxuICBPUklFTlRBVElPTlM6IFtcbiAgICAwLFxuICAgIChNYXRoLlBJIC8gNCksXG4gICAgKE1hdGguUEkgLyAyKSxcbiAgICAoTWF0aC5QSSAqIDMgLyA0KSxcbiAgICBNYXRoLlBJLFxuICAgIC0wLFxuICAgICgtTWF0aC5QSSAvIDQpLFxuICAgICgtTWF0aC5QSSAvIDIpLFxuICAgICgtTWF0aC5QSSAqIDMgLyA0KSxcbiAgICAtTWF0aC5QSVxuICBdXG59OyIsInZhciBSZWN0ID0gcmVxdWlyZSgnLi9SZWN0Jyk7XG52YXIgU3Ryb2tlID0gcmVxdWlyZSgnLi9TdHJva2UnKTtcblxuLyoqXG4gKiBBIGdlc3R1cmUgaXMgYSBoYW5kLWRyYXduIHNoYXBlIG9uIGEgdG91Y2ggc2NyZWVuLiBJdCBjYW4gaGF2ZSBvbmUgb3IgbXVsdGlwbGUgc3Ryb2tlcy5cbiAqIEVhY2ggc3Ryb2tlIGlzIGEgc2VxdWVuY2Ugb2YgdGltZWQgcG9pbnRzLiBBIHVzZXItZGVmaW5lZCBnZXN0dXJlIGNhbiBiZSByZWNvZ25pemVkIGJ5IFxuICogYSBHZXN0dXJlTGlicmFyeS4gXG4gKi9cbmZ1bmN0aW9uIEdlc3R1cmUoc3Ryb2tlcykge1xuICB0aGlzLm1Cb3VuZGluZ0JveCA9IG5ldyBSZWN0KCk7XG4gIHRoaXMubUdlc3R1cmVJRCA9IEdlc3R1cmUuR0VTVFVSRV9JRF9CQVNFICsgKCsrR2VzdHVyZS5HRVNUVVJFX0NPVU5UKTtcbiAgdGhpcy5tU3Ryb2tlcyA9IFtdO1xuICBpZiAoIXN0cm9rZXMpIHJldHVybjtcbiAgZm9yICh2YXIgaT0wOyBpPHN0cm9rZXMubGVuZ3RoOyArK2kpIHtcbiAgICB0aGlzLmFkZFN0cm9rZShzdHJva2VzW2ldKTtcbiAgfVxufVxuXG5HZXN0dXJlLkdFU1RVUkVfSURfQkFTRSA9IERhdGUubm93KCk7XG5cbkdlc3R1cmUuR0VTVFVSRV9DT1VOVCA9IDA7XG5cbkdlc3R1cmUucHJvdG90eXBlLmNsb25lID0gZnVuY3Rpb24oKSB7XG4gIHZhciBnZXN0dXJlID0gbmV3IEdlc3R1cmUoKTtcbiAgZ2VzdHVyZS5tQm91bmRpbmdCb3guc2V0KFxuICAgIHRoaXMubUJvdW5kaW5nQm94LmxlZnQsXG4gICAgdGhpcy5tQm91bmRpbmdCb3gudG9wLFxuICAgIHRoaXMubUJvdW5kaW5nQm94LnJpZ2h0LFxuICAgIHRoaXMubUJvdW5kaW5nQm94LmJvdHRvbVxuICApO1xuICB2YXIgY291bnQgPSB0aGlzLm1TdHJva2VzLmxlbmd0aDtcbiAgZm9yICh2YXIgaT0wOyBpPGNvdW50OyArK2kpIHtcbiAgICB2YXIgc3Ryb2tlID0gdGhpcy5tU3Ryb2tlc1tpXTtcbiAgICBnZXN0dXJlLm1TdHJva2VzLnB1c2goc3Ryb2tlLmNsb25lKCkpO1xuICB9XG4gIHJldHVybiBnZXN0dXJlO1xufTtcblxuLyoqXG4gKiBAcmV0dXJuIGFsbCB0aGUgc3Ryb2tlcyBvZiB0aGUgZ2VzdHVyZVxuICovXG5HZXN0dXJlLnByb3RvdHlwZS5nZXRTdHJva2VzID0gZnVuY3Rpb24oKSB7XG4gIHJldHVybiB0aGlzLm1TdHJva2VzO1xufTtcblxuLyoqXG4gKiBAcmV0dXJuIHRoZSBudW1iZXIgb2Ygc3Ryb2tlcyBpbmNsdWRlZCBieSB0aGlzIGdlc3R1cmVcbiAqL1xuR2VzdHVyZS5wcm90b3R5cGUuZ2V0U3Ryb2tlc0NvdW50ID0gZnVuY3Rpb24oKSB7XG4gIHJldHVybiB0aGlzLm1TdHJva2VzLmxlbmd0aDtcbn07XG5cbi8qKlxuICogQWRkcyBhIHN0cm9rZSB0byB0aGUgZ2VzdHVyZS5cbiAqIFxuICogQHBhcmFtIHN0cm9rZVxuICovXG5HZXN0dXJlLnByb3RvdHlwZS5hZGRTdHJva2UgPSBmdW5jdGlvbihzdHJva2UpIHtcbiAgdGhpcy5tU3Ryb2tlcy5wdXNoKHN0cm9rZSk7XG4gIHRoaXMubUJvdW5kaW5nQm94LnVuaW9uKHN0cm9rZS5ib3VuZGluZ0JveCk7XG59O1xuXG4vKipcbiAqIENhbGN1bGF0ZXMgdGhlIHRvdGFsIGxlbmd0aCBvZiB0aGUgZ2VzdHVyZS4gV2hlbiB0aGVyZSBhcmUgbXVsdGlwbGUgc3Ryb2tlcyBpblxuICogdGhlIGdlc3R1cmUsIHRoaXMgcmV0dXJucyB0aGUgc3VtIG9mIHRoZSBsZW5ndGhzIG9mIGFsbCB0aGUgc3Ryb2tlcy5cbiAqIFxuICogQHJldHVybiB0aGUgbGVuZ3RoIG9mIHRoZSBnZXN0dXJlXG4gKi9cbkdlc3R1cmUucHJvdG90eXBlLmdldExlbmd0aCA9IGZ1bmN0aW9uKCkge1xuICB2YXIgbGVuID0gMDtcbiAgdmFyIHN0cm9rZXMgPSB0aGlzLm1TdHJva2VzO1xuICB2YXIgY291bnQgPSBzdHJva2VzLmxlbmd0aDtcbiAgXG4gIGZvciAodmFyIGk9MDsgaTxjb3VudDsgKytpKSB7XG4gICAgbGVuICs9IHN0cm9rZXNbaV0ubGVuZ3RoO1xuICB9XG5cbiAgcmV0dXJuIGxlbjtcbn07XG5cbi8qKlxuICogQHJldHVybiB0aGUgYm91bmRpbmcgYm94IG9mIHRoZSBnZXN0dXJlXG4gKi9cbkdlc3R1cmUucHJvdG90eXBlLmdldEJvdW5kaW5nQm94ID0gZnVuY3Rpb24oKSB7XG4gIHJldHVybiB0aGlzLm1Cb3VuZGluZ0JveDtcbn07XG5cbi8qKlxuICogU2V0cyB0aGUgaWQgb2YgdGhlIGdlc3R1cmUuXG4gKiBcbiAqIEBwYXJhbSBpZFxuICovXG5HZXN0dXJlLnByb3RvdHlwZS5zZXRJRCA9IGZ1bmN0aW9uKGlkKSB7XG4gIHRoaXMubUdlc3R1cmVJRCA9IGlkO1xufTtcblxuLyoqXG4gKiBAcmV0dXJuIHRoZSBpZCBvZiB0aGUgZ2VzdHVyZVxuICovXG5HZXN0dXJlLnByb3RvdHlwZS5nZXRJRCA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gdGhpcy5tR2VzdHVyZUlEO1xufTtcblxuLy8gLS0tXG5cbkdlc3R1cmUucHJvdG90eXBlLnRvSlNPTiA9IGZ1bmN0aW9uKCkge1xuICB2YXIgc3Ryb2tlcyA9IFtdO1xuICB2YXIgY291bnQgPSB0aGlzLm1TdHJva2VzLmxlbmd0aDtcbiAgZm9yICh2YXIgaT0wOyBpPGNvdW50OyArK2kpIHtcbiAgICBzdHJva2VzLnB1c2godGhpcy5tU3Ryb2tlc1tpXS50b0pTT04oKSk7XG4gIH1cbiAgcmV0dXJuIHN0cm9rZXM7XG59O1xuXG5HZXN0dXJlLmZyb21KU09OID0gZnVuY3Rpb24oanNvbikge1xuICB2YXIgZ2VzdHVyZSA9IG5ldyBHZXN0dXJlKCk7XG4gIGZvciAodmFyIGk9MDsgaTxqc29uLmxlbmd0aDsgKytpKSB7XG4gICAgZ2VzdHVyZS5hZGRTdHJva2UoU3Ryb2tlLmZyb21KU09OKGpzb25baV0pKTtcbiAgfVxuICByZXR1cm4gZ2VzdHVyZTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gR2VzdHVyZTtcbiIsInZhciBDb25zdGFudHMgPSByZXF1aXJlKCcuL0NvbnN0YW50cycpO1xudmFyIEdlc3R1cmUgPSByZXF1aXJlKCcuL0dlc3R1cmUnKTtcbnZhciBJbnN0YW5jZSA9IHJlcXVpcmUoJy4vSW5zdGFuY2UnKTtcbnZhciBJbnN0YW5jZUxlYXJuZXIgPSByZXF1aXJlKCcuL0luc3RhbmNlTGVhcm5lcicpO1xuXG4vKipcbiAqIEdlc3R1cmVTdG9yZSBtYWludGFpbnMgZ2VzdHVyZSBleGFtcGxlcyBhbmQgbWFrZXMgcHJlZGljdGlvbnMgb24gYSBuZXdcbiAqIGdlc3R1cmUuXG4gKi9cbmZ1bmN0aW9uIEdlc3R1cmVTdG9yZSgpIHtcbiAgdGhpcy5tU2VxdWVuY2VUeXBlID0gQ29uc3RhbnRzLlNFUVVFTkNFX1NFTlNJVElWRTtcbiAgdGhpcy5tT3JpZW50YXRpb25TdHlsZSA9IENvbnN0YW50cy5PUklFTlRBVElPTl9TRU5TSVRJVkVfNDtcbiAgdGhpcy5tQ2xhc3NpZmllciA9IG5ldyBJbnN0YW5jZUxlYXJuZXIoKTtcbiAgdGhpcy5tQ2hhbmdlZCA9IGZhbHNlO1xuICB0aGlzLm1OYW1lZEdlc3R1cmVzID0ge307XG59XG5cbi8qKlxuICogU3BlY2lmeSBob3cgdGhlIGdlc3R1cmUgbGlicmFyeSB3aWxsIGhhbmRsZSBvcmllbnRhdGlvbi4gXG4gKiBVc2UgT1JJRU5UQVRJT05fSU5WQVJJQU5UIG9yIE9SSUVOVEFUSU9OX1NFTlNJVElWRVxuICogXG4gKiBAcGFyYW0gc3R5bGVcbiAqL1xuR2VzdHVyZVN0b3JlLnByb3RvdHlwZS5zZXRPcmllbnRhdGlvblN0eWxlID0gZnVuY3Rpb24oc3R5bGUpIHtcbiAgdGhpcy5tT3JpZW50YXRpb25TdHlsZSA9IHN0eWxlO1xufTtcblxuR2VzdHVyZVN0b3JlLnByb3RvdHlwZS5nZXRPcmllbnRhdGlvblN0eWxlID0gZnVuY3Rpb24oKSB7XG4gIHJldHVybiB0aGlzLm1PcmllbnRhdGlvblN0eWxlO1xufTtcblxuLyoqXG4gKiBAcGFyYW0gdHlwZSBTRVFVRU5DRV9JTlZBUklBTlQgb3IgU0VRVUVOQ0VfU0VOU0lUSVZFXG4gKi9cbkdlc3R1cmVTdG9yZS5wcm90b3R5cGUuc2V0U2VxdWVuY2VUeXBlID0gZnVuY3Rpb24odHlwZSkge1xuICB0aGlzLm1TZXF1ZW5jZVR5cGUgPSB0eXBlO1xufTtcblxuLyoqXG4gKiBAcmV0dXJuIFNFUVVFTkNFX0lOVkFSSUFOVCBvciBTRVFVRU5DRV9TRU5TSVRJVkVcbiAqL1xuR2VzdHVyZVN0b3JlLnByb3RvdHlwZS5nZXRTZXF1ZW5jZVR5cGUgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHRoaXMubVNlcXVlbmNlVHlwZTtcbn07XG5cbi8qKlxuICogR2V0IGFsbCB0aGUgZ2VzdHVyZSBlbnRyeSBuYW1lcyBpbiB0aGUgbGlicmFyeVxuICogXG4gKiBAcmV0dXJuIGEgc2V0IG9mIHN0cmluZ3NcbiAqL1xuR2VzdHVyZVN0b3JlLnByb3RvdHlwZS5nZXRHZXN0dXJlRW50cmllcyA9IGZ1bmN0aW9uKCkge1xuICB2YXIgbmFtZXMgPSBbXTtcbiAgZm9yICh2YXIgbmFtZSBpbiB0aGlzLm1OYW1lZEdlc3R1cmVzKSB7XG4gICAgbmFtZXMucHVzaChuYW1lKTtcbiAgfVxuICByZXR1cm4gbmFtZXM7XG59O1xuXG4vKipcbiAqIFJlY29nbml6ZSBhIGdlc3R1cmVcbiAqIFxuICogQHBhcmFtIGdlc3R1cmUgdGhlIHF1ZXJ5XG4gKiBAcmV0dXJuIGEgbGlzdCBvZiBwcmVkaWN0aW9ucyBvZiBwb3NzaWJsZSBlbnRyaWVzIGZvciBhIGdpdmVuIGdlc3R1cmVcbiAqL1xuR2VzdHVyZVN0b3JlLnByb3RvdHlwZS5yZWNvZ25pemUgPSBmdW5jdGlvbihnZXN0dXJlKSB7XG4gIHZhciBpbnN0YW5jZSA9IEluc3RhbmNlLmNyZWF0ZUluc3RhbmNlKFxuICAgICAgdGhpcy5tU2VxdWVuY2VUeXBlLCB0aGlzLm1PcmllbnRhdGlvblN0eWxlLCBnZXN0dXJlLCBudWxsKTtcbiAgcmV0dXJuIHRoaXMubUNsYXNzaWZpZXIuY2xhc3NpZnkoXG4gICAgICB0aGlzLm1TZXF1ZW5jZVR5cGUsIHRoaXMubU9yaWVudGF0aW9uU3R5bGUsIGluc3RhbmNlLnZlY3Rvcik7XG59O1xuXG4vKipcbiAqIEFkZCBhIGdlc3R1cmUgZm9yIHRoZSBlbnRyeVxuICogXG4gKiBAcGFyYW0gZW50cnlOYW1lIGVudHJ5IG5hbWVcbiAqIEBwYXJhbSBnZXN0dXJlXG4gKi9cbkdlc3R1cmVTdG9yZS5wcm90b3R5cGUuYWRkR2VzdHVyZSA9IGZ1bmN0aW9uKGVudHJ5TmFtZSwgZ2VzdHVyZSkge1xuICBpZiAoZW50cnlOYW1lID09IG51bGwgfHwgZW50cnlOYW1lLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybjtcbiAgfVxuICBnZXN0dXJlcyA9IHRoaXMubU5hbWVkR2VzdHVyZXNbZW50cnlOYW1lXTtcbiAgaWYgKGdlc3R1cmVzID09IG51bGwpIHtcbiAgICBnZXN0dXJlcyA9IFtdO1xuICAgIHRoaXMubU5hbWVkR2VzdHVyZXNbZW50cnlOYW1lXSA9IGdlc3R1cmVzO1xuICB9XG4gIGdlc3R1cmVzLnB1c2goZ2VzdHVyZSk7XG4gIHRoaXMubUNsYXNzaWZpZXIuYWRkSW5zdGFuY2UoSW5zdGFuY2UuY3JlYXRlSW5zdGFuY2UoXG4gICAgdGhpcy5tU2VxdWVuY2VUeXBlLCB0aGlzLm1PcmllbnRhdGlvblN0eWxlLCBnZXN0dXJlLCBlbnRyeU5hbWVcbiAgKSk7XG4gIHRoaXMubUNoYW5nZWQgPSB0cnVlO1xufTtcblxuLyoqXG4gKiBSZW1vdmUgYSBnZXN0dXJlIGZyb20gdGhlIGxpYnJhcnkuIElmIHRoZXJlIGFyZSBubyBtb3JlIGdlc3R1cmVzIGZvciB0aGVcbiAqIGdpdmVuIGVudHJ5LCB0aGUgZ2VzdHVyZSBlbnRyeSB3aWxsIGJlIHJlbW92ZWQuXG4gKiBcbiAqIEBwYXJhbSBlbnRyeU5hbWUgZW50cnkgbmFtZVxuICogQHBhcmFtIGdlc3R1cmVcbiAqL1xuR2VzdHVyZVN0b3JlLnByb3RvdHlwZS5yZW1vdmVHZXN0dXJlID0gZnVuY3Rpb24oZW50cnlOYW1lLCBnZXN0dXJlKSB7XG4gIHZhciBnZXN0dXJlcyA9IHRoaXMubU5hbWVkR2VzdHVyZXNbZW50cnlOYW1lXTtcbiAgaWYgKGdlc3R1cmVzID09IG51bGwpIHtcbiAgICByZXR1cm47XG4gIH1cblxuICB2YXIgaW5kZXggPSBnZXN0dXJlcy5pbmRleE9mKGdlc3R1cmUpO1xuICBnZXN0dXJlcy5zcGxpY2UoaW5kZXgsIDEpO1xuXG4gIC8vIGlmIHRoZXJlIGFyZSBubyBtb3JlIHNhbXBsZXMsIHJlbW92ZSB0aGUgZW50cnkgYXV0b21hdGljYWxseVxuICBpZiAoZ2VzdHVyZXMubGVuZ3RoID09PSAwKSB7XG4gICAgZGVsZXRlIHRoaXMubU5hbWVkR2VzdHVyZXNbZW50cnlOYW1lXTtcbiAgfVxuXG4gIHRoaXMubUNsYXNzaWZpZXIucmVtb3ZlSW5zdGFuY2UoZ2VzdHVyZS5nZXRJRCgpKTtcblxuICB0aGlzLm1DaGFuZ2VkID0gdHJ1ZTtcbn07XG5cbi8qKlxuICogUmVtb3ZlIGFuIGVudHJ5IG9mIGdlc3R1cmVzXG4gKiBcbiAqIEBwYXJhbSBlbnRyeU5hbWUgdGhlIGVudHJ5IG5hbWVcbiAqL1xuR2VzdHVyZVN0b3JlLnByb3RvdHlwZS5yZW1vdmVFbnRyeSA9IGZ1bmN0aW9uKGVudHJ5TmFtZSkge1xuICBkZWxldGUgdGhpcy5tTmFtZWRHZXN0dXJlc1tlbnRyeU5hbWVdO1xuICB0aGlzLm1DbGFzc2lmaWVyLnJlbW92ZUluc3RhbmNlcyhlbnRyeU5hbWUpO1xuICB0aGlzLm1DaGFuZ2VkID0gdHJ1ZTtcbn07XG5cbi8qKlxuICogR2V0IGFsbCB0aGUgZ2VzdHVyZXMgb2YgYW4gZW50cnlcbiAqIFxuICogQHBhcmFtIGVudHJ5TmFtZVxuICogQHJldHVybiB0aGUgbGlzdCBvZiBnZXN0dXJlcyB0aGF0IGlzIHVuZGVyIHRoaXMgbmFtZVxuICovXG5HZXN0dXJlU3RvcmUucHJvdG90eXBlLmdldEdlc3R1cmVzID0gZnVuY3Rpb24oZW50cnlOYW1lKSB7XG4gIHZhciBnZXN0dXJlcyA9IHRoaXMubU5hbWVkR2VzdHVyZXNbZW50cnlOYW1lXTtcbiAgaWYgKGdlc3R1cmVzICE9IG51bGwpIHtcbiAgICByZXR1cm4gZ2VzdHVyZXMuc2xpY2UoKTtcbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gW107XG4gIH1cbn07XG5cbkdlc3R1cmVTdG9yZS5wcm90b3R5cGUuaGFzQ2hhbmdlZCA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gdGhpcy5tQ2hhbmdlZDtcbn07XG5cbkdlc3R1cmVTdG9yZS5wcm90b3R5cGUuZ2V0TGVhcm5lciA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gdGhpcy5tQ2xhc3NpZmllcjtcbn07XG5cbi8vIC0tLVxuXG5HZXN0dXJlU3RvcmUucHJvdG90eXBlLnRvSlNPTiA9IGZ1bmN0aW9uKCkge1xuICB2YXIgbyA9IHt9O1xuICBvLnNlcXVlbmNlID0gdGhpcy5tU2VxdWVuY2VUeXBlO1xuICBvLm9yaWVudGF0aW9uID0gdGhpcy5tT3JpZW50YXRpb25TdHlsZTtcbiAgby5nZXN0dXJlcyA9IHt9O1xuICBmb3IgKHZhciBuYW1lIGluIHRoaXMubU5hbWVkR2VzdHVyZXMpIHtcbiAgICB2YXIgZ2VzdHVyZXMgPSB0aGlzLm1OYW1lZEdlc3R1cmVzW25hbWVdO1xuICAgIG8uZ2VzdHVyZXNbbmFtZV0gPSBnZXN0dXJlcy5tYXAoZnVuY3Rpb24oZykgeyByZXR1cm4gZy50b0pTT04oKTsgfSk7XG4gIH1cbiAgcmV0dXJuIG87XG59O1xuXG5HZXN0dXJlU3RvcmUuZnJvbUpTT04gPSBmdW5jdGlvbihqc29uKSB7XG4gIHZhciBncyA9IG5ldyBHZXN0dXJlU3RvcmUoKTtcbiAgZ3Muc2V0U2VxdWVuY2VUeXBlKGpzb24uc2VxdWVuY2UpO1xuICBncy5zZXRPcmllbnRhdGlvblN0eWxlKGpzb24ub3JpZW50YXRpb24pO1xuICBmb3IgKHZhciBuYW1lIGluIGpzb24uZ2VzdHVyZXMpIHtcbiAgICB2YXIgZ2VzdHVyZXMgPSBqc29uLmdlc3R1cmVzW25hbWVdO1xuICAgIGdlc3R1cmVzLmZvckVhY2goZnVuY3Rpb24oZykge1xuICAgICAgZ3MuYWRkR2VzdHVyZShuYW1lLCBHZXN0dXJlLmZyb21KU09OKGcpKTtcbiAgICB9KTtcbiAgfVxuICByZXR1cm4gZ3M7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEdlc3R1cmVTdG9yZTtcbiIsInZhciBDb25zdGFudHMgPSByZXF1aXJlKCcuL0NvbnN0YW50cycpO1xudmFyIFV0aWxzID0gcmVxdWlyZSgnLi9VdGlscycpO1xuXG4vKipcbiAqIEFuIGluc3RhbmNlIHJlcHJlc2VudHMgYSBzYW1wbGUgaWYgdGhlIGxhYmVsIGlzIGF2YWlsYWJsZSBvciBhIHF1ZXJ5IGlmIHRoZVxuICogbGFiZWwgaXMgbnVsbC5cbiAqL1xuZnVuY3Rpb24gSW5zdGFuY2UoaWQsIHNhbXBsZSwgc2FtcGxlTmFtZSkge1xuICB0aGlzLmlkID0gaWQ7XG4gIHRoaXMudmVjdG9yID0gc2FtcGxlO1xuICB0aGlzLmxhYmVsID0gc2FtcGxlTmFtZTtcbn1cblxuSW5zdGFuY2UucHJvdG90eXBlLm5vcm1hbGl6ZSA9IGZ1bmN0aW9uKCkge1xuICB2YXIgc2FtcGxlID0gdGhpcy52ZWN0b3I7XG4gIHZhciBzdW0gPSAwO1xuXG4gIHZhciBzaXplID0gc2FtcGxlLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzaXplOyBpKyspIHtcbiAgICBzdW0gKz0gc2FtcGxlW2ldICogc2FtcGxlW2ldO1xuICB9XG5cbiAgdmFyIG1hZ25pdHVkZSA9IE1hdGguc3FydChzdW0pO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IHNpemU7IGkrKykge1xuICAgIHNhbXBsZVtpXSAvPSBtYWduaXR1ZGU7XG4gIH1cbn07XG5cbi8qKlxuICogY3JlYXRlIGEgbGVhcm5pbmcgaW5zdGFuY2UgZm9yIGEgc2luZ2xlIHN0cm9rZSBnZXN0dXJlXG4gKiBcbiAqIEBwYXJhbSBnZXN0dXJlXG4gKiBAcGFyYW0gbGFiZWxcbiAqIEByZXR1cm4gdGhlIGluc3RhbmNlXG4gKi9cbkluc3RhbmNlLmNyZWF0ZUluc3RhbmNlID0gZnVuY3Rpb24oc2VxdWVuY2VUeXBlLCBvcmllbnRhdGlvblR5cGUsIGdlc3R1cmUsIGxhYmVsKSB7XG4gIHZhciBwdHM7XG4gIHZhciBpbnN0YW5jZTtcbiAgaWYgKHNlcXVlbmNlVHlwZSA9PT0gQ29uc3RhbnRzLlNFUVVFTkNFX1NFTlNJVElWRSkge1xuICAgIHB0cyA9IEluc3RhbmNlLnRlbXBvcmFsU2FtcGxlcihvcmllbnRhdGlvblR5cGUsIGdlc3R1cmUpO1xuICAgIGluc3RhbmNlID0gbmV3IEluc3RhbmNlKGdlc3R1cmUuZ2V0SUQoKSwgcHRzLCBsYWJlbCk7XG4gICAgaW5zdGFuY2Uubm9ybWFsaXplKCk7XG4gIH0gZWxzZSB7XG4gICAgcHRzID0gSW5zdGFuY2Uuc3BhdGlhbFNhbXBsZXIoZ2VzdHVyZSk7XG4gICAgaW5zdGFuY2UgPSBuZXcgSW5zdGFuY2UoZ2VzdHVyZS5nZXRJRCgpLCBwdHMsIGxhYmVsKTtcbiAgfVxuICByZXR1cm4gaW5zdGFuY2U7XG59O1xuXG5JbnN0YW5jZS5zcGF0aWFsU2FtcGxlciA9IGZ1bmN0aW9uKGdlc3R1cmUpIHtcbiAgcmV0dXJuIFV0aWxzLnNwYXRpYWxTYW1wbGluZyhnZXN0dXJlLCBDb25zdGFudHMuUEFUQ0hfU0FNUExFX1NJWkUsIGZhbHNlKTtcbn07XG5cbkluc3RhbmNlLnRlbXBvcmFsU2FtcGxlciA9IGZ1bmN0aW9uKG9yaWVudGF0aW9uVHlwZSwgZ2VzdHVyZSkge1xuICB2YXIgcHRzID0gVXRpbHMudGVtcG9yYWxTYW1wbGluZyhnZXN0dXJlLmdldFN0cm9rZXMoKVswXSxcbiAgICAgICAgICBDb25zdGFudHMuU0VRVUVOQ0VfU0FNUExFX1NJWkUpO1xuICB2YXIgY2VudGVyID0gVXRpbHMuY29tcHV0ZUNlbnRyb2lkKHB0cyk7XG4gIHZhciBvcmllbnRhdGlvbiA9IE1hdGguYXRhbjIocHRzWzFdIC0gY2VudGVyWzFdLCBwdHNbMF0gLSBjZW50ZXJbMF0pO1xuXG4gIHZhciBhZGp1c3RtZW50ID0gLW9yaWVudGF0aW9uO1xuICBpZiAob3JpZW50YXRpb25UeXBlICE9IENvbnN0YW50cy5PUklFTlRBVElPTl9JTlZBUklBTlQpIHtcbiAgICB2YXIgY291bnQgPSBDb25zdGFudHMuT1JJRU5UQVRJT05TLmxlbmd0aDtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGNvdW50OyBpKyspIHtcbiAgICAgIHZhciBkZWx0YSA9IENvbnN0YW50cy5PUklFTlRBVElPTlNbaV0gLSBvcmllbnRhdGlvbjtcbiAgICAgIGlmIChNYXRoLmFicyhkZWx0YSkgPCBNYXRoLmFicyhhZGp1c3RtZW50KSkge1xuICAgICAgICBhZGp1c3RtZW50ID0gZGVsdGE7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgVXRpbHMudHJhbnNsYXRlKHB0cywgLWNlbnRlclswXSwgLWNlbnRlclsxXSk7XG4gIFV0aWxzLnJvdGF0ZShwdHMsIGFkanVzdG1lbnQpO1xuXG4gIHJldHVybiBwdHM7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEluc3RhbmNlOyIsInZhciBQcmVkaWN0aW9uID0gcmVxdWlyZSgnLi9QcmVkaWN0aW9uJyk7XG52YXIgQ29uc3RhbnRzID0gcmVxdWlyZSgnLi9Db25zdGFudHMnKTtcbnZhciBMZWFybmVyID0gcmVxdWlyZSgnLi9MZWFybmVyJyk7XG52YXIgVXRpbHMgPSByZXF1aXJlKCcuL1V0aWxzJyk7XG5cbi8qKlxuICogQW4gaW1wbGVtZW50YXRpb24gb2YgYW4gaW5zdGFuY2UtYmFzZWQgbGVhcm5lclxuICovXG5mdW5jdGlvbiBJbnN0YW5jZUxlYXJuZXIoKSB7XG4gIExlYXJuZXIuY2FsbCh0aGlzKTtcbn1cblxuSW5zdGFuY2VMZWFybmVyLnByb3RvdHlwZSA9IG5ldyBMZWFybmVyKCk7XG5cbkluc3RhbmNlTGVhcm5lci5jb21wYXJlID0gZnVuY3Rpb24ob2JqZWN0MSwgb2JqZWN0Mikge1xuICB2YXIgc2NvcmUxID0gb2JqZWN0MS5zY29yZTtcbiAgdmFyIHNjb3JlMiA9IG9iamVjdDIuc2NvcmU7XG4gIGlmIChzY29yZTEgPiBzY29yZTIpIHtcbiAgICByZXR1cm4gLTE7XG4gIH0gZWxzZSBpZiAoc2NvcmUxIDwgc2NvcmUyKSB7XG4gICAgcmV0dXJuIDE7XG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIDA7XG4gIH1cbn07XG5cbkluc3RhbmNlTGVhcm5lci5wcm90b3R5cGUuY2xhc3NpZnkgPSBmdW5jdGlvbihzZXF1ZW5jZVR5cGUsIG9yaWVudGF0aW9uVHlwZSwgdmVjdG9yKSB7XG4gIHZhciBwcmVkaWN0aW9ucyA9IFtdO1xuICB2YXIgaW5zdGFuY2VzID0gdGhpcy5nZXRJbnN0YW5jZXMoKTtcbiAgdmFyIGNvdW50ID0gaW5zdGFuY2VzLmxlbmd0aDtcbiAgdmFyIGxhYmVsMnNjb3JlID0ge307XG5cbiAgZm9yICh2YXIgaT0wOyBpPGNvdW50OyArK2kpIHtcbiAgICB2YXIgc2FtcGxlID0gaW5zdGFuY2VzW2ldO1xuICAgIGlmIChzYW1wbGUudmVjdG9yLmxlbmd0aCAhPSB2ZWN0b3IubGVuZ3RoKSB7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgdmFyIGRpc3RhbmNlO1xuICAgIGlmIChzZXF1ZW5jZVR5cGUgPT0gQ29uc3RhbnRzLlNFUVVFTkNFX1NFTlNJVElWRSkge1xuICAgICAgZGlzdGFuY2UgPSBVdGlscy5taW5pbXVtQ29zaW5lRGlzdGFuY2Uoc2FtcGxlLnZlY3RvciwgdmVjdG9yLCBvcmllbnRhdGlvblR5cGUpO1xuICAgIH0gZWxzZSB7XG4gICAgICBkaXN0YW5jZSA9IFV0aWxzLnNxdWFyZWRFdWNsaWRlYW5EaXN0YW5jZShzYW1wbGUudmVjdG9yLCB2ZWN0b3IpO1xuICAgIH1cbiAgICB2YXIgd2VpZ2h0O1xuICAgIGlmIChkaXN0YW5jZSA9PSAwKSB7XG4gICAgICB3ZWlnaHQgPSBOdW1iZXIuTUFYX1ZBTFVFO1xuICAgIH0gZWxzZSB7XG4gICAgICB3ZWlnaHQgPSAxIC8gZGlzdGFuY2U7XG4gICAgfVxuICAgIHZhciBzY29yZSA9IGxhYmVsMnNjb3JlW3NhbXBsZS5sYWJlbF07XG4gICAgaWYgKHNjb3JlID09IG51bGwgfHwgd2VpZ2h0ID4gc2NvcmUpIHtcbiAgICAgIGxhYmVsMnNjb3JlW3NhbXBsZS5sYWJlbF0gPSB3ZWlnaHQ7XG4gICAgfVxuICB9XG5cbiAgZm9yICh2YXIgbmFtZSBpbiBsYWJlbDJzY29yZSkge1xuICAgIHZhciBzY29yZSA9IGxhYmVsMnNjb3JlW25hbWVdO1xuICAgIHByZWRpY3Rpb25zLnB1c2gobmV3IFByZWRpY3Rpb24obmFtZSwgc2NvcmUpKTtcbiAgfVxuXG4gIHJldHVybiBwcmVkaWN0aW9ucy5zb3J0KEluc3RhbmNlTGVhcm5lci5jb21wYXJlKTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gSW5zdGFuY2VMZWFybmVyO1xuIiwiLyoqXG4gKiBUaGUgYWJzdHJhY3QgY2xhc3Mgb2YgYSBnZXN0dXJlIGxlYXJuZXJcbiAqL1xuZnVuY3Rpb24gTGVhcm5lcigpIHtcbiAgdGhpcy5tSW5zdGFuY2VzID0gW107XG59XG5cbi8qKlxuICogQWRkIGFuIGluc3RhbmNlIHRvIHRoZSBsZWFybmVyXG4gKiBcbiAqIEBwYXJhbSBpbnN0YW5jZVxuICovXG5MZWFybmVyLnByb3RvdHlwZS5hZGRJbnN0YW5jZSA9IGZ1bmN0aW9uKGluc3RhbmNlKSB7XG4gIHRoaXMubUluc3RhbmNlcy5wdXNoKGluc3RhbmNlKTtcbn07XG5cbi8qKlxuICogUmV0cmlldmUgYWxsIHRoZSBpbnN0YW5jZXNcbiAqIFxuICogQHJldHVybiBpbnN0YW5jZXNcbiAqL1xuTGVhcm5lci5wcm90b3R5cGUuZ2V0SW5zdGFuY2VzID0gZnVuY3Rpb24oKSB7XG4gIHJldHVybiB0aGlzLm1JbnN0YW5jZXM7XG59O1xuXG4vKipcbiAqIFJlbW92ZSBhbiBpbnN0YW5jZSBiYXNlZCBvbiBpdHMgaWRcbiAqIFxuICogQHBhcmFtIGlkXG4gKi9cbkxlYXJuZXIucHJvdG90eXBlLnJlbW92ZUluc3RhbmNlID0gZnVuY3Rpb24oaWQpIHtcbiAgaW5zdGFuY2VzID0gdGhpcy5tSW5zdGFuY2VzO1xuICB2YXIgY291bnQgPSBpbnN0YW5jZXMubGVuZ3RoO1xuICBmb3IgKHZhciBpID0gMDsgaSA8IGNvdW50OyBpKyspIHtcbiAgICB2YXIgaW5zdGFuY2UgPSBpbnN0YW5jZXNbaV07XG4gICAgaWYgKGlkID09PSBpbnN0YW5jZS5pZCkge1xuICAgICAgaW5zdGFuY2VzLnNwbGljZShpLCAxKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gIH1cbn07XG5cbi8qKlxuICogUmVtb3ZlIGFsbCB0aGUgaW5zdGFuY2VzIG9mIGEgY2F0ZWdvcnlcbiAqIFxuICogQHBhcmFtIG5hbWUgdGhlIGNhdGVnb3J5IG5hbWVcbiAqL1xuTGVhcm5lci5wcm90b3R5cGUucmVtb3ZlSW5zdGFuY2VzID0gZnVuY3Rpb24obmFtZSkge1xuICB2YXIgdG9EZWxldGUgPSBbXTtcbiAgdmFyIGluc3RhbmNlcyA9IHRoaXMubUluc3RhbmNlcztcbiAgdmFyIGNvdW50ID0gaW5zdGFuY2VzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBjb3VudDsgKytpKSB7XG4gICAgdmFyIGluc3RhbmNlID0gaW5zdGFuY2VzW2ldO1xuICAgIC8vIHRoZSBsYWJlbCBjYW4gYmUgbnVsbCwgYXMgc3BlY2lmaWVkIGluIEluc3RhbmNlXG4gICAgaWYgKChpbnN0YW5jZS5sYWJlbCA9PSBudWxsICYmIG5hbWUgPT0gbnVsbClcbiAgICAgICAgICB8fCAoaW5zdGFuY2UubGFiZWwgIT0gbnVsbCAmJiBpbnN0YW5jZS5sYWJlbCA9PT0gbmFtZSkpIHtcbiAgICAgIHRvRGVsZXRlLnB1c2goaSk7XG4gICAgfVxuICB9XG4gIGZvciAoaT10b0RlbGV0ZS5sZW5ndGgtMTsgaT49MDsgLS1pKSB7XG4gICAgaW5zdGFuY2VzLnNwbGljZSh0b0RlbGV0ZVtpXSwgMSk7XG4gIH1cbn07XG5cbkxlYXJuZXIucHJvdG90eXBlLmNsYXNzaWZ5ID0gZnVuY3Rpb24oc2VxdWVuY2VUeXBlLCBvcmllbnRhdGlvblR5cGUsIHZlY3Rvcikge1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBMZWFybmVyOyIsIi8qKlxuICogQW4gb3JpZW50ZWQgYm91bmRpbmcgYm94XG4gKi9cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24gT3JpZW50ZWRCb3VuZGluZ0JveChhbmdsZSwgY3gsIGN5LCB3LCBoKSB7XG4gIHRoaXMub3JpZW50YXRpb24gPSBhbmdsZTtcbiAgdGhpcy53aWR0aCA9IHc7XG4gIHRoaXMuaGVpZ2h0ID0gaDtcbiAgdGhpcy5jZW50ZXJYID0gY3g7XG4gIHRoaXMuY2VudGVyWSA9IGN5O1xuICB2YXIgcmF0aW8gPSB3IC8gaDtcbiAgdGhpcy5zcXVhcmVuZXNzID0gcmF0aW8gPiAxID8gKDEvcmF0aW8pIDogcmF0aW87XG59OyIsIi8qKlxuICogQSB0aW1lZCBwb2ludCBvZiBhIGdlc3R1cmUgc3Ryb2tlLiBNdWx0aXBsZSBwb2ludHMgZm9ybSBhIHN0cm9rZS5cbiAqL1xubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBQb2ludCh4LCB5LCB0KSB7XG4gIGlmICh4IGluc3RhbmNlb2YgT2JqZWN0KSB7XG4gICAgdmFyIG8gPSB4O1xuICAgIHRoaXMueCA9IG8ueDtcbiAgICB0aGlzLnkgPSBvLnk7XG4gICAgdGhpcy50aW1lc3RhbXAgPSBvLnQ7XG4gIH0gZWxzZSB7XG4gICAgdGhpcy54ID0geDtcbiAgICB0aGlzLnkgPSB5O1xuICAgIHRoaXMudGltZXN0YW1wID0gdDtcbiAgfVxufTtcbiIsImZ1bmN0aW9uIFByZWRpY3Rpb24obGFiZWwsIHByZWRpY3Rpb25TY29yZSkge1xuICB0aGlzLm5hbWUgPSBsYWJlbDtcbiAgdGhpcy5zY29yZSA9IHByZWRpY3Rpb25TY29yZTtcbn1cblxuUHJlZGljdGlvbi5wcm90b3R5cGUudG9TdHJpbmcgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHRoaXMubmFtZTtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gUHJlZGljdGlvbjtcbiIsIi8qKlxuICogUmVjdGFuZ2xlIG9iamVjdC5cbiAqL1xuZnVuY3Rpb24gUmVjdChsLCB0LCByLCBiKSB7XG4gIHRoaXMuc2V0KGwsIHQsIHIsIGIpO1xufVxuXG5SZWN0LnByb3RvdHlwZS5jbG9uZSA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gbmV3IFJlY3QodGhpcy5sZWZ0LCB0aGlzLnRvcCwgdGhpcy5yaWdodCwgdGhpcy5ib3R0b20pO1xufTtcblxuUmVjdC5wcm90b3R5cGUuY2VudGVyWCA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gKHRoaXMubGVmdCArIHRoaXMucmlnaHQpIC8gMjtcbn07XG5cblJlY3QucHJvdG90eXBlLmNlbnRlclkgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuICh0aGlzLnRvcCArIHRoaXMuYm90dG9tKSAvIDI7XG59O1xuXG5SZWN0LnByb3RvdHlwZS53aWR0aCA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gdGhpcy5yaWdodCAtIHRoaXMubGVmdDtcbn07XG5cblJlY3QucHJvdG90eXBlLmhlaWdodCA9IGZ1bmN0aW9uKCkge1xuICByZXR1cm4gdGhpcy5ib3R0b20gLSB0aGlzLnRvcDtcbn07XG5cblJlY3QucHJvdG90eXBlLnNldCA9IGZ1bmN0aW9uKGwsIHQsIHIsIGIpIHtcbiAgdGhpcy50b3AgPSB0O1xuICB0aGlzLmxlZnQgPSBsO1xuICB0aGlzLmJvdHRvbSA9IGI7XG4gIHRoaXMucmlnaHQgPSByO1xufTtcblxuUmVjdC5wcm90b3R5cGUudW5pb25Qb2ludCA9IGZ1bmN0aW9uKHgsIHkpIHtcbiAgaWYgKHggPCB0aGlzLmxlZnQpIHRoaXMubGVmdCA9IHg7XG4gIGlmICh4ID4gdGhpcy5yaWdodCkgdGhpcy5yaWdodCA9IHg7XG4gIGlmICh5IDwgdGhpcy50b3ApIHRoaXMudG9wID0geTtcbiAgaWYgKHkgPiB0aGlzLmJvdHRvbSkgdGhpcy5ib3R0b20gPSB5O1xufTtcblxuUmVjdC5wcm90b3R5cGUudW5pb24gPSBmdW5jdGlvbihyKSB7XG4gIGlmIChyLmxlZnQgPCB0aGlzLmxlZnQpIHRoaXMubGVmdCA9IHIubGVmdDtcbiAgaWYgKHIucmlnaHQgPiB0aGlzLnJpZ2h0KSB0aGlzLnJpZ2h0ID0gci5yaWdodDtcbiAgaWYgKHIudG9wIDwgdGhpcy50b3ApIHRoaXMudG9wID0gci50b3A7XG4gIGlmIChyLmJvdHRvbSA+IHRoaXMuYm90dG9tKSB0aGlzLmJvdHRvbSA9IHIuYm90dG9tO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBSZWN0O1xuIiwidmFyIFJlY3QgPSByZXF1aXJlKCcuL1JlY3QnKTtcbnZhciBQb2ludCA9IHJlcXVpcmUoJy4vUG9pbnQnKTtcblxuLyoqXG4gKiBBIGdlc3R1cmUgc3Ryb2tlIHN0YXJ0ZWQgb24gYSB0b3VjaCBkb3duIGFuZCBlbmRlZCBvbiBhIHRvdWNoIHVwLiBBIHN0cm9rZVxuICogY29uc2lzdHMgb2YgYSBzZXF1ZW5jZSBvZiB0aW1lZCBwb2ludHMuIE9uZSBvciBtdWx0aXBsZSBzdHJva2VzIGZvcm0gYSBnZXN0dXJlLlxuICovXG5mdW5jdGlvbiBTdHJva2UocG9pbnRzKSB7XG4gIGlmIChwb2ludHMgPT0gbnVsbCkgcmV0dXJuO1xuXG4gIHZhciBjb3VudCA9IHBvaW50cy5sZW5ndGg7XG4gIHZhciB0bXBQb2ludHMgPSBBcnJheShjb3VudCoyKTtcbiAgdmFyIHRpbWVzID0gQXJyYXkoY291bnQpO1xuICB2YXIgYnggPSBudWxsO1xuICB2YXIgbGVuID0gMDtcbiAgdmFyIGluZGV4ID0gMDtcbiAgXG4gIGZvciAodmFyIGk9MDsgaTxjb3VudDsgKytpKSB7XG4gICAgdmFyIHAgPSBwb2ludHNbaV07XG4gICAgdG1wUG9pbnRzW2kqMl0gPSBwLng7XG4gICAgdG1wUG9pbnRzW2kqMisxXSA9IHAueTtcbiAgICB0aW1lc1tpbmRleF0gPSBwLnRpbWVzdGFtcDtcblxuICAgIGlmIChieCA9PSBudWxsKSB7XG4gICAgICBieCA9IG5ldyBSZWN0KHAueCwgcC55LCBwLngsIHAueSk7XG4gICAgICBsZW4gPSAwO1xuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgZHggPSBwLnggLSB0bXBQb2ludHNbKGkgLSAxKSAqIDJdO1xuICAgICAgdmFyIGR5ID0gcC55IC0gdG1wUG9pbnRzWyhpIC0xKSAqIDIgKyAxXTtcbiAgICAgIGxlbiArPSBNYXRoLnNxcnQoZHgqZHggKyBkeSpkeSk7XG4gICAgICBieC51bmlvblBvaW50KHAueCwgcC55KTtcbiAgICB9XG4gICAgaW5kZXgrKztcbiAgfVxuICAgIFxuICB0aGlzLnRpbWVzdGFtcHMgPSB0aW1lcztcbiAgdGhpcy5wb2ludHMgPSB0bXBQb2ludHM7XG4gIHRoaXMuYm91bmRpbmdCb3ggPSBieDtcbiAgdGhpcy5sZW5ndGggPSBsZW47XG59XG5cblN0cm9rZS5wcm90b3R5cGUuY2xvbmUgPSBmdW5jdGlvbigpIHtcbiAgdmFyIHN0cm9rZSA9IG5ldyBTdHJva2UoKTtcbiAgc3Ryb2tlLmJvdW5kaW5nQm94ID0gdGhpcy5ib3VuZGluZ0JveC5jbG9uZSgpO1xuICBzdHJva2UubGVuZ3RoID0gdGhpcy5sZW5ndGg7XG4gIHN0cm9rZS5wb2ludHMgPSB0aGlzLnBvaW50cy5zbGljZSgpO1xuICBzdHJva2UudGltZXN0YW1wcyA9IHRoaXMudGltZXN0YW1wcy5zbGljZSgpO1xuICByZXR1cm4gc3Ryb2tlO1xufTtcblxuLy8gLS0tXG5cblN0cm9rZS5wcm90b3R5cGUudG9KU09OID0gZnVuY3Rpb24oKSB7XG4gIHZhciBwb2ludHMgPSBbXTtcbiAgdmFyIGNvdW50ID0gdGhpcy5wb2ludHMubGVuZ3RoO1xuICBmb3IgKHZhciBpPTA7IGk8Y291bnQ7IGkrPTIpIHtcbiAgICBwb2ludHMucHVzaCh7XG4gICAgICB4OiB0aGlzLnBvaW50c1tpXSxcbiAgICAgIHk6IHRoaXMucG9pbnRzW2krMV0sXG4gICAgICB0OiB0aGlzLnRpbWVzdGFtcHNbaT4+MV1cbiAgICB9KTtcbiAgfVxuICByZXR1cm4gcG9pbnRzO1xufTtcblxuU3Ryb2tlLmZyb21KU09OID0gZnVuY3Rpb24oanNvbikge1xuICB2YXIgcG9pbnRzID0gW107XG4gIGZvciAodmFyIGk9MDsgaTxqc29uLmxlbmd0aDsgKytpKSB7XG4gICAgcG9pbnRzLnB1c2gobmV3IFBvaW50KGpzb25baV0pKTtcbiAgfVxuICByZXR1cm4gbmV3IFN0cm9rZShwb2ludHMpO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBTdHJva2U7XG4iLCJ2YXIgT3JpZW50ZWRCb3VuZGluZ0JveCA9IHJlcXVpcmUoJy4vT3JpZW50ZWRCb3VuZGluZ0JveCcpO1xuXG4vKipcbiAqIFV0aWxpdHkgZnVuY3Rpb25zIGZvciBnZXN0dXJlIHByb2Nlc3NpbmcgJiBhbmFseXNpcywgaW5jbHVkaW5nIG1ldGhvZHMgZm9yOlxuICogPHVsPiBcbiAqIDxsaT5mZWF0dXJlIGV4dHJhY3Rpb24gKGUuZy4sIHNhbXBsZXJzIGFuZCB0aG9zZSBmb3IgY2FsY3VsYXRpbmcgYm91bmRpbmdcbiAqIGJveGVzIGFuZCBnZXN0dXJlIHBhdGggbGVuZ3Rocyk7XG4gKiA8bGk+Z2VvbWV0cmljIHRyYW5zZm9ybWF0aW9uIChlLmcuLCB0cmFuc2xhdGlvbiwgcm90YXRpb24gYW5kIHNjYWxpbmcpO1xuICogPGxpPmdlc3R1cmUgc2ltaWxhcml0eSBjb21wYXJpc29uIChlLmcuLCBjYWxjdWxhdGluZyBFdWNsaWRlYW4gb3IgQ29zaW5lXG4gKiBkaXN0YW5jZXMgYmV0d2VlbiB0d28gZ2VzdHVyZXMpLlxuICogPC91bD5cbiAqL1xudmFyIFV0aWxzID0ge307XG5cblV0aWxzLlNDQUxJTkdfVEhSRVNIT0xEID0gMC4yNjtcblV0aWxzLk5PTlVOSUZPUk1fU0NBTEUgPSBNYXRoLnNxcnQoMik7XG5cblV0aWxzLnplcm9lcyA9IGZ1bmN0aW9uKG4pIHtcbiAgdmFyIGFycmF5ID0gQXJyYXkobik7XG4gIGZvciAodmFyIGk9MDsgaTxuOyArK2kpIGFycmF5W2ldID0gMDtcbiAgcmV0dXJuIGFycmF5O1xufVxuXG4vKipcbiAqIFNhbXBsZXMgdGhlIGdlc3R1cmUgc3BhdGlhbGx5IGJ5IHJlbmRlcmluZyB0aGUgZ2VzdHVyZSBpbnRvIGEgMkQgXG4gKiBncmF5c2NhbGUgYml0bWFwLiBTY2FsZXMgdGhlIGdlc3R1cmUgdG8gZml0IHRoZSBzaXplIG9mIHRoZSBiaXRtYXAuIFxuICogXG4gKiBAcGFyYW0gZ2VzdHVyZSB0aGUgZ2VzdHVyZSB0byBiZSBzYW1wbGVkXG4gKiBAcGFyYW0gYml0bWFwU2l6ZSB0aGUgc2l6ZSBvZiB0aGUgYml0bWFwXG4gKiBAcGFyYW0ga2VlcEFzcGVjdFJhdGlvIGlmIHRoZSBzY2FsaW5nIHNob3VsZCBrZWVwIHRoZSBnZXN0dXJlJ3MgXG4gKiAgICAgICAgYXNwZWN0IHJhdGlvXG4gKiBcbiAqIEByZXR1cm4gYSBiaXRtYXBTaXplIHggYml0bWFwU2l6ZSBncmF5c2NhbGUgYml0bWFwIHRoYXQgaXMgcmVwcmVzZW50ZWQgXG4gKiAgICAgICAgIGFzIGEgMUQgYXJyYXkuIFRoZSBmbG9hdCBhdCBpbmRleCBpIHJlcHJlc2VudHMgdGhlIGdyYXlzY2FsZSBcbiAqICAgICAgICAgdmFsdWUgYXQgcGl4ZWwgW2klYml0bWFwU2l6ZSwgaS9iaXRtYXBTaXplXSBcbiAqL1xuVXRpbHMuc3BhdGlhbFNhbXBsaW5nID0gZnVuY3Rpb24oZ2VzdHVyZSwgYml0bWFwU2l6ZSwga2VlcEFzcGVjdFJhdGlvKSB7XG4gIHZhciB0YXJnZXRQYXRjaFNpemUgPSBiaXRtYXBTaXplIC0gMTtcbiAgdmFyIHNhbXBsZSA9IFV0aWxzLnplcm9lcyhiaXRtYXBTaXplICogYml0bWFwU2l6ZSk7XG4gIHZhciByZWN0ID0gZ2VzdHVyZS5nZXRCb3VuZGluZ0JveCgpO1xuICB2YXIgZ2VzdHVyZVdpZHRoID0gcmVjdC53aWR0aCgpO1xuICB2YXIgZ2VzdHVyZUhlaWdodCA9IHJlY3QuaGVpZ2h0KCk7XG4gIHZhciBzeCA9IHRhcmdldFBhdGNoU2l6ZSAvIGdlc3R1cmVXaWR0aDtcbiAgdmFyIHN5ID0gdGFyZ2V0UGF0Y2hTaXplIC8gZ2VzdHVyZUhlaWdodDtcbiAgdmFyIHNjYWxlO1xuXG4gIGlmIChrZWVwQXNwZWN0UmF0aW8pIHtcbiAgICBzY2FsZSA9IHN4IDwgc3kgPyBzeCA6IHN5O1xuICAgIHN4ID0gc2NhbGU7XG4gICAgc3kgPSBzY2FsZTtcbiAgfSBlbHNlIHtcbiAgICB2YXIgYXNwZWN0UmF0aW8gPSBnZXN0dXJlV2lkdGggLyBnZXN0dXJlSGVpZ2h0O1xuICAgIGlmIChhc3BlY3RSYXRpbyA+IDEpIHtcbiAgICAgIGFzcGVjdFJhdGlvbiA9IDEgLyBhc3BlY3RSYXRpbztcbiAgICB9XG4gICAgaWYgKGFzcGVjdFJhdGlvIDwgVXRpbHMuU0NBTElOR19USFJFU0hPTEQpIHtcbiAgICAgIHNjYWxlID0gc3ggPCBzeSA/IHN4IDogc3k7XG4gICAgICBzeCA9IHNjYWxlO1xuICAgICAgc3kgPSBzY2FsZTtcbiAgICB9IGVsc2Uge1xuICAgICAgaWYgKHN4ID4gc3kpIHtcbiAgICAgICAgc2NhbGUgPSBzeSAqIFV0aWxzLk5PTlVOSUZPUk1fU0NBTEU7XG4gICAgICAgIGlmIChzY2FsZSA8IHN4KSB7IHN4ID0gc2NhbGU7IH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHNjYWxlID0gc3ggKiBVdGlscy5OT05VTklGT1JNX1NDQUxFOyBcbiAgICAgICAgaWYgKHNjYWxlIDwgc3kpIHsgc3kgPSBzY2FsZTsgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHZhciBwcmVEeCA9IC1yZWN0LmNlbnRlclgoKTtcbiAgdmFyIHByZUR5ID0gLXJlY3QuY2VudGVyWSgpO1xuICB2YXIgcG9zdER4ID0gdGFyZ2V0UGF0Y2hTaXplIC8gMjtcbiAgdmFyIHBvc3REeSA9IHRhcmdldFBhdGNoU2l6ZSAvIDI7XG4gIHZhciBzdHJva2VzID0gZ2VzdHVyZS5nZXRTdHJva2VzKCk7XG4gIHZhciBjb3VudCA9IHN0cm9rZXMubGVuZ3RoO1xuICB2YXIgc2l6ZTsgLy8gaW50XG4gIHZhciB4cG9zO1xuICB2YXIgeXBvcztcblxuICBmb3IgKHZhciBpbmRleD0wOyBpbmRleCA8IGNvdW50OyBpbmRleCsrKSB7XG4gICAgdmFyIHN0cm9rZSA9IHN0cm9rZXNbaW5kZXhdO1xuICAgIHZhciBzdHJva2Vwb2ludHMgPSBzdHJva2UucG9pbnRzO1xuICAgIHNpemUgPSBzdHJva2Vwb2ludHMubGVuZ3RoO1xuICAgIHZhciBwdHMgPSBBcnJheShzaXplKTtcbiAgICBmb3IgKHZhciBpPTA7IGkgPCBzaXplOyBpICs9IDIpIHtcbiAgICAgIHB0c1tpXSA9IChzdHJva2Vwb2ludHNbaV0gKyBwcmVEeCkgKiBzeCArIHBvc3REeDtcbiAgICAgIHB0c1tpICsgMV0gPSAoc3Ryb2tlcG9pbnRzW2kgKyAxXSArIHByZUR5KSAqIHN5ICsgcG9zdER5O1xuICAgIH1cbiAgICB2YXIgc2VnbWVudEVuZFggPSAtMTtcbiAgICB2YXIgc2VnbWVudEVuZFkgPSAtMTtcbiAgICBmb3IgKHZhciBpPTA7IGkgPCBzaXplOyBpICs9IDIpIHtcbiAgICAgIHZhciBzZWdtZW50U3RhcnRYID0gcHRzW2ldIDwgMCA/IDAgOiBwdHNbaV07XG4gICAgICB2YXIgc2VnbWVudFN0YXJ0WSA9IHB0c1tpICsgMV0gPCAwID8gMCA6IHB0c1tpICsgMV07XG4gICAgICBpZiAoc2VnbWVudFN0YXJ0WCA+IHRhcmdldFBhdGNoU2l6ZSkge1xuICAgICAgICBzZWdtZW50U3RhcnRYID0gdGFyZ2V0UGF0Y2hTaXplO1xuICAgICAgfSBcbiAgICAgIGlmIChzZWdtZW50U3RhcnRZID4gdGFyZ2V0UGF0Y2hTaXplKSB7XG4gICAgICAgIHNlZ21lbnRTdGFydFkgPSB0YXJnZXRQYXRjaFNpemU7XG4gICAgICB9XG4gICAgICBVdGlscy5wbG90KHNlZ21lbnRTdGFydFgsIHNlZ21lbnRTdGFydFksIHNhbXBsZSwgYml0bWFwU2l6ZSk7XG4gICAgICBpZiAoc2VnbWVudEVuZFggIT0gLTEpIHtcbiAgICAgICAgLy8gRXZhbHVhdGUgaG9yaXpvbnRhbGx5XG4gICAgICAgIGlmIChzZWdtZW50RW5kWCA+IHNlZ21lbnRTdGFydFgpIHtcbiAgICAgICAgICB4cG9zID0gTWF0aC5jZWlsKHNlZ21lbnRTdGFydFgpO1xuICAgICAgICAgIHZhciBzbG9wZSA9IChzZWdtZW50RW5kWSAtIHNlZ21lbnRTdGFydFkpIC8gKHNlZ21lbnRFbmRYIC0gc2VnbWVudFN0YXJ0WCk7XG4gICAgICAgICAgd2hpbGUgKHhwb3MgPCBzZWdtZW50RW5kWCkge1xuICAgICAgICAgICAgeXBvcyA9IHNsb3BlICogKHhwb3MgLSBzZWdtZW50U3RhcnRYKSArIHNlZ21lbnRTdGFydFk7XG4gICAgICAgICAgICBwbG90KHhwb3MsIHlwb3MsIHNhbXBsZSwgYml0bWFwU2l6ZSk7IFxuICAgICAgICAgICAgeHBvcysrO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChzZWdtZW50RW5kWCA8IHNlZ21lbnRTdGFydFgpIHtcbiAgICAgICAgICB4cG9zID0gTWF0aC5jZWlsKHNlZ21lbnRFbmRYKTtcbiAgICAgICAgICB2YXIgc2xvcGUgPSAoc2VnbWVudEVuZFkgLSBzZWdtZW50U3RhcnRZKSAvIChzZWdtZW50RW5kWCAtIHNlZ21lbnRTdGFydFgpO1xuICAgICAgICAgIHdoaWxlICh4cG9zIDwgc2VnbWVudFN0YXJ0WCkge1xuICAgICAgICAgICAgICB5cG9zID0gc2xvcGUgKiAoeHBvcyAtIHNlZ21lbnRTdGFydFgpICsgc2VnbWVudFN0YXJ0WTtcbiAgICAgICAgICAgICAgcGxvdCh4cG9zLCB5cG9zLCBzYW1wbGUsIGJpdG1hcFNpemUpOyBcbiAgICAgICAgICAgICAgeHBvcysrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyBFdmFsdWF0ZSB2ZXJ0aWNhbGx5XG4gICAgICAgIGlmIChzZWdtZW50RW5kWSA+IHNlZ21lbnRTdGFydFkpIHtcbiAgICAgICAgICB5cG9zID0gTWF0aC5jZWlsKHNlZ21lbnRTdGFydFkpO1xuICAgICAgICAgIHZhciBpbnZlcnRTbG9wZSA9IChzZWdtZW50RW5kWCAtIHNlZ21lbnRTdGFydFgpIC8gKHNlZ21lbnRFbmRZIC0gc2VnbWVudFN0YXJ0WSk7XG4gICAgICAgICAgd2hpbGUgKHlwb3MgPCBzZWdtZW50RW5kWSkge1xuICAgICAgICAgICAgICB4cG9zID0gaW52ZXJ0U2xvcGUgKiAoeXBvcyAtIHNlZ21lbnRTdGFydFkpICsgc2VnbWVudFN0YXJ0WDtcbiAgICAgICAgICAgICAgcGxvdCh4cG9zLCB5cG9zLCBzYW1wbGUsIGJpdG1hcFNpemUpOyBcbiAgICAgICAgICAgICAgeXBvcysrO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChzZWdtZW50RW5kWSA8IHNlZ21lbnRTdGFydFkpIHtcbiAgICAgICAgICB5cG9zID0gTWF0aC5jZWlsKHNlZ21lbnRFbmRZKTtcbiAgICAgICAgICB2YXIgaW52ZXJ0U2xvcGUgPSAoc2VnbWVudEVuZFggLSBzZWdtZW50U3RhcnRYKSAvIChzZWdtZW50RW5kWSAtIHNlZ21lbnRTdGFydFkpO1xuICAgICAgICAgIHdoaWxlICh5cG9zIDwgc2VnbWVudFN0YXJ0WSkge1xuICAgICAgICAgICAgeHBvcyA9IGludmVydFNsb3BlICogKHlwb3MgLSBzZWdtZW50U3RhcnRZKSArIHNlZ21lbnRTdGFydFg7IFxuICAgICAgICAgICAgcGxvdCh4cG9zLCB5cG9zLCBzYW1wbGUsIGJpdG1hcFNpemUpOyBcbiAgICAgICAgICAgIHlwb3MrKztcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHNlZ21lbnRFbmRYID0gc2VnbWVudFN0YXJ0WDtcbiAgICAgIHNlZ21lbnRFbmRZID0gc2VnbWVudFN0YXJ0WTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHNhbXBsZTtcbn07XG5cblV0aWxzLnBsb3QgPSBmdW5jdGlvbih4LCB5LCBzYW1wbGUsIHNhbXBsZVNpemUpIHtcbiAgeCA9IHggPCAwID8gMCA6IHg7XG4gIHkgPSB5IDwgMCA/IDAgOiB5O1xuICB2YXIgeEZsb29yID0gTWF0aC5mbG9vcih4KTtcbiAgdmFyIHhDZWlsaW5nID0gTWF0aC5jZWlsKHgpO1xuICB2YXIgeUZsb29yID0gTWF0aC5mbG9vcih5KTtcbiAgdmFyIHlDZWlsaW5nID0gTWF0aC5jZWlsKHkpO1xuICBcbiAgLy8gaWYgaXQncyBhbiBpbnRlZ2VyXG4gIGlmICh4ID09PSB4Rmxvb3IgJiYgeSA9PT0geUZsb29yKSB7XG4gICAgdmFyIGluZGV4ID0geUNlaWxpbmcgKiBzYW1wbGVTaXplICsgeENlaWxpbmc7XG4gICAgaWYgKHNhbXBsZVtpbmRleF0gPCAxKSB7XG4gICAgICBzYW1wbGVbaW5kZXhdID0gMTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgdmFyIHhGbG9vclNxID0gTWF0aC5wb3coeEZsb29yIC0geCwgMik7XG4gICAgdmFyIHlGbG9vclNxID0gTWF0aC5wb3coeUZsb29yIC0geSwgMik7XG4gICAgdmFyIHhDZWlsaW5nU3EgPSBNYXRoLnBvdyh4Q2VpbGluZyAtIHgsIDIpO1xuICAgIHZhciB5Q2VpbGluZ1NxID0gTWF0aC5wb3coeUNlaWxpbmcgLSB5LCAyKTtcbiAgICB2YXIgdG9wTGVmdCA9IE1hdGguc3FydCh4Rmxvb3JTcSArIHlGbG9vclNxKTtcbiAgICB2YXIgdG9wUmlnaHQgPSBNYXRoLnNxcnQoeENlaWxpbmdTcSArIHlGbG9vclNxKTtcbiAgICB2YXIgYnRtTGVmdCA9IE1hdGguc3FydCh4Rmxvb3JTcSArIHlDZWlsaW5nU3EpO1xuICAgIHZhciBidG1SaWdodCA9IE1hdGguc3FydCh4Q2VpbGluZ1NxICsgeUNlaWxpbmdTcSk7XG4gICAgdmFyIHN1bSA9IHRvcExlZnQgKyB0b3BSaWdodCArIGJ0bUxlZnQgKyBidG1SaWdodDtcbiAgICBcbiAgICB2YXIgdmFsdWUgPSB0b3BMZWZ0IC8gc3VtO1xuICAgIHZhciBpbmRleCA9IHlGbG9vciAqIHNhbXBsZVNpemUgKyB4Rmxvb3I7XG4gICAgaWYgKHZhbHVlID4gc2FtcGxlW2luZGV4XSkge1xuICAgICAgc2FtcGxlW2luZGV4XSA9IHZhbHVlO1xuICAgIH1cbiAgICBcbiAgICB2YWx1ZSA9IHRvcFJpZ2h0IC8gc3VtO1xuICAgIGluZGV4ID0geUZsb29yICogc2FtcGxlU2l6ZSArIHhDZWlsaW5nO1xuICAgIGlmICh2YWx1ZSA+IHNhbXBsZVtpbmRleF0pIHtcbiAgICAgIHNhbXBsZVtpbmRleF0gPSB2YWx1ZTtcbiAgICB9XG4gICAgXG4gICAgdmFsdWUgPSBidG1MZWZ0IC8gc3VtO1xuICAgIGluZGV4ID0geUNlaWxpbmcgKiBzYW1wbGVTaXplICsgeEZsb29yO1xuICAgIGlmICh2YWx1ZSA+IHNhbXBsZVtpbmRleF0pIHtcbiAgICAgIHNhbXBsZVtpbmRleF0gPSB2YWx1ZTtcbiAgICB9XG4gICAgXG4gICAgdmFsdWUgPSBidG1SaWdodCAvIHN1bTtcbiAgICBpbmRleCA9IHlDZWlsaW5nICogc2FtcGxlU2l6ZSArIHhDZWlsaW5nO1xuICAgIGlmICh2YWx1ZSA+IHNhbXBsZVtpbmRleF0pIHtcbiAgICAgIHNhbXBsZVtpbmRleF0gPSB2YWx1ZTtcbiAgICB9XG4gIH1cbn07XG5cbi8qKlxuICogU2FtcGxlcyBhIHN0cm9rZSB0ZW1wb3JhbGx5IGludG8gYSBnaXZlbiBudW1iZXIgb2YgZXZlbmx5LWRpc3RyaWJ1dGVkIFxuICogcG9pbnRzLlxuICogXG4gKiBAcGFyYW0gc3Ryb2tlIHRoZSBnZXN0dXJlIHN0cm9rZSB0byBiZSBzYW1wbGVkXG4gKiBAcGFyYW0gbnVtUG9pbnRzIHRoZSBudW1iZXIgb2YgcG9pbnRzXG4gKiBAcmV0dXJuIHRoZSBzYW1wbGVkIHBvaW50cyBpbiB0aGUgZm9ybSBvZiBbeDEsIHkxLCB4MiwgeTIsIC4uLiwgeG4sIHluXVxuICovXG5VdGlscy50ZW1wb3JhbFNhbXBsaW5nID0gZnVuY3Rpb24oc3Ryb2tlLCBudW1Qb2ludHMpIHtcbiAgdmFyIGluY3JlbWVudCA9IHN0cm9rZS5sZW5ndGggLyAobnVtUG9pbnRzIC0gMSk7XG4gIHZhciB2ZWN0b3JMZW5ndGggPSBudW1Qb2ludHMgKiAyO1xuICB2YXIgdmVjdG9yID0gQXJyYXkodmVjdG9yTGVuZ3RoKTtcbiAgdmFyIGRpc3RhbmNlU29GYXIgPSAwO1xuICB2YXIgcHRzID0gc3Ryb2tlLnBvaW50cztcbiAgdmFyIGxzdFBvaW50WCA9IHB0c1swXTtcbiAgdmFyIGxzdFBvaW50WSA9IHB0c1sxXTtcbiAgdmFyIGluZGV4ID0gMDtcbiAgdmFyIGN1cnJlbnRQb2ludFggPSBOdW1iZXIuTUlOX1ZBTFVFO1xuICB2YXIgY3VycmVudFBvaW50WSA9IE51bWJlci5NSU5fVkFMVUU7XG4gIHZlY3RvcltpbmRleF0gPSBsc3RQb2ludFg7XG4gIGluZGV4Kys7XG4gIHZlY3RvcltpbmRleF0gPSBsc3RQb2ludFk7XG4gIGluZGV4Kys7XG4gIHZhciBpID0gMDtcbiAgdmFyIGNvdW50ID0gcHRzLmxlbmd0aCAvIDI7XG4gIHdoaWxlIChpIDwgY291bnQpIHtcbiAgICBpZiAoY3VycmVudFBvaW50WCA9PSBOdW1iZXIuTUlOX1ZBTFVFKSB7XG4gICAgICBpKys7XG4gICAgICBpZiAoaSA+PSBjb3VudCkge1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICAgIGN1cnJlbnRQb2ludFggPSBwdHNbaSAqIDJdO1xuICAgICAgY3VycmVudFBvaW50WSA9IHB0c1tpICogMiArIDFdO1xuICAgIH1cbiAgICB2YXIgZGVsdGFYID0gY3VycmVudFBvaW50WCAtIGxzdFBvaW50WDtcbiAgICB2YXIgZGVsdGFZID0gY3VycmVudFBvaW50WSAtIGxzdFBvaW50WTtcbiAgICB2YXIgZGlzdGFuY2UgPSBNYXRoLnNxcnQoZGVsdGFYKmRlbHRhWCArIGRlbHRhWSpkZWx0YVkpO1xuICAgIGlmIChkaXN0YW5jZVNvRmFyICsgZGlzdGFuY2UgPj0gaW5jcmVtZW50KSB7XG4gICAgICB2YXIgcmF0aW8gPSAoaW5jcmVtZW50IC0gZGlzdGFuY2VTb0ZhcikgLyBkaXN0YW5jZTtcbiAgICAgIHZhciBueCA9IGxzdFBvaW50WCArIHJhdGlvICogZGVsdGFYO1xuICAgICAgdmFyIG55ID0gbHN0UG9pbnRZICsgcmF0aW8gKiBkZWx0YVk7XG4gICAgICB2ZWN0b3JbaW5kZXhdID0gbng7XG4gICAgICBpbmRleCsrO1xuICAgICAgdmVjdG9yW2luZGV4XSA9IG55O1xuICAgICAgaW5kZXgrKztcbiAgICAgIGxzdFBvaW50WCA9IG54O1xuICAgICAgbHN0UG9pbnRZID0gbnk7XG4gICAgICBkaXN0YW5jZVNvRmFyID0gMDtcbiAgICB9IGVsc2Uge1xuICAgICAgbHN0UG9pbnRYID0gY3VycmVudFBvaW50WDtcbiAgICAgIGxzdFBvaW50WSA9IGN1cnJlbnRQb2ludFk7XG4gICAgICBjdXJyZW50UG9pbnRYID0gTnVtYmVyLk1JTl9WQUxVRTtcbiAgICAgIGN1cnJlbnRQb2ludFkgPSBOdW1iZXIuTUlOX1ZBTFVFO1xuICAgICAgZGlzdGFuY2VTb0ZhciArPSBkaXN0YW5jZTtcbiAgICB9XG4gIH1cblxuICBmb3IgKGkgPSBpbmRleDsgaSA8IHZlY3Rvckxlbmd0aDsgaSArPSAyKSB7XG4gICAgdmVjdG9yW2ldID0gbHN0UG9pbnRYO1xuICAgIHZlY3RvcltpICsgMV0gPSBsc3RQb2ludFk7XG4gIH1cbiAgcmV0dXJuIHZlY3Rvcjtcbn07XG5cbi8qKlxuICogQ2FsY3VsYXRlcyB0aGUgY2VudHJvaWQgb2YgYSBzZXQgb2YgcG9pbnRzLlxuICogXG4gKiBAcGFyYW0gcG9pbnRzIHRoZSBwb2ludHMgaW4gdGhlIGZvcm0gb2YgW3gxLCB5MSwgeDIsIHkyLCAuLi4sIHhuLCB5bl1cbiAqIEByZXR1cm4gdGhlIGNlbnRyb2lkXG4gKi9cblV0aWxzLmNvbXB1dGVDZW50cm9pZCA9IGZ1bmN0aW9uKHBvaW50cykge1xuICB2YXIgY2VudGVyWCA9IDA7XG4gIHZhciBjZW50ZXJZID0gMDtcbiAgdmFyIGNvdW50ID0gcG9pbnRzLmxlbmd0aDtcbiAgZm9yICh2YXIgaT0wOyBpPGNvdW50OyArK2kpIHtcbiAgICBjZW50ZXJYICs9IHBvaW50c1tpXTtcbiAgICBpKys7XG4gICAgY2VudGVyWSArPSBwb2ludHNbaV07XG4gIH1cbiAgcmV0dXJuIFtcbiAgICAyICogY2VudGVyWCAvIGNvdW50LFxuICAgIDIgKiBjZW50ZXJZIC8gY291bnRcbiAgXTtcbn07XG5cbi8qKlxuICogQ2FsY3VsYXRlcyB0aGUgdmFyaWFuY2UtY292YXJpYW5jZSBtYXRyaXggb2YgYSBzZXQgb2YgcG9pbnRzLlxuICogXG4gKiBAcGFyYW0gcG9pbnRzIHRoZSBwb2ludHMgaW4gdGhlIGZvcm0gb2YgW3gxLCB5MSwgeDIsIHkyLCAuLi4sIHhuLCB5bl1cbiAqIEByZXR1cm4gdGhlIHZhcmlhbmNlLWNvdmFyaWFuY2UgbWF0cml4XG4gKi9cblV0aWxzLmNvbXB1dGVDb1ZhcmlhbmNlID0gZnVuY3Rpb24ocG9pbnRzKSB7XG4gIHZhciBhcnJheSA9IFtbMCwwXSwgWzAsMF1dO1xuICB2YXIgY291bnQgPSBwb2ludHMubGVuZ3RoO1xuICBmb3IgKHZhciBpPTA7IGk8Y291bnQ7ICsraSkge1xuICAgIHZhciB4ID0gcG9pbnRzW2ldO1xuICAgIGkrKztcbiAgICB2YXIgeSA9IHBvaW50c1tpXTtcbiAgICBhcnJheVswXVswXSArPSB4ICogeDtcbiAgICBhcnJheVswXVsxXSArPSB4ICogeTtcbiAgICBhcnJheVsxXVswXSA9IGFycmF5WzBdWzFdO1xuICAgIGFycmF5WzFdWzFdICs9IHkgKiB5O1xuICB9XG4gIGFycmF5WzBdWzBdIC89IChjb3VudCAvIDIpO1xuICBhcnJheVswXVsxXSAvPSAoY291bnQgLyAyKTtcbiAgYXJyYXlbMV1bMF0gLz0gKGNvdW50IC8gMik7XG4gIGFycmF5WzFdWzFdIC89IChjb3VudCAvIDIpO1xuICByZXR1cm4gYXJyYXk7XG59O1xuXG5VdGlscy5jb21wdXRlVG90YWxMZW5ndGggPSBmdW5jdGlvbihwb2ludHMpIHtcbiAgdmFyIHN1bSA9IDA7XG4gIHZhciBjb3VudCA9IHBvaW50cy5sZW5ndGggLSA0O1xuICBmb3IgKHZhciBpPTA7IGk8Y291bnQ7IGkrPTIpIHtcbiAgICAgIHZhciBkeCA9IHBvaW50c1tpICsgMl0gLSBwb2ludHNbaV07XG4gICAgICB2YXIgZHkgPSBwb2ludHNbaSArIDNdIC0gcG9pbnRzW2kgKyAxXTtcbiAgICAgIHN1bSArPSBNYXRoLnNxcnQoZHgqZHggKyBkeSpkeSk7XG4gIH1cbiAgcmV0dXJuIHN1bTtcbn07XG5cblV0aWxzLmNvbXB1dGVTdHJhaWdodG5lc3MgPSBmdW5jdGlvbihwb2ludHMpIHtcbiAgdmFyIHRvdGFsTGVuID0gVXRpbHMuY29tcHV0ZVRvdGFsTGVuZ3RoKHBvaW50cyk7XG4gIHZhciBkeCA9IHBvaW50c1syXSAtIHBvaW50c1swXTtcbiAgdmFyIGR5ID0gcG9pbnRzWzNdIC0gcG9pbnRzWzFdO1xuICByZXR1cm4gTWF0aC5zcXJ0KGR4KmR4ICsgZHkqZHkpIC8gdG90YWxMZW47XG59O1xuXG5VdGlscy5jb21wdXRlU3RyYWlnaHRuZXNzID0gZnVuY3Rpb24ocG9pbnRzLCB0b3RhbExlbikge1xuICB2YXIgZHggPSBwb2ludHNbMl0gLSBwb2ludHNbMF07XG4gIHZhciBkeSA9IHBvaW50c1szXSAtIHBvaW50c1sxXTtcbiAgcmV0dXJuIE1hdGguc3FydChkeCpkeCArIGR5KmR5KSAvIHRvdGFsTGVuO1xufTtcblxuLyoqXG4gKiBDYWxjdWxhdGVzIHRoZSBzcXVhcmVkIEV1Y2xpZGVhbiBkaXN0YW5jZSBiZXR3ZWVuIHR3byB2ZWN0b3JzLlxuICogXG4gKiBAcGFyYW0gdmVjdG9yMVxuICogQHBhcmFtIHZlY3RvcjJcbiAqIEByZXR1cm4gdGhlIGRpc3RhbmNlXG4gKi9cblV0aWxzLnNxdWFyZWRFdWNsaWRlYW5EaXN0YW5jZSA9IGZ1bmN0aW9uKHZlY3RvcjEsIHZlY3RvcjIpIHtcbiAgdmFyIHNxdWFyZWREaXN0YW5jZSA9IDA7XG4gIHZhciBzaXplID0gdmVjdG9yMS5sZW5ndGg7XG4gIGZvciAodmFyIGk9MDsgaTxzaXplOyArK2kpIHtcbiAgICB2YXIgZGlmZmVyZW5jZSA9IHZlY3RvcjFbaV0gLSB2ZWN0b3IyW2ldO1xuICAgIHNxdWFyZWREaXN0YW5jZSArPSBkaWZmZXJlbmNlICogZGlmZmVyZW5jZTtcbiAgfVxuICByZXR1cm4gc3F1YXJlZERpc3RhbmNlIC8gc2l6ZTtcbn07XG5cbi8qKlxuICogQ2FsY3VsYXRlcyB0aGUgY29zaW5lIGRpc3RhbmNlIGJldHdlZW4gdHdvIGluc3RhbmNlcy5cbiAqIFxuICogQHBhcmFtIHZlY3RvcjFcbiAqIEBwYXJhbSB2ZWN0b3IyXG4gKiBAcmV0dXJuIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIDAgYW5kIE1hdGguUElcbiAqL1xuVXRpbHMuY29zaW5lRGlzdGFuY2UgPSBmdW5jdGlvbih2ZWN0b3IxLCB2ZWN0b3IyKSB7XG4gIHZhciBzdW0gPSAwO1xuICB2YXIgbGVuID0gdmVjdG9yMS5sZW5ndGg7XG4gIGZvciAodmFyIGk9MDsgaTxsZW47ICsraSkge1xuICAgIHN1bSArPSB2ZWN0b3IxW2ldICogdmVjdG9yMltpXTtcbiAgfVxuICByZXR1cm4gTWF0aC5hY29zKHN1bSk7XG59O1xuXG4vKipcbiAqIENhbGN1bGF0ZXMgdGhlIFwibWluaW11bVwiIGNvc2luZSBkaXN0YW5jZSBiZXR3ZWVuIHR3byBpbnN0YW5jZXMuXG4gKiBcbiAqIEBwYXJhbSB2ZWN0b3IxXG4gKiBAcGFyYW0gdmVjdG9yMlxuICogQHBhcmFtIG51bU9yaWVudGF0aW9ucyB0aGUgbWF4aW11bSBudW1iZXIgb2Ygb3JpZW50YXRpb24gYWxsb3dlZFxuICogQHJldHVybiB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdHdvIGluc3RhbmNlcyAoYmV0d2VlbiAwIGFuZCBNYXRoLlBJKVxuICovXG5VdGlscy5taW5pbXVtQ29zaW5lRGlzdGFuY2UgPSBmdW5jdGlvbih2ZWN0b3IxLCB2ZWN0b3IyLCBudW1PcmllbnRhdGlvbnMpIHtcbiAgdmFyIGxlbiA9IHZlY3RvcjEubGVuZ3RoO1xuICB2YXIgYSA9IDA7XG4gIHZhciBiID0gMDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBsZW47IGkgKz0gMikge1xuICAgIGEgKz0gdmVjdG9yMVtpXSAqIHZlY3RvcjJbaV0gKyB2ZWN0b3IxW2kgKyAxXSAqIHZlY3RvcjJbaSArIDFdO1xuICAgIGIgKz0gdmVjdG9yMVtpXSAqIHZlY3RvcjJbaSArIDFdIC0gdmVjdG9yMVtpICsgMV0gKiB2ZWN0b3IyW2ldO1xuICB9XG4gIGlmIChhICE9IDApIHtcbiAgICB2YXIgdGFuID0gYi9hO1xuICAgIHZhciBhbmdsZSA9IE1hdGguYXRhbih0YW4pO1xuICAgIGlmIChudW1PcmllbnRhdGlvbnMgPiAyICYmIE1hdGguYWJzKGFuZ2xlKSA+PSBNYXRoLlBJIC8gbnVtT3JpZW50YXRpb25zKSB7XG4gICAgICByZXR1cm4gTWF0aC5hY29zKGEpO1xuICAgIH0gZWxzZSB7XG4gICAgICB2YXIgY29zaW5lID0gTWF0aC5jb3MoYW5nbGUpO1xuICAgICAgdmFyIHNpbmUgPSBjb3NpbmUgKiB0YW47IFxuICAgICAgcmV0dXJuIE1hdGguYWNvcyhhICogY29zaW5lICsgYiAqIHNpbmUpO1xuICAgIH1cbiAgfSBlbHNlIHtcbiAgICByZXR1cm4gTWF0aC5QSSAvIDI7XG4gIH1cbn07XG5cbi8qKlxuICogQ29tcHV0ZXMgYW4gb3JpZW50ZWQsIG1pbmltdW0gYm91bmRpbmcgYm94IG9mIGEgc2V0IG9mIHBvaW50cy5cbiAqIFxuICogQHBhcmFtIG9yaWdpbmFsUG9pbnRzXG4gKiBAcmV0dXJuIGFuIG9yaWVudGVkIGJvdW5kaW5nIGJveFxuICovXG5VdGlscy5jb21wdXRlT3JpZW50ZWRCb3VuZGluZ0JveFBvaW50cyA9IGZ1bmN0aW9uKG9yaWdpbmFsUG9pbnRzKSB7XG4gIHZhciBjb3VudCA9IG9yaWdpbmFsUG9pbnRzLmxlbmd0aDtcbiAgdmFyIHBvaW50cyA9IEFycmF5KGNvdW50ICogMik7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgY291bnQ7IGkrKykge1xuICAgICAgdmFyIHBvaW50ID0gb3JpZ2luYWxQb2ludHNbaV07XG4gICAgICB2YXIgaW5kZXggPSBpICogMjtcbiAgICAgIHBvaW50c1tpbmRleF0gPSBwb2ludC54O1xuICAgICAgcG9pbnRzW2luZGV4ICsgMV0gPSBwb2ludC55O1xuICB9XG4gIHZhciBtZWFuVmVjdG9yID0gVXRpbHMuY29tcHV0ZUNlbnRyb2lkKHBvaW50cyk7XG4gIHJldHVybiBVdGlscy5jb21wdXRlT3JpZW50ZWRCb3VuZGluZ0JveEZ1bGwocG9pbnRzLCBtZWFuVmVjdG9yKTtcbn07XG5cbi8qKlxuICogQ29tcHV0ZXMgYW4gb3JpZW50ZWQsIG1pbmltdW0gYm91bmRpbmcgYm94IG9mIGEgc2V0IG9mIHBvaW50cy5cbiAqIFxuICogQHBhcmFtIG9yaWdpbmFsUG9pbnRzXG4gKiBAcmV0dXJuIGFuIG9yaWVudGVkIGJvdW5kaW5nIGJveFxuICovXG5VdGlscy5jb21wdXRlT3JpZW50ZWRCb3VuZGluZ0JveCA9IGZ1bmN0aW9uKG9yaWdpbmFsUG9pbnRzKSB7XG4gIHZhciBzaXplID0gb3JpZ2luYWxQb2ludHMubGVuZ3RoO1xuICB2YXIgcG9pbnRzID0gQXJyYXkoc2l6ZSk7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgc2l6ZTsgaSsrKSB7XG4gICAgcG9pbnRzW2ldID0gb3JpZ2luYWxQb2ludHNbaV07XG4gIH1cbiAgdmFyIG1lYW5WZWN0b3IgPSBVdGlscy5jb21wdXRlQ2VudHJvaWQocG9pbnRzKTtcbiAgcmV0dXJuIFV0aWxzLmNvbXB1dGVPcmllbnRlZEJvdW5kaW5nQm94RnVsbChwb2ludHMsIG1lYW5WZWN0b3IpO1xufTtcblxuVXRpbHMuY29tcHV0ZU9yaWVudGVkQm91bmRpbmdCb3hGdWxsID0gZnVuY3Rpb24ocG9pbnRzLCBjZW50cm9pZCkge1xuICBVdGlscy50cmFuc2xhdGUocG9pbnRzLCAtY2VudHJvaWRbMF0sIC1jZW50cm9pZFsxXSk7XG5cbiAgdmFyIGFycmF5ID0gVXRpbHMuY29tcHV0ZUNvVmFyaWFuY2UocG9pbnRzKTtcbiAgdmFyIHRhcmdldFZlY3RvciA9IFV0aWxzLmNvbXB1dGVPcmllbnRhdGlvbihhcnJheSk7XG5cbiAgdmFyIGFuZ2xlO1xuICBpZiAodGFyZ2V0VmVjdG9yWzBdID09IDAgJiYgdGFyZ2V0VmVjdG9yWzFdID09IDApIHtcbiAgICBhbmdsZSA9IC1NYXRoLlBJLzI7XG4gIH0gZWxzZSB7IC8vIC1QSTxhbHBoYTxQSVxuICAgIGFuZ2xlID0gTWF0aC5hdGFuMih0YXJnZXRWZWN0b3JbMV0sIHRhcmdldFZlY3RvclswXSk7XG4gICAgVXRpbHMucm90YXRlKHBvaW50cywgLWFuZ2xlKTtcbiAgfVxuXG4gIHZhciBtaW54ID0gTnVtYmVyLk1BWF9WQUxVRTtcbiAgdmFyIG1pbnkgPSBOdW1iZXIuTUFYX1ZBTFVFO1xuICB2YXIgbWF4eCA9IE51bWJlci5NSU5fVkFMVUU7XG4gIHZhciBtYXh5ID0gTnVtYmVyLk1JTl9WQUxVRTtcbiAgdmFyIGNvdW50ID0gcG9pbnRzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBjb3VudDsgaSsrKSB7XG4gICAgaWYgKHBvaW50c1tpXSA8IG1pbngpIHtcbiAgICAgIG1pbnggPSBwb2ludHNbaV07XG4gICAgfVxuICAgIGlmIChwb2ludHNbaV0gPiBtYXh4KSB7XG4gICAgICBtYXh4ID0gcG9pbnRzW2ldO1xuICAgIH1cbiAgICBpKys7XG4gICAgaWYgKHBvaW50c1tpXSA8IG1pbnkpIHtcbiAgICAgIG1pbnkgPSBwb2ludHNbaV07XG4gICAgfVxuICAgIGlmIChwb2ludHNbaV0gPiBtYXh5KSB7XG4gICAgICBtYXh5ID0gcG9pbnRzW2ldO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBuZXcgT3JpZW50ZWRCb3VuZGluZ0JveChcbiAgICAoYW5nbGUgKiAxODAgLyBNYXRoLlBJKSxcbiAgICBjZW50cm9pZFswXSxcbiAgICBjZW50cm9pZFsxXSxcbiAgICBtYXh4IC0gbWlueCxcbiAgICBtYXh5IC0gbWlueVxuICApO1xufTtcblxuVXRpbHMuY29tcHV0ZU9yaWVudGF0aW9uID0gZnVuY3Rpb24oY292YXJpYW5jZU1hdHJpeCkge1xuICB2YXIgdGFyZ2V0VmVjdG9yID0gWzAsIDBdO1xuICBpZiAoY292YXJpYW5jZU1hdHJpeFswXVsxXSA9PSAwIHx8IGNvdmFyaWFuY2VNYXRyaXhbMV1bMF0gPT0gMCkge1xuICAgIHRhcmdldFZlY3RvclswXSA9IDE7XG4gICAgdGFyZ2V0VmVjdG9yWzFdID0gMDtcbiAgfVxuXG4gIHZhciBhID0gLWNvdmFyaWFuY2VNYXRyaXhbMF1bMF0gLSBjb3ZhcmlhbmNlTWF0cml4WzFdWzFdO1xuICB2YXIgYiA9IGNvdmFyaWFuY2VNYXRyaXhbMF1bMF0gKiBjb3ZhcmlhbmNlTWF0cml4WzFdWzFdIC0gY292YXJpYW5jZU1hdHJpeFswXVsxXVxuICAgICAgICAqIGNvdmFyaWFuY2VNYXRyaXhbMV1bMF07XG4gIHZhciB2YWx1ZSA9IGEgLyAyO1xuICB2YXIgcmlnaHRzaWRlID0gTWF0aC5zcXJ0KE1hdGgucG93KHZhbHVlLCAyKSAtIGIpO1xuICB2YXIgbGFtYmRhMSA9IC12YWx1ZSArIHJpZ2h0c2lkZTtcbiAgdmFyIGxhbWJkYTIgPSAtdmFsdWUgLSByaWdodHNpZGU7XG4gIGlmIChsYW1iZGExID09IGxhbWJkYTIpIHtcbiAgICB0YXJnZXRWZWN0b3JbMF0gPSAwO1xuICAgIHRhcmdldFZlY3RvclsxXSA9IDA7XG4gIH0gZWxzZSB7XG4gICAgdmFyIGxhbWJkYSA9IGxhbWJkYTEgPiBsYW1iZGEyID8gbGFtYmRhMSA6IGxhbWJkYTI7XG4gICAgdGFyZ2V0VmVjdG9yWzBdID0gMTtcbiAgICB0YXJnZXRWZWN0b3JbMV0gPSAobGFtYmRhIC0gY292YXJpYW5jZU1hdHJpeFswXVswXSkgLyBjb3ZhcmlhbmNlTWF0cml4WzBdWzFdO1xuICB9XG4gIHJldHVybiB0YXJnZXRWZWN0b3I7XG59O1xuXG5VdGlscy5yb3RhdGUgPSBmdW5jdGlvbihwb2ludHMsIGFuZ2xlKSB7XG4gIHZhciBjb3MgPSBNYXRoLmNvcyhhbmdsZSk7XG4gIHZhciBzaW4gPSBNYXRoLnNpbihhbmdsZSk7XG4gIHZhciBzaXplID0gcG9pbnRzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzaXplOyBpICs9IDIpIHtcbiAgICB2YXIgeCA9IHBvaW50c1tpXSAqIGNvcyAtIHBvaW50c1tpICsgMV0gKiBzaW47XG4gICAgdmFyIHkgPSBwb2ludHNbaV0gKiBzaW4gKyBwb2ludHNbaSArIDFdICogY29zO1xuICAgIHBvaW50c1tpXSA9IHg7XG4gICAgcG9pbnRzW2kgKyAxXSA9IHk7XG4gIH1cbiAgcmV0dXJuIHBvaW50cztcbn07XG5cblV0aWxzLnRyYW5zbGF0ZSA9IGZ1bmN0aW9uKHBvaW50cywgZHgsIGR5KSB7XG4gIHZhciBzaXplID0gcG9pbnRzLmxlbmd0aDtcbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzaXplOyBpICs9IDIpIHtcbiAgICBwb2ludHNbaV0gKz0gZHg7XG4gICAgcG9pbnRzW2kgKyAxXSArPSBkeTtcbiAgfVxuICByZXR1cm4gcG9pbnRzO1xufTtcblxuVXRpbHMuc2NhbGUgPSBmdW5jdGlvbihwb2ludHMsIHN4LCBzeSkge1xuICB2YXIgc2l6ZSA9IHBvaW50cy5sZW5ndGg7XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgc2l6ZTsgaSArPSAyKSB7XG4gICAgcG9pbnRzW2ldICo9IHN4O1xuICAgIHBvaW50c1tpICsgMV0gKj0gc3k7XG4gIH1cbiAgcmV0dXJuIHBvaW50cztcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gVXRpbHM7Il19 1277 | --------------------------------------------------------------------------------