// Global variable setup

var cardRanks = new Array();
cardRanks[0] = "2";
cardRanks[1] = "3";
cardRanks[2] = "4";
cardRanks[3] = "5";
cardRanks[4] = "6";
cardRanks[5] = "7";
cardRanks[6] = "8";
cardRanks[7] = "9";
cardRanks[8] = "T";
cardRanks[9] = "J";
cardRanks[10] = "Q";
cardRanks[11] = "K";
cardRanks[12] = "A";

var cardHash = {}
for ( var i = 0; i < 13; ++i ) {
   cardHash[ cardRanks[ i ] ] = i + 1;
}

var cardSuits = new Array();
cardSuits[0] = "c";
cardSuits[1] = "d";
cardSuits[2] = "h";
cardSuits[3] = "s";

var cardNames = new Array();
for ( var i = 0; i < 52; ++i ) {
    cardNames[ i ] = cardRanks[ i % 13 ] + cardSuits[ Math.floor( i / 13 ) ];
}

var cardsLeft = {};
var ranksLeft = {};

for ( var i = 0; i < 13; ++i ) {
    ranksLeft[ cardRanks[ i ] ] = 4;
}

for ( var i = 0; i < 52; ++i ) {
    cardsLeft[ cardNames[ i ] ] = 1;
}

var cardsInHand = new Array();

function showCardsInHand() {
    var cell = document.getElementById( "handcards" );
    var text = cardsInHand.sort().reverse().join( " " );
    if ( text == "" ) text = "&nbsp;";
    cell.innerHTML = text;
    if ( cardsInHand.length == 4 ) {
        cell.className = "cardinhand";
    } else {
        cell.className = "cardinvalid";
    }
}

var deadCards = new Array();

function showDeadCards() {
    var cell = document.getElementById( "deadcards" );
    var text = deadCards.join( " " );
    if ( text == "" ) text = "&nbsp;";
    cell.innerHTML = text;
}


function addCardToHand( card ) {
    cardsInHand.push( card );
    if ( cardsLeft[ card ] == 0 ) {
        removeCardFromDeadList( card );
    }
    cardsLeft[ card ]--;
    ranksLeft[ card.substring( 0, 1 ) ]--;

    var cell = document.getElementById( "hand_" + card );
    cell.className = "cardinhand";
}

function removeCardFromHand( card ) {
    for ( var i = 0; i < cardsInHand.length; ++i ) {        
        if ( cardsInHand[i] == card ) {
            cardsInHand.splice( i, 1 );
            break;
        }           
    }       
    cardsLeft[ card ]++;
    ranksLeft[ card.substring( 0, 1 ) ]++;

    var cell = document.getElementById( "hand_" + card );
    cell.className = "card";         
}

function addCardToDeadList( card ) {
    deadCards.push( card );
    if ( cardsLeft[ card ] == 0 ) {
        removeCardFromHand( card );
    }
    cardsLeft[ card ]--;
    ranksLeft[ card.substring( 0, 1 ) ]--;

    var cell = document.getElementById( "dead_" + card );
    cell.className = "carddead";
}

function removeCardFromDeadList( card ) {
    for ( var i = 0; i < deadCards.length; ++i ) {        
        if ( deadCards[i] == card ) {
            deadCards.splice( i, 1 );
            break;
        }           
    }       
    cardsLeft[ card ]++;
    ranksLeft[ card.substring( 0, 1 ) ]++;

    var cell = document.getElementById( "dead_" + card );
    cell.className = "card";         
}

function selectCardInHand() {
    var oldClass = this.className;
    var card = this.id.substring( 5 );
    if ( oldClass == "card" ) {
        addCardToHand( card );
    } else {
        removeCardFromHand( card );
    }
    update();
}

function selectDeadCard() {
    var oldClass = this.className;
    var card = this.id.substring( 5 );
    if ( oldClass == "card" ) {
        addCardToDeadList( card );
    } else {
        removeCardFromDeadList( card );
    }
    update();
}

function includeOpponentHand( handName ) {
    var row = document.getElementById( "row_" + handName );
    row.className = "handincluded";
    handSelected[ handName ] = 1;
}

function unincludeOpponentHand( handName ) {
    var row = document.getElementById( "row_" + handName );
    row.className = "hand";
    handSelected[ handName ] = 0;
}

function selectOpponentHand() {
    var oldClass = this.className;
    var handName = this.id.substring( 4 );
    
    if ( oldClass == "hand" ) {
        includeOpponentHand( handName );
    } else {
        unincludeOpponentHand( handName );
    }
    update();
}

