///////////////////////////////////////////////////////////////////////////////
//
//  gameboard.js
//
// 
// © 2007 Microsoft Corporation. All Rights Reserved.
//
// This file is licensed as part of the Silverlight 1.0 SDK, for details look 
// here: http://go.microsoft.com/fwlink/?LinkID=89144&clcid=0x409
//
///////////////////////////////////////////////////////////////////////////////

/*
 * Globals
 */
var oGameBoard;

function handleLoad() {
    var playerCount = 1;
    var address = window.location.href;
    var index = window.location.href.lastIndexOf("?players=");
    if (index != -1)
        playerCount = window.location.href.substr(index + 9);

    var agControl = document.getElementById("agControl1");
    oGameBoard = new Gameboard(agControl.content.findName("Root"), agControl, playerCount);

    updateScale();
}
    
function Gameboard(target, control, playerCount) {
    this.target = target;
    this.control = control;
    this.playerCount = playerCount;
    this.takingNames = false;
    this.okClicked = false;
    this.playerName  = "";
    
    // Find all the elements we're interested now, for efficiency
    this.gameboard = this.target.findName("Gameboard");
    this.suitsScoreDisplay = this.target.findName("SuitsScore");
    this.cowboyScoreDisplay = this.target.findName("CowboyScore");
    this.perFrameStoryboard = this.target.findName("PerFrameGenerator");
    this.introScreen = this.target.findName("Intro");
    this.root = this.target.findName("Root");
    this.introIntroAnimation = this.target.findName("IntroIntro");
    this.introOutroAnimation = this.target.findName("IntroOutro");
    this.continueIntroAnimation = this.target.findName("ContinueIntro");
    this.winnerIntroAnimation = this.target.findName("WinnerIntro");
    this.loserIntroAnimation = this.target.findName("LoserIntro");
    this.showNameAreaAnimation = this.target.findName("ShowNameArea");
    this.hideNameAreaAnimation = this.target.findName("HideNameArea");
    this.downloadAnimation     = this.target.findName("DownloadAnimation");
    this.downloadUI            = this.target.findName("downloadUI");
    this.fadeDownloadUIAnimation = this.target.findName("FadeDownloadUI");
    this.animationCallbacks = new Array();
    this.moveSound = this.target.findName("MoveSound");
    
    // Hook up necessary events
    setCallback(this.introScreen, "mouseLeftButtonDown", delegate(this, this.handleGameStartClick));
    setCallback(this.introOutroAnimation, "completed", delegate(this, this.handleIntroOutroCompleted));
    setCallback(this.root, "KeyDown", delegate(this, this.keyDown));
    
    this.configureTimer();
    
    this.loadHexes();
}

Gameboard.prototype.suitXaml = null;
Gameboard.prototype.cowboyXaml = null;
Gameboard.prototype.markerXaml = null;
Gameboard.prototype.loader = null;
Gameboard.prototype.cols = 9;
Gameboard.prototype.rows = 9;
Gameboard.prototype.suitsScore = 0;
Gameboard.prototype.cowboyScore = 0;
Gameboard.prototype.movingPlayers = 0;
Gameboard.prototype.openSpaces = 0;
Gameboard.prototype.lastHex = null;
Gameboard.prototype.techyXaml = null;
Gameboard.prototype.levelIndex = -1;
Gameboard.prototype.partsLoaded = false;
Gameboard.prototype.started = false;

Gameboard.prototype.loadHexes = function() {
    
    // Helper class that loads all the XAML asynchronously then provides
    // a single callback when it's all loaded.
    this.loader = new Loader("xaml", this.control);
    this.loader.uris.push("Concrete1.xaml");
    this.loader.uris.push("Concrete2.xaml");
    this.loader.uris.push("Concrete3.xaml");
    this.loader.uris.push("Grass1.xaml");
    this.loader.uris.push("Grass2.xaml");
    this.loader.uris.push("Grass3.xaml");
    this.loader.uris.push("Suit.xaml");
    this.loader.uris.push("Cowboy.xaml");
    this.loader.uris.push("ActiveMarker.xaml");
    this.loader.uris.push("Sprawl1.xaml");
    this.loader.uris.push("Sprawl2.xaml");
    this.loader.uris.push("Sprawl3.xaml");
    this.loader.uris.push("Techy.xaml");
    this.loader.completed = delegate(this, this.handleXamlLoaded);

    this.downloadUI.opacity = 1;
    this.downloadAnimation.begin();
    this.loader.start();
}

