--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]

local PLUGIN = PLUGIN
-- vars for the radio displays
PLUGIN.pitch = 180
PLUGIN.yaw = 270
PLUGIN.roll = 270
PLUGIN.x = 8.7
PLUGIN.y = -14
PLUGIN.z = 17

-- stores the vector that the signal lines are pointing to, per entity
-- tracks update targets for lerping
local mradioSigLines = {}
local leftMostSigLine = Vector(56, 62, 0)
local possibleSigLineTargets = {
    [1] = Vector(103, 62, 0),
    [2] = Vector(95,  55, 0),
    [3] = Vector(89,  52, 0),
    [4] = Vector(98,  57, 0),
    [5] = Vector(101, 59, 0),
}

-- stores the X that the frequency cursor should be at
-- stores data used for lerping on frequency changes
local mradioFreqLines = {}

-- used to illuminate a 'hitbox' around the interactable 3d2d elements
local hitboxTargets = {
    volume = {
        x = 69,
        y = 181,
        w = 25,
        h = 25,
        shouldDraw = false
    },
    onOff = {
        x = 160,
        y = 130,
        w = 40,
        h = 30,
        shouldDraw = false
    },
    dial = {
        x = 280,
        y = 170,
        w = 50,
        h = 50,
        shouldDraw = false
    },
}

local function drawActiveHitbox()
    for _, hitbox in pairs(hitboxTargets) do
        if (hitbox.shouldDraw) then
            surface.SetDrawColor(180, 21, 0, 120)
            surface.DrawOutlinedRect(hitbox.x, hitbox.y, hitbox.w, hitbox.h, 2)
        end
    end
end

-- lerp between current and desired line position; draw
local function drawMRadioSigLine(ent)
    local idx = ent:EntIndex()
    if (!mradioSigLines[idx] or !istable(mradioSigLines[idx])) then
        return
    end

    local lineDat = mradioSigLines[idx]
    lineDat.lineLastV = LerpVector(math.Clamp((SysTime() - (lineDat.lastUpdate or SysTime())) / 15, 0, 1),
        lineDat.lineLastV or leftMostSigLine,
        lineDat.lineNextV or leftMostSigLine)

    if (ent:IsPlayingMusic()) then
        render.DrawLine(Vector(83, 75, 0),
            lineDat.lineLastV, Color(255, 0, 0), true)
    end
end

-- update the desired position of the line
local function updateMRadioSigLine(ent)
    local idx = ent:EntIndex()
    if (!mradioSigLines[idx] or !istable(mradioSigLines[idx])) then
        mradioSigLines[idx] = {}
        mradioSigLines[idx].lineLastV = leftMostSigLine
        mradioSigLines[idx].lineNextV = possibleSigLineTargets[1]
        mradioSigLines[idx].lastUpdate = SysTime()
    end

    if (ent:IsPlayingMusic()) then
        -- pick a random place towards the right to point to
        mradioSigLines[idx].lineNextV = possibleSigLineTargets[math.random(1,
            table.Count(possibleSigLineTargets))]

    else
        mradioSigLines[idx].lineNextV = leftMostSigLine
    end

    mradioSigLines[idx].lastUpdate = SysTime()
end

local function drawMRadioDLight(ent, pos)
    if (ent:IsPlayingMusic()) then
        local dlight = DynamicLight(ent:EntIndex())
        if (dlight) then
            dlight.pos = pos
            dlight.r = 124
            dlight.g = 107
            dlight.b = 72
            dlight.brightness = 1
            dlight.decay = 256
            dlight.size = 128
            dlight.dietime = CurTime() + 1
            dlight.style = 6 -- strobe
	    end
    end
end

