// Functions that process dates given a certain set of frequency and formatting
// settings

// Node modules
var d3 = require("d3");
require("sugar-date");

// Parse dates and return strings based on selected format
var dateParsers = {

	"lmdy": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		return d.format('{M}/{d}/{yy}');
	},

	"mmdd": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		return d.format('{M}/{d}');
	},

	"Mdd": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		var month = d.getMonth() + 1;
		if (month == 5) {
			return d.format('{Month} {d}');
		} else {
			return d.format('{Mon}. {d}');
		}
	},

	"M1d": function(d,i,o) {
		if(o) {
			d.addMinutes(o)
		}
		var date = d.getDate();
		if(date == 1) {
			return dateParsers.M(d);
		} else if (i === 0) {
			return dateParsers.M(d) + d.format(' {d}');
		} else {
			return d.format('{d}');
		}
	},

	"ddM": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		var month = d.getMonth() + 1;
		if (month == 5) {
			return d.format('{d} {Month}');
		} else {
			return  d.format('{d} {Mon}.');
		}
	},

	"mmyy": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		return d.format('{M}/' + dateParsers.yyyy(d).slice(-2));
	},

	"yy": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		return "’" + dateParsers.yyyy(d).slice(-2);
	},

	"yyyy": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		return "" + d.getFullYear();
	},

	"MM": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		var month = d.getMonth() + 1;
		if (month == 1) {
			return "" + d.getFullYear();
		} else {
			return d.format('{Month}');
		}
	},

	"M": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		var month = d.getMonth() + 1;
		if (month == 1) {
			return "’" + dateParsers.yyyy(d).slice(-2);
		} else if (month == 5) {
			return d.format('{Mon}');
		} else {
			return d.format('{Mon}.');
		}
	},

	"hmm": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		return d.format("{h}:{mm}");
	},

	"h": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		return d.format("{h}{tt}");
	},

	"QJan": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		var year = d.getFullYear();
		var month = d.getMonth() + 1;
		var day = d.getDate();
		if (day == 1) {
			if (month == 1) {
				return year;
			}

			if (month == 4 || month == 7 || month == 10) {
				return "Q" + (((month - 1) / 3) + 1);
			}

		}

		return "";
	},

	"QJul": function(d, i, o) {
		if(o) {
			d.addMinutes(o)
		}
		var year = d.getFullYear();
		var month = d.getMonth() + 1;
		var day = d.getDate();
		if (day == 1) {
			if (month == 7) {
				return year;
			}

			if (month == 1) {
				return "Q3";
			}

			if (month == 4) {
				return "Q4";
			}

			if (month == 10) {
				return "Q2";
			}

		}
		return "";
	}
};

// Define time intervals and number of steps for date frequency options
var dateFrequencies = {
	"auto": {
		interval: null,
		steps: null
	},
	"1h": function(minDate, maxDate) {
		var interval = d3.time.hour;
		return interval.range(minDate, maxDate, 1);
	},
	"2h": function(minDate, maxDate) {
		var interval = d3.time.hour;
		return interval.range(minDate, maxDate, 2);
	},
	"3h": function(minDate, maxDate) {
		var interval = d3.time.hour;
		return interval.range(minDate, maxDate, 3);
	},
	"4h": function(minDate, maxDate) {
		var interval = d3.time.hour;
		return interval.range(minDate, maxDate, 4);
	},
	"6h": function(minDate, maxDate) {
		var interval = d3.time.hour;
		return interval.range(minDate, maxDate, 6);
	},
	"1d": function(minDate, maxDate) {
		var interval = d3.time.day;
		return interval.range(minDate, maxDate, 1);
	},
	"1w": function(minDate, maxDate) {
		var interval = d3.time.week;
		return interval.range(minDate, maxDate, 1);
	},
	"1m": function(minDate, maxDate) {
		var interval = d3.time.month;
		return interval.range(minDate, maxDate, 1);
	},
	"3m": function(minDate, maxDate) {
		var interval = d3.time.month;
		return interval.range(minDate, maxDate, 3);
	},
	"6m": function(minDate, maxDate) {
		var interval = d3.time.month;
		return interval.range(minDate, maxDate, 6);
	},
	"1y": function(minDate, maxDate) {
		var interval = d3.time.year;
		maxDate = d3.time.day.offset(maxDate, 1);
		return interval.range(minDate, maxDate, 1);
	},
	"2y": function(minDate, maxDate) {
		var interval = d3.time.year;
		maxDate = d3.time.day.offset(maxDate, 1);
		return interval.range(minDate, maxDate, 2);
	},
	"5y": function(minDate, maxDate) {
		var interval = d3.time.year;
		maxDate = d3.time.day.offset(maxDate, 1);
		return interval.range(minDate, maxDate, 5);
	},
	"10y": function(minDate, maxDate) {
		var interval = d3.time.year;
		maxDate = d3.time.day.offset(maxDate, 1);
		return interval.range(minDate, maxDate, 10);
	},
	"20y": function(minDate, maxDate) {
		var interval = d3.time.year;
		maxDate = d3.time.day.offset(maxDate, 1);
		return interval.range(minDate, maxDate, 20);
	},
	"100y": function(minDate, maxDate) {
		var interval = d3.time.year;
		maxDate = d3.time.day.offset(maxDate, 1);
		return interval.range(minDate, maxDate, 100);
	},
};

function humanReadableNumber(n) {
	//turns a number into an int that is either 1, 2, 5, or a multiple of 10
	var rounded = Math.round(n);
	if (rounded < 2) {
		return 1;
	}

	if (n < 3.5) {
		return 2;
	}

	if (n < 7) {
		return 5;
	}

	if (n <= 10) {
		return 10;
	}

	var magnitude = Math.floor((Math.log(n) / Math.LN10)) + 1;
	var factor = Math.pow(10,magnitude);
	return humanReadableNumber(n/factor) * factor;
}

// Automatically calculate date frequency if not selected
function autoDateFormatAndFrequency(minDate, maxDate, dateFormat, availableWidth) {
	var timespan = Math.abs(maxDate - minDate);
	var years = timespan / 31536000000;
	var months = timespan / 2628000000;
	var days = timespan / 86400000;
	var yearGap;
	var hourGap;
	var interval;

	var targetPixelGap = 64;
	var maximum_ticks = Math.max(Math.floor(availableWidth / targetPixelGap), 1);
	var time_gap = timespan / maximum_ticks;

	if (dateFormat == "auto") {
		//lets go small to large
		if (days <= 2) {
			dateFormat = "h";
		}
		else if (days <= 91) {
			dateFormat = "M1d";
		}
		else if (months < 36) {
			dateFormat = "M";
		}
		else {
			dateFormat = "yy";
		}
	}

	var gapInYears = humanReadableNumber(Math.floor(time_gap / 31536000000));
	var gapInMonths = Math.ceil(time_gap / 2628000000);
	var gapInDays = humanReadableNumber(time_gap / 86400000);
	var gapInHours = humanReadableNumber(time_gap / 3600000);

	//make sure that the interval include the maxDate in the interval list
	maxDate.addMilliseconds(0.1);

	switch (dateFormat) {
		case "yy":
			// Add a day to the max date for years to make inclusive of max date
			// irrespective of time zone / DST
			maxDate = d3.time.day.offset(maxDate, 1);
			interval = d3.time.year.range(minDate, maxDate, gapInYears);
			break;

		case "yyyy":
			// See above
			maxDate = d3.time.day.offset(maxDate, 1);
			interval = d3.time.year.range(minDate, maxDate, gapInYears);
			break;

		case "MM":
			interval = d3.time.month.range(minDate, maxDate, gapInMonths);
			break;

		case "M":
			interval = d3.time.month.range(minDate, maxDate, gapInMonths);
			break;

		case "Mdd":
			interval = d3.time.day.range(minDate, maxDate, gapInDays);
			break;

		case "M1d":
			interval = d3.time.day.range(minDate, maxDate, gapInDays);
			break;

		case "YY":
			interval = d3.time.year.range(minDate, maxDate, gapInYears);
			break;

		case "QJan":
			interval = d3.time.month.range(minDate, maxDate, 4);
			break;

		case "QJul":
			interval = d3.time.month.range(minDate, maxDate, 4);
			break;

		case "h":
			interval = d3.time.hour.range(minDate, maxDate, gapInHours);
			break;

		default:
			interval = d3.time.year.range(minDate, maxDate, 1);
	}

	interval = cleanInterval(interval);
	return {"format": dateFormat, "frequency": interval};
}

function cleanInterval(interval) {
	return interval.reduce(function(out, currDate, i, arr) {
		if (i === 0) {
			return [currDate];
		}
		var prevDate = out[out.length - 1].getDate();
		var excludeRange = (prevDate >= 28 && prevDate <= 31);
		if(excludeRange && currDate.getDate() == 1) {
			out.pop();
			return out.concat(currDate);
		} else {
			return out.concat(currDate);
		}
	}, []);
}

module.exports = {
	dateParsers: dateParsers,
	dateFrequencies: dateFrequencies,
	autoDateFormatAndFrequency: autoDateFormatAndFrequency
};


/**
 * Test whether a string is a valid Chartbuilder model
 * @param {string} modelStr - unparsed string of chart model JSON
 * @returns {object or null} parsed - parsed object
 * @static
 * @memberof helper
*/
function validate_chart_model(modelStr) {
	var parsed;

	try {
		parsed = JSON.parse(modelStr);
	} catch (e) {
		throw new TypeError("Chart model is not valid JSON");
	}

	var isValidChartModel = (parsed.hasOwnProperty("chartProps") && parsed.hasOwnProperty("metadata"));
	if (isValidChartModel) {
		return parsed;
	} else {
		throw new TypeError("Not a valid Chartbuilder model");
	}

}

module.exports = validate_chart_model;


var chartSizes = require("../../config/chart-sizes");