Gameboard.prototype.startPlay = function() {
    // Wait for all the parts to download.
    if (this.partsLoaded && !this.started) {
        this.started = true;

        // Animate out the intro
        this.introOutroAnimation.begin();
        this.introIntroAnimation.stop();
    }
}

Gameboard.prototype.nameOkButtonClicked = function(sender, args) {
    this.takingNames = false;
    this.okClicked = true;
    this.control.content.findName("nameAreaBlock").opacity = 0;
    this.showNameAreaAnimation.stop();
    this.hideNameAreaAnimation.begin();
    this.startPlay();
    
    var oNameArea = this.root.findName("nameArea");
    if (oNameArea == null) { return; }
    oNameArea.Text = this.playerName;
}

Gameboard.prototype.keyDown = function(sender, args) {
    if (!this.takingNames) { return; }
    var sKey = this.KeyMap(args.key);
    switch (sKey)
    {
        case "bksp":
            if (this.playerName == "") return;
            this.playerName = this.playerName.substr(0, this.playerName.length - 1);
            break;
        case "enter":
            this.takingNames = false;
            this.hideNameAreaAnimation.begin();
            this.showNameAreaAnimation.stop();
            this.startPlay();
            break;
        default:
            if (args.shift) { sKey = sKey.toUpperCase();}
            this.playerName += sKey;
    }  
    var oNameArea = this.root.findName("nameArea");
    if (oNameArea == null) { return; }
    oNameArea.Text = this.playerName;
}

Gameboard.prototype.KeyMap = function(key) {
   // it is necessary to build this map, because the key property is an internal enum.
   // using args.platformKeyCode is possible, but requires you to switch based on the
   // platform
   switch (key) {
      case  1: return "bksp";
      case  3: return "enter";
      case  9: return " "
      case 30: return "a";
      case 31: return "b";
      case 32: return "c";
      case 33: return "d";
      case 34: return "e";
      case 35: return "f";
      case 36: return "g";
      case 37: return "h";
      case 38: return "i";
      case 39: return "j";
      case 40: return "k";
      case 41: return "l";
      case 42: return "m";
      case 43: return "n";
      case 44: return "o";
      case 45: return "p";
      case 46: return "q";
      case 47: return "r";
      case 48: return "s";
      case 49: return "t";
      case 50: return "u";
      case 51: return "v";
      case 52: return "w";
      case 53: return "x";
      case 54: return "y";
      case 55: return "z";
      default: return "";
   }
}

/* Called from the loader when all the contents are finished loading.
 * The loader is really dumb and assumes it always works.
 */
Gameboard.prototype.handleXamlLoaded = function(responses) {    
    // this.downloadAnimation.stop();
    this.fadeDownloadUIAnimation.begin();
    
    this.suitXaml = responses["Suit.xaml"];
    this.cowboyXaml = responses["Cowboy.xaml"];
    this.techyXaml = responses["Techy.xaml"];
    this.markerXaml = responses["ActiveMarker.xaml"];
    
    this.concretes = new Array();
    this.concretes.push(responses["Concrete1.xaml"]);
    this.concretes.push(responses["Concrete2.xaml"]);
    this.concretes.push(responses["Concrete3.xaml"]);
    
    this.grasses = new Array();
    this.grasses.push(responses["Grass1.xaml"]);
    this.grasses.push(responses["Grass2.xaml"]);
    this.grasses.push(responses["Grass3.xaml"]);
    
    this.sprawls = new Array();
    this.sprawls.push(responses["Sprawl1.xaml"]);
    this.sprawls.push(responses["Sprawl2.xaml"]);
    this.sprawls.push(responses["Sprawl3.xaml"]);
    
    //All the xaml parts are loaded, show the 'click here to begin'
    this.partsLoaded = true;
    this.continueIntroAnimation.begin();
}

