/**
 * Copyright (c) 2009, Benjamin Joffe
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

function solveHand(players, table, results)
{
    var n = players.length;
    var entire = []; // array of 7 cards for each player
    var player = null; // temporary entry of entire[i]
    
    // temporarys
    var temp = [];
    
    // loopers
    var i=0;
    var j=0;
    var k=0;
    
    // other temporary integers
    var count = 0;
    var h = 0;
    var p = 0;
    var q = 0;
    var r = 0;
    
    var special = []; // high-card or undefined (used for straights and straight flushes)
    var finalists = []; // index of players fighting for kickers
    var flushSuit = -1;
    
    var win = []; // true for 'win or tie' or undefined
    var tie = false;
    
    results.completed += 1;
    
    for (i=0; i<n; i++)
    {
        entire[i] = players[i].concat(table);
    }
    
    /***
        Look for STRAIGHT FLUSH
        Cache the results of the flushes
    ***/

    for (i=0; i<n; i++)
    {
        temp = [0,0,0,0]; // suits
        player = entire[i];
        
        for (j=0; j<7; j++)
        {
            
            temp[player[j][1]]++; // fill the suits
        }
        
        for (j=0; j<4; j++) 
        {
            if (temp[j]<5) // no found flush
            {
                continue;
            }
            
            temp.length = 0; // array of true/false indicating flush faces
            for (k=0; k<7; k++)
            {
                if (player[k][1]==j)
                {
                    temp[player[k][0]] = true;
                }
            }
            
            // cache the flush in case it's needed later
            finalists[finalists.length] = i;
            flushSuit = j;
            
            // count from the top to find the highest straight flush
            count = 1; // count top card in advance
            for (k=temp.length-2; k>=-1; k--) // go to -1 for possible A2345
            {
                if (temp[(k+13)%13]) // allow count of ace twice
                {
                    count++;
                    if (count==5) // straight-flush found
                    {
                        special[i] = k+4; // store highest card
                        break;
                    }
                }
                else
                {
                    count=0;
                }
            }
            break; // player can only have 1 flush suit
        }
    }
    
    if (special.length) // somebody won with straight flush
    {
	j = 3; // start with lowest possible straight flush
	for (i=0; i<special.length; i++)
	{
	    if (special[i]>=j)
	    {
		if (special[i]>j)
		{
		    j = special[i];
		    win.length = 0; // other players lose
		}
		win[win.length] = i;
	    }
	}
	if (win.length > 1) // tie
	{
	    for (i=win.length-1; i>=0; i--)
	    {
		results[win[i]].tie.total ++;
		results[win[i]].tie.straightflush ++;
	    }
	}
	else // outright win
	{
	    results[win[0]].win.total ++;
	    results[win[0]].win.straightflush ++;
	}
	return;
    }

    /***
        Build card map
    ***/

    var cardMap = [];
    for (i=0; i<n; i++)
    {
        cardMap[i]=[0,0,0,0,0,0,0,0,0,0,0,0,0];
        player = entire[i];
        for (j=0; j<7; j++)
        {
            cardMap[i][player[j][0]]++;
        }
        cardMap[i] = cardMap[i].join('');
    }

    /**
        Look for 4 OF A KIND
    **/

    j = 0; // best player's quad type+1
    for (i=0; i<n; i++)
    {
        k = cardMap[i].indexOf('4') + 1;
        if (k)
        {
            if (j == k)
            {
                // if two players have the same quad then everybody does
                // look for high card
                j = 0; // best player's high card+1
                for (i=0; i<n; i++)
                {
                    k = cardMap[i].search(/[^04][04]*$/)+1; // highest card not in quad
                    if (k >= j)
                    {
                        tie = (k ==j );
                        
                        if (!tie)
                        {
                            j = k;
                            win.length = 0;
                        }
                        win[i] = true;
                    }
                }
                if (tie)
                {
                    for (i=0;i<win.length;i++)
                    {
                        if (win[i])
                        {
                            results[i].tie.total ++;
                            results[i].tie.quad ++;
                        }
                    }
                    return;
                }
                else
                {
                    break; // one outright winner below
                }
            }
            if (k > j)
            {
                j = k;
                win.length = 0;
                win[i] = true;
            }
        }
    }
    if (j)
    {
        for (i=0; i<win.length; i++)
        {
            if (win[i])
            {
                results[i].win.total ++;
                results[i].win.quad ++;
            }
        }
        return;
    }

    /**
        Look for FULL HOUSE
    **/

    var j = -1; // best FH trip
    var k = -1; // best FH pair
    for (i=0; i<n; i++)
    {
        p = cardMap[i].lastIndexOf('3');
        if (p>=0 && p >= j)
        {
            if (p == (q = cardMap[i].indexOf('3')))
            {
                // pair could another 3 or a 2 of kind
                q = cardMap[i].lastIndexOf('2');
            }
            if (q>=0 && (p > j || q >=k)) // equal or better full house
            {
                tie = !(p > j || q > k);
                if (!tie) // is a better full house
                {
                    j = p;
                    k = q;
                    win.length = 0;
                }
                win[i] = true;
            }
            
        }
    }
    if (j >= 0) // record the results
    {
        if (tie)
        {
            for (i=0; i<players.length; i++)
            {
                if (win[i])
                {
                    results[i].tie.total ++;
                    results[i].tie.fullhouse ++;
                }
            }
            return;
        }
        
        for (i=0; i<players.length; i++)
        {
            if (win[i])
            {
                results[i].win.total ++;
                results[i].win.fullhouse ++;
                return;
            }
        }
        
    }

    /**
        Look for FLUSH
    **/

    if (flushSuit >= 0) // we have some flushes!
    {
        // remove non-suited cards since they can't kick
        for (i=0; i<finalists.length; i++)
        {
            temp = cardMap[finalists[i]].split('');
            
            for (j=0; j<table.length; j++)
            {
                if (table[j][1]!=flushSuit)
                {
                    temp[table[j][0]]--;
                }
            }
            player = players[finalists[i]];
            
            for (j=0; j<2; j++)
            {
                if (player[j][1] != flushSuit)
                {
                    temp[player[j][0]]--;
                }
            }
            
            cardMap[finalists[i]] = temp.join('');
        }
        count = 5; // kickers
        p = 0; // sentinel value for flush
    }
    else
    {

        /**
            Look for STRAIGHT
        **/
        
        for (i=0; i<n; i++)
        {
            temp.length = 0; // temp holds faces
            for (j=0; j<7; j++)
            {
                temp[entire[i][j][0]] = true
            }
            // count from the top to find the highest straight
            count = 1; // count top card in advance
            for (k=temp.length-2; k>=-1; k--) // go to -1 for possible A2345
            {
                if (temp[(k+13)%13]) // allow count of ace twice
                {
                    count++;
                    if (count==5) // straight found
                    {
                        
                        special[i] = k+4; // store highest card
                        break;
                    }
                }
                else
                {
                    count=0;
                }
            }
        }
        
        if (special.length) // somebody won with straight
        {
            j = 3; // start with lowest possible straight
            for (i=0; i<special.length; i++)
            {
                if (special[i]>=j)
                {
                    if (special[i]>j)
                    {
                        j = special[i];
                        win.length = 0; // other players lose
                    }
                    win[win.length] = i; // win or tie
                }
            }
            if (win.length > 1) // tie
            {
                for (i=win.length-1; i>=0; i--)
                {
                    results[win[i]].tie.total ++;
                    results[win[i]].tie.straight ++;
                }
            }
            else // outright win
            {
                results[win[0]].win.total ++;
                results[win[0]].win.straight ++;
            }
            return;
        }
        
        /**
            Look for THREE OF A KIND
        **/
        
        temp.length = 0; // player's highest pair
        k = -1; // best pair any player has
        for (i=n-1; i>=0; i--)
        {
            j = temp[i] = cardMap[i].lastIndexOf('3');
            if (j > k)
            {
                k = j;
            }
        }
        
        // players who have three of kind go to finalists
        if (k > -1)
        {
            for (i=temp.length; i>=0; i--)
            {
                if (temp[i]==k)
                {
                    finalists[finalists.length] = i;
                }
            }
            
            count = 2; // kickers
            p = 1; // sentinel
        }
        else
        {
            /**
                Look for 2 PAIR
            **/
            
            j = -1; // best pair
            k = -1; // 2nd best pair
            h = -1; // best kicker
            for (i=0; i<n; i++)
            {
                p = cardMap[i].lastIndexOf('2');
                if (p > -1 && p >= j)
                {
                    q = ('xx'+cardMap[i]).lastIndexOf('2',(p-1)+2)-2; // have to add the 'xx' to avoid in valid 2nd parameter
                    
                    if (q > -1 && (p > j || q >= k))
                    {
                        r = Math.max( // kicker
                                cardMap[i].lastIndexOf('1'),
                                ('xx'+cardMap[i]).lastIndexOf('2', (q-1)+2)-2 // could be "3 pair"
                            );
                            
                        if (p > j || q > k || r >= h)
                        {
                            if (p > j || q>k || r > h)
                            {
                                j = p;
                                k = q;
                                h = r;
                                win.length = 0;
                            }
                            win[win.length] = i;
                        }
                    }
                }
            }
            if (win.length)
            {
                if (win.length > 1) // tie
                {
                    for (i=win.length-1; i>=0; i--)
                    {
                        results[win[i]].tie.total ++;
                        results[win[i]].tie.twopair ++;
                    }
                }
                else // outright win
                {
                    results[win[0]].win.total ++;
                    results[win[0]].win.twopair ++;
                }
                return;
            }
            
            /**
                Look for PAIR
            **/
            
            temp.length = 0; // player's highest pair
            k = -1; // best pair any player has
            for (i=n-1; i>=0; i--)
            {
                j = temp[i] = cardMap[i].lastIndexOf('2');
                if (j > k)
                {
                    k = j;
                }
            }
            
            // players who have top pair (top pair may be no pair) go to finalists
            for (i=temp.length; i>=0; i--)
            {
                if (temp[i]==k)
                {
                    finalists[finalists.length] = i;
                }
            }
            count = k < 0 ? 5 : 3; // kickers
            p = k < 0 ? 3 : 2; // sentinel value for flush
        }
    }

    /**
        Look for HIGH CARD
        
        Used for flushes, trips, 1 pairs and nothings
        By here finalists must be an array of the index
        of each player still in the running.
    **/

     // check each face and who has it
    for (j=12; count>0 && finalists.length>1; j--) // count always reaches 0 before j does
    {
        for (i=finalists.length-1; i>=0; i--)
        {
            if (cardMap[finalists[i]][j]=='1') // player has a kicker
            {
                finalists.length = i+1; // players after in array didn't have this kicker, remove them
                count--;
                i--;
                for (; i>=0; i--)
                {
                    // now remove the plays before in the array that don't have this kicker
                    if (cardMap[finalists[i]][j]!='1')
                    {
                        finalists[i] = finalists[finalists.length-1];
                        finalists.length--;
                    }
                }
            }
        }
    }

    if (finalists.length > 1) // tie
    {
        for (i=0; i<finalists.length; i++)
        {
            results[finalists[i]].tie.total ++;
            results[finalists[i]].tie[['flush','trip','pair','high'][p]] ++;
        }
    }
    else
    {
        results[finalists[0]].win.total ++;
        results[finalists[0]].win[['flush','trip','pair','high'][p]] ++;
    }
    return;
}