selfcare = {}
selfcare.func = {}
selfcare.form = {}
selfcare.huds = {}
local random = math.random
local tn = tonumber
local ts = tostring

selfcare.deadly_breath = minetest.settings:get_bool("selfcare.deadly_breath") or false


--	[ registering ]

function selfcare.add_product(name, def)
    minetest.register_tool(name, def)
    def.product_uses = def.product_uses
end

function selfcare.add_block(name, def)
    minetest.register_node(name, def)
end

function selfcare.add_item(name, def)
    minetest.register_craftitem(name, def)
end

function selfcare.add_craft(def)
    minetest.register_craft(def)
end

function selfcare.add_command(name, def)
    minetest.register_chatcommand(name, {
        description = def.info,
        func = function(param)
            return def.func(param)
        end,
    })
end

function selfcare.send(name, message, color)
    local plyr = minetest.get_player_by_name(name)
    local hex
    if not color or color == nil then hex = "FFFFFF" end
    if color == "bad" then
	hex = "0xD53130"
    elseif color == "warning" then
	hex = "0xFFA755"
    elseif color == "info" then
	hex = "0xFFF0A6"
    elseif color == "yay" then
	hex = "0x9BD4FF"
    elseif color == "positive" then
	hex = "0xB8E993"
    end

    if selfcare.huds[name] then
	plyr:hud_remove(selfcare.huds[name])
	selfcare.huds[name] = nil
    end

    --minetest.chat_send_player(name, minetest.colorize(hex, message))

   local hud = plyr:hud_add({
	hud_elem_type = "text",
	position  = {x = 0.5, y = 0.5},
	offset    = {x = 0, y = 30},
	text      = message,
	number = hex,
	scale     = { x = 5, y = 5},
	textsize = 15,
	alignment = { x = 0, y = 0 },
    })

    selfcare.huds[name] = hud

    minetest.after(2.5, function()
	if plyr and selfcare.huds[name] then
	    plyr:hud_remove(selfcare.huds[name])
	    selfcare.huds[name] = nil
	end
    end)
end

function selfcare.sendall(message)
    minetest.chat_send_all(message)
end

function selfcare.use_item(item, uses, remove)
    if not remove then remove = 1 end
    if not uses then return end
    local meta = item:get_meta()
    local used = meta:get_float("used")

    used = math.min(used + remove, uses)
    meta:set_float("used", used)

    local uses_left = uses - used
    local desc = item:get_short_description()
    local clr = minetest.colorize

    desc = desc .. "\n"..clr("#A1A1A1", " uses left: "..math.floor(uses_left))
    meta:set_string("description", desc)

    if uses_left <= 0 then
	item:take_item()
    end

    return item
end
    

--	[ setting/getting attributes ]

function selfcare.set(who, what, to)
    who:set_attribute(what, to)
end

function selfcare.get(who, what)
    return who:get_attribute(what)
end



--	[ idk ]



local function ts_state(val, type)
    local words = {
	body = {"healthy", "at average health", "feeling rough", "neglected"},
	pimples = {"blemish-free", "is slightly blemished", "is breaking out", "is breaking out a lot"},
	mouth = {"fresh", "clean", "unclean", "unsanitary"},
    }

    local list = words[type]

    if val > 30 then return list[1]
    elseif val > 19 then return list[2]
    elseif val > 9 then return list[3]
    else return list[4] end
end

