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

/**
 * Created by middleca on 6/18/15.
 */

var fs = require('fs');
var when = require('when');
var pipeline = require('when/pipeline');
var extend = require('xtend');
var util = require('util');
var utilities = require('./utilities.js');

var HalModuleParser = require('./HalModuleParser.js');
var FirmwareModule = require('./FirmwareModule');

/**
 * fancy module that can assimilate system modules and their version info.
 * It uses that combined with the describe payload from your device, and
 * the version info from a new binary to recursively resolve
 * any missing modules you might need to run that firmware.  **phew!**
 *
 * @constructor
 */
var HalDependencyResolver = function() {

};
HalDependencyResolver.prototype = {

	/*
	 * expects a fileBuffer and the JSON describe payload for the 'm'
	 * modules array
	 *
	 * @param Object describe
	 * @param Buffer fileBuffer
	 * @returns Promise
	 */
	parseAndResolve: function(describe, fileBuffer) {
		if (describe && !util.isArray(describe) && describe.m) {
			// those goofs, they sent the whole thing, instead of just the
			// modules section.
			describe = describe.m;
		}

		if (!Buffer.isBuffer(fileBuffer)) {
			return when.reject('fileBuffer was invalid');
		}

		var parser = new HalModuleParser();
		var fileInfo = {
			filename: 'user-file',
			fileBuffer: fileBuffer
		};

		var that = this;
		return pipeline([
			function() {
				return parser.parseBuffer(fileInfo);
			},
			function() {
				return that.resolveDependencies(describe, fileInfo);
			}
		]);
	},

	/*
	 *
	 * @param Object describe
	 * @param Object fileInfo
	 * @returns Promise
	 */
	resolveDependencies: function(describe, fileInfo) {
		if (!describe || !util.isArray(describe)) {
			return when.reject('wrong describe payload given');
		}

		if (!fileInfo) {
			return when.reject('no fileInfo provided');
		}

		console.log('resolving dependencies for ', fileInfo.filename);

		var that = this;
		return pipeline([
			function() {
				// find out what we need for the binary described by fileInfo
				return that.solve(describe, fileInfo.prefixInfo);
			},
			function(modules) {
				if (!modules || (modules.length <= 0)) {
					return when.resolve([]);		//no updates needed
				}

				// go get the module that we need if we have it.
				return that.retrieveModules(modules);
			},
			function(updates) {
				if (!updates || (updates.length <= 0)) {
					return when.resolve([]); 	//'no updates available'
				}

				// go figure out if the updates we found
				var promises = [];
				for (var i = 0; i < updates.length; i++) {
					var promise = that.resolveDependencies(describe, updates[i]);
					promises.push(promise);
				}

				return when.all(promises)
					.then(function(arr) {
						for (var j = 0; j < arr.length; j++) {
							var resvArr = arr[j];
							if (resvArr && (resvArr.length > 0)) {

								//NOTE: the photon isn't accepting updates in this order
								// part1, part2, user
								// insert these new dependencies at the start of the array
								updates = resvArr.concat(updates);

								// so lets flip that to
								// part2, part1, user
								//updates = updates.concat(resvArr);
							}
						}
						return when.resolve(updates);
					},
					function(err) {
						return when.resolve(updates);
					});
			}
		]);
	},

	/*
		so, lets take our typical path:

		user has old system modules, compiles new firmware.

		new firmware requires new system 2 module,

		we figure this out, and grab new module

		before we send that, we check this new module to see what it requires.

		turns out it requires a new system 1 module.

		we grab this new module, and before we send that, check again to see
		what it requires.

		it doesn't have dependencies, so we send:

		part1,
		part2,
		user-app

--> therefore, we need a step, post solve, that grabs our binary, and does
	a dependency check on it,
	how do we know to keep going?
		// if we suggest a file,
		// we need to be able to grab that file and re-process it.
	  */

	/*
	 * go read in the file, parse the data for it, convert its precious
	 * contents to JSON.
	 *
	 * @param String filename
	 * @returns Boolean
	 */
	assimilateModule: function(filename) {
		if (!fs.existsSync(filename)) {
			return false;
		}

		if (!this._moduleStorage) {
			this._moduleStorage = [];
		}

		var that = this;
		var parser = new HalModuleParser();
		return parser.parseFile(filename)
			.then(
			function(fileInfo) {
				fileInfo.describe = that._binaryToDescribe(fileInfo.prefixInfo);

				that._moduleStorage.push(fileInfo);
			},
			function(err) {
				console.error('assimilateModule err: ', err);
			});
	},

	/*
	 * expects the array of module descriptions back from the solve routine
	 * and returns amodule description result complete with fileBuffer
	 * @param Array modules
	 * @returns Object
	 */
	retrieveModules: function(modules) {
		// expects that we've been given storage of all our available
		// modules, and their binary info.
		if (!this._moduleStorage) {
			return null;
		}

		if (!modules || (modules.length <= 0)) {
			return null;
		}

		var results = [];

		// expecting something like...
		// { f: "s", n: "2", v: 2 }

		//iterate over our requirements
		for (var i = 0; i < modules.length; i++) {
			var m = modules[i];

			//iterate over what's available
			for (var a = 0; a < this._moduleStorage.length; a++) {
				var module = this._moduleStorage[a];
				var avail = module.describe;	//grab the converted bits

				var isMatch = ((avail.n === m.n)
					&& (avail.f === m.f)
					&& (avail.v >= m.v));

				//console.log('comparing ', m, avail);

				if (isMatch) {
					results.push(module);
					break;
				}
			}
		}
		return when.resolve(results);
	},

	/*
	 * given what modules are 'described' as being on a photon,
	 * and target binary info, figure out which modules need to be updated
	 * on that photon.
	 *
	 * @param deviceModules
	 * @param binaryInfo
	 */
	solve: function(deviceModules, binaryInfo) {
		var safeDeviceModules = this._repairDescribeErrors(deviceModules);
		var safeBinaryRequires = this._binaryDepsToDescribe(binaryInfo);
		var safeBinaryRequires2 = this._binaryDepsToDescribe(binaryInfo, 2);

		var result =  this._walkChain(safeDeviceModules, safeBinaryRequires);
		this._walkChain(safeDeviceModules, safeBinaryRequires2, result);
		return result;
	},

	/*
	 * given what modules are 'described' as being on a device,
	 * and known firmware module info, figure out which modules need
	 * to be updated on the device.
	 *
	 * @param deviceModules
	 * @param firmwareModule
	 */
	solveFirmwareModule: function(deviceModules, firmwareModule) {
		var safeDeviceModules = this._repairDescribeErrors(deviceModules);
		var safeModuleRequires = firmwareModule.toDescribe();

		return this._walkChain(safeDeviceModules, safeModuleRequires);
	},

	/*
	 * given device's describe message figure out if it has
	 * missing modules. Rejected value contains missing
	 * dependencies
	 *
	 * @param Object describe - describe message
	 * @param Promise
	 */
	userModuleHasMissingDependencies: function(describe) {
		if (!Array.isArray(describe.m)) {
			return when.reject('no modules in describe message');
		}

		var modules = [];
		var userModule = null;
		for (var i = 0; i < describe.m.length; i++) {
			var module = new FirmwareModule(describe.m[i]);
			//modules.push(module);

			if (module.isUserModule() && module.isMainLocation()) {
				userModule = describe.m[i];
			}
		}

		if (!userModule) {
			return when.resolve("no user module");
		}

		//return this._getModuleFirstDependecy(modules, userModule);

		for(var i=0;i<userModule.d.length;i++) {
			var dep = userModule.d[i];

			var deps = this._walkChain(describe.m, dep);
			if (deps && (deps.length > 0)) {
				// this function only originally returned one dependency.
				return when.reject([new FirmwareModule(deps[0])]);
			}
		}

//		if (deps && (deps.length > 0)) {
//			return when.reject(new FirmwareModule(deps[0]));
//		}
//		else {
			return when.resolve("nothing missing");
//		}
	},

	/**
	 * tell us if anything with dependencies is missing something
	 *
	 * (this covers scenarios where user app needs something else, or user app has no deps, and part 1 is old, but
	 * part2 is current, etc.)  Built this because I hit this during testing.
	 *
	 * in what order should we apply updates?
	 * is it possible for us to get conflicting / out of order update recommendations?!
	 *
	 * @param describe
	 */
	findAnyMissingDependencies: function(describe) {
		if (!Array.isArray(describe.m)) {
			return when.reject('no modules in describe message');
		}

		var allDeps = [];
		var modules = describe.m;

		for(var i=0;i<modules.length;i++) {
			var checkModule = modules[i];

			// don't look for dependencies of things that don't have dependencies.
			// they'll never cause safe mode as a result of their requirements,
			// and they're probably referenced by other things

			for(var d = 0; d < checkModule.d.length; d++) {
				var moduleNeeds = checkModule.d[d];

				// what things do we need that we don't have?
				var deps = this._walkChain(modules, moduleNeeds);
				if (deps && (deps.length > 0)) {
					allDeps = allDeps.concat(deps);
				}
			}
		}

		var keyFn = function(dep) {
			// todo - location should also be taken into account
			return [dep.f, dep.n, dep.v].join('_');
		};
		return utilities.dedupArray(allDeps, keyFn);
	},

	/*
	 * walk over the described modules until we've resolved our
	 * dependency chain for all the dependent things
	 * that need updating.
	 *
	 * (typically system module 1, and 2)
	 *
	 * @param modules - what was in the describe message (the modules on the device)
	 * @param needs - what was described in the binary header info - the dependencies being checked
	 * @param [arr] - our accumulator array
	 * @returns {*|Array}
	 * @private
	 */
	_walkChain: function(modules, needs, arr) {
		arr = arr || [];

		// todo - this assumes the entire set of dependencies exists on the device (i.e. a module with the same n/f values)
		// but this is not the case, e.g. when upgrading from a 2 module system to 3 module system.
		for (var i = 0; i < modules.length; i++) {
			var m = modules[i];

			if (m.n !== needs.n) {
				continue;
			}
			if (m.f !== needs.f) {
				continue;
			}

			//found one!
			if (m.v < needs.v) {
				//
				// it's...
				//	  .---.       .-''-.     .-'''-.    .-'''-.     ,---------. .---.  .---.    ____    ,---.   .--.
				//	  | ,_|     .'_ _   \   / _     \  / _     \    \          \|   |  |_ _|  .'  __ `. |    \  |  |
				//	,-./  )    / ( ` )   ' (`' )/`--' (`' )/`--'     `--.  ,---'|   |  ( ' ) /   '  \  \|  ,  \ |  |
				//	\  '_ '`) . (_ o _)  |(_ o _).   (_ o _).           |   \   |   '-(_{;}_)|___|  /  ||  |\_ \|  |
				//	 > (_)  ) |  (_,_)___| (_,_). '.  (_,_). '.         :_ _:   |      (_,_)    _.-`   ||  _( )_\  |
				//	(  .  .-' '  \   .---..---.  \  :.---.  \  :        (_I_)   | _ _--.   | .'   _    || (_ o _)  |
				//	 `-'`-'|___\  `-'    /\    `-'  |\    `-'  |       (_(=)_)  |( ' ) |   | |  _( )_  ||  (_,_)\  |
				//	  |        \\       /  \       /  \       /         (_I_)   (_{;}_)|   | \ (_ o _) /|  |    |  |
				//	  `--------` `'-..-'    `-...-'    `-...-'          '---'   '(_,_) '---'  '.(_,_).' '--'    '--'
				//

				//arr.push(m);

				// instead of returning the module we found, lets return the module with the version we need,
				// and any dependencies it requires, I think that's more clear.

				var missing = extend(m, { v: needs.v });
				arr.push(missing);
				// todo mdm - this is wrong. we shouldn't be fabricating dependencies for a new version of a module
				// (need) from the installed version (m) - dependencies can and do change between versions.
				// The database of known modules should be the definitive source of module dependencies.

				// if we're updating this, we better check its dependencies too.
				if (m.d && (m.d.length > 0)) {
					//oh no!  this module has dependencies!
					// do we have to update those too?
					// (this doesn't fully make sense to me right now, but lets go with it.)
					// todo mdm - this won't do anything, since `m` is always a satisfied dependency
					// (it came from the modules array.)
					// at the very least we should be iterating over m.d[] as the needed modules
					arr = this._walkChain(modules, m, arr);
				}
			}
		}

		return arr;
	},

	/*
	 * for modules in 'location:main'
	 * not for user modules 'function:user'
	 *
	 * for modules with 'function:system' or 'function:bootloader'
	 *
	 * fix the array so that 'n' is ascending '0', '1', '2' ...
	 * set any missing version to 'version:0'
	 * set missing function to 'function:system'
	 *
	 *
	 * @param describeInfo
	 */
	_repairDescribeErrors: function(describeInfo) {
		var arr = describeInfo;

		// we're assuming the modules are in the reported order,
		// which should be dependency order.

		// essentially we're looking to add this to the correct module:
		//	f: 's', n: '1', v: 1,

		for (var i = 0; i < arr.length; i++) {
			var item = arr[i];

			if (item.l !== 'm') {
				//not stored in main
				continue;
			} else if (item.f === 'u') {
				//I'm a user module, bail
				break;
			}

			// are we the first thing and we don't have a name?
			if ((i === 0) && (!item.n)) {
				item.n = i + '';
			}

			//skip the first one.
			if (i === 0) {
				continue;
			}

			// i is at least 1
			var lastItem = arr[i - 1];
			//var nextOne = ((i+1) < arr.length) ? arr[i+1] : null;

			if (lastItem.n && !item.n) {
				//last one had a name, and I don't have a name.
				item.n = (parseInt(lastItem.n) + 1) + '';

				if (!item.f) {
					// was missing a function designation
					item.f = 's';
				}
				if (!item.v) {
					// was missing a version number
					item.v = 0;
				}
			}
		}
		return describeInfo;
	},

	/*
	 * the binary prefix contains a numeric value for its dependent
	 * module function convert that number into the same characters
	 * reported by the photon (s, u, b, etc)
	 *
	 * @param moduleFunction
	 * @returns {*}
	 * @private
	 */
	_modFuncToChar: function(moduleFunction) {
		var result;

		//var moduleFunctions = [ 'system' | 'user' | 'boot' | 'res' | 'mono' ];

		switch (moduleFunction) {
			case 0:
				result = null;	// undefined / none?
				break;
			case 1:
				result = '1_unknown';
				break;
			case 2:
				result = 'b';	// bootloader?
				break;
			case 3:
				result = '3_unknown';		//monolithic?
				break;
			case 4:
				result = 's';	// system?
				break;
			case 5:
				result = 'u';	// user?
				break;

			default:
				result = 'undefined';
				break;
		}
		return result;
	},

	/*
	 * convert the 'dependency' keys from the binary prefix info into
	 * the common JSON
	 * format used by the describe messages.
	 *
	 * @private
	 */
	_binaryDepsToDescribe: function(binaryInfo, dep) {
		var result = {};
		dep = dep || 1;
		var depString = '';
		if (dep>1) {
			depString = ''+dep;
		}
		if (!binaryInfo) {
			return result;
		}

		var keys = Object.keys(binaryInfo);

		// iterate over the prefix info, looking for the keys we need.
		for (var i = 0; i < keys.length; i++) {
			var key = keys[i];
			var value = binaryInfo[key];

			switch (key) {
				case 'dep'+depString+'ModuleFunction':
					result.f = this._modFuncToChar(value);
					break;

				case 'dep'+depString+'ModuleIndex':
					result.n = value + '';
					break;

				case 'dep'+depString+'ModuleVersion':
					result.v = value;
					break;
			}
		}

		return result;
	},

	/*
	 * convert the binary function/name/index/version keys from the binary
	 * prefix info into the common JSON format used by the describe messages.
	 *
	 * NOTE: this is separate so as to not conflict with the dependency
	 * conversion above.
	 *
	 * @private
	 * @param Object binaryInfo
	 * @returns Object
	 */
	_binaryToDescribe: function(binaryInfo) {
		if (!binaryInfo) {
			return null;
		}
//		else if (typeof binaryInfo !== 'object') {
//			console.log('HERE', binaryInfo);
//		}

		var result = {};
		var keys = Object.keys(binaryInfo);

		// iterate over the prefix info, looking for the keys we need.
		for (var i = 0; i < keys.length; i++) {
			var key = keys[i];
			var value = binaryInfo[key];

			switch (key) {
				case 'moduleFunction':
					result.f = this._modFuncToChar(value);
					break;

				case 'moduleIndex':
					result.n = value + '';
					break;

				case 'moduleVersion':
					result.v = value;
					break;
			}
		}

		return result;
	},

	_: null
};
module.exports = HalDependencyResolver;


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