local function drawMRadioSigLevel(ent, pos, angle, scale, alpha)
    surface.SetTextColor(0, 0, 0, alpha)

    -- top left background
    if (ent:IsPlayingMusic()) then
        surface.SetDrawColor(124, 107, 72)
    else
        surface.SetDrawColor(12, 12, 12)
    end

    surface.DrawRect(46, 34, 76, 46)
    surface.SetDrawColor(0, 0, 0)
    surface.DrawOutlinedRect(46, 34, 76, 46, 2)

    surface.SetTextPos(48, 35)
    surface.SetFont("radioSurfaceSm")
    surface.DrawText("SIGNAL LEVEL")

    surface.SetTextPos(75, 65)
    surface.SetFont("radioSurfaceXs")
    surface.DrawText("dBm")

    -- draw the db guides
    local curDBm, _x, _y, symb
    surface.SetDrawColor(0, 0, 0, alpha - 50)
    for i=1, 5 do
        curDBm = -30 + (10 * i) -- -20, -10, 0, +10, +20
        _x = 38 + (13 * i)

        if (i == 3) then
            _y = 48
        elseif (i == 2 or i == 4) then
            _y = 50
        else
            _y = 52
        end

        if (i >= 4) then
            symb = "+"
        elseif (i == 3) then
            symb = " " -- fix spacing for 0dbm
        else
            symb = ""
        end

        surface.SetTextPos(_x, _y)
        surface.DrawText(symb..curDBm)
    end

    -- draw the vert guidelines
    local markerAlpha = alpha - 50
    if (!ent:IsPlayingMusic()) then
        markerAlpha = 0
    end

    for i=1, 20 do
        if (i > 10 and i <= 15) then
            surface.SetDrawColor(2, 220, 17, markerAlpha)
        elseif (i > 15) then
            surface.SetDrawColor(255, 0, 0, markerAlpha)
        else
            surface.SetDrawColor(14, 15, 41, markerAlpha)
        end

        surface.DrawRect(52 + (3 * i), 61, 1, 2)
    end

    surface.SetDrawColor(0, 0, 0, alpha)
    surface.DrawOutlinedRect(50, 46, 68, 30, 1)
end

local function drawMRadioFreqDialLine(ent, pos, ang, scale, alpha, freq)
    if (!ent:IsPlayingMusic()) then return end

    local idx = ent:EntIndex()
    if (!mradioFreqLines[idx] or !istable(mradioFreqLines[idx])) then
        return
    end

    local lineDat = mradioFreqLines[idx]
    lineDat.lineLastX = Lerp(math.Clamp((SysTime() - (lineDat.lastUpdate or SysTime())) / 20, 0, 1),
        lineDat.lineLastX, lineDat.lineNextX)

    surface.SetDrawColor(255, 0, 0, alpha + 40)
    surface.DrawRect(lineDat.lineLastX, 47, 4, 21)
end

local function updateMRadioFreqDialLine(ent)
    local idx = ent:EntIndex()
    if (!mradioFreqLines[idx] or !istable(mradioFreqLines[idx])) then
        mradioFreqLines[idx] = {}
        mradioFreqLines[idx].lineLastX = 220
        mradioFreqLines[idx].lineNextX = 220
        mradioFreqLines[idx].lastUpdate = SysTime()
    end

    if (ent:IsPlayingMusic()) then
        -- pick a random place towards the right to point to
        local ch = ent:GetChan()
        if (ch and string.len(ch) > 0) then
            local chan = ix.musicRadio:GetChannel(ch)
            if (chan and istable(chan)) then
                mradioFreqLines[idx].lineNextX = chan.freqMap.dispX or 220
            end
        end
    else
        mradioFreqLines[idx].lineNextX = 220
    end

    mradioFreqLines[idx].lastUpdate = SysTime()
end

local function drawMRadioOnOff(ent, pos, ang, scale, alpha)
    surface.SetDrawColor(30, 32, 28, 129)
    surface.DrawRect(166, 136, 30, 20)
    surface.SetDrawColor(0, 0, 0, 255)
    surface.DrawOutlinedRect(166, 136, 30, 20, 1)

    surface.SetFont("radioSurfaceSmLight")

    surface.SetTextColor(0, 0, 0)
    if (ent:IsPlayingMusic()) then
        surface.SetTextColor(40, 180, 0, alpha - 35)
    end
    surface.SetTextPos(171, 137)
    surface.DrawText("ON")
end

local function drawMRadioVolume(ent, pos, ang, scale, alpha)
    surface.SetDrawColor(0, 0, 0, alpha - 70)

    surface.SetFont("radioSurfaceSm")
    local function _drawMRadioVolumeButton(x, y, text, bOn)
        surface.SetTextColor(141, 141, 141, alpha - 30)
        surface.SetTextPos(x + 2, y - 1)

        if (bOn and ent:IsPlayingMusic()) then
            surface.SetTextColor(35, 175, 23, alpha)
        end
        surface.DrawText(text)
    end

    local onButton = ent:GetIlluminatedVolButton()
    local volButtons = {
        [1] = { x = 53, y = 202 },
        [2] = { x = 50, y = 191 },
        [3] = { x = 53, y = 180 },
        [4] = { x = 61, y = 170 },
        [5] = { x = 72, y = 164 },
        [6] = { x = 84, y = 166 },
        [7] = { x = 95, y = 172 },
        [8] = { x = 101, y = 181 },
        [9] = { x = 104, y = 191 },
        [10] = { x = 99, y = 202 },
    }

    for key, button in ipairs(volButtons) do
        if (key == onButton) then
            _drawMRadioVolumeButton(button.x, button.y, tostring(key), true)
        else
            _drawMRadioVolumeButton(button.x, button.y, tostring(key), false)
        end
    end
