/**********************************************************************
 * TERRAIN GENERATION
 **********************************************************************/

// Perlin noise implementation
// Based on https://github.com/josephg/noisejs
class Perlin {
  constructor() {
    this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],
                  [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],
                  [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];
    this.p = [];
    for (let i=0; i<256; i++) {
      this.p[i] = Math.floor(Math.random()*256);
    }
    
    // To remove the need for index wrapping, double the permutation table length
    this.perm = new Array(512);
    this.gradP = new Array(512);
    
    // Skipping the seed function for simplicity
    this.seed(0);
  }
  
  seed(seed) {
    if(seed > 0 && seed < 1) {
      // Scale the seed out
      seed *= 65536;
    }

    seed = Math.floor(seed);
    if(seed < 256) {
      seed |= seed << 8;
    }

    for(let i = 0; i < 256; i++) {
      let v;
      if (i & 1) {
        v = this.p[i] ^ (seed & 255);
      } else {
        v = this.p[i] ^ ((seed>>8) & 255);
      }

      this.perm[i] = this.perm[i + 256] = v;
      this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12];
    }
  }
  
  // 2D Perlin Noise
  noise(x, y) {
    // Find unit grid cell containing point
    let X = Math.floor(x), Y = Math.floor(y);
    // Get relative xy coordinates of point within that cell
    x = x - X; y = y - Y;
    // Wrap the integer cells at 255 (smaller integer period can be introduced here)
    X = X & 255; Y = Y & 255;

    // Calculate noise contributions from each of the four corners
    let n00 = this.dot2(this.gradP[X+this.perm[Y]], x, y);
    let n01 = this.dot2(this.gradP[X+this.perm[Y+1]], x, y-1);
    let n10 = this.dot2(this.gradP[X+1+this.perm[Y]], x-1, y);
    let n11 = this.dot2(this.gradP[X+1+this.perm[Y+1]], x-1, y-1);

    // Compute the fade curve value for x
    let u = this.fade(x);

    // Interpolate the four results
    return this.lerp(
      this.lerp(n00, n10, u),
      this.lerp(n01, n11, u),
      this.fade(y));
  }
  
  // Fading function
  fade(t) {
    return t*t*t*(t*(t*6-15)+10);
  }
  
  // Linear interpolation
  lerp(a, b, t) {
    return (1-t)*a + t*b;
  }
  
  // Dot product
  dot2(g, x, y) {
    return g[0]*x + g[1]*y;
  }
}

// Terrain types
const TERRAIN_WATER = 0;
const TERRAIN_GRASS = 1;
const TERRAIN_SAND = 2;
const TERRAIN_DIRT = 3;
const TERRAIN_STONE = 4;

// Generate terrain map
function generateTerrain(width, height, scale) {
  const terrain = new Array(width);
  for (let x = 0; x < width; x++) {
    terrain[x] = new Array(height);
    for (let y = 0; y < height; y++) {
      // Generate noise value
      let nx = x / scale;
      let ny = y / scale;
      let value = perlin.noise(nx, ny);
      
      // Adjust the value to get more interesting terrain
      // Add multiple octaves of noise for more natural look
      value += 0.5 * perlin.noise(nx * 2, ny * 2);
      value += 0.25 * perlin.noise(nx * 4, ny * 4);
      value /= 1.75; // Normalize
      
      // Generate a second noise value for stone distribution
      let stoneNoise = perlin.noise(nx * 3, ny * 3);
      
      // Determine terrain type based on noise value
      // Adjusted to get more water
      if (value < -0.2) {  // More water
        terrain[x][y] = TERRAIN_WATER;
        
        // Check for sand near water (beach)
        let sandCheck = perlin.noise((nx + 0.1) * 8, (ny + 0.1) * 8);
        if (value > -0.25 && sandCheck > 0) {
          terrain[x][y] = TERRAIN_SAND;
        }
      } else if (value < 0.0) {
        // Sand appears near water
        terrain[x][y] = TERRAIN_SAND;
      } else if (value < 0.3) {
        // Grass in middle elevations
        terrain[x][y] = TERRAIN_GRASS;
      } else if (stoneNoise > 0.3) {
        // Stone in higher elevations with specific noise pattern
        terrain[x][y] = TERRAIN_STONE;
      } else {
        // Dirt in higher elevations
        terrain[x][y] = TERRAIN_DIRT;
      }
    }
  }
  
  // Second pass to smooth terrain and create better beaches
  smoothTerrain(terrain, width, height);
  
  return terrain;
}

function smoothTerrain(terrain, width, height) {
  // Create sand around water
  for (let x = 1; x < width - 1; x++) {
    for (let y = 1; y < height - 1; y++) {
      if (terrain[x][y] !== TERRAIN_WATER) {
        // Check if adjacent to water
        let adjacentToWater = false;
        for (let dx = -1; dx <= 1; dx++) {
          for (let dy = -1; dy <= 1; dy++) {
            if (x + dx >= 0 && x + dx < width && y + dy >= 0 && y + dy < height) {
              if (terrain[x + dx][y + dy] === TERRAIN_WATER) {
                adjacentToWater = true;
                break;
              }
            }
          }
          if (adjacentToWater) break;
        }
        
        // If adjacent to water and not already sand, make it sand
        if (adjacentToWater && terrain[x][y] !== TERRAIN_SAND && Math.random() > 0.3) {
          terrain[x][y] = TERRAIN_SAND;
        }
      }
    }
  }
}

// Check if a position is in water
function isWater(x, y) {
  return getTerrainType(x, y) === TERRAIN_WATER;
}

// Get terrain type at a position
function getTerrainType(x, y) {
  // Convert world coordinates to terrain grid coordinates
  const gridX = Math.floor((x + 2000) / 10);
  const gridY = Math.floor((y + 2000) / 10);
  
  // Check bounds
  if (gridX < 0 || gridX >= terrainWidth || gridY < 0 || gridY >= terrainHeight) {
    return TERRAIN_GRASS; // Default to grass if out of bounds
  }
  
  return terrainMap[gridX][gridY];
}

// Set terrain type at a position
function setTerrainType(x, y, type) {
  // Convert world coordinates to terrain grid coordinates
  const gridX = Math.floor((x + 2000) / 10);
  const gridY = Math.floor((y + 2000) / 10);
  
  // Check bounds
  if (gridX < 0 || gridX >= terrainWidth || gridY < 0 || gridY >= terrainHeight) {
    return false; // Can't set if out of bounds
  }
  
  terrainMap[gridX][gridY] = type;
  return true;
}

// Get terrain name from type
function getTerrainName(type) {
  switch(type) {
    case TERRAIN_WATER: return "Water";
    case TERRAIN_GRASS: return "Grass";
    case TERRAIN_SAND: return "Sand";
    case TERRAIN_DIRT: return "Dirt";
    case TERRAIN_STONE: return "Stone";
    default: return "Unknown";
  }
}

// Terrain dimensions
const terrainWidth = 400;
const terrainHeight = 400;
const terrainScale = 50; // Smaller scale = more detailed terrain

// Initialize Perlin noise
const perlin = new Perlin();

// Generate the terrain map
let terrainMap = generateTerrain(terrainWidth, terrainHeight, terrainScale);