/**
 */
Ext.define("Terrasoft.manager.CronExpressionParser", {
	alternateClassName: "Terrasoft.CronExpressionParser",

	/**
  * Array of cron expression possible days of the week.
  * @private
  * @type {Array}
  */
	_dayOfWeek: ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"],

	/**
  * Array of cron expression possible month.
  * @private
  * @type {Array}
  */
	_month: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"],

	/**
  * Allowed seconds parameter value.
  * @private
  * @type {String}
  */
	_allowedSecondsValue: "0",

	/**
  * Maximum possible year value.
  * @private
  * @type {Number}
  */
	_maximumYearValue: 2099,

	/**
  * Default year cron parameter value.
  * @private
  * @type {String}
  */
	_defaultYear: "*",

	/**
  * Regex tests input for year cron parameter.
  * @private
  * @type {RegExp}
  */
	_isYearRegex: /\d{4}$/,

	/**
  * Regex replaces multiple spaces.
  * @private
  * @type {RegExp}
  */
	_replaceMultipleSpacesRegex: /\s\s+/g,

	/**
  * Special symbols regex.
  * @private
  * @type {RegExp}
  */
	_specialSymbolsRegexp: /^[^\*\-\,]+$/,

	/**
  * Regex for number extraction.
  * @private
  * @type {RegExp}
  */
	_numberExtractorRegex: /\d+/g,

	/**
  * Regex for time valid symbols.
  * @private
  * @type {RegExp}
  */
	_baseTimeSymbolsRegex: /^([\,\-\*\/]|\d)+$/,

	/**
  * Regex for day of month valid symbols.
  * @private
  * @type {RegExp}
  */
	_dayOfMonthRegex: /^([\,\-\*\/\?LW]|\d)+$/,

	/**
  * Regex for day of the week valid symbols.
  * @private
  * @type {RegExp}
  */
	_dayOfWeekRegex: /^([\,\-\*\/\?L\#]|\d)+$/,

	/**
  * Cron parameters separator.
  * @private
  * @type {String}
  */
	_parametersSeparator: " ",

	/**
  * Parsed cron expression.
  * @private
  * @type {Array}
  */
	_parsedExpression: null,

	/**
  * Parsing information config.
  * @private
  * @type {Object}
  */
	_parsingParameters: null,

	/**
  * validation information_
  * @private
  * @type {Object}
  */
	_parsingInfo: null,

	/**
  * Normalizes time prefix in cron parameters.
  * @private
  * @param {String} parameter Cron parameter.
  */
	_normalizeTimePrefix: function (parameter) {
		return Ext.String.startsWith(parameter, "0/") ? parameter.replace("0/", "*/") : parameter;
	},

	/**
  * Normalizes day prefix in cron parameters.
  * @private
  * @param {String} parameter Cron parameter.
  */
	_normalizeDatePrefix: function (parameter) {
		return Ext.String.startsWith(parameter, "1/") ? parameter.replace("1/", "*/") : parameter;
	},

	/**
  * Normalizes day names in cron parameters.
  * @private
  * @param {Array} expressionParts Cron parameters.
  */
	_normalizeDays: function (expressionParts) {
		for (var i = 0; i <= 6; i++) {
			var currentDay = this._dayOfWeek[i];
			expressionParts[5] = expressionParts[5].replace(currentDay, i + 1);
		}
	},

	/**
  * Normalizes month names in cron parameters.
  * @private
  * @param {Array} expressionParts Cron parameters.
  */
	_normalizeMonth: function (expressionParts) {
		for (var i = 0; i < 12; i++) {
			var currentMonth = this._month[i];
			expressionParts[4] = expressionParts[4].replace(currentMonth, i + 1);
		}
	},

	/**
  * Normalizes ranges in cron parameters.
  * @private
  * @param {Array} expressionParts Cron parameters.
  */
	_normalizeRanges: function (expressionParts) {
		for (var i = 0; i < expressionParts.length; i++) {
			if (expressionParts[i] === "*/1") {
				expressionParts[i] = "*";
			}
			if (Terrasoft.includes(expressionParts[i], "/") && this._specialSymbolsRegexp.test(expressionParts[i])) {
				var stepRangeThrough = null;
				switch (i) {
					case 4:
						stepRangeThrough = "12";
						break;
					case 5:
						stepRangeThrough = "6";
						break;
					case 6:
						stepRangeThrough = this._maximumYearValue;
						break;
					default:
						stepRangeThrough = null;
						break;
				}
				if (stepRangeThrough != null) {
					var parts = expressionParts[i].split("/");
					expressionParts[i] = Ext.String.format("{0}-{1}/{2}", parts[0], stepRangeThrough, parts[1]);
				}
			}
		}
	},

	/**
  * Normalizes cron parameters time.
  * @private
  * @param {Array} expressionParts Cron parameters.
  */
	_normalizeTime: function (expressionParts) {
		for (var i = 0; i <= 2; i++) {
			expressionParts[i] = this._normalizeTimePrefix(expressionParts[i]);
		}
	},

	/**
  * Normalizes cron parameter date.
  * @private
  * @param {Array} expressionParts Cron parameters.
  */
	_normalizeDate: function (expressionParts) {
		for (var i = 3; i <= 6; i++) {
			expressionParts[i] = this._normalizeDatePrefix(expressionParts[i]);
		}
	},

	/**
  * Normalizes cron expression.
  * @private
  * @param {Array} expressionParts Cron parameters.
  */
	_normalizeExpression: function (expressionParts) {
		this._normalizeTime(expressionParts);
		this._normalizeDate(expressionParts);
		this._normalizeDays(expressionParts);
		this._normalizeMonth(expressionParts);
		this._normalizeRanges(expressionParts);
	},

	/**
  * Prepares cron expression parts from cron string.
  * @private
  * @returns {Array} Cron expression parts.
  */
	_prepareExpressionParameters: function (unParsed) {
		var clearString = unParsed.replace(this._replaceMultipleSpacesRegex, this._parametersSeparator);
		var result = clearString.split(this._parametersSeparator);
		result = result.filter(function (item) {
			return !Ext.isEmpty(item);
		});
		this._parsingParameters = result;
		return result;
	},

	/**
  * Validates value range.
  * @private
  * @param {Object} value Value to validate.
  * @param {Number} min Valid min number.
  * @param {Number} max Valid max number.
  * @param {String} parameterName Cron parameter name.
  */
	_validateRange: function (value, min, max, parameterName) {
		if (value < min || value > max) {
			throw Terrasoft.InvalidFormatException({
				message: Ext.String.format(Terrasoft.Resources.Cron.ValueOutOfRangeException, parameterName, value, min, max)
			});
		}
	},

	/**
  * Validates number ranges.
  * @private
  * @param {String} parameter Parameter to validate.
  * @param {Number} min Minimal parameter value.
  * @param {Number} max Maximum parameter value.
  * @param {String} parameterName Cron parameter name.
  */
	_validateNumberRanges: function (parameter, min, max, parameterName) {
		var numbers = this._extractNumbers(parameter);
		if (numbers) {
			numbers.forEach(function (number) {
				this._validateRange(number, min, max, parameterName);
			}, this);
		}
	},

	/**
  * Validates parameter symbols by regex.
  * @private
  * @param {String} parameter Cron parameter to validate.
  * @param {RegExp} regex Validation regexp.
  * @param {String} parameterName Cron parameter name.
  */
	_validateSymbols: function (parameter, regex, parameterName) {
		if (!regex.test(parameter)) {
			throw Terrasoft.InvalidFormatException({
				message: Ext.String.format(Terrasoft.Resources.Cron.InvalidSymbolsException, parameterName, parameter)
			});
		}
	},

	/**
  * Returns all numbers from cron parameter.
  * @private
  * @param {String} parameter Cron parameter.
  * @returns {Array} Array of numbers from parameter.
  */
	_extractNumbers: function (parameter) {
		return parameter.match(this._numberExtractorRegex);
	},

	/**
  * Validates cron seconds parameter.
  * @private
  * @param {String} seconds Seconds parameter.
  */
	_validateSeconds: function (seconds) {
		var secondsName = Terrasoft.Resources.Cron.Parameters.Seconds;
		this._validateSymbols(seconds, this._baseTimeSymbolsRegex, secondsName);
		this._validateNumberRanges(seconds, 0, 59, secondsName);
	},

	/**
  * Validates cron minutes parameter.
  * @private
  * @param {String} minutes Minutes parameter.
  */
	_validateMinutes: function (minutes) {
		var minutesName = Terrasoft.Resources.Cron.Parameters.Minutes;
		this._validateSymbols(minutes, this._baseTimeSymbolsRegex, minutesName);
		this._validateNumberRanges(minutes, 0, 59, minutesName);
	},

	/**
  * Validates cron hours parameter.
  * @private
  * @param {String} hours Hours parameter.
  */
	_validateHours: function (hours) {
		var hoursName = Terrasoft.Resources.Cron.Parameters.Hours;
		this._validateSymbols(hours, this._baseTimeSymbolsRegex, hoursName);
		this._validateNumberRanges(hours, 0, 23, hoursName);
	},

	/**
  * Validates cron day of month parameter.
  * @private
  * @param {String} dayOfMonth Day of month parameter.
  */
	_validateDayOfMonth: function (dayOfMonth) {
		var daysOfMonthName = Terrasoft.Resources.Cron.Parameters.DaysOfMonth;
		this._validateSymbols(dayOfMonth, this._dayOfMonthRegex, daysOfMonthName);
		this._validateNumberRanges(dayOfMonth, 1, 31, daysOfMonthName);
	},

	/**
  * Validates cron month parameter.
  * @private
  * @param {String} month Month parameter.
  */
	_validateMonth: function (month) {
		var monthsName = Terrasoft.Resources.Cron.Parameters.Months;
		this._validateSymbols(month, this._baseTimeSymbolsRegex, monthsName);
		this._validateNumberRanges(month, 1, 12, monthsName);
	},

	/**
  * Validates cron day of week parameter.
  * @private
  * @param {String} dayOfWeek Day of week parameter.
  */
	_validateDayOfWeek: function (dayOfWeek) {
		var daysOfWeekName = Terrasoft.Resources.Cron.Parameters.DaysOfWeek;
		this._validateSymbols(dayOfWeek, this._dayOfWeekRegex, daysOfWeekName);
		this._validateNumberRanges(dayOfWeek, 1, 7, daysOfWeekName);
	},

	/**
  * Validates cron year parameter.
  * @private
  * @param {String} year Year parameter.
  */
	_validateYear: function (year) {
		var yearsName = Terrasoft.Resources.Cron.Parameters.Years;
		this._validateSymbols(year, this._baseTimeSymbolsRegex, yearsName);
		this._validateNumberRanges(year, 0, this._maximumYearValue, yearsName);
	},

	/**
  * Validates day of parameters.
  * @private
  * @param {String} dayOfWeek Day of week parameter.
  * @param {String} dayOfMonth Day of month parameter.
  */
	_validateDayOfParameters: function (dayOfWeek, dayOfMonth) {
		if (dayOfWeek !== "?" && dayOfMonth !== "?") {
			throw Terrasoft.InvalidFormatException({
				message: Ext.String.format(Terrasoft.Resources.Cron.DayOfNotImplementedException, dayOfWeek, dayOfMonth)
			});
		}
	},

	/**
  * Validates expression.
  * @private
  * @param {Array} parameters Cron parameters.
  */
	_validateExpression: function (parameters) {
		this._validateSeconds(parameters[0]);
		this._validateMinutes(parameters[1]);
		this._validateHours(parameters[2]);
		this._validateDayOfMonth(parameters[3]);
		this._validateMonth(parameters[4]);
		this._validateDayOfWeek(parameters[5]);
		this._validateYear(parameters[6]);
		this._validateDayOfParameters(parameters[3], parameters[5]);
	},

	/**
  * Normalizes cron length.
  * @private
  * @param {Array} expressionParameters Cron parameters.
  * @returns {Array} Normalized cron by length.
  */
	_normalizeLength: function (expressionParameters) {
		var length = expressionParameters.length;
		switch (length) {
			case 5:
				expressionParameters.unshift(this._allowedSecondsValue);
				expressionParameters.push(this._defaultYear);
				break;
			case 6:
				var dayOfWeek = expressionParameters[5];
				if (this._isYearRegex.test(dayOfWeek)) {
					expressionParameters.unshift(this._allowedSecondsValue);
				} else {
					expressionParameters.push(this._defaultYear);
				}
				break;
			default:
				if (length < 5) {
					throw new Terrasoft.InvalidFormatException({
						message: Ext.String.format(Terrasoft.Resources.Cron.ShortCronException, length)
					});
				}
				if (length > 7) {
					throw new Terrasoft.InvalidFormatException({
						message: Ext.String.format(Terrasoft.Resources.Cron.LongCronException, length)
					});
				}
				break;
		}
	},

	/**
  * Parse cron expression
  * @param {String} cron expression
  * @return {Array} parsed Cron expression
  */
	parse: function (unParsedExpression) {
		var result = null;
		this._parsingInfo = {
			isValid: true,
			error: "",
			parsingParameters: []
		};
		try {
			if (Ext.isEmpty(unParsedExpression)) {
				throw new Terrasoft.InvalidFormatException({
					message: Terrasoft.Resources.Exception.NullOrEmptyException
				});
			}
			var expressionParameters = this._prepareExpressionParameters(unParsedExpression.slice());
			var normalizedExpression = this.normalize(expressionParameters);
			this._validateExpression(normalizedExpression);
			this._parsedExpression = normalizedExpression;
			result = this._parsedExpression;
			this._parsingInfo.parsingParameters = this._parsedExpression;
		} catch (exception) {
			this._parsingInfo = {
				isValid: false,
				error: exception.message,
				parsingParameters: this._parsingParameters
			};
		}
		return result;
	},

	/**
  * Normalizes cron expression parts.
  * @returns {Array} Normalized cron parts.
  */
	normalize: function (expressionParts) {
		var result = expressionParts.slice();
		this._normalizeLength(result);
		this._normalizeExpression(result);
		return result;
	},

	/**
  * Returns cron expression from parsed cron string.
  * @returns {Terrasoft.CronExpression} Cron expression.
  */
	getCronObject: function () {
		var cron = Ext.create("Terrasoft.CronExpression", { parameters: this.getCronParameters() });
		return cron;
	},

	/**
  * Returns cron parameters array from parsed cron string.
  * @returns {Array} Cron parameters.
  */
	getCronParameters: function () {
		return this._parsedExpression;
	},

	/**
  * Returns parsing info
  * @returns {Object}
  */
	getParsingInfo: function () {
		return this._parsingInfo;
	},

	/**
  * Returns normalizes cron string from input cron string.
  * @returns {String} Normalized cron expression string.
  */
	toString: function () {
		return this._parsedExpression.join(this._parametersSeparator);
	}

});