/**
 * Created by middleca on 6/11/15.
 */

module.exports = {
	/*
	 * TODO: replace with "buffer.compare" once we move to node 0.12
	 * @param Buffer left
	 * @param Buffer right
	 * @returns Boolean
	 */
	bufferCompare: function(left, right) {
		if ((left === null) && (right === null)) {
			return true;
		} else if ((left === null) || (right === null)) {
			return false;
		}

		if (!Buffer.isBuffer(left)) {
			left = new Buffer(left);
		}
		if (!Buffer.isBuffer(right)) {
			right = new Buffer(right);
		}

		var same = (left.length === right.length),
			i = 0,
			max = left.length;

		while (i < max) {
			same &= (left[i] == right[i]); //eslint-disable-line eqeqeq
			i++;
		}

		return same;
	},

	dedupArray: function(arr, keyFn) {
		if (!keyFn) {
			return arr;
		}

		var results = [];
		var hash = {};

		for(var i=0;i<arr.length;i++) {
			var dep = arr[i];
			var key = keyFn(dep);

			if (hash[key]) {
				continue;
			}
			hash[key] = true;

			results.push(dep);
		}
		return results;
	},

	_: null
};


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

/**
 * Created by middleca on 6/11/15.
 */

var fs = require('fs');
var when = require('when');
var pipeline = require('when/pipeline');
var crc32 = require('buffer-crc32');
var utilities = require('./utilities.js');
var buffers = require('h5.buffers');

//var BufferStream = require('./BufferStream.js');

//Buffer is global

/**
 * Understands how to parse out the header and footer metadata sections
 * of the 0.4.0 and later hal firmware for the
 * core/photon/electron / etc
 * @constructor
 */
