var imgPath = '../images/mastermind/';
var colorsAll = new Array( "R", "U", "Y", "G", "B", "W", "P", "O", "C", "I" );
var colorsHash = new Array();
colorsHash['R'] = 'Red';
colorsHash['U'] = 'Blue';
colorsHash['Y'] = 'Yellow';
colorsHash['G'] = 'Green';
colorsHash['B'] = 'Black';
colorsHash['W'] = 'White';
colorsHash['P'] = 'Purple';
colorsHash['O'] = 'Orange';
colorsHash['C'] = 'Cyan';
colorsHash['I'] = 'Pink';
colorsHash['Red']    = 'R';
colorsHash['Blue']   = 'U';
colorsHash['Yellow'] = 'Y';
colorsHash['Green']  = 'G';
colorsHash['Black']  = 'B';
colorsHash['White']  = 'W';
colorsHash['Purple'] = 'P';
colorsHash['Orange'] = 'O';
colorsHash['Cyan']   = 'C';
colorsHash['Pink']   = 'I';

// declare as global
var guessesArray = new Array();

// classic values as defaults
var numColors = 6;
var codeSize  = 4;
var repeatsAllowed   = true;

var colorsUse = colorsAll.slice( 0, numColors );
var code = makeCode( codeSize, colorsUse );

var guessPerm = new Array();

var games = 0;
var turns = 0;
var totalTurns = 0;
var avgTurns = 0;

/*
Still needs extensive debugging in IE...
'Putting' the piece requires two clicks instead of one in IE.
activeRow isn't being incremented correctly.  Styles don't change.
'activeRow' class is being properly updated.
Or maybe it's being *partly* incremented; I can pick up played pieces in the completed row,
 but I can only put them down in the *next* row.
Hm - seems I can only pick up the second piece in row 1, or the third in row 2, etc.


Need a function to optimize guesses, using the future code scoreset partition min-max method.
[see http://www.delphiforfun.org/Programs/MasterMind.htm - Background & Techniques]
[May want to use this function to generate initial guesses too; see above.]

consider having the advanced guess-searching algorithm search not only those
guesses that are still possible codes, but ALL guesses, assessing each as to
how many possibilities would be left with each possible score.  This would be
slower, but would also probably improve its play.
Could I use only possible codes left when ennumerating codeset partitions?
Think so...that would be better too.
*/


/*******************************************************************************
  Game reset functions
*******************************************************************************/

function newGame() {
/*******************************************************************************
  Runs (onpageload and) when a 'New Game' button on page is clicked (or maybe
  'onChange' in the control panel elements?).
  [How about this: the form elements could have 'onChange' calls to this, but
   the function would detect if there is a game in progress, and only run if
   there is not.  Would also need the button, if this were implemented.]
*******************************************************************************/

    // detect if there is a game in progress...(how?)
    // activeRow>1?  only true if 'score my guess' has been pressed...

    // abort if numColors<codeSize AND repeatsAllowed=false
  if( !document.getElementById('repeatColors').checked && parseInt( document.getElementById('numColors').value )<parseInt( document.getElementById( 'codeSize').value ) ) {
        alert('Impossible to make a non-color-repeated code if there are fewer colors than code pegs.');
        return false;
    }

    // (re)set numColors, codeSize, repeatsAllowed according to form values
    var numColorsOld      = numColors;
    var codeSizeOld       = codeSize;
    var repeatsAllowedOld = repeatsAllowed;
    numColors      = parseInt( document.getElementById('numColors').value );
    codeSize       = parseInt( document.getElementById( 'codeSize').value );
    repeatsAllowed = document.getElementById('repeatColors').checked;
    colorsUse      = new Array();
    colorsUse      = colorsAll.slice( 0, numColors );

    // make a new code
    code = makeCode( codeSize, colorsUse );

    // draw a new board
    makeBoardTable( codeSize );

    // make a new unplayedPieceTray (if numColors has changed? any reason not to always do it?)
    if( numColors != numColorsOld ) {
        fillPieceTray();
        fillMovingPiecesDiv();
    }

    // reset the active row and turns
    activeRow = 1;
    turns = 0;

    // create a new guessesArray
    fillGuessesArray();

}

