/**
 * Grid object.
 * @author Mark Woodman
 */
 
 /**
  * Constructor of the Grid, which is essentially the
  * data model & controller rolled into one.
  */
function Grid(editMode)
{
    // A psuedo-Singleton makes for easy access elsewhere.
    Grid.instance = this;
    
    // The view node in HTML where grid hardware will be rendered
    this.containerNode = document.getElementById("gridContainer");
    
    // The puzzle info node
    this.puzzleInfoNode = document.getElementById("puzzleInfo");
    this.turnInfoNode = document.getElementById("turnInfo");
    
    // Default grid size.  This may change in future versions
    this.cols = 10;
    this.rows = 10;
    
    // Test mode allows player to return to editor
    this.testMode = false;
    
    // Whether the grid is being displayed for edit or for gameplay
    if(editMode) 
    {
        this.editMode = editMode;
    }
    else
    {
        this.editMode = false;
    }
    
    // Default Puzzle metadata
    this.puzzleAuthor = "Anonymous";
    this.puzzleName = "A Puzzle";
    this.puzzlePar = 0;
    
    // Now for the essense of the grid itself.
    this.hardwareMatrix = new Array(this.cols);
    this.imageMatrix = new Array(this.cols);
    for(var x=0;x<this.cols;x++)
    {
       this.hardwareMatrix[x] = new Array(this.rows);
       this.imageMatrix[x] = new Array(this.rows);
       for(var y=0;y<this.rows;y++)
       {
            var hardware = new EmptySlot(new Array(x,y) , "north");
            this.hardwareMatrix[x][y] = hardware;
            this.imageMatrix[x][y] = hardware.getImage();
       } 
    }
    
    // Generate the view objects (images) within the grid container
    this.render();
    
    // Preload all the hardware images used in the game.
    Utils.loadImages();
    
    // Read the URL for puzzle data
    this.reset();
}

/**
 * Reset the puzzle.
 */
Grid.prototype.reset = function()
{
    if(this.currentPuzzle==null)
    {
        this.hasCustomPuzzle = this.readUrl();
        if(!this.editMode && !this.hasCustomPuzzle) 
        {
            this.currentPuzzle = Levels[0];
        }
    }

    this.loadPuzzle();
    
    if(this.editMode==false)
    {
        this.containerNode.style.border="solid red";
    }
}

/**
 * Generate the table and images which
 * represent the grid and its hardware.
 */
Grid.prototype.render = function()
{
    // Create table to contain the grid
    var tableNode = document.createElement("TABLE");
    var tableBodyNode = document.createElement("TBODY");
    
    // Create rows of hardware images
    for(var y=0;y<this.rows;y++)
    {
       // Row
       tableRowNode = document.createElement("TR");
       
       // Cells
       for(var x=0;x<this.rows;x++)
       {           
           // Create cell to hold hardware image
           tableCellNode = document.createElement("TD");
           tableCellNode.appendChild(this.imageMatrix[x][y]);
           
           // Show coords in editMode
           if(this.editMode)
           {
                this.imageMatrix[x][y].title=("(" + x + "," + y + ")"); 
           }

            // Add cell to row
            tableRowNode.appendChild(tableCellNode);
       }
       
       // Add row to table
       tableBodyNode.appendChild(tableRowNode);
    }     
    
    // Add table body to table
    tableNode.appendChild(tableBodyNode);
    
    //Show border in edit mode only
    var border = (this.editMode==true) ? "1" : "0";
    tableNode.setAttribute("border", border);
    
    // Keep it tight.
    tableNode.setAttribute("cellPadding", "0");
    tableNode.setAttribute("cellSpacing", "0");
    
    // Add the table to the container node
    this.containerNode.appendChild(tableNode);
}

/**
 * Rotate the hardware at a grid point.  
 */
Grid.prototype.rotatePoint= function(x,y)
{
    debug("rotate @ " + x + "," + y);
    this.hardwareMatrix[x][y].rotate();
}

/**
 * Triggered by a user click on a grid point.  
 */
Grid.prototype.pointClicked = function(x,y)
{
    if(this.editMode)
    {
        Editor.instance.pointClicked(x,y);
    }
    else
    {
        if(!this.solved)
        {
            this.rotatePoint(x,y);
        }
    }
}

