--[[

    Tower Crane Mod
    ===============

    v0.01 by JoSt
    
    Copyright (C) 2017 Joachim Stolberg
    LGPLv2.1+
    See LICENSE.txt for more information

    History:
    2017-06-04  v0.01  first version
    2017-06-06  v0.02  Hook bugfix
]]--


towercrane = {}

dofile(minetest.get_modpath("towercrane") .. "/config.lua")

--##################################################################################################
--##  Tower Crane Hook
--##################################################################################################
local hook = {
    physical = true, 
    collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
    collide_with_objects = false,   
    visual = "cube",
    visual_size = {x=0.6, y=0.6},
    textures = {
        "towercrane_hook.png",
        "towercrane_hook.png",
        "towercrane_hook.png",
        "towercrane_hook.png",
        "towercrane_hook.png",
        "towercrane_hook.png",
    },
    groups = {cracky=1},
    -- local variabels
    driver = nil,
    speed_forward=0,
    speed_right=0,
    speed_up=0,
    sound=nil,
}
----------------------------------------------------------------------------------------------------
-- Enter/leave the Hook
----------------------------------------------------------------------------------------------------
function hook:on_rightclick(clicker)
    local name = clicker:get_player_name()
    if self.driver and clicker == self.driver then  -- leave?
        clicker:set_detach()
        default.player_attached[name] = false
        default.player_set_animation(clicker, "stand" , 10)
        self.driver = nil
        if self.sound ~= nil then
            minetest.sound_stop(self.sound)
            self.sound = nil
        end
    elseif not self.driver then                     -- enter?
        self.driver = clicker
        clicker:set_attach(self.object, "", {x=0,y=15,z=-3}, {x=0,y=0,z=0})
        default.player_attached[name] = true
        default.player_set_animation(clicker, "sit" , 10)
    end
end

----------------------------------------------------------------------------------------------------
-- Hook control
----------------------------------------------------------------------------------------------------
function hook:on_step(dtime)
    -- remove hook from  last visit
    if self.pos1 == nil or self.pos2 == nil then
        self.object:remove()
        return
    end
    if self.driver then
        local ctrl = self.driver:get_player_control()
        local yaw = self.driver:get_look_horizontal()
        local pos = self.driver:getpos()
        local max_speed = 5
        local velocity = 0.5

        if ctrl.up then             -- forward
            self.speed_forward = math.min(self.speed_forward + velocity, max_speed)
        elseif ctrl.down then       -- backward
            self.speed_forward = math.max(self.speed_forward - velocity, -max_speed)
        elseif self.speed_forward > 0 then
            self.speed_forward = self.speed_forward - velocity
        elseif self.speed_forward < 0 then
            self.speed_forward = self.speed_forward + velocity
        end
        
        if ctrl.right then          -- right
            self.speed_right = math.min(self.speed_right + velocity, max_speed)
        elseif ctrl.left then       -- left
            self.speed_right = math.max(self.speed_right - velocity, -max_speed)
        elseif self.speed_right > 0 then
            self.speed_right = self.speed_right - velocity
        elseif self.speed_right < 0 then
            self.speed_right = self.speed_right + velocity
        end

        if ctrl.jump then           -- up
            self.speed_up = math.min(self.speed_up + velocity, 5)
        elseif ctrl.sneak then      -- down
            self.speed_up = math.max(self.speed_up - velocity, -5)
        elseif self.speed_up > 0 then
            self.speed_up = self.speed_up - velocity
        elseif self.speed_up < 0 then
            self.speed_up = self.speed_up + velocity
        end

        -- calculate the direction vector
        local vx = math.cos(yaw+math.pi/2) * self.speed_forward + math.cos(yaw) * self.speed_right
        local vz = math.sin(yaw+math.pi/2) * self.speed_forward + math.sin(yaw) * self.speed_right

        -- check if outside of the construction area
        if pos.x < self.pos1.x then vx= velocity end
        if pos.x > self.pos2.x then vx= -velocity end
        if pos.y < self.pos1.y then self.speed_up=  velocity end
        if pos.y > self.pos2.y then self.speed_up= -velocity end
        if pos.z < self.pos1.z then vz=  velocity end
        if pos.z > self.pos2.z then vz= -velocity end

        if vx ~= 0 or vz ~= 0 or self.speed_up ~= 0 then
            if self.sound == nil then
                self.sound = minetest.sound_play({name="crane"},{object=self.object,
                                                 gain=towercrane.gain, max_hear_distance=20,
                                                 loop=true})
            end
        elseif self.sound ~= nil then
            minetest.sound_stop(self.sound)
            self.sound = nil
        end
        
        self.object:setvelocity({x=vx, y=self.speed_up,z=vz})
    else
        self.object:setvelocity({x=0, y=0,z=0})
    end