var HalModuleParser = function() {

};
HalModuleParser.prototype = {

	/*
	 * given a file / callback, goes and reads out all the info!
	 *
	 * meant to be multiply-re-entrant / thread safe
	 *
	 * @param filename
	 * @param callback
	 * @returns {*}
	 */
	parseFile: function(filename, callback) {
		var fileInfo = {
			filename: filename
		};

		var that = this;
		var allDone = pipeline([
			function() {
				return that._loadFile(filename);
			},
			function(fileBuffer) {
				fileInfo.fileBuffer = fileBuffer;

				return that.parseBuffer(fileInfo);
			}
		]);

		if (callback) {
			when(allDone).then(
				function(info) {
					callback(info, null);
				},
				function(err) {
					callback(null, err);
				});
		}

		return allDone;
	},

	/*
	 * broken out if you're working with a buffer instead, expects a
	 * 'fileInfo' object with the
	 * 'fileBuffer' property set to your buffer.
	 * @param Object fileInfo
	 * @param Function callback
	 * @returns Promise
	 */
	parseBuffer: function(fileInfo, callback) {
		if (!Buffer.isBuffer(fileInfo.fileBuffer)) {
			return when.reject('fileBuffer was invalid');
		}

		var that = this;
		var allDone = pipeline([
			function() {
				return that._validateCRC(fileInfo.fileBuffer);
			},
			function(crcInfo) {
				fileInfo.crc = crcInfo;

				return that._readPrefix(fileInfo.fileBuffer);
			},

			function(prefixInfo) {
				fileInfo.prefixInfo = prefixInfo;

				return that._readSuffix(fileInfo.fileBuffer);
			},
			function(suffixInfo) {
				fileInfo.suffixInfo = suffixInfo;

				return when.resolve();
			},

			function() {
				return fileInfo;
			}
		]);

		if (callback) {
			when(allDone).then(
				function(info) {
					callback(info, null);
				},
				function(err) {
					callback(null, err);
				});
		}

		return allDone;
	},

	/*
	 * goes and reads out the file if it exists and returns
	 * a promise with the fileBuffer
	 * @param String filename
	 * @returns Promise
	 * @private
	 */
	_loadFile: function(filename) {
		if (!fs.existsSync(filename)) {
			return when.reject(filename + ' doesn\'t exist');
		}

		var fileBuffer = fs.readFileSync(filename);
		if (!fileBuffer || (fileBuffer.length === 0)) {
			return when.reject(filename + ' was empty!');
		}

		return when.resolve(fileBuffer);
	},

	/*
	 * calculates the CRC of the buffer, and compares it to the
	 * stored CRC region of the file
	 * @param Object fileBuffer
	 * @returns Boolean
	 * @private
	 */
	_validateCRC: function(fileBuffer) {
		if (!fileBuffer || (fileBuffer.length === 0)) {
			//console.log('validateCRC: buffer was empty!');
			return false;
		}

		var dataRegion = fileBuffer.slice(0, fileBuffer.length - 4);
		var storedCrcValue = fileBuffer.slice(fileBuffer.length - 4, fileBuffer.length);

		var crcResult = crc32(dataRegion);
		var matching = utilities.bufferCompare(storedCrcValue, crcResult);
		//var matching = Buffer.compare(storedCrcValue, crcResult);

		var result = {
			//ok: (matching === 0),
			ok: matching,
			storedCrc: storedCrcValue.toString('hex'),
			actualCrc: crcResult.toString('hex')
		};

		//console.log('calculated crc was ' + result.actualCrc);

		return when.resolve(result);
	},

	/*
	 * tries to determine where we should be looking in the file.
	 *
	 * This changes based on whether or not we're in the bootloader,
	 * and we attempt to validate the result based on the assumption
	 * that firmware addresses tend to start with hex '80' since
	 * they're stored after
	 * 0x0800xxxx, etc.
	 *
	 * @param fileBuffer
	 * @returns {number}
	 * @private
	 */
	_divineModulePrefixOffset: function(fileBuffer) {
		// try no offset
		var r = new buffers.BufferReader(fileBuffer);
		var userModuleStartAddy = r.shiftUInt32(true).toString(16);

		// start over
		r = new buffers.BufferReader(fileBuffer);
		// skip 0x184 for system modules to skip the vector table
		r.skip(388);

		var sysModuleStartAddy = r.shiftUInt32(true).toString(16);

		// start over, test for Mesh modular firmware which has a different offset than Photon/Electron modular firmware
		r = new buffers.BufferReader(fileBuffer);
		// skip 0x200 for Mesh firmware
		r.skip(0x200);
		var meshModule = this._parsePrefix(r);

		// start over, test for Core monolithic firmware which has a different offset than modular firmware
		r = new buffers.BufferReader(fileBuffer);
		// skip 0x10C for Core firmware
		r.skip(0x10C);

		var coreModuleStartAddy = r.shiftUInt32(true).toString(16);

		// start over, test for bluz system part which has a different offset than Photon
		r = new buffers.BufferReader(fileBuffer);
		// skip 0xc0 for bluz system modules to skip the vector table
		r.skip(192);
		var bluzModuleStartAddy = r.shiftUInt32(true);
		// also check for the platform ID since the address is pretty nebulous to check for
		r = new buffers.BufferReader(fileBuffer);
		r.skip(192+12);
		var bluzModulesPlatformID = r.shiftUInt16(true);

		//just system modules have the offset at the beginning.
		// system module addresses always tend to start at 0x2xxxxxxx
		// while user modules tend to start around 0x8xxxxxxx

		// but any valid address right now should start with 80... something, since they're small / in the realm
		// of the bootloader....  we'll need some extra sanity checks somehow later if hardware changes dramatically.
		var mightBeUser = (userModuleStartAddy.indexOf("80") === 0) || (userModuleStartAddy === 'd4000');
		var mightBeSystem = (sysModuleStartAddy.indexOf("80") === 0);
		var isCore = ((userModuleStartAddy.indexOf("20") === 0) && (coreModuleStartAddy === "8005000"));
		var mightBeBluz = (bluzModulesPlatformID == 103 && bluzModuleStartAddy < 262144);
		var isMesh = (meshModule.platformID >= 12 && meshModule.platformID <= 30) && (meshModule.moduleStartAddy === '30000' || meshModule.moduleStartAddy === 'f4000') &&
			(userModuleStartAddy.indexOf("20") == 0);   // stack is located in ram at 0x2000000

		if (isCore) {
			return 0x10C;
		}
		else if (!mightBeUser && mightBeSystem) {
			return 388;
		} else if (mightBeUser && !mightBeSystem) {
			return 0;
		} else if (mightBeBluz) {
			return 192;
		} else if (isMesh) {
			return 0x200;
		} else {
			return 0;
		}
	},

	/*
	 * parses out the prefix area of the binary, after attempting
	 * to determine the correct offset. Returns:
	 * {
	 *	moduleStartAddy: string,
	 *	moduleEndAddy: string,
	 *	moduleVersion: number,
	 *	platformID: number,
	 *	moduleFunction: number,
	 *	moduleIndex: number,
	 *	depModuleFunction: number,
	 *	depModuleIndex: number,
	 *	depModuleVersion: number
	 * }
	 * @param fileBuffer
	 * @returns {{moduleStartAddy: string, moduleEndAddy: string, moduleVersion: number, platformID: number, moduleFunction: number, moduleIndex: number, depModuleFunction: number, depModuleIndex: number, depModuleVersion: number}}
	 * @private
	 */
	_readPrefix: function(fileBuffer) {
		var prefixOffset = this._divineModulePrefixOffset(fileBuffer);
		var r = new buffers.BufferReader(fileBuffer);

		// skip to system module offset, or stay at user module no offset
		r.skip(prefixOffset);

		return this._parsePrefix(r);
	},

	_parsePrefix: function(r) {
		var isLittleEndian = true;
		//offset 0, 4-bytes: module start address
		var moduleStartAddy = r.shiftUInt32(isLittleEndian).toString(16);

		//offset 4, 4-bytes: module end address
		var moduleEndAddy = r.shiftUInt32(isLittleEndian).toString(16);

		r.skip(2);

		// offset 10, 2-bytes: module version (this is not the same as
		// product version, it relates to the module export functions)
		var moduleVersion = r.shiftUInt16(isLittleEndian);

		// offset 12, 2-bytes: Platform ID (6 for Photon)
		var platformID = r.shiftUInt16(isLittleEndian);

		// offset 14, 1-byte: module function (5 for user firmware)
		var moduleFunction = r.shiftUInt8();

		// offset 15, 1-byte: module index (1 for user firmware)
		var moduleIndex = r.shiftUInt8();

		// offset 16, 1-byte: dependency module function (usually system, 4)
		var depModuleFunction = r.shiftUInt8(isLittleEndian);

		// offset 17, 1-byte: dependency module index (usually 2, so
		// dependency is system-part2)
		var depModuleIndex = r.shiftUInt8(isLittleEndian);

		// offset 18, 1-byte: minimum version of system dependency
		var depModuleVersion = r.shiftUInt16(isLittleEndian);

		// offset 20, 1-byte: dependency module function (usually system, 4)
		var dep2ModuleFunction = r.shiftUInt8(isLittleEndian);

		// offset 21, 1-byte: dependency module index (usually 2, so
		// dependency is system-part2)
		var dep2ModuleIndex = r.shiftUInt8(isLittleEndian);

		// offset 22, 2-byte: minimum version of system dependency
		var dep2ModuleVersion = r.shiftUInt16(isLittleEndian);

		return {
			moduleStartAddy: moduleStartAddy,
			moduleEndAddy: moduleEndAddy,
			moduleVersion: moduleVersion,
			platformID: platformID,
			moduleFunction: moduleFunction,
			moduleIndex: moduleIndex,
			depModuleFunction: depModuleFunction,
			depModuleIndex: depModuleIndex,
			depModuleVersion: depModuleVersion,
			dep2ModuleFunction: dep2ModuleFunction,
			dep2ModuleIndex: dep2ModuleIndex,
			dep2ModuleVersion: dep2ModuleVersion
		};
	},

	/*
	 * parses out the suffix area of the binary, only reads back
	 * into the file as far
	 * as is provided by the 'suffixSize' value. Returns:
	 * {
	 *	productId: (*|Number),
	 *	productVersion: (*|Number),
	 *	fwUniqueId: (*|String),
	 *	reserved: (*|Number),
	 *	suffixSize: (*|Number),
	 *	crcBlock: (*|String)
	 * }
	 *
	 * @param fileBuffer
	 * @returns Object
	 * @private
	 */
	_readSuffix: function(fileBuffer) {
		// last 4 bytes of the file are the crc
		// 2 bytes before that is suffix payload size

		//lets read the suffix backwards.

		var idx = fileBuffer.length - 4;
		var crcBlock = fileBuffer.slice(idx, idx + 4).toString('hex');

		idx -= 2;
		var suffixSize = fileBuffer.readUInt16LE(idx);

		idx -= 32;
		var fwUniqueId = fileBuffer.slice(idx, idx + 32).toString('hex');

		idx -= 2;
		var reserved = fileBuffer.readUInt16LE(idx);

		idx -= 2;
		var productVersion = fileBuffer.readUInt16LE(idx);

		idx -= 2;
		var productId = fileBuffer.readUInt16LE(idx);

		if (reserved === 0) {
			//cool!
		}

		if (suffixSize < 40) {
			productId = -1;
			productVersion = -1;
		}

		return {
			productId: productId,
			productVersion: productVersion,
			fwUniqueId: fwUniqueId,
			reserved: reserved,
			suffixSize: suffixSize,
			crcBlock: crcBlock
		};
	},

	_: null
};
module.exports = HalModuleParser;


/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const {EOFError, readTo} = require('promised-read');

// Same error text as highline
const EOF_MESSAGE = 'The input stream is exhausted.';

/** Creates a new instance of Shortline which can be used to prompt users
 * for input.
 *
 * This class is intended to be similar to {@link http://highline.rubyforge.org
 * Highline}, although it is currently missing nearly all of its functionality.
 *
 * @constructor
 * @param {{
 *  input: stream.Readable|undefined,
 *  output: stream.Writable|undefined
 * }=} options Options to control the input source and output destination.
 */
function Shortline(options) {
  const self = this;

  self._input = (options && options.input) || process.stdin;
  self._output = (options && options.output) || process.stderr;

  /** Most recent error emitted by the input stream.
   * @type {Error}
   */
  self.inputError = null;
  self._input.on('end', () => {
    self.inputError = new EOFError(EOF_MESSAGE);
  });
  // Note:  Can't listen for 'error' since it changes behavior if there are no
  // other listeners.  Listen for it only when reading from input (since that
  // is our error and will be returned to the caller).
}

/** Options for {@link Shortline#ask}.
 *
 * @ template ReturnType
 * @typedef {{
 *  convert: ((function(string): ReturnType)|undefined),
 *  default: string|undefined,
 *  responses: {
 *    notValid: string|undefined
 *  }|undefined,
 *  trim: boolean|undefined,
 *  validate: RegExp|undefined
 * }} ShortlineAskOptions
 * @property {(function(string): ReturnType)=} convert Type conversion used to
 * create the return value from the trimmed and validated user input.
 * @property {string=} default Default value used in place of empty user input.
 * @property {{notValid: string|undefined}} responses Responses to various user
 * input.  notValid is printed if the input does not validate.
 * @property {boolean=} trim Right-trim user input?
 * @property {RegExp=} Prompt repeatedly until the trimmed user input matches a
 * given RegExp.
 */
// var ShortlineAskOptions;

/** Asks the user a "yes or no" question.
 *
 * @param {string} question Question to ask the user.
 * @param {ShortlineAskOptions=} options Options.
 * @return {!Promise<ReturnType>} Promise with result of
 * options.convert applied to the user-entered text, or Error.
 * @private
 */
Shortline.prototype.agree = function agree(question, options) {
  options = Object.assign({
    convert: function agreeToBoolean(answer) {
      return answer.charAt(0).toLowerCase() === 'y';
    },
    validate: /^y(?:es)?|no?$/i
  }, options);
  options.responses = Object.assign({
    notValid: 'Please enter "yes" or "no".'
  }, options.responses);
  return this.ask(question, options);
};