/* User clicked intro screen to start the game */
Gameboard.prototype.handleGameStartClick = function() {
    if (!this.takingNames && !this.okClicked) {
        if (this.playerCount <= 1) { 
            var nameOkButton = this.control.content.findName("nameOkButton");
            setCallback(nameOkButton, "mouseLeftButtonDown", delegate(this, this.nameOkButtonClicked));
            this.inputName();
        } else {
            this.startPlay();
        }
    }
}


Gameboard.prototype.inputName = function() {
    this.showNameAreaAnimation.begin();
    this.takingNames = true;
}

/* Called when the intro is done animating and the game needs to begin */
Gameboard.prototype.handleIntroOutroCompleted = function() {
    this.introOutroAnimation.stop();
    this.continueIntroAnimation.stop();
    this.root.children.remove(this.introScreen);
    
    this.nextLevel();
}

/* Starts a new game with the specified characters
 * Techys are real timid and only move short distances, but they're quite crafty
 * Suits move longer distances, but go for more points.
 * Cowboys are controlled by the user.
 * @param techyCount #of techys that are on the screen
 * @param suitCount #of suits on the screen
 * @param cowboyCount #of cowboys on the screen
 */
Gameboard.prototype.newGame = function(techyCount, suitCount, cowboyCount) {
    while(this.gameboard.children.count > 0)
        this.gameboard.children.removeAt(0);
        
    this.hexes = new Array();
    this.players = new Array();
    
    this.openSpaces = 0;
    this.movingPlayers = 0;
    this.addToSuitsScore(-this.suitsScore);
    this.addToCowboyScore(-this.cowboyScore);
    
    for (var x = 0; x < this.rows; ++x)
        this.hexes[x] = new Array();
    
    for (var y = 0; y < this.cols; ++y) {
        for (var x = 0; x < this.rows; ++x) {
        
            var hexElement;
            
            var value = Math.round(Math.random() * 2);
            hexElement = this.control.content.createFromXaml(this.concretes[value]);
            
            this.gameboard.children.add(hexElement);
            this.hexes[x][y] = new Hex(this, hexElement, x, y, value + 1);
        }
    }
    
    for (var i = 0; i < techyCount; ++i) {
        var techyGraphic = this.control.content.createFromXaml(this.techyXaml);
        this.gameboard.children.add(techyGraphic);
        var techy = new Player(this, techyGraphic, this.sprawls);
        techy.setPosition(0, Math.floor(this.rows / 2) + (i - Math.floor((techyCount + suitCount) / 2) ) * 2);
        techy.scoreCallback = delegate(this, this.addToSuitsScore);
        
        // Change techy's strategy to be lame :)
        techy.findBestPlay = techy.timidFindBestPlay;
        
        this.players.push(techy);
        
        if (this.playerCount < 2)
            techy.isAutoPlayer = true;
    }
    
    for (var i = 0; i < suitCount; ++i) {
        var suitGraphic = this.control.content.createFromXaml(this.suitXaml);
        this.gameboard.children.add(suitGraphic);
        var suit = new Player(this, suitGraphic, this.sprawls);
        suit.setPosition(0, Math.floor(this.rows / 2) + (i - Math.floor((suitCount + techyCount) / 2) ) * 2);
        suit.scoreCallback = delegate(this, this.addToSuitsScore);
        this.players.push(suit);
        
        if (this.playerCount < 2)
            suit.isAutoPlayer = true;
    }
    
    for (var i = 0; i < cowboyCount; ++i) {
        var cowboyGraphic = this.control.content.createFromXaml(this.cowboyXaml);
        if (this.playerName != "") {
            var cowboyName    = this.control.content.createFromXaml(
                "<Canvas xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' Canvas.Left='17' Canvas.Top='40' x:Name='nameCanvas" + i + "'>" + 
                "<Rectangle x:Name='nameRect" + i + "' Fill='#77000000' Width='70' Height='25' RadiusX='3' RadiusY='3' />" +
                "<TextBlock x:Name='nameText" + i + "' Foreground='White' Canvas.Left='5' Canvas.Top='2' FontSize='14' Text='" + this.playerName + "' />" +
                "</Canvas>");
            cowboyGraphic.children.add(cowboyName);
        }
        this.gameboard.children.add(cowboyGraphic);
        var cowboy = new Player(this, cowboyGraphic, this.grasses);
        cowboy.setPosition(this.cols - 1, Math.floor(this.rows / 2) + (i - Math.floor(cowboyCount / 2) ) * 2);
        cowboy.scoreCallback = delegate(this, this.addToCowboyScore);
            
        this.players.push(cowboy);
        
        if (this.playerCount == 0)
            cowboy.isAutoPlayer = true;
    }
    
    this.currentPlayer = 0;
    this.players[this.currentPlayer].activate();
    
    // var cowboyGraphic = this.control.content.createFromXaml(this.cowboyXaml);

    for (var i = 0; i < cowboyCount; ++i) {
        if (this.playerName != "") {
            var textWidth = this.control.content.findName("nameText" + i).actualWidth + 10;
            this.control.content.findName("nameRect" + i).width = textWidth;
            this.control.content.findName("nameCanvas" + i)["canvas.left"] -= (textWidth - 68) / 2;
        }
    }
    
}