function selectOpponentHandUpTo() {
    var oldClass = this.className;
    var lastHandName = this.id.substring( 7 );
    var i = 0;
    for ( ; i < handNames.length; ++i ) {
        includeOpponentHand( handNames[ i ] );
        if ( handNames[ i ] == lastHandName ) break;
    }
    for ( ; i < handNames.length; ++i ) {
        unincludeOpponentHand( handNames[ i ] );
    }
    update();
}

function initHandSelection() {
    for ( var i = 0; i < 52; ++i ) {
        var id = "hand_" + cardNames[ i ];
        var cell = document.getElementById( id );
        if ( cell ) {
            cell.onclick = selectCardInHand;
        } else { 
            alert( "Internal error: Couldn't find cell " + id );
            return;
        }
    }         
}

function initDeadCardSelection() {
    for ( var i = 0; i < 52; ++i ) {
        var id = "dead_" + cardNames[ i ];
        var cell = document.getElementById( id );
        if ( cell ) {
            cell.onclick = selectDeadCard;
        } else { 
            alert( "Internal error: Couldn't find cell " + id );
            return;
        }
    }         
}

var handNames = new Array();
handNames[0] = "75432";
handNames[1] = "76432";
handNames[2] = "76532";
handNames[3] = "76542";
handNames[4] = "85432";
handNames[5] = "86432";
handNames[6] = "86532";
handNames[7] = "86542";
handNames[8] = "86543";
handNames[9] = "87432";
handNames[10] = "87532";
handNames[11] = "87542";
handNames[12] = "87543";
handNames[13] = "87632";
handNames[14] = "87642";
handNames[15] = "87643";
handNames[16] = "87652";
handNames[17] = "87653";
handNames[18] = "95432";
handNames[19] = "96432";
handNames[20] = "96532";
handNames[21] = "96542";
handNames[22] = "96543";
handNames[23] = "97432";
handNames[24] = "97532";
handNames[25] = "97542";
handNames[26] = "97543";
handNames[27] = "97632";
handNames[28] = "97642";
handNames[29] = "97643";
handNames[30] = "97652";
handNames[31] = "97653";
handNames[32] = "97654";
handNames[33] = "98432";
handNames[34] = "98532";
handNames[35] = "98542";
handNames[36] = "98543";
handNames[37] = "98632";
handNames[38] = "98642";
handNames[39] = "98643";
handNames[40] = "98652";
handNames[41] = "98653";
handNames[42] = "98654";
handNames[43] = "98732";
handNames[44] = "98742";
handNames[45] = "98743";
handNames[46] = "98752";
handNames[47] = "98753";
handNames[48] = "98754";
handNames[49] = "98762";
handNames[50] = "98763";
handNames[51] = "98764";

var handSelected = {};

function addRow( h ) {
    var t = document.getElementById( "opphandtable" );
    var tb = t.getElementsByTagName( "tbody" )[0];

    handSelected[ h ] = 0;
    
    var r = document.createElement( "tr" );
    r.setAttribute( "id", "row_" + h );
    r.setAttribute( "class", "hand" );
    
    var d = document.createElement( "td" );
    var text = document.createTextNode( h );
    d.appendChild( text );
    r.appendChild( d );
    
    d = document.createElement( "td" );
    d.setAttribute( "id", "prob_" + h );
    r.appendChild( d );
    
    d = document.createElement( "td" );
    d.setAttribute( "id", "outs_" + h );
    r.appendChild( d );
    
    d = document.createElement( "td" );
    d.setAttribute( "id", "select_" + h );
    d.setAttribute( "align", "center" );
    text = document.createTextNode( "\u25b2" );
    d.appendChild( text );
    r.appendChild( d );
    
    tb.appendChild( r );
}

function writeHandSelectionRows() {
    for ( var i = 0; i < handNames.length; ++i ) {
        var h = handNames[ i ];
        addRow( h );
    }       
}