function fillGuessesArray() {

  //    alert( 'colorsUse='+colorsUse+'\nnumColors='+numColors+'\ncodeSize='+codeSize+'\nrepeats='+repeatsAllowed );

    // re-initialize guess arrays
    guessesArray = new Array();
    guessPerm = new Array();
    makeGuessPermutations( 1 );

//    alert( guessesArray.length );

    // print new set of guesses to page
    var GAdiv = document.getElementById('guessesArray');
    GAdiv.innerHTML
      = '\n          <h4>Hints</h4>'
      + "\n          There are "+guessesArray.length+" possible codes."
      + '\n          <br />';
    if( repeatsAllowed ) {
      GAdiv.innerHTML
        += '\n          <span style="position: relative; top: -12px;">Try starting with</span> <span style="display: vertical-align: middle;">' + makePegRowHTML( make2ColorGuess( codeSize, colorsUse ), colorsHash ) + '</span>'
        +  '\n          ';
    }
    else {
      GAdiv.innerHTML += "\n          Try whatever you like as a first guess.";
    }
}


function makeGuessPermutations( depth ) {
    for( var i=0; i<numColors; i++ ) {

        guessPerm[depth-1] = colorsUse[i];
        if( depth<codeSize ) {
            makeGuessPermutations( depth+1 );
        }
        else {
            for( var i=0; i<numColors; i++ ) {
                guessPerm[depth-1] = colorsUse[i];

                // check for repeated colors in the guess (set a flag if there are)
                var repeat = false;
                for( var g1 in guessPerm ) {
                    for( var g2 in guessPerm ) {
                        if( g1 != g2 && guessPerm[g1] == guessPerm[g2] ) {
                            repeat = true;
                        }
                    }
                }

                if( !repeatsAllowed && repeat ) {
                    continue;
                }
                else {
                    guessesArray.push( guessPerm.join('') );
                }
            }
            return;
        }
    }
}


/*******************************************************************************
  Functions for creating or altering control panel items
*******************************************************************************/

function makeColorsMenu() {
    var menuElmt = document.getElementById('numColors');
//    numColors = menuElmt.value;
    var html = '';
    var selected = '';
    var blurb = '';
    for( var n=3; n<=colorsAll.length; n++ ) {
        selected = ( n==numColors ) ? ' selected' : '';
        blurb = ( n==6 ) ? ' (classic)' : '';
        html += '\n      <option value="'+n+'"'+selected+'>'+n+blurb+'</option>';
    }
//    alert( html );
    menuElmt.innerHTML = html;
}


/*******************************************************************************
  Functions for making the game board 
*******************************************************************************/

function makeBoardTable( codeSize ) {
    var boardTableObj = document.getElementById("board");
    var numRows = 2*codeSize + 2; // should # of colors matter here?
    var tableHTML = '\n    <table id="boardTable" class="boardTable">'
        + '\n      <tbody>'
        + '\n        <tr id="codeRow" class="codeRow" style="visibility: hidden">'
        + '\n          <td></td>'
        + makePegRowHTML( code, colorsHash )
        + '\n        </tr>';
    for( var r=numRows; r>=1; r-- ) {
        activeRowAtts = ( r==1 ) ? ' class="activeRow" title="You can click on a played peg to remove it"' : '';
        rowHTML = '\n        <tr'+activeRowAtts+' align="center">\n          <td>'+scoreArrayHTML( codeSize, r )+'\n          </td>';
        for( p=1; p<=codeSize; p++ ) {
            rowHTML += '\n          <td id="r'+r+'p'+p+'" onMouseUp="javascript: putPiece(this)" ><img src="'+imgPath+'emptyCodePegHole.png"></td>';
        }
        rowHTML += '\n        </tr>';
        tableHTML += rowHTML;
    }
    tableHTML += '\n      </tbody>\n    </table>\n  ';
    boardTableObj.innerHTML = tableHTML;
}

function makePegRowHTML( pegs, colorsHash ) {
    var html = '';
    for( peg in pegs ) {
        html += '\n          <td><img src="'+imgPath+'piecePlayed' + colorsHash[pegs[peg]] + '.png"></td>';
    }
    return html;
}

