/*
 * Copyright (c) 2016-2019 Marco Hladik <marco@icculus.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#define SF_BTT_NOMOVE		1
#define SF_BTT_TOGGLE		32
#define SF_BTT_SPARKS		64
#define SF_BTT_TOUCH_ONLY	256

enum
{
	STATE_RAISED,
	STATE_LOWERED,
	STATE_UP,
	STATE_DOWN
};

enum
{
	FRAME_OFF,
	FRAME_ON
};

class func_button:CBaseTrigger
{
	float m_flSpeed;
	float m_flLip;
	int m_iDamage;
	int m_iState;
	vector m_vecPos1;
	vector m_vecPos2;
	int m_iSounds;
	float m_flNextTrigger;
	vector m_vecDest;
	string m_strNoise;
	float m_flWait;
	float m_flDelay;
	vector m_vecMoveDir;
	virtual void() m_pMove = 0;
	
	virtual void() Precache;
	virtual void() Respawn;
	virtual void() Arrived;
	virtual void() Returned;
	virtual void() MoveBack;
	virtual void() MoveAway;
	virtual void() Touch;
	virtual void() Blocked;
	virtual void() Trigger;
	virtual void() Use;
	virtual void(entity eAttacker, int iType, int iDamage) vPain;
	virtual void(entity eAttacker, int iType, int iDamage) vDeath;
	
	virtual void() SetMovementDirection;
	virtual void(vector vdest, void() func) MoveToDestination;
	virtual void() MoveToDestination_End;
};

void func_button::Precache(void)
{
	precache_model(model);
	switch(m_iSounds) {
		case 0: 
			m_strNoise = "common/null.wav";
			break;
		case 1:
			m_strNoise = "buttons/button1.wav";
			break;
		case 2:
			m_strNoise = "buttons/button2.wav";
			break;
		case 3:
			m_strNoise = "buttons/button3.wav";
			break;
		case 4:
			m_strNoise = "buttons/button4.wav";
			break;
		case 5:
			m_strNoise = "buttons/button5.wav";
			break;
		case 6:
			m_strNoise = "buttons/button6.wav";
			break;
		case 7:
			m_strNoise = "buttons/button7.wav";
			break;
		case 8:
			m_strNoise = "buttons/button8.wav";
			break;
		case 9:
			m_strNoise = "buttons/button9.wav";
			break;
		case 10:
			m_strNoise = "buttons/button10.wav";
			break;
		case 11:
			m_strNoise = "buttons/button11.wav";
			break;
		case 12:
			m_strNoise = "buttons/latchlocked1.wav";
			break;
		case 13:
			m_strNoise = "buttons/latchunlocked1.wav";
			break;
		case 14:
			m_strNoise = "buttons/lightswitch2.wav";
			break;
		case 21:
			m_strNoise = "buttons/lever1.wav";
			break;
		case 22:
			m_strNoise = "buttons/lever2.wav";	
			break;
		case 23:
			m_strNoise = "buttons/lever3.wav";
			break;
		case 24:
			m_strNoise = "buttons/lever4.wav";
			break;
		case 25:
			m_strNoise = "buttons/lever5.wav";
			break;
		default:
			m_strNoise = "buttons/button9.wav";
	}
	
	precache_sound(m_strNoise);
}

void func_button::Arrived(void)
{
	m_iState = STATE_RAISED;
	
	if (spawnflags & SF_BTT_TOUCH_ONLY) {
		touch = Touch;
	} else {
		touch = __NULL__;
	}

	if (spawnflags & SF_BTT_TOGGLE) {
		return;
	}
	
	if (m_flWait != -1) {
		think = MoveBack;
		nextthink = (ltime + m_flWait);
	}
}

void func_button::Returned(void)
{
	if (spawnflags & SF_BTT_TOUCH_ONLY) {
		touch = Touch;
	}
    
	m_iState = STATE_LOWERED;
	frame = FRAME_OFF;
}

void func_button::MoveBack(void)
{
	touch = __NULL__;
	m_iState = STATE_DOWN;
	
	if (m_vecPos2 != m_vecPos1) {
		func_button::MoveToDestination (m_vecPos1, Returned);
	} else {
		func_button::Returned();
	}
}

void func_button::MoveAway(void)
{
	if (m_iState == STATE_UP) {
		return;
	}
	touch = __NULL__;

	if (m_iState == STATE_RAISED) {
		nextthink = (ltime + m_flWait);
		return;
	}
	
	m_iState = STATE_UP;
	
	if (m_vecPos2 != m_vecPos1) {
		func_button::MoveToDestination(m_vecPos2, Arrived);
	} else {
		func_button::Arrived();
	}
	
	frame = FRAME_ON;
}

void func_button::Trigger(void)
{
	if (m_flNextTrigger > time) {
		return;
	}

	m_flNextTrigger = time + m_flWait;
	
	if ((m_iState == STATE_UP) || (m_iState == STATE_RAISED)){
		if (m_flWait != -1) {
			func_button::MoveBack();
		}
		return;
	}

	sound(this, CHAN_VOICE, m_strNoise, 1.0, ATTN_NORM);
	func_button::MoveAway();
	
	if (m_flDelay) {
#ifdef GS_DEVELOPER
		dprint(sprintf("func_button: Delayed trigger of `%s`\n", m_strTarget));
#endif
		CBaseTrigger::UseTargets_Delay(m_flDelay);
	} else {
#ifdef GS_DEVELOPER
		print(sprintf("func_button: Normal trigger of `%s`\n", m_strTarget));
#endif
		CBaseTrigger::UseTargets();
	}
}

void func_button::Touch(void)
{
	if (other.movetype == MOVETYPE_WALK) {
		func_button::Trigger();
    
		if (!(spawnflags & SF_BTT_TOUCH_ONLY)) {
			touch = __NULL__;
		}
	}
}

void func_button::Use(void)
{
	Trigger();
}

void func_button::vPain (entity attacker, int type, int damage)
{
	Trigger();
}

void func_button::vDeath (entity attacker, int type, int damage)
{
	Trigger();
}

void func_button::Blocked(void)
{
	if (m_iDamage) {
		//Damage_Apply(other, this, dmg, other.origin, FALSE);
	}
	
	if (m_flWait >= 0) {
		if (m_iState == STATE_DOWN) {
			func_button::MoveAway();
		} else {
			func_button::MoveBack();
		}
	}
}

void func_button::SetMovementDirection(void)
{
	if (m_oldAngle == '0 -1 0') {
		m_vecMoveDir = '0 0 1';
	} else if (m_oldAngle == '0 -2 0') {
		m_vecMoveDir = '0 0 -1';
	} else {
		makevectors(m_oldAngle);
		m_vecMoveDir = v_forward;
	}
}

void func_button::MoveToDestination_End(void)
{
	setorigin(this, m_vecDest);
	velocity = [0,0,0];
	nextthink = -1;
	m_pMove();
}

void func_button::MoveToDestination(vector vDestination, void() func)
{
	vector vecDifference;
	float flTravel, fTravelTime;

	if (!m_flSpeed) {
		objerror("No speed defined for moving entity! Will not divide by zero.");
	}

	m_pMove = func;
	m_vecDest = vDestination;
	think = MoveToDestination_End;

	if (vDestination == origin) {
		velocity = '0 0 0';
		nextthink = (ltime + 0.1);
		return;
	}

	vecDifference = (vDestination - origin);
	flTravel = vlen(vecDifference);
	fTravelTime = (flTravel / m_flSpeed);

	if (fTravelTime < 0.1) {
		velocity = '0 0 0';
		nextthink = ltime + 0.1;
		return;
	}
	
	nextthink = (ltime + fTravelTime);
	velocity = (vecDifference * (1 / fTravelTime));
}

void func_button::Respawn(void)
{
	func_button::SetMovementDirection();

	solid = SOLID_BSP;
	movetype = MOVETYPE_PUSH;
	setorigin(this, m_oldOrigin);
	setmodel(this, m_oldModel);
	blocked = Blocked;
	velocity = [0,0,0];
	nextthink = -1;
	health = m_oldHealth;
	
	if (health > 0) {
		takedamage = DAMAGE_YES;
	}

	if (!m_flSpeed) {
		m_flSpeed = 100;
	}

	if (spawnflags & SF_BTT_TOUCH_ONLY) {
		touch = Touch;
		PlayerUse = __NULL__;
	} else {
		touch = __NULL__;
		PlayerUse = Use;
	}

	m_vecPos1 = m_oldOrigin;

	if (spawnflags & SF_BTT_NOMOVE) {
		m_vecPos2 = m_vecPos1;
	} else {
		m_vecPos2 = (m_vecPos1 + m_vecMoveDir * (fabs(m_vecMoveDir * size) - m_flLip));
	}

	m_iState = STATE_LOWERED;
	angles = [0,0,0];
}

void func_button::func_button(void)
{
	func_button::Precache();
	CBaseTrigger::CBaseTrigger();

	for (int i = 1; i < (tokenize(__fullspawndata) - 1); i += 2) {
		switch (argv(i)) {
		case "speed":
			m_flSpeed = stof(argv(i + 1));
			break;
		case "lip":
			m_flLip = stof(argv(i + 1));
			break;
		case "sounds":
			m_iSounds = stoi(argv(i + 1));
			break;
		case "wait":
			m_flWait = stof(argv(i + 1));
			break;
		case "delay":
			m_flDelay = stof(argv(i + 1));
			break;
		default:
			break;
		}
	}

	func_button::Respawn();
}
