// SkatTrainer
//
// (c) 2009 Michael Buro
// Licensed under GPLv3

window.onload = init;

VERSION="1.0.9";
cardNum = 32;
cardIndex = 0; // index of next card to show
leftNum = 4;   // number of unknown cards left
currentCard = 0;
STATE_DEAL=0;
STATE_PLAY=1;
STATE_TEST=2;
STATE_RESULT=3;
STATE_INIT=4;
state=STATE_INIT;
unknownNum = 32;
removed=0;
trumpSuit=0;
deck=new Array();
myCards=new Array();
myCardNum = 0;
testCards=new Array();
testCardNum = 0;
testCardPosX = new Array();
testCardPosY = new Array();
selected=new Array(); // 1 -> testcard selected
SEL_OK = 2;
SEL_WRONG=3;
SEL_LEFT=4;
cardNames=new Array();
cardImages = new Array();
newCardImages = new Array();
suitImages = new Array();
newSuitImages = new Array();
suitCode=new Array("1","2","3","0"); // card suit prefixes
suitNames=new Array("diamonds", "hearts", "spades", "clubs");
rankCode=new Array(6,7,8,9,10,11,12,0); // 7,8,9,T,J,Q,K,A
sortRank=new Array(0,1,2,5,7,3,4,6); // card sort ranking (10 high)
bgColor="grey";
selColor="#00f0f0";
WIDTH=800;
HEIGHT=400;
XMAXNUM=8;
TESTCARDNUMMAX=2*XMAXNUM;
csW = 92;
csH = 143; // factor 1.55
cbW = csW*1.65;
cbH = csH*1.65;
JACK = 4;
ACE = 7;
TEN = 3;
imageSet=new Array(); // for load
loadActive = 0; // != 0: cards are being loaded

// show help page
function about()
{
  window.open("about.html");
}


// load new card deck
function cardChange(v)
{
  if      (v == "English/4") initCards("E4");
  else if (v == "English/2") initCards("E2");
  if      (v == "German/4")  initCards("G4");
  else if (v == "German/2")  initCards("G2");
  else                       initCards("GG");  
}

// callback function
// called when loading failed
function error(index)
{
  alert("error " + index);
}

// callback function
// called when image has been loaded
function loaded(index)
{
  if (!loadActive) return;
  
  //  alert("loaded: " + index);
  var i, c;
  imageSet[index] = 1;
  c=0;
  for (i=0; i < 36; i++) c += imageSet[i];

  if (c == 36) {

    // all cards loaded
    // copy references
    // bug: sometimes no card visible
    loadActive = 0;
    for (i=0; i < 36; i++) imageSet[i] = 0;

    for (i=0; i < 32; i++) {
      cardImages[i] = newCardImages[i];
    }

    for (i=0; i < 4; i++) {
      suitImages[i] = newSuitImages[i];
    }

    //alert("done");
    draw();
  }
}


function loadImage(img, par, url)
{
  img.onload  = function() { loaded(par); }
  img.onerror = function() { error(par); }
  img.src = url;
}


function initCards(cardSet)
{
  var i, s, x;

  if (loadActive != 0) return;
  loadActive = 1;

  imageSet = new Array();
  for (i=0; i < 36; i++) imageSet[i] = 0;

  i = 0;
  for (s=0; s < 4; s++){
    for (x=0; x < 8; x++){
      cardNames[i]=suitCode[s]+"_" + rankCode[x];
      var image = new Image();
      newCardImages[i] = image;
      loadImage(image, i, "./cards/" + cardSet + "/" + cardNames[i] + ".gif");
      i++;
    }

    var image = new Image();
    newSuitImages[s] = image;
    loadImage(image, s+32, "./cards/" + cardSet + "/" + suitNames[s] + ".gif");
  }
}


function init()
{
  loadActive = 0;
  document.getElementById('canvas').addEventListener('mousedown', mouseDown, false);
  initCards("E4");
  state=STATE_INIT; 
  document.getElementById("button_next").innerHTML = "&nbsp;&nbsp;New&nbsp;&nbsp;";
  document.getElementById("text_result").innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;";  
  draw();
}


