/**** level.js ****
 * Handles levels */


/** Level system.
  * Initializes the entire level, with enemies, items etc.
  * Also has the background code for any important game event that happen in-level. That is,
  * the win screen, lose screen and pause menu, and the triggers for all of this.
  **/
function Level(mapfile, music)
{
  //IDEA SCRAPPED/DELAYED: level generation
  this.font = res.load("joystix.rfn");
  this.timer = 0;
  this.paused = false;
  this.items = [];
  this.mapfile = mapfile;
  this.music = music ? music : res.load("music/levelsong1.ogg");
  this.powerupmusic = res.load("music/powerup.ogg");
  this.gotcupcakes = 0;
  this.totalcupcakes = 0; //Total amount of cupcakes in level
  this.width = 0;
  this.height = 0;
  
  this.projectiles = []; //Projectiles being fired in this map.

  //Help text to display at the top of the screen.
  this.helptext = "";


  //Do I need this? Should I just start up the game on init, since I'll have to have a map open in order to modify it?
  this.start = function()
  {
    var files = GetFileList("maps/"+GAME.levelset+"/");
    var mapexists = false;
    for (i in files) { if (files[i] == this.mapfile.split("/")[1]) { mapexists = true; break; } } //Check if map file exists
    
    if (mapexists)
    {
			MUSIC.play(this.music, true);
			SetRenderScript("LEVEL.renderHUD();");
			ENEMIES = [];
			MapEngine(this.mapfile, FPS);
		}
  }


  /** Constructor of this object, sort of. **/
  this.init = function()
  {
    PLAYER.resetMovement();
    PLAYER.weaponsthrown = 0;
    LEVEL = this;  //Just to be sure this can be referenced everywhere.
    SetUpdateScript("LEVEL.update();");
    SetDefaultMapScript(SCRIPT_ON_ENTER_MAP, "LEVEL.enterMapScript();");

    /** Debugging/cheat keys! */
    //BindKey(KEY_D, "PLAYER.die();", "");
    BindKey(KEY_F7, "LEVEL.win();", "");
    BindKey(KEY_ESCAPE, "", ""); //Prevent default behavior
    BindKey(KEY_R, "ChangeMap('" + this.mapfile + "');", "");  //Restart level
  }
  
  this.enterMapScript = function()
  {
		CAMERA = new Camera();
		CAMERA.fixToPoint(Math.floor(GetPersonX("player")/SW)*SW+SW/2, Math.floor(GetPersonY("player")/SH)*SH+SH/2);
		CAMERA.bind(PLAYER.name);
		GAME.mapStart();
		
		this.width = GetLayerWidth(0)*GetTileWidth();
		this.height = GetLayerHeight(0)*GetTileHeight();
		
		//Count all the cupcakes.
		this.totalcupcakes = 0;
		var ppl = GetPersonList();
		for (i in ppl)
			if (ppl[i].substring(0, 7) == "cupcake") this.totalcupcakes++;
  }


  /** Update all game logic (as an UpdateScript). **/
  this.update = function()
  {
    this.timer++;

    //Control buttons
    if (INPUT.pressed("menu"))
      this.pauseMenu();

    if (this.powerupmusic.isPlaying()) this.music.setVolume(0);
    else if (!this.powerupmusic.isPlaying()) this.music.setVolume(255);

    //Update the rest now.
    PLAYER.update();
    CAMERA.update();

    for (var i = 0; i < ENEMIES.length; i++)
      ENEMIES[i].update();
    
    for (var i = 0; i < this.projectiles.length; i++)
      this.projectiles[i].update();
  }


  res.load("hearts.rss");
  var time = 0;
  var textbox = new TextBox(this.helptext);
  var hts;  //For hearts renderer.
  /** Render the HUD (things like player score and health). **/
  this.renderHUD = function()
  {
    time = Math.floor(this.timer*10/FPS)/10;

    //Ugh, lol.
    this.font.setColorMask(COLORS.BLACK);
    this.font.drawText(7, 10, "SCORE: " + PLAYER.score);
    this.font.drawText(7, 20, "TIME: " + time);
    this.font.drawText(5, 10, "SCORE: " + PLAYER.score);
    this.font.drawText(5, 20, "TIME: " + time);
    this.font.drawText(6, 9, "SCORE: " + PLAYER.score);
    this.font.drawText(6, 19, "TIME: " + time);
    this.font.drawText(6, 11, "SCORE: " + PLAYER.score);
    this.font.drawText(6, 21, "TIME: " + time);

    this.font.setColorMask(COLORS.WHITE);
    this.font.drawText(6, 10, "SCORE: " + PLAYER.score);
    this.font.drawText(6, 20, "TIME: " + time);

    //Draw one full heart per 2 health and a health-dependent heart as the last.
    for (hts = 0; hts < (PLAYER.health-2)/2; hts++)
      res.items["hearts.rss"].images[0].blit(6+hts*14, 32);
    
    res.items["hearts.rss"].images[-(PLAYER.health-hts*2)+2].blit(6+hts*14, 32);
    
    
    if (!this.paused) {}
			//Do something here if required.

    if (this.helptext != "")
      textbox.render();

    if (this.helptext != textbox.text)
      textbox = new TextBox(this.helptext);
  }


  /** The pause menu. **/
  this.pauseMenu = function()
  {
    SetUpdateScript("");
    SetFrameRate(FPS);

    this.paused = true;
    var sel = 0;
    var pausetext = new BouncyText(this.font, "GAME PAUSED");
    var grav = 1;
    var gravdir = -1;
    
    while (INPUT.pressed("cancel")) UpdateMapEngine();

    while(!INPUT.pressed("cancel"))
    {
      UpdateMapEngine();
      RenderMap();
      Rectangle(0, 0, SW, SH, COLORS.TRANSPARENT_BLACK);

      grav += (gravdir*0.001);
      if (grav < 0.2) gravdir = 1;
      else if (grav > 1) gravdir = -1;
      pausetext.setGravity(grav);

      this.font.setColorMask(COLORS.GREY);
      pausetext.update();
      pausetext.draw(115+(Math.sin(grav*100)*5), 60);

      this.font.setColorMask(COLORS.YELLOW);
      this.font.drawText(120, 102+sel*15, "*");

      this.font.setColorMask(COLORS.WHITE);
      this.font.drawText(130, 100, "Continue");
      this.font.drawText(130, 115, "Quit");
      FlipScreen();

      //Input.
      if (INPUT.pressed("up") && sel)
      {
        res.load("coin02.ogg").stop();
        if (GAME.sound) res.load("coin02.ogg").play(false);
        sel = 0;
      }
      else if (INPUT.pressed("down") && !sel)
      {
        res.load("coin02.ogg").stop();
        if (GAME.sound) res.load("coin02.ogg").play(false);
        sel = 1;
      }
      else if (INPUT.pressed("confirm") && !sel) //Continue
      {
        if (GAME.sound) res.load("coin01.ogg").play(false);
        break;
      }
      else if (INPUT.pressed("confirm") && sel) //Save & Quit
      {
				GAME.end();
				break;
      }
    }
    
    while (INPUT.pressed("cancel")) UpdateMapEngine();

    this.paused = false;
    SetFrameRate(0);
    SetUpdateScript("LEVEL.update();");
  }


  /** Death screen. **/
  this.dieScreen = function()
  {
    SetUpdateScript("");
    SetFrameRate(FPS);
    this.paused = true;

    var bouncytext = new BouncyText(this.font, "YOU DIED");
    
    //Random death messages
    var messages = ["Let's try that again, shall we?",
                    "No, no, no. Aim for the cupcakes, not the death traps!",
                    "I know you can do better than that.",
                    "Keep going. For the cupcakes!",
                    "Come on man. You can do much better.",
                    "Keep going. You'll get there eventually.",
                    "Bummer. Try again.",
                    "Keep trying. I'm rooting for you.",
                    "One of these days, you are going to make that jump. And then I will be proud of you.",
                    "No, you dolt. That's not what you're supposed to do.",
                    "Try not dying in exactly the same way next time.",
                    "Oh, come on. You found " + this.gotcupcakes + " cupcakes in this level already. Don't die now!",
                    "You have died. It's as simple as that.\n\n:(",
                    "Maybe it's time for a little break?",
                    "Concentrate. Focus. Don't let it get to you. Take a deep breath. Then, try again.",
                    "Keep going man I really need those cupcakes aarghllblgbl",
                    "Snake? SNAAAA-- okay that joke is overused.",
                    "Tough, ain't it?",
                    "You need to get better at this.",
                    "I hope you didn't die at the easy part.",
                    "Ouch. There he goes.",
                    "Well, that didn't work.",
                    "Well! Let's try not doing that again, shall we?",
                    "Dude, careful! You've only got an infinite amount of lives...\n\n...Oh, wait.",
                    "Maybe you should sleep a (k)night over it.",
                    "BOOM. " + PLAYER.deaths + " deaths, baby.",
                    "That's " + PLAYER.deaths + " deaths now. That's right, I'm keeping track.",
                    (PLAYER.deaths < 100)
                      ? "Well, at least you haven't died over a hundred times."
                      : "Wow. You've died over a hundred times. " + PLAYER.deaths + ", to be exact.",
                    "You died this much: " + PLAYER.deaths + ".",
                    "What. No. That was totally unfair. Don't you think so, either?",
                    "Tough luck!",
                    "Show me that you can do this.",
                    "Yes, hmm. It seems that doing what you just did apparently kills you. Oh well, back to the drawing board.",
                    (GAME.currentlevel <= GAME.levels.length/5)
                      ? "Dying pretty early, aren't we? You've finished only " + Math.floor((GAME.currentlevel/GAME.levels.length)*100) + "% of all the levels..."
                      : (GAME.currentlevel) + " out of " + GAME.levels.length + " levels done. Are things getting tough for you?",
                    "Alright, let's go train some muscle memory here. Or don't make silly mistakes. Whichever's appropriate.",
                    "Hey there everyone! This is Sir Boingers, and welcome to another episode of failure!",
                    "I am 23 and what are you doing?!",
                    "RIP\nHere lies Sir Boingers. He tried to jump into something deadly.",
                    "You know, while you're stumbling around here the cupcake thief is making his exit! Good job.",
                    "Well done.",
                    "You are fantastic.",
                    "Go, you.",
                    "Some fine words of encouragement is what you need!\n\nCome on, you. You can do it. And stuff.",
                    "Almost there. I think. Maybe.",
                    "Don't forget: get all those cupcakes if you want a high score! Or just a good challenge.",
                    "TIP: you can hold and release the jump button slightly to make a medium height jump.",
                    "TIP: if you hold jump while falling from a higher up point, you'll keep making super high jumps afterwards. Use this to your advantage.",
                    "TIP: look around for secret areas, even where you'd expect death. Not necessarily in this level, though."
                   ];
    var random = Math.floor(Math.random()*messages.length);
    var deathmessage = messages[random];
    var messagex = Math.floor((SW-this.font.getStringWidth(deathmessage))/2);
    if (messagex < 0) messagex = 32;

    while(!INPUT.pressed("confirm"))
    {
      UpdateMapEngine();
      RenderMap();
      Rectangle(0, 0, SW, SH, COLORS.TRANSPARENT_BLACK);  //Make this transparent red maybe?

      this.font.setColorMask(COLORS.RED);
      bouncytext.update();
      bouncytext.draw(120, 60);
      
      this.font.setColorMask(COLORS.GREY);
      this.font.drawTextBox(messagex, 80, SW-messagex, 80, 0, deathmessage);

      this.font.setColorMask(COLORS.WHITE);
      this.font.drawText(40, 160, "Press space to try again.");

      FlipScreen();
    }

    SetFrameRate(0);
    this.restartAfterDie();
  }
  
  /** Died, restart the level. **/
  this.restartAfterDie = function()
  {
		SetUpdateScript("LEVEL.update();");
		
		PLAYER.score -= this.gotcupcakes;
		this.gotcupcakes = 0;
    this.paused = false;
    MUSIC.play(this.music, true);
    ChangeMap(this.mapfile);
  }


  /** Level won. **/
  this.win = function()
  {
    SetUpdateScript("");
    SetFrameRate(FPS);
    this.paused = true;

    MUSIC.play("music/wintune.ogg", false);
    
    //Calculate endtime and total time
    var endtime = Math.floor(this.timer*10/FPS)/10;
    PLAYER.endtimes.push(endtime);
    var totaltime = 0;
    for (i in PLAYER.endtimes) totaltime += PLAYER.endtimes[i];
    totaltime = Math.floor(totaltime*10)/10;
    
    var strings = [
      "LEVEL FINISHED",
      "Cupcakes obtained: " + this.gotcupcakes + " / " + this.totalcupcakes,
      "Total cupcakes obtained: " + PLAYER.score,
      "Level time: " + endtime + " sec",
      "Total time: " + totaltime + " sec",
      "Press button 1 (space)!"];

    var stringsx = [];
    for (var i = 0; i < strings.length; i++) //Center text
      stringsx[i] = (SW - this.font.getStringWidth(strings[i])) / 2;

    var bouncytext = new BouncyText(this.font, strings[0]);
    var clr = CreateColor(0, 0, 0, 200);
    
    //Display win screen, all formatted nicely.
    while (!INPUT.pressed("confirm"))
    {
      UpdateMapEngine();
      RenderMap();
      Rectangle(0, 0, SW, SH, clr);
      var y = 40;
      var s = 0;
      
      this.font.setColorMask(COLORS.GREEN);
      bouncytext.update();
      bouncytext.draw(stringsx[0], y);
      this.font.setColorMask(COLORS.WHITE);
      this.font.drawText(stringsx[1], y+=45, strings[1]);
      this.font.setColorMask(COLORS.YELLOW);
      this.font.drawText(stringsx[2], y+=15, strings[2]);
      this.font.setColorMask(COLORS.WHITE);
      
      this.font.setColorMask(COLORS.WHITE);
      this.font.drawText(stringsx[3], y+=30, strings[3]);
      this.font.setColorMask(COLORS.YELLOW);
      this.font.drawText(stringsx[4], y+=15, strings[4]);
      
      this.font.setColorMask(COLORS.GREY);
      this.font.drawText(stringsx[5], y+=45, strings[5]);

      FlipScreen();
    }

    res.load("music/wintune.ogg").stop();
    this.end();
    SetFrameRate(0);
  }


  /** Level end, no matter the circumstances. **/
  this.end = function()
  {
    this.music.stop();
    this.ended = true;
    if (IsMapEngineRunning()) ExitMapEngine();
  }

  this.init();  //Auto-invoked as a constructor of sorts
}






