---
--- Generated by EmmyLua.
--- Created by Michieal (FaerRaven).
--- DateTime: 10/14/22 4:05 PM
---

--local logging = minetest.settings:get_bool("mcl_logging_mcl_signs",true)

local DEBUG = minetest.settings:get_bool("mcl_logging_mcl_signs", false) -- special debug setting.
local table = table -- copied from the original signs init file.

if DEBUG then
    minetest.log("action", "[mcl_signs] Signs API Loading")
end

-- LOCALIZATION
local S = minetest.get_translator("mcl_signs")
-- Signs form
local F = minetest.formspec_escape

-- PATHs
local modpath = minetest.get_modpath("mcl_signs")

-- CONSTANTS
local SIGN_WIDTH = 115

local LINE_LENGTH = 15
local NUMBER_OF_LINES = 4

local LINE_HEIGHT = 14
local CHAR_WIDTH = 5
-- -----------------------

-- CACHE NODE_SOUNDS
local node_sounds
if minetest.get_modpath("mcl_sounds") then
    node_sounds = mcl_sounds.node_sound_wood_defaults()
end

-- SET UP THE CHARACTER MAPPING
-- Load the characters map (characters.txt)
--[[ File format of characters.txt:
It's an UTF-8 encoded text file that contains metadata for all supported characters. It contains a sequence of info
    blocks, one for each character. Each info block is made out of 3 lines:
Line 1: The literal UTF-8 encoded character
Line 2: Name of the texture file for this character minus the “.png” suffix; found in the “textures/” sub-directory
Line 3: Currently ignored. Previously this was for the character width in pixels

After line 3, another info block may follow. This repeats until the end of the file.

All character files must be 5 or 6 pixels wide (5 pixels are preferred)
]]

local chars_file = io.open(modpath .. "/characters.txt", "r")
-- FIXME: Support more characters (many characters are missing). Currently ASCII and Latin-1 Supplement are supported.
local charmap = {}
if not chars_file then
    minetest.log("error", "[mcl_signs] : character map file not found")
else
    while true do
        local char = chars_file:read("*l")
        if char == nil then
            break
        end
        local img = chars_file:read("*l")
        chars_file:read("*l")
        charmap[char] = img
    end
end

local pi = 3.1415926 -- enough accuracy, to build an engine for a car.

local math = math

-- locally cached copy of the official colors; this way, it updates as mcl_colors updates.
local mcl_colors_official = mcl_colors
if DEBUG then
    minetest.log("verbose", "[mcl_signs]Official MCL_Colors:\n" .. dump(mcl_colors_official))
end

-- INITIALIZE THE GLOBAL API FOR SIGNS.
mcl_signs = {}

-- GLOBALS
mcl_signs.sign_groups = { handy = 1, axey = 1, deco_block = 1, material_wood = 1, attached_node = 1, dig_by_piston = 1, flammable = -1 }
--- colors used for wools.
mcl_signs.mcl_wool_colors = {
    unicolor_white = "#FFFFFF",
    unicolor_dark_orange = "#502A00",
    unicolor_grey = "#5B5B5B",
    unicolor_darkgrey = "#303030",
    unicolor_blue = "#0000CC",
    unicolor_dark_green = "#005000",
    unicolor_green_or_lime = "#50CC00",
    unicolor_violet_purple = "#5000CC",
    unicolor_light_red_pink = "#FF5050",
    unicolor_yellow = "#CCCC00",
    unicolor_orange = "#CC5000",
    unicolor_red = "#CC0000",
    unicolor_cyan = "#00CCCC",
    unicolor_red_violet_magenta = "#CC0050",
    unicolor_black = "#000000",
    unicolor_light_blue = "#5050FF",
}
mcl_signs.signtext_info_wall = {}
mcl_signs.signtext_info_standing = {} -- built in build_signs_info().
-- the rotational levels for all of the standing signs.
mcl_signs.standing_rotation_levels = {}

-- data structure block for dynamically registered signs.
mcl_signs.registered_signs = {}
mcl_signs.registered_signs.wall_signs = {}
mcl_signs.registered_signs.standing_signs = {}
mcl_signs.registered_signs.hanging_signs = {} -- unused. prepping for future use.
-- DEFINE SIGN BASE TYPES
mcl_signs.wall_standard = {} -- initialize
mcl_signs.standing_standard = {} -- initialize

function mcl_signs.build_signs_info()
    local n = 23 / 56 - 1 / 128 -- some required magic number from the original code.
    local m = -1 / 16 + 1 / 64  -- "     "        "     "       "    "   "       "

    mcl_signs.signtext_info_wall = {
        { delta = { x = 0, y = 0, z = n }, yaw = 0 },
        { delta = { x = n, y = 0, z = 0 }, yaw = pi / -2 },
        { delta = { x = 0, y = 0, z = -n }, yaw = pi },
        { delta = { x = -n, y = 0, z = 0 }, yaw = pi / 2 },
    }

    -- PLACE YAW VALUES INTO THE TABLE.
    for rot = 0, 15 do
        local yaw = pi * 2 - (((pi * 2) / 16) * rot)
        local delta = vector.multiply(minetest.yaw_to_dir(yaw), m)
        -- Offset because sign is a bit above node boundaries
        delta.y = delta.y + 2 / 28
        table.insert(mcl_signs.signtext_info_standing, { delta = delta, yaw = yaw })
    end

end