local function get_overall_health(plyr)
    local to = selfcare.get(plyr, "tongue") or "pink"
    local b = selfcare.get(plyr, "breath") or "minty"
    local p = tn(selfcare.get(plyr, "pimples")) or 0
    local _t = selfcare.get(plyr, "_teeth") or "nothing"
    local bdr = selfcare.get(plyr, "b_dryness") or "smooth"
    local bdi = selfcare.get(plyr, "b_dirtiness") or "clean"
    local bi = selfcare.get(plyr, "b_itchiness") or "not"
    local __bs, __p, __m = 0, 0, 0

    local bs_tbl, m_tbl = {
	bdr = {
	    ["smooth"] = 12, ["soft"] = 12, ["normal"] = 10, ["slightly dry"] = 6,
	    ["slightly flaky"] = 6, ["flaky"] = 3
	},
	bdi = {
	    ["clean"] = 10, ["neutral"] = 10, ["slightly unclean"] = 6, ["slightly dirty"] = 6,
	    ["dirty"] = 3, ["grimy"] = 3
	},
	bi = {
	    ["not"] = 12, ["a bit"] = 10, ["very"] = 6, ["irritatingly"] = 2,
	    ["concerningly"] = 0, ["is"] = 8
	},
    }, {
	to = {
	    ["pink"] = 14, ["peach"] = 10, ["yellow"] = 5
	},
	_t = {
	    ["nothing"] = 15, ["some dirt"] = 11, ["build-up"] = 11, ["plaque"] = 6,
	    ["a plaque colony"] = 2
	},
	b = {
	    ["minty"] = 12, ["neutral"] = 9, ["slightly bad"] = 7, ["bad"] = 4,
	    ["disgusting"] = 0
	},
    }

    __bs = bs_tbl.bdr[bdr] + bs_tbl.bdi[bdi] + bs_tbl.bi[bi]
    __m = m_tbl.to[to] + m_tbl._t[_t] + m_tbl.b[b]
    __p = (p > 4) and 5 or (p > 2) and 13 or (p > 0) and 23 or (p == 0) and 34

    local body = ts_state(__bs, "body")
    local pimples = ts_state(__p, "pimples")
    local mouth = ts_state(__m, "mouth")

    return {bskin = body, pimples = pimples, mouth = mouth}
end



--	[ formspecs ]

function selfcare.form.SimpleHygiene(name)
    local plyr = minetest.get_player_by_name(name)

    if not selfcare.get(plyr, "selfcare") then 
	selfcare.send(name, "something went wrong! type /reset_selfcare and rejoin to fix. if you still get this message/can't check the mirror, then tell me.", "warning")
    return end

    local _s = get_overall_health(plyr)
    local skin = _s.bskin
    local p = _s.pimples
    local m = _s.mouth

    local fs = selfcare.get(plyr, "faceskin") or "clean"
    local _fs = selfcare.get(plyr, "_faceskin") or "soft"
    local hair1 = selfcare.get(plyr, "condish") or "soft"
    local hair2 = selfcare.get(plyr, "hair") or "clean"
    local hair3 = selfcare.get(plyr, "hair_tex") or "wavy"
    local s = selfcare.get(plyr, "smell") or "clean"
    local pf = selfcare.get(plyr, "perfume")

    if not pf then pf = "" else pf = pf .." " end

    local shown_s = (s == "neutral") and "Haven't showered." or (s == "bad") and "I need to shower!" or "I smell "..s.."."
    local rawteeth = selfcare.get(plyr, "teeth") or "white"
    local teeth = (rawteeth == "slightly yellow") and "beige" or rawteeth
    local clr = minetest.colorize

    local form = "size[5.5,1.8]" ..
	"background[4.5,2;0,0;selfcare_bg.png;false]" ..
        "label[-0.15,-0.1;"..clr("#FFFFFF", shown_s.." My skin is "..skin)..".]" ..
        "label[-0.15,0.3;"..clr("#FFFFFF", "My face is "..fs..", ".._fs..", and "..p)..".]" ..
        "label[-0.15,0.7;"..clr("#FFFFFF", "I have "..hair1.." and "..hair2.." "..hair3.." hair")..".]" ..
        "label[-0.15,1.1;"..clr("#FFFFFF", "My teeth look "..teeth.." and my mouth is "..m)..".]" ..
        "label[-0.1,1.5;"..clr("#FFFFFF", "I'm "..pf.."wearing perfume")..".]" ..
    "image_button_exit[5.2,-0.3;0.6,0.6;selfcare_button.png^[opacity:200;exit;"..clr("#55423E", "X").."]"

    minetest.show_formspec(name, "self_care:hygiene", form)
    return form
end






--	[ initializing ]

local hairs = {"straight", "wavy", "curly"}

function selfcare.func.init(p)
    selfcare.set(p, "selfcare", "1")
    selfcare.set(p, "smell", "clean")
    selfcare.set(p, "hair", "clean")
    selfcare.set(p, "condish", "soft")
    selfcare.set(p, "teeth", "white")
    selfcare.set(p, "_teeth", "nothing") -- in between teeth
    selfcare.set(p, "breath", "minty")
    selfcare.set(p, "pimples", "0")
    selfcare.set(p, "pimpatch", "no")
    selfcare.set(p, "faceskin", "clean")
    selfcare.set(p, "_faceskin", "soft")
    selfcare.set(p, "perfume", "not")
    selfcare.set(p, "b_dryness", "smooth")
    selfcare.set(p, "b_dirtiness", "clean") -- dead skin cells
    selfcare.set(p, "b_itchiness", "not")
    selfcare.set(p, "tongue", "pink")
    selfcare.set(p, "t_whitening", "no")
    selfcare.set(p, "hair_type", hairs[random(3)])
    selfcare.set(p, "hair_tex", selfcare.get(p, "hair_type"))
    selfcare.func.SetSkinType(p)