/* Adds the value to the suits score and updates the score in the UI. */
Gameboard.prototype.addToSuitsScore = function(value) {
    this.suitsScore += value;
    this.suitsScoreDisplay.text = "" + this.suitsScore;
}

/* Adds the value to the cowboy score and updates the score in the UI. */
Gameboard.prototype.addToCowboyScore = function(value) {
    this.cowboyScore += value;
    this.cowboyScoreDisplay.text = "" + this.cowboyScore;
}

/* Try to move the current player to the specified hex.
 * This will do nothing if the hex is not a valid move.
 */
Gameboard.prototype.tryMoveTo = function(hex) {
    var currentPlayer = this.players[this.currentPlayer];
    
    if (currentPlayer.isAnimating())
        return;
    
    if (this.canMoveTo(hex, currentPlayer, currentPlayer.getCurrentHex())) {
        currentPlayer.addScore(hex.value);
        currentPlayer.deactivate();
        
        currentPlayer.animatePlayerTo(hex.x, hex.y);
        this.moveSound.stop();
        this.moveSound.play();
        if (!currentPlayer.isAutoPlayer)
            this.clearSemiSelect();
    }
}

/* Advance to the next player.
 * Will take care of advancing the level etc if no players can move.
 */
Gameboard.prototype.nextPlayer = function() {
    var currentPlayer = this.players[this.currentPlayer];
    currentPlayer.deactivate();

    // Here as a shortcut to show the win screen. It's not all that great, if you're curious.    
//    if (!currentPlayer.isAutoPlayer) {
//        this.showWinnar();
//        return;
//    }
    
    var anyAutoPlayers = false;
    for (var i = 0; i < this.players.length; ++i) {
        var player = this.players[i];
        if (player.isAutoPlayer && player.canMove) {
            anyAutoPlayers = true;
            break;
        }
    }
    
    if (!anyAutoPlayers && this.cowboyScore > this.suitsScore) {
        this.levelEnded();
        return;
    }
    
    if (this.movingPlayers != 0) {
        ++this.currentPlayer;
        if (this.currentPlayer >= this.players.length)
            this.currentPlayer = 0;
            
        this.players[this.currentPlayer].activate();
        
        if (this.lastHex != null)
            this.trySelectTo(this.lastHex);
    }
    else
        this.levelEnded();
}

