/*----------------------------------\*
||             menu.js              ||
||==================================||
||           Menu system.           ||
\*----------------------------------*/
//© Tengu 2010, taken from Moon Blade project.


/* Tutorial on inheritance in JS: 
   http://phrogz.net/JS/Classes/OOPinJS2.html */


/** Generic Menu class for other menus to inherit from.
  * Does not function properly on its own because some important classes are left empty.
  */
function Menu(x, y, width, height)
{
  /* NOTE ABOUT OPTION SELECTION
   * When an item is selected, its renderSelected() and renderUnselected() methods
   * will determine what the menu item looks like inside a menu. So it's not up to
   * the menu but the option to render in a specific way when selected. */
  
  //this.options contains a linear array of MenuOption objects. Where the options are actually placed
  //in the menu is information that's stored inside instances of these Options, not in the menu.
  this.options = [];
  
  this.selected_option = 0;  //Uses a number that references an item in this.options.
  this.x = x;
  this.y = y;
  this.width = width;   //Might determine option sizes in GridMenu?
  this.height = height;
  this.font = GetSystemFont();
  this.arrow = undefined;  //Define to get an arrow image in the options.
  this.arrow_pos = [0, 0];
  this.sound = undefined;
  this.confirmsound = undefined;
  
  //The following are used for rendering the background window in any special way.
  this.bg_image = undefined;
  this.bg_color = undefined;
  this.bg_gradient = undefined;  //[color1, color2, color3, color4] directions order: TL, TR, BR, BL
  this.windowstyle = undefined;
  
  //The following should perhaps be properties of Option instead, so we can mix the use of showing/hiding icons and text.
  this.show_icons = false;
  this.show_text = true;
  
  this.bgRender = function() {}  //User-defined.
  this.fgRender = function() {}  //User-defined.
  this.userUpdate =   function() {}  //User-defined. If any extra logic is required. Runs before the rendering cycles.
  this.menuOptionRender = function() {}  //Menu type-defined, because x and y properties of MenuOptions can mean different things per menu type.
  
  /** This function keeps all the options inside the menu up-to-date according to the menu's state. Differs per menu. */
  this.update = function() { }
  
  /** Convenience function to try to render any special types of window backgrounds like images, gradients or windowstyles.
    * Not user-defined. */
  this.windowRender = function()
  {
    if (this.windowstyle) this.windowstyle.drawWindow(this.x, this.y, this.width, this.height);
    if (this.bg_image) this.bg_image.drawImage(this.x, this.y);
    if (this.bg_color) Rectangle(this.x, this.y, this.width, this.height, this.bg_color);
    else if (this.bg_gradient) GradientRectangle(this.x, this.y, this.width, this.height, this.bg_gradient[0], this.bg_gradient[1], this.bg_gradient[2], this.bg_gradient[3]);
  }
  
  
  /** Add menu option to the menu. Also changes other information inside the grid, if necessary. */
  this.addOption = function(option)
  {
    this.options.push(option);
  }
  
  this.removeOption = function(option)
  {
    for (var i = 0; i < this.options.length; i++)
    {
      //TODO: search for the right option object here.
    }
  }
  
  
  this.removeAllOptions = function()
  {
    this.options = [];
  }
  
  
  /** Opens the menu. Keeps the system in an internal loop until done. When done, returns the option selection. */
  this.open = function()
  {
    var done = false;
    
    //When not done with the menu: show the menu and get input.
    while (!done)
    {
      while (INPUT.pressed("any")) this.update(), this.render(), FlipScreen();
      
      this.update();
      this.render();
      FlipScreen();
      
      done = this.getInput();
    }
    return this.done();
  }
  
  
  /** Return a specific value when done. */
  this.done = function()
  {
    return this.options[this.selected_option];
  }
  
  
  /** getInput() takes care of the keypresses and how to change the "selected" value accordingly to these keypresses.
    * getInput() is "done" when an option is selected or the menu is closed.
    * OVERLOADED FUNCTION: This function should handle keypresses and confirmation differently per menu type. Otherwise acts as stub or testfunction.
    */
  this.getInput = function() { this.selected_option = 1; }
  
  
  /** Renders the menu onto the backbuffer. */
  this.render = function()
  {
    this.update();
    this.userUpdate();
    
    this.bgRender();
    this.windowRender();
    this.menuOptionRender();  //Menu-type-specific.
    this.fgRender();
  }
}