function scoreArrayHTML( codeSize, row ) {
    var html = "\n            <table class=\"scoreArray\">";
    html += "\n              <tbody>";
    html += "\n                <tr>";
    var half = codeSize/2;
    for( c=1; c<=codeSize; c++ ) {
        html += '\n                  <td id="r'+row+'score'+c+'"><img src="'+imgPath+'emptyScorePegHole.png"></td>';
        if( c == half ) {   // *if* codeSize is even, divide table into 2 rows
            html += "\n                </tr>";
            html += "\n                <tr>";
        }
    }
    html += "\n                </tr>";
    html += "\n              </tbody>";
    html += "\n            </table>";
    return html;
}


/*******************************************************************************
  Functions for creating the unplayed piece tray and moving piece div,
  and for playing, moving, and removing pegs.
*******************************************************************************/

var activeRow = 1;
var guess = new Array( codeSize );

function fillPieceTray() {
    var unplayedPieceTrayElmt = document.getElementById('unplayedPieceTray');
    var newWidth
        = ( numColors<5 ) ? 135 : numColors*20;
    unplayedPieceTrayElmt.style.width = newWidth+'px';
    var html = '';
    for( var clr in colorsUse ) {
        html = html
            + '\n'
            + '    <div id="unplayedPiece'+colorsHash[colorsUse[clr]]+'" class="unplayedPieceDiv"\n'
            + '         onClick="javascript: document.onmousemove=follow;\n'
            + '                  document.getElementById(\'color\').value=\''+colorsHash[colorsUse[clr]]+'\';">\n'
            + '      <img src="'+imgPath+'pieceUnplayed'+colorsHash[colorsUse[clr]]+'.png">\n'
            + '    </div>';
    }
    html += '\n  ';
    unplayedPieceTrayElmt.innerHTML = html;
}

function fillMovingPiecesDiv() {
    var movingPiecesElmt = document.getElementById('movingPieces');
    var html;
    for( var clr in colorsUse ) {
        html = '\n'
            + '    <div id="movingPiece'+colorsHash[colorsUse[clr]]+'"\n'
            + '         class="movingPieceDiv"\n'
            + '         onMouseDown="javascript:\n'
            + '                      this.style.visibility = \'hidden\';\n'
          // want to try changing the movingPiece's position, or maybe use 'none' or something for visibility
            + '                      document.onmousemove = null;">\n'
            + '      <img src="'+imgPath+'pieceUnplayed'+colorsHash[colorsUse[clr]]+'.png">\n'
            + '    </div>';
        movingPiecesElmt.innerHTML += html;
    }
    movingPiecesElmt.innerHTML += '\n  ';
}

function putPiece( element ) {

    var pieceColor = document.getElementById('color').value;

    // abort unless mouse was carrying a piece, as indicated by 'color' element
//    if( !pieceColor ) alert("aborting 'putPiece' because pieceColor is null");
    if( !pieceColor ) return null;

    // abort unless 'element' is in the current active row
    var coords = element.id.split(/\D/);
    if( !coords[0] ) { coords.splice(0,1) } /* Firefox(!) split puts a null element at beginning of coords array */
    if( coords[0] != activeRow ) {
      alert("aborting 'putPiece' because clicked on non-active row"+"\nactive row: \n"+activeRow+"\nelement id: \n"+(element.id)+", from which I parse the row as "+coords[0]+"\ncoords array: "+coords);
        document.getElementById('color').value = '';
        return null;
    }

    guess[coords[1]-1] = colorsHash[pieceColor];

    element.innerHTML = '<img src="'+imgPath+'piecePlayed'+pieceColor+'.png" onClick="javascript: removePiece(this.parentNode);">';
    element.setAttribute("onmouseup", null);
    document.getElementById('color').value = '';
}

function removePiece( element ) {
    var coords = element.id.split(/\D/);
    if( coords[1] != activeRow ) {
        return null;
    }
    guess[coords[2]-1] = '';

    imgElmtSrc = element.firstChild.src
    var regexp = new RegExp(/piecePlayed(.+)\.png/);
    regexp.test( imgElmtSrc );
    document.getElementById('color').value = RegExp.lastParen;

    element.innerHTML = '<img src="'+imgPath+'emptyCodePegHole.png">';
    element.setAttribute('onmouseup', "javascript: putPiece(this);");

    document.onmousemove = follow;
}

/* Simple follow-the-mouse script */

var offX = -15;          // X offset from mouse position
var offY = -15;          // Y offset from mouse position

