function Map() {
  
  this.width = 64;
  this.height = 64;
  
  this.gridW = 256;
  this.gridH = 256;
  
  this.tileWidth = 158;
  this.tileHeight = 80;
  this.halfTileWidth = 79;
  this.halfTileHeight = 40;
  
  this.homePort;
  
  this.piratePorts = [];
  this.pirateShips = {};
  this.pirateShipCounter = 0;
  
  this.tiles = [];
  
  this.grid = [];
  
  this.waterAnimation = 0;
  this.wavesNum = 12;
  this.waves = [];
  
  this.cannonballs = {};
  this.cannonballCounter = 0;
  
  this.paused = true;
  
  this.won = false;
  
  
  this.init = function() {
    
    for(var x = 0; x < this.width; x++) {
      this.tiles[x] = [];
      for(var y = 0; y < this.width; y++) {
        this.tiles[x][y] = 0;
      }
    }
    
    for(var x = 0; x < this.gridW; x++) {
      this.grid[x] = [];
      for(var y = 0; y < this.gridH; y++) {
        this.grid[x][y] = 0;
      }
    }
    
    for(var i = 0; i < this.wavesNum; i++) {
      this.waves[i] = new ParticleSystem();
      this.waves[i].setType(2);
      this.waves[i].setV(-0.1, 0.1, -0.05, 0.05);
      this.waves[i].setLife(15, 20);
    }

    this.generate();
  };
  
  
  this.generate = function() {
    
    var portPos = this.createIsland(6, 32, 32);
    this.homePort = new Port();
    this.homePort.init(-1, true, portPos.x, portPos.y, 0);
    
    var piratePortPositions = [20, 8, 50, 10, 54, 46, 28, 54, 10, 38];
    
    for(var i = 0; i < 5; i++) {
      var piratePortX = piratePortPositions[i * 2];
      var piratePortY = piratePortPositions[(i * 2) + 1];
      var portPos = this.createIsland(4, piratePortX, piratePortY);
      this.piratePorts[i] = new Port();
      this.piratePorts[i].init(i, false, portPos.x, portPos.y, i + 1);
    }
    
    for(var i = 0; i < 5; i++) {
      var randX = rand(1, this.width - 2);
      var randY = rand(1, this.height - 2);
      if(this.farEnoughFromPort(randX, randY)) {
        this.tiles[randX][randY] = 1;
      }
    }
    
    for(var i = 0; i < 8; i++) {
      var randX = rand(1, this.width - 3);
      var randY = rand(1, this.height - 3);
      if(this.farEnoughFromPort(randX, randY)) {
        this.tiles[randX][randY] = zeroOrOne(0.7);
        this.tiles[randX + 1][randY] = zeroOrOne(0.7);
        this.tiles[randX][randY + 1] = zeroOrOne(0.7);
        this.tiles[randX + 1][randY + 1] = zeroOrOne(0.7);
      }
    }
    
    for(var i = 0; i < 16; i++) {
      var randX = rand(1, this.width - 4);
      var randY = rand(1, this.height - 4);
      if(this.farEnoughFromPort(randX, randY)) {
        this.tiles[randX    ][randY    ] = zeroOrOne(0.4);
        this.tiles[randX + 1][randY    ] = zeroOrOne(0.8);
        this.tiles[randX + 2][randY    ] = zeroOrOne(0.4);
        this.tiles[randX    ][randY + 1] = zeroOrOne(0.8);
        this.tiles[randX + 1][randY + 1] = 1;
        this.tiles[randX + 2][randY + 1] = zeroOrOne(0.8);
        this.tiles[randX    ][randY + 2] = zeroOrOne(0.4);
        this.tiles[randX + 1][randY + 2] = zeroOrOne(0.8);
        this.tiles[randX + 2][randY + 2] = zeroOrOne(0.4);
      }
    }
    
    
    for(var x = 0; x < this.width; x++) {
      for(var y = 0; y < this.height; y++) {
        if(this.tiles[x][y] != 0) {
          for(var gX = 0; gX < 4; gX++) {
            for(var gY = 0; gY < 4; gY++) {
              this.grid[(4 * x) + gX][(4 * y) + gY] = 2;
            }
          }
        }
        if(this.tiles[x][y] == 1) {
          
          var topIsWater = !this.tileIsNotWater(x, y - 1);
          var rightIsWater = !this.tileIsNotWater(x + 1, y);
          var bottomIsWater = !this.tileIsNotWater(x, y + 1);
          var leftIsWater = !this.tileIsNotWater(x - 1, y);
          var topLeftIsWater = !this.tileIsNotWater(x - 1, y - 1);
          var topRightIsWater = !this.tileIsNotWater(x + 1, y - 1);
          var bottomRightIsWater = !this.tileIsNotWater(x + 1, y + 1);
          var bottomLeftIsWater = !this.tileIsNotWater(x - 1, y + 1);
          
          if(topIsWater || rightIsWater || bottomIsWater || leftIsWater) {
            var value = 0;
            if(topIsWater) {
              value += 1;
            }
            if(rightIsWater) {
              value += 2;
            }
            if(bottomIsWater) {
              value += 4;
            }
            if(leftIsWater) {
              value += 8;
            }
            
            if(value == 1 && bottomRightIsWater && bottomLeftIsWater) {
              value = 32;
            } else if(value == 1 && bottomRightIsWater) {
              value = 33;
            } else if(value == 1 && bottomLeftIsWater) {
              value = 34;
              
            } else if(value == 2 && bottomLeftIsWater && topLeftIsWater) {
              value = 35;
            } else if(value == 2 && bottomLeftIsWater) {
              value = 36;
            } else if(value == 2 && topLeftIsWater) {
              value = 37;
              
            } else if(value == 4 && topLeftIsWater && topRightIsWater) {
              value = 38;
            } else if(value == 4 && topLeftIsWater) {
              value = 39;
            } else if(value == 4 && topRightIsWater) {
              value = 40;
              
            } else if(value == 8 && topRightIsWater && bottomRightIsWater) {
              value = 41;
            } else if(value == 8 && topRightIsWater) {
              value = 42;
            } else if(value == 8 && bottomRightIsWater) {
              value = 43;
              
            } else if(value == 3 && bottomLeftIsWater) {
              value = 44;
            } else if(value == 6 && topLeftIsWater) {
              value = 45;
            } else if(value == 9 && bottomRightIsWater) {
              value = 46;
            } else if(value == 12 && topRightIsWater) {
              value = 47;
            }
            
            this.tiles[x][y] = value;
            
          } else {
            if(topLeftIsWater || topRightIsWater || bottomRightIsWater || bottomLeftIsWater) {
              var value = 16;
              if(topLeftIsWater) {
                value += 1;
              }
              if(topRightIsWater) {
                value += 2;
              }
              if(bottomRightIsWater) {
                value += 4;
              }
              if(bottomLeftIsWater) {
                value += 8;
              }
              this.tiles[x][y] = value;
            
            } else {
              this.tiles[x][y] = rand(48, 63);
            }
          }
        }
      }
    }
    
    for(var piratePort in this.piratePorts) {
      this.piratePorts[piratePort].spawnShips(3, 1);
    }
  };
  
  
  this.createIsland = function(size, islandX, islandY) {
    islandX += rand(-3, 3);
    islandY += rand(-3, 3);
    var possibleFrequencies = [5, 7, 9, 11];
    var frequency = possibleFrequencies[rand(0, 3)];
    var offset = randFloat(0.1, 2.5);
    for(var x = islandX - 10; x < islandX + 10; x++) {
      for(var y = islandY - 10; y < islandY + 10; y++) {
        var angle1 = offset + (Math.atan2(islandX - x, islandY - y) * frequency);
        var angle2 = (offset * 3) + (Math.atan2(islandX - x, islandY - y) * 3);
        var maxDist = size * (1 + (0.1 * Math.sin(angle1)) + (0.1 * Math.sin(angle2)));
        if(distance(islandX, islandY, x, y) < maxDist) {
          this.tiles[x][y] = 1;
        }
      }
    }
    var portX = islandX;
    var portY = islandY;
    while(this.tiles[portX + 1][portY + 1] != 0 && portX < this.width - 1 && portY < this.height - 1) {
      portX++;
      portY++;
    }
    for(var x = 0; x <= 3; x++) {
      for(var y = 0; y <= 3; y++) {
        if(x != 0 || y != 0) {
          this.tiles[portX + x][portY + y] = 0;
        }
      }
    }
    return { x : portX, y : portY};
  };
  
  
  this.tileIsNotWater = function(x, y) {
    if(x < 0) {
      return false;
    }
    if(x >= this.width) {
      return false;
    }
    if(y < 0) {
      return false;
    }
    if(y >= this.height) {
      return false;
    }
    return (this.tiles[x][y] != 0);
  };
  
  
  this.farEnoughFromPort = function(x, y) {
    if(distance(x, y, this.homePort.x, this.homePort.y) < 6) {
      return false;
    }
    for(var piratePort in this.piratePorts) {
      var port = this.piratePorts[piratePort];
      if(distance(x, y, port.x, port.y) < 6) {
        return false;
      }
    }
    return true;
  };
  
  
  this.update = function() {
    
    this.waterAnimation++;
    if(this.waterAnimation > 240) {
      this.waterAnimation = 0;
    }
    
    this.homePort.update();
    for(var piratePort in this.piratePorts) {
      this.piratePorts[piratePort].update();
    }
    
    for(var i in this.cannonballs) {
      if(this.cannonballs[i].dead) {
        delete this.cannonballs[i];
      } else {
        this.cannonballs[i].update();
      }
    }
    
    if(!this.paused) {
      for(var i in this.pirateShips) {
        if(this.pirateShips[i].destroyed) {
          delete this.pirateShips[i];
        } else {
          this.pirateShips[i].update();
        }
      }
    };
    
    if(!this.won) {
      var win = (Object.keys(this.pirateShips).length == 0);
      for(var piratePort in this.piratePorts) {
        win = win && this.piratePorts[piratePort].destroyed;
      }
      if(win) {
        panel.openInfoBox(3);
        this.won = true;
      }
    }
  };
  
  
  this.draw = function() {
    
    var viewportX = -viewport.sceneX;
    var viewportY = -viewport.sceneY;
    var viewportW = viewport.w;
    var viewportH = viewport.h;
    
    var startX = Math.floor(this.reverseTransform(viewportX, viewportY).x) - 1;
    var endX = Math.ceil(this.reverseTransform(viewportX + viewportW, viewportY + viewportH).x);
    var startY = Math.floor(this.reverseTransform(viewportX + viewportW, viewportY).y) - 1;
    var endY = Math.ceil(this.reverseTransform(viewportX, viewportY + viewportH).y);
    
    startX = setInRange(startX, 0, this.width - 1);
    startY = setInRange(startY, 0, this.height - 1);
    endX = setInRange(endX, 0, this.width - 1);
    endY = setInRange(endY, 0, this.height - 1);
    
    
    var waterX = -20 + viewportX - ((viewportX) % 360);
    var waterY = -30 + viewportY - ((viewportY) % 260);
    
    for(var x = 0; x < 3; x++) {
      for(var y = 0; y < 3; y++) {
        img.drawSprite("water", waterX + (x * 360), waterY + (y * 260), 360, 260, 0, Math.round((this.waterAnimation + 30) / 60) % 4);
        c.globalAlpha = 0.5 + 0.5 * Math.cos(0.1047 * this.waterAnimation);
        img.drawSprite("water", waterX + (x * 360), waterY + (y * 260), 360, 260, 0, (Math.round((this.waterAnimation) / 60) + 2) % 4);
        c.globalAlpha = 1;
      }
    }
    
    for(var i = 0; i < this.wavesNum; i++) {
      var randX = rand(viewportX - 100, viewportX + 200 + viewportW);
      var randY = rand(viewportY - 100, viewportY + 200 + viewportH);
      this.waves[i].setEmitter(randX, randY);
      this.waves[i].draw();
    }
    
    for(var x = startX; x <= endX; x++) {
      for(var y = startY; y <= endY; y++) {
        if(this.tiles[x][y] > 0) {
          var pos = this.transform(x, y);
          var tile = map.tiles[x][y];
          if(tile > 0 && tile < 64) {
            var spriteX = tile % 8;
            var spriteY = Math.floor(tile / 8);
            img.drawSprite("tiles", pos.x - 80, pos.y - 40, 160, 80, spriteX, spriteY);
          }
        }
      }
    }
    
    /*for(var x = startX * 4; x <= endX * 4; x++) {
      for(var y = startY * 4; y <= endY * 4; y++) {
        if(this.grid[x][y] > 0) {
          var pos = this.transform((x / 4) - 0.375, (y / 4) - 0.375);
          c.globalAlpha = 0.5;
          c.fillStyle = "#f00";
          c.fillRect(pos.x - 10, pos.y - 5, 20, 10);
          c.globalAlpha = 1;
        }
      }
    }*/
    
    if(this.homePort.x >= startX && this.homePort.x <= endX && this.homePort.y >= startY && this.homePort.y <= endY) {
      this.homePort.draw();
    }
    
    for(var piratePort in this.piratePorts) {
      var port = this.piratePorts[piratePort];
      if(port.x >= startX && port.x <= endX && port.y >= startY && port.y <= endY) {
        port.draw();
      }
    }
    
    for(var i in this.pirateShips) {
      this.pirateShips[i].drawWake();
    }
    
    ship.drawWake();
    
    for(var i in this.pirateShips) {
      this.pirateShips[i].draw();
    }
    
  };
  
  
  this.drawCannonballs = function() {
    for(var i in this.cannonballs) {
      this.cannonballs[i].draw();
    }
  };
  
  
  this.isFreeForFriendlyShip = function(x, y) {
    x = Math.round(4 * (x + 0.25));
    y = Math.round(4 * (y + 0.25));
    if(x < 0 || y < 0 || x >= this.gridW - 2 || y >= this.gridH - 2) {
      return true;
    }
    return this.grid[x][y] == 0 && this.grid[x][y + 1] == 0 && this.grid[x + 1][y] == 0 && this.grid[x + 1][y + 1] == 0;
  };
  
  
  this.isFreeForPirateShip = function(x, y) {
    if(x < 0 || y < 0 || x >= this.gridW - 2 || y >= this.gridH - 2) {
      return false;
    }
    return this.grid[x][y] == 0 && this.grid[x][y + 1] == 0 && this.grid[x + 1][y] == 0 && this.grid[x + 1][y + 1] == 0;
  };
  
  
  this.setPirateShipSpace = function(x, y, value) {
    this.grid[x][y] = value;
    this.grid[x][y + 1] = value;
    this.grid[x + 1][y] = value;
    this.grid[x + 1][y + 1] = value;
  };
  
  
  this.setFriendlyShipSpace = function(x, y, value) {
    x = Math.round(4 * (x + 0.25));
    y = Math.round(4 * (y + 0.25));
    if(x < 0 || y < 0 || x >= this.gridW - 1 || y >= this.gridH - 1) {
      return;
    }
    this.grid[x][y] = value;
    this.grid[x][y + 1] = value;
    this.grid[x + 1][y] = value;
    this.grid[x + 1][y + 1] = value;
  };
  
  
  this.pushCannonball = function(friendly, x, y, velocity, damage) {
    this.cannonballs[this.cannonballCounter] = new Cannonball();
    this.cannonballs[this.cannonballCounter].init(friendly, x, y, velocity, damage);
    this.cannonballCounter++;
  };
  
  
  this.pushPirateShip = function(x, y, parentPortId) {
    this.pirateShips[this.pirateShipCounter] = new PirateShip();
    this.pirateShips[this.pirateShipCounter].init(x, y, parentPortId);
    this.pirateShipCounter++;
  };

  
  this.transform = function(x, y) {
    var transformX = this.halfTileWidth * ((map.width - 1) + x - y);
    var transformY = this.halfTileHeight * (x + y);
    return { x : transformX, y : transformY };
  };
  
  
  this.reverseTransform = function(x, y) {
    y = y / this.tileHeight;
    x = x / this.tileWidth;
    x -= (map.width - 1) / 2;
    var transformX = y + x;
    var transformY = (y - x);
    return { x : transformX, y : transformY };
  };
  
  
  
}