// mouse click: (de)select card
function mouseDown(e)
{
  var posx = 0;
  var posy = 0;

  if (e.pageX || e.pageY) {

    posx = e.pageX;
    posy = e.pageY;

  } else if (e.clientX || e.clientY) {

    posx = e.clientX + document.body.scrollLeft
      + document.documentElement.scrollLeft;
    posy = e.clientY + document.body.scrollTop
      + document.documentElement.scrollTop;
  }

  // posx and posy contain the mouse position relative to the document

  if (state == STATE_TEST) {

    // (de)select card, check front to back
    sel = 0;
    for (i=testCardNum-1; i >= 0; i--) {
      if (posx > testCardPosX[i] - csW/2 && posx < testCardPosX[i] + csW/2 &&
          posy > testCardPosY[i] - csH/2 && posy < testCardPosY[i] + csH/2) {
        selected[i] = 1-selected[i];
        draw();
        sel = 1;
        break;
      }
    }

    if (sel == 0) step();

  } else {
    step();
  }
}

// return speed 0,1,2,3    
function getSpeed()
{
  var w = document.getElementById('sel_speed').selectedIndex;
  var selected_speed = document.getElementById('sel_speed').options[w].text;
  if (selected_speed == "1 sec") 
    return 1;
  else if (selected_speed == "2 sec")
    return 2;
  else if (selected_speed == "3 sec")
    return 3;
  else
    return 0; // click
}


// start new game
function newGame()
{
  state = STATE_DEAL;
  document.getElementById("button_next").innerHTML = "&nbsp;&nbsp;Next&nbsp;&nbsp;";
  document.getElementById("text_result").innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;";  
  step();
}


// new/next/result button was clicked
function step()
{
  if (state == STATE_DEAL) {

    do {
      createDeck();
    } while (unknownNum < leftNum+2);
    
    cardIndex = cardNum-1;

    draw();
    
    state = STATE_PLAY;
    s = getSpeed();
    if (s == 0) return; // click
    setTimeout('step()', 5000); // wait 5 seconds
    return;

  } else if (state == STATE_PLAY) {

    removed = 0;
    currentCard = -1;
    
    if (unknownNum > leftNum) {

      // remove current card
      currentCard = deck[cardIndex];
      unknownNum--;
      
      for (i=0; i < myCardNum; i++) {
        if (myCards[i] == currentCard) {
          for (j=i; j < myCardNum-1; j++) {
            myCards[j] = myCards[j+1];
          }
          removed = 1;
          myCardNum--;
          unknownNum++; // card known
          break;
        }
      }

      // watch out: comparing i >= myCardNum doesn't seem to work here?!
      
      cardIndex--;

      draw();

      s = getSpeed();
      if (s == 0) return; // click
      setTimeout('step()', 1000*s);
      return;
    }

    // create test set

    testCards = new Array();
    tci = 0;
    avoid=0;
    for (i=0; i < myCardNum; i++) {
      avoid |= (1 << myCards[i]);
    }

    testCardNum = TESTCARDNUMMAX;
    if (cardNum - myCardNum < TESTCARDNUMMAX)
      testCardNum = cardNum - myCardNum;

    // first select all remaining unknown cards
    for (i=cardIndex; i >= 0; i--) {
      card = deck[i];
      bit = 1 << card;
      if ((avoid & bit) == 0) {
        avoid |= bit;
        selected[tci] = 0;
        testCards[tci] = card;
        tci++;
      }
    }

    // then randomize rest
    for (; tci < testCardNum; tci++) {
      do {
        r = deck[Math.floor(Math.random() * cardNum)];
      } while ((avoid & (1 << r)) != 0);
      
      testCards[tci] = r;
      selected[tci] = 0;
      avoid |= (1 << r);
    }

    testCards.sort(cardComp);
    
    state = STATE_TEST;
    document.getElementById("button_next").innerHTML = "&nbsp;Result&nbsp;";
    draw();
    return;

  } else if (state == STATE_TEST) {

    // compute result

    //    s = "ci=" + cardIndex + " leftNum=" + leftNum;
    //for (j=0; j < cardIndex; j++) {
    //  s += " " + deck[j];
    //}
    //alert(s);
    
    for (i=0; i < testCardNum; i++) {

      correct = 0;
      for (j=0; j <= cardIndex; j++) {
        if (testCards[i] == deck[j]) {
          correct = 1; break;
        }
      }

      if (selected[i] > 0) {
        if (correct) {
          selected[i] = SEL_OK;
        } else {
          selected[i] = SEL_WRONG;
        }
      } else {
        if (correct) {
          selected[i] = SEL_LEFT;
        }
      }
    }
    
    state = STATE_RESULT;
    document.getElementById("button_next").innerHTML = "&nbsp;&nbsp;New&nbsp;&nbsp;";
    draw();
  } else if (state == STATE_RESULT || state == STATE_INIT) {
    newGame();
  }
}