/**
 * see [ChartConfig#calculateDimensions](#chartconfig/calculatedimensions)
 * @see ChartConfig#calculateDimensions
 * @instance
 * @memberof chart_grid_config
 */
function chartGridDimensions(width, opts) {
	var height;
	var metadata = opts.metadata;
	var grid = opts.grid;

	if (metadata.size == "auto" || opts.enableResponsive) {
		// use current width
	} else {
		width = chartSizes[metadata.size].width;
	}

	if (grid.type == "bar") {
		var numDataPoints = opts.data[0].values.length;
		height = calculate_bar_height(numDataPoints, grid, opts.displayConfig);
	} else {
		height = calculate_cartesian_height(width, grid, opts.displayConfig);
	}

	if (!opts.showMetadata) {
		height -= opts.displayConfig.padding.bottom;
	}

	return {
		width: width,
		height: height
	};
}

function calculate_bar_height(numDataPoints, grid, displayConfig) {
	return displayConfig.barHeight * numDataPoints * grid.rows;
}

function calculate_cartesian_height(width, grid, displayConfig, extraHeight) {
	var height = (
		grid.rows *
		((width / grid.cols) *
		displayConfig.xy.aspectRatio.wide)
	);
	return height;
}

module.exports = chartGridDimensions;


/*
 * Store the Chart's metadata. These are properties used to describe the chart
 * and are unrelated to the actual rendering of the chart.
*/
var assign = require("lodash/assign");
var clone = require("lodash/clone");
var EventEmitter = require("events").EventEmitter;

/* Flux dispatcher */
var Dispatcher = require("../dispatcher/dispatcher");

/* Require the `ChartProptiesStore so that we can wait for it to update */
var ChartPropertiesStore = require("./ChartPropertiesStore");

/* Singleton that houses metadata */
var _metadata = {};
var titleDirty = false;
var CHANGE_EVENT = "change";

/**
 * ### ChartMetadataStore.js
 * Flux store for chart metadata such as title, source, size, etc.
*/
var ChartMetadataStore = assign({}, EventEmitter.prototype, {

	emitChange: function() {
		this.emit(CHANGE_EVENT);
	},

	addChangeListener: function(callback) {
		this.on(CHANGE_EVENT, callback);
	},

	removeChangeListener: function(callback) {
		this.removeListener(CHANGE_EVENT, callback);
	},

	/**
	 * get
	 * @param k
	 * @return {any} - Return value at key `k`
	 * @instance
	 * @memberof ChartMetadataStore
	 */
	get: function(k) {
		return _metadata[k];
	},

	/**
	 * getAll
	 * @return {object} - Return all metadata
	 * @instance
	 * @memberof ChartMetadataStore
	 */
	getAll: function() {
		return clone(_metadata);
	},

	/**
	 * clear
	 * Set metadata to empty
	 * @instance
	 * @memberof ChartMetadataStore
	 */
	clear: function() {
		_metadata = {};
	}

});

/* Respond to actions coming from the dispatcher */
function registeredCallback(payload) {
	var action = payload.action;
	var data;

	switch(action.eventName) {
		/*
		* New chart model is received. Respond by first waiting for
		* `ChartProptiesStore`
		*/
		case "receive-model":
			Dispatcher.waitFor([ChartPropertiesStore.dispatchToken]);
			_metadata = action.model.metadata;
			data = ChartPropertiesStore.get("data");
			_metadata.title = defaultTitle(data);
			ChartMetadataStore.emitChange();
			break;

		/* Metadata alone is being updated */
		case "update-metadata":
			_metadata[action.key] = action.value;
			// if title is edited, set dirty to true and dont generate default anymore
			// TODO: we don't need to do this every time
			if (action.key == "title") {
				titleDirty = true;
			}
			ChartMetadataStore.emitChange();
			break;

		case "update-and-reparse":
			if (!titleDirty) {
				data = ChartPropertiesStore.get("data");
				_metadata.title = defaultTitle(data);
				ChartMetadataStore.emitChange();
			}
			break;

		case "update-data-input":
			if (!titleDirty) {
				data = ChartPropertiesStore.get("data");
				_metadata.title = defaultTitle(data);
				ChartMetadataStore.emitChange();
			}
			break;

		default:
			// do nothing
	}

	return true;

}

//Dispatcher.register(registeredCallback);
/* Respond to actions coming from the dispatcher */
ChartMetadataStore.dispatchToken = Dispatcher.register(registeredCallback);

function defaultTitle(data) {
	if (data.length === 1 && _metadata.title === "") {
		return data[0].name;
	} else {
		return _metadata.title;
	}
}

module.exports = ChartMetadataStore;


var isArray = require("lodash/isArray");
var isUndefined = require("lodash/isUndefined");
var keys = require("lodash/keys");
var reduce = require("lodash/reduce");
var d3 = require("d3");
var processDates = require("./process-dates");

/**
 * Generate an exact number of ticks given a domain
 *
 * @param {number[]} domain - min/max of the current scale
 * @param {number} numticks - desired number of ticks
 * @return {string[]} Array of ticks
 * @static
 * @memberof helper
 */
function exact_ticks(domain, numticks) {
	numticks -= 1;
	var ticks = [];
	var delta = domain[1] - domain[0];
	var i;
	for (i = 0; i < numticks; i++) {
		ticks.push(domain[0] + (delta / numticks) * i);
	}
	ticks.push(domain[1]);

	if (domain[1] * domain[0] < 0) {
		//if the domain crosses zero, make sure there is a zero line
		var hasZero = false;
		for (i = ticks.length - 1; i >= 0; i--) {
			//check if there is already a zero line
			if (ticks[i] === 0) {
				hasZero = true;
			}
		}
		if (!hasZero) {
			ticks.push(0);
		}
	}

	return ticks;
}

/**
 * compute_scale_domain
 *
 * @param scaleObj - Current scale before generating new domain
 * @param {number[]} data - All values in the current scale
 * @param {object} opts - Whether to return nice values or force a minimum of 0
 * or below
 * @return {object} { domain: [min, max], custom: <boolean> }
 * @static
 * @memberof helper
 */
function compute_scale_domain(scaleObj, data, opts) {
	// Compute the domain (`[min, max]`) of a scale based on its data points.
	// `data` is a flat array of all values used in this scale, and is
	// created by `input-parsers/parse-<chart>.js`
	opts = opts || {};
	var scaleDomain = scaleObj.domain || [];
	var _domain;
	var defaultMin;
	var defaultMax;

	if (!isArray(data)) {
		throw new TypeError("data passed to compute_scale_domain must be an array");
	}

	var extent = d3.extent(data);
	var niced = d3.scale.linear()
			.domain(extent)
			.nice()
			.domain();

	if (!scaleObj.domain || !scaleObj.custom) {
		if (opts.nice) {
			_domain = niced;
		} else {
			_domain = extent;
		}
		defaultMin = true;
		defaultMax = true;
	} else {
		_domain = (opts.nice) ? niced : extent;
		defaultMin = (_domain[0] === scaleDomain[0] || isUndefined(scaleDomain[0]));
		defaultMax = (_domain[1] === scaleDomain[1] || isUndefined(scaleDomain[1]));
		_domain = scaleDomain;
	}

	if (opts.minZero) {
		_domain[0] = Math.min(_domain[0], 0);
	}

	return {
		domain: _domain,
		custom: (!defaultMin || !defaultMax)
	};
}

/**
 * round_to_precision
 * Round a number to N decimal places
 *
 * @param {number} num - Number to be rounded
 * @param {number} precision - Desired precision
 * @param {boolean} supress_thou_sep
 * @static
 * @memberof helper
 * @return {number} Rounded number
 */
function round_to_precision(num, precision, supress_thou_sep) {
	//zero should always be "0"
	if (num === 0) return "0";

	var s = Math.round(num * Math.pow(10,precision)) / Math.pow(10,precision);
	s = s + "";

	s = s.split(".");

	if (s.length == 1) {
		s[1] = "";
	}

	if (s[1].length < precision) {
		s[1] += Array(precision-s[1].length + 1).join("0");
	}

	if (!supress_thou_sep) {
		s[0] = d3.format(",")(parseInt(s[0]));
	}

	if (precision === 0) {
		return s[0];
	}

	return s.join(".");
}

/**
 * combine_margin_pading
 *
 * @param m
 * @param p
 * @static
 * @memberof helper
 * @return {object}
 */
function combine_margin_pading(m,p) {
	return {
		top: m.top + p.top,
		right: m.right + p.right,
		bottom: m.bottom + p.bottom,
		left: m.left + p.left
	};
}

/**
 * precision
 *
 * @param a
 * @static
 * @memberof helper
 * @return {undefined}
 */
function precision(a) {
  // http://stackoverflow.com/a/27865285/1181761

  // guard for NaN
  if (a === a) {
		var e = 1;
		while (Math.round(a * e) / e !== a) e *= 10;
		return Math.round(Math.log(e) / Math.LN10);
  } else {
		return 0;
  }
}

/**
 * transform_coords
 *
 * @param transformString
 * @static
 * @memberof helper
 * @return {undefined}
 */
function transform_coords(transformString) {
	// Split on both space and comma because IE10 likes spaces?
	var s = transformString.split(/\s|,/);
	return [s[0].split("(")[1],s[1].split(")")[0]].map(parseFloat);
}

/**
 * Given a defaults object and a source object, copy the value from the source
 * if it contains the same key, otherwise return the default. Skip keys that
 * only exist in the source object.
 * @param {object} defaults - Default schema
 * @param {object} source - Source object to copy properties from
 * @returns {object} - Result has identical keys to defaults
 * @static
 * @memberof helper
*/
function merge_or_apply(defaults, source) {
	var defaultKeys = keys(defaults);
	var sourceKeys = keys(source);
	return reduce(defaultKeys, function(result, key) {
		if (sourceKeys.indexOf(key) > -1) {
			result[key] = source[key];
			return result;
		} else {
			result[key] = defaults[key];
			return result;
		}
	}, {});
}