function addNewHand() {
    var textField = document.getElementById( "newHandText" );
    var nh = textField.value;

    // Damn freeform user input.  Validate it.
    if ( nh.length != 5 ) {
        alert( "Opponent hands must contain 5 cards." );
        return;
    }

    nh = nh.toUpperCase();
    
    var hc = new Array();
    
    for ( var i = 0; i < 5; ++i ) {
        hc[i] = nh.substring( i, i + 1 );
        if ( cardHash[ hc[ i ] ] == undefined ) {
            alert( "Unknown cards, use 23456789TJQKA.");
            return;
        }
        hc[i] = cardHash[ hc[ i ] ];
        for ( var j = 0; j < i; ++j ) {
            if ( hc[ i ] == hc[ j ] ) {
                alert( "Sorry, can't handle pairs in opponent hands." );
                return;
            }
        }
    }
    hc.sort( descendingOrder );

    if ( hc[ 0 ] == hc[ 1 ] + 1 &&
         hc[ 1 ] == hc[ 2 ] + 1 &&
         hc[ 2 ] == hc[ 3 ] + 1 &&
         hc[ 3 ] == hc[ 4 ] + 1 ) {
        alert( "Sorry, can't handle straights in opponent hands." );
        return;
    }

    nh = "";
    for ( var i = 0; i < 5; ++i ) {
        nh += cardRanks[ hc[ i ] - 1 ];        
    }  

    for ( var i = 0; i < handNames.length; ++i ) {
        if ( nh == handNames[ i ] ) {
            alert( "Hand already exists in table." );
            return;
        }
    }

    handNames.push( nh );
    addRow( nh );
    setupRowHandlers( nh );
}

function setupRowHandlers( h ) {
    var id = "row_" + h;
    var cell = document.getElementById( id );
    cell.onclick = selectOpponentHand;
    
    id = "select_" + h;
    cell = document.getElementById( id );
    cell.onclick = selectOpponentHandUpTo;
}

function initOpponentHandSelection() {
    writeHandSelectionRows();

    for ( var i = 0; i < handNames.length; ++i ) {
        setupRowHandlers( handNames[ i ] );
    }         
}

var handWeights = new Array();
var totalWeight = 0;

function calcNumberOfHands( h, j ) {
    var hc = new Array();
    hc[0] = h.substring( 0, 1 );
    hc[1] = h.substring( 1, 2 );
    hc[2] = h.substring( 2, 3 );
    hc[3] = h.substring( 3, 4 );
    hc[4] = h.substring( 4, 5 );
    
    var numPossible = 1;
    for ( var i = 0; i < 5; ++i ) {
         numPossible *= ranksLeft[ hc[ i ] ];
    }      

    var numFlushes = 4;
    for ( var s = 0; s < 4; ++s ) {
        for ( var i = 0; i < 5; ++i ) {
            if ( !cardsLeft[ hc[i] + cardSuits[s] ] ) {
               --numFlushes;
               break;
            }
        }
    }

    handWeights[ j ] = numPossible - numFlushes;
    return numPossible - numFlushes;
}

function calcNumberOfHandsUsingCardAndSuit( h, c, s ) {
    var hc = new Array();
    hc[0] = h.substring( 0, 1 );
    hc[1] = h.substring( 1, 2 );
    hc[2] = h.substring( 2, 3 );
    hc[3] = h.substring( 3, 4 );
    hc[4] = h.substring( 4, 5 );
    
    var numPossible = 1;
    for ( var i = 0; i < 5; ++i ) {
        if ( hc[ i ] != c )
            numPossible *= ranksLeft[ hc[ i ] ];
    }      

    var numFlushes = 1;
    for ( var i = 0; i < 5; ++i ) {
        if ( !cardsLeft[ hc[i] + s ] ) {
            --numFlushes;
            break;
        }
    }
    return numPossible - numFlushes;    
}

function updateHandWeights() {
    totalWeight = 0;

    for ( var i = 0; i < handNames.length; ++i ) {
        var h = handNames[ i ];

        if ( handSelected[ h ] ) {
            var w = calcNumberOfHands( h, i );
            totalWeight += w;
        } else {
            handWeights[ i ] = 0;
        }
    }

    for ( var i = 0; i < handNames.length; ++i ) {
        var h = handNames[ i ];
        var probCell = document.getElementById( "prob_" + h );
        
        if ( handSelected[ h ] ) {
            probCell.innerHTML = 
                ( handWeights[ i ] / totalWeight ).toFixed( 6 );
        } else {
            probCell.innerHTML = "";
        }
    }
}

var ranksInHand;
var suitsInHand;
var flushDraw;
var pairDraw;

function descendingOrder(a, b) {
   return ( b - a );
}