function drawCard(card, ctx, x, y, w, h)
{
  ctx.drawImage(cardImages[card], x-w/2, y-h/2, w, h);
}


// draw entire canvas
function draw()
{
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.save();
  ctx.fillStyle = "lightgrey";
  ctx.fillRect(0,0,WIDTH, HEIGHT);

  if (state == STATE_INIT) {

    // write welcome message
    ctx.font = "30px Times New Roman";
    ctx.fillStyle = "Black";
    y = 80;
    x = 50;
    ctx.font = "35px Times New Roman";    
    ctx.fillText("Welcome to SkatTrainer!", x+170, y);

    ctx.font = "30px Times New Roman";    
    ctx.fillText("Select cards to memorize, speed, and card deck.", x+30, y+80);

    ctx.fillText("Then click \"New\" or click on the canvas to begin.", x+30, y+160);

    ctx.fillText("Click \"Help\" for more information.", x+130, y+240);    

    
    ctx.font = "20px Times New Roman";
    ctx.fillText("[On N900, tap the lower right corner twice to see the buttons]", x-20, y+300);
    ctx.fillText("v" + VERSION, x+660, y+300);
    
    
  } else {

    // draw suit sybol
    ctx.drawImage(suitImages[trumpSuit], 10, HEIGHT-100, 80, 80);

    if (state != STATE_TEST && state != STATE_DEAL && state != STATE_RESULT && unknownNum >= leftNum) {
    
      // draw current card
      drawCard(currentCard, ctx, WIDTH/2+cbW/2, cbH/2, cbW, cbH);
    }

    ok = 0;
    left = 0;
    wrong = 0;
    
    if (state == STATE_TEST || state == STATE_RESULT) {

      // draw test cards in two rows and memorize position for mouse events
      testCardPosX = new Array();
      testCardPosY = new Array();      
      
      XMAXNUM = 8;
      for (i=0; i < testCardNum; i++) {
        y = Math.floor(i / XMAXNUM);
        x = i % XMAXNUM;

        if (y == 0) {
          xnum = testCardNum;
          if (xnum >= XMAXNUM) xnum = XMAXNUM;
        } else {
          xnum = testCardNum - XMAXNUM;
        }

        posX = WIDTH/2-(xnum-1)*(6+csW+1)/2+x*(6+csW+1);
        posY = 4 + (csH/2 + y*(csH/2+4));

        drawCard(testCards[i], ctx, posX, posY, csW, csH);
        ctx.lineWidth = 6;
        ctx.strokeStyle = "black";   // default
        if (selected[i] == 1) {
          ctx.strokeStyle = "#0080FF"; // selected
        } else if (selected[i] == SEL_OK) {
          ctx.strokeStyle = "green";   // OK
          ok++;
        } else if (selected[i] == SEL_WRONG) {
          ctx.strokeStyle = "red";     // wrong
          wrong++;
        } else if (selected[i] == SEL_LEFT) {
          ctx.strokeStyle = "cyan";  // left
          left++;
        }

        if (selected[i] != 0)
          ctx.strokeRect(posX-csW/2, posY-csH/2, csW, csH);
        testCardPosX[i] = posX;
        testCardPosY[i] = posY;
      }
    }

    // draw my cards
    dxf = cbW/2.3;
    ox = (myCardNum-1)*dxf/2
    for (i=0; i < myCardNum; i++) {
      drawCard(myCards[i], ctx,
               WIDTH/2-ox+i*dxf+cbW/2, HEIGHT-cbH/7, cbW, cbH);
    }

    if (state == STATE_RESULT) {
      
      //ctx.fillStyle = "black";
      //rx = 200; ry = HEIGHT-50;
      //ctx.fillRect(200,HEIGHT-50,400,50);
      //ctx.lineWidth = 1.75;
      //ctx.fillStyle = "white";
      //ctx.font = "20pt Arial";
      //ctx.fillText(ok + " of " + (ok+left) + " correct", rx, ry+20);

      if (left == 0) {
        s = "<font color=\"green\">";
      } else {
        s = "<font color=\"red\">";
      }
      
      s += "&nbsp;&nbsp;" + ok + " of " + (ok+left) + " correct</font> ; ";
      diff = Math.abs(left - wrong);
      if (left > wrong) {
        s += "<font color=\"red\">" + diff + " card";
        if (diff > 1) s += "s";
        s += " under</font>";
      } else if (left < wrong) {
        s += "<font color=\"red\">" + diff + " card";
        if (diff > 1) s += "s";
        s += " over</font>";
      } else {
        s += "<font color=\"green\">number correct</font>";
      }
      document.getElementById("text_result").innerHTML = s;

      // alert("leftNum=" + leftNum + " unknown=" + unknownNum);
      
    } else if (removed && state == STATE_PLAY) {
      // draw arrow when player card was played
      drawArrow(ctx, WIDTH/2+cbW/2, HEIGHT/1.8, "blue", 180, 1);
    }
  }
  
  ctx.restore();

  // display number of cards left
  //l = unknownNum;
  //if (l < 10) ltext = "<tt>&nbsp;" + l + "</tt>";
  //else        ltext = "<tt>" + l + "</tt>";
  //document.getElementById("lab_left").innerHTML = ltext;
}


