package main

import (
	"fmt"
	"sort"

	h "git.bullercodeworks.com/brian/adventofcode/helpers"
)

var p2 bool

// 251515138 is too high

func main() {
	inp := h.StdinToStringSlice()
	part1(inp)
	fmt.Println()
	part2(inp)
}

func part1(input []string) {
	var hands []*Hand
	for i := range input {
		hands = append(hands, NewHand(input[i]))
	}

	sort.Sort(sort.Reverse(ByRank(hands)))

	var total int
	for i := range hands {
		total += hands[i].bid * (i + 1)
	}
	fmt.Println()
	fmt.Println("# Part 1")
	fmt.Println(total)
}

func part2(input []string) {
	p2 = true
	var hands []*Hand
	for i := range input {
		hands = append(hands, NewHand(input[i]))
	}

	sort.Sort(sort.Reverse(ByRank(hands)))

	var total int
	for i := range hands {
		total += hands[i].bid * (i + 1)
	}
	fmt.Println()
	fmt.Println("# Part 2")
	fmt.Println(total)
}

type Hand struct {
	raw      string
	cards    []byte
	bid      int
	handType int
	cardMap  map[byte]int
}

const (
	TypeFiveOfKind  = 6
	TypeFourOfKind  = 5
	TypeFullHouse   = 4
	TypeThreeOfKind = 3
	TypeTwoPair     = 2
	TypeOnePair     = 1
	TypeHighCard    = 0
)

var typeToString = map[int]string{
	TypeFiveOfKind:  "Five of a Kind",
	TypeFourOfKind:  "Four of a Kind",
	TypeFullHouse:   "Full House",
	TypeThreeOfKind: "Three of a Kind",
	TypeTwoPair:     "Two Pair",
	TypeOnePair:     "One Pair",
	TypeHighCard:    "High Card",
}

func cardToValue(b byte) int {
	switch b {
	case 'T':
		return 10
	case 'J':
		if p2 {
			return 1
		}
		return 11
	case 'Q':
		return 12
	case 'K':
		return 13
	case 'A':
		return 14
	}
	return int(b - '0')
}

func NewHand(input string) *Hand {
	hand := Hand{raw: input}
	i := 0
	for i = range input {
		if input[i] == ' ' {
			break
		}
		hand.cards = append(hand.cards, input[i])
	}
	hand.bid = h.Atoi(input[i:])
	hand.handType = hand.findType()
	return &hand
}

func (hand *Hand) findType() int {
	hand.cardMap = make(map[byte]int)
	for i := range hand.cards {
		hand.cardMap[hand.cards[i]]++
	}
	if p2 {
		if jCnt, ok := hand.cardMap['J']; ok {
			var c byte
			cCnt := h.MIN_INT
			for k, v := range hand.cardMap {
				if k != 'J' {
					if v > cCnt {
						cCnt = v
						c = k
					}
				}
			}
			hand.cardMap[c] = hand.cardMap[c] + jCnt
			delete(hand.cardMap, 'J')
		}
	}
	max := h.MIN_INT
	for _, v := range hand.cardMap {
		if v > max {
			max = v
		}
	}
	switch len(hand.cardMap) {
	case 5:
		return TypeHighCard
	case 4:
		return TypeOnePair
	case 3:
		if max == 3 {
			return TypeThreeOfKind
		}
		return TypeTwoPair
	case 2:
		if max == 3 {
			return TypeFullHouse
		}
		return TypeFourOfKind
	default:
		return TypeFiveOfKind
	}
}

func (hand Hand) String() string {
	return fmt.Sprintf("%s %d", string(hand.cards), hand.bid)
}

type ByRank []*Hand

func (rank ByRank) Len() int      { return len(rank) }
func (rank ByRank) Swap(i, j int) { rank[i], rank[j] = rank[j], rank[i] }
func (rank ByRank) Less(i, j int) bool {
	if rank[i].handType > rank[j].handType {
		return true
	} else if rank[j].handType > rank[i].handType {
		return false
	}
	for k := range rank[i].cards {
		cmp := cmpCards(rank[i].cards[k], rank[j].cards[k])
		if cmp != 0 {
			return cmp == 1
		}
	}
	return false
}

func cmpCards(c1, c2 byte) int {
	if cardToValue(c1) > cardToValue(c2) {
		return 1
	} else if cardToValue(c1) < cardToValue(c2) {
		return -1
	}
	return 0
}