end

----------------------------------------------------------------------------------------------------
-- LuaEntitySAO (non-player moving things): see http://dev.minetest.net/LuaEntitySAO
----------------------------------------------------------------------------------------------------
minetest.register_entity("towercrane:hook", hook)



--##################################################################################################
--##  Tower Crane
--##################################################################################################

local function turnright(dir)
    local facedir = minetest.dir_to_facedir(dir)
    return minetest.facedir_to_dir((facedir + 1) % 4)
end

local function turnleft(dir)
    local facedir = minetest.dir_to_facedir(dir)
    return minetest.facedir_to_dir((facedir + 3) % 4)
end


    if minetest.get_node_or_nil(pos) ~= nil then
        return false
    end

----------------------------------------------------------------------------------------------------
-- Check space for mast and arm
----------------------------------------------------------------------------------------------------
local function check_space(pos, dir, height, width)
    for i = 1,height+2 do
        pos.y = pos.y + 1
        if minetest.get_node_or_nil(pos).name ~= "air" then
            return false
        end
    end
    
    pos.x = pos.x + dir.x*2
    pos.z = pos.z + dir.z*2
    for i = 1,width+3 do
        pos.x = pos.x + dir.x
        pos.z = pos.z + dir.z
        if minetest.get_node_or_nil(pos).name ~= "air" then
            return false
        end
    end
    return true
end

----------------------------------------------------------------------------------------------------
-- Constuct mast and arm
----------------------------------------------------------------------------------------------------
local function construct_crane(pos, dir, height, width, owner)
    pos.y = pos.y + 1
    minetest.env:add_node(pos, {name="towercrane:mast_ctrl_off", param2=minetest.dir_to_facedir(dir)})
    local meta = minetest.get_meta(pos)
    meta:set_string("dir", minetest.pos_to_string(dir))
    meta:set_string("owner", owner)
    meta:set_int("height", height)
    meta:set_int("width", width)

    for i = 1,height+1 do
        pos.y = pos.y + 1
        minetest.env:add_node(pos, {name="towercrane:mast"})
    end
    
    pos.y = pos.y - 2
    pos.x = pos.x - dir.x
    pos.z = pos.z - dir.z
    minetest.env:add_node(pos, {name="towercrane:arm2"})
    pos.x = pos.x - dir.x
    pos.z = pos.z - dir.z
    minetest.env:add_node(pos, {name="towercrane:arm"})
    pos.x = pos.x - dir.x
    pos.z = pos.z - dir.z
    minetest.env:add_node(pos, {name="towercrane:balance"})
    pos.x = pos.x + 3 * dir.x
    pos.z = pos.z + 3 * dir.z
    
    for i = 1,width do
        pos.x = pos.x + dir.x
        pos.z = pos.z + dir.z
        if i % 2 == 0 then
            minetest.env:add_node(pos, {name="towercrane:arm2"})
        else
            minetest.env:add_node(pos, {name="towercrane:arm"})
        end
    end
end