-- wall signs' & hanging signs' base (definition)
mcl_signs.wall_standard = {
    description = S("Sign"),
    _tt_help = S("Can be written"),
    _doc_items_longdesc = S("Signs can be written and come in two variants: Wall sign and sign on a sign post. Signs can be placed on the top and the sides of other blocks, but not below them."),
    _doc_items_usagehelp = S("After placing the sign, you can write something on it. You have 4 lines of text with up to 15 characters for each line; anything beyond these limits is lost. Not all characters are supported. The text can not be changed once it has been written; you have to break and place the sign again. Can be colored and made to glow."),
    inventory_image = "default_sign.png",
    walkable = false,
    is_ground_content = false,
    wield_image = "default_sign.png",
    node_placement_prediction = "",
    paramtype = "light",
    sunlight_propagates = true,
    paramtype2 = "wallmounted",
    drawtype = "mesh",
    mesh = "mcl_signs_signonwallmount.obj",
    selection_box = { type = "wallmounted", wall_side = { -0.5, -7 / 28, -0.5, -23 / 56, 7 / 28, 0.5 } },
    tiles = { "mcl_signs_sign.png" },
    use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "opaque" or false,
    groups = mcl_signs.sign_groups,
    stack_max = 16,
    sounds = node_sounds,

    on_place = function(itemstack, placer, pointed_thing)
        local above = pointed_thing.above
        local under = pointed_thing.under

        -- Use pointed node's on_rightclick function first, if present
        local node_under = minetest.get_node(under)
        if placer and not placer:get_player_control().sneak then
            if minetest.registered_nodes[node_under.name] and minetest.registered_nodes[node_under.name].on_rightclick then
                return minetest.registered_nodes[node_under.name].on_rightclick(under, node_under, placer, itemstack) or itemstack
            end
        end

        local dir = vector.subtract(under, above)

        -- Only build when it's legal
        local abovenodedef = minetest.registered_nodes[minetest.get_node(above).name]
        if not abovenodedef or abovenodedef.buildable_to == false then
            return itemstack
        end

        local wdir = minetest.dir_to_wallmounted(dir)

        --local placer_pos = placer:get_pos()

        local fdir = minetest.dir_to_facedir(dir)

        local sign_info
        local nodeitem = ItemStack(itemstack)
        -- Ceiling
        if wdir == 0 then
            --how would you add sign to ceiling?
            return itemstack
            -- Floor
        end

        if wdir == 1 then
            -- Standing sign

            -- Determine the sign rotation based on player's yaw
            local yaw = pi * 2 - placer:get_look_horizontal()

            -- Select one of 16 possible rotations (0-15)
            local rotation_level = mcl_signs:round((yaw / (pi * 2)) * 16)

            if rotation_level > 15 then
                rotation_level = 0
            elseif rotation_level < 0 then
                rotation_level = 15
            end

            -- The actual rotation is a combination of predefined mesh and facedir (see node definition)
            if rotation_level % 4 == 0 then
                nodeitem:set_name("mcl_signs:standing_sign")
            elseif rotation_level % 4 == 1 then
                nodeitem:set_name("mcl_signs:standing_sign22_5")
            elseif rotation_level % 4 == 2 then
                nodeitem:set_name("mcl_signs:standing_sign45")
            elseif rotation_level % 4 == 3 then
                nodeitem:set_name("mcl_signs:standing_sign67_5")
            end
            fdir = math.floor(rotation_level / 4)

            -- Place the node!
            local _, success = minetest.item_place_node(nodeitem, placer, pointed_thing, fdir)
            if not success then
                return itemstack
            end
            if not minetest.is_creative_enabled(placer:get_player_name()) then
                itemstack:take_item()
            end
            sign_info = mcl_signs.signtext_info_standing[rotation_level + 1]
            -- Side
        else
            -- Wall sign
            local _, success = minetest.item_place_node(itemstack, placer, pointed_thing, wdir)
            if not success then
                return itemstack
            end
            sign_info = mcl_signs.signtext_info_wall[fdir + 1]
        end

        -- Determine spawn position of entity
        local place_pos
        if minetest.registered_nodes[node_under.name].buildable_to then
            place_pos = under
        else
            place_pos = above
        end

        local text_entity = minetest.add_entity({
            x = place_pos.x + sign_info.delta.x,
            y = place_pos.y + sign_info.delta.y,
            z = place_pos.z + sign_info.delta.z }, "mcl_signs:text")
        text_entity:set_yaw(sign_info.yaw)
        text_entity:get_luaentity()._signnodename = nodeitem:get_name()
        if DEBUG then
            minetest.log("verbose", "[mcl_signs]Placed position:" .. dump(place_pos) .. "\nSign_info: " .. dump(sign_info))
        end

        minetest.sound_play({ name = "default_place_node_hard", gain = 1.0 }, { pos = place_pos }, true)

        mcl_signs:show_formspec(placer, place_pos)
        return itemstack
    end,
    on_destruct = function(pos)
        mcl_signs:destruct_sign(pos)
    end,

    -- Not Useless Code. force updates the sign.
    on_punch = function(pos, node, puncher)
        mcl_signs:update_sign(pos)
        if DISINTEGRATE then
            mcl_signs:destruct_sign(pos)
        end
    end,
    on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            local r = screwdriver.rotate.wallmounted(pos, node, mode)
            node.param2 = r
            minetest.swap_node(pos, node)
            mcl_signs:update_sign(pos, nil, nil, true)
            return true
        else
            return false
        end
    end,
    on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
        if DEBUG then
            minetest.log("verbose", "[mcl_signs] Wall_Sign Right Click event.")
        end

        -- make sure player is clicking
        if not clicker or not clicker:is_player() then
            return
        end

        local item = clicker:get_wielded_item()
        local iname = item:get_name()

        if node then
            if DEBUG then
                minetest.log("verbose", "[mcl_signs] Wall_Sign Right Click event on valid node.")
            end

            -- handle glow from glow_ink_sac *first*
            if (iname == "mcl_mobitems:glow_ink_sac") then
                clicker:set_wielded_item(item)
                local success = mcl_signs:glow_sign(pos)
                if success then
                    if DEBUG then
                        minetest.log("verbose", "[mcl_signs] Sign Glow Success.")
                    end
                    itemstack:take_item()
                end
                return
            end

            -- "mcl_dye:black" is a special case: it makes the sign's lettering black AND removes glow.
            if (iname == "mcl_dye:black") then
                clicker:set_wielded_item(item)
                local success = mcl_signs:glow_sign(pos, true)
                mcl_signs:color_sign(pos, mcl_colors.BLACK)
                if success then
                    if DEBUG then
                        minetest.log("verbose", "[mcl_signs] Sign Glow removal Success.")
                    end

                    itemstack:take_item()
                end
                return
            end

            -- check the wielded item to make sure that it is a dye.
            local txt_color = mcl_signs:get_color_for_sign(iname)
            if txt_color ~= "false" then
                clicker:set_wielded_item(item)
                local success = mcl_signs:color_sign(pos, txt_color)
                if success then
                    if DEBUG then
                        minetest.log("verbose", "[mcl_signs] Sign Color Success.")
                    end
                    itemstack:take_item()
                end
            end
        end
    end,

    _mcl_hardness = 1,
    _mcl_blast_resistance = 1,
}
-- standing sign base (definition)
mcl_signs.standing_standard = {
    paramtype = "light",
    use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "opaque" or false,
    sunlight_propagates = true,
    walkable = false,
    is_ground_content = false,
    paramtype2 = "facedir",
    drawtype = "mesh",
    mesh = "mcl_signs_sign.obj",
    selection_box = { type = "fixed", fixed = { -0.2, -0.5, -0.2, 0.2, 0.5, 0.2 } },
    tiles = { "mcl_signs_sign.png" },
    groups = mcl_signs.sign_groups,
    drop = "mcl_signs:wall_sign",
    stack_max = 16,
    sounds = node_sounds,

    on_destruct = function(pos)
        mcl_signs:destruct_sign(pos)
    end,

    -- Not Useless Code. this force updates the sign.
    on_punch = function(pos, node, puncher)
        mcl_signs:update_sign(pos)
        if DISINTEGRATE then
            mcl_signs:destruct_sign(pos)
        end
    end,
    on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign22_5"
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end,

    on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)

        if DEBUG then
            minetest.log("verbose", "[mcl_signs] Standing_Sign Right Click event.")
        end

        -- make sure player is clicking
        if not clicker or not clicker:is_player() then
            return
        end

        local item = clicker:get_wielded_item()
        local iname = item:get_name()

        if node then
            -- handle glow from glow_ink_sac *first*
            if DEBUG then
                minetest.log("verbose", "[mcl_signs] Standing_Sign Right Click event on valid node.")
            end

            if (iname == "mcl_mobitems:glow_ink_sac") then
                clicker:set_wielded_item(item)
                local success = mcl_signs:glow_sign(pos)
                if success then
                    if DEBUG then
                        minetest.log("verbose", "[mcl_signs] Sign Glow Success.")
                    end
                    itemstack:take_item()
                end
                return
            end

            -- check the wielded item to make sure that it is a dye.
            local txt_color = mcl_signs:get_color_for_sign(iname)
            if txt_color ~= "false" then
                clicker:set_wielded_item(item)
                local success = mcl_signs:color_sign(pos, txt_color)
                if success then
                    if DEBUG then
                        minetest.log("verbose", "[mcl_signs] Sign Color Success.")
                    end
                    itemstack:take_item()
                end
            end
        end
    end,

    _mcl_hardness = 1,
    _mcl_blast_resistance = 1,
}

-- HELPER FUNCTIONS' VARIABLES
local sign_glow = 6
local Dyes_table = {
    { "mcl_dye:aqua", mcl_colors_official.AQUA },
    { "mcl_dye:black", mcl_colors_official.BLACK },
    { "mcl_dye:blue", mcl_colors_official.BLUE },
    { "mcl_dye:brown", mcl_colors_official.brown },
    { "mcl_dye:cyan", mcl_signs.mcl_wool_colors.unicolor_cyan },
    { "mcl_dye:green", mcl_colors_official.GREEN },
    { "mcl_dye:dark_green", mcl_colors_official.DARK_GREEN },
    { "mcl_dye:grey", mcl_colors_official.GRAY },
    { "mcl_dye:dark_grey", mcl_colors_official.DARK_GRAY },
    { "mcl_dye:lightblue", mcl_signs.mcl_wool_colors.unicolor_light_blue },
    { "mcl_dye:lime", mcl_signs.unicolor_green_or_lime },
    { "mcl_dye:magenta", mcl_colors_official.LIGHT_PURPLE },
    { "mcl_dye:orange", mcl_signs.mcl_wool_colors.unicolor_orange },
    { "mcl_dye:pink", mcl_signs.mcl_wool_colors.unicolor_light_red_pink },
    { "mcl_dye:purple", mcl_colors_official.LIGHT_PURPLE },
    { "mcl_dye:red", mcl_signs.mcl_wool_colors.unicolor_red },
    { "mcl_dye:silver", mcl_signs.mcl_wool_colors.unicolor_grey },
    { "mcl_dye:violet", mcl_colors_official.DARK_PURPLE },
    { "mcl_dye:white", mcl_colors_official.WHITE },
    { "mcl_dye:yellow", mcl_colors_official.YELLOW },
}

