/**
 * @name CeL function for poker calculations.
 * @fileoverview 本檔案包含了撲克牌演算用的功能。
 * 
 * @see https://en.wikipedia.org/wiki/Poker
 * 
 * @since 2015/11/19 17:16:35
 */

'use strict';

// ------------------------------------------------------------------------------------------------------------------//

if (typeof CeL === 'function')
	CeL.run({
		name : 'application.poker',
		require : 'data.code.compatibility.|data.native.',

		code : function(library_namespace) {

			// ------------------------------------------------------------------------------------------------------//

			var
			/** {RegExp}判斷輸入 Poker_cards() 之撲克牌牌面表達規則。 */
			PATTERN_poker_card = /(10?|[2-9TJQKA])([SHDC♠♥♦♣])/g,

			/**
			 * 各點數 index 值之名稱。
			 * 
			 * 0–12. 0:2, 1:3, ...<br />
			 * 7:9, 8:10, 9:Jack, 10:Queen, 11:King, 12:Ace
			 * 
			 * @type {Array}
			 */
			value_name = '234567891JQKA'.split(''),
			/** {Object}各點數之 index 值。 */
			value_index = Object.create(null),
			/** {Array}各花色 index 值之名稱。 */
			suit_name = '♠♥♦♣'.split(''),
			/** {Object}各花色之 index 值。 */
			suit_index = Object.create(null),

			/** {Object}各牌型之基礎配分。 */
			category_score = {
				'High Card' : 100,
				'One Pair' : 200,
				'Two Pairs' : 300,
				'Three of a Kind' : 400,
				'Straight' : 500,
				'Flush' : 600,
				'Full House' : 700,
				'Four of a Kind' : 800,
				'Straight Flush' : 900,
				'Royal Flush' : 1000
			};

			// [10 - 2]: 1 → 10
			value_name[10 - 2] += 0;
			value_name.forEach(function(name, index) {
				value_index[name] = index;
			});
			value_index[1] = value_name.indexOf('A');
			// Ten
			value_index['T'] = value_name.indexOf('10');

			function set_suit_index(names) {
				if (typeof names === 'string')
					names = names.split('');
				names.forEach(function(name, index) {
					suit_index[name] = index;
				})
			}

			set_suit_index(suit_name);
			set_suit_index('SHDC');

			// ----------------------------------------------------------------

			/**
			 * parse poker card.
			 * 
			 * @param {String}hand
			 *            card 牌面。
			 * @param {Array}values
			 *            value array
			 * @param {Array}suits
			 *            suit array
			 */
			function parse(hand, values, suits) {
				var matched;
				hand = hand.toUpperCase();
				while (matched = PATTERN_poker_card.exec(hand)) {
					values.push(value_index[matched[1]]);
					suits.push(suit_index[matched[2]]);
				}
			}

			// ----------------------------------------------------------------

			/**
			 * poker cards handler.
			 * 
			 * @param {String}hand
			 *            cards 牌面。
			 * 
			 * @constructor
			 */
			function Poker_cards(hand) {
				var values = this.values = [], suits = this.suits = [];
				if (hand.forEach) {
					hand.forEach(function(card) {
						parse(card, values, suits);
					});
				} else if (typeof hand === 'string') {
					parse(hand, values, suits);
				}
			}

			function to_Array() {
				return this.values.map(function(value, index) {
					return value_name[value] + suit_name[this.suits[index]];
				}, this);
			}

			function toString() {
				return this.to_Array().join(' ');
			}

			function consecutive() {
				var values = this.values;
				return values.combines_AP('consecutive')
				// 同花順和順子中A如果配上2345時當做1點
				|| values.includes(12) && values.map(function(value) {
					return value === 12 ? -1 : value;
				}).combines_AP('consecutive');
			}

			// ----------------------------------------------------------------

			// five-card deal 牌型
			// TODO: get specified category
			function category(get_rank) {
				function category_name(name) {
					var score = 0;
					if (high_card && high_card.length > 0) {
						if (get_rank && high_card.length === 1)
							// 只有一張時，直接將之配入 score，也無須登記 high_card 了。
							score = high_card[0];
						else
							_this.high_card = high_card;
					}
					// card rank
					return get_rank ? category_score[name] + score : name;
				}

				var suits = this.suits;
				if (this.suits.length !== 5)
					return;

				suits.max = suits.frequency(1);
				var max_suit = suits.max.value,
				//
				max_suit_count = suits.max.count,
				//
				values = this.values, max_value = Math.max.apply(null, values);
				// console.log(suits.max);

				if (max_suit_count === 5 && this.consecutive()) {
					if (max_value === 12)
						return category_name('Royal Flush');
					high_card.push(max_value);
					return category_name('Straight Flush');
				}

				values.max = values.frequency(1);
				var value, value_frequency = values.max,
				// Hands are ranked first by category, then by individual card
				// ranks
				// 這邊僅列出需要比較之點數，由小至大由後比起。
				// assert: 同種牌型具有相同的 high_card.length
				high_card = [], _this = this,
				//
				max_of_a_kind = value_frequency.count,
				//
				most_frequency_value = value_frequency.value;
				// console.log(value_frequency);

				if (max_of_a_kind === 4) {
					for (value in value_frequency.hash)
						if (+value !== most_frequency_value) {
							high_card.push(+value);
							// 應該就只有這一張。
							break;
						}
					high_card.push(most_frequency_value);
					return category_name('Four of a Kind');
				}

				if (max_of_a_kind === 3) {
					for (value in value_frequency.hash)
						if (+value !== most_frequency_value) {
							if (value_frequency.hash[value] === 2) {
								// [3,2]。
								high_card.push(+value, most_frequency_value);
								return category_name('Full House');
							}
							// 應該為 'Three of a Kind', [3,1,1]。
							break;
						}
				}

				if (max_suit_count === 5) {
					high_card = values.clone().sort();
					return category_name('Flush');
				}

				if (this.consecutive()) {
					high_card.push(most_frequency_value);
					return category_name('Straight');
				}

				if (max_of_a_kind === 3) {
					// assert: [3,2] 已經在 'Full House' 處理過了，
					// 因此此處應該為 [3,1,1]。
					for (value in value_frequency.hash)
						if (+value !== most_frequency_value) {
							high_card.push(+value);
						}
					high_card.push(most_frequency_value);
					return category_name('Three of a Kind');
				}

				if (max_of_a_kind === 2) {
					max_of_a_kind = [];
					// 此處應該為 [2,1,1,1] or [2,2,1]。
					for (value in value_frequency.hash) {
						if (value_frequency.hash[value] === 1)
							high_card.push(+value);
						else
							// assert: value_frequency.hash[value] === 2
							max_of_a_kind.push(+value);
					}
					if (max_of_a_kind.length === 1) {
						// 此處應該為 [2,1,1,1]。
						high_card.sort();
						high_card.push(most_frequency_value);
						return category_name('One Pair');
					}

					// 此處應該為 [2,2,1]。
					if (max_of_a_kind[0] < max_of_a_kind[1]) {
						high_card.push(max_of_a_kind[0], max_of_a_kind[1]);
					} else {
						high_card.push(max_of_a_kind[1], max_of_a_kind[2]);
					}
					return category_name('Two Pairs');
				}

				// assert: max_of_a_kind === 1
				// 'High Card'
				if (get_rank) {
					// max 會被 return
					(this.high_card = values.clone().sort()).pop();

					// assert: score ≥ 1, it's when [2,2,2,2,3]
					return most_frequency_value;
				}
			}

			/**
			 * 判斷是否贏對手。
			 * 
			 * @param {String}hand
			 *            cards 對手牌面。
			 * 
			 * @returns {Boolean}wins, beats; or undefined: ties with
			 */
			function defeats(hand) {
				if (typeof hand === 'string')
					hand = new Poker_cards(hand);
				var my_category = this.category(true), category = hand
						.category(true);
				if (category !== my_category)
					return category < my_category;

				var my_high_card = this.high_card;
				if (!my_high_card)
					return;

				var index = my_high_card.length,
				// 先比牌型，再比點數 (最後花色)。
				// 先比同樣點數張數最多的牌，再比同樣點數張數少的牌。
				high_card = hand.high_card;
				if (!high_card) {
					CeL.warn(this.toString() + ', ' + hand.toString());
					CeL.warn(this);
					CeL.warn(hand);
				}
				while (index--)
					if (high_card[index] !== my_high_card[index])
						return high_card[index] < my_high_card[index];
			}

			// ---------------------------------------------------------------------------------------------------------------------------------------//
			// export.

			Poker_cards.value_name = value_name;
			Poker_cards.value_index = value_index;

			Poker_cards.suit_name = suit_name;
			Poker_cards.suit_index = suit_index;
			Poker_cards.set_suit_index = set_suit_index;

			Poker_cards.category_score = category_score;

			Poker_cards.prototype = {
				to_Array : to_Array,
				toString : toString,

				// TODO: arrange : arrange

				consecutive : consecutive,

				category : category,
				defeats : defeats
			};

			// ---------------------------------------

			return (Poker_cards// JSDT:_module_
			);
		}
	});
