"use strict";

module.exports = {

	init() {
		//
	},

	// ------------------------------------------------------------ Constants


	positions: ['prop', 'hooker', 'lock', 'flanker', 'no8', 'scrumhalf', 'flyhalf', 'wing', 'center', 'fullback'],
	attributes: ['Speed', 'Acceleration', 'Agility', 'Strength', 'Power', 'Coordination', 'Endurance', 'Weight', 'Height', 'Aggression', 'Leadership', 'Composure'],
	skills: ['Rucking', 'CounterRucking', 'Scrum', 'Maul', 'LineoutJumping', 'LineoutThrowing', 'Passing', 'Catching', 'Kicking', 'Tackling', 'LineBreaking', 'Awareness'],
	skillMaxLevelStandard: 77,
	skillMaxLevelStars: 90,
	skillMaxLevelSuperStars: 100,

	// ------------------------------------------------------------ Retrieving players


	getPlayer() {},

	// ------------------------------------------------------------ Helpers


	/**
  * Returns a player level from a player object
  * @return {int} The player level
  */
	getPlayerLevel(player) {

		let level = 1;

		if (player.Ratings) {

			// Sort ratings
			let sortedPositions = blackout.getKeysSortedByValue(player.Ratings, 'desc');
			let bestPosition = sortedPositions[0];

			// Get highest rating
			level = player.Ratings[bestPosition];
		} else {
			blackout.errorInternal(new Error('Ratings could not be found on the given player for `getPlayerLevel()`'));
			return false;
		}

		return level;
	},

	/**
  * Refreshes the player ratings for all positions, unless a position is specified
  * @param  {object} player  The player to refresh
  * @param  {string} pos     Optional. A single position to refresh
  * @return {object}         The updated player object
  */
	refreshPlayerRatings(player, pos) {

		// Create Ratings map if it doesn't exist already
		if (!('Ratings' in player)) {
			player.Ratings = {};
		}

		for (let position of this.positions) {

			if (pos && position !== pos) continue;

			// Get positional skills/attrs
			let { skillsCat1, skillsCat2, skillsCat3 } = this.getPositionalSkills(position);

			// Tracking
			let totalLevels = 0;
			let num = 0;

			// -------------------------- Prepare general skills

			// Construct merged list of skills
			let generalSkills = Object.assign({}, player.Attributes, player.Skills);

			// Remove unneeded
			delete generalSkills.Weight;
			delete generalSkills.Height;
			delete generalSkills.Aggression;
			delete generalSkills.Leadership;
			let multiplers = {
				cat1: 11,
				cat2: 4,
				cat3: 2
			};

			// -------------------------- Category 1 skills

			for (let skill of skillsCat1) {
				let points = skill in player.Attributes ? player.Attributes[skill] : player.Skills[skill];
				totalLevels += this.pointsToLevel(points) * multiplers.cat1;
				num += multiplers.cat1;
				delete generalSkills[skill];
			}

			// -------------------------- Category 2 skills

			for (let skill of skillsCat2) {
				let points = skill in player.Attributes ? player.Attributes[skill] : player.Skills[skill];
				totalLevels += this.pointsToLevel(points) * multiplers.cat2;
				num += multiplers.cat2;
				delete generalSkills[skill];
			}

			// -------------------------- Category 3 skills

			for (let skill of skillsCat3) {
				let points = skill in player.Attributes ? player.Attributes[skill] : player.Skills[skill];
				totalLevels += this.pointsToLevel(points) * multiplers.cat3;
				num += multiplers.cat3;
				delete generalSkills[skill];
			}

			// -------------------------- Category 4 (Top 6 general) skills

			// Sort all skills and attrs
			let sortedSkills = blackout.getKeysSortedByValue(generalSkills, 'desc');
			for (var i = 0; i < 6; i++) {
				let skill = sortedSkills[i];
				let points = skill in player.Attributes ? player.Attributes[skill] : player.Skills[skill];
				totalLevels += this.pointsToLevel(points);
				num++;
			}

			// -------------------------- 1x weight for all players

			// Convert weight to level
			let weightLevel = this.weightToLevel(player.Attributes.Weight);
			totalLevels += weightLevel;
			num++;

			// -------------------------- 1x height for locks only

			if (position === 'lock') {
				let heightLevel = this.heightToLevel(player.Attributes.Height);
				totalLevels += heightLevel;
				num++;
			}

			// -------------------------- Update player

			let posKey = position.upperCaseFirst();
			player.Ratings[posKey] = Math.round(totalLevels / num);
		}

		return player;
	},

	/**
  * Converts a given weight to a level
  * @param  {int} weight The player's weight
  * @return {int}        The relative level based on the player's weight
  */
	weightToLevel(weight) {

		if (!weight || isNaN(weight)) {
			blackout.errorInternal(new Error('An invalid number was given to weightToLevel()'));
		}

		// 70kg > 140kg
		let min = 70;
		let max = 140;

		let level = Math.round((weight - min) / (max - min) * this.skillMaxLevelStandard);
		return Math.max(0, Math.min(this.skillMaxLevelStandard, level));
	},

	/**
  * Converts a given height to a level
  * @param  {int} height The player's height
  * @return {int}        The relative level based on the player's height
  */
	heightToLevel(height) {

		if (!height || isNaN(height)) {
			blackout.errorInternal(new Error('An invalid number was given to heightToLevel()'));
		}

		// 170cm > 210cm
		let min = 170;
		let max = 210;

		let level = Math.round((height - min) / (max - min) * this.skillMaxLevelStandard);
		return Math.max(0, Math.min(this.skillMaxLevelStandard, level));
	},

	/**
  * Gets the important skills for each position. Both the Category 1 skills, and the Category 2 skills
  * @param  {int|string} position The position
  * @return {object}              And object containing both { skillsCat1: [], skillsCat2: [] } arrays of skills
  */
	getPositionalSkills(position) {

		if (!isNaN(position)) {
			position = this.positionNumberToWord(position);
		}

		let skillsCat1, skillsCat2, skillsCat3;

		// Get positional skills/attrs
		switch (position) {

			case 'prop':

				// Positional Skills
				skillsCat1 = ['Strength', 'Scrum'];
				skillsCat2 = ['Power', 'Maul', 'Rucking'];
				skillsCat3 = ['CounterRucking', 'Tackling', 'LineoutJumping'];

				break;

			case 'hooker':

				// Positional Skills
				skillsCat1 = ['Scrum', 'LineoutThrowing'];
				skillsCat2 = ['Strength', 'Rucking', 'Power'];
				skillsCat3 = ['Maul', 'CounterRucking', 'Tackling'];

				break;

			case 'lock':

				// Positional Skills
				skillsCat1 = ['Scrum', 'LineoutJumping'];
				skillsCat2 = ['Power', 'Maul', 'Rucking'];
				skillsCat3 = ['Strength', 'CounterRucking', 'Tackling'];

				break;

			case 'flanker':

				// Positional Skills
				skillsCat1 = ['CounterRucking', 'Rucking'];
				skillsCat2 = ['Tackling', 'Maul', 'Power'];
				skillsCat3 = ['Strength', 'Scrum', 'LineoutJumping'];

				break;

			case 'no8':

				// Positional Skills
				skillsCat1 = ['Power', 'Scrum'];
				skillsCat2 = ['Tackling', 'Rucking', 'CounterRucking'];
				skillsCat3 = ['Maul', 'Strength', 'LineoutJumping'];

				break;

			case 'scrumhalf':

				// Positional Skills
				skillsCat1 = ['Passing', 'Awareness'];
				skillsCat2 = ['Acceleration', 'Coordination', 'LineBreaking'];
				skillsCat3 = ['Kicking', 'Agility', 'Catching'];

				break;

			case 'flyhalf':

				// Positional Skills
				skillsCat1 = ['Awareness', 'Kicking'];
				skillsCat2 = ['Passing', 'Composure', 'LineBreaking'];
				skillsCat3 = ['Acceleration', 'Coordination', 'Catching'];

				break;

			case 'wing':

				// Positional Skills
				skillsCat1 = ['Speed', 'Acceleration'];
				skillsCat2 = ['LineBreaking', 'Agility', 'Coordination'];
				skillsCat3 = ['Awareness', 'Catching', 'Passing'];

				break;

			case 'center':

				// Positional Skills
				skillsCat1 = ['Passing', 'LineBreaking'];
				skillsCat2 = ['Catching', 'Acceleration', 'Coordination'];
				skillsCat3 = ['Tackling', 'Awareness', 'Speed'];

				break;

			case 'fullback':

				// Positional Skills
				skillsCat1 = ['Awareness', 'LineBreaking'];
				skillsCat2 = ['Catching', 'Tackling', 'Kicking'];
				skillsCat3 = ['Passing', 'Acceleration', 'Speed'];

				break;

			default:
				blackout.errorInternal(new Error('An unknown rugby position was provided to getPositionalSkills()'));

		}

		return { skillsCat1: skillsCat1, skillsCat2: skillsCat2, skillsCat3: skillsCat3 };
	},

	/**
  * Converts from a number between 1-15 to a word based position group
  * Note, this can return the same word for multiple positions, e.g. 4 and 5 will both return 'lock'
  * @param  {int} positionNumber The number of the rugby position on the field
  * @return {string}             The word describing a position group.
  */
	positionNumberToWord(positionNumber) {

		switch (positionNumber) {

			case 1:
			case 3:
				return 'prop';

			case 2:
				return 'hooker';

			case 4:
			case 5:
				return 'lock';

			case 6:
			case 7:
				return 'flanker';

			case 8:
				return 'no8';

			case 9:
				return 'scrumhalf';

			case 10:
				return 'flyhalf';

			case 11:
			case 14:
				return 'wing';

			case 12:
			case 13:
				return 'center';

			case 15:
				return 'fullback';

			default:
				blackout.errorInternal(new Error('Invalid rugby position number given to positionNumberToWord()'));
				return false;

		}
	},

	/**
  * Converts from a position word to number between 1-15
  * Note, this can return the multiple positions for one word, e.g. 'lock' can return either 4 or 5
  * @param  {int} positionNumber The number of the rugby position on the field
  * @return {string}             The word describing a position group.
  */
	positionWordToNumber(positionWord) {

		switch (positionWord) {

			case 'prop':
				return blackout.rand(1, 2) === 1 ? 1 : 3;

			case 'hooker':
				return 2;

			case 'lock':
				return blackout.rand(4, 5);

			case 'flanker':
				return blackout.rand(6, 7);

			case 'no8':
				return 8;

			case 'scrumhalf':
				return 9;

			case 'flyhalf':
				return 10;

			case 'wing':
				return blackout.rand(1, 2) === 1 ? 11 : 14;

			case 'center':
				return blackout.rand(12, 13);

			case 'fullback':
				return 15;

			default:
				blackout.errorInternal(new Error('Invalid rugby position word given to positionWordToNumber()'));
				return false;

		}
	},

	/**
  * This function calculates all the levels for a given player, and adds them to the player array
  * @param {object} player A Player
  */
	addLevels(player) {

		if (!player) return;
		if (!player.Meta) player.Meta = {};

		// Attributes
		player.Meta.AttributeLevels = {};
		for (let skill in player.Attributes) {
			if (skill === 'Height' || skill === 'Weight' || skill === 'Aggression') continue;
			let points = player.Attributes[skill];
			player.Meta.AttributeLevels[skill] = this.pointsToLevel(points);
		}

		// Skills
		player.Meta.SkillLevels = {};
		for (let skill in player.Skills) {
			let points = player.Skills[skill];
			player.Meta.SkillLevels[skill] = this.pointsToLevel(points);
		}

		// Skill Starts
		player.Meta.StartLevels = {};
		for (let skill in player.Meta) {
			if (typeof player.Meta[skill] === 'object' && player.Meta[skill].Start) {
				let points = player.Meta[skill].Start;
				player.Meta.StartLevels[skill] = this.pointsToLevel(points);
			}
		}

		// Skill Starts
		player.Meta.MaxLevels = {};
		for (let skill in player.Meta) {
			if (typeof player.Meta[skill] === 'object' && player.Meta[skill].Max) {
				let points = player.Meta[skill].Max;
				player.Meta.MaxLevels[skill] = this.pointsToLevel(points);
			}
		}

		return player;
	},

	/**
  * Converts skill points to a levels
  * @param  {int} points The points to convert to a level
  * @return {int}        The level
  */
	pointsToLevel(points) {
		return this.levels(null, points);
	},

	/**
  * Converts skill points to a levels
  * @param  {int} points The points to convert to a level
  * @return {int}        The level
  */
	levelToPoints(level) {
		return this.levels(level);
	},

	/**
  * Gets either a level, or the number of points by iterating over each level
  * @param  {int} level  A specified level, if you want to get points back
  * @param  {int} points The specified number of points if you want to get a level back
  * @return {int}        A level, or the number of points
  */
	levels(level, points = null, printInfo = null) {

		// -------------- Integrity Checks

		if (level && isNaN(level)) {
			blackout.errorInternal(new Error(`Bad \`level\` variable passed to player.levels() ${level}`));
			return false;
		} else if (points && isNaN(points)) {
			blackout.errorInternal(new Error(`Bad \`points\` variable passed to player.levels() ${points}`));
			return false;
		}

		// -------------- Try an in-memory cache first!

		if (this.levelsCache && !printInfo) {
			if (level) {
				return this.levelsCache[level - 1];
			} else {

				// Reverse search the cache

				/**
     * Linear search algorithm (Best time 0.32ms)
     */
				/*level = 1;
    for( let levelPoints of this.levelsCache ){
    	if(points < levelPoints){
    		return level-1;
    	}
    	level++;
    }*/

				/**
     * Binary search algorithm (Best time 0.27ms)
     * 
     * This outperforms linear by about 18%
     */
				let checkPoints = (min, max) => {
					let i = Math.floor((max + min) * 0.5);
					if (this.levelsCache[i] <= points && (!this.levelsCache[i + 1] || this.levelsCache[i + 1] > points)) {
						return i + 1;
					} else if (this.levelsCache[i] <= points) {
						// Go up
						return checkPoints(i, max);
					} else {
						// Go down
						return checkPoints(min, i);
					}
				};
				return checkPoints(0, this.levelsCache.length);
			}
		}

		// -------------- Settings

		let start = 100; // The level points we want to start at
		let base = 11; // The base to which the exponent will be applied
		let diluter = 44; // Dilutes the exponent to slow growth (The high this is the slower the growth)

		// -------------- Process (Is only run once per memory session)

		let currentPoints = 0;
		let offset = Math.log(start) / Math.log(base) * diluter - 1;
		this.levelsCache = [];

		for (let l = 1; l <= 100; l++) {

			// Growth
			let pointsForNextLevel = Math.pow(base, (l + offset) / diluter);

			// Rounding
			if (pointsForNextLevel > 100000) {
				pointsForNextLevel = Math.round(pointsForNextLevel / 1000) * 1000;
			} else if (pointsForNextLevel > 1000) {
				pointsForNextLevel = Math.round(pointsForNextLevel / 100) * 100;
			} else if (pointsForNextLevel > 100) {
				pointsForNextLevel = Math.round(pointsForNextLevel / 10) * 10;
			} else {
				pointsForNextLevel = Math.round(pointsForNextLevel);
			}

			// Info
			if (printInfo === 'csv') {
				console.log(`${l},${pointsForNextLevel},${currentPoints}`);
			} else if (printInfo) {
				console.log(`Level ${l}`, `Points for next level ${pointsForNextLevel}`, `Total Points ${currentPoints}`);
			}

			// Build cache
			this.levelsCache.push(currentPoints);

			// Tally
			currentPoints += pointsForNextLevel;
		}

		if (!printInfo) {
			// Call again now that we have a cache
			return this.levels(level, points);
		}
	},

	/**
  * Calculates a player salary
  * @param  {[type]} player [description]
  * @return {[type]}        [description]
  */
	getSalary(player) {

		return this.salaries(player.Level, true);
	},

	/**
  * Creates a base salary from a player level
  */
	salaries(level, randomise, printInfo = null) {

		// -------------- Integrity Checks

		if (isNaN(level)) {
			blackout.errorInternal(new Error(`Bad \`level\` variable passed to player.levels() ${level}`));
			return false;
		}

		// -------------- Try an in-memory cache first!

		if (this.salariesCache && !printInfo) {
			let salary = this.salariesCache[level - 1];

			if (randomise) {
				let threshold;
				if (salary > 1000000) {
					threshold = 50000;
				} else if (salary > 100000) {
					threshold = 5000;
				} else if (salary > 10000) {
					threshold = 1000;
				} else if (salary > 1000) {
					threshold = 500;
				} else {
					threshold = 50;
				}
				let mod = blackout.rand(-threshold, threshold);

				if (salary > 1000000) {
					mod = Math.round(mod / 10000) * 10000;
				} else if (salary > 100000) {
					mod = Math.round(mod / 1000) * 1000;
				} else if (salary > 10000) {
					mod = Math.round(mod / 100) * 100;
				} else if (salary > 1000) {
					mod = Math.round(mod / 100) * 100;
				} else {
					mod = Math.round(mod / 10) * 10;
				}

				salary += mod;
			}

			return salary;
		}

		// -------------- Settings

		let start = 1000; // The salary we want to start at
		let base = 7; // The base to which the exponent will be applied
		let diluter = 37; // Dilutes the exponent to slow growth (The high this is the slower the growth)

		// -------------- Process (Is only run once per memory session)

		let currentDollars = start;
		let offset = Math.log(start) / Math.log(base) * diluter;
		this.salariesCache = [];

		for (let l = 1; l <= 100; l++) {

			// Growth
			let dollarsForNextLevel = Math.pow(base, (l + offset) / diluter);

			// Info
			if (printInfo === 'csv') {
				console.log(`${l},${dollarsForNextLevel},${currentDollars}`);
			} else if (printInfo) {
				console.log(`Level ${l}`, `Salary ${currentDollars}`);
			}

			// Build cache
			this.salariesCache.push(currentDollars);

			// Rounding
			if (dollarsForNextLevel > 1000000) {
				dollarsForNextLevel = Math.round(dollarsForNextLevel / 100000) * 100000;
			} else if (dollarsForNextLevel > 100000) {
				dollarsForNextLevel = Math.round(dollarsForNextLevel / 10000) * 10000;
			} else if (dollarsForNextLevel > 1000) {
				dollarsForNextLevel = Math.round(dollarsForNextLevel / 1000) * 1000;
			} else if (dollarsForNextLevel > 100) {
				dollarsForNextLevel = Math.round(dollarsForNextLevel / 100) * 100;
			} else {
				dollarsForNextLevel = Math.round(dollarsForNextLevel);
			}

			// Tally
			currentDollars += dollarsForNextLevel;
		}

		if (!printInfo) {
			// Call again now that we have a cache
			return this.salaries(level, randomise);
		}
	}

};
//# sourceMappingURL=player.js.map