local mod_name = minetest.get_current_modname()
local mod_path = minetest.get_modpath(mod_name)

local callback_on_complete

function gm_windmills.sch(name)
    return (mod_path .. DIR_DELIM .. "schematics" .. DIR_DELIM .. name .. ".mts")
end

local function test_callback(calls_remaining)
    if calls_remaining == 0 and callback_on_complete then
        callback_on_complete()
        callback_on_complete = nil
    end
end

local perlin = {
    np1 = {
        name = "np1",
        np = {
            offset = 0.5,
            scale = 0.5,
            spread = {x = 5, y = 4, z = 5},
            seed = 67549 + (minetest.get_mapgen_setting("seed") or 0),
            octaves = 2,
            persist = 0.1,
            lacunarity = 2.1,
        },
        perlin = nil,
        data = {},
    },
    np2 = {
        name = "np2",
        np = {
            offset = -0.0,
            scale = 1.0,
            spread = {x = 9, y = 9, z = 9},
            seed = 7657 + (minetest.get_mapgen_setting("seed") or 0),
            octaves = 2,
            persist = 0.1,
            lacunarity = 2.1,
        },
        perlin = nil,
        data = {},
    },
    ngrass = {
        name = "ngrass",
        np = {
            offset = 0.2,
            scale = 0.8,
            spread = {x = 4, y = 9, z = 4},
            seed = 7657 + (minetest.get_mapgen_setting("seed") or 0),
            octaves = 2,
            persist = 0.0,
            lacunarity = 1.954,
        },
        perlin = nil,
        data = {},
    },
    ntree = {
        name = "ntree",
        np = {
            offset = 0.0,
            scale = 1.0,
            spread = {x = 1, y = 1, z = 1},
            seed = 5439 + (minetest.get_mapgen_setting("seed") or 0),
            octaves = 1,
            persist = 0.0,
            lacunarity = 1.0,
        },
        perlin = nil,
        data = {},
    },
    nmill = {
        name = "nmill",
        np = {
            offset = 0.0,
            scale = 1.0,
            spread = {x = 1, y = 1, z = 1},
            seed = 8757 + (minetest.get_mapgen_setting("seed") or 0),
            octaves = 1,
            persist = 0.0,
            lacunarity = 1.0,
        },
        perlin = nil,
        data = {},
    },
}

local decorations = {
    "tree_oak_0",
    "tree_oak_1",
    "tree_oak_2",
    "tree_oak_small_0",
    "tree_oak_small_1",
}