function follow( evt ) {
    if( !evt ) var evt = window.event;
    if( document.getElementById ) {
        // div that is to follow the mouse (must be position:absolute)
        var divName = 'movingPiece' + document.getElementById('color').value;
        var obj = document.getElementById( divName ).style;
        obj.visibility = 'visible';
        obj.left = ( parseInt( mouseX( evt ) ) + offX ) + 'px';
        obj.top  = ( parseInt( mouseY( evt ) ) + offY ) + 'px';
    }
}

function mouseX( evt ) {

    if( !evt ) { evt = window.event; }

    if( evt.pageX ) { return evt.pageX; }
    else if( evt.clientX ) {
        return evt.clientX
            + ( document.documentElement.scrollLeft
                ? document.documentElement.scrollLeft
                : document.body.scrollLeft );
    }
    else { return 0; }

}

function mouseY( evt ) {

    if( !evt ) { evt = window.event; }

    if( evt.pageY ) { return evt.pageY; }
    else if( evt.clientY ) {
        return evt.clientY
            + ( document.documentElement.scrollTop
                ? document.documentElement.scrollTop
                : document.body.scrollTop );
    }
    else { return 0; }

}


/*******************************************************************************
  Functions for game play - scoring, etc.
*******************************************************************************/

function makeCode( codeSize, colorsUse ) {
    var repeatsAllowed = true;
    if( document.getElementById('repeatColors') ) {
        repeatsAllowed = document.getElementById('repeatColors').checked;
    }
    var code = new Array( codeSize );

    for( i=0; i<codeSize; i++ ) {
        var color = colorsUse[ Math.floor( numColors*Math.random() ) ];
        var do_over = false;
        if( !repeatsAllowed ) {
            for( clr in code ) {
                if( code[clr] == color ) {
                    do_over = true;
                    i--;
                    break;
                }
            }
        }
        if( !do_over ) {
            code[i] = color;
        }
    }
    return code;
}

function doScore() {

    /* abort if guess array isn't full */
    if( guess.join('').length != codeSize ) {
        alert( 'Your guess is incomplete' );
        return false;
    }

    turns++;

    /* call scoreGuess to get score pegs */
    var scorePegs = scoreGuess( code, guess );

    /* get preferred scoring peg 'hit' color */
    var prefHitColor;
    if( document.getElementById('blackPegs').checked ) {
      prefHitColor = 'Black';
    }
    else if ( document.getElementById('redPegs').checked ) {
      prefHitColor = 'Red';
    }

    /* populate scoreArray table (ids like 'r#score#') */
    for( var sp=1; sp<=scorePegs[0]+scorePegs[1]; sp++ ) {
        spColor = ( sp<=scorePegs[0] ) ? prefHitColor : 'White';
        SAtd = document.getElementById( 'r'+activeRow+'score'+sp );
        SAtd.innerHTML = '<img src="'+imgPath+'scorePeg'+spColor+'.png">';
    }

    /* if guess is completely correct, or player has run out of turns, end game & reveal code */
    var activeRowObj = document.getElementById('r'+activeRow+'p1').parentNode;
    if( scorePegs[0] == codeSize || activeRow==(2*codeSize+2) ) {
        if( activeRow==(2*codeSize+2) ) { // then the player has run out of turns
            alert("You have run out of turns!\nI'll show you the code.");
        }
        document.getElementById('codeRow').style.visibility = 'visible';
        activeRowObj.setAttribute("class", null );
        activeRow = 0;            // kind of a hack - lets me just look at activeRow in RemovePiece (instead of having to actually check the class of the parentNode)
        // update stats
        games++;
        document.getElementById('statsGames').innerHTML = games;
        totalTurns += turns;
        avgTurns = Math.round(100*totalTurns/games)/100;
        document.getElementById('statsAvgTurns').innerHTML = avgTurns;
        return;
    }

    /* otherwise, game play continues... */

    // shift active row
    activeRowObj.setAttribute("class", null );
    activeRowObj.setAttribute("title", null );
    activeRow++;
    activeRowObj = document.getElementById('r'+activeRow+'p1').parentNode;
    activeRowObj.setAttribute("class", 'activeRow');
    activeRowObj.style.className = 'activeRow';

    /* Activate code to generate next guess */
    // this code will be activated differently, if I ever really make the game 2-way
    guessToTry = bestGuess( guessesArray, scorePegs );

    // print new set of guesses to page
    var GAdiv = document.getElementById('guessesArray');
    GAdiv.innerHTML
      = '\n          <h4>Hints</h4>'
      + '\n          Remaining possible combinations: ' + guessesArray.length
      + '\n          <br />'
      + '\n          Try <span style="vertical-align: middle;">' + makePegRowHTML( guessToTry, colorsHash ) + '</span>'
      + '\n          ';

    // clear guess array
    guess = new Array( codeSize );

}