/**
 * Create a puzzle key from the current grid layout.
 * This can be used within a URL to generate the puzzle.
 * This relies upon the Hardware.hash() method to uniquely
 * identify both type and orientation of the hardware.
 */
Grid.prototype.createKey = function()
{
    var key = "";
    for(var y=0;y<this.rows;y++)
    {
        for(var x=0;x<this.cols;x++)
        {
           if(x>0) key+=",";
           key += this.hardwareMatrix[x][y].hash();          
        }
        
        // Trim all EmptySlots right of last "real" hardware
        var tokens = key.split(",");
        if(tokens.length>0)
        {
            while(tokens[tokens.length-1]=="") tokens.pop();
            key = tokens.join(",");
        }
        key += ";";
    }  
    
    // Trim all blank rows below last "real" hardware.
    var tokens = key.split(";");
    if(tokens.length>0)
    {
        while(tokens[tokens.length-1]=="") tokens.pop();
        key = tokens.join(";");
    }
    
    return key;
}

/**
 * Display the puzzle's author and name.
 */
Grid.prototype.displayPuzzleInfo = function()
{
    var windowTitle;
    var puzzleTitle;
    if(this.hasCustomPuzzle)
    {
        windowTitle = "LAN Panic! Custom Puzzle: '" + this.puzzleName + "' by " + this.puzzleAuthor;
        puzzleTitle = "'" + this.puzzleName + "'";
    }
    else
    {
        windowTitle = "LAN Panic!";
        puzzleTitle = this.puzzleName;
    }
    
    if(this.testMode && this.editMode==false)
    {
        puzzleTitle = "(TEST) " + puzzleTitle;
        windowTitle = "(TEST) " + windowTitle;
    }
    
    document.title = (windowTitle);   
    
    if(this.puzzleInfoNode!=null)
    {
    	while(this.puzzleInfoNode.hasChildNodes()) this.puzzleInfoNode.removeChild(this.puzzleInfoNode.firstChild);
    	this.puzzleInfoNode.appendChild(document.createTextNode(puzzleTitle));
    }
}

/**
 * Display the puzzle par and user turns taken
 */
Grid.prototype.displayTurnInfo = function()
{   
    if(this.turnInfoNode)
    {
        var turnText = document.createTextNode("Turns: " + this.pings + " (Par: " + this.puzzlePar + ")");   
        while(this.turnInfoNode.hasChildNodes())
        {
            this.turnInfoNode.removeChild(this.turnInfoNode.firstChild);
        }
        this.turnInfoNode.appendChild(turnText);
    }
}

/**
 * Read the URL of the document to generate
 * the puzzle and its metadata.
 * @return true if puzzle data is in URL
 */
Grid.prototype.readUrl = function()
{
    // Get everything after the ? in the URL
    var dataArray = new String(document.location).split("?");
    if(dataArray.length>1)
    {
        this.currentPuzzle = dataArray[1];
        return true;
    }
    return false;
}

/**
 * Read and load puzzle data.
 */
Grid.prototype.loadPuzzle = function()
{
    // Disable clicking
    this.solved = true;
    
    // Get all parameters
    if(this.currentPuzzle!=null)
    {
	    var parms = this.currentPuzzle.split("&");
	    for(i=0;i<parms.length;i++)
	    {
	        var nameValue = parms[i].split("=");
	        if(nameValue.length>1)
	        {
	            var val = unescape(nameValue[1]);
	            // Determine the puzzle author
	            if(nameValue[0]=="by") 
	            {
	                this.puzzleAuthor = val;
	            }
	            
	            // Determine the puzzle name
	            if(nameValue[0]=="name")
	            {
	                this.puzzleName = val;
	            }
	            
	            // Determine the puzzle best score
	            if(nameValue[0]=="par")
	            {
	                this.puzzlePar = val;
	            }
	            
	            // Determine the puzzle layout itself
	            if(nameValue[0]=="key")
	            {
	                this.readKey(val);
	            }
	            
	            // Determine whether in test mode
	            if(nameValue[0]=="test")
	            {
	                this.testMode = val;
	            }
	        }   
	    }   
	  }
    this.solved = false;
    this.pings = 0;     
    this.displayPuzzleInfo();
    this.displayTurnInfo();
    Utils.showNavigation();
}

