-- Extract particle graphics from a node texture
local function extract_tile(tiles,color)
  if not tiles then
    return "blank.png"
  end

  if not color and tiles.color then
    color = tiles.color
  end

  if type(tiles[1]) == "string" then
    return tiles[1], color
  elseif type(tiles[1]) == "table" then
    return tiles[1].name or tiles[1].image, color
  else
    return "blank.png"
  end
end

local function extract_particles(tiles,color,coords)
  tiles, color = extract_tile(tiles,color)
  return tiles .. "^[resize:16x16^[sheet:8x8:" .. (coords or (math.random(0,7) .. "," .. math.random(0,7))) .. (color and ("^[multiply:" .. color) or "")
end

-- Dusty particles (sand, dry grass, etc.)
effervescence.register_environmental_particles({
  name = "effervescence:dusty",
  check = function(self, pos)
    return (pos.y < 0 or pos.y > 6) and core.get_node(pos:offset(0,1,0)).name == "air"
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local does_apply = (groups.stone and groups.stone > 0) or (groups.sand and groups.sand > 0) or (groups.everness_sand and groups.everness_sand > 0) or node:find("dry_") or node:find("clay") or node:find("gravel") or node:find("litter$") or node:find("podzol")
    return does_apply and { "effervescence:floors" }
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(56,64),
      time = 5,
      pos = {
        min = pos:add(vector.new(-6,0.575,-6)),
        max = pos:add(vector.new(6,1,6)),
      },
      minsize = 0.2,
      maxsize = 0.275,
      minvel = { x = -1, y = -0.02, z = -1 },
      maxvel = { x = 1, y = 0.02, z = 1 },
      minexptime = 6,
      maxexptime = 8,
      minacc = {x = -1.5, y = 0, z = -1.5},
      maxacc = {x = 1.5, y = 0.05, z = 1.5},
      texture = extract_particles(ndef.tiles,ndef.color),
      collisiondetection = false,
      vertical = false,
    }
  end,
})

-- Crumbly particles (falling bits of stone, dirt, gravel, etc.)
effervescence.register_environmental_particles({
  name = "effervescence:crumbly",
  check = function(self, pos)
    return core.get_node(pos:offset(0,-1,0)).name == "air"
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local does_apply = (groups.stone and groups.stone > 0) or (groups.crumbly and groups.crumbly > 0) or (groups.soil and groups.soil > 0) or node:find("gravel") or node:find("ore") or node:find("moss") or node:find(":dirt_")
    return does_apply and { "effervescence:ceilings" }
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = 6,
      time = 0.5,
      pos = {
        min = pos:add(vector.new(-0.45,-0.75,-0.45)),
        max = pos:add(vector.new(0.45,-0.9,0.45)),
      },
      minvel = {x = 0, y = -5, z = 0},
      maxvel = {x = -0.1, y = -7, z = -0.1},
      minacc = {x = -0.1, y = -2, z = -0.1},
      maxacc = {x = 0.1, y = -3, z = 0.1},
      minexptime = 0.25,
      maxexptime = 1,
      minsize = 0.25,
      maxsize = 1.25,
      glow = ndef.glow or ndef.light_source,
      texture = extract_particles(ndef.tiles,ndef.color),
      collisiondetection = false,
      vertical = false,
    }
  end,
})

-- Bustling particles (bit of grasses and mosses lifting off the ground)
effervescence.register_environmental_particles({
  name = "effervescence:bustling",
  check = function(self, pos)
    return core.get_node(pos:offset(0,1,0)).name == "air"
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local does_apply = node:find("grass") or node:find("moss") or node:find("lichen") or node:find("^ethereal:.+_dirt$") or node:find("litter$") or node:find("mycelium")
    return does_apply and { "effervescence:floors" }
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(40,48),
      time = 6,
      pos = {
        min = pos:add(vector.new(-8,0.5,-8)),
        max = pos:add(vector.new(8,0.75,8)),
      },
      minvel = {x = -0.5, y = 0.1, z = -0.5},
      maxvel = {x = 0.5, y = 0.175, z = 0.5},
      minacc = {x = -0.325, y = 0.325, z = -0.325},
      maxacc = {x = 0.325, y = 0.5, z = 0.325},
      minexptime = 6,
      maxexptime = 12,
      minsize = 0.25,
      maxsize = 0.375,
      glow = ndef.glow or ndef.light_source,
      texture = extract_particles(ndef.tiles,ndef.color),
      collisiondetection = false,
      vertical = false,
    }
  end,
})