/**
 * Given a the domain of a scale suggest the most numerous number
 * of round number ticks that it cold be divided into while still containing
 values evenly divisible by 1, 2, 2.5, 5, 10, or 25.
 * @param {array} domain - An array of two number like objects
 * @returns {integer} - Result is a single integer representing a nice number of ticks to use
 * @static
 * @memberof helper
*/
function suggest_tick_num(domain) {
	var MAX_TICKS = 10;
	var INTERVAL_BASE_VALS = [1, 2, 2.5, 5, 10, 25];
	var range = Math.abs(domain[0] - domain[1])
	var minimum = range / MAX_TICKS;
	var digits = Math.floor(range).toString().length;
	var multiplier = Math.pow(10, (digits - 2));

	var acceptable_intervals = reduce(INTERVAL_BASE_VALS, function(prev, curr) {
		var mult = curr * multiplier;

		if (mult >= minimum) {
			prev = prev.concat([mult]);
		}

		return prev;
	}, []);

	for (var i = 0; i < acceptable_intervals.length; i++) {
		var interval = acceptable_intervals[i]
		if(range % interval == 0) {
			return (range / interval) + 1
		}
	};

	return 11;
}

/**
 * Given a timezone offset in an hour:minute format and return the equivalent
 * number of minutes as a number
 * only exist in the source object.
 * @param {object} offset - A string in a hh:mm format or "Z" for no offset
 * @returns {number} - Number of minutes
 * @static
 * @memberof helper
*/
function tz_offset_to_minutes(offset) {
	if (offset == "Z") {
		return 0
	}

	var offset = offset.split(":")

	if(offset.length == 1) {
		offset = offset[0]
		split_loc = offset.length - 2
		offset = [offset.substring(0, split_loc), offset.substring(split_loc)]
	}
	sign = offset[0].indexOf("-") > -1 ? -1 : 1

	offset = offset.map(parseFloat)

	return (offset[0]*60) + (sign * offset[1])
}

function compute_text_width(text, font) {
	// re-use canvas object for better performance
	var canvas = compute_text_width.canvas || (compute_text_width.canvas = document.createElement("canvas"));
	var context = canvas.getContext("2d");
	context.font = font;
	var metrics = context.measureText(text);
	return (Math.round(metrics.width * 100) / 100);
};

function add_pref_suf(tickText, renderPrefSuf, prefix, suffix) {
	if (renderPrefSuf) {
		return [prefix, tickText, suffix].join("");
	} else {
		return tickText;
	}
}

/**
 * Helper functions!
 * @name helper
 */
var helper = {
	exactTicks : exact_ticks,
	roundToPrecision: round_to_precision,
	combineMarginPadding: combine_margin_pading,
	computeScaleDomain: compute_scale_domain,
	precision: precision,
	transformCoords: transform_coords,
	mergeOrApply: merge_or_apply,
	suggestTickNum: suggest_tick_num,
	TZOffsetToMinutes: tz_offset_to_minutes,
	computeTextWidth: compute_text_width,
	addPrefSuf: add_pref_suf
};

module.exports = helper;


/**
* ### Chart config
* Set up a configuration object for a given chart type
* @name ChartConfig
* @class
* @property {object} settings
* @property {string} settings.displayName - How this type's name should be displayed in the interface
* @property {function} settings.parser - Func to parse input for this chart type
* @property {function} settings.calculateDimensions - Func to calculate dimensions of this chart type
* @property {object} settings.display - Static display config for this chart type, such as positioning and spacing
* @property {object} settings.defaultProps - Defaults for dynamic properties that will be used to draw the chart
*/

function ChartConfig(settings) {
	this.displayName = settings.displayName;

	/**
	 * Func that parses input and settings to return newly parsed `chartProps`
	 * @param {object} config - The parsed configuration for this chart type
	 * @param {object} _chartProps - Previous `chartProps`
	 * @param {function} callback - Function to pass new `chartProps` to upon parse completion
	 * @param {object} parseOpts - Additional parse options
	 *
	 * @return {Object} chartProps - Updated `chartProps`
	 * @memberof ChartConfig
	 * @instance
	 */
	this.parser = settings.parser;

	/**
	 * Func that returns an object of `{width: N, height: N}` that will determine
	 * dimensions of a chart
	 * @param {number} width - Width of container or area that will contain the chart
	 * @param {object} model - The `chartProps` and `metadata` of the current chart
	 * @param {object} chartConfig - Parsed chart configuration
	 * @param {boolean} enableResponsive - Should we make dimensions relative to
	 * container or use preset sizes
	 * @param {number} extraHeight - Additional height we need to account for, eg
	 * from wrapped text at the footer
	 *
	 * @return {Object} dimensions - Dimensions returned by calculation
	 * @return {number} dimensions.width
	 * @return {number} dimension.height
	 * @memberof ChartConfig
	 * @instance
	 */
	this.calculateDimensions = settings.calculateDimensions;
	this.display = settings.display;
	this.defaultProps = settings.defaultProps;
}

module.exports = ChartConfig;


// Separate a flat array of `{ column: value }` objects into a multi-array, one
// for each column. Each object contains a `values` array, with each entry
// looking like:
// ```
// { entry: <rowName>, value: <callValue> }
// ```

var datePattern = /date|time|year/i;
var parseDelimInput = require("./parse-delimited-input").parser;

// Parse data by series. Options:
// checkForDate: bool | tell parser to return dates if key column is date/time/year
function dataBySeries(input, opts) {
	var series;
	opts = opts || {};

	var parsedInput = parseDelimInput(input, {
		checkForDate: opts.checkForDate,
		type: opts.type
	});

	var columnNames = parsedInput.columnNames;
	var keyColumn = columnNames.shift();

	if (columnNames.length === 0) {
		series = [{
			name: keyColumn,
			values: parsedInput.data.map(function(d) {
				return {
					name: keyColumn,
					value: d[keyColumn]
				};
			})
		}];
	} else {
		series = columnNames.map(function(header, i) {
			return {
				name: header,
				values: parsedInput.data.map(function(d) {
					return {
						name: header,
						entry: d[keyColumn],
						value: d[header]
					};
				})
			};
		});
	}

	return {
		series: series,
		input: { raw: input, type: opts.type },
		hasDate: parsedInput.hasDate && (!opts.type || opts.type == "date"),
		isNumeric: parsedInput.isNumeric && (!opts.type || opts.type == "numeric")
	};
}

module.exports = dataBySeries;


var d3 = require("d3");
var d3scale = require("d3-scale");
var clone = require("lodash/clone");
var assign = require("lodash/assign");
var reduce = require("lodash/reduce");
var map = require("lodash/map");
var processDates = require("./process-dates");
var help = require("./helper");

var scale_types = {
	"linear": _linearScale,
	"ordinal": _ordinalScale,
	"time": _timeScale
};

var defaultPadding = {
	inner: 0.2,
	outer: 0.1
};

/**
 * generateScale
 *
 * @param type
 * @param scaleOptions
 * @param data
 * @param range
 * @returns {scale: d3 scale, tickValues: array, tickFormat: format func}
 */
function generate_scale(type, scaleOptions, data, range, additionalOpts) {
	if (!scaleOptions) return {};
	return scale_types[type](scaleOptions, data, range, additionalOpts);
}

function _timeScale(scaleOptions, data, range) {
	// Return the ticks used for a time scale based on the time span and settings
	var formatAndFreq = {};
	var dateFormat = scaleOptions.dateFormat;
	var dateFrequency = scaleOptions.dateFrequency;

	// grab the first series to get the first/last dates
	var firstSeries = data[0].values;
	// make sure dates are in chronological order
	var dateRange = [
		firstSeries[0].entry,
		firstSeries[firstSeries.length - 1].entry
	].sort(d3.ascending);

	var minDate = dateRange[0];
	var maxDate = dateRange[1];

	if (dateFrequency === "auto" || dateFormat === "auto") {
		var autoSettings = processDates.autoDateFormatAndFrequency(minDate, maxDate, dateFormat, range[1]);
	}

	if (dateFrequency !== "auto") {
		var freqSettings = processDates.dateFrequencies[dateFrequency];
		formatAndFreq.frequency = freqSettings(minDate, maxDate);
	} else {
		formatAndFreq.frequency = autoSettings.frequency;
	}
	if (dateFormat !== "auto") {
		formatAndFreq.format = dateFormat;
	} else {
		formatAndFreq.format = autoSettings.format;
	}

	// use min/max dates if our auto calc comes up with empty array
	if (formatAndFreq.frequency.length === 0) {
		formatAndFreq.frequency = [minDate, maxDate];
	}

	return {
		scale: d3.time.scale().range(range).domain([minDate, maxDate]),
		tickValues: formatAndFreq.frequency,
		tickFormat: processDates.dateParsers[formatAndFreq.format]
	};
}

function _linearScale(scaleOptions, data, range) {
	return {
		scale: d3.scale.linear().domain(scaleOptions.domain).range(range),
		tickValues: scaleOptions.tickValues,
		tickFormat: function(d) {
			return help.roundToPrecision(d, scaleOptions.precision);
		}
	};
}

function _ordinalScale(scaleOptions, data, range, _padding) {
	var padding = assign({}, defaultPadding, _padding);

	var entries = map(data[0].values, function(value) {
		return value.entry;
	});

	var scale = d3scale.scaleBand()
		.domain(entries)
		.range(range)
		.paddingInner(padding.inner)
		.paddingOuter(padding.outer);

	return {
		scale: scale,
		tickValues: entries,
	};
}

//TODO: make this keyed funcs that accept a `type` param
//(like "ordinal", "time", "linear") so that we dont have to check every time
function _ordinalAdjust(scale, value) {
	var isOrdinal = scale.hasOwnProperty("bandwidth");
	if (isOrdinal) {
		return scale(value) + scale.bandwidth() / 2;
	} else {
		return scale(value);
	}
}

/**
 * get_tick_widths
 *
 * @param scaleOptions object
 * @param font string
 * @returns {width: [number], max: number}
 */
