Utils <- {};

Utils.Schedule <- function(func, params, delay)
{
	//	@TODO	If the script terminates before deleting the unique key, then it will create a memory leak!
	//			Advise keeping track of unique keys so that they can be cleaned if the script terminates before execution!
	//			Actually... would that actually happen? It's using ent_fire so the responsibility is passed to the engine instead of the script, so it would clean even if the script termiantes...? Needs testing.

	local uniqueKey = UniqueString();

	getroottable()[uniqueKey] <- function()
	{
		func(params);
		delete getroottable()[uniqueKey];
	}

	local worldspawn = Entities.FindByClassname(null, "worldspawn");
	EntFireByHandle(worldspawn, "runscriptcode", "::" + uniqueKey + "()", delay, null, null);
};

Utils.Min <- function(a, b)
{
	if (a < b)
	{
		return a;
	}

	return b;
};

Utils.Max <- function(a, b)
{
	if (a > b)
	{
		return a;
	}

	return b;
};

Utils.Clamp <- function(value, min, max)
{
	if (max < min)
	{
		return max;
	}
	if (value < min)
	{
		return min;
	}
	if (value > max)
	{
		return max;
	}
	return value;
};

Utils.NormalizeAngle <- function(target)
{
	target %= 360.0;

	if (target > 180.0)
	{
		target -= 360.0;
	}
	else if (target < -180.0)
	{
		target += 360.0;
	}
	return target;
};

Utils.ApproachAngle <- function(target, value, speed)
{
	target = NormalizeAngle(target);
	value = NormalizeAngle(value);
	local delta = NormalizeAngle(target - value);
	if (delta > speed)
	{
		return value + speed;
	}
	else if (delta < -speed)
	{
		return value - speed;
	}
	return value;
};

Utils.VectorAngles <- function(forward)
{
	local yaw, pitch;
	if (forward.y == 0.0 && forward.x == 0.0)
	{
		yaw = 0.0
		if (forward.z > 0.0)
		{
			pitch = 270.0;
		}
		else
		{
			pitch = 90.0;
		}
	}
	else
	{
		yaw = (atan2(forward.y, forward.x) * 180.0 / PI);
		if (yaw < 0.0)
		{
			yaw += 360.0;
		}
		pitch = (atan2(-forward.z, forward.Length2D()) * 180.0 / PI);
		if (pitch < 0.0)
		{
			pitch += 360.0;
		}
	}

	return QAngle(pitch, yaw, 0.0);
};

Utils.IsBuilding <- function (ent)
{
	if (ent == null || ent.IsValid() == false)
	{
		return false;
	}

	switch (ent.GetClassname())
	{
		case "obj_sentrygun":
		case "obj_dispenser":
		case "obj_teleporter":
			return true;
	}

	return false;
};

Utils.BuildEmptyDispenser <- function (origin, angles, team)
{
    local ent_dispenser = SpawnEntityFromTable("obj_dispenser", {
        origin          =   origin,
        angles          =   angles,
        teamnum         =   team,
        SolidToPlayer   =   0,
    });

    //  Find and destroy extra entities that were created with the dispenser

    local ent_vgui = null;
    while (ent_vgui = Entities.FindByClassname(ent_vgui, "vgui_screen"))
    {
        if (ent_vgui.GetOwner() == ent_dispenser)
		{
            ent_vgui.Destroy();
		}
    }

	local ent_touchTrigger = null;
    while (ent_touchTrigger = Entities.FindByClassname(ent_touchTrigger, "dispenser_touch_trigger"))
    {
        if (ent_touchTrigger.GetOwner() == ent_dispenser)
		{
            ent_touchTrigger.Destroy();
		}
    }

    //  Stop the dispenser from functioning as a dispenser

	ent_dispenser.AcceptInput("Disable", "", null, null);

    return ent_dispenser;
};

Utils.AmbientGeneric <- function (sound, origin, radius, volume, pitch, destroyDelay = 5.0)
{
    local ent_sound = SpawnEntityFromTable("ambient_generic", {
        origin  = origin,
        message = sound,
        radius  = radius,
        health  = volume,
        pitch   = pitch,
    });

    ent_sound.AcceptInput("PlaySound", "", null, null);

	Schedule(function(params) { ent_sound.Destroy(); }, {}, destroyDelay);
};

Utils.CreateWeapon <- function(classname, itemDefinitionIndex)
{
    local weapon = Entities.CreateByClassname(classname);

    NetProps.SetPropInt(weapon, "m_AttributeManager.m_Item.m_iItemDefinitionIndex", itemDefinitionIndex);
    NetProps.SetPropBool(weapon, "m_AttributeManager.m_Item.m_bInitialized", true);
    NetProps.SetPropBool(weapon, "m_bValidatedAttachedEntity", true);
    Entities.DispatchSpawn(weapon);

    return weapon;
};