function scoreGuess( code, guess ) {
    var exactMatches = 0;
    var colorMatches = 0;
    var alreadyMatchedInGuess = new Array( codeSize );
    var alreadyMatchedInCode  = new Array( codeSize );
    for( i=0; i<codeSize; i=i+1 ) {
        // assess all guess pegs for exact matches before looking for color matches
        if( guess[i] == code[i] ) {
            // increment white marks, record matched positions, and go to next code peg
            exactMatches++;
            alreadyMatchedInGuess[i] = 1;
            alreadyMatchedInCode[i]  = 1;
            continue;
        }
    }
    for( i=0; i<codeSize; i=i+1 ) { // guess pegs loop
        for( j=0; j<codeSize; j=j+1 ) { // code pegs loop
            // don't need to skip i=j case; they should NOT match because we have passed through that previous 'if'
            // *do* need to skip indices that have already been used in a previous color match
            if( alreadyMatchedInGuess[i] || alreadyMatchedInCode[j] ) { continue; }
            if( guess[i] == code[j] ) {
                // increment white marks, record matched positions, and go to next *guess* peg
                colorMatches++;
                alreadyMatchedInGuess[i] = 1;
                alreadyMatchedInCode[j]  = 1;
                break;
            }
        }
    }
    return new Array( exactMatches, colorMatches );
}

function bestGuess( guessesArray, lastScore ) {

    /* Prune the guessesArray - remove any 'guesses' that are inconsistent with lastScore */
    var lastScoreText = lastScore.join('');  // for comparison below
    // go through guessesArray backward, to avoid problems from re-indexing upon item deletion
    for( var ri=guessesArray.length-1; ri>=0; ri-- ) {
        var guessText = guessesArray[ri];
        var guessArray = guessText.split('');
        // get the score the current *real* guess would have gotten, if this guess (from the array) were the real code
        var guessScore = scoreGuess( guessArray, guess );
//        alert( ' lastScoreText ('+guess+')='+lastScoreText+'\nguessScoreText ('+guessArray+')='+guessScore.join('') );
        if( guessScore.join('') != lastScoreText ) {
            // remove this guess from guessesArray
            guessesArray.splice( ri, 1 );
        }
    }
    // at this point we have a cut-down list of possibilities - select one at random
    var bestGuessArray = guessesArray[ Math.floor( guessesArray.length*Math.random() ) ].split('');
//    if( level<3 ) { return bestGuessArray; }

    /* For each remaining guess, partition all the remaining guesses into 'scoresets'     */
    /* according to the score each would receive if the current guess were the true code. */
    /* Then, choose as the best next guess that guess which has the lowest maximum size   */
    /* of all its scoresets.  This improves on randomly choosing among the remaining      */
    /* possible guesses by narrowing the field maximally with each guess.                 */





    // return chosen next guess
    return bestGuessArray;
}

function make2ColorGuess( codeSize, colorsUse ) {
  // we should probably assume here that repeatsAllowed is 'true'

  var color1 = colorsUse[ Math.floor( numColors*Math.random() ) ];
  var color2 = colorsUse[ Math.floor( numColors*Math.random() ) ];
  while( color2 == color1 ) {
    color2 = colorsUse[ Math.floor( numColors*Math.random() ) ];
  }
  var guess = new Array( codeSize );
  for( var c=1; c<=codeSize; c++ ) {
    if( c <= codeSize/2 ) {
      guess[c] = color1;
    }
    else {
      guess[c] = color2;
    }
    alert('color for guess['+c+'] is '+guess[c]);
  }
  return FYshuffle( guess );
}

function FYshuffle(o) {
  for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
  return o;
};



// browser-specific warning message
document.onload = warnIE();

function warnIE() {
    if( navigator.appName=="Microsoft Internet Explorer" ) {
        alert("This game does not yet work on Internet Explorer!");
    }
}