Menu.prototype.toString = function() { return "Menu"; }





/** The GridMenu is a 2D grid of menu options with clear row and column separations.
  * @inherits: Menu
  */
GridMenu.prototype = new Menu();            // Here's where the inheritance occurs
GridMenu.prototype.constructor = GridMenu;  // Otherwise instances of GridMenu would have a constructor of Menu
GridMenu.prototype.parent = Menu.prototype;
function GridMenu(x, y, col_amount, width, height)
{
  this.row_amount = 1;      //Amt of items in the largest row.
  this.col_amount = col_amount;  //Amount of columns.
  this.option_width = 100;  //Sensible defaults
  this.option_height = this.font.getHeight()*2;
  this.autoset_option_width = true;    //Might want to turn it off to, for example, display a very small col next to a wide one.
  this.autoset_option_height = false;  //Automatically set the option height when adding options instead of 20 if true.
  this.allow_horiz_rowchange = false;  //Allow changing rows when pressing left or right when on the first/last column.
  this.x = x;
  this.y = y;
  this.width = width ? width : 0;
  this.height = height ? height : 0;
  
  //Modified from regular menu.
  this.selected_option = [0, 0];
  this.options = [];
  
  
  /** Overload the Menu.done() method to accomodate for the differently stored menu options. */
  this.done = function()
  {
    if (this.selected_option == undefined) { return undefined; }
    return this.options[this.selected_option[0]][this.selected_option[1]];  //this.options[x][y]
  }
  
  
  /** Update all options depending on the menu's state. */
  this.update = function()
  {
    for (var y = 0; y < this.row_amount; y++)
    { for (var x = 0; x < this.col_amount; x++)
      { var opt = this.options[x][y];
        if (opt != undefined)  //The option might be undefined, in that case don't bother with all this.
        {
          opt.arrow = this.arrow;  //Set arrow.
          opt.arrow_pos = this.arrow_pos;
          if (this.autoset_option_width) this.option_width = this.width / this.col_amount;
          if (this.autoset_option_height) this.option_height = this.height / this.row_amount;
          opt.width = this.option_width;
          opt.height = this.option_height;
          opt.update();
          opt.userUpdate();
        }
      }
    }
  }
  
  
  /** Render menu options on the screen. */
  this.menuOptionRender = function()
  {
    for (var y = 0; y < this.row_amount; y++)
    {
      for (var x = 0; x < this.col_amount; x++)
      {
        if (this.options[x][y] != undefined)  //The option might be undefined, in that case don't bother with all this.
        {
          var _x = this.x + (x * this.option_width);
          var _y = this.y + (y * this.option_height);
          var selected = (this.selected_option[0] == x && this.selected_option[1] == y);
          this.options[x][y].render(_x, _y, selected);
        }
      }
    }
  }
  
  
  /** Change the state of the menu according to the input (ex. selected option, closed menu). */
  this.getInput = function()
  {
		//while (AreKeysLeft()) GetKey();
    if (INPUT.pressed("cancel") || INPUT.pressed("menu")) { this.selected_option = undefined; return true; }
    if (INPUT.pressed("confirm")
        && this.options[this.selected_option[0]][this.selected_option[1]] != undefined  //Not an empty option
        && this.options[this.selected_option[0]][this.selected_option[1]].active)       //Not an inactive option
    {
      if (this.confirmsound) this.confirmsound.play(false);
      return true;
    }
    
    if (INPUT.pressed("up") && this.selected_option[1] > 0)
    {
      this.selected_option[1]--;
      if (this.sound) this.sound.stop();
      if (this.sound) this.sound.play(false);
    }
    else if (INPUT.pressed("down") && this.selected_option[1] < this.options[0].length-1)
    {
      this.selected_option[1]++;
      if (this.sound) this.sound.stop();
      if (this.sound) this.sound.play(false);
    }
    
    if (INPUT.pressed("left"))
    {
      if (this.selected_option[0] > 0)
        this.selected_option[0]--;
      else if (this.selected_option[1] > 0 && this.allow_horiz_rowchange)  //We already know x is 0, so only compare to y.
      {
        this.selected_option[0] = this.col_amount;
        this.selected_option[1]--;
      }
      if (this.sound) this.sound.stop();
      if (this.sound) this.sound.play(false);
    }
    
    else if (INPUT.pressed("right"))
    {
      if (this.selected_option[0] < this.col_amount-1)
        this.selected_option[0]++;
      else if (this.selected_option[1] < this.row_amount-1 && this.allow_horiz_rowchange)
      {
        this.selected_option[0] = 0;
        this.selected_option[1]++;
      }
      if (this.sound) this.sound.stop();
      if (this.sound) this.sound.play(false);
    }
    
    //TODO: check for option.selectable and if false, skip to the next (or previous) option.
  }
  
  
  /** Overloaded to optionally change each option's physical size as well as remember the current amount of rows. */
  this.addOption = function(x, y, option)
  {
    if (this.options[x] == undefined) this.options[x] = [];
    option.x = x;
    option.y = y;
    this.options[x][y] = option;
    this.row_amount = this.options[x].length;
    if (!this.autoset_option_height) this.height = this.row_amount*this.option_height;
    
    //TODO? Check for empty rows above the current entry and auto-replace them with [].
  }
}