end

minetest.register_on_joinplayer(function(player)
    local n = player:get_player_name()
    local heat = {
	protectant = "no",
	temp = 30,
	target_temp = 180,
	heat_tool = "off",
	heating = "not"
    }
    selfcare.set(player, "hair_heating", minetest.serialize(heat))

    local update = {
	{"perfume", "aren't", "not"},
	{"perfume", "are", ""},
	{"_teeth", nil, "nothing"},
	{"b_dryness", nil, "smooth"},
	{"b_itchiness", nil, "not"},
	{"b_dirtiness", nil, "clean"},
	{"tongue", nil, "pink"},
	{"t_whitening", nil, "no"},
	{"hair_type", nil, hairs[random(3)]},
	{"hair_tex", nil, selfcare.get(player, "hair_type")}
	}

    for i in ipairs(update) do
	local _i1 = update[i][1]
	local _i2 = update[i][2]
	local _i3 = update[i][3]

	if selfcare.get(player, _i1) == _i2 then
	    selfcare.set(player, _i1, _i3)
	end
    end

    if not selfcare.get(player, "selfcare") then
	selfcare.func.init(player)
    end

    if not selfcare.get(player, "skin_type") then
	selfcare.func.SetSkinType(player)
    end
end)

--	[ tools ]

function selfcare.func.Loofah(itemstack, player, extra, pointed_thing)
    local inv = player:get_inventory()
    local n = player:get_player_name()
    local bodysoap = nil
    local foam_type


	for i = 1, inv:get_size("main") do
	    local stack = inv:get_stack("main", i)
	   -- 'if stack:get_definition().type["bodysoap"] then' didnt work
	    if  stack:get_name() == "self_care:floral_bodysoap" or
		stack:get_name() == "self_care:almond_bodysoap" or
		stack:get_name() == "self_care:watermelon_bodysoap" or
		stack:get_name() == "self_care:banana_bodysoap" then
	          bodysoap = i
		  local soap_name = stack:get_name()
		local foam_table = {
		    ["self_care:floral_bodysoap"] = "rinsing",
		    ["self_care:watermelon_bodysoap"] = "rinsing",
		    ["self_care:almond_bodysoap"] = "foaming",
		    ["self_care:banana_bodysoap"] = "foaming"
		}

		local color_table = {
		    ["self_care:watermelon_bodysoap"] = "#DF8C83"
		}

		local foam_clr = color_table[soap_name]
		foam_type = foam_table[soap_name]

	      break
	    end

	end

	if bodysoap and bodysoap > 1 then
	    local loofah_i = bodysoap - 1
	    local loofah_stack = inv:get_stack("main", loofah_i)

	  if loofah_stack:get_name() == "self_care:white_loofah" then
	      local bodysoap_stack = inv:get_stack("main", bodysoap)

		selfcare.use_item(loofah_stack, 200)
		selfcare.use_item(bodysoap_stack, 16, 0.25)
		inv:set_stack("main", loofah_i, loofah_stack)
		inv:set_stack("main", bodysoap, bodysoap_stack)
		player:get_meta():set_string("foam_type", foam_type)
		player:get_meta():set_string("foam_clr", foam_clr)
		selfcare.effect.reset_wash(n)
		selfcare.effect.WashForm(n, foam_type, foam_clr)

	  end

	else
		selfcare.send(n, "soap must be to the right of the loofah", "info")

	end

	return itemstack
end

function selfcare.func.Toothbrush(itemstack, player, extra)
    local inv = player:get_inventory()
    local n = player:get_player_name()
    local toothpaste = nil


	for i = 1, inv:get_size("main") do
	    local stack = inv:get_stack("main", i)
	    if stack:get_name() == "self_care:coco_toothpaste" or
		stack:get_name() == "self_care:mint_toothpaste"then
	          toothpaste = i
	      break
	    end
	end

	if toothpaste and toothpaste > 1 then
	    local brush_i = toothpaste - 1
	    local brush_stack = inv:get_stack("main", brush_i)

	  if brush_stack:get_name() == "self_care:toothbrush" or
	     brush_stack:get_name() == "self_care:wooden_toothbrush" then
	      local toothpaste_stack = inv:get_stack("main", toothpaste)

		brush_stack:add_wear(65535 / 200)
		toothpaste_stack:add_wear(65535 / 20)
		inv:set_stack("main", brush_i, brush_stack)
		inv:set_stack("main", toothpaste, toothpaste_stack)
		selfcare.effect.BrushTeeth(player, extra)

	  end

	else
		selfcare.send(n, "toothpaste must be to the right of the brush", "info")

	end

	return itemstack