/** Asks the user to provide input.
 *
 * @ template ReturnType
 * @param {string} question Question to ask the user.
 * @param {ShortlineAskOptions=} options Options.
 * @return {!Promise<ReturnType>} Promise with result of
 * options.convert applied to the user-entered text, or Error.
 * @private
 */
Shortline.prototype.ask = function ask(question, options) {
  const self = this;
  options = options || {};

  let fullQuestion = question;
  if (options.default) {
    fullQuestion = question.replace(
      /\s*$/,
      (padding) => ` |${options.default}|${padding || ' '}`
    );
  }

  return self.prompt(fullQuestion).then((answer) => {
    if (options.default) {
      answer = answer || options.default;
    }

    if (options.trim) {
      answer = answer.trimRight();
    }

    if (options.validate && !options.validate.test(answer)) {
      let response = options.responses && options.responses.notValid;
      if (!response) {
        response = `Your answer isn't valid (must match ${
          options.validate.source}).`;
      }
      self._output.write(`${response}\n`);
      return self.ask(question, options);
    }

    if (options.convert) {
      answer = options.convert(answer);
    }

    return answer;
  });
};

/** Prompts the user for input without validation, retry, type conversion, or
 * trimming.
 *
 * @param {string} text Text with which to prompt the user.
 * @return {!Promise<string>} Promise with user-entered text up to (but not
 * including) the first newline or Error.
 * @private
 */
Shortline.prototype.prompt = function prompt(text) {
  const self = this;
  const input = self._input;
  const output = self._output;

  output.write(text);

  if (self.inputError) {
    return Promise.reject(self.inputError);
  }

  function onError(err) {
    self.inputError = err;
  }
  input.once('error', onError);
  return readTo(input, '\n').then(
    (result) => {
      input.removeListener('error', onError);
      // Trim \n, which is considered a non-input signaling character
      // Convert to a string, since stream may not have an encoding set
      return String(result.slice(0, -1));
    },
    (err) => {
      input.removeListener('error', onError);
      if (err.name === 'EOFError') {
        err.message = EOF_MESSAGE;
      }
      throw err;
    }
  );
};

module.exports = Shortline;


/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const TravisHttp = require('travis-ci/lib/travis-http');
const caseless = require('caseless');
const {inherits} = require('util');
const request = require('request');
const requestPackageJson = require('request/package.json');

const constants = require('./constants');
const packageJson = require('../package.json');
const trimSlash = require('./trim-slash');

/** Default User-Agent header to send with API requests.
 * @const
 * @private
 */
const DEFAULT_USER_AGENT = `node-travis-status/${packageJson.version
} Request/${requestPackageJson.version
} Node.js/${process.version}`;

/** Creates an instance of the travis-ci HTTP agent with a given endpoint
 * and request options.
 *
 * This class has the following features above TravisHttp:
 * - Uses a newer version of request (for gzip and proxy support)
 * - Supports caller-specified API endpoints (e.g. for enterprise or local use)
 * - Supports caller-specified request options (e.g. pooling, strictSSL, proxy,
 *   tunnel, timeouts, etc.)
 * - Improved error values which are real Error instances and include the
 *   HTTP response information, regardless of content.
 *
 * @param {string=} endpoint Travis CI API endpoint (base URL).
 * (default: {@link TravisStatusChecker.ORG_URI})
 * @param {Object=} options Options to pass to the request
 * constructor.
 */
function TravisStatusHttp(endpoint, options) {
  if (endpoint && typeof endpoint !== 'string') {
    throw new TypeError('endpoint must be a string');
  }
  endpoint = endpoint && trimSlash(endpoint);

  if (options && typeof options !== 'object') {
    throw new TypeError('options must be an object');
  }

  options = Object.assign({gzip: true}, options);
  options.headers = Object.assign({}, options.headers);

  // Careful about providing default values for case-insensitive headers
  const caselessHeaders = caseless(options.headers);
  // The Travis CI API docs say
  // "Always set the Accept header to application/vnd.travis-ci.2+json"
  // but the API actually sends Content-Type application/json.
  // Declare that we accept either.
  if (!caselessHeaders.has('Accept')) {
    options.headers.Accept =
      'application/vnd.travis-ci.2+json, application/json';
  }
  if (!caselessHeaders.has('User-Agent')) {
    options.headers['User-Agent'] = DEFAULT_USER_AGENT;
  }

  TravisHttp.call(
    this,
    endpoint === constants.PRO_URI,
    options.headers
  );

  this._endpoint = endpoint || constants.ORG_URI;
  // Set this._headers as TravisHttp does
  this._headers = options.headers;
  delete options.headers;
  this._options = options;
}
inherits(TravisStatusHttp, TravisHttp);

TravisStatusHttp.prototype._getHeaders = function _getHeaders() {
  let headers = this._headers;

  const token = this._getAccessToken();
  if (token) {
    let quotedToken;
    // From https://tools.ietf.org/html/rfc7235#section-2.1
    if (/^[A-Za-z0-9._~+/-]+$/.test(token)) {
      // The token is a valid HTTP token68 and doesn't require quoting
      quotedToken = token;
    } else {
      // Convert it to a quoted string
      // Note:  No validation is done to ensure it doesn't have prohibited
      // control characters, since there's no way to quote them and I'm more
      // likely to screw up unicode handling than catch real errors.
      quotedToken = `"${token.replace(/["\\]/g, '\\$&')}"`;
    }

    headers = Object.assign({}, headers);
    caseless(headers).del('Authorization');
    headers.Authorization = `token ${quotedToken}`;
  }

  return headers;
};

TravisStatusHttp.prototype.request = function(method, path, data, callback) {
  if (typeof data === 'function') {
    callback = data;
    data = undefined;
  }

  const options = Object.assign({}, this._options, {
    method,
    url: this._endpoint + path,
    headers: this._getHeaders()
  });

  if (data instanceof Buffer) {
    options.body = data;
  } else {
    options.json = data || true;
  }
  return request(options, (errRequest, res, body) => {
    if (errRequest) {
      callback(errRequest);
      return;
    }

    let err;

    if (res.statusCode >= 400) {
      err = new Error(res.statusMessage);
    }

    if (typeof body === 'string') {
      try {
        body = JSON.parse(body);
      } catch (errJson) {
        err = err || errJson;
      }
    }

    // Note:  This error handling deviates from travis-ci (which returns body
    // or statusCode as err).  I think this is much more sane.
    if (err) {
      err.statusCode = res.statusCode;
      err.statusMessage = res.statusMessage;
      err.headers = res.headers;
      err.body = body;
      callback(err);
      return;
    }

    callback(null, body);
  });
};

module.exports = TravisStatusHttp;


/**
 * @copyright Copyright 2017 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const {execFile} = require('child_process');

/** Runs git with given arguments.
 * @param {...string} args Arguments to git command.
 * @return {Promise} Promise with the process output or Error for non-0 exit.
 * @private
 */
function git(...args) {
  return new Promise((resolve, reject) => {
    const child = execFile('git', args, (err, stdout, stderr) => {
      if (err) {
        reject(err);
      } else {
        // Creating an object with named properties would probably be clearer
        // but this is compatible with thenify/promisify if we switch later.
        resolve([stdout, stderr]);
      }
    });
    child.stdin.end();
  });
}

module.exports = git;


#!/usr/bin/env node
/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

// Set NODE_DEBUG for request before importing it
if (require.main === module
    && (process.argv.indexOf('--debug') >= 0
     || process.argv.indexOf('--debug-http') >= 0)) {
  const nodeDebug = process.env.NODE_DEBUG;
  if (!nodeDebug) {
    process.env.NODE_DEBUG = 'request';
  } else if (!/\brequest\b/.test(nodeDebug)) {
    process.env.NODE_DEBUG = `${nodeDebug},request`;
  }
}

const Chalk = require('chalk').constructor;
const {Command} = require('commander');
const util = require('util');

const packageJson = require('../package.json');
const stateInfo = require('../lib/state-info');
const travisStatus = require('..');

const debug = util.debuglog('travis-status');

/** Options for command entry points.
 *
 * @typedef {{
 *   in: (stream.Readable|undefined),
 *   out: (stream.Writable|undefined),
 *   err: (stream.Writable|undefined)
 * }} CommandOptions
 * @property {stream.Readable=} in Stream from which input is read. (default:
 * process.stdin)
 * @property {stream.Writable=} out Stream to which output is written.
 * (default: process.stdout)
 * @property {stream.Writable=} err Stream to which errors (and non-output
 * status messages) are written. (default: process.stderr)
 */
// var CommandOptions;

/** Entry point for this command.
 *
 * @param {!Array<string>} args Command-line arguments.
 * @param {CommandOptions=} options Options.
 * @param {?function(Error, number=)=}
 * callback Callback for the exit code or an Error.
 * @return {Promise<number>|undefined} If callback is not given,
 * a Promise with the exit code or Error.
 */
