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

local function sch(name)
    return (mod_path .. "/schematics/" .. name .. ".mts")
end






local ryo = {
    segsize = 16,
    biome = {},
    name = {},
    sun = {
        visible = true,
        texture = "blank.png",
        sunrise_visible = false,
    },
    moon = {
        visible = false,
    },
    clouds = {
        density = 0.6,
        thickness = 2.3,
        ambient = "#333",
        color = "#33333320",
        height = 167 * br_core.level_height - br_core.offset + 80 + 50,
        speed = {x=6.9, z=12}
    },
    sky = {
		base_color = "#b1b2b1",
		type = "regular",
		clouds = true,
		sky_color = {
			day_sky = "#b1b2b1" ,
			day_horizon = "#6a6270",
			dawn_sky = "#5d5d5f" ,
			dawn_horizon = "#6e5766",
			night_sky = "#b1b2b1" ,
			night_horizon = "#6a6270",
			indoors = "#b1b2b1",
			fog_sun_tint = "#b1b2b1",
			fog_moon_tint = "#b1b2b1",
			fog_tint_type = "custom",
		}
	},
}

minetest.register_on_joinplayer(function(player, last_login)
    player:set_sky(ryo.sky)
    player:set_sun(ryo.sun)
    player:set_clouds(ryo.clouds)
    player:set_moon(ryo.moon)
    player:override_day_night_ratio(0.7)
end)

