package pokercore

import "fmt"

type PokerHandType int

const (
	HighCard PokerHandType = iota
	Pair
	TwoPair
	ThreeOfAKind
	Straight
	Flush
	FullHouse
	FourOfAKind
	StraightFlush
	RoyalFlush
)

type PokerHand struct {
	Hand  Cards
	Type  PokerHandType
	Score HandScore
}

func (c Cards) Append(other Cards) Cards {
	return append(c, other...)
}

func (c Cards) PokerHand() (*PokerHand, error) {
	if len(c) != 5 {
		return nil, fmt.Errorf("hand must have 5 cards to be scored as a poker hand")
	}
	if c.containsDuplicates() {
		return nil, fmt.Errorf("hand must have no duplicates to be scored as a poker hand")
	}

	ph := new(PokerHand)
	ph.Hand = c
	if c.containsRoyalFlush() {
		ph.Type = RoyalFlush
		ph.Score = ScoreRoyalFlush
		return ph, nil
	}

	if c.containsStraightFlush() {
		ph.Type = StraightFlush
		ph.Score = ph.Hand.scoreStraightFlush()
		return ph, nil
	}

	if c.containsFourOfAKind() {
		ph.Type = FourOfAKind
		ph.Score = ScoreFourOfAKind + 1000*c.fourOfAKindRank().Score() + c.fourOfAKindKicker().Score()
		return ph, nil
	}

	if c.containsFullHouse() {
		ph.Type = FullHouse
		ph.Score = ScoreFullHouse + 1000*c.fullHouseTripsRank().Score() + c.fullHousePairRank().Score()
		return ph, nil
	}

	if c.containsFlush() {
		ph.Type = Flush
		ph.Score = c.scoreFlush()
		return ph, nil
	}

	if c.containsStraight() {
		ph.Type = Straight

		// Straights are scored by the highest card in the straight
		// UNLESS the second highest card is a 5 and the highest card is an Ace
		// In that case, the straight is a 5-high straight, not an Ace-high straight
		sorted := c.SortByRankAscending()

		if sorted[3].Rank == FIVE && sorted[4].Rank == ACE {
			// 5-high straight
			ph.Score = ScoreStraight + 1000*sorted[3].Score()
		} else {
			// All other straights
			ph.Score = ScoreStraight + 1000*sorted[4].Score()
		}
		return ph, nil
	}

	if c.containsThreeOfAKind() {
		ph.Type = ThreeOfAKind
		ph.Score = ScoreThreeOfAKind
		ph.Score += 1000 * c.threeOfAKindTripsRank().Score()
		ph.Score += 100 * c.threeOfAKindFirstKicker().Score()
		ph.Score += 10 * c.threeOfAKindSecondKicker().Score()
		return ph, nil
	}

	if c.containsTwoPair() {
		ph.Type = TwoPair
		ph.Score = ScoreTwoPair
		ph.Score += 1000 * c.twoPairBiggestPair().Score()
		ph.Score += 100 * c.twoPairSmallestPair().Score()
		ph.Score += 10 * c.twoPairKicker().Score()
		return ph, nil
	}

	if c.containsPair() {
		ph.Type = Pair
		ph.Score = ScorePair
		ph.Score += 1000 * c.pairRank().Score()
		ph.Score += 100 * c.pairFirstKicker().Score()
		ph.Score += 10 * c.pairSecondKicker().Score()
		ph.Score += c.pairThirdKicker().Score()
		return ph, nil
	}

	ph.Type = HighCard
	ph.Score = c.scoreHighCard()
	return ph, nil
}

func (ph PokerHand) ToSortedCards() Cards {
	sorted := ph.Hand.SortByRankAscending()
	return sorted
}

func (ph PokerHand) Compare(other PokerHand) int {
	if ph.Score > other.Score {
		return 1
	}
	if ph.Score < other.Score {
		return -1
	}
	return 0
}

func (ph PokerHand) HighestRank() Rank {
	return ph.Hand.HighestRank()
}

func (ph PokerHand) String() string {
	return fmt.Sprintf("<PokerHand: %s (%s)>", ph.Hand.String(), ph.Description())
}

func (c PokerHand) Description() string {
	sortedHand := c.Hand.SortByRankAscending()
	if c.Type == RoyalFlush {
		return fmt.Sprintf("a royal flush in %s", c.Hand[0].Suit)
	}
	if c.Hand.containsStraightFlush() {
		if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE {
			// special case for steel wheel
			return fmt.Sprintf("%s high straight flush in %s", sortedHand[3].Rank.WithArticle(), sortedHand[4].Suit)
		}
		return fmt.Sprintf("%s high straight flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit)
	}
	if c.Hand.containsFourOfAKind() {
		return fmt.Sprintf("four %s with %s", c.Hand.fourOfAKindRank().Pluralize(), c.Hand.fourOfAKindKicker().Rank.WithArticle())
	}
	if c.Hand.containsFullHouse() {
		return fmt.Sprintf("a full house, %s full of %s", c.Hand.fullHouseTripsRank().Pluralize(), c.Hand.fullHousePairRank().Pluralize())
	}
	if c.Hand.containsFlush() {
		return fmt.Sprintf("%s high flush in %s", c.HighestRank().WithArticle(), sortedHand[4].Suit)
	}
	if c.Hand.containsStraight() {
		if sortedHand[3].Rank == FIVE && sortedHand[4].Rank == ACE {
			// special case for wheel straight
			return fmt.Sprintf("%s high straight", sortedHand[3].Rank.WithArticle())
		}
		return fmt.Sprintf("%s high straight", c.HighestRank().WithArticle())
	}
	if c.Hand.containsThreeOfAKind() {
		return fmt.Sprintf(
			"three %s with %s and %s",
			c.Hand.threeOfAKindTripsRank().Pluralize(),
			c.Hand.threeOfAKindFirstKicker().Rank.WithArticle(),
			c.Hand.threeOfAKindSecondKicker().Rank.WithArticle(),
		)
	}
	if c.Hand.containsTwoPair() {
		return fmt.Sprintf(
			"two pair, %s and %s with %s",
			c.Hand.twoPairBiggestPair().Pluralize(),
			c.Hand.twoPairSmallestPair().Pluralize(),
			c.Hand.twoPairKicker().Rank.WithArticle(),
		)
	}
	if c.Hand.containsPair() {
		return fmt.Sprintf(
			"a pair of %s with %s, %s, and %s",
			c.Hand.pairRank().Pluralize(),
			c.Hand.pairFirstKicker().Rank.WithArticle(),
			c.Hand.pairSecondKicker().Rank.WithArticle(),
			c.Hand.pairThirdKicker().Rank.WithArticle(),
		)
	}

	return fmt.Sprintf(
		// "ace high with an eight, a seven, a six, and a deuce"
		"%s high with %s, %s, %s, and %s",
		sortedHand[4].Rank,
		sortedHand[3].Rank.WithArticle(),
		sortedHand[2].Rank.WithArticle(),
		sortedHand[1].Rank.WithArticle(),
		sortedHand[0].Rank.WithArticle(),
	)
}