/** Camera system.
  * The camera moves on a screen-by-screen basis and the action pauses when
  * going from screen to screen, much like Mega man.
**/
function Camera()
{
	this.previousPerson = undefined;		//Previous person it was bound to
	this.currentPerson = undefined;			//Current person it is bound to
	this.bound = false;		//Bound to a person or not?
	this.x = 1;
	this.y = 1;
	this.destX = 0;		//Destination X
	this.destY = 0;		//Destination Y
	this.speed = 12;	//Set the camera speed. Lower is faster.
	this.speedX = 0;	//Speed of movement per frame
	this.speedY = 0;
	
	/** Unbind the camera from any person it is bound to. */
	this.unbind = function()
	{
		this.previousPerson = this.currentPerson;
		this.currentPerson = undefined;
		this.bound = false;
	}
	
	/** Bind the camera to a person. */
	this.bind = function(person)
	{
		this.previousPerson = this.currentPerson;
		this.currentPerson = person;
		this.bound = true;
	}
	
	/** Update the camera position. */
	this.update = function()
	{
		//Only update to the player's position if the camera is bound to a person.
		if (this.bound)
		{
			//this.destX = GetPersonX(this.currentPerson)+(PLAYER.xdir*64);
			this.destX = Math.floor(GetPersonX(this.currentPerson)/SW)*SW+SW/2;
			this.destY = Math.floor((GetPersonY(this.currentPerson)+16)/SH)*SH+SH/2;
		}
		
		//Level borders (not needed with current per-screen transition system)
		/*if (this.destX < SW/2) this.destX=SW/2;
		else if (this.destX > LEVEL.width-SW/2) this.destX = LEVEL.width-SW/2;
		if (this.destY < SH/2) this.destY=SH/2;
		else if (this.destY > LEVEL.height-SH/2) this.destY = LEVEL.height-SH/2;*/
		
		this.speedX = (this.destX-this.x) / this.speed;
		this.x += this.speedX;
		
		this.speedY = (this.destY-this.y) / this.speed;
		this.y += this.speedY;
		
		SetCameraX(this.x);
		SetCameraY(this.y);
	}
	
	/** Set a destination point on the map to go to. When the third parameter is true it'll use tile coordinates instead. */
	this.setDestination = function(x, y, gotoTile)
	{
		if (gotoTile) x*=16, y*=16;
		this.destX = x;
		this.destY = y;
	}
	
	/** Immediately position the camera at a specified point (or tile position). */
	this.fixToPoint = function(x, y, gotoTile)
	{
		if (gotoTile === true) x*=16, y*=16;
		this.x = x;
		this.y = y;
		this.destX = x;
		this.destY = y;
		this.speedX = 0;
		this.speedY = 0;
	}
	
	/** Check if the camera is in a specified zone. */
	this.isInZone = function(x1, y1, x2, y2)
	{
		return (camera.x >= x1 && camera.x <= x2 && camera.y >= y1 && camera.y <=y2);
	}
}