end

local function drawMRadioSurface(ent, pos, ang, scale)
    local backlightAlphaPulse = 150
    if (ent:IsPlayingMusic()) then
        backlightAlphaPulse = 255 - math.Clamp(
            math.floor(math.sin(CurTime() * 120) * 100) - 10,
            10,
            30
        )
    end

    drawMRadioSigLevel(ent, pos, angle, scale, backlightAlphaPulse)
    drawMRadioSigLine(ent)

    -- large display center
    if (ent.IsPlayingMusic and ent:IsPlayingMusic()) then
        surface.SetDrawColor(124, 107, 72)
    else
        surface.SetDrawColor(12, 12, 12)
    end
    surface.DrawRect(160, 34, 340, 68)
    surface.SetDrawColor(10, 10, 10)
    surface.DrawOutlinedRect(160, 34, 340, 68, 2)

    -- draw the frequency info
    surface.SetDrawColor(0, 0, 0, backlightAlphaPulse)
    surface.DrawOutlinedRect(180, 77, 280, 18, 2)
    surface.SetFont("radioSurfaceSm")
    local xStep = 50
    for i=1, 6 do
        surface.SetTextPos(135 + (xStep * i), 80)
        surface.DrawText(tostring(500 + (50 * i)))
        surface.DrawRect(135 + (xStep * i) + 10, 69, 4, 8) -- center
        if (i == 1) then
            -- draw first guideline
            surface.DrawRect(184, 73, 2, 4)
        end
        if (i < 6) then
            -- draw four guidelines right of current (unless at end)
            surface.DrawRect(135 + (xStep * i) + 22.5, 73, 2, 4)
            surface.DrawRect(135 + (xStep * i) + 35, 73, 2, 4)
            surface.DrawRect(135 + (xStep * i) + 47.5, 73, 2, 4)
        end
    end

    surface.SetDrawColor(0, 0, 0, backlightAlphaPulse)
    surface.DrawOutlinedRect(180, 45, 280, 35, 2)

    drawMRadioFreqDialLine(ent, pos, angle, scale, backlightAlphaPulse)
    -- draw the light from the backlight
    drawMRadioDLight(ent, ent:LocalToWorld(Vector(10, 5, 1)))

    -- draw the toggle on/off button
    drawMRadioOnOff(ent, pos, ang, scale, backlightAlphaPulse)

    -- draw the volume button
    drawMRadioVolume(ent, pos, ang, scale, backlightAlphaPulse)
end

do
	-- step one: use consolas for everything
	-- step two: profit???
    surface.CreateFont("radioSurfaceXs", {
		font = "Consolas",
		size = 9,
		extended = true,
		weight = 400,
        antialias = true,
	})

	surface.CreateFont("radioSurfaceSm", {
		font = "Consolas",
		size = 12,
		extended = true,
		weight = 400,
        antialias = true,
	})

    surface.CreateFont("radioSurfaceSmLight", {
		font = "Open Sans Bold",
		size = 18,
		extended = true,
		weight = 200,
        antialias = true,
	})

	surface.CreateFont("radioSurfaceReg", {
		font = "Consolas",
		size = 16,
		extended = true,
		weight = 400,
        antialias = true,
	})

	surface.CreateFont("radioSurfaceBold", {
		font = "Consolas",
		extended = true,
		size = 20,
		weight = 800,
		antialias = true,
	})

    timer.Create("UpdateMusicRadioDisplayData", 0.15, 0, function()
        for _, ent in ipairs(ents.FindByClass("wn_musicradio")) do
            if (ent and ent.IsPlayingMusic) then
                updateMRadioSigLine(ent)
                updateMRadioFreqDialLine(ent)
            end
        end
    end)
end