function splitHand() {
    ranksInHand = new Array();
    suitsInHand = new Array();
    flushDraw = "";
    pairDraw = "";

    for ( var i = 0; i < 4; ++i ) {
        ranksInHand[ i ] = cardHash[ cardsInHand[ i ].substring( 0, 1 ) ];
        suitsInHand[ i ] = cardsInHand[ i ].substring( 1, 2 );
    }
    if ( suitsInHand[ 0 ] == suitsInHand[ 1 ] &&
         suitsInHand[ 0 ] == suitsInHand[ 2 ] &&
         suitsInHand[ 0 ] == suitsInHand[ 3 ] ) {
        flushDraw = suitsInHand[ 0 ];
    }
    if ( ranksInHand[ 0 ] == ranksInHand[ 1 ] ||
         ranksInHand[ 0 ] == ranksInHand[ 2 ] ||
         ranksInHand[ 0 ] == ranksInHand[ 3 ] ) {
        pairDraw = ranksInHand[ 0 ];
    } else if ( ranksInHand[ 1 ] == ranksInHand[ 2 ] ||
                ranksInHand[ 1 ] == ranksInHand[ 3 ] ) {
        pairDraw = ranksInHand[ 1 ];
    } else if ( ranksInHand[ 2 ] == ranksInHand[ 3 ] ) {
        pairDraw = ranksInHand[ 2 ];
    }
 
    // ranksInHand = ranksInHand.sort( descendingOrder );
}

function drawRank( c ) {
    if ( ranksInHand[ 0 ] == c ||
         ranksInHand[ 1 ] == c ||
         ranksInHand[ 2 ] == c ||
         ranksInHand[ 3 ] == c ) {
         return 14 * 14 * 14 * 14 * 14; // Bigger than any unpaired hand.
    }
    var newRanks = ranksInHand.slice( 0 );
    newRanks.push( c );
    newRanks.sort( descendingOrder );
    if ( newRanks[ 0 ] == newRanks[ 1 ] + 1 &&
         newRanks[ 1 ] == newRanks[ 2 ] + 1 &&
         newRanks[ 2 ] == newRanks[ 3 ] + 1 &&
         newRanks[ 3 ] == newRanks[ 4 ] + 1 ) {
         return 14 * 14 * 14 * 14 * 14; // Bigger than any unpaired hand.
    }

    return (
        14 * 14 * 14 * 14 * newRanks[ 0 ] +
        14 * 14 * 14 * newRanks[ 1 ] +
        14 * 14 * newRanks[ 2 ] +
        14 * newRanks[ 3 ] +
        newRanks[ 4 ] );
}

// This function doesn't handle opponent hands that are pairs.
function numOuts( ix ) {
    if ( pairDraw ) return 0;

    var oppHand = handNames[ ix ];    
    var oppRank = 
        14 * 14 * 14 * 14 * cardHash[ oppHand.substring( 0, 1 ) ] +
        14 * 14 * 14 *  cardHash[ oppHand.substring( 1, 2 ) ] +
        14 * 14 * cardHash[ oppHand.substring( 2, 3 ) ] +
        14 * cardHash[ oppHand.substring( 3, 4 ) ] +
        cardHash[ oppHand.substring( 4, 5 ) ];

    var nOuts = 0;
    for ( var ci = 0; ci < 13; ++ci ) {
       var cn = cardRanks[ ci ];
       var myRank = drawRank( ci + 1 );
       if ( myRank <= oppRank ) {
          var baseOuts = ranksLeft[ cn ];
          if ( baseOuts > 0 ) {
             var inOppHand = ( oppHand.indexOf( cn ) >= 0 );
             var flushPossible = ( flushDraw && 
                 cardsLeft[ cn + "" + flushDraw ] );
             var adjustedOuts;
             if ( !inOppHand ) {
                 if ( flushPossible ) {
                     adjustedOuts = baseOuts - 1 ;   
                 } else {
                     adjustedOuts = baseOuts;
                 }
             } else { // One of these outs is in the opponent's hand
                 if ( flushPossible ) {
                     // Sometimes the flush card may be in the opponent
                     // hand.  We don't want to double-count.  If we knew
                     // that opponent couldn't make a flush, then 1/Nth
                     // of the time the opponent would have our flush card
                     // and (N-1)/N of the time there would be 2 dead outs.
                     //
                     // This is only correct to a first approximation, though.
                     // What we need to do is look at how many non-flush
                     // cases the opponent can make with the flush card compared
                     // with the other cards.
                     var numUsingFlushCard = 
                         calcNumberOfHandsUsingCardAndSuit( oppHand, cn, flushDraw );
                     var singleOutProb = numUsingFlushCard / handWeights[ ix ];
                     adjustedOuts = baseOuts - 1 * singleOutProb - 2 * ( 1 - singleOutProb );
                 } else {
                     adjustedOuts = baseOuts - 1;
                 }
             }
             if ( myRank == oppRank ) {
                 nOuts += adjustedOuts / 2;
             } else {
                 nOuts += adjustedOuts;
             }
          }          
       }
    }

    return nOuts;
}