function get_tick_widths(scaleOptions, font) {
	if (!scaleOptions) return { width: [], max: 0};

	var numTicks = scaleOptions.tickValues.length - 1;
	var formattedTicks = reduce(scaleOptions.tickValues, function(prev, tick, i) {
		if (i === numTicks) {
			return prev.concat([
				scaleOptions.prefix,
				help.roundToPrecision(tick, scaleOptions.precision),
				scaleOptions.suffix
			].join(""));
		} else {
			return prev.concat(help.roundToPrecision(tick, scaleOptions.precision));
		}
	}, []);

	var widths = map(formattedTicks, function(text) {
		return help.computeTextWidth(text, font);
	});

	return {
		widths: widths,
		max: d3.max(widths.slice(0, -1)) // ignore the top tick
	};
}

module.exports = {
	generateScale: generate_scale,
	getTickWidths: get_tick_widths,
	ordinalAdjust: _ordinalAdjust
};


require("sugar-date");
var assign = require("lodash/assign");
var EventEmitter = require("events").EventEmitter;

/* Flux dispatcher */
var Dispatcher = require("../dispatcher/dispatcher");
var now = new Date();

var _session = {
	separators: detectNumberSeparators(),
	emSize: 10,
	width: 640,
	timerOn: (localStorage.hasOwnProperty("model") === true),
	nowOffset: getTZOffset(now),
	now: now
};

var CHANGE_EVENT = "change";

/**
 * ### SessionStore
 * Flux store for the current session, ie session data that persits regardless
 * interaction. Includes locale-aware settings and are unrelated to the actual
 * rendering of the chart.
*/
var SessionStore = assign({}, EventEmitter.prototype, {

	emitChange: function() {
		this.emit(CHANGE_EVENT);
	},

	addChangeListener: function(callback) {
		this.on(CHANGE_EVENT, callback);
	},

	removeChangeListener: function(callback) {
		this.removeListener(CHANGE_EVENT, callback);
	},

	/**
	 * get
	 * @return {any} - Return value at key `k`
	 * @instance
	 * @memberof SessionStore
	 */
	get: function(k) {
		return _session[k];
	},

	/**
	 * getAll
	 * @return {object} - Return all session data
	 * @instance
	 * @memberof SessionStore
	 */
	getAll: function() {
		return _session;
	}

});

function registeredCallback(payload) {
	var action = payload.action;

	switch(action.eventName) {
		case "start-timer":
			_session.timerOn = true;
			SessionStore.emitChange();
			break;

		case "stop-timer":
			_session.timerOn = false;
			SessionStore.emitChange();
			break;

		case "update-session":
			_session[action.key] = action.value;
			SessionStore.emitChange();
			break;

		default:
			// do nothing
	}

	return true;
}

// Get thousands and decimal separators based on locale
function detectNumberSeparators() {
	var n = 1000.50;
	var l = n.toLocaleString();
	var s = n.toString();
	var o = {
		decimal: l.substring(5,6),
		thousands: l.substring(1,2)
	};

	if (l.substring(5,6) == s.substring(5,6)) {
		o.decimal = ".";
	}
	if (l.substring(1,2) == s.substring(1,2)) {
		o.thousands = ",";
	}

	return o;
}

function getTZOffset(date) {
	var _offset = date.getUTCOffset().split("");
	_offset.splice(3, 0, ":")
	return _offset.join("");
}

SessionStore.dispatchToken = Dispatcher.register(registeredCallback);

module.exports = SessionStore;


// Return input with validation statuses. These get sent to the UI in order
// to prevent drawing and give error messages.

var map = require("lodash/map");
var max = require("lodash/max");
var filter = require("lodash/filter");
var some = require("lodash/some");
var unique = require("lodash/uniq");
var catchChartMistakes = require("./catch-chart-mistakes");

types = {
	"number": "numeric",
	"object": "date",
	"string": "ordinal"
};

var MAX_BYTES = 400000; // Max 400k for chartProps

function validateDataInput(chartProps) {
	var input = chartProps.input.raw;
	var series = chartProps.data;
	var hasDate = chartProps.scale.hasDate;
	var isNumeric = chartProps.scale.isNumeric;
	var type = chartProps.input.type;
	var scale = chartProps.scale;

	var inputErrors = [];

	// Check whether we have input
	if (input.length === 0) {
		inputErrors.push("EMPTY");
		return inputErrors;
	}

	if (series.length && !series[0].values[0].entry) {
		// Check that we have at least 1 value col (i.e. minimum header + 1 data col)
		inputErrors.push("TOO_FEW_SERIES");
	} else if (series.length > 12) {
		// Check whether there are too many series
		inputErrors.push("TOO_MANY_SERIES");
	}

	// Whether a column has a different number of values
	var unevenSeries = dataPointTest(
			series,
			function(val) { return val.value !== null ? (val.value === undefined || val.value.length === 0) : false;},
			function(empty,vals) { return empty.length !== vals[0].length;}
		);

	if (unevenSeries) {
		inputErrors.push("UNEVEN_SERIES");
	}

	// Whether a column has something that is NaN but is not nothing (blank) or `null`
	var nanSeries = somePointTest(
			series,
			function(val) {
				return (isNaN(val.value) && val.value !== undefined && val.value !== "");
			}
		);

	if (nanSeries) {
		inputErrors.push("NAN_VALUES");
	}

	// Are there multiple types of axis entries
	var entryTypes = unique(series[0].values.map(function(d){return typeof d.entry;}));
	if(entryTypes.length > 1 && !chartProps.input.type) {
		inputErrors.push("CANT_AUTO_TYPE");
	}

	//Whether an entry column that is supposed to be a Number is not in fact a number
	if(isNumeric || chartProps.input.type == "numeric") {
		var badNumSeries = somePointTest(
				series,
				function(val) { return isNaN(val.entry); }
			);

		if (badNumSeries) {
			inputErrors.push("NAN_VALUES");
		}
	}

	// Whether an entry column that is supposed to be a date is not in fact a date
	if(hasDate || chartProps.input.type == "date") {
		var badDateSeries = somePointTest(
				series,
				function(val) { return !val.entry.getTime || isNaN(val.entry.getTime()); }
			);

		if (badDateSeries) {
			inputErrors.push("NOT_DATES");
		}

		var tz_pattern = /([+-]\d\d:*\d\d)/gi;
		var found_timezones = input.match(tz_pattern);
		if(found_timezones && found_timezones.length != series[0].values.length) {
			inputErrors.push("UNEVEN_TZ");
		}
	}

	// Whether a column has numbers that should be divided
	var largeNumbers = somePointTest(
			series,
			function(val) { return Math.floor(val.value).toString().length > 4; },
			function(largeNums, vals) { return largeNums.length > 0;}
		);

	if (largeNumbers) {
		inputErrors.push("LARGE_NUMBERS");
	}

	// Whether the number of bytes in chartProps exceeds our defined maximum
	if (catchChartMistakes.tooMuchData(chartProps)) {
		inputErrors.push("TOO_MUCH_DATA");
	}

	// Whether axis ticks divide evenly
	if (!catchChartMistakes.axisTicksEven(scale.primaryScale)) {
		inputErrors.push("UNEVEN_TICKS");
	}

	// Whether axis is missing pref and suf
	if (catchChartMistakes.noPrefixSuffix(scale.primaryScale)) {
		inputErrors.push("NO_PREFIX_SUFFIX");
	}

	return inputErrors;

}

// Func that checks wheter a single data point failes a test.
// Will return as soon as failure is found
function somePointTest(series, someTest) {
	return some(series, function(s) {
		return some(s.values, someTest);
	});
}

// Func that checks wheter all data points pass a test
function dataPointTest(series, filterTest, someTest) {
	var vals = map(series, function(d,i) {
		return filter(d.values, filterTest);
	});

	return some(vals, function(n) {
		return someTest(n,vals);
	});
}

module.exports = validateDataInput;


var assign = require("lodash/assign");
var clone = require("lodash/clone");
var some = require("lodash/some");
var filter = require("lodash/filter");
var EventEmitter = require("events").EventEmitter;

/* Flux dispatcher */
var Dispatcher = require("../dispatcher/dispatcher");

var errorNames = require("../util/error-names");
var validateDataInput = require("../util/validate-data-input");
var ChartPropertiesStore = require("./ChartPropertiesStore");

/* Singleton that houses errors */
var _errors = { valid: true, messages: [] };
var CHANGE_EVENT = "change";

/**
 * ### ErrorStore.js
 * Store for errors/warnings to users about the bad/dumb things they are
 * probably doing
*/
var ErrorStore = assign({}, EventEmitter.prototype, {

	emitChange: function() {
		this.emit(CHANGE_EVENT);
	},

	addChangeListener: function(callback) {
		this.on(CHANGE_EVENT, callback);
	},

	removeChangeListener: function(callback) {
		this.removeListener(CHANGE_EVENT, callback);
	},

	/**
	 * get
	 * @param k
	 * @return {any} - Return value at key `k`
	 * @instance
	 * @memberof ErrorStore
	 */
	get: function(k) {
		return _errors[k];
	},

	/**
	 * getAll
	 * @return {object} - Return all errors
	 * @instance
	 * @memberof ErrorStore
	 */
	getAll: function() {
		return clone(_errors);
	},

	/**
	 * clear
	 * Set errors to empty
	 * @instance
	 * @memberof ErrorStore
	 */
	clear: function() {
		_errors = {};
	}

});

/* Respond to actions coming from the dispatcher */
function registeredCallback(payload) {
	var action = payload.action;
	var chartProps;
	var error_messages;
	var input_errors;

	switch(action.eventName) {
		/* *
		* Data input updated or reparse called
		* */
		case "update-data-input":
		case "update-and-reparse":

			Dispatcher.waitFor([ChartPropertiesStore.dispatchToken]);
			chartProps = ChartPropertiesStore.getAll();

			error_messages = [];
			input_errors = validateDataInput(chartProps);
			error_messages = error_messages.concat(input_errors);

			_errors.messages = error_messages.map(function(err_name) {
				return errorNames[err_name];
			});

			var isInvalid = some(_errors.messages, { type: "error" } );
			_errors.valid = !isInvalid;

			ErrorStore.emitChange();
			break;

		default:
			// do nothing
	}

	return true;

}