/* Called when the current level is done and the next level should be started */
Gameboard.prototype.levelEnded = function() {
    if (this.suitsScore > this.cowboyScore)
        this.showLosar();
    else
        this.nextLevel();
}

/* Advance the game to the next level, or announce winner if no levels left */
Gameboard.prototype.nextLevel = function() {
    ++this.levelIndex;
    if (this.levelIndex == 0) {
        this.newGame(1, 0, 1);
    }
    else if (this.levelIndex == 1) {
        this.newGame(0, 1, 1);
    }
    else if (this.levelIndex == 2) {
        this.newGame(2, 0, 1);
    }
    else if (this.levelIndex == 3) {
        this.newGame(0, 2, 2);
    }
    else if (this.levelIndex == 4) {
        this.newGame(0, 2, 1);
    }
    else
        this.showWinnar();
}

/* Show the 'You Win' screen */
Gameboard.prototype.showWinnar = function() {
    this.root.children.add(this.introScreen);
    this.winnerIntroAnimation.begin();
}

/* Show the 'You Lose' screen */
Gameboard.prototype.showLosar = function() {
    this.root.children.add(this.introScreen);
    this.loserIntroAnimation.begin();
}

Gameboard.prototype.decrementMovingPlayers = function() {
    --this.movingPlayers;
}

/* Highlight the path to the specified hex if it is a valid move
 * This will show the score for the hex if it is valid and add the
 * yellow highlight to hexes in the middle.
 */
Gameboard.prototype.trySelectTo = function(hex) {
    var currentPlayer = this.players[this.currentPlayer];
    if (currentPlayer.isAnimating()) {
        this.lastHex = hex;
        return;
    }
        
    if (this.canMoveTo(hex, currentPlayer, currentPlayer.getCurrentHex())) {
        var selector = function(hex) {
            hex.setMode("SemiSelected");
        }
        
        this.walk(currentPlayer.x, currentPlayer.y, hex.x, hex.y, selector);
        
        hex.showScore();
    }
    this.lastHex = null;
}

/* Checks to see if the player can move to the specified position */
Gameboard.prototype.canMoveTo = function(hex, player, playerLocation) {
    var isValid = true;
    var checker = function(hex) {
        if (hex.occupier != null) {
            isValid = false;
        }
    }
    if (this.walk(playerLocation.x, playerLocation.y, hex.x, hex.y, checker) && isValid) {
        return true;
    }
    return false
}

/* Clears all the highlighted hexes that show whether the move is valid or not. */
Gameboard.prototype.clearSemiSelect = function() {
    for (var x = 0; x < this.hexes.length; ++x) {
        var row = this.hexes[x];
        for (var y = 0; y < row.length; ++y) {
            var hex = row[y];
            if (hex.mode == "SemiSelected")
                hex.setMode("Deselected");
        }
    }
    this.lastHex = null;
}

/* Calls the specified callback for every hex within maxRadius of hex
 * This is a helper method for iterating all the possible moves
 * Callback is expected to return true or false whether to advance the
 * radius further, allows an early exit if the direction is invalid after a certain point.
 */
Gameboard.prototype.walkPossibilities = function(hex, maxRadius, callback) {
    for (var radius = 1; radius <= maxRadius; ++radius) {
        var y = hex.y + radius;
        var x = radius / 2;
        if (hex.y % 2 == 1)
            x += .5;
        var x = Math.floor(x) + hex.x;
        
        if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
            if (!callback(this.hexes[x][y]))
                break;
        }
    }
    
    for (var radius = 1; radius <= maxRadius; ++radius) {
        var y = hex.y + radius;
        var x = radius / 2;
        if (hex.y % 2 == 0)
            x += .5;
        x = hex.x - Math.floor(x);
        
        if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
            if (!callback(this.hexes[x][y]))
                break;
        }
    }
    
    for (var radius = 1; radius <= maxRadius; ++radius) {
        var y = hex.y - radius;
        x = radius / 2;
        if (hex.y % 2 == 1)
            x += .5;
        x = Math.floor(x) + hex.x;
        
        if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
            if (!callback(this.hexes[x][y]))
                break;
        }
    }
    
    for (var radius = 1; radius <= maxRadius; ++radius) {
        var y = hex.y - radius;
        x = radius / 2;
        if (hex.y % 2 == 0)
            x += .5;
        x = hex.x - Math.floor(x);
        
        if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
            if (!callback(this.hexes[x][y]))
                break;
        }
    }
    
    y = hex.y;
    
    for (var radius = 1; radius <= maxRadius; ++radius) {
        x = hex.x + radius;
        if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
            if (!callback(this.hexes[x][y]))
                break;
        }
    }
    
    for (var radius = 1; radius <= maxRadius; ++radius) {
        x = hex.x - radius;
        if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
            if (!callback(this.hexes[x][y]))
                break;
        }
    }
}