----------------------------------------------------------------------------------------------------
-- Remove the crane
----------------------------------------------------------------------------------------------------
local function dig_crane(pos, dir, height, width)
    pos.y = pos.y + 1
    minetest.env:remove_node(pos, {name="towercrane:mast_ctrl_off"})
    
    for i = 1,height+1 do
        pos.y = pos.y + 1
        minetest.env:remove_node(pos, {name="towercrane:mast"})
    end

    pos.y = pos.y - 2
    pos.x = pos.x - dir.x
    pos.z = pos.z - dir.z
    minetest.env:remove_node(pos, {name="towercrane:arm2"})
    pos.x = pos.x - dir.x
    pos.z = pos.z - dir.z
    minetest.env:remove_node(pos, {name="towercrane:arm"})
    pos.x = pos.x - dir.x
    pos.z = pos.z - dir.z
    minetest.env:remove_node(pos, {name="towercrane:balance"})
    pos.x = pos.x + 3 * dir.x
    pos.z = pos.z + 3 * dir.z
    
    for i = 1,width do
        pos.x = pos.x + dir.x
        pos.z = pos.z + dir.z
        if i % 2 == 0 then
            minetest.env:remove_node(pos, {name="towercrane:arm2"})
        else
            minetest.env:remove_node(pos, {name="towercrane:arm"})
        end
    end
end

----------------------------------------------------------------------------------------------------
-- Place the hook in front of the base
----------------------------------------------------------------------------------------------------
local function place_hook(pos, dir)
    pos.y = pos.y - 1
    pos.x = pos.x + dir.x
    pos.z = pos.z + dir.z
    return minetest.add_entity(pos, "towercrane:hook")
end

----------------------------------------------------------------------------------------------------
-- Register Crane base
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:base", {
    description = "Tower Crane Base",
    inventory_image = "towercrane_invent.png",
    tiles = {
        "towercrane_base_top.png",
        "towercrane_base.png",
        "towercrane_base.png",
        "towercrane_base.png",
        "towercrane_base.png",
        "towercrane_base.png",
    },
    paramtype2 = "facedir",
    is_ground_content = false,
    groups = {cracky=2},
    formspec = set_formspec,

    -- set meta data (form for crane height and width, dir of the arm)
    after_place_node = function(pos, placer)
        local meta = minetest.get_meta(pos)
        local owner = placer:get_player_name()
        meta:set_string("owner", owner)
        local formspec = "size[5,4]"..
            "label[0,0;Construction area size]" ..
            "field[1,1.5;3,1;size;height,width;]" ..
            "button_exit[1,2;2,1;exit;Save]"
        meta:set_string("formspec", formspec)

        local fdir = minetest.dir_to_facedir(placer:get_look_dir(), false)
        local dir = minetest.facedir_to_dir(fdir)
        meta:set_string("dir", minetest.pos_to_string(dir))
    end,

    -- evaluate user input (height, width), destroyed old crane and build a new one with
    -- the given size
    on_receive_fields = function(pos, formname, fields, player)
        if fields.size == nil then
            return
        end
        local meta = minetest.get_meta(pos)

        if not player or not player:is_player() then
            return
        end
        local owner = meta:get_string("owner")
        if player:get_player_name() ~= owner then
            return
        end
        
        local dir = minetest.string_to_pos(meta:get_string("dir"))
        local height = meta:get_int("height")
        local width = meta:get_int("width")
        if dir ~= nil and height ~= nil and width ~= nil then
           dig_crane(pos, dir, height, width)
        end
        --check for correct size format
        size = string.split(fields.size, ",")
        if #size == 2  then
            local height = tonumber(size[1])
            local width = tonumber(size[2])
            if height ~= nil and width ~= nil then
                height = math.max(height, 8)
                height = math.min(height, towercrane.max_height)
                width = math.max(width, 8)
                width = math.min(width, towercrane.max_width)
                meta:set_int("height", height)
                meta:set_int("width", width)
                meta:set_string("infotext", "Crane size: " .. height .. "," .. width)
                if dir ~= nil then
                    if check_space(table.copy(pos), dir, height, width) then
                        construct_crane(table.copy(pos), dir, height, width, owner)
                    else
                        minetest.chat_send_player(owner, "Too less space to raise up the tower crane!")
                    end
                end
            end
        end
    end,

    -- remove mast and arm if base gets destroyed
    on_destruct = function(pos)
        local meta = minetest.get_meta(pos)
        local dir = minetest.string_to_pos(meta:get_string("dir"))
        local height = meta:get_int("height")
        local width = meta:get_int("width")

        -- remove crane
        if dir ~= nil and height ~= nil and width ~= nil then
           dig_crane(pos, dir, height, width)
        end
        -- remove hook
        local id = minetest.hash_node_position(pos)
        if towercrane.id then
            towercrane.id:remove()
            towercrane.id = nil
        end
    end,
})