/* Respond to actions coming from the dispatcher */
ErrorStore.dispatchToken = Dispatcher.register(registeredCallback);
module.exports = ErrorStore;


var chartSizes = require("../../config/chart-sizes");

// TODO: jsDocify this if it works

/**
 * see [ChartConfig#calculateDimensions](#chartconfig/calculatedimensions)
 * @see ChartConfig#calculateDimensions
 * @instance
 * @memberof xy_config
 */
function calculate_xy_dimensions(width, opts) {
	var height;
	var aspectRatio = opts.displayConfig.aspectRatio;
	var metadata = opts.metadata;

	if (metadata.size == "auto" || opts.enableResponsive) {
		// use current width
	} else {
		width = chartSizes[metadata.size].width;
	}

	switch (metadata.size) {
		case "auto":
			height = width * aspectRatio.wide;
			break;

		case 'medium':
			height = width * aspectRatio.wide;
			break;

		case "spotLong":
			height = width * aspectRatio.longSpot;
			break;

		case "spotSmall":
			height = width * aspectRatio.smallSpot;
			break;

		default:
			height = width * aspectRatio.wide;
	}

	return {
		width: width,
		height: height
	};
}

module.exports = calculate_xy_dimensions;


import Immutable from 'immutable';

// TODO need to find a way to reference Redux's init for compatability
const ActionTypes = { INIT: 'INIT' };
const isImmutable = (obj) => {
  return Immutable.Iterable.isIterable(obj);
};

/* eslint-disable no-console */

function getErrorMessage(key, action) {
  var actionType = action && action.type;
  var actionName = actionType && `"${actionType.toString()}"` || 'an action';

  return (
    `Reducer "${key}" returned undefined handling ${actionName}. ` +
    `To ignore an action, you must explicitly return the previous state.`
  );
}