function drawArrow(ctx,x,y,col,angle,scale)
{
  ctx.save();
  ctx.lineWidth = 1;
  ctx.fillStyle = col;
  ctx.translate(x,y);
  ctx.scale(scale,scale);
  ctx.rotate(angle*Math.PI/180);
  ctx.beginPath();
  ctx.moveTo(0,30);
  ctx.lineTo(20,0);
  ctx.lineTo(10,0);
  ctx.lineTo(10,-30);
  ctx.lineTo(-10,-30);
  ctx.lineTo(-10,0);
  ctx.lineTo(-20,0);
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}

// create and shuffle deck
// also create my hand
function createDeck()
{
  deck = new Array();

  var w = document.getElementById('sel_mode').selectedIndex;
  var selected_mode = document.getElementById('sel_mode').options[w].text;

  trumpSuit = Math.floor(Math.random() * 4);

  if (selected_mode == "Trumps") {

    // only trumps
    i = 0;
    for (j=0; j < 8; j++)
      deck[i++] = trumpSuit*8 + j;

    // add other jacks    
    for (j=0; j < 4; j++) {
      if (j != trumpSuit)
        deck[i++] = j*8 + JACK; 
    }

    cardNum = 11;
    leftNum = Math.floor(Math.random()*3)+2; // 2,3,4
    
  } else if (selected_mode == "T.+High") {

    // trumps and high cards
    i = 0;
    for (j=0; j < 8; j++)
      deck[i++] = trumpSuit*8 + j;

    // add other jacks and high cards
    for (j=0; j < 4; j++) {
      if (j != trumpSuit) {
        deck[i++] = j*8 + JACK; // jack
        deck[i++] = j*8 + ACE; // ace
        deck[i++] = j*8 + TEN; // ten        
      }
    }

    cardNum = 17;
    leftNum = Math.floor(Math.random()*3)+3; // 3,4,5
    
  } else {

    // full deck
    for (i=0; i < 32; i++) deck[i] = i;
    cardNum = 32;
    leftNum = Math.floor(Math.random()*3)+4; // 4,5,6
  }

  // shuffle

  for (i=cardNum-1; i > 0; i--) {
    r = Math.floor(Math.random()*(i+1));
    t = deck[r]; deck[r] = deck[i]; deck[i] = t;
  }

  myCards = new Array();
  myCardNum = 0;

  if (cardNum < 32) {

    // pick cards with prob 1/3, at most 10
    for (i=0; i < cardNum; i++) {
      r = Math.floor(Math.random() * 3);
      if (r != 0) continue;
      myCards[myCardNum++] = deck[i];
      if (myCardNum >= 10) break;
    }
    
  } else {

    // pick 10 cards randomly

    tmpDeck = new Array();
    for (i=0; i < 32; i++) {
      tmpDeck[i] = deck[i];
    }

    n = 32;
    for (i=0; i < 10; i++) {
      r = Math.floor(Math.random()*n);
      myCards[i] = tmpDeck[r];
      tmpDeck[r] = tmpDeck[n-1];
      n--;
    }

    myCardNum = 10;
  }

  myCards.sort(cardComp);
  unknownNum = cardNum - myCardNum;
}


// jacks,trumps,other
// 10 high
function cardComp(a, b)
{
  var sa = Math.floor(a/8);
  var ra = Math.round(a - sa*8);
  var sb = Math.floor(b/8);
  var rb = Math.round(b - sb*8);
  var ta = (sa == trumpSuit) || (ra == JACK);
  var tb = (sb == trumpSuit) || (rb == JACK);

  var va = sa*8+sortRank[ra];
  if (ta) va += 100;
  if (ra == JACK) va += 200;

  var vb = sb*8+sortRank[rb];
  if (tb) vb += 100;
  if (rb == JACK) vb += 200;

  return vb - va;
}
