      // 4x4, 4x5 == 76px
      // 5x5, 5x6 == 57px

      function Level() {
         this.revealedTiles = new Array();
         this.numTileTurns = 0; // count of how many times tiles are turned
         this.numMatchedTiles = 0; // how many tiles have been matched
         this.uniqueTiles = false;
         this.nextLevel = null;
         this.prevLevel = null;
      }
      
      Level.prototype.init = function(_tilePic, _revealMax, _totalTiles, _uniqueTiles, _levelNum, _levelName, _levelDesc) {
         this.tilePic = _tilePic;
         this.revealMax = _revealMax;
         this.totalTiles = _totalTiles;
         this.uniqueTiles = _uniqueTiles;
         this.levelName = _levelName;
         this.levelDesc = _levelDesc;
         this.levelNum = _levelNum; 
         this.tileWidth = (this.totalTiles > 20)? 57 : 76; // 2 sizes
         this.numMatches = Math.floor(this.totalTiles/this.revealMax);
         this.numTiles = Math.ceil(this.totalTiles/this.revealMax);
      }
     
      Level.prototype.setNextLevel = function(_next) {
         this.nextLevel = _next;
      }
      Level.prototype.setPreviousLevel = function(_prev) {
         this.prevLevel = _prev;
      }
      
      Level.prototype.reveal = function(event) {
        if (this.revealedTiles.length < this.revealMax) {
           this.numTileTurns++; // increment 
           var tileSize = (this.tileWidth == 76) ? "large" : "small";
           var el = event.currentTarget;
           el.removeEventListener("click",revealTiles,false);
           this.revealedTiles.push(el);
           el.className = "tile flipped " + tileSize;
           if (this.revealedTiles.length > 1) {
              this.checkMatch();
           }
        }
      }

      Level.prototype.checkMatch = function() {
         var match = true;
         var lastid = null;
         var board = document.getElementById("gameboard");
         board.addEventListener("click",doNothing,true);
         for (var i = 0; i < this.revealedTiles.length && match; i++) {
            if (lastid == null) {
               lastid = this.revealedTiles[i].getAttributeNS("private","uid");
            }
            else {
               match = (lastid == this.revealedTiles[i].getAttributeNS("private","uid"));
               lastid = this.revealedTiles[i].getAttributeNS("private","uid");
            }
         }
         if (match && this.revealedTiles.length == this.revealMax) {
            for (var i = 0; i < this.revealMax; i++) {
              var t = this.revealedTiles.pop();
              t.removeEventListener("click", revealTiles, false);
              this.numMatchedTiles++; // increment number of revealed, matched tiles
            }
           if (this.totalTiles - this.numMatchedTiles < this.revealMax) {
              flipAll(true);
              setTimeout(showScoreboard, 2000);
           }
           board.removeEventListener("click",doNothing,true);
         }
         else if (!match) {
            this.doNoMatch();
         }
         else {
           board.removeEventListener("click",doNothing,true);
         }
      }
      
      Level.prototype.doNoMatch = function() {
        setTimeout(restoreTiles, 1000);
      }
 
      Level.prototype.randomize = function() {
         var multiplier = Math.ceil(this.totalTiles / this.numTiles);
         var randomTiles = [];
         for (var i=0; i < multiplier; i++) {
            for (var j=0; j < this.numMatches; j++) {
               var tileVersion = (this.uniqueTiles) ? i : 0;
               randomTiles.push([j,i]);
            }
         }
         for (var i=randomTiles.length; i < this.totalTiles; i++) {
            randomTiles.push([this.numTiles - 1, 0]);
         }
         for (var i=0; i < randomTiles.length; i++) {
            var idx = Math.floor(Math.random() * (randomTiles.length - 1));
            var tmp = randomTiles[idx];
            randomTiles[idx] = randomTiles[i];
            randomTiles[i] = tmp;
         }
         return randomTiles;
      }

      Level.prototype.layoutTiles = function() {
        var tiles = this.randomize();
        var board = document.getElementById("gameboardfront");
        
        while (board.hasChildNodes()) {
           board.removeChild(board.firstChild);
        }

        var numPerRow = (this.tileWidth == 76) ? 4 : 5;
        var tileSize = (this.tileWidth == 76) ? "large" : "small";
        for (var i=0; i < this.totalTiles; i++) {
           var tile = document.createElement("div");
           tile.setAttribute("id", "tile"+i);
           tile.setAttribute("class", "tile normal " + tileSize);
           tile.setAttributeNS("private","uid", tiles[i][0]);
           var left = 2 + (i % numPerRow) * (this.tileWidth + 4);
           var top = 2 + Math.floor(i / numPerRow) * (this.tileWidth + 4);
           tile.setAttribute("style",  "left: " + left + "px; top: " + top + "px;");
           tile.addEventListener("click", revealTiles, false);
           var face = document.createElement("div");
           face.setAttribute("class", "back face " + tileSize);
           face.style.setProperty("background", "url(" + this.tilePic + ")   -" + ((tiles[i][0] + 1)* this.tileWidth) + "px " + (tiles[i][1] * this.tileWidth) + "px","");
           tile.appendChild(face);

           face = document.createElement("div");
           face.setAttribute("class", "front face " + tileSize);
           face.style.setProperty("background","url(" + this.tilePic + ")   0px 0px","");
           tile.appendChild(face);
           board.appendChild(tile);
        }

        this.numTileTurns = 0; // reset
        this.numMatchedTiles = 0; // reset
        this.revealedTiles = new Array();
      }
      
      Level.prototype.restore = function() {
         var tileSize = (this.tileWidth == 76) ? "large" : "small";
         for (var i = 0; i < this.revealMax; i++) {
            var t = this.revealedTiles.pop();
            if (t) {
              t.className = "tile normal " + tileSize;
              t.addEventListener("click", revealTiles, false);
            }
         }
         var board = document.getElementById("gameboard");
         board.removeEventListener("click",doNothing,true);
      }
      
      Level.prototype.setLevelName = function(_name) {
         this.levelName = _name;
      }
      Level.prototype.setLevelDesc = function(_desc) {
         this.levelDesc = _desc;
      }

      function BombLevel() {
      }
      BombLevel.prototype = new Level();

      BombLevel.prototype.doNoMatch = function() {
        var bFoundBomb = false;
        for (i=0; i < this.revealedTiles.length && !bFoundBomb; i++) {
            if (this.numTiles - 1 == this.revealedTiles[i].getAttributeNS("private","uid")) {
                bFoundBomb = true;
            }
        }
        if (bFoundBomb) {
            this.numMatchedTiles = 0;
            setTimeout(coverAll,950);
        }
        setTimeout(restoreTiles,1000);
      }

      function SwapLevel() {
         this.tilesToSwap = [];
      }
      SwapLevel.prototype = new Level();
      
      SwapLevel.prototype.doNoMatch = function() {
        this.tilesToSwap = [];
        for (i=0; i < this.revealedTiles.length; i++) {
          this.tilesToSwap.push(this.revealedTiles[i]);
        }
        setTimeout(restoreTiles, 1000);
        setTimeout(swapTiles, 1005);
      }
      
      function swapTiles() {
        if (currentLevel.tilesToSwap) {
            var tilesToSwap = currentLevel.tilesToSwap;
            var laststyle = null;
            for (i=0; i < tilesToSwap.length; i++) {
                if (laststyle == null) {
                    laststyle = tilesToSwap[i].getAttribute("style");
                }
                else {
                    var tmpstyle = laststyle;
                    laststyle = tilesToSwap[i].getAttribute("style");
                    tilesToSwap[i].setAttribute("style",tmpstyle);
                }
            }
            tilesToSwap[0].setAttribute("style",laststyle);
        }
      }         
      
      function BombSwapLevel() {
      }
      BombSwapLevel.prototype = new SwapLevel();

      BombSwapLevel.prototype.doNoMatch = function() {
        var bFoundBomb = false;
        for (i=0; i < this.revealedTiles.length && !bFoundBomb; i++) {
            if (this.numTiles - 1 == this.revealedTiles[i].getAttributeNS("private","uid")) {
                bFoundBomb = true;
            }
        }
        this.tilesToSwap = [];
        for (i=0; i < this.revealedTiles.length; i++) {
          this.tilesToSwap.push(this.revealedTiles[i]);
        }
        if (bFoundBomb) {
            this.numMatchedTiles = 0;
            setTimeout(coverAll,990);
        }
        setTimeout(swapTiles, 1005);
        setTimeout(restoreTiles,1000);
      }


      var currentLevel = null;
      function loadLevel(level) {
         currentLevel = level;
         var ln = document.getElementById("levelname");
         while (ln && ln.hasChildNodes()) {
            ln.removeChild(ln.firstChild);
         }
         var t = document.createTextNode(currentLevel.levelName);
         ln.appendChild(t);

         ln = document.getElementById("levelnumber");
         while (ln && ln.hasChildNodes()) {
            ln.removeChild(ln.firstChild);
         }
         t = document.createTextNode("Level " + currentLevel.levelNum);
         ln.appendChild(t);
         
         var ld = document.getElementById("leveldesc");
         while (ld && ld.hasChildNodes()) {
            ld.removeChild(ld.firstChild);
         }
         t = document.createTextNode(currentLevel.levelDesc);
         ld.appendChild(t);
      }
      function loadNextLevel() {
         if (currentLevel.nextLevel) {
            loadLevel(currentLevel.nextLevel);
         }
      }
      function loadPreviousLevel() {
         if (currentLevel.prevLevel) {
            loadLevel(currentLevel.prevLevel);
         }
      }
      function revealTiles(event) {
         currentLevel.reveal(event);
      }
      function restoreTiles() {
        currentLevel.restore()
      }
      function doNothing(event) {
        event.stopPropagation();
      }

    // create the levels   
	var level_easy = new Level();
    level_easy.init("level_easy.png", 2, 20, false, 1, "Easy Peasy", "Find matching pairs of tiles. Simple enough, right?");
    var level_bomb = new BombLevel();
    level_bomb.init("level_bomb.png", 2, 20, false, 2, "Da Bomb", "Uncover a bomb and all the tiles will be reset. Match the bombs to 'defuse' them.");
    var level_swapmeet = new SwapLevel();
    level_swapmeet.init("level_swapmeet.png", 2, 20, false, 3, "Swap Meet", "Mismatched tiles switch places in this level. Can you keep track of where things are?");
    var level_threeofakind = new BombLevel();
    level_threeofakind.init("level_threeofakind.png", 3, 20, true, 4, "Three of a Kind", "Match sets of 3 tiles in this level. But watch out for the jokers: uncovering one will reset all the tiles.");
    var level_rorschach = new Level();
    level_rorschach.init("level_rorschach.png", 2, 20, true, 5, "Rorschach Test", "Match pairs of inverted inkblots. It won't tell you anything about your personality but it might drive you crazy.");
    var level_ladybugs = new BombLevel();
    level_ladybugs.init("level_ladybugs.png", 3, 30, true, 6, "Seeing Spots", "This level will drive you buggy! Match sets of 3 ladybugs but watch out for the spiders: they'll set you back. ");
    
    var level_ornaments = new SwapLevel();
    level_ornaments.init("level_ornaments.png", 2, 20, false, 7, "Deck the Halls", "Decorate the tree with holiday ornaments. But watch out: mismatched ornaments will switch places. Can you keep track of where they are?");
    
    var level_treelights = new BombLevel();
    level_treelights.init("level_treelights.png", 3, 20, false, 8, "Trim the Tree", "Untangle the lights by matching three of a kind. But watch out for the dud lights. You'll lose all your hard work!");
    
    var level_candyhearts = new Level();
    level_candyhearts.init("level_candyhearts.png", 2, 20, false, 9, "Candy Hearts", "Straight up matching candy hearts. What could be sweeter?");
    
    // chain the levels together
    level_easy.setNextLevel(level_bomb);
    level_bomb.setPreviousLevel(level_easy);
    level_bomb.setNextLevel(level_swapmeet);
    level_swapmeet.setPreviousLevel(level_bomb);
    level_swapmeet.setNextLevel(level_threeofakind);
    level_threeofakind.setPreviousLevel(level_swapmeet);
    level_threeofakind.setNextLevel(level_easy);
    level_easy.setPreviousLevel(level_threeofakind);
    
    var levels = new Array();
    levels[level_candyhearts.levelName] = level_candyhearts;
    levels[level_easy.levelName] = level_easy;
    levels[level_bomb.levelName] = level_bomb;
    levels[level_swapmeet.levelName] = level_swapmeet;
    levels[level_threeofakind.levelName] = level_threeofakind;
    levels[level_rorschach.levelName] = level_rorschach;
    levels[level_ladybugs.levelName] = level_ladybugs;    
    //levels[level_ornaments.levelName] = level_ornaments;
    //levels[level_treelights.levelName] = level_treelights;
    
    // set the first level
    currentLevel = level_easy;
    
    function coverAll() {
       flipAll(false);
    }
    function uncoverAll() {
       flipAll(true);
    }

    function flipAll(bReveal) {
        var gbf = document.getElementById("gameboardfront");
        var tiles = gbf.getElementsByTagName("div");
        var tileSize = (currentLevel.tileWidth == 76) ? "large" : "small";
        for (i=0; i < tiles.length; i++) {
            var cc = tiles[i].getAttribute("class");
            if (cc.indexOf("tile normal") == 0 && bReveal) {
                tiles[i].setAttribute("class", "tile flipped " + tileSize);
                tiles[i].removeEventListener("click", revealTiles, false);
            }
            else if (cc.indexOf("tile flipped") == 0 && !bReveal) {
                tiles[i].setAttribute("class", "tile normal " + tileSize);
                tiles[i].addEventListener("click", revealTiles, false);
            }
        }
    }

    function showScoreboard() {
        var score = document.getElementById("score");
        score.innerHTML = currentLevel.numTileTurns;
        var scoreboard = document.getElementById("scoreboard");
        scoreboard.style.setProperty("display","block","");
        scoreboard.setAttribute("class", "visible");
    }
    function hideScoreboard() {
        var scoreboard = document.getElementById("scoreboard");
        scoreboard.setAttribute("class", "hidden");
        var selectElem = document.getElementById("levelSelect2");
        selectElem.selectedIndex = 0;
        setTimeout(function() {scoreboard.style.setProperty("display","none","");},500);
    }
    function showTitle() {
        var title = document.getElementById("titlescreen");
        title.style.setProperty("display","block","");
        title.setAttribute("class", "visible");
    }
    function hideTitle() {
        var title = document.getElementById("titlescreen");
        title.setAttribute("class", "hidden");
        setTimeout(function(){title.style.setProperty("display","none")},500);
    }
    function showLevelTitle() {
        var title = document.getElementById("leveltitle");
        title.style.setProperty("display","block","");
        title.setAttribute("class", "visible");
    }
    function hideLevelTitle() {
        var title = document.getElementById("leveltitle");
        title.setAttribute("class", "hidden");
        setTimeout(function(){title.style.setProperty("display","none","")},500);
    }
    function play() {
        currentLevel.layoutTiles();
    }

    function imageLoad() {
       var preload_image = new Image();
       for (var level in levels) {
          preload_image.src = levels[level].tilePic;
       }
    }
    // do it now
    imageLoad();

    function loadSelect(id) {
       var s = document.getElementById(id);
       if (s) {
          for (var level in levels) {
             var opt = document.createElement("option");
             opt.setAttribute("value", level);
             s.appendChild(opt);
             var text = document.createTextNode(/*levels[level].levelNum +". " +*/level);
             opt.appendChild(text);
          }
       }
    }
    
    function selectLevel(key) {
          if (key && key != "none") {
             var level = levels[key];
             loadLevel(level);
             setTimeout(hideScoreboard,250);
             setTimeout(showLevelTitle,800);
          }
    }