function travisStatusCmd(args, options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options;
    options = null;
  }

  if (!callback) {
    return new Promise((resolve, reject) => {
      travisStatusCmd(args, options, (err, result) => {
        if (err) { reject(err); } else { resolve(result); }
      });
    });
  }

  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  try {
    if (args === undefined || args === null || args.length === 0) {
      // Fake args to keep Commander.js happy
      args = [
        process.execPath,
        __filename
      ];
    } else if (typeof args !== 'object'
               || Math.floor(args.length) !== args.length) {
      throw new TypeError('args must be Array-like');
    } else if (args.length < 2) {
      throw new RangeError('non-empty args must have at least 2 elements');
    } else {
      args = Array.prototype.map.call(args, String);
    }

    if (options && typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }

    options = Object.assign(
      {
        in: process.stdin,
        out: process.stdout,
        err: process.stderr
      },
      options
    );

    if (!options.in || typeof options.in.read !== 'function') {
      throw new TypeError('options.in must be a stream.Readable');
    }
    if (!options.out || typeof options.out.write !== 'function') {
      throw new TypeError('options.out must be a stream.Writable');
    }
    if (!options.err || typeof options.err.write !== 'function') {
      throw new TypeError('options.err must be a stream.Writable');
    }
  } catch (err) {
    process.nextTick(() => {
      callback(err);
    });
    return undefined;
  }

  const command = new Command()
    .description('Checks status of the latest build.')
    // Note:  Option order matches travis.rb with new ones at bottom
    .option('-i, --interactive', 'be interactive and colorful')
    .option('-E, --explode', 'ignored for compatibility with travis.rb')
    .option('--skip-version-check', 'ignored for compatibility with travis.rb')
    .option('--skip-completion-check',
      'ignored for compatibility with travis.rb')
    .option('-I, --insecure', 'do not verify SSL certificate of API endpoint')
    .option('-e, --api-endpoint <URL>', 'Travis API server to talk to')
    .option('--pro',
      `short-cut for --api-endpoint '${travisStatus.PRO_URI}'`)
    .on('option:pro', function() {
      this.apiEndpoint = travisStatus.PRO_URI;
    })
    .option('--org',
      `short-cut for --api-endpoint '${travisStatus.ORG_URI}'`)
    .on('option:org', function() {
      this.apiEndpoint = travisStatus.ORG_URI;
    })
    .option('--staging', 'talks to staging system')
    .on('option:staging', function() {
      this.apiEndpoint = (this.apiEndpoint || travisStatus.ORG_URI)
        .replace(/api/g, 'api-staging');
    })
    .option('-t, --token <ACCESS_TOKEN>', 'access token to use')
    .option('--debug', 'show API requests')
    .option('--debug-http', 'show HTTP(S) exchange')
    .option('-r, --repo <SLUG>',
      'repository to use (will try to detect from current git clone)')
    .option('-R, --store-repo <SLUG>',
      'like --repo, but remembers value for current directory')
    .on('option:store-repo', function() {
      this.repo = this.storeRepo;
    })
    .option('-x, --exit-code', 'sets the exit code to 1 if the build failed')
    .option('-q, --quiet', 'does not print anything')
    .option('-p, --fail-pending',
      'sets the status code to 1 if the build is pending')
    .option('-b, --branch [BRANCH]',
      'query latest build for a branch (default: current)')
    .option('-c, --commit [COMMIT]',
      'require build to be for a specific commit (default: HEAD)')
    .option('-w, --wait [TIMEOUT]',
      'wait if build is pending (timeout in seconds)')
    .version(packageJson.version);

  // Patch stdout, stderr, and exit for Commander
  // See: https://github.com/tj/commander.js/pull/444
  const exitDesc = Object.getOwnPropertyDescriptor(process, 'exit');
  const stdoutDesc = Object.getOwnPropertyDescriptor(process, 'stdout');
  const stderrDesc = Object.getOwnPropertyDescriptor(process, 'stderr');
  const consoleDesc = Object.getOwnPropertyDescriptor(global, 'console');
  const errExit = new Error('process.exit() called');
  process.exit = function throwOnExit(code) {
    errExit.code = code;
    throw errExit;
  };
  if (options.out) {
    Object.defineProperty(
      process,
      'stdout',
      {configurable: true, enumerable: true, value: options.out}
    );
  }
  if (options.err) {
    Object.defineProperty(
      process,
      'stderr',
      {configurable: true, enumerable: true, value: options.err}
    );
  }
  if (options.out || options.err) {
    Object.defineProperty(
      global,
      'console',
      {
        configurable: true,
        enumerable: true,
        // eslint-disable-next-line no-console
        value: new console.Console(process.stdout, process.stderr)
      }
    );
  }
  try {
    command.parse(args);
  } catch (errParse) {
    const exitCode = errParse === errExit ? errExit.code || 0 : null;
    process.nextTick(() => {
      if (exitCode !== null) {
        callback(null, exitCode);
      } else {
        callback(errParse);
      }
    });
    return undefined;
  } finally {
    Object.defineProperty(process, 'exit', exitDesc);
    Object.defineProperty(process, 'stdout', stdoutDesc);
    Object.defineProperty(process, 'stderr', stderrDesc);
    Object.defineProperty(global, 'console', consoleDesc);
  }

  if (command.commit === true) {
    command.commit = 'HEAD';
  }
  if (typeof command.interactive === 'undefined') {
    // Note:  Same default as travis.rb
    // Need cast to Boolean so undefined becomes false to disable Chalk
    command.interactive = Boolean(options.out.isTTY);
  }
  if (command.wait === true) {
    command.wait = Infinity;
  }

  const chalk = new Chalk({
    enabled: command.interactive,
    // Note:  level: 0 overrides enabled: true, so must be specified here in
    // case supports-color returns false causing 0 default level.
    level: 1
  });

  if (command.args.length > 0) {
    options.err.write(`${chalk.red('too many arguments')}\n${
      command.helpInformation()}`);
    process.nextTick(() => { callback(null, 1); });
    return undefined;
  }

  if (hasOwnProperty.call(command, 'wait')) {
    const wait = Number(command.wait);
    if (Number.isNaN(wait)) {
      const waitErr = chalk.red(`invalid wait time "${command.wait}"`);
      options.err.write(`${waitErr}\n`);
      process.nextTick(() => { callback(null, 1); });
      return undefined;
    }
    command.wait = wait * 1000;
  }

  // Pass through options
  command.in = options.in;
  command.out = options.out;
  command.err = options.err;

  // Use HTTP keep-alive to avoid unnecessary reconnections
  command.requestOpts = {
    forever: true
  };

  if (command.insecure) {
    command.requestOpts.strictSSL = false;
  }

  travisStatus(command, (err, build) => {
    if (err && err.name === 'SlugDetectionError') {
      debug('Error detecting repo slug', err);
      options.err.write(chalk.red(
        'Can\'t figure out GitHub repo name. '
        + 'Ensure you\'re in the repo directory, or specify the repo name via '
        + 'the -r option (e.g. travis-status -r <owner>/<repo>)\n'
      ));
      callback(null, 1);
      return;
    }

    if (err) {
      options.err.write(`${chalk.red(err.message)}\n`);
      callback(null, 1);
      return;
    }

    const state = build.repo ? build.repo.last_build_state : build.branch.state;

    if (!command.quiet) {
      const color = stateInfo.colors[state] || 'yellow';
      const number =
        build.repo ? build.repo.last_build_number : build.branch.number;
      options.out.write(`build #${number} ${chalk[color](state)
      }\n`);
    }

    let code = 0;
    if ((command.exitCode && stateInfo.isUnsuccessful[state])
        || (command.failPending && stateInfo.isPending[state])) {
      code = 1;
    }

    callback(null, code);
  });

  return undefined;
}

module.exports = travisStatusCmd;

if (require.main === module) {
  // This file was invoked directly.
  /* eslint-disable no-process-exit */
  const mainOptions = {
    in: process.stdin,
    out: process.stdout,
    err: process.stderr
  };
  travisStatusCmd(process.argv, mainOptions, (err, code) => {
    if (err) {
      if (err.stdout) { process.stdout.write(err.stdout); }
      if (err.stderr) { process.stderr.write(err.stderr); }
      process.stderr.write(`${err.name}: ${err.message}\n`);

      code = typeof err.code === 'number' ? err.code : 1;
    }

    process.exit(code);
  });
}


/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const {inherits} = require('util');

/** Constructs a SlugDetectionError.
 *
 * @class Represents an error during repository slug detection.
 * @constructor
 * @param {string=} message Human-readable description of the error.
 */
// Note:  Only needed before https://github.com/eslint/eslint/pull/5398
// eslint-disable-next-line consistent-return
function SlugDetectionError(message) {
  if (!(this instanceof SlugDetectionError)) {
    return new SlugDetectionError(message);
  }

  Error.captureStackTrace(this, SlugDetectionError);
  // Like http://www.ecma-international.org/ecma-262/6.0/#sec-error-message
  if (message !== undefined) {
    Object.defineProperty(this, 'message', {
      value: String(message),
      configurable: true,
      writable: true
    });
  }
}
inherits(SlugDetectionError, Error);
SlugDetectionError.prototype.message = 'Slug detection error';
SlugDetectionError.prototype.name = 'SlugDetectionError';

module.exports = SlugDetectionError;


const camelcase = require('camelcase');
const css = require('css');
const decamelize = require('decamelize');
const selectorParser = require('postcss-selector-parser');

const {getMatchingSelectors} = require('./css-matcher');

module.exports = {
	createScopedCss,
	combineCss,
	getMarkup,
	getMatchingSelectors
};

// https://regex101.com/r/EFULGo/2
const styleMatcher = /<style(?:.+)scoped(?:.*)>((?:.|[\r\n])*?)<\/style>/i;

/**
 * Returns the markup part of the combined css and html source.
 *
 * @param {string} code Source to extract markup from
 * @returns {string} Markup of the given source
 */
function getMarkup(code) {
	return code.replace(styleMatcher, '').trim();
}

/**
 * Strips scoped style from html and return html and metadata.
 *
 * @param {string} html HTML input source
 * @param {Object|string} scope Namespace for generated classNames
 * @param {string?} filepath Input file path (mainly for debugging)
 * @param {boolean} cssVariables True if css-variables support should be enabled
 * @returns [classNames: any, vars: any, css: string, transformMap: Object]
 */
function createScopedCss(html, scope, filepath, cssVariables) {
	scope = typeof scope === 'string' ? {ns: scope, vars: new Map()} : scope;

	const style = html.match(styleMatcher);
	if (!style) {
		return [{}, scope.vars, ''];
	}
	const cssom = css.parse(style[1], {source: filepath});
	const vars = new Map(scope.vars.entries());
	getVariables(cssom).forEach((value, key) => vars.set(key, value));

	if (cssVariables) {
		resolveScopeVariables(cssom, vars);
	}
	const [classes, transformMap] = rewriteSelectors(`${decamelize(scope.ns, '-')}`, cssom);

	return [classes, vars, css.stringify(cssom), transformMap];
}

// Based on http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
function createHash(input) {
	let hash = 0;
	if (input.length === 0) {
		return hash;
	}
	for (let i = 0; i < input.length; i++) {
		const char = input.charCodeAt(i);
		hash = ((hash << 5) - hash) + char;
		// Convert to 32bit integer
		hash &= hash;
	}
	return hash;
}

/**
 * Returns a combined CSS from all component CSS and scopedCss.
 *
 * @param {object} templates A map of schlump templates
 * @param {string[]} scopedCss A list of page CSS
 * @returns {string} CSS result
 */
function combineCss(templates, scopedCss) {
	if (!Array.isArray(scopedCss)) {
		scopedCss = [scopedCss];
	}
	return [
		...Object.keys(templates).map(name => templates[name].css),
		...scopedCss
	]
	.join('\n').trim();
}

function isClassSelectorNode(selector, i) {
	return selector.nodes[i].type === selectorParser.CLASS;
}
function isClassWithAttributeSelectorNode(selector, i) {
	return selector.nodes[i].type === selectorParser.ATTRIBUTE &&
		(i === 0 || selector.nodes[i - 1].type !== selectorParser.CLASS);
}
function isSimpleClassSelector(selector) {
	return selector.nodes.length === 1 && selector.nodes[0].type === selectorParser.CLASS;
}
function isSimpleClassWithPseudoSelector(selector) {
	return selector.nodes.length === 2 && selector.nodes[0].type === selectorParser.CLASS &&
		selector.nodes[1].type === selectorParser.PSEUDO;
}
function isOnlyPseudoRootSelector(selector) {
	return selector.nodes.length === 1 && selector.first.type === selectorParser.PSEUDO &&
		selector.first.value === ':root';
}

