local zero = { x=0, y=0, z=0 }

-- creates a buffer for the rotation
local function create_buffer(data, size, max)
    local area_src = VoxelArea:new({MinEdge=zero, MaxEdge=size})
    local area_dst = VoxelArea:new({MinEdge=zero, MaxEdge=max})

    local buf = {}

    for z=0,size.z do
    for x=0,size.x do
    for y=0,size.y do
        local i_src = area_src:index(x,y,z)
        local i_dst = area_dst:index(x,y,z)
        buf[i_dst] = data[i_src]
    end
    end
    end

    return buf
end

-- extracts the rotated new size from the buffer
local function extract_from_buffer(buf, size, max, offset)
    local area_src = VoxelArea:new({MinEdge=zero, MaxEdge=max})
    local area_dst = VoxelArea:new({MinEdge=zero, MaxEdge=size})

    local data = {}

    for z=0,size.z do
    for x=0,size.x do
    for y=0,size.y do
        local i_src = area_src:index(x+offset.x,y+offset.y,z+offset.z)
        local i_dst = area_dst:index(x,y,z)
        data[i_dst] = buf[i_src]
    end
    end
    end

    return data
end

local function apply_offset(metadata_map, offset)
    local new_metadata_map = {}
    for pos_str, metadata in pairs(metadata_map) do
        local pos = minetest.string_to_pos(pos_str)
        local new_pos = vector.subtract(pos, offset)
        local new_pos_str = minetest.pos_to_string(new_pos)

        new_metadata_map[new_pos_str] = metadata
    end
    return new_metadata_map
end

function pick_and_place.schematic_rotate(schematic, rotation)
    if rotation <= 0 or rotation > 270 then
        -- invalid or no rotation
        return
    end

    local other1, other2 = "x", "z"
    local rotated_size = pick_and_place.rotate_size(schematic.size, rotation)

    local metadata = schematic.metadata

    local max_xz_axis = math.max(schematic.size.x, schematic.size.z)
    local max = { x=max_xz_axis-1, y=schematic.size.y-1, z=max_xz_axis-1 }

    -- create transform buffers
    local node_id_buf = create_buffer(schematic.node_id_data, vector.subtract(schematic.size, 1), max)
    local param2_buf = create_buffer(schematic.param2_data, vector.subtract(schematic.size, 1), max)

    -- rotate
    if rotation == 90 then
        pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other1)
        pick_and_place.schematic_transpose(node_id_buf, param2_buf, metadata, max, other1, other2)
    elseif rotation == 180 then
        pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other1)
        pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other2)
    elseif rotation == 270 then
        pick_and_place.schematic_transpose(node_id_buf, param2_buf, metadata, max, other1, other2)
        pick_and_place.schematic_flip(node_id_buf, param2_buf, metadata, max, other1)
    end

    -- extract from buffer with offset
    local offset = {x=0, y=0, z=0}
    local z_larger = schematic.size.z > schematic.size.x
    local x_larger = schematic.size.z < schematic.size.x
    local xz_diff = math.abs(schematic.size.x - schematic.size.z)
    if rotation == 90 then
        if z_larger then
            offset.z = xz_diff
        end
    elseif rotation == 180 then
        if x_larger then
            offset.z = xz_diff
        elseif z_larger then
            offset.x = xz_diff
        end
    elseif rotation == 270 then
        if x_larger then
            offset.x = xz_diff
        end
    end
    schematic.node_id_data = extract_from_buffer(node_id_buf, vector.subtract(rotated_size, 1), max, offset)
    schematic.param2_data = extract_from_buffer(param2_buf, vector.subtract(rotated_size, 1), max, offset)
    schematic.metadata = apply_offset(metadata, offset)

    -- rotate size
    schematic.size = rotated_size

    -- orient rotated schematic
    pick_and_place.schematic_orient(
        schematic.node_id_data,
        schematic.param2_data,
        vector.subtract(rotated_size, 1),
        rotation
    )
end