----------------------------------------------------------------------------------------------------
-- Register Crane balance
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:balance", {
    description = "Tower Crane Balance",
    tiles = {
        "towercrane_base.png",
        "towercrane_base.png",
        "towercrane_base.png",
        "towercrane_base.png",
        "towercrane_base.png",
        "towercrane_base.png",
    },
    paramtype2 = "facedir",
    is_ground_content = false,
    groups = {crumbly=0, not_in_creative_inventory=1},
})

----------------------------------------------------------------------------------------------------
-- Register Crane mast
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:mast", {
    description = "Tower Crane Mast",
    drawtype = "glasslike_framed",
    tiles = {
        "towercrane_mast.png",
        "towercrane_mast.png",
        "towercrane_mast.png",
        "towercrane_mast.png",
        "towercrane_mast.png",
        "towercrane_mast.png",
    },
    paramtype2 = "facedir",
    is_ground_content = false,
    groups = {crumbly=0, not_in_creative_inventory=1},
})

----------------------------------------------------------------------------------------------------
-- Register Crane Switch (on)
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:mast_ctrl_on", {
    description = "Tower Crane Mast Ctrl On",
    drawtype = "node",
    tiles = {
        "towercrane_mast_ctrl.png",
        "towercrane_mast_ctrl.png",
        "towercrane_mast_ctrl_on.png",
        "towercrane_mast_ctrl_on.png",
        "towercrane_mast_ctrl.png",
        "towercrane_mast_ctrl.png",
    },
    on_rightclick = function (pos, node, clicker)
        local meta = minetest.get_meta(pos)
        if not clicker or not clicker:is_player() then
            return
        end
        if clicker:get_player_name() ~= meta:get_string("owner") then
            return
        end
        node.name = "towercrane:mast_ctrl_off"
        minetest.swap_node(pos, node)

        local id = minetest.hash_node_position(pos)
        if towercrane.id then
            towercrane.id:remove()
            towercrane.id = nil
        end
    end,

    on_construct = function(pos)
        local meta = minetest.get_meta(pos)
        meta:set_string("infotext", "Switch crane on/off")
    end,

    after_place_node = function(pos, placer, itemstack, pointed_thing)
        local meta = minetest.get_meta(pos)
        local owner = placer:get_player_name()
        meta:set_string("owner", owner)
    end,

    paramtype2 = "facedir",
    is_ground_content = false,
    groups = {crumbly=0, not_in_creative_inventory=1},
})