-- Snowy particles (snowflakes gusting up from the ground)
effervescence.register_environmental_particles({
  name = "effervescence:snowy",
  check = function(self, pos)
    return core.get_node(pos:offset(0,1,0)).name == "air"
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local is_snow = (groups.snowy and groups.snowy > 0) or node:find(":snow") or node:find("_with_snow")
    if is_snow then
      return { def.drawtype == "nodebox" and "effervescence:rare" or "effervescence:floors" }
    else
      return nil
    end
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(8,40),
      time = 2,
      pos = {
        min = pos:add(vector.new(-3,0,-3)),
        max = pos:add(vector.new(3,0.4,3)),
      },
      minvel = {x = -4, y = 2, z = -4},
      maxvel = {x = 4, y = 3, z = 4},
      minacc = {x = -1.75, y = 1, z = -1.75},
      maxacc = {x = 1.75, y = 1.5, z = 1.75},
      minexptime = 3,
      maxexptime = 5,
      minsize = 0.175,
      maxsize = 0.225,
      texture = extract_particles(ndef.tiles,ndef.color),
      collisiondetection = false,
      vertical = false,
    }
  end,
})

-- Leafy particles (falling leaves and snow on leaves)
local leaves = {
  air = true, -- special exception for easier check logic
}

effervescence.register_environmental_particles({
  name = "effervescence:leafy",
  check = function(self, pos)
    local below = core.get_node(pos:offset(0,-1,0)).name
    return leaves[below]
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local is_leaves = groups.leaves and groups.leaves > 0
    if is_leaves then
      leaves[node] = true
      return { "effervescence:few" }
    elseif node:find(":snow$") then
      return { "effervescence:many" }
    else
      return nil
    end
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(1,2),
      time = 3,
      pos = {
        min = pos:add(vector.new(-0.45,-0.3,-0.45)),
        max = pos:add(vector.new(0.45,-0.475,0.45)),
      },
      minvel = {x = -1, y = -0.75, z = -1},
      maxvel = {x = 1, y = -1.5, z = 1},
      minacc = {x = -0.75, y = -1, z = -0.75},
      maxacc = {x = 0.75, y = -1.5, z = 0.75},
      minexptime = 4,
      maxexptime = 6,
      minsize = 1,
      maxsize = 1.5,
      glow = ndef.glow or ndef.light_source,
      texture = extract_particles(ndef.tiles,ndef.color),
      collisiondetection = false,
      vertical = false,
    }
  end,
})

-- Blossoming particles (flower petals on the breeze)
local colors = {
  black = "#000000",
  white = "#ffffff",
  blue = "#1f75fe",
  cyan = "#00b7eb",
  orange = "#ff8c00",
  yellow = "#ffd700",
  purple = "#6f2da8",
  violet = "#6f2da8",
  magenta = "#ff33cc",
  red = "#ff2400",
  pink = "#ffc0cb",
  green = "#7cfc00",
  dark_green = "#013220",
  dark_gray = "#333333",
  darkgray = "#333333",
  grey = "#9a9a9a",
  brown = "#704214",
}

local flowers = { -- special cases listed here
  ["farming:sunflower_8"] = "#ffd700",
  ["farming:cotton_8"] = "#ffffff",
  ["x_farming:cotton_8"] = "#ffffff",
  ["dorwinion:dorwinion_glow_leaves"] = "self",
  ["nightshade:nightshade_glowin_leaves_1"] = "self",
  ["naturalbiomes:heatherflowernode"] = "self",
  ["naturalbiomes:heatherflower2node"] = "self",
  ["naturalbiomes:heatherflower3node"] = "self",
  ["naturalbiomes:heatherflower4node"] = "self",
}