function verifyStateShape(initialState, currentState) {
  var reducerKeys = currentState.keySeq();

  if (reducerKeys.size === 0) {
    console.error(
      'Store does not have a valid reducer. Make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    );
    return;
  }

  if (!isImmutable(initialState)) {
    console.error(
      'initialState has unexpected type of "' +
      ({}).toString.call(initialState).match(/\s([a-z|A-Z]+)/)[1] +
      '". Expected initialState to be an instance of Immutable.Iterable with the following ' +
      `keys: "${reducerKeys.join('", "')}"`
    );
    return;
  }

  const unexpectedKeys = initialState.keySeq().filter(
   key => reducerKeys.indexOf(key) < 0
  );

  if (unexpectedKeys.size > 0) {
    console.error(
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" in initialState will be ignored. ` +
      `Expected to find one of the known reducer keys instead: "${reducerKeys.join('", "')}"`
    );
  }
}

/**
 * Turns an object whose values are different reducer functions, into a single
 * reducer function. It will call every child reducer, and gather their results
 * into a single state object, whose keys correspond to the keys of the passed
 * reducer functions.
 *
 * @param {Object} reducers An object whose values correspond to different
 * reducer functions that need to be combined into one. One handy way to obtain
 * it is to use ES6 `import * as reducers` syntax. The reducers may never return
 * undefined for any action. Instead, they should return their initial state
 * if the state passed to them was undefined, and the current state for any
 * unrecognized action.
 *
 * @returns {Function} A reducer function that invokes every reducer inside the
 * passed object, and builds a state object with the same shape.
 */

export default function combineReducers(reducers) {
  reducers = isImmutable(reducers) ? reducers : Immutable.fromJS(reducers);
  const finalReducers = reducers.filter(v => typeof v === 'function');

  finalReducers.forEach((reducer, key) => {
    if (typeof reducer(undefined, { type: ActionTypes.INIT }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined, you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined.`
      );
    }

    var type = Math.random().toString(36).substring(7).split('').join('.');
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead, you must return the ` +
        `current state for any unknown actions, unless it is undefined, ` +
        `in which case you must return the initial state, regardless of the ` +
        `action type. The initial state may not be undefined.`
      );
    }
  });

  var defaultState = finalReducers.map(r => undefined);
  var stateShapeVerified;

  return function combination(state = defaultState, action) {

    let finalState = state;
    finalReducers.forEach( ( reducer, key ) => {
      const oldState = state.get( key );
      const newState = reducer( oldState, action );

      if (typeof newState === 'undefined') {
        throw new Error(getErrorMessage(key, action));
      }

      finalState = finalState.set( key, newState );
    });

    if ((
      // Node-like CommonJS environments (Browserify, Webpack)
      typeof process !== 'undefined' &&
      typeof process.env !== 'undefined' &&
      process.env.NODE_ENV !== 'production'
    ) ||
      // React Native
      typeof __DEV__ !== 'undefined' &&
      __DEV__ // eslint-disable-line no-undef
    ) {
      if (!stateShapeVerified) {
        verifyStateShape(state, finalState);
        stateShapeVerified = true;
      }
    }

    return finalState;
  };
}


'use strict'

var url = require('url')
var propose = require('propose')
var visit = require('unist-util-visit')
var definitions = require('mdast-util-definitions')
var toString = require('mdast-util-to-string')
var hostedGitInfo = require('hosted-git-info')
var urljoin = require('urljoin')
var slugs = require('github-slugger')()
var xtend = require('xtend/mutable.js')

// Optional Node dependencies.
var fs
var path

try {
  fs = require('fs')
  path = require('path')
} catch (error) {}

module.exports = validateLinks

var referenceId = 'remarkValidateLinksReferences'
var landmarkId = 'remarkValidateLinksLandmarks'
var sourceId = 'remark-validate-links'
var headingRuleId = 'missing-heading'
var headingInFileRuleId = 'missing-heading-in-file'
var fileRuleId = 'missing-file'

cliCompleter.pluginId = sourceId

var parse = url.parse
var format = url.format

var viewPaths = {
  github: 'blob',
  gitlab: 'blob',
  bitbucket: 'src'
}

var headingPrefixes = {
  github: '#',
  gitlab: '#',
  bitbucket: '#markdown-header-'
}

var lineLinks = {
  github: true,
  gitlab: true
}

var lineExpression = /^#?l\d/i

function validateLinks(options, fileSet) {
  var repo = (options || {}).repository
  var info
  var pack

  // Try to get the repo from `package.json` when not given.
  if (!repo && fs && fileSet) {
    try {
      pack = fileSet.files[0].cwd
      pack = JSON.parse(fs.readFileSync(path.resolve(pack, 'package.json')))
    } catch (error) {
      pack = {}
    }

    repo = pack.repository ? pack.repository.url || pack.repository : ''
  }

  if (repo) {
    info = hostedGitInfo.fromUrl(repo)

    if (!info) {
      throw new Error(
        'remark-validate-links cannot parse `repository` (`' + repo + '`)'
      )
    } else if (info.domain === 'gist.github.com') {
      throw new Error(
        'remark-validate-links does not support gist repositories'
      )
    }
  }

  // Attach a plugin that adds our transformer after it.
  this.use(subplugin)

  // Attach a `completer`.
  if (fileSet) {
    fileSet.use(cliCompleter)
  } else {
    this.use(apiCompleter)
  }

  function subplugin() {
    // Expose transformer.
    return transformerFactory(fileSet, info)
  }
}

// Completer for the API (one file, only headings are checked).
function apiCompleter() {
  return apiTransform
}

function apiTransform(tree, file) {
  checkFactory(file.data[landmarkId])(file)
}

// Completer for the CLI (multiple files, and support to add more).
function cliCompleter(set, done) {
  var exposed = {}

  set.valueOf().forEach(expose)
  set.valueOf().forEach(checkFactory(exposed))

  done()

  function expose(file) {
    var landmarks = file.data[landmarkId]

    if (landmarks) {
      xtend(exposed, landmarks)
    }
  }
}

function checkFactory(exposed) {
  return check
  function check(file) {
    /* istanbul ignore else - stdin */
    if (file.path) {
      validate(exposed, file)
    }
  }
}

// Factory to create a transformer based on the given info and set.
function transformerFactory(fileSet, info) {
  return transformer

  // Transformer. Adds references files to the set.
  function transformer(ast, file) {
    var filePath = file.path
    var space = file.data
    var links = []
    var landmarks = {}
    var references
    var current
    var link
    var pathname

    /* istanbul ignore if - stdin */
    if (!filePath) {
      return
    }

    references = gatherReferences(file, ast, info, fileSet)
    current = getPathname(filePath)

    for (link in references) {
      pathname = getPathname(link)

      if (
        fileSet &&
        pathname !== current &&
        getHash(link) &&
        links.indexOf(pathname) === -1
      ) {
        links.push(pathname)
        fileSet.add(pathname)
      }
    }

    landmarks[filePath] = true

    slugs.reset()

    visit(ast, mark)

    space[referenceId] = references
    space[landmarkId] = landmarks

    function mark(node) {
      var data = node.data || {}
      var props = data.hProperties || {}
      var id = props.name || props.id || data.id

      if (!id && node.type === 'heading') {
        id = slugs.slug(toString(node))
      }

      if (id) {
        landmarks[filePath + '#' + id] = true
      }
    }
  }
}

// Check if `file` references headings or files not in `exposed`.
function validate(exposed, file) {
  var references = file.data[referenceId]
  var filePath = file.path
  var reference
  var nodes
  var real
  var hash
  var pathname
  var warning
  var suggestion
  var ruleId

  for (reference in references) {
    nodes = references[reference]
    real = exposed[reference]
    hash = getHash(reference)

    // Check if files without `hash` can be linked to.  Because there’s no need
    // to inspect those files for headings they are not added to remark.  This
    // is especially useful because they might be non-markdown files. Here we
    // check if they exist.
    if ((real === undefined || real === null) && !hash && fs) {
      real = fs.existsSync(path.join(file.cwd, decodeURI(reference)))
      references[reference] = real
    }

    if (!real) {
      if (hash) {
        pathname = getPathname(reference)
        warning = 'Link to unknown heading'
        ruleId = headingRuleId

        if (pathname !== filePath) {
          warning += ' in `' + pathname + '`'
          ruleId = headingInFileRuleId
        }

        warning += ': `' + hash + '`'
      } else {
        warning = 'Link to unknown file: `' + decodeURI(reference) + '`'
        ruleId = fileRuleId
      }

      suggestion = getClosest(reference, exposed)

      if (suggestion) {
        warning += '. Did you mean `' + suggestion + '`'
      }

      warnAll(file, nodes, warning, ruleId)
    }
  }
}

// Gather references: a map of file-paths references to be one or more nodes.
function gatherReferences(file, tree, info, fileSet) {
  var cache = {}
  var getDefinition = definitions(tree)
  var prefix = ''
  var headingPrefix = '#'
  var lines

  if (info && info.type in viewPaths) {
    prefix = '/' + info.path() + '/' + viewPaths[info.type] + '/'
  }

  if (info && info.type in headingPrefixes) {
    headingPrefix = headingPrefixes[info.type]
  }

  lines = info && info.type in lineLinks ? lineLinks[info.type] : false

  visit(tree, ['link', 'image', 'linkReference', 'imageReference'], onresource)

  return cache

  // Handle resources.
  function onresource(node) {
    var link = node.url
    var definition
    var index
    var uri
    var pathname
    var hash

    // Handle references.
    if (node.identifier) {
      definition = getDefinition(node.identifier)
      link = definition && definition.url
    }

    // Ignore definitions without url.
    if (!link) {
      return
    }

    uri = parse(link)

    // Drop `?search`
    uri.search = ''
    link = format(uri)

    if (!fileSet && (uri.hostname || uri.pathname)) {
      return
    }

    if (!uri.hostname) {
      if (lines && lineExpression.test(uri.hash)) {
        uri.hash = ''
      }

      // Handle hashes, or relative files.
      if (!uri.pathname && uri.hash) {
        link = file.path + uri.hash
        uri = parse(link)
      } else {
        link = urljoin(file.dirname, link)

        if (uri.hash) {
          link += uri.hash
        }

        uri = parse(link)
      }
    }

    // Handle full links.
    if (uri.hostname) {
      if (!prefix || !fileSet) {
        return
      }

      if (
        uri.hostname !== info.domain ||
        uri.pathname.slice(0, prefix.length) !== prefix
      ) {
        return
      }

      link = uri.pathname.slice(prefix.length) + (uri.hash || '')

      // Things get interesting here: branches: `foo/bar/baz` could be `baz` on
      // the `foo/bar` branch, or, `baz` in the `bar` directory on the `foo`
      // branch.
      //  Currently, we’re ignoring this and just not supporting branches.
      link = link.slice(link.indexOf('/') + 1)
    }

    // Handle file links, or combinations of files and hashes.
    index = link.indexOf(headingPrefix)

    if (index === -1) {
      pathname = link
      hash = null
    } else {
      pathname = link.slice(0, index)
      hash = link.slice(index + headingPrefix.length)

      if (lines && lineExpression.test(hash)) {
        hash = null
      }
    }

    if (!cache[pathname]) {
      cache[pathname] = []
    }

    cache[pathname].push(node)

    if (hash) {
      link = pathname + '#' + hash

      if (!cache[link]) {
        cache[link] = []
      }

      cache[link].push(node)
    }
  }
}

// Utility to warn `reason` for each node in `nodes` on `file`.
function warnAll(file, nodes, reason, ruleId) {
  nodes.forEach(one)

  function one(node) {
    file.message(reason, node, [sourceId, ruleId].join(':'))
  }
}

// Suggest a possible similar reference.
function getClosest(pathname, references) {
  var hash = getHash(pathname)
  var base = getPathname(pathname)
  var dictionary = []
  var reference
  var subhash
  var subbase

  for (reference in references) {
    subbase = getPathname(reference)
    subhash = getHash(reference)

    if (getPathname(reference) === base) {
      if (subhash && hash) {
        dictionary.push(subhash)
      }
    } else if (!subhash && !hash) {
      dictionary.push(subbase)
    }
  }

  return propose(hash ? hash : base, dictionary, {threshold: 0.7})
}

// Get the `hash` of `uri`, if applicable.
function getHash(uri) {
  var hash = parse(uri).hash
  return hash ? hash.slice(1) : null
}

// Get the `pathname` of `uri`, if applicable.
function getPathname(uri) {
  return parse(uri).pathname
}


'use strict';

var path = require('path');
var postcss = require('postcss');
var diff = require('diff');
var chalk = require('chalk');

module.exports = function(grunt) {
    var options;
    var processor;

    /**
     * Returns an input map contents if a custom map path was specified
     * @param {string} from Input CSS path
     * @returns {?string}
     */
    function getPrevMap(from) {
        if (typeof options.map.prev === 'string') {
            var mapPath = options.map.prev + path.basename(from) + '.map';

            if (grunt.file.exists(mapPath)) {
                return grunt.file.read(mapPath);
            }
        }
    }

    /**
     * @param {string} to Output CSS path
     * @returns {string}
     */
    function getSourcemapPath(to) {
        return path.join(options.map.annotation, path.basename(to)) + '.map';
    }

    /**
     * @param {string} to Output CSS path
     * @returns {boolean|string}
     */
    function getAnnotation(to) {
        var annotation = true;

        if (typeof options.map.annotation === 'boolean') {
            annotation = options.map.annotation;
        }

        if (typeof options.map.annotation === 'string') {
            annotation = path.relative(path.dirname(to), getSourcemapPath(to)).replace(/\\/g, '/');
        }

        return annotation;
    }

    /**
     * @param {string} input Input CSS contents
     * @param {string} from Input CSS path
     * @param {string} to Output CSS path
     * @returns {{css: string, map: ?string}}
     */
    function process(input, from, to) {
        return processor.process(input, {
            map: (typeof options.map === 'boolean') ? options.map : {
                prev: getPrevMap(from),
                inline: (typeof options.map.inline === 'boolean') ? options.map.inline : true,
                annotation: getAnnotation(to),
                sourcesContent: (typeof options.map.sourcesContent === 'boolean') ? options.map.sourcesContent : true
            },
            from: from,
            to: to,
            parser: options.parser,
            stringifier: options.stringifier,
            syntax: options.syntax
        });
    }

    /**
     * @param {string} msg Log message
     */
    function log(msg) {
        grunt.verbose.writeln(msg);
    }

    grunt.registerMultiTask('postcss', 'Process CSS files.', function() {
        options = this.options({
            processors: [],
            map: false,
            diff: false,
            safe: false,
            failOnError: false,
            writeDest: true
        });

        var tally = {
            sheets: 0,
            maps: 0,
            diffs: 0,
            issues: 0
        };

        if (typeof options.processors === 'function') {
            processor = postcss(options.processors.call());
        } else {
            processor = postcss(options.processors);
        }

        var done = this.async();
        var tasks = [];

        this.files.forEach(function(f) {
            var src = f.src.filter(function(filepath) {
                if (!grunt.file.exists(filepath)) {
                    grunt.log.warn('Source file ' + chalk.cyan(filepath) + ' not found.');

                    return false;
                }

                return true;
            });

            if (src.length === 0) {
                grunt.log.error('No source files were found.');

                return done();
            }

            Array.prototype.push.apply(tasks, src.map(function(filepath) {
                var dest = f.dest || filepath;
                var input = grunt.file.read(filepath);

                return process(input, filepath, dest).then(function(result) {
                    var warnings = result.warnings();

                    tally.issues += warnings.length;

                    warnings.forEach(function(msg) {
                        grunt.log.error(msg.toString());
                    });

                    if (options.writeDest) {
                        grunt.file.write(dest, result.css);
                        log('File ' + chalk.cyan(dest) + ' created.');
                    }

                    tally.sheets += 1;

                    if (result.map) {
                        var mapDest = dest + '.map';

                        if (typeof options.map.annotation === 'string') {
                            mapDest = getSourcemapPath(dest);
                        }

                        grunt.file.write(mapDest, result.map.toString());
                        log('File ' + chalk.cyan(dest + '.map') + ' created (source map).');

                        tally.maps += 1;
                    }

                    if (options.diff) {
                        var diffPath = (typeof options.diff === 'string') ? options.diff : dest + '.diff';

                        grunt.file.write(diffPath, diff.createPatch(dest, input, result.css));
                        log('File ' + chalk.cyan(diffPath) + ' created (diff).');

                        tally.diffs += 1;
                    }
                });
            }));
        });

        Promise.all(tasks).then(function() {
            if (tally.sheets) {
                if (options.writeDest) {
                    grunt.log.ok(tally.sheets + ' processed ' + grunt.util.pluralize(tally.sheets, 'stylesheet/stylesheets') + ' created.');
                } else {
                    grunt.log.ok(tally.sheets + ' ' + grunt.util.pluralize(tally.sheets, 'stylesheet/stylesheets') + ' processed, no files written.');
                }
            }

            if (tally.maps) {
                grunt.log.ok(tally.maps + ' ' + grunt.util.pluralize(tally.maps, 'sourcemap/sourcemaps') + ' created.');
            }

            if (tally.diffs) {
                grunt.log.ok(tally.diffs + ' ' + grunt.util.pluralize(tally.diffs, 'diff/diffs') + ' created.');
            }

            if (tally.issues) {
                grunt.log.error(tally.issues + ' ' + grunt.util.pluralize(tally.issues, 'issue/issues') + ' found.');

                if (options.failOnError) {
                    return done(false);
                }
            }

            done();
        }).catch(function(error) {
            if (error.name === 'CssSyntaxError') {
                grunt.fatal(error.message + error.showSourceCode());
            } else {
                grunt.fatal(error);
            }

            done(error);
        });
    });
};


//set the start directory and file the middleware override will then ensure that all non file routes get redirected back to the SPA
var startDir = '/demo';
var startFile = 'index.html';
var startPath = startDir + '/' + startFile;

module.exports = {
    logLevel: 'silent',
    browser: 'chrome',
    startPath: startPath,
    server: {
        middleware: {
            0: null, //to add back in logging comment this line out
            1: function (req, res, next) {
                if(req.url.indexOf('.') === -1 && req.url.indexOf(startDir) > -1){
                    req.url = startPath;
                }

                return next();
            }
        }
    }
};


module.exports = strip

function strip() {
  return one
}

/* Expose modifiers for available node types.
 * Node types not listed here are not changed
 * (but their children are). */
var map = {}

map.heading = paragraph
map.text = text
map.inlineCode = text
map.image = image
map.imageReference = image
map.break = lineBreak

map.blockquote = children
map.list = children
map.listItem = children
map.strong = children
map.emphasis = children
map.delete = children
map.link = children
map.linkReference = children

map.code = empty
map.horizontalRule = empty
map.thematicBreak = empty
map.html = empty
map.table = empty
map.tableCell = empty
map.definition = empty
map.yaml = empty
map.toml = empty

/* One node. */
function one(node) {
  var type = node && node.type

  if (type in map) {
    node = map[type](node)
  }

  if ('length' in node) {
    node = all(node)
  }

  if (node.children) {
    node.children = all(node.children)
  }

  return node
}

/* Multiple nodes. */
function all(nodes) {
  var index = -1
  var length = nodes.length
  var result = []
  var value

  while (++index < length) {
    value = one(nodes[index])

    if (value && typeof value.length === 'number') {
      result = result.concat(value.map(one))
    } else {
      result.push(value)
    }
  }

  return clean(result)
}

/* Clean nodes: merges text's. */
function clean(values) {
  var index = -1
  var length = values.length
  var result = []
  var prev = null
  var value

  while (++index < length) {
    value = values[index]

    if (prev && 'value' in value && value.type === prev.type) {
      prev.value += value.value
    } else {
      result.push(value)
      prev = value
    }
  }

  return result
}

/* Return an stringified image. */
function image(token) {
  return {type: 'text', value: token.alt || token.title || ''}
}

/* Return `token`s value. */
function text(token) {
  return {type: 'text', value: token.value}
}

/* Return a paragraph. */
function paragraph(token) {
  return {type: 'paragraph', children: token.children}
}

/* Return the concatenation of `token`s children. */
function children(token) {
  return token.children
}

/* Return line break. */
function lineBreak() {
  return {type: 'text', value: '\n'}
}

/* Return nothing. */
function empty() {
  return {type: 'text', value: ''}
}


var assert = require('assert');
var dgram = require('dgram');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var Query = require('./query');
var DnsError = require('./errors');

///--- Globals

var sprintf = util.format;

var ExceptionError = DnsError.ExceptionError;
var ProtocolError = DnsError.ProtocolError;

///--- API

function Server(options) {
        if (typeof(options) !== 'object')
                throw new TypeError('options (object) is required');

        this._log = options.log.child({component: 'agent'}, true);
        this._name = options.name || "named";
        this._socket = null;

}
util.inherits(Server, EventEmitter);

Server.prototype.close = function close(callback) {
        if (typeof (callback) === 'function')
                this._socket.once('close', callback);

        this._socket.close();
};

Server.prototype.listen = function listen(port, address, callback) {
        if (!port)
                throw new TypeError('port (number) is required');

        if (typeof (address) === 'function' || !address) {
                callback = address;
                address = '0.0.0.0';
        }

        var self = this;

        this._socket = dgram.createSocket('udp6');
        this._socket.once('listening', function () {
                self.emit('listening');
                if (typeof (callback) === 'function')
                        process.nextTick(callback);
        });
        this._socket.on('close', function onSocketClose() {
                self.emit('close');
        });
        this._socket.on('error', function onSocketError(err) {
                self.emit('error', err);
        });
        this._socket.on('message', function (buffer, rinfo) {
                var decoded;
                var query;
                var raw = {
                        buf: buffer,
                        len: rinfo.size
                };

                var src = {
                        family: 'udp6',
                        address: rinfo.address,
                        port: rinfo.port
                };

                try {
                        decoded = Query.parse(raw, src);
                        query = Query.createQuery(decoded);
                } catch (e) {
                        self.emit('clientError',
                                  new ProtocolError('invalid DNS datagram'));
                }

                if (query === undefined || query === null) {
                        return;
                }

                query.respond = function respond() {
                        self.send(query);
                };

                try {
                        self.emit('query', query);
                } catch (e) {
                        self._log.warn({
                                err: e
                        }, 'query handler threw an uncaughtException');
                        self.emit('uncaughtException', e);
                }
        });
        this._socket.bind(port, address);
};

Server.prototype.send = function send(res) {
        assert.ok(res);

        try {
                res._flags.qr = 1;  // replace with function
                res.encode();
        } catch (e) {
                this._log.trace({err: e}, 'send: uncaughtException');
                var err = new ExceptionError('unable to encode response');
                this.emit('uncaughtException', err);
                return false;
        }

        var addr = res._client.address;
        var buf = res._raw.buf;
        var len = res._raw.len;
        var port = res._client.port;
        var self = this;

        this._log.trace({
                adddress: addr,
                port: port,
                len: len
        }, 'send: writing DNS message to socket');

        this._socket.send(buf, 0, len, port, addr, function (err, bytes) {
                if (err) {
                        self._log.warn({
                                adddress: addr,
                                port: port,
                                err: err
                        }, 'send: unable to send response');
                        self.emit('error', new ExceptionError(err.message));
                } else {
                        self._log.trace({
                                adddress: addr,
                                port: port
                        }, 'send: DNS response sent');
                        self.emit('after', res, bytes);
                }
        });
};

Server.prototype.toString = function toString() {
        var str = '[object named.Server <';
        str += sprintf('name=%s, ', this._name);
        str += sprintf('socket=%j', this._socket ? this._socket.address() : {});
        str += '>]';
        return (str);
};

///--- Exports

module.exports = Server;


/*

  # Protocol

  Stores protocol definitions and their primitives as well as any other
  associated protocol constants

  ## References

  http://tools.ietf.org/html/rfc1035
  http://tools.ietf.org/html/rfc4408
  http://tools.ietf.org/html/rfc2782
  http://tools.ietf.org/html/rfc3596

  ## Notes

  * Even though RFC1035 says that questions should support multiple queries, the
  reality is *nobody* does this. MS DNS doesn't support it and apparently BIND
  doesn't support it as well. That implies no client side tools do either - so
  we will not worry about that complication.

  * DNS Extensions have been proposed, but another case of chicken-and-egg.
  These extensions make it _possible_ to have DNS queries over 512 bytes in
  length, but because it is not universally supported, nobody does it.

*/

var ipaddr = require('ipaddr.js');

QCLASS_IN   = 0x01, // the internet
QCLASS_CS   = 0x02, // obsolete
QCLASS_CH   = 0x03, // chaos class. yes this actually exists
QCLASS_HS   = 0x04, // Hesiod
DNS_ENOERR  = 0x00, // No error
DNS_EFORMAT = 0x01, // Formatting Error
DNS_ESERVER = 0x02, // server it unable to process
DNS_ENONAME = 0x03, // name does not exist
DNS_ENOTIMP = 0x04, // feature not implemented on this server
DNS_EREFUSE = 0x05, // refused for policy reasons

Formats = {};

Formats.answer = {
        name: { type: '_nsName' },
        rtype: { type: 'UInt16BE' },
        rclass: { type: 'UInt16BE' },
        rttl: { type: 'UInt32BE' },
        rdata: { type: '_nsData' },     // rdlength is prepended to this field
};

Formats.question = {
        name: { type: '_nsName' },
        type: { type: 'UInt16BE' },
        qclass: { type: 'UInt16BE' }
};

Formats.header = {
        id: { type: 'UInt16BE' },
        flags: { type: '_nsFlags' },
        qdCount: { type: 'UInt16BE' },
        anCount: { type: 'UInt16BE' },
        nsCount: { type: 'UInt16BE' },
        srCount: { type: 'UInt16BE' },
};

Formats.soa = {
        host: { type: '_nsName' },
        admin: { type: '_nsName' },
        serial: { type: 'UInt32BE' },
        refresh: { type: 'UInt32BE' },
        retry: { type: 'UInt32BE' },
        expire: { type: 'UInt32BE' },
        ttl: { type: 'UInt32BE' }
};

Formats.mx = {
        priority: { type: 'UInt16BE' },
        exchange: { type: '_nsName' }
};

Formats.txt = {
        text: { type: '_nsText' }
};

Formats.srv = {
        priority: { type: 'UInt16BE' },
        weight: { type: 'UInt16BE' },
        port: { type: 'UInt16BE' },
        target: { type: '_nsName' }
};

Formats.queryMessage = {
        header: { type: { format: 'header' } },
        question: { type: { format: 'question' } }
};

Formats.answerMessage = {
        header: { type: { format: 'header' } },
        question: { type: { format: 'question' } },
        answers: { type: '_nsAnswers' }
};

// turns a dotted-decimal address into a UInt32
function parseIPv4(addr) {
        if (typeof(addr) !== 'string')
                throw new TypeError('addr (string) is required');

        var octets = addr.split(/\./).map(function (octet) {
                return (parseInt(octet, 10));
        });
        if (octets.length !== 4)
                throw new TypeError('valid IP address required');

        var uint32 = ((octets[0] * Math.pow(256, 3)) +
                      (octets[1] * Math.pow(256, 2)) +
                      (octets[2] * 256) + octets[3]);
        return (uint32);
}

function parseIPv6(addr) {
        if (typeof(addr) !== 'string')
                throw new TypeError('addr (string) is required');

        var addr;
        try {
                addr = ipaddr.parse(addr);
        } catch (e) {
                return false;
        }

        return addr.parts;

}

// each of these serializers are functions which accept a value to serialize
// and must returns the serialized value as a buffer
var serializers = {
        'UInt32BE': {
                encoder: function(v) {
                        var b = new Buffer(4);
                        b.writeUInt32BE(v, 0);
                        return b;
                },
                decoder: function(v, p) {
                        return v.readUInt32BE(v, p);
                }
        },
        'UInt16BE': {
                encoder: function(v) {
                        var b = new Buffer(2);
                        b.writeUInt16BE(v, 0);
                        return b;
                },
                decoder: function(v, p) {
                        var res = v.readUInt16BE(p);
                        return { val: res, len: 2 };
                }
        },
        '_nsAnswers': {
                encoder: function(v) {
                        var s = 0, p = 0, answers = [];
                        for (i in v) {
                                var r = encode(v[i], 'answer');
                                answers.push(r);
                                s = s + r.length;
                        }
                        b = new Buffer(s);
                        for (n in answers) {
                                answers[n].copy(b, p);
                                p = p + answers[n].length;
                        }
                        return b;
                }
        },
        '_nsFlags': {
                encoder: function(v) {
                        if (typeof(v) !== 'object') {
                                throw new TypeError("flag must be an object");
                        }
                        var b = new Buffer(2);
                        var f = 0x0000;
                        f = f | (v.qr << 15);
                        f = f | (v.opcode << 11);
                        f = f | (v.aa << 10);
                        f = f | (v.tc << 9);
                        f = f | (v.rd << 8);
                        f = f | (v.ra << 7);
                        f = f | (v.z  << 6);
                        f = f | (v.ad << 5);
                        f = f | (v.cd << 4);
                        f = f | v.rcode;
                        b.writeUInt16BE(f, 0);
                        return b;
                },
                decoder: function(v, p) {
                        var flags, f;
                        flags = v.readUInt16BE(p);
                        f = {
                                qr:     (( flags & 0x8000 )) ? true : false,
                                opcode: (( flags & 0x7800 )),
                                aa:     (( flags & 0x0400 )) ? true : false,
                                tc:     (( flags & 0x0200 )) ? true : false,
                                rd:     (( flags & 0x0100 )) ? true : false,
                                ra:     (( flags & 0x0080 )) ? true : false,
                                z:      (( flags & 0x0040 )) ? true : false,
                                ad:     (( flags & 0x0020 )) ? true : false,
                                cd:     (( flags & 0x0010 )) ? true : false,
                                rcode:  (( flags & 0x000F ))
                        };
                        return { val: f, len: 2 };
                }
        },
        '_nsIP4': {
                encoder: function(v) {
                        var a, b;
                        a = parseIPv4(v);
                        b = new Buffer(4);
                        b.writeUInt32BE(a, 0);
                        return b;
                }
        },
        '_nsIP6': {
                encoder: function(v) {
                        var a, b, i = 0;
                        a = parseIPv6(v);
                        b = new Buffer(16);
                        for (var i=0; i<8; i++) {
                                b.writeUInt16BE(a[i], i * 2);
                        }
                        return b;
                }
        },
        '_nsName': {
                encoder: function(v) {
                        if (typeof(v) !== 'string')
                                throw new TypeError('name (string) is required')
                        var n = v.split(/\./);

                        var b = new Buffer(n.toString().length + 2);
                        var o = 0; //offset

                        for (var i = 0; i < n.length; i++) {
                                var l = n[i].length;
                                b[o] = l;
                                b.write(n[i], ++o, l, 'utf8');
                                o += l;
                        }
                        b[o] = 0x00;

                        return b;
                },
                decoder: function(v, p) {
                        var rle, start = p, name = [];

                        rlen = v.readUInt8(p);
                        while (rlen != 0x00) {
                                p++;
                                var t = v.slice(p, p + rlen);
                                name.push(t.toString());
                                p = p + rlen;
                                rlen = v.readUInt8(p);
                        }

                        return { val: name.join('.'), len: (p - start + 1) };
                }
        },
        '_nsText': {
                encoder: function(v) {
                        var b;
                        b = new Buffer(v.length + 1);
                        b.writeUInt8(v.length, 0);
                        b.write(v, 1);
                        return b;
                }
        },
        '_nsData': {
                encoder: function(v, t) {
                        var r, b, l;
                        // TODO with the new queryTypes layout this could probably be mostly
                        // eliminated

                        switch(t) {
                        case queryTypes['A']:
                                r = serializers['_nsIP4'].encoder(v.target);
                                break;
                        case queryTypes['CNAME']:
                                r = serializers['_nsName'].encoder(v.target);
                                break;
                        case queryTypes['NS']:
                                r = serializers['_nsName'].encoder(v.target);
                                break;
                        case queryTypes['SOA']:
                                r = encode(v, 'soa');
                                break;
                        case queryTypes['MX']:
                                r = encode(v, 'mx');
                                break;
                        case queryTypes['TXT']:
                                r = serializers['_nsText'].encoder(v.target);
                                break;
                        case queryTypes['AAAA']:
                                r = serializers['_nsIP6'].encoder(v.target);
                                break;
                        case queryTypes['SRV']:
                                r = encode(v, 'srv');
                                break;
                        default:
                                throw new Error('unrecognized nsdata type');
                                break;
                        }

                        l = r.length;
                        b = new Buffer(l + 2);
                        b.writeUInt16BE(l, 0);
                        r.copy(b, 2);
                        return b;
                }
        },
};

function encode(obj, format) {
        var size = 0, pos = 0, fmt, field, type, result, encoder, results = [];

        fmt = Formats[format];

        for (f in fmt) {
                var type, decoder, res;
                type = fmt[f].type;

                if (typeof(type) === 'string') {
                        //XXX I dont like this
                        if (type == '_nsData') {
                                res = serializers['_nsData'].encoder(obj[f], obj['rtype']);
                        }
                        else {
                                res = serializers[type].encoder(obj[f]);
                        }
                }
                else if (typeof(type) === 'object') {
                        reftype = type.format;
                        res = encode(obj[f], reftype);
                }
                else {
                        throw new TypeError('invalid type');
                }

                results.push(res);
                size = size + res.length;

        }

        result = new Buffer(size);

        for (i in results) {
                var buf = results[i];
                buf.copy(result, pos);
                pos = pos + buf.length;
        }

        return result;
}

function decode(raw, format, pos) {
        var size = 0, fmt, field, type, decoder, result = {}

        if (!pos) pos = 0;
        fmt = Formats[format];

        for (var f in fmt) {
                var type, decoder, res;
                type = fmt[f].type;

                // if the type is a string its a reference to a serializer
                // if the type is an object its a nested format and we call decode again
                // with the appropriate offset

                if (typeof(type) === 'string') {
                        res = serializers[type].decoder(raw, pos);
                }
                else if (typeof(type) === 'object') {
                        reftype = type.format;
                        res = decode(raw, reftype, pos);
                }
                else {
                        throw new TypeError('invalid type');
                }

                pos += res.len;
                result[f] = res.val;

        }

        return {val: result, len: pos};
}

var queryTypes = {
        A     : 0x01,   // ipv4 address
        NS    : 0x02,   // nameserver
        MD    : 0x03,   // obsolete
        MF    : 0x04,   // obsolete
        CNAME : 0x05,   // alias
        SOA   : 0x06,   // start of authority
        MB    : 0x07,   // experimental
        MG    : 0x08,   // experimental
        MR    : 0x09,   // experimental
        NULL  : 0x0A,   // experimental null RR
        WKS   : 0x0B,   // service description
        PTR   : 0x0C,   // reverse entry (inaddr.arpa)
        HINFO : 0x0D,   // host information
        MINFO : 0x0E,   // mailbox or mail list information
        MX    : 0x0F,   // mail exchange
        TXT   : 0x10,   // text strings
        AAAA  : 0x1C,   // ipv6 address
        SRV   : 0x21,   // srv records
        AXFR  : 0xFC,   // request to transfer entire zone
        MAILA : 0xFE,   // request for mailbox related records
        MAILB : 0xFD,   // request for mail agent RRs
        ANY   : 0xFF,   // any class
        0x01  : 'A' ,   // ipv4 address
        0x02  : 'NS',   // nameserver
        0x03  : 'MD',   // obsolete
        0x04  : 'MF',   // obsolete
        0x05  : 'CNAME',// alias
        0x06  : 'SOA',  // start of authority
        0x07  : 'MB',   // experimental
        0x08  : 'MG',   // experimental
        0x09  : 'MR',   // experimental
        0x0A  : 'NULL', // experimental null RR
        0x0B  : 'WKS',  // service description
        0x0C  : 'PTR',  // reverse entry (inaddr.arpa)
        0x0D  : 'HINFO',// host information
        0x0E  : 'MINFO',// mailbox or mail list information
        0x0F  : 'MX',   // mail exchange
        0x10  : 'TXT',  // text strings
        0x1C  : 'AAAA', // ipv6 address
        0x21  : 'SRV',  // srv records
        0xFC  : 'AXFR', // request to transfer entire zone
        0xFE  : 'MAILA',// request for mailbox related records
        0xFD  : 'MAILB',// request for mail agent RRs
        0xFF  : 'ANY',  // any class
}

module.exports = {
        DNS_ENOERR  : 0x00, // No error
        DNS_EFORMAT : 0x01, // Formatting Error
        DNS_ESERVER : 0x02, // server it unable to process
        DNS_ENONAME : 0x03, // name does not exist
        DNS_ENOTIMP : 0x04, // feature not implemented on this server
        DNS_EREFUSE : 0x05, // refused for policy reasons
        encode: encode,
        decode: decode,
        queryTypes: queryTypes
}