GridMenu.prototype.toString = function()
{
  return "GridMenu[" + this.options + "]";
}




/** This menu allows floating elements instead of in a grid. */
function FloatMenu(x, y)
{
  
}




/** Menu option, works with either menu type.
  * TODO: incorporate all the new ideas that put the option-specific stuff back into the option code and not the menu code. */
function MenuOption(text, icon, x, y)
{
	//TODO: support animations for the pictures.
	
  this.text = text;
  this.icon = icon ? icon : undefined;
  
  //x and y mean different things for different menus.
  //In a FloatMenu, they are x and y positions of the option in pixels.
  //In a GridMenu, they indicate the row (x) and column (y) the option is in, but aren't actually filled in in the args.
  this.x = x ? x : 0;
  this.y = y ? y : 0;
  this.width = 0;  //Defaults are generated when using GridMenu.addOption(), if using the GridMenu.
  this.height = 0;
  
  this.description = "No help for this item (" + this + ") is available.";
  this.selectable = true;  //Can you select this option or should the menu system skip over to the next one?
  this.active = true;      //Is the option active (you can move over it but not press keys.select on it)?
  
  //Visuals.
  this.font = res.load("tinyfont.rfn");  //Automatically set a font if there's text, else don't.
  this.font_color = COLORS.WHITE;
  this.text_pos = [0, 0];         //Can be modified if the text needs to go somewhere else relative to the option.
  this.selected_text_pos = [0, 0];
  this.arrow = undefined;  //Either shown or not shown, no special effects.
  this.arrow_pos = [0, 0];        //Define where the arrow is displayed (in pixels), relative to the option.
  
  this.bg_graphic = undefined;          //Background pic to show on the option, ex. a button background, or a generic icon.
  this.icon = undefined;
  this.icon_pos = [0, 0];
  
  //Visuals and graphic changers for selected options.
  this.selected_font_color = COLORS.YELLOW;
  this.selected_bg_graphic = undefined; //Graphic for when the option is selected.
  this.selected_bg_graphic_color = undefined;   //Change the bg_graphic's color when defined.
  this.selected_font = this.font;       //Change the font when defined.
  this.selected_icon = undefined;
  this.selected_icon_color = undefined; //Change icon's color if defined.
  this.selected_icon_size = undefined;  //When defined, it's in the format [width, height].
  this.selected_icon_pos = [0, 0];      //An icon might be zoomed in so then the user might want to adjust the position of the icon.
  
  /** Default update script to keep the menu option up-to-date. */
  this.update = function() { }  //Nothing in here yet, do I even need it?
  
  /** User-defined updates for the option, used for (for example) text or size animation. */
  this.userUpdate = function() { }
  
  /** Draw the menu option onto the screen at the predefined position.
    * @param prex      Predefined X position to render at.
    * @param prey      Predefined Y position to render at.
    * @param selected  Determines whether to return a queue that shows a selected or unselected view of items.
    */
  this.render = function(prex, prey, selected)
  {
    //Order is important here. First things first.
    var queue = [];
    
    if (selected)
    {
      //bg or image
      if (this.selected_bg_graphic)
        this.selected_bg_graphic.blit(prex, prey);
      else if (this.bg_graphic)
        this.bg_graphic.blit(prex, prey);
      if (this.selected_bg_graphic_color)
        Rectangle(prex, prey, this.bg_graphic.width, this.bg_graphic.height, this.selected_bg_graphic_color);
      
      //icon
      if (this.selected_icon)
      {
        var iconx = prex + this.selected_icon_pos[0];
        var icony = prey + this.selected_icon_pos[1];
        
        if (this.arrow)
          iconx += this.arrow.width;
        
        if (this.selected_icon_size)
        {
          this.selected_icon.transformBlit(iconx, icony, iconx+this.selected_icon_size[0], icony,
               iconx, icony+this.selected_icon_size[1], iconx+this.selected_icon_size[0], icony+this.selected_icon_size[1]);
          if (this.selected_icon_color)
            Rectangle(iconx, icony, this.selected_icon_size[0], this.selected_icon_size[1], this.selected_icon_color);
        }
        
        else  //Blit regular size icon.
        {
          this.selected_icon.blit(iconx, icony);
          if (this.selected_icon_color)
            Rectangle(iconx, icony, this.selected_icon.width, this.selected_icon.height, this.selected_icon_color);
        }
      }
      
      //text
      if (this.selected_font)
      {
        var textx = prex;
        if (this.selected_font_color) this.selected_font.setColorMask(this.selected_font_color);
        
        var _w = this.width;
        var _x = textx + this.selected_text_pos[0];
        if (this.selected_icon)
          _w -= this.selected_icon.width + this.selected_icon_pos[0],
          _x += this.selected_icon.width;
        if (this.arrow)
          _w -= this.arrow.width + this.arrow_pos[0],
          _x += this.arrow.width;
        this.selected_font.drawTextBox(_x, prey + this.selected_text_pos[1], _w, this.height, 0, this.text);
        
        //Revert back to previous color when done
        if (this.selected_font_color) this.selected_font.setColorMask(this.font_color);
      }
      
      //arrow
      if (this.arrow)
        this.arrow.blit(prex + this.arrow_pos[0], prey + this.arrow_pos[1]);
    }
    
    
    //IF NOT SELECTED //
    else
    {
      //bg
      if (this.bg_graphic)
        this.bg_graphic.blit(prex, prey);
      
      //icon
      if (this.icon)
      {
        var iconx = prex + this.icon_pos[0];
        var icony = prey + this.icon_pos[1];
        
        if (this.icon_size)
        {
          this.icon.transformBlit(iconx, icony, iconx+this.icon_size[0], icony,
               iconx, icony+this.icon_size[1], iconx+this.icon_size[0], icony+this.icon_size[1]);
        }
        else  //Blit regular size icon.
          this.icon.blit(iconx, icony);
      }
      
      //text
      if (this.font)
      {
        var textx = prex;
        if (this.font_color) this.font.setColorMask(this.font_color);
        
        var _w = this.width;
        var _x = textx + this.text_pos[0];
        if (this.icon)
          _w -= this.icon.width + this.icon_pos[0],
          _x += this.icon.width;
        
        this.font.drawTextBox(_x, prey + this.text_pos[1], _w, this.height, 0, this.text);
      }
    }
    return queue;
  }
}


MenuOption.prototype.toString = function()
{
  return "MenuOption['" + this.x + ", " + this.y + ", " + this.text + "']";
}