function createHashFromRule(nsPrefix, rule) {
	const ruleBody = rule.declarations
		.map(declaration => `${declaration.property}: ${declaration.value}`)
		.join('\n');
	return `${nsPrefix}-${createHash(ruleBody)}`;
}

function selectorTransformFactory(nsPrefix) {
	const classNameCache = new Map();
	const classes = {};
	const map = {};

	return {
		classes,
		map,
		createTransform(rule) {
			const ns = createHashFromRule(nsPrefix, rule);
			return selectors => {
				selectors.each(selector => {
					const sourceSelector = String(selector);

					if (isSimpleClassSelector(selector)) {
						classes[camelcase(String(selector))] = `${ns}-${selector.nodes[0].value}`;
					} else if (isSimpleClassWithPseudoSelector(selector)) {
						classes[camelcase(String(selector.nodes[0].value))] = `${ns}-${selector.nodes[0].value}`;
					}

					if (isOnlyPseudoRootSelector(selector)) {
						selector.first.replaceWith(selectorParser.className({value: `${ns}-root`}));
						map[sourceSelector] = String(`${ns}-root`).replace(/^\./, '');
					} else {
						for (let i = 0, n = selector.nodes.length; i < n; i++) {
							if (isClassSelectorNode(selector, i)) {
								let newClass = classNameCache.get(selector.nodes[i].value);
								if (!newClass) {
									newClass = selectorParser.className({value: `${ns}-${selector.nodes[i].value}`});
									classNameCache.set(selector.nodes[i].value, newClass);
								}
								selector.nodes[i].replaceWith(newClass);
								map[sourceSelector] = String(selector.last).replace(/^\./, '');
							} else if (isClassWithAttributeSelectorNode(selector, i)) {
								const classSelector = selectorParser.className({value: ns});
								selector.insertBefore(selector.nodes[i], classSelector);
								map[sourceSelector] = String(classSelector).replace(/^\./, '');
							}
						}
						if (selector.last.type === selectorParser.TAG) {
							selector.last.replaceWith(selectorParser.className({value:
								`${ns}-${selector.last.value}`}));
							map[sourceSelector] = String(selector.last).replace(/^\./, '');
						}
					}
				});
			};
		}
	};
}

function rewriteSelectors(ns, cssom) {
	const factory = selectorTransformFactory(ns);
	cssom.stylesheet.rules
		.filter(rule => rule.type === 'rule')
		.forEach(rule => {
			const transform = factory.createTransform(rule);
			rule.selectors = rule.selectors
				.map(selector =>
					selectorParser(transform).process(selector).result);
		});
	return [factory.classes, factory.map];
}

function getDeclarations(cssom) {
	return cssom.stylesheet.rules
		.filter(rule => rule.type === 'rule')
		.reduce((declarations, rule) => [...declarations, ...rule.declarations], [])
		.filter(declaration => declaration.type === 'declaration');
}

function getVariables(cssom) {
	return getDeclarations(cssom)
		.filter(declaration => declaration.property.startsWith('--'))
		.reduce((vars, declaration) => {
			vars.set(declaration.property, declaration.value);
			return vars;
		}, new Map());
}

function resolveScopeVariables(cssom, scope) {
	getDeclarations(cssom)
		.forEach(declaration => {
			while (declaration.value.includes('var(')) {
				scope.forEach((value, variableName) => {
					declaration.value = declaration.value.replace(`var(${variableName})`, value);
				});
			}
		});
}


/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const {inherits} = require('util');

/** Constructs an InvalidSlugError.
 *
 * @class Represents an error caused by a repository slug which does not
 * fit the required format.
 * @constructor
 * @param {string=} message Human-readable description of the error.
 */
// Note:  Only needed before https://github.com/eslint/eslint/pull/5398
// eslint-disable-next-line consistent-return
function InvalidSlugError(message) {
  if (!(this instanceof InvalidSlugError)) {
    return new InvalidSlugError(message);
  }

  Error.captureStackTrace(this, InvalidSlugError);
  // Like http://www.ecma-international.org/ecma-262/6.0/#sec-error-message
  if (message !== undefined) {
    Object.defineProperty(this, 'message', {
      value: String(message),
      configurable: true,
      writable: true
    });
  }
}
inherits(InvalidSlugError, Error);
InvalidSlugError.prototype.message = 'Invalid repository slug';
InvalidSlugError.prototype.name = 'InvalidSlugError';

module.exports = InvalidSlugError;


/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const assert = require('assert');
const http = require('http');
const https = require('https');
const nodeify = require('promise-nodeify');
const url = require('url');

const GitStatusChecker = require('./lib/git-status-checker');
const TravisStatusChecker = require('./lib/travis-status-checker');

/** Checks that a build has an expected commit hash.
 * @param {!{commit:!{sha: string}}} build Build (or branch) object returned
 * by the Travis CI API.
 * @param {!{sha: string, name: ?string}} localCommit Expected commit
 * information.
 * @return {!Object} build
 * @throws AssertionError If build.commit.sha is not equal to
 * expected.
 */
function checkBuildCommit(build, localCommit) {
  const buildCommit = build.commit;
  let message = `Build commit ${buildCommit.sha
  } does not match ${localCommit.sha}`;
  if (localCommit.name) {
    message += ` (${localCommit.name})`;
  }
  // assert gives us useful exception properties for callers
  assert.strictEqual(
    buildCommit.sha,
    localCommit.sha,
    message
  );
  return build;
}

/** Options for {@link travisStatus}.
 *
 * @typedef {{
 *   apiEndpoint: string|undefined,
 *   branch: string|boolean|undefined,
 *   commit: string|undefined,
 *   err: stream.Writable|undefined,
 *   in: stream.Readable|undefined,
 *   out: stream.Writable|undefined,
 *   repo: string|undefined,
 *   requestOpts: Object|undefined,
 *   storeRepo: string|undefined,
 *   token: string|undefined,
 *   wait: number|undefined
 * }} TravisStatusOptions
 * @property {boolean=} interactive behave as if being run interactively
 * @property {string=} apiEndpoint Travis API server to talk to
 * @property {(string|boolean)=} branch query latest build for named branch,
 * or the current branch
 * @property {string=} commit require build to be for a specific commit
 * @property {stream.Writable=} err Stream to which errors (and non-output
 * status messages) are written. (default: process.stderr)
 * @property {stream.Readable=} in Stream from which input is read. (default:
 * process.stdin)
 * @property {stream.Writable=} out Stream to which output is written.
 * (default: process.stdout)
 * @property {string=} repo repository to use (default: will try to detect from
 * current git clone)
 * @property {Object=} requestOpts Options for Travis CI API requests (suitable
 * for the {@link https://www.npmjs.com/package/request request module}).
 * Callers are encouraged to pass the agent or
 * forever options to leverage TCP keep-alive across requests.
 * @property {string=} storeRepo repository value (as described for
 * repo) to store permanently for future use.  Is used for this
 * invocation if repo is not set.
 * @property {string=} token access token to use
 * @property {number=} wait wait if build is pending (timeout in milliseconds)
 */
// var TravisStatusOptions;

/** Gets the current Travis CI status of a repo/branch.
 *
 * @param {?TravisStatusOptions=} options Options.
 * @param {?function(Error, Object=)=} callback Callback function called
 * with the current build information from the Travis CI API, or an
 * Error if it could not be retrieved.
 * @return {!Promise<!Object>|undefined} If callback is not given,
 * a Promise with the current build information from the Travis CI
 * API, or Error if it could not be retrieved.
 * Otherwise undefined.
 */
function travisStatus(options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options;
    options = null;
  }

  if (callback && typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  let agent, gitChecker, travisChecker;
  try {
    if (options && typeof options !== 'object') {
      throw new TypeError('options must be an object');
    }
    options = options || {};

    if (options.repo) {
      GitStatusChecker.checkSlugFormat(options.repo);
    }
    if (options.storeRepo) {
      GitStatusChecker.checkSlugFormat(options.storeRepo);
    }

    // If the caller didn't request an agent behavior, control it ourselves.
    // Each function call will use HTTP keep-alive for the duration of the
    // function, but not after completion, which callers may not expect.
    let {requestOpts} = options;
    if (!requestOpts
        || (requestOpts.agent === undefined
         && requestOpts.agentClass === undefined
         && requestOpts.agentOptions === undefined
         && requestOpts.forever === undefined
         && requestOpts.pool === undefined)) {
      const apiUrl =
        url.parse(options.apiEndpoint || TravisStatusChecker.ORG_URI);
      const Agent = apiUrl.protocol === 'https:' ? https.Agent
        : apiUrl.protocol === 'http:' ? http.Agent
          : null;
      if (Agent) {
        agent = new Agent({keepAlive: true});
        // .destroy() and keepAlive added to Agent in 0.11.4, nodejs@9fc9b874
        // If Agent doesn't support keepAlive/destroy, we don't need/want it.
        if (typeof agent.destroy === 'function') {
          requestOpts = Object.assign({}, requestOpts);
          requestOpts.agent = agent;
          options = Object.assign({}, options);
          options.requestOpts = requestOpts;
        } else {
          agent = undefined;
        }
      }
    }

    gitChecker = new GitStatusChecker(options);
    travisChecker = new TravisStatusChecker(options);
  } catch (errOptions) {
    const errResult = Promise.reject(errOptions);
    return nodeify(errResult, callback);
  }

  let repoSlugP;
  if (options.storeRepo) {
    const storedSlugP = gitChecker.tryStoreSlug(options.storeRepo);
    // If both .repo and .storeRepo are present, store .storeRepo and use .repo
    repoSlugP =
      options.repo ? storedSlugP.then(() => options.repo)
        : storedSlugP;
  } else if (options.repo) {
    repoSlugP = Promise.resolve(options.repo);
  } else {
    const foundSlugP = gitChecker.findSlug()
      .then(GitStatusChecker.checkSlugFormat);
    if (options.interactive) {
      repoSlugP = foundSlugP.then((slug) => gitChecker.tryStoreSlug(slug));
    } else {
      repoSlugP = foundSlugP;
    }
  }

  let localCommitP;
  if (options.commit) {
    localCommitP = gitChecker.resolveHash(options.commit)
      .then((resolved) => {
        const localCommit = {sha: resolved};
        if (resolved !== options.commit) {
          localCommit.name = options.commit;
        }
        return localCommit;
      });
  }

  // Before doing remote queries, ensure that there are no errors locally
  const slugForQueryP = Promise.all([repoSlugP, localCommitP])
    .then((slugAndHash) => slugAndHash[0]);

  let resultP;
  if (options.branch) {
    const branchP = options.branch === true ? gitChecker.detectBranch()
      : Promise.resolve(options.branch);
    resultP = Promise.all([slugForQueryP, branchP])
      .then((results) => {
        const slug = results[0];
        const branch = results[1];
        return travisChecker.getBranch(slug, branch, options);
      });
  } else {
    const repoP =
      slugForQueryP.then((slug) => travisChecker.getRepo(slug, options));

    if (localCommitP) {
      // Add build information to result
      resultP = repoP.then((repo) => travisChecker.getBuild(
        repo.repo.slug,
        repo.repo.last_build_id
      )
        .then((build) => Object.assign({}, repo, build)));
    } else {
      resultP = repoP;
    }
  }

  let checkedResultP = resultP;
  if (localCommitP) {
    checkedResultP = Promise.all([resultP, localCommitP])
      .then((all) => {
        const result = all[0];
        const localCommit = all[1];
        checkBuildCommit(result, localCommit);
        return result;
      });
  }

  let cleanupP;
  if (agent) {
    cleanupP = checkedResultP.then(
      (result) => { agent.destroy(); return result; },
      (err) => { agent.destroy(); return Promise.reject(err); }
    );
  } else {
    cleanupP = checkedResultP;
  }

  return nodeify(cleanupP, callback);
}