/* Call the provided callback for every hex between the two specified points
 * This will return false if there is no path between the two points.
 */
Gameboard.prototype.walk = function(xo, yo, xf, yf, handler) {
    if (yo == yf) {
        for (var x = xo; x != xf; x += (xo < xf ? 1 : -1)) {
            if (x != xo)
                handler(this.hexes[x][yf]);
        }
        handler(this.hexes[xf][yf]);
        return true;
    }
    else {
        var yOffset = (yf - yo) / 2;
        if (yo % 2 == 1)
            yOffset += .5;
            
        if (xf - xo == Math.floor(yOffset)) {
            
            for (var y = yo; y != yf; y += (yo < yf ? 1 : -1)) {
                var x = (y - yo) / 2;
                if (y % 2 == 0)
                    x += .5;
                x = Math.floor(x) + xo;
                
                if (!(x == xo && y == yo))
                    handler(this.hexes[x][y]);
            }
            handler(this.hexes[xf][yf]);
            return true;
        }
        else {
            yOffset = (yf - yo) / 2;
            if (yo % 2 == 0)
                yOffset += .5;
                
            if (xo - xf == Math.floor(yOffset)) {
                for (var y = yo; y != yf; y += (yo < yf ? 1 : -1)) {
                    var x = (y - yo) / 2;
                    if (yo % 2 == 0)
                        x += .5;
                    x = xo - Math.floor(x);
                    
                    if (!(x == xo && y == yo))
                        handler(this.hexes[x][y]);
                }
                handler(this.hexes[xf][yf]);
                return true;
            }
        }
    }
    return false;
}

/* Single animation callback to handle the manual player animations. */
Gameboard.prototype.handlePerFrameTick = function() {
    for (var i = 0; i < this.animationCallbacks.length; ++i)
        this.animationCallbacks[i].handleAnimationUpdate();
}

/* Used for the manual player animation */
Gameboard.prototype.startAnimation = function(handler) {
    this.animationCallbacks.push(handler);
    this.perFrameStoryboard.begin();
}

/* Used for the manual player animation */
Gameboard.prototype.stopAnimation = function(handler) {
    for (var i = 0; i < this.animationCallbacks.length; ++i)
        if (this.animationCallbacks[i] == handler) {
            this.animationCallbacks.splice(i, 1);
            break;
        }
}

/* Sets up the per-frame timer callbacks for manual animations */
Gameboard.prototype.configureTimer = function() {
    // on Firefox use the window's interval handler, prevents FF from generating unresponsive script warnings
    if (navigator.userAgent.indexOf("Firefox")!=-1) {
        window.setInterval(delegate(this, this.handleIntervalTick), 30);
    }
    // use a zero-duration storyboard on IE so the animation is smoother.
    else {
        setCallback(this.perFrameStoryboard, "completed", delegate(this, this.handleStoryboardTick));
    }
}

Gameboard.prototype.handleStoryboardTick = function() {
    this.handlePerFrameTick();
    this.perFrameStoryboard.begin();
}

Gameboard.prototype.handleIntervalTick = function() {
    this.handlePerFrameTick();
}