import { cactusFastRankHand } from './cactusFastRankHand';
import { Card } from './card';

type RankerFunc = (hand: [Card, Card, Card, Card, Card]) => number;

export const ROYAL_FLUSH = 1;
export const STRAIGHT_FLUSH = 10;
export const FOUR_OF_A_KIND = 166;
export const FULL_HOUSE = 322;
export const FLUSH = 1599;
export const STRAIGHT = 1609;
export const THREE_OF_A_KIND = 2467;
export const TWO_PAIR = 3325;
export const PAIR = 6185;
export const NOTHING = 7462;
export const HIGH_CARD = NOTHING;
export const INVALID = 65535;

export const SIXPLUS_FLUSH = 166 + 1277;
export const SIXPLUS_FULL_HOUSE = SIXPLUS_FLUSH + 156;

export const EIGHT_OR_BETTER_MAX = 512;

const t7c5 = [
    [0, 1, 2, 3, 4, 5, 6],
    [0, 1, 2, 3, 5, 4, 6],
    [0, 1, 2, 3, 6, 4, 5],
    [0, 1, 2, 4, 5, 3, 6],
    [0, 1, 2, 4, 6, 3, 5],
    [0, 1, 2, 5, 6, 3, 4],
    [0, 1, 3, 4, 5, 2, 6],
    [0, 1, 3, 4, 6, 2, 5],
    [0, 1, 3, 5, 6, 2, 4],
    [0, 1, 4, 5, 6, 2, 3],
    [0, 2, 3, 4, 5, 1, 6],
    [0, 2, 3, 4, 6, 1, 5],
    [0, 2, 3, 5, 6, 1, 4],
    [0, 2, 4, 5, 6, 1, 3],
    [0, 3, 4, 5, 6, 1, 2],
    [1, 2, 3, 4, 5, 0, 6],
    [1, 2, 3, 4, 6, 0, 5],
    [1, 2, 3, 5, 6, 0, 4],
    [1, 2, 4, 5, 6, 0, 3],
    [1, 3, 4, 5, 6, 0, 2],
    [2, 3, 4, 5, 6, 0, 1],
];

export type HandRank = {
    rank: number;
    madeHand: [Card, Card, Card, Card, Card];
    low?: {
        rank: number;
        madeHand: [Card, Card, Card, Card, Card];
    };
};

const rank567cardHand = (hand: Card[], f: RankerFunc = cactusFastRankHand): HandRank => {
    if (hand.length === 5) {
        const rank = f([hand[0], hand[1], hand[2], hand[3], hand[4]]);

        return {
            rank,
            madeHand: [hand[0], hand[1], hand[2], hand[3], hand[4]],
        };
    }

    if (hand.length === 6) {
        const possibleHands: [Card, Card, Card, Card, Card][] = [
            [hand[0], hand[1], hand[2], hand[3], hand[4]],
            [hand[0], hand[1], hand[2], hand[3], hand[5]],
            [hand[0], hand[1], hand[2], hand[4], hand[5]],
            [hand[0], hand[1], hand[3], hand[4], hand[5]],
            [hand[0], hand[2], hand[3], hand[4], hand[5]],
            [hand[1], hand[2], hand[3], hand[4], hand[5]],
        ];

        const sortedHands = possibleHands
            .map((h) => ({
                rank: f(h),
                madeHand: h,
            }))
            .sort((a, b) => a.rank - b.rank);

        return sortedHands[0];
    }

    if (hand.length === 7) {
        let r = 0;
        let rank = 9999;
        let bestHand: [Card, Card, Card, Card, Card] = [hand[0], hand[1], hand[2], hand[3], hand[4]];

        for (let i = 0; i < 21; i++) {
            const inputHand: [Card, Card, Card, Card, Card] = [
                hand[t7c5[i][0]],
                hand[t7c5[i][1]],
                hand[t7c5[i][2]],
                hand[t7c5[i][3]],
                hand[t7c5[i][4]],
            ];
            r = f(inputHand);
            if (r < rank) {
                rank = r;
                bestHand = inputHand;
            }
        }
        return {
            rank,
            madeHand: bestHand,
        };
    }

    throw new Error(`Hand ranker doesn't support ${hand.length} cards`);
};

export const toFixedTexasRank = (r: number) => {
    if (r === ROYAL_FLUSH) {
        return ROYAL_FLUSH;
    }
    if (r <= STRAIGHT_FLUSH) {
        return STRAIGHT_FLUSH;
    }
    if (r <= FOUR_OF_A_KIND) {
        return FOUR_OF_A_KIND;
    }
    if (r <= FULL_HOUSE) {
        return FULL_HOUSE;
    }
    if (r <= FLUSH) {
        return FLUSH;
    }
    if (r <= STRAIGHT) {
        return STRAIGHT;
    }
    if (r <= THREE_OF_A_KIND) {
        return THREE_OF_A_KIND;
    }
    if (r <= TWO_PAIR) {
        return TWO_PAIR;
    }
    if (r <= PAIR) {
        return PAIR;
    }
    if (r != INVALID) {
        return NOTHING;
    }
    return INVALID;
};