local function update_sign_registry(type, name)
    if type == "wall" then
        table.insert(mcl_signs.registered_signs.wall_signs, name)
    end
    if type == "standing" then
        table.insert(mcl_signs.registered_signs.standing_signs, name)
    end
    if type == "hanging" then
        table.insert(mcl_signs.registered_signs.hanging_signs, name)
    end
end

function mcl_signs.make_lbm()

    local registered_sign_nodenames = {}

    for i = 0, #mcl_signs.registered_signs.wall_signs do
        table.insert(registered_sign_nodenames, mcl_signs.registered_signs.wall_signs[i])
    end

    for i = 0, #mcl_signs.registered_signs.standing_signs do
        table.insert(registered_sign_nodenames, mcl_signs.registered_signs.standing_signs[i])
    end

    for i = 0, #mcl_signs.registered_signs.hanging_signs do
        table.insert(registered_sign_nodenames, mcl_signs.registered_signs.hanging_signs[i])
    end

    -- the above is not yet used.
    minetest.register_lbm({
        name = "mcl_signs:respawn_entities",
        label = "Respawn sign text entities",
        run_at_every_load = true,
        nodenames =  registered_sign_nodenames ,
        action = function(pos, node)
            mcl_signs:update_sign(pos)
        end,
    })

end

function mcl_signs.register_dye (modname, item_name, color_code)
    if minetest.get_modpath(modname) then
        table.insert(Dyes_table, { item_name, color_code })
    end
end

--- Register a new sign, tint the textures, and gives it an unique node name. Creates both wall and standing signs.
--- modname: optional (pass "" or "false" to ignore), for use with other mods to
--- allow the creation of a sign from the mod's wood (if installed).
---
--- color: the color code to color the base sign textures. must be a valid html color code.
---
--- _name: the sign's name suffix, such as "_dark" or "_red", etc., appended to "wall_sign" or "standing_sign"
---
--- ttsign: the tool tip of the sign that gets translated. Shown when the mouse hovers the inventory sign.
--- For example: the basic, default oak (wood) sign is just "Sign"; and a spruce sign would be "Spruce Sign"
function mcl_signs.register_sign (modname, color, _name, ttsign)
    local mod_name_pass = false
    if modname ~= "" and modname ~= "false" then
        if minetest.get_modpath(modname) then
            mod_name_pass = true
        end
        if mod_name_pass == false then
            return
        end
    end
    local new_sign = {}

    if color == nil or color == "" then
        color = "#FFFFFF"
    end

    new_sign = table.copy(mcl_signs.wall_standard)
    new_sign.description = S(ttsign)

    new_sign.wield_image = "(default_sign.png^[multiply:" .. color .. ")"
    new_sign.tiles = { "(mcl_signs_sign.png^[multiply:" .. color .. ")" }
    new_sign.inventory_image = "(default_sign.png^[multiply:" .. color .. ")"

    -- currently have to do this, because of how the base node placement works.
    new_sign.on_place = function(itemstack, placer, pointed_thing)
        local above = pointed_thing.above
        local under = pointed_thing.under

        -- Use pointed node's on_rightclick function first, if present
        local node_under = minetest.get_node(under)
        if placer and not placer:get_player_control().sneak then
            if minetest.registered_nodes[node_under.name] and minetest.registered_nodes[node_under.name].on_rightclick then
                return minetest.registered_nodes[node_under.name].on_rightclick(under, node_under, placer, itemstack) or itemstack
            end
        end

        local dir = vector.subtract(under, above)

        -- Only build when it's legal
        local abovenodedef = minetest.registered_nodes[minetest.get_node(above).name]
        if not abovenodedef or abovenodedef.buildable_to == false then
            return itemstack
        end

        local wdir = minetest.dir_to_wallmounted(dir)
        local fdir = minetest.dir_to_facedir(dir)

        local sign_info
        local nodeitem = ItemStack(itemstack)
        -- Ceiling
        if wdir == 0 then
            --how would you add sign to ceiling?
            return itemstack
            -- Floor
        elseif wdir == 1 then
            -- Standing sign

            -- Determine the sign rotation based on player's yaw
            local yaw = pi * 2 - placer:get_look_horizontal()

            -- Select one of 16 possible rotations (0-15)
            local rotation_level = mcl_signs:round((yaw / (pi * 2)) * 16)

            if rotation_level > 15 then
                rotation_level = 0
            elseif rotation_level < 0 then
                rotation_level = 15
            end

            -- The actual rotation is a combination of predefined mesh and facedir (see node definition)
            if rotation_level % 4 == 0 then
                nodeitem:set_name("mcl_signs:standing_sign" .. _name)
            elseif rotation_level % 4 == 1 then
                nodeitem:set_name("mcl_signs:standing_sign22_5" .. _name)
            elseif rotation_level % 4 == 2 then
                nodeitem:set_name("mcl_signs:standing_sign45" .. _name)
            elseif rotation_level % 4 == 3 then
                nodeitem:set_name("mcl_signs:standing_sign67_5" .. _name)
            end
            fdir = math.floor(rotation_level / 4)

            -- Place the node!
            local _, success = minetest.item_place_node(nodeitem, placer, pointed_thing, fdir)
            if not success then
                return itemstack
            end
            if not minetest.is_creative_enabled(placer:get_player_name()) then
                itemstack:take_item()
            end
            sign_info = mcl_signs.signtext_info_standing[rotation_level + 1]
            -- Side
        else
            -- Wall sign
            local _, success = minetest.item_place_node(itemstack, placer, pointed_thing, wdir)
            if not success then
                return itemstack
            end
            sign_info = mcl_signs.signtext_info_wall[fdir + 1]
        end

        -- Determine spawn position of entity
        local place_pos
        if minetest.registered_nodes[node_under.name].buildable_to then
            place_pos = under
        else
            place_pos = above
        end

        if DEBUG then
            minetest.log("action", "[mcl_signs] Register_Sign::Placed position:" .. dump(place_pos) .. "\nSign_info: " .. dump(sign_info))
        end

        local text_entity = minetest.add_entity({
            x = place_pos.x + sign_info.delta.x,
            y = place_pos.y + sign_info.delta.y,
            z = place_pos.z + sign_info.delta.z }, "mcl_signs:text")
        text_entity:set_yaw(sign_info.yaw)
        text_entity:get_luaentity()._signnodename = nodeitem:get_name()

        minetest.sound_play({ name = "default_place_node_hard", gain = 1.0 }, { pos = place_pos }, true)

        mcl_signs:show_formspec(placer, place_pos)
        return itemstack
    end

    minetest.register_node("mcl_signs:wall_sign" .. _name, new_sign)
    update_sign_registry("wall", "mcl_signs:wall_sign" .. _name)

    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Registered: mcl_signs:wall_sign" .. _name .. color .. "\n" .. dump(new_sign))
        minetest.log("action", "[mcl_signs] mcl_signs:wall_sign_standard\n" .. dump(mcl_signs.wall_standard))
    end

    -- standing sign base.
    local new_sign_standing = {}
    new_sign_standing = table.copy(mcl_signs.standing_standard)
    new_sign_standing.drop = "mcl_signs:wall_sign" .. _name
    new_sign_standing.wield_image = "(default_sign.png^[multiply:" .. color .. ")"
    new_sign_standing.tiles = { "(mcl_signs_sign.png^[multiply:" .. color .. ")" }
    new_sign_standing.inventory_image = "(default_sign.png^[multiply:" .. color .. ")"

    new_sign_standing.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign22_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end,

    minetest.register_node("mcl_signs:standing_sign" .. _name, new_sign_standing)
    update_sign_registry("standing", "mcl_signs:standing_sign" .. _name)
    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Registered: mcl_signs:standing_sign" .. _name .. color .. "\n" .. dump(new_sign_standing))
    end

    -- 22.5°
    local ssign22_5d = table.copy(new_sign_standing)
    ssign22_5d.mesh = "mcl_signs_sign22.5.obj"
    ssign22_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign45" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.register_node("mcl_signs:standing_sign22_5" .. _name, ssign22_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign22_5" .. _name)

    -- 45°
    local ssign45d = table.copy(new_sign_standing)
    ssign45d.mesh = "mcl_signs_sign45.obj"
    ssign45d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign67_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.register_node("mcl_signs:standing_sign45" .. _name, ssign45d)
    update_sign_registry("standing", "mcl_signs:standing_sign45" .. _name)

    -- 67.5°
    local ssign67_5d = table.copy(new_sign_standing)
    ssign67_5d.mesh = "mcl_signs_sign67.5.obj"
    ssign67_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign" .. _name
            node.param2 = (node.param2 + 1) % 4
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.register_node("mcl_signs:standing_sign67_5" .. _name, ssign67_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign67_5" .. _name)

    -- register Doc entry
    if minetest.get_modpath("doc") then
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:wall_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign22_5" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign45" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign67_5" .. _name)
    end

    --register standing sign's rotation_levels
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign22_5" .. _name , 1})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign45" .. _name , 2})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign67_5" .. _name , 3})
end