function clearOuts() {
    for ( var i = 0; i < handNames.length; ++i ) {
        var h = handNames[ i ];
        var outsCell = document.getElementById( "outs_" + h );
        outsCell.innerHTML = "";
    }
    document.getElementById( "draw1outs" ).innerHTML = "N/A";
    document.getElementById( "draw2outs" ).innerHTML = "N/A";
    document.getElementById( "draw3outs" ).innerHTML = "N/A";
    document.getElementById( "draw1prob" ).innerHTML = "";
    document.getElementById( "draw2prob" ).innerHTML = "";
    document.getElementById( "draw3prob" ).innerHTML = "";
    document.getElementById( "draw1odds" ).innerHTML = "";
    document.getElementById( "draw2odds" ).innerHTML = "";
    document.getElementById( "draw3odds" ).innerHTML = "";
}

function updateOuts() {
    splitHand();
    var sumOuts = 0;
    for ( var i = 0; i < handNames.length; ++i ) {
        var h = handNames[ i ];        
        if ( handSelected[ h ] ) {
            var nOuts = numOuts( i );
            sumOuts += nOuts * handWeights[ i ];

            var outsCell = document.getElementById( "outs_" + h );        
            outsCell.innerHTML = nOuts.toFixed( 2 );
        } else {
            var outsCell = document.getElementById( "outs_" + h );        
            outsCell.innerHTML = "";
        }
    }
    var avgOuts = sumOuts / totalWeight;

    document.getElementById( "draw1outs" ).innerHTML =
        avgOuts.toFixed( 2 );
    document.getElementById( "draw2outs" ).innerHTML =
        avgOuts.toFixed( 2 );
    document.getElementById( "draw3outs" ).innerHTML =
        avgOuts.toFixed( 2 );

    var prob1 = avgOuts / cardsRemaining;
    var prob2 = prob1 + ( 1 - prob1 ) * ( avgOuts / ( cardsRemaining - 1 ) );
    var prob3 = prob2 + ( 1 - prob2 ) * ( avgOuts / ( cardsRemaining - 2 ) );

    document.getElementById( "draw1prob" ).innerHTML = prob1.toFixed( 6 );
    document.getElementById( "draw2prob" ).innerHTML = prob2.toFixed( 6 );
    document.getElementById( "draw3prob" ).innerHTML = prob3.toFixed( 6 );

    if ( prob1 != 0 ) {
        var odds1 = ( 1 - prob1 ) / prob1;
        var odds2 = ( 1 - prob2 ) / prob2;
        var odds3 = ( 1 - prob3 ) / prob3;

        if ( odds1 >= 1 ) {
            odds1 = odds1.toFixed( 2 ) + " to 1";
        } else {
            odds1 = "1 to " + ( 1 / odds1 ).toFixed( 2 );
        }

        if ( odds2 >= 1 ) {
            odds2 = odds2.toFixed( 2 ) + " to 1";
        } else {
            odds2 = "1 to " + ( 1 / odds2 ).toFixed( 2 );
        }

        if ( odds3 >= 1 ) {
            odds3 = odds3.toFixed( 2 ) + " to 1";
        } else {
            odds3 = "1 to " + ( 1 / odds3 ).toFixed( 2 );
        }
        document.getElementById( "draw1odds" ).innerHTML = odds1;
        document.getElementById( "draw2odds" ).innerHTML = odds2;
        document.getElementById( "draw3odds" ).innerHTML = odds3;
    } else {
        document.getElementById( "draw1odds" ).innerHTML = "never";
        document.getElementById( "draw2odds" ).innerHTML = "never";
        document.getElementById( "draw3odds" ).innerHTML = "never";
    }
}

var cardsRemaining;

function updateStubSize() {
    cardsRemaining = 52 - cardsInHand.length - deadCards.length - 4;
    var cell = document.getElementById( "draw1stub" );
    cell.innerHTML = cardsRemaining;
    cell = document.getElementById( "draw2stub" );
    cell.innerHTML = cardsRemaining;
    cell = document.getElementById( "draw3stub" );
    cell.innerHTML = cardsRemaining;
}

function update() {
    showCardsInHand();
    showDeadCards();
    updateHandWeights();
    updateStubSize();
    if ( cardsInHand.length == 4 && totalWeight > 0 ) {
        updateOuts();
    } else {
        clearOuts();
    }
}

window.onload = function() {
    initHandSelection();
    initDeadCardSelection();
    initOpponentHandSelection();
}


