package pokercore

import (
	"fmt"
	rand "math/rand"
	"sync"
)

type Deck struct {
	mu             sync.Mutex
	Cards          Cards
	ShuffleSeedVal int64
	Dealt          int
}

func NewDeckFromCards(cards Cards) *Deck {
	d := new(Deck)
	d.Cards = make([]Card, len(cards))
	copy(d.Cards, cards)
	return d
}

func NewEmptyDeck() *Deck {
	d := new(Deck)
	d.Cards = make([]Card, 0)
	return d
}

// NewDeck returns a new deck of 52 sorted cards.
func NewDeck() *Deck {
	d := NewEmptyDeck()

	ranks := []Rank{
		ACE, DEUCE, THREE, FOUR, FIVE,
		SIX, SEVEN, EIGHT, NINE, TEN, JACK,
		QUEEN, KING}

	// This is the suit order used by dealers at
	// The Golden Nugget in Las Vegas.
	suits := []Suit{SPADE, HEART, DIAMOND, CLUB}

	for _, s := range suits {
		for _, r := range ranks {
			d.AddCard(Card{Rank: r, Suit: s})
		}
	}

	return d
}

func (d *Deck) AddCard(c Card) {
	d.mu.Lock()
	defer d.mu.Unlock()
	d.Cards = append(d.Cards, c)
}

// ShuffleRandomly shuffles the deck using cryptographically random numbers.
func (d *Deck) ShuffleRandomly() {
	d.mu.Lock()
	defer d.mu.Unlock()
	rnd := rand.New(rand.NewSource(int64(cryptoUint64())))
	//FIXME(sneak) not sure if this is constant time or not
	rnd.Shuffle(len(d.Cards), func(i, j int) { d.Cards[i], d.Cards[j] = d.Cards[j], d.Cards[i] })
}

// ShuffleDeterministically shuffles the deck using a deterministic seed for testing.
func (d *Deck) ShuffleDeterministically(seed int64) {
	d.mu.Lock()
	defer d.mu.Unlock()
	r := rand.New(rand.NewSource(seed))
	//FIXME(sneak) not sure if this is constant time or not
	r.Shuffle(len(d.Cards), func(i, j int) { d.Cards[i], d.Cards[j] = d.Cards[j], d.Cards[i] })
}

// Deal removes n cards from the top of the deck and returns them.  The Deck is
// modified in place.to remove the dealt cards, just like in real life.
func (d *Deck) Deal(n int) (output Cards) {
	d.mu.Lock()
	defer d.mu.Unlock()
	for i := 0; i < n; i++ {
		output = append(output, d.Cards[i])
	}
	d.Cards = d.Cards[n:]
	d.Dealt += n
	return output
}

func (d *Deck) FormatForTerminal() string {
	return d.Cards.FormatForTerminal()
}

func (d *Deck) String() string {
	return fmt.Sprintf("Deck{%s size=%d dealt=%d}", d.Cards, d.Count(), d.Dealt)
}

func (d *Deck) Count() int {
	return len(d.Cards)
}