--- The same as register_sign, except caller defines the textures. Note, there is a greyscale version of the sign,
--- called "default_sign_greyscale.png" and "mcl_signs_sign_greyscale.png" for optional use in the textures directory.
---
--- modname: optional (pass "" or "false" to ignore), for use with other mods to
--- allow the creation of a sign from the mod's wood (if installed).
---
--- _name: the sign's name suffix, such as "_dark" or "_red", etc., appended to "wall_sign" or "standing_sign"
---
--- tiles: the texture file to use for the sign.
---
--- color: color the texture file to use with this color. Use white (#FFFFFF) to negate the color,
--- and just use the texture as is
---
--- inventory_image: the texture file to use for the sign's display in inventory.
---
--- wield_image: the texture file to use for the sign's weilded (in hand) object.
---
--- inventory_image: the image used for in-inventory and in hand.
---
--- ttsign: the tool tip of the sign that gets translated. Shown when the mouse hovers the inventory sign.
--- For example: the basic, default oak (wood) sign is just "Sign"; and a spruce sign would be "Spruce Sign"
function mcl_signs.register_sign_custom (modname, _name, tiles, color, inventory_image, wield_image, ttsign)
    local mod_name_pass = false
    if modname ~= "" and modname ~= "false" then
        if minetest.get_modpath(modname) then
            mod_name_pass = true
        end
        if mod_name_pass == false then
            return
        end
    end
    local new_sign = {}

    new_sign = table.copy(mcl_signs.wall_standard)

    new_sign.wield_image ="("..wield_image.."^[multiply:" .. color .. ")"
    new_sign.tiles = { "("..tiles.."^[multiply:" .. color .. ")" }
    new_sign.inventory_image = "("..inventory_image.."^[multiply:" .. color .. ")"
    new_sign.description = S(ttsign)
    -- currently have to do this, because of how the base node placement works.
    new_sign.on_place = function(itemstack, placer, pointed_thing)
        local above = pointed_thing.above
        local under = pointed_thing.under

        -- Use pointed node's on_rightclick function first, if present
        local node_under = minetest.get_node(under)
        if placer and not placer:get_player_control().sneak then
            if minetest.registered_nodes[node_under.name] and minetest.registered_nodes[node_under.name].on_rightclick then
                return minetest.registered_nodes[node_under.name].on_rightclick(under, node_under, placer, itemstack) or itemstack
            end
        end

        local dir = vector.subtract(under, above)

        -- Only build when it's legal
        local abovenodedef = minetest.registered_nodes[minetest.get_node(above).name]
        if not abovenodedef or abovenodedef.buildable_to == false then
            return itemstack
        end

        local wdir = minetest.dir_to_wallmounted(dir)
        local fdir = minetest.dir_to_facedir(dir)

        local sign_info
        local nodeitem = ItemStack(itemstack)
        -- Ceiling
        if wdir == 0 then
            --how would you add sign to ceiling?
            return itemstack
            -- Floor
        elseif wdir == 1 then
            -- Standing sign

            -- Determine the sign rotation based on player's yaw
            local yaw = pi * 2 - placer:get_look_horizontal()

            -- Select one of 16 possible rotations (0-15)
            local rotation_level = mcl_signs:round((yaw / (pi * 2)) * 16)

            if rotation_level > 15 then
                rotation_level = 0
            elseif rotation_level < 0 then
                rotation_level = 15
            end

            -- The actual rotation is a combination of predefined mesh and facedir (see node definition)
            if rotation_level % 4 == 0 then
                nodeitem:set_name("mcl_signs:standing_sign" .. _name)
            elseif rotation_level % 4 == 1 then
                nodeitem:set_name("mcl_signs:standing_sign22_5" .. _name)
            elseif rotation_level % 4 == 2 then
                nodeitem:set_name("mcl_signs:standing_sign45" .. _name)
            elseif rotation_level % 4 == 3 then
                nodeitem:set_name("mcl_signs:standing_sign67_5" .. _name)
            end
            fdir = math.floor(rotation_level / 4)

            -- Place the node!
            local _, success = minetest.item_place_node(nodeitem, placer, pointed_thing, fdir)
            if not success then
                return itemstack
            end
            if not minetest.is_creative_enabled(placer:get_player_name()) then
                itemstack:take_item()
            end
            sign_info = mcl_signs.signtext_info_standing[rotation_level + 1]
            -- Side
        else
            -- Wall sign
            local _, success = minetest.item_place_node(itemstack, placer, pointed_thing, wdir)
            if not success then
                return itemstack
            end
            sign_info = mcl_signs.signtext_info_wall[fdir + 1]
        end

        -- Determine spawn position of entity
        local place_pos
        if minetest.registered_nodes[node_under.name].buildable_to then
            place_pos = under
        else
            place_pos = above
        end

        local text_entity = minetest.add_entity({
            x = place_pos.x + sign_info.delta.x,
            y = place_pos.y + sign_info.delta.y,
            z = place_pos.z + sign_info.delta.z }, "mcl_signs:text")
        text_entity:set_yaw(sign_info.yaw)
        text_entity:get_luaentity()._signnodename = nodeitem:get_name()

        minetest.sound_play({ name = "default_place_node_hard", gain = 1.0 }, { pos = place_pos }, true)

        mcl_signs:show_formspec(placer, place_pos)
        return itemstack
    end
    minetest.register_node("mcl_signs:wall_sign" .. _name, new_sign)
    update_sign_registry("wall", "mcl_signs:wall_sign" .. _name)

    -- standing sign base.
    local new_sign_standing = {}
    new_sign_standing = table.copy(mcl_signs.standing_standard)
    new_sign_standing.drop = "mcl_signs:wall_sign" .. _name
    new_sign_standing.wield_image ="("..wield_image.."^[multiply:" .. color .. ")"
    new_sign_standing.tiles = { "("..tiles.."^[multiply:" .. color .. ")" }
    new_sign_standing.inventory_image = "("..inventory_image.."^[multiply:" .. color .. ")"
    new_sign_standing.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign22_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end,
    minetest.register_node("mcl_signs:standing_sign" .. _name, new_sign_standing)
    update_sign_registry("standing", "mcl_signs:standing_sign" .. _name)

    -- 22.5°
    local ssign22_5d = table.copy(new_sign_standing)
    ssign22_5d.mesh = "mcl_signs_sign22.5.obj"
    ssign22_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign45" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.register_node("mcl_signs:standing_sign22_5" .. _name, ssign22_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign22_5" .. _name)

    -- 45°
    local ssign45d = table.copy(new_sign_standing)
    ssign45d.mesh = "mcl_signs_sign45.obj"
    ssign45d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign67_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.register_node("mcl_signs:standing_sign45" .. _name, ssign45d)
    update_sign_registry("standing", "mcl_signs:standing_sign45" .. _name)

    -- 67.5°
    local ssign67_5d = table.copy(new_sign_standing)
    ssign67_5d.mesh = "mcl_signs_sign67.5.obj"
    ssign67_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign" .. _name
            node.param2 = (node.param2 + 1) % 4
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.register_node("mcl_signs:standing_sign67_5" .. _name, ssign67_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign67_5" .. _name)

    -- register Doc entry
    if minetest.get_modpath("doc") then
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:wall_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign22_5" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign45" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign67_5" .. _name)
    end

    --register standing sign's rotation_levels
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign22_5" .. _name , 1})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign45" .. _name , 2})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign67_5" .. _name , 3})