module.exports = travisStatus;
module.exports.ORG_URI = TravisStatusChecker.ORG_URI;
module.exports.PRO_URI = TravisStatusChecker.PRO_URI;
module.exports.GitStatusChecker = GitStatusChecker;
module.exports.TravisStatusChecker = TravisStatusChecker;


/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

/** Trims a single slash from the end of a string, if present.
 * @ template ArgType
 * @param {ArgType} string String to trim.
 * @return {ArgType} If string is a string which ends with
 * '/', string with the last character removed.
 * Otherwise, string unmodified.
 * @ nosideeffects
 */
function trimSlash(string) {
  if (typeof string !== 'string') {
    return string;
  }
  if (string.length > 0 && string.charAt(string.length - 1) === '/') {
    return string.slice(0, string.length - 1);
  }
  return string;
}

module.exports = trimSlash;


const reshape = require('reshape')

module.exports = function (source) {
  this.cacheable && this.cacheable()

  // configure options
  const cb = this.async()
  let options = parseOptions.call(this, this.query)

  // if we are producing a template (no locals or multi options), we are for
  // sure going to be returning a string, so we don't need to eval into a
  // function. we can set the returnString generator option to slice off some
  // overhead here
  if (!options.locals && !options.multi) {
    if (!options.generatorOptions) { options.generatorOptions = {} }
    options.generatorOptions.returnString = true
  }

  // set loader override options - filename and deps must be set by the loader
  options = Object.assign(options, {
    filename: this.resourcePath,
    dependencies: []
  })

  // custom plugin hook for options modification
  this._compiler.applyPlugins('before-loader-process', this, options)

  // if the multi option was specified, we have a slightly different compile
  // process. basically, we run through each of the multi options, merge it
  // with priority into the base options, and compile the base template once
  // for each multi option. The output is an object with the key being the
  // specified "name", and the value being a string with the compiled contents
  if (options.multi) {
    let depsAdded = false
    promiseReduce.call(this, options.multi, (m, opt) => {
      // no name is a no-go
      if (!opt.name) {
        throw new Error('multi options must have a "name" property')
      }

      // merge multi options back with default options
      const mergedOpts = Object.assign({}, options, opt)

      // run it
      return reshape(mergedOpts)
        .process(source)
        .then((res) => {
          // Only add dependencies once to prevent dupes
          if (!depsAdded && res.dependencies) {
            res.dependencies.map((dep) => this.addDependency(dep.file))
          }

          // Multi always produces a compiled template
          m[mergedOpts.name] = res.output(mergedOpts.locals)
          return m
        })
        // this needs to add module.exports and skip the source loader
    }, {}).then((res) => {
      // hack semi-specific to source-loader to ensure this is loaded
      // as a js object and not stringified
      this._module._jsSource = true
      this._module._outputMultiple = true
      cb(null, JSON.stringify(res))
    }).catch(cb)
  } else {
    // run reshape
    reshape(options)
      .process(source)
      .then((res) => {
        // If there are any dependencies reported, add them to webpack
        if (res.dependencies) {
          res.dependencies.map((dep) => this.addDependency(dep.file))
        }
        // If the locals option is specified, we call the output function and
        // return static html. Otherwise, we return the function & runtime to be
        // used client-side.
        if (options.locals) {
          cb(null, res.output(options.locals))
        } else {
          // TODO: more efficient shared runtime export
          cb(null, `var __runtime = ${serializeVerbatim(res.runtime)}; module.exports = ${res.output.substr(1)}`)
        }
      })
      .catch(cb)
  }
}

// Allows any option to be passed as a function which gets webpack's context
// as its first argument, in case some info from the loader context is necessary
function parseOptions (opts) {
  return removeEmpty({
    plugins: convertFn.call(this, opts.plugins),
    locals: convertFn.call(this, opts.locals),
    filename: convertFn.call(this, opts.filename),
    parserOptions: convertFn.call(this, opts.parserOptions),
    generatorOptions: convertFn.call(this, opts.generatorOptions),
    runtime: convertFn.call(this, opts.runtime),
    parser: convertFnSpecial.call(this, opts.parser),
    multi: convertFn.call(this, opts.multi)
  })
}

function convertFn (opt) {
  return typeof opt === 'function' ? opt(this) : opt
}

function convertFnSpecial (opt) {
  return typeof opt === 'function' && opt.convert ? opt(this) : opt
}

function removeEmpty (obj) {
  Object.keys(obj).forEach((key) => (obj[key] == null) && delete obj[key])
  return obj
}

module.exports.parseOptions = parseOptions

// The runtime contains functions which must be converted into strings without
// any escaping at all. Yes, this method of doing so is insane. But it works!
function serializeVerbatim (obj) {
  let i = 0
  const fns = []
  let res = JSON.stringify(obj, (k, v) => {
    if (typeof v === 'function') {
      fns.push(v.toString())
      return `__REPLACE${i++}`
    } else {
      return v
    }
  })
  res = res.replace(/"__REPLACE(\d{1})"/g, (m, v) => {
    return fns[v]
  })
  return res
}

function promiseReduce (arr, fn, memo) {
  return Promise.all(arr.map(fn.bind(this, memo)))
    .then(_ => memo)
}


const vm = require('vm');
const path = require('path');
const sander = require('sander');
const React = require('react');
const ReactDOM = require('react-dom/server');
const matter = require('gray-matter');
const chalk = require('chalk');
const figures = require('figures');
const {oneLine} = require('common-tags');
const camelcase = require('camelcase');
const uppercaseFirst = require('upper-case-first');
const beautifyHtml = require('js-beautify').html;

const {validatePages} = require('./validator');
const {createReactComponent} = require('./templates');

module.exports = {
	renderPages,
	getDestinationPath
};

function getDestinationPath(filepath, dest) {
	let destinationpath = path.join(dest, filepath.replace(/src\/pages/, ''));
	if (!path.extname(destinationpath)) {
		destinationpath += '/index.html';
	}
	return destinationpath;
}

/**
 * Renders all given pages as static HTML into the destination folder.
 *
 * @param {string[]} filepaths Input files to generate
 * @param {string} dest Path to write files to
 * @param {any} {components, vars, statics, disableValidation, scopedCss}
 *                 components
 *                 vars key-value pairs of globals
 *                 statics list of all available static files
 *                 disableValidation true to disable sanity checks
 * @returns
 */
function renderPages(filepaths, dest, {templates, vars, statics, disableValidation, cssVariables, host}) {
	console.log(`\nGenerating pages...`);
	return Promise.all(filepaths.map(filepath => {
		return sander.readFile(filepath)
			.then(content => renderPage(content, filepath, {templates, vars, dest, cssVariables}))
			.then(([html, destinationPath, cssParts]) => sander.writeFile(destinationPath, html)
				.then(() => [destinationPath, cssParts]))
			.then(([destinationPath, cssParts]) => {
				console.log(`  ${chalk.bold.green(figures.tick)} ${filepath} -> ${destinationPath}`);
				return [destinationPath, cssParts];
			});
	}))
	.then(pageResults => disableValidation ||
		validatePages(host, dest, pageResults.map(result => result[0]), statics)
			.then(() => pageResults.map(result => result[1])));
}

function deprecatedGlobals(target, name, filepath) {
	console.warn('  ' + oneLine`${chalk.bold.red(figures.warning)} Use of ${chalk.bold(`global.${name}`)}
		in page ${filepath} is deprecated. Use ${chalk.bold(`props.${name}`)} instead.`);
	return target[name];
}

function renderPage(content, filepath, {templates, vars, dest, cssVariables}) {
	const parsed = matter(content.toString());
	const destinationPath = getDestinationPath(parsed.data.route || filepath, dest);
	const pageComponent = createPageComponent({vars, filepath, parsed, templates, cssVariables});
	const cssParts = [];
	const sandbox = Object.assign(
		{},
		templates,
		{
			React,
			__html__: undefined,
			DecoratedRootComponent: createDecoratedRootComponent(pageComponent, new Map(), cssParts)
		}
	);
	const opts = {
		filename: filepath,
		displayErrors: true
	};

	vm.runInNewContext('__html__ = React.createElement(DecoratedRootComponent);', sandbox, opts);
	return [beautifyHtml(`<!DOCTYPE html>${ReactDOM.renderToStaticMarkup(sandbox.__html__)}`), destinationPath, cssParts.join('\n')];
}

function createPageComponent({vars, filepath, parsed, templates, cssVariables}) {
	const pageName = filepath.replace(/[./]/g, '-').replace(/^--/, '');
	const pageComponentSandbox = {
		global: new Proxy(Object.assign({}, vars), {
			get: (target, name) => deprecatedGlobals(target, name, filepath)
		}),
		props: vars,
		frontmatter: parsed.data
	};
	return createReactComponent(filepath, templates, pageComponentSandbox,
		{name: uppercaseFirst(camelcase(pageName)), code: parsed.content, cssVariables}).Component;
}

function createDecoratedRootComponent(Page, cssScope, cssParts) {
	class DecoratedRootComponent extends React.Component {
		getChildContext() {
			return {
				scope: {
					get() {
						return cssScope;
					},
					set(newScope) {
						cssScope = newScope;
					},
					css: css => {
						cssParts.push(css);
					}
				}
			};
		}

		render() {
			return React.createElement(Page);
		}
	}
	DecoratedRootComponent.childContextTypes = {
		scope: React.PropTypes.any
	};
	return DecoratedRootComponent;
}


/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const Travis = require('travis-ci');

const TravisStatusHttp = require('./travis-status-http');
const constants = require('./constants');
const stateInfo = require('./state-info');
const trimSlash = require('./trim-slash');

/** Time to wait before polling status on first retry (subsequent retries use
 * truncated exponential backoff) in milliseconds.
 * @const
 * @private
 */
const POLL_TIME_START_MS = 4000;

/** Maximum amount of time to wait between each status request when polling.
 * @const
 * @private
 */
const POLL_TIME_MAX_MS = 60000;