local function add_biome(def)

    ryo.name[def.name] = def
    if def.nameonly then
        return
    end

    local schems = {}
    for i, s in pairs(def.schems) do
        for k=1, s.prevalence or 1 do
            schems[#schems+1] = s
        end
    end
    def.schems = schems

    for i=1, def.prevalence or 1 do
        ryo.biome[#ryo.biome+1] = def
    end
end


add_biome({
    level = 167,
    name = "sparse",
    danger = 2,
    on_generate = nil,
    segheight = 16,
    schems = {
        {name=sch("167_main_2"),      free_rotation=true, prevalence=8},
        {name=sch("167_main_0"),      free_rotation=true, prevalence=5},
        {name=sch("167_main_1"),      free_rotation=true, prevalence=4},
        {name=sch("167_main_3"),      free_rotation=true, prevalence=1},
        {name=sch("167_main_4"),      free_rotation=true, prevalence=1},
        {name=sch("167_main_5"),      free_rotation=true, prevalence=1},
    }
})
add_biome({
    level = 167,
    name = "main",
    danger = 2,
    on_generate = nil,
    segheight = 16,
    prevalence = 2,
    schems = {
        {name=sch("167_main_2"),      free_rotation=true, prevalence=3},
        {name=sch("167_main_0"),      free_rotation=true, prevalence=3},
        {name=sch("167_main_1"),      free_rotation=true, prevalence=3},
        {name=sch("167_main_3"),      free_rotation=true, prevalence=3},
        {name=sch("167_main_4"),      free_rotation=true, prevalence=3},
        {name=sch("167_main_5"),      free_rotation=true, prevalence=3},
    }
})
add_biome({
    level = 167,
    name = "apartment",
    danger = 2,
    on_generate = nil,
    segheight = 16,
    prevalence = 3,
    schems = {
        {name=sch("167_apartment_0"), free_rotation=true, prevalence=1},
        {name=sch("167_apartment_1"), free_rotation=true, prevalence=1},
        {name=sch("167_apartment_2"), free_rotation=true, prevalence=1},
        {name=sch("167_apartment_3"), free_rotation=true, prevalence=1},
    }
})

-----------------

add_biome({
    level = 167,
    name = "stairs",
    nameonly = true,
    danger = 2,
    on_generate = nil,
    segheight = 16,
    schems = {
        {name=sch("167_stairs_0"),    free_rotation=true, skip_above=1},
        {name=sch("167_stairs_1"),    free_rotation=true, skip_above=1},
        {name=sch("167_stairs_2"),    free_rotation=true, skip_above=1},
    }
})


local perlin = {}
local function register_noise(p)
    perlin[p.name] = p
end

local cid = {}
local nam = {}
local air = nil

minetest.register_on_mods_loaded(function()
    local on_gen_list = br_core.get_on_generate_node_list()
    for node_name, list in pairs(on_gen_list) do
        local contentid = minetest.get_content_id(node_name)
        cid[node_name] = contentid
        nam[contentid] = node_name
    end
    air = minetest.get_content_id("air")
end)

register_noise({
    name = "placement",
    np = {
        offset = 0.6,
        scale = 0.5,
        spread = {x = 32, y = 16, z = 32},
        seed = 6536 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0.9,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "noplacement",
    np = {
        offset = 0.3,
        scale = 0.5,
        spread = {x = 8, y = 16, z = 8},
        seed = 786589 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0.2,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "placement_col",
    np = {
        offset = 0.1,
        scale = 0.5,
        spread = {x = 4, y = 16, z = 4},
        seed = 6536 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0.3,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "biome",
    np = {
        offset = 0.5,
        scale = 0.5,
        spread = {x = 80, y = 80, z = 80},
        seed = 25478 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "vert",
    np = {
        offset = 0.15,
        scale = 0.5,
        spread = {x = 1, y = 1, z = 1},
        seed = 542 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "variant",
    np = {
        offset = 0.5,
        scale = 0.5,
        spread = {x = 1, y = 1, z = 1},
        seed = 289046 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0.1,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "pixel",
    np = {
        offset = 0.5,
        scale = 0.5,
        spread = {x = 10, y = 4, z = 10},
        seed = 67549 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0.1,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "roughness",
    np = {
        offset = 0.5,
        scale = 0.5,
        spread = {x = 80, y = 80, z = 80},
        seed = 67549 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0.1,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})
register_noise({
    name = "preservevert",
    np = {
        offset = 0.5,
        scale = 0.5,
        spread = {x = 80, y = 80, z = 80},
        seed = 67549 + minetest.get_mapgen_setting("seed"),
        octaves = 1,
        persist = 0.1,
        lacunarity = 2.0,
    },
    perlin = nil,
    data = {},
})

local rotations = {
    "0", "90", "180", "270"
}

local function to_grid(n, seg)
    seg = seg or br_core.chunk_width
    return math.floor((n+16)/seg)
end

function br_core.generators.ryokou(minp, maxp)
    local level = 167
    local level_def = ryo
    local segsize = ryo.segsize
    local chunk_width = br_core.chunk_width
    local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")

    local sidelen = math.floor((chunk_width/segsize))
    local permapdims3d = {x = sidelen + 2, y = sidelen + 2, z = sidelen + 2}

    -- get the perlin noise data
    for name, p in pairs(perlin) do
        p.perlin = ((p.sidelen == sidelen) and p.perlin) or minetest.get_perlin_map(p.np, permapdims3d)
        p.data3d = p.perlin:get_3d_map(vector.floor(vector.divide(minp, 16)))
    end

    local biomes = ryo.biome
    -- error(dump(biomes))

    local ni = 1
    -- local pixel = (perlin.pixel.data3d[1][1][1] > 0.5)

    local function get_placement(x, y, z, f)
        if not f then f = 1 end
        return ((perlin.placement.data3d[z][y][x]*f > 0.5)
        or (perlin.placement_col.data3d[z][y][x]*f > 0.5))
        and perlin.noplacement.data3d[z][y][x]*f < 0.5
    end

    for z = 1, sidelen do
    for x = 1, sidelen do
        local biome
        local rotation = "0"

        for y = 1, sidelen do
            local preservevert = (perlin.preservevert.data3d[z][y][x] > 0.5)
            if not preservevert or not biome then
                rotation = rotations[(math.floor(92801747 * perlin.variant.data3d[z+1][2][x+1]) % #rotations) + 1]
                biome = biomes[math.floor(perlin.biome.data3d[z+1][y+1][x+1]) % #biomes + 1]
            end

            local placement_below = get_placement(x+1, y, z+1)
            local pixel = perlin.pixel.data3d[z+1][y+1][x+1]
            local p = vector.new(x, y, z)
            if pixel > 0.5 then
                p = vector.multiply(vector.floor(vector.divide(p, 4)), 4)
            end

            local placement = get_placement(p.x+1, p.y+1, p.z+1)

            local stair = (perlin.vert.data3d[z+1][y+1][x+1] > 0.5)
            local stair_below = (perlin.vert.data3d[z+1][y][x+1] > 0.5)

            if (placement or placement_below) and not stair_below then
                if stair then
                    biome = ryo.name.stairs
                end
                -- make sure you have a biome
                if not biome then
                    -- get the biome for this horizontal segment location
                    local zero_dist = math.sqrt((x + to_grid(minp.x, segsize)) ^ 2 + (z + to_grid(minp.z, segsize)) ^ 2)
                    local biomeindex = 1
                    if zero_dist > (level_def.grace_dist or 20)
                    or (not biomes[biomeindex].can_generate)
                    or not biomes[biomeindex].can_generate(vector.new(x + to_grid(minp.x, segsize), 0, z + to_grid(minp.z, segsize))) then
                        -- get a random biome from the list based on the noise
                        local pxfactor = level_def.biome_pixelization or 1
                        local nv = perlin.biome.data3d[math.ceil(z/pxfactor)*pxfactor+1][2][math.ceil(x/pxfactor)*pxfactor+1]
                        biomeindex = math.floor(nv * (level_def.biome_roughness or 37.239) * #biomes) % (#biomes) + 1
                        -- don't bother checking the same biome, skip duplicates
                        local last_biome = biomes[biomeindex].uid
                        -- check this biome can go here, and if not, cycle through until you find one that works
                        for i=0, #biomes - 1 do
                            local index = (biomeindex + i) % #biomes + 1
                            if (i==0) or last_biome ~= biomes[index].uid then
                                last_biome = biomes[index].uid
                                if ((not biomes[index].can_generate)
                                or biomes[index].can_generate(vector.new(x + to_grid(minp.x, segsize), 0, z + to_grid(minp.z, segsize))) == true) then
                                    biomeindex = index
                                    break -- when you find one, end the search
                                end
                            end
                        end
                    end
                    -- get the biome definition so we know stuff about the biome
                    biome = ryo.biome[biomeindex]
                end

                local roughness = perlin.roughness.data3d[z+1][y+1][x+1] * 82126891
                local variant = math.floor(perlin.variant.data3d[z+1][y+1][x+1] * roughness) % #biome.schems + 1
                local schem = biome.schems[variant]

                -- make sure you have a schem
                if schem and schem.can_generate
                and not schem.can_generate(vector.new(x + to_grid(minp.x, segsize), y, z + to_grid(minp.z, segsize))) then
                    for i=0, #biome.schems - 1 do
                        local index = (variant + i) % #biome.schems + 1
                        if biome.vert_schems[index] and ((not biome.vert_schems[index].can_generate)
                        or biome.vert_schems[index].can_generate(vector.new(
                        x + to_grid(minp.x, segsize),
                        y,
                        z + to_grid(minp.z, segsize))) == true) then
                            variant = index
                            schem = biome.schems[variant]
                            break -- when you find one, end the search
                        end
                    end
                end

                -- actually place
                if schem then
                    local y_offset = schem.y_offset or 0
                    local pos = vector.new((x-1)*segsize+minp.x, (y-1)*biome.segheight+minp.y + y_offset, (z-1)*segsize+minp.z)
                    local rot = schem.rotation or rotation
                    if schem.free_rotation then
                        rot = rotations[(math.floor(92801747 * perlin.variant.data3d[z+1][y+1][x+1]) % #rotations) + 1]
                    end
                    if not minetest.place_schematic_on_vmanip(vm, pos, schem.name, rot, nil, true, nil) then
                        minetest.log("warning", "ERROR NO FAILED TO PLACE SCHEMATIC WHEN GENERATING")
                    end
                end
            end
            ni = ni + 1
        end
    end
    end
    -- vm:set_data(data)
    minetest.generate_decorations(vm, minp, maxp)
    minetest.generate_ores(vm, minp, maxp)

	local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}

    local data = vm:get_data()
    for i in area:iterp(minp, maxp) do
        local pos = area:position(i)
        if nam[data[i]] then
            br_core.on_generate_node(nam[data[i]], pos)
        elseif data[i] == air and (pos.x%16 == 7) and (pos.y%16 == 7) and (pos.z%16 == 7) then
            minetest.set_node(pos, {name="br_core:null_node"})
        end
    end

    -- vm:set_data(data)

    vm:calc_lighting()
    vm:write_to_map()
    vm:update_liquids()
end