end

--- Override an existing sign, tint the textures, and gives it an unique node name. Creates both wall and standing signs.
--- modname: optional (pass "" or "false" to ignore), for use with other mods to
--- allow the creation of a sign from the mod's wood (if installed).
---
--- color: the color code to color the base sign textures. must be a valid html color code.
---
--- _name: the sign's name suffix, such as "_dark" or "_red", etc., appended to "wall_sign" or "standing_sign"
---
--- ttsign: the tool tip of the sign that gets translated. Shown when the mouse hovers the inventory sign.
--- For example: the basic, default oak (wood) sign is just "Sign"; and a spruce sign would be "Spruce Sign"
function mcl_signs.reregister_sign (modname, color, _name, ttsign)
    local mod_name_pass = false
    if modname ~= "" and modname ~= "false" then
        if minetest.get_modpath(modname) then
            mod_name_pass = true
        end
        if mod_name_pass == false then
            return
        end
    end
    local new_sign = {}

    if color == nil or color == "" then
        color = "#FFFFFF"
    end

    new_sign = table.copy(mcl_signs.wall_standard)
    new_sign.description = S(ttsign)

    new_sign.wield_image = "(default_sign.png^[multiply:" .. color .. ")"
    new_sign.tiles = { "(mcl_signs_sign.png^[multiply:" .. color .. ")" }
    new_sign.inventory_image = "(default_sign.png^[multiply:" .. color .. ")"

    -- currently have to do this, because of how the base node placement works.
    new_sign.on_place = function(itemstack, placer, pointed_thing)
        local above = pointed_thing.above
        local under = pointed_thing.under

        -- Use pointed node's on_rightclick function first, if present
        local node_under = minetest.get_node(under)
        if placer and not placer:get_player_control().sneak then
            if minetest.registered_nodes[node_under.name] and minetest.registered_nodes[node_under.name].on_rightclick then
                return minetest.registered_nodes[node_under.name].on_rightclick(under, node_under, placer, itemstack) or itemstack
            end
        end

        local dir = vector.subtract(under, above)

        -- Only build when it's legal
        local abovenodedef = minetest.registered_nodes[minetest.get_node(above).name]
        if not abovenodedef or abovenodedef.buildable_to == false then
            return itemstack
        end

        local wdir = minetest.dir_to_wallmounted(dir)
        local fdir = minetest.dir_to_facedir(dir)

        local sign_info
        local nodeitem = ItemStack(itemstack)
        -- Ceiling
        if wdir == 0 then
            --how would you add sign to ceiling?
            return itemstack
            -- Floor
        elseif wdir == 1 then
            -- Standing sign

            -- Determine the sign rotation based on player's yaw
            local yaw = pi * 2 - placer:get_look_horizontal()

            -- Select one of 16 possible rotations (0-15)
            local rotation_level = mcl_signs:round((yaw / (pi * 2)) * 16)

            if rotation_level > 15 then
                rotation_level = 0
            elseif rotation_level < 0 then
                rotation_level = 15
            end

            -- The actual rotation is a combination of predefined mesh and facedir (see node definition)
            if rotation_level % 4 == 0 then
                nodeitem:set_name("mcl_signs:standing_sign" .. _name)
            elseif rotation_level % 4 == 1 then
                nodeitem:set_name("mcl_signs:standing_sign22_5" .. _name)
            elseif rotation_level % 4 == 2 then
                nodeitem:set_name("mcl_signs:standing_sign45" .. _name)
            elseif rotation_level % 4 == 3 then
                nodeitem:set_name("mcl_signs:standing_sign67_5" .. _name)
            end
            fdir = math.floor(rotation_level / 4)

            -- Place the node!
            local _, success = minetest.item_place_node(nodeitem, placer, pointed_thing, fdir)
            if not success then
                return itemstack
            end
            if not minetest.is_creative_enabled(placer:get_player_name()) then
                itemstack:take_item()
            end
            sign_info = mcl_signs.signtext_info_standing[rotation_level + 1]
            -- Side
        else
            -- Wall sign
            local _, success = minetest.item_place_node(itemstack, placer, pointed_thing, wdir)
            if not success then
                return itemstack
            end
            sign_info = mcl_signs.signtext_info_wall[fdir + 1]
        end

        -- Determine spawn position of entity
        local place_pos
        if minetest.registered_nodes[node_under.name].buildable_to then
            place_pos = under
        else
            place_pos = above
        end

        if DEBUG then
            minetest.log("action", "[mcl_signs] Register_Sign::Placed position:" .. dump(place_pos) .. "\nSign_info: " .. dump(sign_info))
        end

        local text_entity = minetest.add_entity({
            x = place_pos.x + sign_info.delta.x,
            y = place_pos.y + sign_info.delta.y,
            z = place_pos.z + sign_info.delta.z }, "mcl_signs:text")
        text_entity:set_yaw(sign_info.yaw)
        text_entity:get_luaentity()._signnodename = nodeitem:get_name()

        minetest.sound_play({ name = "default_place_node_hard", gain = 1.0 }, { pos = place_pos }, true)

        mcl_signs:show_formspec(placer, place_pos)
        return itemstack
    end

    minetest.override_item("mcl_signs:wall_sign" .. _name, new_sign)
    update_sign_registry("wall", "mcl_signs:wall_sign" .. _name)

    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Registered: mcl_signs:wall_sign" .. _name .. color .. "\n" .. dump(new_sign))
        minetest.log("action", "[mcl_signs] mcl_signs:wall_sign_standard\n" .. dump(mcl_signs.wall_standard))
    end

    -- standing sign base.
    local new_sign_standing = {}
    new_sign_standing = table.copy(mcl_signs.standing_standard)
    new_sign_standing.drop = "mcl_signs:wall_sign" .. _name
    new_sign_standing.wield_image = "(default_sign.png^[multiply:" .. color .. ")"
    new_sign_standing.tiles = { "(mcl_signs_sign.png^[multiply:" .. color .. ")" }
    new_sign_standing.inventory_image = "(default_sign.png^[multiply:" .. color .. ")"
    new_sign_standing.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign22_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end,
    minetest.override_item("mcl_signs:standing_sign" .. _name, new_sign_standing)
    update_sign_registry("standing", "mcl_signs:standing_sign" .. _name)
    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Registered: mcl_signs:standing_sign" .. _name .. color .. "\n" .. dump(new_sign_standing))
    end

    -- 22.5°
    local ssign22_5d = table.copy(new_sign_standing)
    ssign22_5d.mesh = "mcl_signs_sign22.5.obj"
    ssign22_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign45" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.override_item("mcl_signs:standing_sign22_5" .. _name, ssign22_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign22_5" .. _name)

    -- 45°
    local ssign45d = table.copy(new_sign_standing)
    ssign45d.mesh = "mcl_signs_sign45.obj"
    ssign45d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign67_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.override_item("mcl_signs:standing_sign45" .. _name, ssign45d)
    update_sign_registry("standing", "mcl_signs:standing_sign45" .. _name)

    -- 67.5°
    local ssign67_5d = table.copy(new_sign_standing)
    ssign67_5d.mesh = "mcl_signs_sign67.5.obj"
    ssign67_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign" .. _name
            node.param2 = (node.param2 + 1) % 4
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.override_item("mcl_signs:standing_sign67_5" .. _name, ssign67_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign67_5" .. _name)

    -- register Doc entry
    if minetest.get_modpath("doc") then
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:wall_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign22_5" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign45" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign67_5" .. _name)
    end

    --register standing sign's rotation_levels
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign22_5" .. _name , 1})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign45" .. _name , 2})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign67_5" .. _name , 3})
end