----------------------------------------------------------------------------------------------------
-- Register Crane Switch (off)
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:mast_ctrl_off", {
    description = "Tower Crane Mast Ctrl Off",
    drawtype = "node",
    tiles = {
        "towercrane_mast_ctrl.png",
        "towercrane_mast_ctrl.png",
        "towercrane_mast_ctrl_off.png",
        "towercrane_mast_ctrl_off.png",
        "towercrane_mast_ctrl.png",
      "towercrane_mast_ctrl.png",
    },
    on_rightclick = function (pos, node, clicker)
        -- switch switch on, calculate the construction area, and place the hook
        local meta = minetest.get_meta(pos)
        -- only the owner is allowed to switch
        if not clicker or not clicker:is_player() then
            return
        end
        if clicker:get_player_name() ~= meta:get_string("owner") then
            return
        end
        -- swap to the other node
        node.name = "towercrane:mast_ctrl_on"
        minetest.swap_node(pos, node)
        local dir = minetest.string_to_pos(meta:get_string("dir"))
        if pos ~= nil and dir ~= nil then
            -- store hook instance in 'towercrane'
            local id = minetest.hash_node_position(pos)
            towercrane.id = place_hook(table.copy(pos), dir)

            -- calculate the construction area dimension (pos, pos2)
            local height = meta:get_int("height")
            local width = meta:get_int("width")

            -- pos1 = close/right/below
            dir = turnright(dir)
            local pos1 = vector.add(pos, vector.multiply(dir, width/2))
            dir = turnleft(dir)
            local pos1 = vector.add(pos1, vector.multiply(dir, 1))
            pos1.y = pos.y - 2 - height

            -- pos2 = far/left/above
            local pos2 = vector.add(pos1, vector.multiply(dir, width-1))
            dir = turnleft(dir)
            pos2 = vector.add(pos2, vector.multiply(dir, width))
            pos2.y = pos.y - 3 + height

            -- normalize x/z so that pos2 > pos1
            if pos2.x < pos1.x then
                pos2.x, pos1.x = pos1.x, pos2.x
            end
            if pos2.z < pos1.z then
                pos2.z, pos1.z = pos1.z, pos2.z
            end

            -- store pos1/pos2 in the hook (LuaEntitySAO)
            towercrane.id:get_luaentity().pos1 = pos1
            towercrane.id:get_luaentity().pos2 = pos2
        end
    end,

    on_construct = function(pos)
        -- add infotext
        local meta = minetest.get_meta(pos)
        meta:set_string("infotext", "Switch crane on/off")
    end,

    after_place_node = function(pos, placer, itemstack, pointed_thing)
        -- store owner for dig protection
        local meta = minetest.get_meta(pos)
        local owner = placer:get_player_name()
        meta:set_string("owner", owner)
    end,

    paramtype2 = "facedir",
    is_ground_content = false,
    groups = {crumbly=0, not_in_creative_inventory=1},
})

----------------------------------------------------------------------------------------------------
-- Register Crane arm 1
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:arm", {
    description = "Tower Crane Arm",
    drawtype = "glasslike_framed",
    tiles = {
        "towercrane_arm.png",
        "towercrane_arm.png",
        "towercrane_arm.png",
        "towercrane_arm.png",
        "towercrane_arm.png",
        "towercrane_arm.png",
    },
    paramtype2 = "facedir",
    is_ground_content = false,
    groups = {crumbly=0, not_in_creative_inventory=1},
})

----------------------------------------------------------------------------------------------------
-- Register Crane arm 2
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:arm2", {
    description = "Tower Crane Arm2",
    drawtype = "glasslike_framed",
    tiles = {
        "towercrane_arm2.png",
        "towercrane_arm2.png",
        "towercrane_arm2.png",
        "towercrane_arm2.png",
        "towercrane_arm2.png",
        "towercrane_arm2.png",
    },
    paramtype2 = "facedir",
    is_ground_content = false,
    groups = {crumbly=0, not_in_creative_inventory=1},
})


----------------------------------------------------------------------------------------------------
-- Register Recipe
----------------------------------------------------------------------------------------------------
minetest.register_craft({
	output = "towercrane:base",
	recipe = {
		{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
		{"default:steel_ingot", "", ""},
		{"default:steel_ingot", "dye:yellow", ""}
	}
})