--- Require

local mp = require("mp")

--- Constants

local SHADER_PARAM = [[
//!PARAM angle
//!DESC Rotation angle in degrees
//!TYPE float
0.0
]]

local SHADER_META = [[
//!HOOK MAINPRESUB
//!DESC GPU rotate
//!BIND HOOKED
]]

local SHADER_DEFINE = "#define angle"

local SHADER_HOOK = [[
vec4 hook()
{
    float rad = radians(angle);
    float c   = cos(rad);
    float s   = sin(rad);
    mat2  rot = mat2(c, -s, s, c);
    vec2  pos = rot * ((HOOKED_pos - 0.5) * HOOKED_size) * HOOKED_pt + 0.5;
    if (!all(equal(pos, clamp(pos, 0.0, 1.0))))
        return vec4(0.0);
    return HOOKED_tex(pos);
}
]]

--- State

local state = {
    angle    = 0.0,
    shader   = nil,
    gpu_next = false,
}

--- Functions

local function shader_param()
    if state.gpu_next then
        return SHADER_PARAM
    else
        return ""
    end
end

local function shader_define()
    if state.gpu_next then
        return ""
    else
        return table.concat({SHADER_DEFINE, state.angle}, " ")
    end
end

local function shader_opt()
    return table.concat({"angle", state.angle}, "=")
end

local function remove()
    if state.shader then
        mp.msg.debug("Removing", state.shader)
        mp.commandv("change-list", "glsl-shaders", "remove", state.shader)
        os.remove(state.shader)
        state.shader = nil
    end
end

local function append(angle)
    state.angle = angle % 360
    local shader = nil
    if state.angle ~= 0 then
        if not state.gpu_next or not state.shader then
            shader = os.tmpname()
            mp.msg.debug("Writing", shader)
            local file = io.open(shader, "w")
            file:write(table.concat({
                shader_param(),
                SHADER_META,
                shader_define(),
                SHADER_HOOK,
            }, "\n"))
            file:close()
            mp.commandv("change-list", "glsl-shaders", "append", shader)
        end
        if state.gpu_next then
            shader = shader or state.shader
            mp.msg.debug("Setting shader opt")
            mp.commandv("change-list", "glsl-shader-opts", "append", shader_opt())
        end
    end
    if state.angle == 0 or not state.gpu_next then
        remove()
    end
    state.shader = shader
end

--- Properties

local function current_vo(_, vo)
    local gpu_next = vo == "gpu-next"
    if state.gpu_next ~= gpu_next then
        mp.msg.debug("gpu-next", gpu_next)
        state.gpu_next = gpu_next
        remove()
        append(state.angle)
    end
end
mp.observe_property("current-vo", "string", current_vo)

--- Events

mp.register_event("shutdown", remove)

--- Script messages

local function set(angle) append(angle)               end
local function add(angle) append(angle + state.angle) end
mp.register_script_message("set", set)
mp.register_script_message("add", add)