--- The same as reregister_sign, except caller defines the textures. Note, there is a greyscale version of the sign,
--- called "default_sign_greyscale.png" and "mcl_signs_sign_greyscale.png" for optional use in the textures directory.
---
--- modname: optional (pass "" or "false" to ignore), for use with other mods to
--- allow the creation of a sign from the mod's wood (if installed).
---
--- _name: the sign's name suffix, such as "_dark" or "_red", etc., appended to "wall_sign" or "standing_sign"
---
--- tiles: the texture file to use for the sign.
---
--- color: color the texture file to use with this color. Use white (#FFFFFF) to negate the color,
--- and just use the texture as is
---
--- inventory_image: the texture file to use for the sign's display in inventory.
---
--- wield_image: the texture file to use for the sign's weilded (in hand) object.
---
--- inventory_image: the image used for in-inventory and in hand.
---
--- ttsign: the tool tip of the sign that gets translated. Shown when the mouse hovers the inventory sign.
--- For example: the basic, default oak (wood) sign is just "Sign"; and a spruce sign would be "Spruce Sign"
function mcl_signs.reregister_sign_custom (modname, _name, tiles, color, inventory_image, wield_image, ttsign)
    local mod_name_pass = false
    if modname ~= "" and modname ~= "false" then
        if minetest.get_modpath(modname) then
            mod_name_pass = true
        end
        if mod_name_pass == false then
            return
        end
    end
    local new_sign = {}

    new_sign = table.copy(mcl_signs.wall_standard)

    new_sign.wield_image ="("..wield_image.."^[multiply:" .. color .. ")"
    new_sign.tiles = { "("..tiles.."^[multiply:" .. color .. ")" }
    new_sign.inventory_image = "("..inventory_image.."^[multiply:" .. color .. ")"
    new_sign.description = S(ttsign)
    -- currently have to do this, because of how the base node placement works.
    new_sign.on_place = function(itemstack, placer, pointed_thing)
        local above = pointed_thing.above
        local under = pointed_thing.under

        -- Use pointed node's on_rightclick function first, if present
        local node_under = minetest.get_node(under)
        if placer and not placer:get_player_control().sneak then
            if minetest.registered_nodes[node_under.name] and minetest.registered_nodes[node_under.name].on_rightclick then
                return minetest.registered_nodes[node_under.name].on_rightclick(under, node_under, placer, itemstack) or itemstack
            end
        end

        local dir = vector.subtract(under, above)

        -- Only build when it's legal
        local abovenodedef = minetest.registered_nodes[minetest.get_node(above).name]
        if not abovenodedef or abovenodedef.buildable_to == false then
            return itemstack
        end

        local wdir = minetest.dir_to_wallmounted(dir)
        local fdir = minetest.dir_to_facedir(dir)

        local sign_info
        local nodeitem = ItemStack(itemstack)
        -- Ceiling
        if wdir == 0 then
            --how would you add sign to ceiling?
            return itemstack
            -- Floor
        elseif wdir == 1 then
            -- Standing sign

            -- Determine the sign rotation based on player's yaw
            local yaw = pi * 2 - placer:get_look_horizontal()

            -- Select one of 16 possible rotations (0-15)
            local rotation_level = mcl_signs:round((yaw / (pi * 2)) * 16)

            if rotation_level > 15 then
                rotation_level = 0
            elseif rotation_level < 0 then
                rotation_level = 15
            end

            -- The actual rotation is a combination of predefined mesh and facedir (see node definition)
            if rotation_level % 4 == 0 then
                nodeitem:set_name("mcl_signs:standing_sign" .. _name)
            elseif rotation_level % 4 == 1 then
                nodeitem:set_name("mcl_signs:standing_sign22_5" .. _name)
            elseif rotation_level % 4 == 2 then
                nodeitem:set_name("mcl_signs:standing_sign45" .. _name)
            elseif rotation_level % 4 == 3 then
                nodeitem:set_name("mcl_signs:standing_sign67_5" .. _name)
            end
            fdir = math.floor(rotation_level / 4)

            -- Place the node!
            local _, success = minetest.item_place_node(nodeitem, placer, pointed_thing, fdir)
            if not success then
                return itemstack
            end
            if not minetest.is_creative_enabled(placer:get_player_name()) then
                itemstack:take_item()
            end
            sign_info = mcl_signs.signtext_info_standing[rotation_level + 1]
            -- Side
        else
            -- Wall sign
            local _, success = minetest.item_place_node(itemstack, placer, pointed_thing, wdir)
            if not success then
                return itemstack
            end
            sign_info = mcl_signs.signtext_info_wall[fdir + 1]
        end

        -- Determine spawn position of entity
        local place_pos
        if minetest.registered_nodes[node_under.name].buildable_to then
            place_pos = under
        else
            place_pos = above
        end

        local text_entity = minetest.add_entity({
            x = place_pos.x + sign_info.delta.x,
            y = place_pos.y + sign_info.delta.y,
            z = place_pos.z + sign_info.delta.z }, "mcl_signs:text")
        text_entity:set_yaw(sign_info.yaw)
        text_entity:get_luaentity()._signnodename = nodeitem:get_name()

        minetest.sound_play({ name = "default_place_node_hard", gain = 1.0 }, { pos = place_pos }, true)

        mcl_signs:show_formspec(placer, place_pos)
        return itemstack
    end
    minetest.override_item("mcl_signs:wall_sign" .. _name, new_sign)
    update_sign_registry("wall", "mcl_signs:wall_sign" .. _name)

    -- standing sign base.
    local new_sign_standing = {}
    new_sign_standing = table.copy(mcl_signs.standing_standard)
    new_sign_standing.drop = "mcl_signs:wall_sign" .. _name
    new_sign_standing.wield_image ="("..wield_image.."^[multiply:" .. color .. ")"
    new_sign_standing.tiles = { "("..tiles.."^[multiply:" .. color .. ")" }
    new_sign_standing.inventory_image = "("..inventory_image.."^[multiply:" .. color .. ")"
    new_sign_standing.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign22_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end,
    minetest.override_item("mcl_signs:standing_sign" .. _name, new_sign_standing)
    update_sign_registry("standing", "mcl_signs:standing_sign" .. _name)

    -- 22.5°
    local ssign22_5d = table.copy(new_sign_standing)
    ssign22_5d.mesh = "mcl_signs_sign22.5.obj"
    ssign22_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign45" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.override_item("mcl_signs:standing_sign22_5" .. _name, ssign22_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign22_5" .. _name)

    -- 45°
    local ssign45d = table.copy(new_sign_standing)
    ssign45d.mesh = "mcl_signs_sign45.obj"
    ssign45d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign67_5" .. _name
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.override_item("mcl_signs:standing_sign45" .. _name, ssign45d)
    update_sign_registry("standing", "mcl_signs:standing_sign45" .. _name)

    -- 67.5°
    local ssign67_5d = table.copy(new_sign_standing)
    ssign67_5d.mesh = "mcl_signs_sign67.5.obj"
    ssign67_5d.on_rotate = function(pos, node, user, mode)
        if mode == screwdriver.ROTATE_FACE then
            node.name = "mcl_signs:standing_sign" .. _name
            node.param2 = (node.param2 + 1) % 4
            minetest.swap_node(pos, node)
        elseif mode == screwdriver.ROTATE_AXIS then
            return false
        end
        mcl_signs:update_sign(pos, nil, nil, true)
        return true
    end
    minetest.override_item("mcl_signs:standing_sign67_5" .. _name, ssign67_5d)
    update_sign_registry("standing", "mcl_signs:standing_sign67_5" .. _name)

    -- register Doc entry
    if minetest.get_modpath("doc") then
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:wall_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign22_5" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign45" .. _name)
        doc.add_entry_alias("nodes", "mcl_signs:wall_sign", "nodes", "mcl_signs:standing_sign67_5" .. _name)
    end

    --register standing sign's rotation_levels
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign22_5" .. _name , 1})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign45" .. _name , 2})
    table.insert(mcl_signs.standing_rotation_levels, {"mcl_signs:standing_sign67_5" .. _name , 3})