Utils.ForceTaunt <- function(player, tauntID)
{
	/*
	local weapon = Utils.CreateWeapon("tf_weapon_lunchbox", ITEM_DEFINITION_INDEX.SANDVICH);
	NetProps.SetPropEntity(weapon, "m_hOwner", player);
	weapon.PrimaryAttack();
	weapon.Destroy();
	*/

	local weapon = Entities.CreateByClassname("tf_weapon_bat");
	local activeWeapon = player.GetActiveWeapon();
	player.StopTaunt(true);
	player.RemoveCond(Constants.ETFCond.TF_COND_TAUNTING);
	weapon.DispatchSpawn()
	NetProps.SetPropInt(weapon, "m_AttributeManager.m_Item.m_iItemDefinitionIndex", tauntID);
	NetProps.SetPropBool(weapon, "m_AttributeManager.m_Item.m_bInitialized", true);
	NetProps.SetPropBool(weapon, "m_bForcePurgeFixedupStrings", true);
	NetProps.SetPropEntity(player, "m_hActiveWeapon", weapon);
	NetProps.SetPropInt(player, "m_iFOV", 0);
	player.HandleTauntCommand(0);
	NetProps.SetPropEntity(player, "m_hActiveWeapon", activeWeapon);
	weapon.Kill();
};

Integer <- {};

Integer.SINT32_UPPER <- 2147483647;
Integer.SINT32_LOWER <- -2147483648;

Integer.SINT64_UPPER <- 9223372036854775807;
Integer.SINT64_LOWER <- -9223372036854775808;

Integer.IntMax <- function()
{
	if (_intsize_ == 4)
	{
		return SINT32_UPPER;
	}

	if (_intsize_ == 8)
	{
		return SINT64_UPPER;
	}

	return null;
};

Integer.IntMin <- function()
{
	if (_intsize_ == 4)
	{
		return SINT32_LOWER;
	}

	if (_intsize_ == 8)
	{
		return SINT64_LOWER;
	}

	return null;
};

Matrix <- {};

Matrix.BuildMatrix <- function (translation, rotation, scale)
{
    local sinyaw    = sin(DEG2RAD * rotation.Yaw());
    local cosyaw    = cos(DEG2RAD * rotation.Yaw());
    local sinpitch  = sin(DEG2RAD * rotation.Pitch());
    local cospitch  = cos(DEG2RAD * rotation.Pitch());
    local sinroll   = sin(DEG2RAD * rotation.Roll());
    local cosroll   = cos(DEG2RAD * rotation.Roll());

    local m = array(16);

    m[0]  = ((cosyaw * cospitch)) * scale.x;        m[4]  = ((cosyaw * sinpitch * sinroll) - (sinyaw * cosroll)) * scale.y;     m[8]  =  ((cosyaw * sinpitch * cosroll) + (sinyaw * sinroll)) * scale.z;    m[12] =  translation.x;
    m[1]  = ((sinyaw * cospitch)) * scale.x;        m[5]  = ((sinyaw * sinpitch * cosroll) + (cosyaw * cosroll)) * scale.y;     m[9]  =  ((sinyaw * sinpitch * cosroll) - (cosyaw * sinroll)) * scale.z;    m[13] =  translation.y;
    m[2]  = ((-sinpitch)) * scale.x;                m[6]  = ((cospitch * sinroll)) * scale.y;                                   m[10] =  ((cospitch * cosroll)) * scale.z;                                  m[14] =  translation.z;
    m[3]  =  0;                                     m[7]  =  0;                                                                 m[11] =  0;                                                                 m[15] =  1;

    return m;
};

Matrix.BuildIdentityMatrix <- function ()
{
    local m = array(16);

    m[0]  =  1;   m[4]  =  0;   m[8]  =  0;    m[12] =  0;
    m[1]  =  0;   m[5]  =  1;   m[9]  =  0;    m[13] =  0;
    m[2]  =  0;   m[6]  =  0;   m[10] =  1;    m[14] =  0;
    m[3]  =  0;   m[7]  =  0;   m[11] =  0;    m[15] =  1;

    return m;
};

Matrix.TransformVector <- function (matrix, vector)
{
    local dest = Vector(
        vector.x * matrix[0] + vector.y * matrix[4] + vector.z * matrix[8]  + matrix[12], //  X
        vector.x * matrix[1] + vector.y * matrix[5] + vector.z * matrix[9]  + matrix[13], //  Y
        vector.x * matrix[2] + vector.y * matrix[6] + vector.z * matrix[10] + matrix[14]  //  Z
    );

    return dest;
};