/** Path items are persons that move in a fixed path and don't have any sort of AI of their own.
  * HOW TO USE
  * 1. Add a new person to your map.
  * 2. Add something like this to its On Create code:
  *   new PathItem(GetCurrentPerson(), "platform", [["south", 100], ["north", 100]]);
  * 3. You're done.
  *
  * @param name: person name to associate with. Also takes starting x and y from here.
  * @param type: type of item; eg. enemy or platform.
  * @param path: array of movement instructions in the form of ["north", 100] or ["west", 20].
  **/
function PathItem(name, type, path)
{
  this.name = name;
  this.type = type ? type : "enemy";
  this.active = true;  //Camera system should make it active once on-screen.
  this.x = GetPersonX(this.name);
  this.y = GetPersonY(this.name);
  this.path = path;

  this.cur_path_item = 0;  //Current item in the path to check for instructions.
  this.cur_item_prog = 0;  //Progress in the current path item.

  this.init = function()
  {
    SetPersonValue(this.name, "new", true);
    SetPersonValue(this.name, "type", this.type);
    ENEMIES.push(this);
  }

  this.update = function()
  {
    if (GetPersonValue(this.name, "new")) SetPersonValue(this.name, "new", false);
    if (!this.active || this.path.length == 0) return;

    this.cur_item_prog++;
    if (this.cur_item_prog > this.path[this.cur_path_item][1])
    {
      this.cur_item_prog = 0;
      this.cur_path_item++;
    }

    if (this.cur_path_item >= this.path.length)
      this.cur_path_item = 0;

    switch(this.path[this.cur_path_item][0])
    {
      case "north":
        this.y--;
        break;
      case "south":
        this.y++;
        break;
      case "west":
        this.x--;
        break;
      case "east":
        this.x++;
        break;
    }
    SetPersonX(this.name, this.x);
    SetPersonY(this.name, this.y);
  }
  this.init();
}