effervescence.register_environmental_particles({
  name = "effervescence:blossoming",
  check = function(self, pos)
    return core.get_node(pos:offset(0,1,0)).name == "air"
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local is_plant = not def.walkable and def.drawtype:find("plant") and not node:find("shroom") and ((groups.flower and groups.flower > 0) or node:find("flower"))
    if is_plant then
      local dye = core.get_craft_result({
        method = "normal",
        width = 1,
        items = { node },
      })
      if not dye.item:is_empty() and dye.item:get_name():find("dye") then
        local dgroups = core.registered_items[dye.item:get_name()].groups
        for color,hex in pairs(colors) do
          if (dgroups["color_" .. color] and dgroups["color_" .. color] > 0) or (dgroups["basecolor_" .. color] and dgroups["basecolor_" .. color] > 0) then
            flowers[node] = hex
            break
          end
        end
      end
    end
    return flowers[node] and { "effervescence:many" }
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(2,3),
      time = 1,
      pos = {
        min = pos:add(vector.new(-0.25,-0.125,-0.25)),
        max = pos:add(vector.new(0.25,0.25,0.25)),
      },
      minvel = {x = -3, y = -0.05, z = -3},
      maxvel = {x = 3, y = 1.5, z = 3},
      minacc = {x = -0.125, y = -0.125, z = -0.125},
      maxacc = {x = 0.125, y = 0.75, z = 0.125},
      minexptime = 3,
      maxexptime = 5,
      minsize = 0.375,
      maxsize = 0.5,
      glow = ndef.glow or ndef.light_source,
      texture = extract_particles(flowers[node.name] == "self" and ndef.tiles or {[1] = "effervescence_petals.png"},flowers[node.name] ~= "self" and flowers[node.name]),
      collisiondetection = true,
      collision_removal = true,
      vertical = false,
    }
  end,
})

-- Sporogenic particles (primarily mushrooms, glow worms, and certain vines)
effervescence.register_environmental_particles({
  name = "effervescence:sporogenic",
  check = function(self, pos)
    return true
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local does_apply = not def.walkable and def.drawtype:find("plant") and (node:find("glow_worm") or node:find("shroom") or node:find("fung") or node:find("myc") or node:find("coral_grass") or node:find("coral_plant") or node:find("[:_]vine") or node:find("spore") or (groups.mushroom and groups.mushroom > 0))
    return does_apply and { "effervescence:many" }
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(9,12),
      time = 1.5,
      pos = {
        min = pos:add(vector.new(-0.25,-0.1,-0.25)),
        max = pos:add(vector.new(0.25,0.1,0.25)),
      },
      minvel = {x = -0.625, y = -0.075, z = -0.625},
      maxvel = {x = 0.625, y = -0.05, z = 0.625},
      minacc = {x = 0, y = -0.25, z = 0},
      maxacc = {x = 0, y = -0.125, z = 0},
      minexptime = 5,
      maxexptime = 8,
      minsize = 0.25,
      maxsize = 0.325,
      glow = ndef.glow or ndef.light_source or 2,
      texture = extract_particles(ndef.tiles,ndef.color,"4,7"),
      collisiondetection = true,
      collision_removal = true,
      vertical = false,
    }
  end,
})

-- Sparkly particles (glistening crystals or ice)
local neighbors = {
  vector.new(0,1,0),
  vector.new(1,0,0),
  vector.new(0,0,1),
  vector.new(-1,0,0),
  vector.new(0,0,-1),
  vector.new(0,-1,0),
}

