(function game() {
  var screen = document.getElementById('screen');
  var unitlist = new LinkedList();
  var levelarray = [
  [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
  [1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1],
  [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1],
  [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
  ];
  var blockarray = [[],[],[],[],[],[],[],[],[],[],[],[]];
  
  var time = new Date().getTime();
  var lastupdatetime = new Date().getTime();
  var lastrendertime = new Date().getTime();
  var lastframetime = new Date().getTime();
  var updatedelay = 1000/50;
  var renderdelay = 1000/60;
  var framecount = 0;
  var timer = setInterval(cycle,0);
  
  var GRAVITY = 0.7;

  var player;

  function cycle() {
    lastcycletime = time;
    time = new Date().getTime(); //We use a static decided time per game cycle, instead of getting the time over and over each time we need it
    var cycleupdate = true; //We use this variable to determine if this cycle has the update function run. If not, then no point in updating the grapics
    while(time > lastupdatetime+updatedelay) {lastupdatetime+=updatedelay; cycleupdate=true; lastupdatetime = time; update();} //We run the update function many times if we are slowing after desired update rate
    if (cycleupdate && time > lastrendertime+renderdelay) {framecount++; lastrendertime = time; render();} //We don't run the grapic function many times. That's a waste, instead, we run it max once per game cycle, if at all
  }
  function update() {
    var foreach = unitlist.first; while(foreach) { //We loop though each unit
      foreach.value.update(); //Call upon the update function for the unit. The update function do things like move the unit according to it's speed and test for collisions
    foreach = foreach.next;}
  }
  function render() {
    if (time > lastframetime + 1000) {
      //document.title = framecount;
      lastframetime = time;
      framecount = 0;
    }
    var foreach = unitlist.first; while(foreach) { //We loop though each unit
      foreach.value.draw(); //Call upon the drawing function for the unit
    foreach = foreach.next;}
  }
  function drawworld() { //This function just loops though the map and draws it.
    for(var x = 0; x < levelarray.length; x++) {
      for(var y = 0; y < levelarray[x].length; y++) {
        if (levelarray[x][y] == 1) {
          blockarray[x][y] = new Block(y,x);
	}
      }
    }
  }
  function Block(x,y) { //This is a quick hack object for the tiles
    var element = new Image();
    element.src = 'block.png';
    element.style.position = 'absolute';
    element.style.left = x * 32;
    element.style.top = y * 32;
    screen.appendChild(element);
  }
  function activateunit(unit) { //Activating a unit means it gets added to the unitlist which makes it have game physics, and appends it's element to the html document
    unitlist.add(unit); //Native function to the LinkedList
    screen.appendChild(unit.element);
    unit.draw();//We force a draw for this unit to avoid glitching as it appears
  }
  function deactivateunit(unit) { //The reverse opposite of the activateunit functino
    unitlist.removebyobject(unit);
    screen.removeChild(unit.element);
  }
  function AI(data,unit) { //The big trick here will be that one AI is the player AI. It controls the unit depending on keypresses.
    this.unit = unit;
    this.state;
    this.tick = data.tick;
  }
  function UnitBlueprint(data) { //We use blueprints for units to make mass producing of units easier
    this.sprite = data.sprite;
    this.advancedcollision = data.advancedcollision;
    this.xblocksize = data.xblocksize;
    this.yblocksize = data.yblocksize;
    this.ai = data.ai;
  }
  function Unit(blueprint,data) { //The blueprint is used to gain genetic data for the unit, like size. The data is used for dynamic data, like position.
    this.element = new Image();
    this.element.style.position = 'absolute';
    this.sprite = blueprint.sprite;
    this.advancedcollision = blueprint.advancedcollision;
    this.xblocksize = blueprint.xblocksize;
    this.yblocksize = blueprint.yblocksize;
    this.x = data.x;
    this.y = data.y;
    this.h = this.yblocksize*32;
    this.w = this.xblocksize*32;
    this.xspeed = 0;
    this.yspeed = 0;
    this.ai = new AI(blueprint.ai,this);
    this.abilities = blueprint.abilities;
    this.animation = 'stand';
    this.direction = 0;
    this.frame = 0;
    this.delaytimestamp = time;
    this.cx;this.cy;this.ch;this.cw;this.canimation;this.cdirection;this.cframe;//Duplicated numbers of the of the element to avoid unecessary DOM changes
    this.touchfloor;this.touchroof;this.touchright;this.touchleft; //We use these in the unit.update function to remember if the unit is touching something. But it can also be used in the AI
  }
  Unit.prototype.update = function() {
    if (this.advancedcollision) { //advancedcollision is a system where we check very carefully collisions from all four directions, for both walls and units. This allows good platforming, but is hardly neccessary for simple missiles.
      this.touchfloor = this.touchroof = this.touchright = this.touchleft = false; //We start out with the assumption that the unit is not touching anything, and let the code below prove us wrong.
      /* We do vertical and horizontal movement separate as two steps to avoid one really nasty bug where the unit skips into a wall when they are in a corner */
      var x1,x2,x3,x4,y1,y2,y3,y4, i; //since we need to check a lot of directions, we simply reuse these local variables which gives us the possibility to reuse results
      // >>5 is the same as / 32
    
      /* we apply the vertical movement and collision checks with the solid world*/
      this.y+=this.yspeed;
      x1 = (this.x+6)>>5; //We with a bias of 6 pixels to avoid having a character standing on the very last pixel of an edge
      x2 = (this.x+30)>>5; //We test the collision of each block twice to avoid making the block as thin as a nedlee when it comes to collisions
      y1 = (this.y+this.h)>>5; //We take the y position of the unit, add the height of the unit, to get the position of the unit's feet
      i = this.xblocksize-1; //If the unit is more than 1 block thick in width, then we need to check the collision possibilities for each block
      do {
        if(levelarray[y1][x1+i] || levelarray[y1][x2+i]) { //Again, we test the collision of each block of the unit twice. Otherwise, the percived collision size of a block is no thicker than a needle
          this.touchfloor = true; //The unit touched the floor! Remember this!
        }
      } while (i--) //This is just a fancy replacement for a FOR loop, that's slightly faster
      if (this.touchfloor) { //This unit is touching the floor, so we need to stop it from falling
        this.y = (y1-this.yblocksize)<<5; //Doing the whole >>5 <<5 thing snaps the position to a 32 pixel wide grind, making the collision result seem very smooth and not bumpy
        this.yspeed = 0; //If the unit is touching the floor, it obviously loses it's falling speed
      }
      
      //We are lucky! the x cordinates are identical to when testing the floor and the roof, so we don't need to redo them here.
      y2 = ((this.y)>>5); //Since we need to check for the top of the unit crashing with the roof rather than the feet, we need to recalculate the y value
      i = this.xblocksize-1; //Again, to support units thicker than 1 block in width
      do {
        if(levelarray[y2][x1+i] || levelarray[y2][x2+i]) { //Again, we test the collision of each block of the unit twice. Otherwise, the percived collision size of a block is no thicker than a needle
          this.touchroof = true;  //The unit touched the roof! Remember this!
        }
      } while (i--)
      if (this.touchroof) { //The unit touches the roof, so we need to stop it from going upwards
        this.y = (y2+1)<<5; //Again, we reverse the >>5 we did earlier with a <<5 to snap the unit perfectly to a 32 pixel grind, making the results of a collision very smooth and non-bumpy
        this.yspeed = 0; //Obviously, stop the unit that crashes into the roof.
      }

      /* We apply the horizontal movement and collision checks */
      this.x+=this.xspeed;
      x3 = (this.x+this.w)>>5;
      y3 = (this.y+6)>>5;
      y4 = (this.y+30)>>5;
      i = this.yblocksize-1;
      do {
        if(levelarray[y3+i][x3] || levelarray[y4+i][x3]) { //we test on two points for each block. This is to avoid making blocks point of touching as thin as a needle
          this.touchright = true; //The unit touched the right wall! Remember this!
        }
      } while (i--)
      if (this.touchright) {
        this.x = (x3-this.xblocksize)<<5;
        this.xspeed = 0;
      }
      
      x4 = (this.x)>>5;
      //The y values are the same as the left collision! no need to recalculate them
      i = this.yblocksize-1;
      do {
        if(levelarray[y3+i][x4] || levelarray[y4+i][x4]) { //we test on two points for each block. This is to avoid making blocks point of touching as thin as a needle
          this.touchleft = true; //The unit touched the right wall! Remember this!
        }
      } while (i--)
      if (this.touchleft) { 
        this.x = (x4+1)<<5;
        this.xspeed = 0;
      }
      
      /* time to do the unit collisions */
      var foreach = unitlist.first; while(foreach) { //We loop though each unit, but I need to make this into a shifted grind system to only check for units that are near. protip: remember to use bit shift sorting
        if (this == foreach.value) { foreach = foreach.next; continue; } //We don't want units colliding with themselves

        //For standing on top of another unit
        if (this.x+this.w>foreach.value.x+6 && this.x<foreach.value.x+foreach.value.w-6) { //Again, we have a 6 pixel bias for better gameplay and to avoid a ton of bugs
          if (this.y+this.h>foreach.value.y && this.y+this.h<foreach.value.y+foreach.value.h) {
            this.y = foreach.value.y-this.h;
            this.yspeed = 0;
            this.touchfloor = true; //The unit is standing on top of an unit as if it was a floor, remember this!
          } 
        }
	
        //For jumping into the bottom of another unit
        if (this.x+this.w>foreach.value.x+6 && this.x<foreach.value.x+foreach.value.w-6) { //6 pixel bias again
          if (this.y<foreach.value.y+foreach.value.h && this.y+this.h>foreach.value.y+foreach.value.h) {
            this.y = foreach.value.y+foreach.value.h;
            this.yspeed = 0;
            this.touchroof = true; //The unit is crashing into an unit as if it was a roof, remember this!
          } 
        }
	
        //For colliding into the right side of an unit
        if (this.x+this.w>foreach.value.x && this.x+this.w<foreach.value.x+10) {
          if (this.y+this.h>foreach.value.y && this.y<foreach.value.y+foreach.value.h) {
            this.x = foreach.value.x-foreach.value.w;
            this.xspeed = 0;
            this.touchright = true; //The unit is touch into an unit as if it was a right wall, remember this!
          } 
        }
	
        //For colliding into the left side of an unit
        if (this.x < foreach.value.x+foreach.value.w && this.x  > foreach.value.x+foreach.value.w-10) {
          if (this.y+this.h>foreach.value.y && this.y<foreach.value.y+foreach.value.h) {
            this.x = foreach.value.x+foreach.value.w;
            this.xspeed = 0;
	    this.touchleft = true; //The unit is touch into an unit as if it was a left wall, remember this!
          } 
        }
      foreach = foreach.next;}
      
      if (this.touchfloor) { //If the unit is touching the ground, then his X speed goes down, due to friction
        if(this.xspeed>0) {this.xspeed--;} 
        if(this.xspeed<0) {this.xspeed++;}
      } else { //If the unit isn't touching the ground, apply gravity
        this.yspeed+=GRAVITY; //Unit isn't touching the ground, apply gravity
      }
      
   } else { //Simple collision
      var foreach = unitlist.first; while(foreach) { //We loop though each unit, but I need to make this into a shifted grind system to only check for units that are near. protip: remember to use bit shift sorting
      
      foreach = foreach.next;}
    }
    
    /* We run the AI's tick function */
    if (this.ai.tick) {this.ai.tick()};
  }
  Unit.prototype.draw = function() {//We cache all values for the unit. If the value is not changed, then we won't touch the DOM since it's really slow. If the values are changed, when we fix the new DOM stuff, and reset the cache value
    if (this.y != this.cy) {this.element.style.top = this.cy = this.y;}  //If the Y position is diffrent from before, then update the element's Y position
    if (this.h != this.ch) {this.element.style.height = this.ch = this.h;} //Same here

    if (this.animation != this.canimation || this.direction != this.cdirection || this.w != this.cw) { //Because direction, animation and width all works by manipulating some of the same values, we mash em together here. You cannot change one without influencing the other.
      this.element.src = this.sprite[this.animation].image[this.direction].src;
      this.canimation =  this.animation;
      this.cdirection =  this.direction;
      this.element.style.width = this.w*this.sprite[this.animation].length; //It's simple. We take the width of the unit and multiply it with the length (number of frames in the animation).
      this.cw = this.w;
    }

    if (this.sprite[this.animation].length > 1) {
    while(this.delaytimestamp+this.sprite[this.animation].delay < time) { //We find what frame that is supposed to be shown right now. We do this as a while loop because there might have been more than one frame changed since last render
      this.delaytimestamp+=this.sprite[this.animation].delay; //We add the delay to the timestamp, this is for the next time we need to test if the current frame is on time, in this render or the next
      this.frame++; //Advance one frame, obviously
      if (this.frame >= this.sprite[this.animation].length) { this.frame = 0; }
    }
    } else {
      this.frame = 0;
    }

    //We do X diffrently from the height and Y because our animation technique which combines CSS sprites and CSS clip.
    if (this.frame != this.cframe || this.x != this.cx) {//We use CSS clip (google it) to show only the relevant frame. This is a build on the CSS sprite (google it) technique.
      this.element.style.clip = 'rect(0px, '+this.w*(this.frame+1)+'px, '+this.h+'px, '+this.w*this.frame+'px)'; //We clip away all the frames but the one we want to show
      this.element.style.left = this.x-(this.w*(this.frame)); //We set the X position, but correct it by a factor of what frame we are showing. this is because the clipping screws up the position of the unit.
      this.cx = this.x;
      this.cframe = this.frame;
    }
    

  }
  /* LinkedList is like an diffrent structured Array. It allows you to store a lot of information, and remove such information without creating "holes" in the list */
  function LinkedList() {
    this.first = null;
    this.last = null;
    this.length = 0;
  }
  LinkedList.prototype.Link = function(object) {
    this.previous;
    this.next;
    this.value = object;
  }
  LinkedList.prototype.add = function(object) {
    if (!this.first) {
      this.first = new this.Link(object);
      this.last = this.first;
    } else {
      var oldlast = this.last;
      this.last = new this.Link(object);
      oldlast.next = this.last;
      this.last.previous = oldlast;
    }
    this.length++;
  }
  LinkedList.prototype.removebynode = function(node) {
    if (node == this.first && node == this.last) {
      this.first = null;
      this.last = null;
    } else if (node != this.first && node != this.last) {
      node.previous.next = node.next;
      node.next.previous = node.previous;
    } else {
      if (node == this.first) {
        this.first = this.first.next;
      } else {
        this.last = this.last.previous;
      }
    }
    this.length--;
  }
  LinkedList.prototype.removebyobject = function(object) {
    var foreach = this.first; while(foreach) {
      if (foreach.value == object) {
        this.removebynode(foreach);
	break;
      }
    foreach = foreach.next;}
  }
  LinkedList.prototype.exist = function() {
    return (!!this.first);
  }
  LinkedList.prototype.getlast = function() {
    return this.last.value;
  }
  LinkedList.prototype.getfirst = function() {
    return this.first.value;
  }
  LinkedList.prototype.destory = function() {
    var foreach = this.first; while(foreach) {
      var temp = foreach.next;
      delete foreach.next;
      delete foreach.previous;
    foreach = temp.next;}
    delete this.first;
    delete this.last;
    this.length = 0;
  }

  function keydown(e) {
    var keyCode = (e||event).keyCode;
    switch(keyCode) {
    case 39:
      key.right = true;
    break;
    case 37:
      key.left = true;
    break;
    case 38:
      key.up = true;
    break;
    case 40:
      key.down = true;
    break;    
    }
  }

  function keyup(e) {
    var keyCode = (e||event).keyCode;
    switch(keyCode) {
    case 39:
      key.right = false;
    break;
    case 37:
      key.left = false;
    break;
    case 38:
      key.up = false;
    break;
    case 40:
      key.down = false;
    break;    
    }
  }



  var key = {
    left: false,
    right: false,
    up: false,
    down: false
  }

  document.onkeydown = keydown; //Register the keypresses
  document.onkeyup = keyup;
  drawworld(); //We run this once for now, but if scrolling is going to be possible, this needs to be more dynamic
  Cache = {};
  function addsprite(name,data) {
    Cache[name] = {};
    var object = Cache[name]
    for (x in data)  {
      object[x] = {};
      object[x].image = [];
      object[x].image[0]  = new Image();
      object[x].image[1]  = new Image();
      object[x].image[0].src = data[x].url[0];
      object[x].image[1].src = data[x].url[1];
      object[x].length = data[x].length;
      object[x].delay = data[x].delay;
    }

  }

  addsprite('link',{
    stand: {
      url: ['link_stand_right.png','link_stand_left.png'],
      length: 1,
      delay: 0
    },
    walk: {
      url: ['link_walk_right.png','link_walk_left.png'],
      length: 4,
      delay: 80
    },
    jump: {
      url: ['link_jump_right.png','link_jump_left.png'],
      length: 1,
      delay: 0
    }
  });

  addsprite('skeleton',{
    stand: {
      url: ['skeleton_stand_right.png','skeleton_stand_left.png'],
      length: 1,
      delay: 0
    },
    walk: {
      url: ['skeleton_walk_right.png','skeleton_walk_left.png'],
      length: 2,
      delay: 180
    }
  });
  addsprite('octorok',{
    stand: {
      url: ['octorok_stand_right.png','octorok_stand_left.png'],
      length: 2,
      delay: 200
    }
  });

  var skeletonblueprint = new UnitBlueprint({
    sprite: Cache.skeleton,
    advancedcollision: true,
    xblocksize: 1,
    yblocksize: 2,
    ai:{
      tick: function() {
        if (this.unit.touchright) {
	  this.unit.direction = 1;
	} else if(this.unit.touchleft) {
	  this.unit.direction = 0;
	}
	
	if (this.unit.direction == 0) {
          this.unit.xspeed = 2;
	  this.unit.animation = 'walk';
	} else {
          this.unit.xspeed = -2;
	  this.unit.animation = 'walk';
	}

      }
    }
  })
  var octorokblueprint = new UnitBlueprint({
    sprite: Cache.octorok,
    advancedcollision: true,
    xblocksize: 1,
    yblocksize: 1,
    ai:{
      tick: function() {
        if (this.unit.touchfloor) {
	  this.unit.yspeed = -10;
	}
      }
    }
  })
  var linkblueprint = new UnitBlueprint({
    sprite: Cache.link,
    xblocksize: 1,
    yblocksize: 2,
    advancedcollision: true,
    abilities: {
    
    },
    ai:{
      tick: function() {
        if (player.xspeed == 0 && player.touchfloor) {player.animation = 'stand';}
        if (key.left) {
          if (player.xspeed > -4) {
            player.xspeed-=2;
            player.direction = 1;
	   if (player.touchfloor) {player.animation = 'walk';}
          }
        }
        if (key.right) {
          if (player.xspeed < 4) {
            player.xspeed+=2;
            player.direction = 0;
	    if (player.touchfloor) {player.animation = 'walk';}
          }
        }
        if (key.up && player.touchfloor) {
          player.yspeed = -10;
	  player.animation = 'jump';
        }
        if (key.down) {
	  player.animation = 'jump';
        }
      }
    }
  })
  

  var link = new Unit(linkblueprint,{x:100,y:200});
  
  player = link;
  activateunit(link);
  var enemy = new Unit(skeletonblueprint,{x:36,y:60});
  activateunit(enemy);
  
  var enemy2 = new Unit(octorokblueprint,{x:400,y:50});
  activateunit(enemy2);
})()