end

--- Usage: Call this with the mod's name, the wood's item string (for the planks), and with the sign's suffix.
--- Registers the crafting recipe for that sign. for every registered sign, call this function to register the
--- standard recipe for the sign. Otherwise, you have to do your own register craft call.
---
--- modname: optional (pass "" or "false" to ignore), for use with other mods to
--- allow the creation of a sign from the mod's wood (if installed). Example: "mcl_core".
---
--- wood_item_string: example: "mcl_core:wood" or "mcl_core:sprucewood"
---
--- _name: the sign's name suffix, such as "_dark" or "_red", etc., appended to "wall_sign" or "standing_sign"
function mcl_signs.register_sign_craft(modname, wood_item_string, _name)
    local mod_name_pass = false
    if modname ~= "" and modname ~= "false" then
        if minetest.get_modpath(modname) then
            mod_name_pass = true
        end
        if mod_name_pass == false then
            return
        end
    end

    minetest.register_craft({
        type = "fuel",
        recipe = "mcl_signs:wall_sign" .. _name,
        burntime = 10,
    })

    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Register Sign Crafts: \n" .. modname .. "\n" .. wood_item_string .. "\n" .. _name)
    end

    -- register crafts (actual recipe)
    if minetest.get_modpath(modname) then

        local itemstring = "mcl_signs:wall_sign"

        minetest.register_craft({
            output = itemstring .. _name .. " 3",
            recipe = {
                { wood_item_string, wood_item_string, wood_item_string },
                { wood_item_string, wood_item_string, wood_item_string },
                { "", "mcl_core:stick", "" },
            },
        })
    end

end

-- Helper functions
local function string_to_array(str)
    local string_table = {}
    for i = 1, string.len(str) do
        table.insert(string_table, string.sub(str, i, i))
    end
    return string_table
end

local function string_to_line_array(str)
    local linechar_table = {}
    local current = 1
    local linechar = 1
    linechar_table[1] = ""
    for _, char in ipairs(string_to_array(str)) do
        -- New line
        if char == "\n" then
            current = current + 1
            linechar_table[current] = ""
            linechar = 1
        else
            linechar_table[current] = linechar_table[current] .. char
            linechar = linechar + 1
        end
    end
    return linechar_table
end

local function get_rotation_level(facedir, nodename)
    local nnames = mcl_signs.standing_rotation_levels -- functional copy... was easier this way. #LazyAF :P

    local rl
    local offset = 0
    for x = 1, #nnames do
        if nnames[x][1] == nodename then
            offset = nnames[x][2]
            break
        end
    end
    rl = facedir * 4 + offset
    if DEBUG then
        minetest.log("action", "[mcl_signs] GetRotationLevel: NodeName: " .. nodename .. " RL value: " .. rl)
    end
    return rl
end

function mcl_signs:round(num, idp)
    local mult = 10 ^ (idp or 0)
    return math.floor(num * mult + 0.5) / mult
end

function mcl_signs:get_color_for_sign(item_name)

    for d = 1, #Dyes_table do
        if Dyes_table[d][1] == item_name then
            return Dyes_table[d][2]
        end
    end
    return "false"
end

function mcl_signs:color_sign (pos, text_color)

    local success = mcl_signs:update_sign(pos, nil, nil, true, text_color)

    -- debug step
    local meta = minetest.get_meta(pos)
    if not meta then
        minetest.log("error", "[mcl_signs] Sign Color Fail - Metadata.")

        return false
    end
    if DEBUG then
        minetest.log("verbose", "[mcl_signs] Post-Sign Color: " .. meta:get_string("mcl_signs:text_color") .. " " .. meta:get_string("mcl_signs:glowing_sign") .. ".\n" .. dump(pos))
    end

    return success

end

function mcl_signs:glow_sign (pos, remove_glow)
    local success = true
    -- Get Meta Data for the sign.
    local meta = minetest.get_meta(pos)

    if not meta then
        return false
    end
    local text = meta:get_string("text")
    if text == nil then
        text = ""
    end

    -- we can't make the text glow if there isn't any text
    if text == "" then
        return false
    end

    if remove_glow == nil then
        remove_glow = false
    end

    -- set up text glow
    local objects = minetest.get_objects_inside_radius(pos, 0.5)
    local text_entity
    for _, v in ipairs(objects) do
        local ent = v:get_luaentity()
        if ent and ent.name == "mcl_signs:text" then
            text_entity = v
            break
        end
    end
    if remove_glow == true then
        text_entity:set_properties({
            glow = nil,
        })
        meta:set_string("mcl_signs:glowing_sign", "false")
    else
        text_entity:set_properties({
            glow = sign_glow,
        })
        meta:set_string("mcl_signs:glowing_sign", "true")
    end
    if not text_entity then
        return false
    end
    text_entity:get_luaentity()._glowing_sign = meta:get_string("mcl_signs:glowing_sign")

    -- debug step
    if DEBUG then
        minetest.log("verbose", "[mcl_signs] Post-Sign Glow: " .. meta:get_string("mcl_signs:text_color") .. " " .. meta:get_string("mcl_signs:glowing_sign") .. ".\n" .. dump(pos))
    end
    return success
end

function mcl_signs:create_lettering(text, signnodename, sign_color)
    if sign_color == nil then
        sign_color = mcl_colors.BLACK
    end
    local texture = mcl_signs:generate_texture(mcl_signs:create_lines(text), signnodename, sign_color)

    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Creating sign text; text:" .. text)
    end

    return texture
end

function mcl_signs:create_lines(text)
    local line_num = 1
    local text_table = {}
    for _, line in ipairs(string_to_line_array(text)) do
        if line_num > NUMBER_OF_LINES then
            break
        end
        table.insert(text_table, line)
        line_num = line_num + 1
    end
    return text_table
end

function mcl_signs:generate_line(s, ypos)
    local i = 1
    local parsed = {}
    local width = 0
    local chars = 0
    local printed_char_width = CHAR_WIDTH + 1
    while chars < LINE_LENGTH and i <= #s do
        local file
        -- Get and render character
        if charmap[s:sub(i, i)] then
            file = charmap[s:sub(i, i)]
            i = i + 1
        elseif i < #s and charmap[s:sub(i, i + 1)] then
            file = charmap[s:sub(i, i + 1)]
            i = i + 2
        else
            -- No character image found.
            -- Use replacement character:
            file = "_rc"
            i = i + 1
            if DEBUG then
                minetest.log("verbose", "[mcl_signs] Unknown symbol in '" .. s .. "' at " .. i)
            end
        end
        if file then
            width = width + printed_char_width
            table.insert(parsed, file)
            chars = chars + 1
        end
    end
    width = width - 1

    local texture = ""
    local xpos = math.floor((SIGN_WIDTH - width) / 2)

    for j = 1, #parsed do
        texture = texture .. ":" .. xpos .. "," .. ypos .. "=" .. parsed[j] .. ".png"
        xpos = xpos + printed_char_width
    end
    return texture
end