effervescence.register_environmental_particles({
  name = "effervescence:sparkly",
  check = function(self, pos)
    for _,direction in ipairs(neighbors) do
      if core.get_node(pos:add(direction)).name == "air" then
        return true
      end
    end
    return false
  end,
  applies_to = function(self, node, def)
    local does_apply = (def.drawtype:find("plant") and (node:find("[:_]crystal") or node:find("[:_]gem") or node:find("^caverealms:spike$") or node:find("icicle")) or node:find("[:_]ice$") or node:find("[:_]cave_ice$"))
    return does_apply and { "effervescence:many" }
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(9,11),
      time = 4,
      pos = {
        min = pos:add(vector.new(-0.75,-0.25,-0.75)),
        max = pos:add(vector.new(0.75,0.575,0.75)),
      },
      minvel = {x = 0, y = -0.05, z = 0},
      maxvel = {x = 0, y = 0, z = 0},
      minacc = {x = 0, y = 0, z = 0},
      maxacc = {x = 0, y = 0, z = 0},
      minexptime = 3,
      maxexptime = 6,
      minsize = 0.1,
      maxsize = 0.2,
      glow = ndef.glow or ndef.light_source or 2,
      texture = extract_particles(ndef.tiles,nil,"4,7") .. "^[sheet:4x4:1,1^[opacity:255^[brighten",
      collisiondetection = false,
      vertical = false,
    }
  end,
})

-- Tiny bubbles at the surface of water
effervescence.register_environmental_particles({
  name = "effervescence:bubbling",
  check = function(self, pos)
    return core.get_node(pos:offset(0,1,0)).name == "air"
  end,
  applies_to = function(self, node, def)
    local does_apply = (def.liquidtype == "source" and node:find("[:_]water"))
    return does_apply and { "effervescence:liquid_surface" }
  end,
  emit = function(self, pos)
    local node = core.get_node(pos)
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(4,6),
      time = math.random(3,4),
      pos = {
        min = pos:add(vector.new(-0.45,0.5,-0.45)),
        max = pos:add(vector.new(0.45,0.525,0.45)),
      },
      minvel = {x = -0.0275, y = -0.025, z = -0.0275},
      maxvel = {x = 0.0275, y = 0, z = 0.0275},
      minacc = {x = 0, y = 0, z = 0},
      maxacc = {x = 0, y = 0, z = 0},
      minexptime = 1,
      maxexptime = 2,
      minsize = 1,
      maxsize = 1.25,
      glow = ndef.glow or ndef.light_source,
      texture = extract_particles(ndef.tiles,ndef.color) .. "^[brighten^[opacity:127",
      collisiondetection = false,
      vertical = false,
    }
  end,
})

-- Player walk particles (bits of dirt, snow, etc. kicked up underfoot)
local walking_nodes = {}

effervescence.register_player_particles({
  name = "effervescence:walking",
  check = function(self, player)
    local velocity = player:get_velocity()
    if math.abs(velocity.x) > 0.025 or math.abs(velocity.z) > 0.025 then
      local below = core.get_node(player:get_pos():offset(0,-0.1,0)).name
      return walking_nodes[below]
    end
  end,
  applies_to = function(self, node, def)
    local groups = def.groups or {}
    local does_apply = (groups.crumbly and groups.crumbly > 0) or (groups.soil and groups.soil > 0) or (groups.sand and groups.sand > 0) or (groups.leaves and groups.leaves > 0) or (groups.snowy and groups.snowy > 0) or node:find(":snow") or node:find("_with_snow")
    if does_apply then
      walking_nodes[node] = true
      return true
    else
      return false
    end
  end,
  emit = function(self, player)
    local pos = player:get_pos()
    local node = core.get_node(pos:offset(0,-0.1,0))
    local ndef = core.registered_nodes[node.name]
    return {
      amount = math.random(3,4),
      time = 0.25,
      pos = {
        min = pos:add(vector.new(-0.45,0.175,-0.45)),
        max = pos:add(vector.new(0.45,0.275,0.45)),
      },
      minvel = {x = -1, y = 0.75, z = -1},
      maxvel = {x = 1, y = 1, z = 1},
      minacc = {x = 0, y = -9.81, z = 0},
      maxacc = {x = 0, y = -9.81, z = 0},
      minexptime = 1,
      maxexptime = 1,
      minsize = 0.75,
      maxsize = 1,
      glow = ndef.glow or ndef.light_source,
      texture = extract_particles(ndef.tiles,ndef.color),
      collisiondetection = true,
      collision_removal = true,
      vertical = false,
    }
  end,
})