hook.Add("PostDrawOpaqueRenderables", "draw_music_radio_ui", function()
    local epos
    local eang
    local lpos
    local lang
    local scale = 0.05 -- dat nice scale for good text (still not enough)
    local distsqr = 500 ^ 2
    local client = LocalPlayer()

    for _, ent in ipairs(ents.FindByClass("wn_musicradio")) do
        if (client:GetPos():DistToSqr(ent:GetPos()) < distsqr) then
            if (!ent or !IsEntity(ent)) then
                continue
            end

            epos = ent:GetPos()
            eang = ent:GetAngles()

            if (!epos or !isvector(epos) or !eang or !isangle(eang)) then
                continue
            end

            --[[
                Set the pos and angle so that they match the face of the radio prop
                And so that they align to the top left corner
                And are local to the entity in worldspace
            ]]
            lpos = ent:LocalToWorld(Vector(PLUGIN.x, PLUGIN.y, PLUGIN.z))
            lang = ent:LocalToWorldAngles(Angle(PLUGIN.pitch, PLUGIN.yaw, PLUGIN.roll))
            cam.Start3D2D(lpos, lang, scale)
                drawMRadioSurface(ent, lpos, lang, scale)
            cam.End3D2D()
            -- do the same thing, but a bit further forward, for the hitbox rectangles
            lpos2 = ent:LocalToWorld(Vector(PLUGIN.x + 0.9, PLUGIN.y, PLUGIN.z))
            cam.Start3D2D(lpos2, lang, scale)
                drawActiveHitbox()
            cam.End3D2D()
        end
    end
end)

--[[
    this code is sus
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠛⠉⠙⠛⠛⠛⠛⠻⢿⣿⣷⣤⡀⠀⠀⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠋⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠈⢻⣿⣿⡄⠀⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⣸⣿⡏⠀⠀⠀⣠⣶⣾⣿⣿⣿⠿⠿⠿⢿⣿⣿⣿⣄⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠁⠀⠀⢰⣿⣿⣯⠁⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣷⡄⠀ 
⠀⠀⣀⣤⣴⣶⣶⣿⡟⠀⠀⠀⢸⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣷⠀ 
⠀⢰⣿⡟⠋⠉⣹⣿⡇⠀⠀⠀⠘⣿⣿⣿⣿⣷⣦⣤⣤⣤⣶⣶⣶⣶⣿⣿⣿⠀ 
⠀⢸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀ 
⠀⣸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠉⠻⠿⣿⣿⣿⣿⡿⠿⠿⠛⢻⣿⡇⠀⠀ 
⠀⣿⣿⠁⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣧⠀⠀ 
⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀ 
⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀ 
⠀⢿⣿⡆⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡇⠀⠀ 
⠀⠸⣿⣧⡀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠃⠀⠀ 
⠀⠀⠛⢿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⣰⣿⣿⣷⣶⣶⣶⣶⠶⠀⢠⣿⣿⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⣽⣿⡏⠁⠀⠀⢸⣿⡇⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⢹⣿⡆⠀⠀⠀⣸⣿⠇⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⢿⣿⣦⣄⣀⣠⣴⣿⣿⠁⠀⠈⠻⣿⣿⣿⣿⡿⠏⠀⠀⠀⠀ 
⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⠿⠿⠿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
]]
function PLUGIN:Think()
    -- we're checking every frame if the player is looking at a radio?? 
    -- someone cooked here
    local tr = LocalPlayer():GetEyeTrace()
    if (tr.Entity and IsValid(tr.Entity) and tr.Entity:GetClass() == "wn_musicradio") then
        local volButton  = tr.Entity:GetAbsolutePanelButtonPosition(80, 170)
        local onButton   = tr.Entity:GetAbsolutePanelButtonPosition(135, 150)
        local dialButton = tr.Entity:GetAbsolutePanelButtonPosition(190, 170)

        hitboxTargets.volume.shouldDraw = false
        hitboxTargets.onOff.shouldDraw = false
        hitboxTargets.dial.shouldDraw = false

        if (tr.HitPos:Distance(volButton) <= 2) then
            if (tr.Entity:IsPlayingMusic()) then
                hitboxTargets.volume.shouldDraw = true
            end
        elseif (tr.HitPos:Distance(onButton) <= 2) then
            hitboxTargets.onOff.shouldDraw = true
        elseif (tr.HitPos:Distance(dialButton) <= 2) then
            if (tr.Entity:IsPlayingMusic()) then
                hitboxTargets.dial.shouldDraw = true
            end
        end
    end
end