function mcl_signs:generate_texture(lines, signnodename, letter_color)
    local texture = "[combine:" .. SIGN_WIDTH .. "x" .. SIGN_WIDTH
    local ypos = 0

    -- Handle all of the dynamically created signs.
    for x = 1, #mcl_signs.registered_signs.wall_signs do
        if signnodename == mcl_signs.registered_signs.wall_signs[x] then
            ypos = 30
            break
        end
    end
    for x = 1, #mcl_signs.registered_signs.standing_signs do
        if signnodename == mcl_signs.registered_signs.standing_signs[x] then
            ypos = 0
            break
        end
    end
    -- for future inclusion, when the hanging sings are made.
    --[[
    for x = 1, #mcl_signs.registered_signs.hanging_signs do
        if signnodename == mcl_signs.registered_signs.hanging_signs[x] then
            ypos = 30
            break
        end
    end
    ]]

    -- kept in for now, compatibility with existing hard coded signs. TODO: Remove after done with api.
    if signnodename == "mcl_signs:wall_sign" or signnodename == "mcl_signs:wall_sign_dark" then
        ypos = 30
    end

    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Generate_Texture::Debug_Data:\nSignNodeName: " .. dump(signnodename) .. "\nYPOS: " .. ypos)
    end

    for i = 1, #lines do
        texture = texture .. mcl_signs:generate_line(lines[i], ypos)
        ypos = ypos + LINE_HEIGHT
    end

    texture = "(" .. texture .. "^[multiply:" .. letter_color .. ")"
    return texture
end

function mcl_signs:get_wall_signtext_info(param2, nodename)
    local dir = minetest.wallmounted_to_dir(param2)
    if dir.x > 0 then
        return 2
    elseif dir.z > 0 then
        return 1
    elseif dir.x < 0 then
        return 4
    else
        return 3
    end
end

function mcl_signs:destruct_sign(pos)
    local objects = minetest.get_objects_inside_radius(pos, 0.5)
    for _, v in ipairs(objects) do
        local ent = v:get_luaentity()
        if ent and ent.name == "mcl_signs:text" then
            v:remove()
        end
    end
    local players = minetest.get_connected_players()
    for p = 1, #players do
        if vector.distance(players[p]:get_pos(), pos) <= 30 then
            minetest.close_formspec(players[p]:get_player_name(), "mcl_signs:set_text_" .. pos.x .. "_" .. pos.y .. "_" .. pos.z)
        end
    end
end

function mcl_signs:update_sign(pos, fields, sender, force_remove, text_color)
    -- Get Meta Data for the sign.
    local meta = minetest.get_meta(pos)

    if not meta then
        return false
    end
    local text = meta:get_string("text")
    if fields and (text == "" and fields.text) then
        meta:set_string("text", fields.text)
        text = fields.text
    end
    if text == nil then
        text = ""
    end

    -- find text color.
    local sign_color

    if meta:get_string("mcl_signs:text_color") == "" then
        -- if no sign text color has been assigned, make it black.
        sign_color = mcl_colors.BLACK
        meta:set_string("mcl_signs:text_color", sign_color)
    else
        sign_color = meta:get_string("mcl_signs:text_color")
    end

    if text_color == nil or text == "" then
        text_color = "false"
    end

    if text_color == "false" then
        text_color = sign_color --if a new color hasn't been chosen, then keep the existing color.
    end

    -- find the sign's glow value
    local has_glow = false

    if meta:get_string("mcl_signs:glowing_sign") == "" or meta:get_string("mcl_signs:glowing_sign") == "false" then
        has_glow = false
        meta:set_string("mcl_signs:glowing_sign", "false")
    else
        has_glow = true
    end

    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Update_Signs: Pre-Sign Update: " .. sign_color .. " " .. meta:get_string("mcl_signs:glowing_sign") .. ".\n" .. dump(pos))
    end

    local sign_info
    local npos = minetest.get_node(pos)
    local npos_name = npos.name

    -- Handle all of the dynamically created signs.
    for x = 1, #mcl_signs.registered_signs.wall_signs do
        if npos_name == mcl_signs.registered_signs.wall_signs[x] then
            sign_info = mcl_signs.signtext_info_wall[mcl_signs:get_wall_signtext_info(npos.param2)]
            break
        end
    end
    for x = 1, #mcl_signs.registered_signs.standing_signs do
        if npos_name == mcl_signs.registered_signs.standing_signs[x] then
            sign_info = mcl_signs.signtext_info_standing[get_rotation_level(npos.param2, npos_name) + 1]
            break
        end
    end
    -- for future inclusion, when the hanging sings are made.
    --[[
    for x = 1, #mcl_signs.registered_signs.hanging_signs do
        if nn == mcl_signs.registered_signs.hanging_signs[x] then
            sign_info = mcl_signs.signtext_info_wall[mcl_signs:get_wall_signtext_info(n.param2)]
            break
        end
    end
    ]]

    -- the following if..elseif..end block is here for compatibility with the old code. TODO: remove this block after the new api is complete.
    if npos_name == "mcl_signs:standing_sign_dark" or npos_name == "mcl_signs:standing_sign22_5_dark" or npos_name == "mcl_signs:standing_sign45_dark" or npos_name == "mcl_signs:standing_sign67_5_dark" then
        sign_info = mcl_signs.signtext_info_standing[get_rotation_level(npos.param2, npos_name) + 1]
    elseif npos_name == "mcl_signs:wall_sign_dark" then
        sign_info = mcl_signs.signtext_info_wall[mcl_signs:get_wall_signtext_info(npos.param2)]
    end
    if sign_info == nil then
        minetest.log("error", "[mcl_signs] Update_Signs: Missing sign_info!")
        return false
    end

    local objects = minetest.get_objects_inside_radius(pos, 0.5)
    local text_entity
    for _, v in ipairs(objects) do
        local ent = v:get_luaentity()
        if ent and ent.name == "mcl_signs:text" then
            if force_remove then
                v:remove()
            else
                text_entity = v
                break
            end
        end
    end

    if not text_entity then
        if DEBUG then
            minetest.log("action", "[mcl_signs] Update_Sign: Text_Entity - does not exist, creating it now.")
        end
        text_entity = minetest.add_entity({
            x = pos.x + sign_info.delta.x,
            y = pos.y + sign_info.delta.y,
            z = pos.z + sign_info.delta.z }, "mcl_signs:text")

        if DEBUG then
            minetest.log("action", "[mcl_signs] Update_Sign: Placed position:" .. dump(pos) .. "\nSign_info: " .. dump(sign_info))
        end
    end
    text_entity:get_luaentity()._signnodename = npos_name

    -- set up special case: Dark Oak Sign. Dark Oak signs are soooo dark, they start off with white lettering.
    if npos_name == "mcl_signs:wall_sign_darkwood" or
            npos_name == "mcl_signs:standing_sign67_5_darkwood" or
            npos_name == "mcl_signs:standing_sign45_darkwood" or
            npos_name == "mcl_signs:standing_sign22_5_darkwood" or
            npos_name == "mcl_signs:standing_sign_darkwood"
    then
        if text_color == "#000000" then
            text_color = "#ffffff"
        end
    end

    -- Set the actual properties for the sign

    text_entity:set_properties({
        textures = { mcl_signs:create_lettering(text, npos_name, text_color) },
    })

    if has_glow then
        text_entity:set_properties({
            glow = sign_glow,
        })
    end

    text_entity:set_yaw(sign_info.yaw)
    if DEBUG then
        minetest.log("verbose", "[mcl_signs] Update_Sign: After texture recreation.")
        minetest.log("action","[mcl_signs] Update_Sign: " .. npos_name .. "\nPlaced position:" .. dump(pos) .. "\nSign_info: " .. dump(sign_info))
    end

    -- save sign metadata.
    meta:set_string("mcl_signs:text_color", text_color)
    -- debug step
    if DEBUG then
        minetest.log("action", "[mcl_signs] Update_Sign: Post-Sign Update: " .. meta:get_string("mcl_signs:text_color") .. " " .. meta:get_string("mcl_signs:glowing_sign") .. ".\n" .. dump(pos))
    end

    return true

end

function mcl_signs:show_formspec(player, pos)
    minetest.show_formspec(
            player:get_player_name(),
            "mcl_signs:set_text_" .. pos.x .. "_" .. pos.y .. "_" .. pos.z,
            "size[6,3]textarea[0.25,0.25;6,1.5;text;" .. F(S("Enter sign text:")) .. ";]label[0,1.5;" .. F(S("Maximum line length: 15")) .. "\n" .. F(S("Maximum lines: 4")) .. "]button_exit[0,2.5;6,1;submit;" .. F(S("Done")) .. "]"
    )
end