/**
 * Replace the old hardware at a given point
 * with the new hardware specified.
 */
Grid.prototype.replaceHardware = function(x, y, newHardware)
{   
    // Reassign the hardware in the grid
    this.hardwareMatrix[x][y] = newHardware;
    
    // Reassign the image node to the new hardware
    newHardware.setImage(this.imageMatrix[x][y]);
}

/**
 * Read a key generated by createKey() and
 * populate the grid with the resulting puzzle.
 */
Grid.prototype.readKey = function(key)
{
    var keyRows = key.split(";");
    for(var y=0;y<keyRows.length;y++)
    {
       var keyCols = keyRows[y].split(",");
       
       // Fill in hardware designated in key
       for(var x=0;x<keyCols.length;x++)
       {
           var hash = keyCols[x];
           if(hash=="")
           {
                this.replaceHardware(x,y,new EmptySlot(new Array(x,y), "north"));
           }
           else
           {    
                this.replaceHardware(x,y,Hardware.newInstance(new Array(x,y), hash));
           }
       }
       
       // Fill in remaining cols with EmptySlot
       for(var x=(keyCols.length);x<this.cols;x++)
       {
           this.replaceHardware(x,y,new EmptySlot(new Array(x,y), "north"));
       } 
    }
    
    // Fill in remaining rows with EmptySlot
    for(var y=(keyRows.length);y<this.rows;y++)
    {
       for(var x=0;x<this.cols;x++)
       {   
           this.replaceHardware(x,y,new EmptySlot(new Array(x,y), "north"));
       } 
    }   
    
    // Identify where the servers are
    this.locateServers();
    
    // Update the connection paths
    this.requestPing(true);
}

/**
 * Identify all of the servers on the grid.
 */
Grid.prototype.locateServers = function()
{
    this.servers = new Array();
    for(var y=0;y<this.rows;y++)
    {
       for(var x=0;x<this.cols;x++)
       {   
            var hardware = this.hardwareMatrix[x][y];
            if(hardware.isServer()) this.servers.push(hardware);
       } 
    }      
}

/**
 * Kick off a ping from all servers in the grid.
 * This is used to update connection paths; show
 * what is and isn't connected.
 */
Grid.prototype.requestPing = function(systemRequest)
{
    // Ensure we know where the servers are
    if(!this.servers) this.locateServers();
    
    // If no ping count yet, start at 0
    if(!this.pings) this.pings = 0;
    
    // If not a systemRequest, then the ping
    // was triggered by a user click on the grid,
    // which counts as a turn.
    if(!systemRequest) 
    {
        this.pings++;
        this.displayTurnInfo();
    }
    
    // Assume disconnectivity
    for(var y=0;y<this.rows;y++)
    {
       for(var x=0;x<this.cols;x++)
       {   
            var hardware = this.hardwareMatrix[x][y];
            if(!hardware.isServer()) 
            {
                hardware.setIsOnline(false);
            }
       } 
    }
    
    // Start ping from servers to indicate connectivity
    for(var i=0;i<this.servers.length;i++)
    {
        this.servers[i].ping(new Array());
    }
    
    // Refresh images
    for(var y=0;y<this.rows;y++)
    {
       for(var x=0;x<this.cols;x++)
       {   
            var hardware = this.hardwareMatrix[x][y];
            hardware.refreshImage();
       } 
    }
    
    // Check complete connectivity
    if(!this.editMode)
    {
        var gridOnline = true;
        gridCheck : for(var y=0;y<this.rows;y++)
        {
           for(var x=0;x<this.cols;x++)
           {   
                var hardware = this.hardwareMatrix[x][y];
                if(!hardware.isOnline())
                {
                    gridOnline = false;
                    break gridCheck;
                }
           } 
        }
        
        if(gridOnline) this.puzzleSolved();
    }
}

/**
 * The user has solved the puzzle.
 */
Grid.prototype.puzzleSolved = function()
{
    if(!this.solved)
    {
        this.solved = true;
        this.containerNode.style.border="groove black";
        alert("You saved the LAN in " + this.pings + " turns!");
        Utils.showNavigation();
    }
}