/** Options for {@link TravisStatusChecker}.
 *
 * @typedef {{
 *   apiEndpoint: string|undefined,
 *   requestOpts: Object|undefined,
 *   token: string|undefined
 * }} TravisStatusCheckerOptions
 * @property {string=} apiEndpoint Travis API server to talk to
 * @property {Object=} requestOpts Options for Travis CI API requests (suitable
 * for the {@link https://www.npmjs.com/package/request request module}).
 * Callers are encouraged to pass the agent or
 * forever options to leverage TCP keep-alive across requests.
 * @property {string=} token access token to use for Travis CI API requests
 */
// var TravisStatusCheckerOptions;

/** Creates a status checker for Travis CI.
 *
 * @constructor
 * @param {TravisStatusCheckerOptions=} options Options.
 */
function TravisStatusChecker(options) {
  if (options && typeof options !== 'object') {
    throw new TypeError('options must be an object');
  }
  options = options || {};

  const apiEndpoint = options.apiEndpoint && trimSlash(options.apiEndpoint);
  this._travis = new Travis({
    pro: apiEndpoint === constants.PRO_URI,
    version: '2.0.0'
  });
  this._travis.agent = new TravisStatusHttp(apiEndpoint, options.requestOpts);
  if (options.token) {
    this._travis.agent.setAccessToken(options.token);
  }
}

function branchIsPending(branch) {
  return stateInfo.isPending[branch.branch.state];
}

function buildIsPending(build) {
  return stateInfo.isPending[build.build.state];
}

function repoIsPending(repo) {
  return stateInfo.isPending[repo.repo.last_build_state];
}

/** Options for {@link TravisStatusQueryOptions}.
 *
 * @typedef {{
 *   wait: number|undefined
 * }} TravisStatusQueryOptions
 * @property {number=} wait The maximum amount of time during which to retry
 * while the response is pending (in milliseconds). (default: no retry)
 */
// var TravisStatusQueryOptions;

/** Performs a travis-ci query, retrying while pending, up to a given wait
 * time.
 * @param {!{get: callback}} query travis-ci resource to query.
 * @param {(function(!Object): boolean)=} valueIsPending Function which
 * determines if an API value is considered to be pending for retry purposes.
 * @param {TravisStatusQueryOptions=} options Query options.
 * @return {Promise<!Object>} Promise with the response content or Error.
 * @private
 */
function queryWithWait(query, valueIsPending, options) {
  const maxWaitMs = options && options.wait ? Number(options.wait) : 0;
  if (Number.isNaN(maxWaitMs)) {
    return Promise.reject(new TypeError('wait must be a number'));
  }
  if (maxWaitMs < 0) {
    return Promise.reject(new RangeError('wait must be non-negative'));
  }

  const startMs = Date.now();
  // Note:  Divide by 2 so we can double unconditionally below
  let nextWaitMs = POLL_TIME_START_MS / 2;

  function doQuery(cb) {
    query.get(cb);
  }

  return new Promise((resolve, reject) => {
    function checkBuild(err, result) {
      if (err) {
        reject(err);
        return;
      }

      if (maxWaitMs) {
        let isPending;
        try {
          isPending = valueIsPending(result);
        } catch (errPending) {
          reject(errPending);
          return;
        }

        if (isPending) {
          const nowMs = Date.now();
          const totalWaitMs = nowMs - startMs;
          if (totalWaitMs < maxWaitMs) {
            nextWaitMs = Math.min(
              nextWaitMs * 2,
              POLL_TIME_MAX_MS,
              maxWaitMs - totalWaitMs
            );
            setTimeout(doQuery, nextWaitMs, checkBuild);
            return;
          }
        }
      }

      resolve(result);
    }

    doQuery(checkBuild);
  });
}

/** Gets the Travis CI branch information for a given branch and repo name.
 * @param {string} repoName Travis repository name to query (e.g. owner/repo).
 * @param {string} branchName Branch name to query.
 * @param {TravisStatusQueryOptions=} options Query options.
 * @return {Promise<!Object>} Promise with the response content or Error.
 */
TravisStatusChecker.prototype.getBranch = function getBranch(repoName,
  branchName, options) {
  const queryBranch = this._travis.repos(repoName).branches(branchName);
  return queryWithWait(queryBranch, branchIsPending, options);
};

/** Gets the Travis CI build information for a given build.
 * @param {string} repoName Travis repository name to query (e.g. owner/repo).
 * @param {string} buildId ID of the build to query.
 * @param {TravisStatusQueryOptions=} options Query options.
 * @return {Promise<!Object>} Promise with the response content or Error.
 */
TravisStatusChecker.prototype.getBuild = function getBuild(repoName, buildId,
  options) {
  // Note:  travis-ci doesn't expose /repos/{repository.id}/builds/{build.id}
  // Use /builds/{build.id} instead
  const queryBuild = this._travis.builds(buildId);
  return queryWithWait(queryBuild, buildIsPending, options);
};

/** Gets the Travis CI repository information for a given name.
 * @param {string} repoName Travis repository name to query (e.g. owner/repo).
 * @param {TravisStatusQueryOptions=} options Query options.
 * @return {Promise<!Object>} Promise with the response content or Error.
 */
TravisStatusChecker.prototype.getRepo = function getRepo(repoName, options) {
  const queryRepo = this._travis.repos(repoName);
  return queryWithWait(queryRepo, repoIsPending, options);
};

/** Travis CI for open source API endpoint.
 * Same name/value as lib/travis/client.rb#ORG_URI.
 * @const
 */
TravisStatusChecker.ORG_URI = `${constants.ORG_URI}/`;

/** Travis Pro API endpoint.
 * Same name/value as lib/travis/client.rb#PRO_URI.
 * @const
 */
TravisStatusChecker.PRO_URI = `${constants.PRO_URI}/`;

module.exports = TravisStatusChecker;


var fs = require('fs');
var url = require('url');
var path = require('path');
var http = require('http');
var request = require('request');

var ZNMAP = {

	'\\534E\\6587\\7EC6\\9ED1': ['STHeiti Light [STXihei]', 'STXiheii'],
	'\\534E\\6587\\9ED1\\4F53': 'STHeiti',
	'\\4E3D\\9ED1 Pro': 'LiHei Pro Medium',
	'\\4E3D\\5B8B Pro': 'LiSong Pro Light',
	'\\6807\\6977\\4F53': ["DFKai-SB","BiauKai"],
	'\\82F9\\679C\\4E3D\\4E2D\\9ED1': 'Apple LiGothic Medium',
	'\\82F9\\679C\\4E3D\\7EC6\\5B8B': 'Apple LiSung Light',

	'\\65B0\\7EC6\\660E\4F53': 'PMingLiU',
	'\\7EC6\\660E\\4F53': 'MingLiU',
	'\\9ED1\\4F53': 'SimHei',
	'\\5B8B\\4F53': 'SimSun',
	'\\65B0\\5B8B\\4F53': 'NSimSun',
	'\\4EFF\\5B8B': 'FangSong',
	'\\6977\\4F53': 'KaiTi',
	'\\4EFF\\5B8B_GB2312': 'FangSong_GB2312',
	'\\6977\\4F53_GB2312': 'KaiTi_GB2312',
	'\\5FAE\\x8F6F\\6B63\\9ED1\\4F53': 'Microsoft JhengHei',
	'\\5FAE\\8F6F\\96C5\\9ED1': 'Microsoft YaHei',

	'\\96B6\\4E66': 'LiSu',
	'\\5E7C\\5706': 'YouYuan',
	'\\534E\\6587\\6977\\4F53': 'STKaiti',
	'\\534E\\6587\\5B8B\\4F53': 'STSong',
	'\\534E\\6587\\4E2D\\5B8B': 'STZhongsong',
	'\\534E\\6587\\4EFF\\5B8B': 'STFangsong',
	'\\65B9\\6B63\\8212\\4F53': 'FZShuTi',
	'\\65B9\\6B63\\59DA\\4F53': 'FZYaoti',
	'\\534E\\6587\\5F69\\4E91': 'STCaiyun',
	'\\534E\\6587\\7425\\73C0': 'STHupo',
	'\\534E\\6587\\96B6\\4E66': 'STLiti',
	'\\534E\\6587\\884C\\6977': 'STXingkai',
	'\\534E\\6587\\65B0\\9B4F': 'STXinwei'
};

module.exports = {
	debug: false,
	log: function(msg, type) {
		var self = this;
		type = type ? type: 'info';
		if (msg && (self.debug || (!self.debug && type != 'debug'))) {
			console.log((type ? '[' + type.toUpperCase() + '] ': '') + msg);
		}
	},
	/**
     * analyze @charset first.
     * @example:
     * 1. @charset 'gbk';
     * 2. @charset "gbk";
     * @link: https://developer.mozilla.org/en/CSS/@charset
     */
	detectCharset: function(input) {
		var result = /@charset\s+['|"](\w*)["|'];/.exec(input),
		charset = 'UTF-8';
		if (result && result[1]) {
			charset = result[1];
		}
		//        else{
		//            var detect = jschardet.detect(input);
		//            if(detect && detect.confidence > 0.9){
		//                charset = detect.encoding;
		//            }
		//        }
		return charset;
	},
	mkdirSync: function(dirpath, mode) {
		var self = this;
		if (!fs.existsSync(dirpath)) {
			// try to create parent dir first.
			self.mkdirSync(path.dirname(dirpath), mode);
			fs.mkdirSync(dirpath, mode);
		}
	},
	getRemoteFile: function(filePath, callback) {
		var self = this;
		if(filePath.substr(0, 2) === '//'){
			// TODO this is just a patch. It need to consider more complex situations such as https only.
			filePath = 'http:' + filePath;
		}
		request({
			url: filePath,
			headers: {
				'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36'
			}
		}, function (error, response, body) {
			if (!error && response.statusCode == 200) {
				var charset = 'utf-8';
				if (typeof response.headers['content-type'] !== 'undefined') {
					var regResult = response.headers['content-type'].match(/;charset=(\S+)/);
					if (regResult !== null && regResult[1]) {
						charset = regResult[1];
						self.log('The charset of url ' + filePath + ' is: ' + charset, 'debug');
					}
				}
				callback && callback(body, charset);
			}else{
				self.log('request for ' + filePath + ' error: ' + error);
			}
		});
	},
	//中文转unicode
	a2u: function(text) {
		text = escape(text.toString()).replace(/\+/g, "%2B");
		var matches = text.match(/(%([0-9A-F]{2}))/gi);
		if (matches) {
			for (var matchid = 0; matchid < matches.length; matchid++) {
				var code = matches[matchid].substring(1, 3);
				if (parseInt(code, 16) >= 128) {
					text = text.replace(matches[matchid], '%u00' + code);
				}
			}
		}
		text = text.replace('%25', '%u0025');

		return text;
	},
	unicode2En: function(code) {
        if(ZNMAP.hasOwnProperty(code)){
            return ZNMAP[code];
        }
        return code;
	}
};