end

--	[ commands ]

selfcare.add_command("reset_selfcare", {
    info = "resets your hygiene data",
    func = function(name)
	local p = minetest.get_player_by_name(name)
	selfcare.set(p, "selfcare", nil)
	selfcare.send(name, "rejoin to reset selfcare!", "positive")
    end,
})

selfcare.add_command("disable_selfcare", {
    info = "disables the need to shower, brush teeth, etc",
    func = function(name)
	local p = minetest.get_player_by_name(name)
	selfcare.set(p, "selfcare", nil)
	selfcare.send(name, "self care is disabled!", "info")
    end,
})

selfcare.add_command("enable_selfcare", {
    info = "enables the need to shower, brush teeth, etc",
    func = function(name)
	local p = minetest.get_player_by_name(name)
	selfcare.set(p, "selfcare", "1")
	selfcare.send(name, "self care is enabled!", "info")
    end,
})

selfcare.add_command("skintype", {
    info = "what is my skintype",
    func = function(name)
	local p = minetest.get_player_by_name(name)
	selfcare.send(name, "your skin type: "..p:get_attribute("skin_type"), "info")
    end,
})

local stink_timer = 0

minetest.register_globalstep(function(dtime)
  for _, player in ipairs(minetest.get_connected_players()) do

    local n = player:get_player_name()
    local pos = player:get_pos()
    local e = player:get_properties().eye_height - 0.5
    local dir = player:get_look_dir()

    stink_timer = stink_timer + dtime

	if stink_timer > 0.2 then
	    if selfcare.get(player, "self_care") then
		if selfcare.get(player, "smell") == "bad" then

	minetest.add_particlespawner({
	pos = {min = {x = pos.x - 0.2, y = pos.y + e, z = pos.z - 0.2 + (dir.z * -0.2)}, max = {x = pos.x + 0.2, y = pos.y + e, z = pos.z  + 0.2 + (dir.z * -0.2)}},
	time = 0.05,
	amount = random(3),
	vel = {min = {x = -0.2, y = -0.2, z = -0.2}, max = {x = 0.2, y = 0.2, z = 0.2}},
	acc = {min = {x = -0.2, y = -0.2, z = -0.2}, max = {x = 0.2, y = 0.2, z = 0.2}},
	size = 6,
	texture = "selfcare_spray.png^[multiply:#9FA275^[opacity:120",
	collisiondetection = true,
	collision_removal = false,
	minexptime = 1,
	maxexptime = 2
	})
		end
	    end
	    stink_timer = 0
	end
  end
end)

function selfcare.func.SetSkinType(player)
    local skintypes = {"oily", "normal", "dry"}
    local skin
    local tmf = random(0, 60)

	if tmf > 40 then skin = skintypes[1]
	 elseif tmf > 20 then skin = skintypes[2]
	 elseif tmf >= 0 then skin = skintypes[3]
	end

	player:set_attribute("skin_type", skin)
end

--	[ globalstep ]


local timer = 0
minetest.register_globalstep(function(dtime)
  for _, player in ipairs(minetest.get_connected_players()) do
	local s = selfcare.get(player, "smell")
	local b = selfcare.get(player, "breath")

	if p and s ~= "bad" then
	  selfcare.func.stink(player)
	end

    if selfcare.deadly_breath == true then
	if b and b == "disgusting" then
	  local players = {}
	  local players_nearby = minetest.get_objects_inside_radius(player:get_pos(), 0.5)

	    for _, obj in ipairs(players_nearby) do
		if obj:is_player() then
		  table.insert(players, obj)
		  if obj == player then table.remove(players, tonumber(obj)) end
		end
	    end

	    if #players > 0 then
		local victim = players[random(#players)] -- victim fr

		if victim and victim ~= nil then
		    if timer > 2 then
			if victim:get_hp() < 4 then return end
			victim:set_hp(victim:get_hp() - 1)
		    end
		end
	    else
		local victim = nil
	    end
		timer = 0
	end
    end

  end -- close for
end)