function gm_windmills.generate_islands(seed)
    local sch = pmb_sch.get_schematic_path

    local placepos = gm_windmills.game_origin
    minetest.place_schematic(placepos, gm_windmills.sch("gm_windmills_start"), "random", {}, true, "place_center_x, place_center_z")

    local vm = minetest.get_voxel_manip()
    local emin, emax = vm:read_from_map(gm_windmills.minp, gm_windmills.maxp)
    local va = VoxelArea:new{
        MinEdge = emin,
        MaxEdge = emax
    }
    local data = vm:get_data()

    local dist_factor = 30*20

    local cid = {
        stone = minetest.get_content_id("pmb_stone:stone"),
        dirt = minetest.get_content_id("pmb_soil:dirt"),
        grass = minetest.get_content_id("pmb_soil:grass"),
        barrier = minetest.get_content_id("pmb_util:barrier"),
        lamp = minetest.get_content_id("pmb_stone:stone_brick_lamp"),
    }

    local permapdims3d = {
        x = gm_windmills.maxp.x - gm_windmills.minp.x + 1,
        y = gm_windmills.maxp.y - gm_windmills.minp.y + 1,
        z = gm_windmills.maxp.z - gm_windmills.minp.z + 1,
    }
    local _i = 1
    for name, p in pairs(perlin) do
        _i = _i + 791
        p.np.seed = _i + (seed or math.random(12387))
        p.perlin = minetest.get_perlin_map(p.np, permapdims3d)
        p.data3d = p.perlin:get_3d_map(gm_windmills.minp)
    end

    local schems_to_place = {}
    -- local mills_to_place = {}

    local _size = vector.subtract(gm_windmills.maxp, gm_windmills.minp)
    for z=1, _size.z+1 do
    for x=1, _size.x+1 do
    local since_air = 0
    for y=_size.y+1, 1, -1 do
        repeat
            -- local vi = va:index(x, y, z)
            local p = vector.new(x, y, z)
            local absp = vector.add(p, gm_windmills.minp)
            local vi = va:indexp(absp)

            if x == 1 or y == 1 or z == 1 or x == _size.x or y == _size.y or z == _size.z then
                data[vi] = cid.barrier
                break
            end

            local d = math.max(math.min(dist_factor / (
                (p.x - _size.x*0.5) * (p.x - _size.x*0.5) +
                (p.y - _size.y*0.5) * (p.y - _size.y*0.5) * 30 +
                (p.z - _size.z*0.5) * (p.z - _size.z*0.5)), 1), 0)

            local nv1 = perlin.np1.data3d[z][y][x] or 0
            local nv2 = perlin.np2.data3d[z][y][x] or 0
            local nvgrass = perlin.ngrass.data3d[z][y][x] or 0
            local nvmill = perlin.nmill.data3d[z][y][x] or 0

            local total_nv = (nv1 + nv2) * d

            if total_nv < 0.9 then
                since_air = 0
                break
            end

            since_air = since_air + 1

            if nvgrass < 0.2 then
                if since_air <= 1 then
                    data[vi] = cid.grass

                    if nvgrass > 0.195 then
                        schems_to_place[#schems_to_place+1] = absp
                    end
                    break
                elseif since_air <= 3 and nvgrass < 0.1 then
                    data[vi] = cid.dirt
                    break
                end
            end

            data[vi] = cid.stone
        until true
    end
    end
    end

    vm:set_data(data)

    if not perlin.ngrass.perlin_direct then
        perlin.ngrass.perlin_direct = PerlinNoise(perlin.ngrass.np)
    end
    if not perlin.nmill.perlin_direct then
        perlin.nmill.perlin_direct = PerlinNoise(perlin.nmill.np)
    end
    for i, pos in pairs(schems_to_place) do
        local nv = perlin.ngrass.perlin_direct:get_3d(pos) % 1
        local variant = math.ceil(nv * #decorations)
        local name = decorations[variant]
        if name then
            minetest.place_schematic_on_vmanip(vm, pos, sch(name), "random", nil, false, "place_center_x, place_center_z")
        end
    end
    local rot = math.random() * math.pi * 2
    for i = 1, gm_windmills.windmill_count do
        local o = gm_windmills.game_origin
        local yaw = (math.pi * 2 / 3) * (i-1)
        local dir = minetest.yaw_to_dir(yaw)
        local nvmill1 = perlin.ngrass.perlin_direct:get_3d({x=i,y=0,z=0}) % 1
        local pos = vector.add(o, vector.multiply(dir, 15 * math.min(nvmill1 + 0.3, 1)))
        local nvmill2 = perlin.ngrass.perlin_direct:get_3d(pos) % 1
        pos.y = pos.y + nvmill2 * 4 - 12
        pos.x = pos.x + nvmill2 * -8 + 4
        pos.z = pos.z + nvmill2 *  8 - 4

        pos = vector.round(pos)

        minetest.place_schematic_on_vmanip(vm, pos, gm_windmills.sch("gm_windmills_"..math.random(1,3)), "random", nil, true, "place_center_x, place_center_z")
        pos.y = pos.y + 16
        minetest.place_schematic_on_vmanip(vm, pos, gm_windmills.sch("gm_windmills_windmill"), "random", nil, true, nil)
        minetest.after(0.2, function()
            local obj = minetest.add_entity(pos, "gm_windmills:windmill_head_ENTITY")
            obj:set_rotation(vector.new(0, rot, 0))
        end)
    end
    vm:write_to_map()
end

function gm_windmills.on_generate_block(blockpos, action, calls_remaining, param)
    if action == minetest.EMERGE_ERRORED or action == minetest.EMERGE_CANCELLED then
        test_callback(calls_remaining)
        return
    end
    test_callback(calls_remaining)
end

function gm_windmills.regenerate(seed, callback)
    minetest.log("action", "regenerating for gm_windmills")
    callback_on_complete = callback
    minetest.delete_area(gm_windmills.minp, gm_windmills.maxp)
    minetest.emerge_area(gm_windmills.minp, gm_windmills.maxp, gm_windmills.on_generate_block, {seed=seed})
end