export const toFixedSixPlusRank = (r: number) => {
    if (r === ROYAL_FLUSH) {
        return ROYAL_FLUSH;
    }
    if (r <= STRAIGHT_FLUSH) {
        return STRAIGHT_FLUSH;
    }
    if (r <= FOUR_OF_A_KIND) {
        return FOUR_OF_A_KIND;
    }
    if (r <= SIXPLUS_FLUSH) {
        return SIXPLUS_FLUSH;
    }
    if (r <= SIXPLUS_FULL_HOUSE) {
        return SIXPLUS_FULL_HOUSE;
    }
    if (r <= STRAIGHT) {
        return STRAIGHT;
    }
    if (r <= THREE_OF_A_KIND) {
        return THREE_OF_A_KIND;
    }
    if (r <= TWO_PAIR) {
        return TWO_PAIR;
    }
    if (r <= PAIR) {
        return PAIR;
    }
    if (r != INVALID) {
        return NOTHING;
    }
    return INVALID;
};

export const rankTexasHand = (pocket: Card[], board: Card[]): HandRank => {
    return rank567cardHand([...pocket, ...board], cactusFastRankHand);
};

export const convertToSixPlusHandRank = (handRank: number): number => {
    if (handRank === 747) {
        handRank = 6;
    } else if (handRank === 6610) {
        handRank = 1605;
    }

    // Swap full_house with flush
    const fixedRank = toFixedTexasRank(handRank);

    if (fixedRank === FULL_HOUSE) {
        handRank = handRank - FOUR_OF_A_KIND + 166 + 1277;
    } else if (fixedRank === FLUSH) {
        handRank = handRank - FULL_HOUSE + 166;
    }
    return handRank;
};

export const rankShortDeckHand = (pocket: Card[], board: Card[]): HandRank => {
    const f = (hand: [Card, Card, Card, Card, Card]) => {
        const handRank = cactusFastRankHand(hand);
        return convertToSixPlusHandRank(handRank);
    };

    return rank567cardHand([...pocket, ...board], f);
};

const t4c2 = [
    [0, 1, 2, 3],
    [0, 2, 1, 3],
    [0, 3, 1, 2],
    [1, 2, 0, 3],
    [1, 3, 0, 2],
    [2, 3, 0, 1],
];

const t3c3 = [[0, 1, 2]];

const t4c3 = [
    [0, 1, 2, 3],
    [1, 2, 3, 0],
    [2, 3, 0, 1],
    [3, 0, 1, 2],
];

const t5c3 = [
    [0, 1, 2, 3, 4],
    [0, 1, 3, 2, 4],
    [0, 1, 4, 2, 3],
    [0, 2, 3, 1, 4],
    [0, 2, 4, 1, 3],
    [0, 3, 4, 1, 2],
    [1, 2, 3, 0, 4],
    [1, 2, 4, 0, 3],
    [1, 3, 4, 0, 2],
    [2, 3, 4, 0, 1],
];

// AceRank returns the card [Ace]-low index.
export const getAceRank = (card: Card) => {
    // int(c>>8&0xf+1) % 13
    return (((card >> 8) & 0xf) + 1) % 13;
};

export const rankAceFiveLow = (mask: number, hand: [Card, Card, Card, Card, Card]) => {
    let rank = 0;

    for (const card of hand) {
        const n = getAceRank(card);
        rank |= (1 << n) | ((((mask & (1 << n)) >>> n) & 1) * 0x8000);
        mask |= 1 << n;
    }

    return rank;
};

// RankEightOrBetter is a 8-or-better low rank eval func. [Ace]'s are low,
// [Straight]'s and [Flush]'s do not count.
export const rankEightOrBetter = (hand: [Card, Card, Card, Card, Card]) => {
    return rankAceFiveLow(0xff00, hand);
};

export const rankOmahaHand = (pocket: Card[], board: Card[], low = false): HandRank => {
    let bestHand: [Card, Card, Card, Card, Card] = [pocket[0], pocket[1], board[0], board[1], board[2]];
    let bestRank = INVALID;

    let lowBestHand: [Card, Card, Card, Card, Card] = [pocket[0], pocket[1], board[0], board[1], board[2]];
    let lowBestRank = INVALID;

    const boardTable = board.length === 3 ? t3c3 : board.length === 4 ? t4c3 : t5c3;

    for (let i = 0; i < 6; i++) {
        for (let j = 0; j < boardTable.length; j++) {
            const hand: [Card, Card, Card, Card, Card] = [
                pocket[t4c2[i][0]],
                pocket[t4c2[i][1]],
                board[boardTable[j][0]],
                board[boardTable[j][1]],
                board[boardTable[j][2]],
            ];

            const rank = cactusFastRankHand(hand);
            if (rank < bestRank) {
                bestHand = hand;
                bestRank = rank;
            }
            if (low) {
                const lowRank = rankEightOrBetter(hand);
                if (lowRank < lowBestRank) {
                    lowBestHand = hand;
                    lowBestRank = lowRank;
                }
            }
        }
    }
    if (low && lowBestRank < EIGHT_OR_BETTER_MAX) {
        return {
            rank: bestRank,
            madeHand: bestHand,
            low: {
                rank: lowBestRank,
                madeHand: lowBestHand,
            },
        };
    }

    return {
        rank: bestRank,
        madeHand: bestHand,
    };
};
