#include "monsters"
#include "../weapons/projectile"

const string Q1_SCRAG_MODEL = "models/quake1/m_scrag.mdl";

const string Q1_SCRAG_IDLE  = "quake1/monsters/scrag/idle.wav";
const string Q1_SCRAG_SIGHT = "quake1/monsters/scrag/sight.wav";
const string Q1_SCRAG_PAIN = "quake1/monsters/scrag/pain.wav";
const string Q1_SCRAG_DEATH = "quake1/monsters/scrag/death.wav";
const string Q1_SCRAG_SHOOT = "quake1/monsters/scrag/shoot.wav";
const string Q1_SCRAG_HIT = "quake1/monsters/scrag/hit.wav";

enum Q1_SCRAG_EVENTS {
  SCRAG_START_ATTACK = 1,
  SCRAG_END_ATTACK = 2,
  SCRAG_IDLE_SOUND = 3
}

class monster_qscrag : ScriptBaseMonsterEntity, monster_qgeneric {
  void Spawn() {
    Precache();

    g_EntityFuncs.SetModel(self, Q1_SCRAG_MODEL);
    g_EntityFuncs.SetSize(self.pev, Vector(-16, -16, -24), Vector(16, 16, 40));
    self.m_bloodColor = BLOOD_COLOR_RED;

    self.pev.health = 80;
    self.pev.solid = SOLID_SLIDEBOX;
    self.pev.movetype = MOVETYPE_FLY;

    // self.StartMonster();
    self.m_MonsterState = MONSTERSTATE_NONE;

    self.m_FormattedName = "Scrag";

    self.pev.pain_finished = 0.0;

    m_iGibHealth = -50;

    FlyMonsterInit();
  }

  void Precache() {
    g_Game.PrecacheModel(Q1_SCRAG_MODEL);
    g_Game.PrecacheModel("models/quake1/spike.mdl");

    g_SoundSystem.PrecacheSound(Q1_SCRAG_IDLE);
    g_SoundSystem.PrecacheSound(Q1_SCRAG_SIGHT);
    g_SoundSystem.PrecacheSound(Q1_SCRAG_DEATH);
    g_SoundSystem.PrecacheSound(Q1_SCRAG_SHOOT);
    g_SoundSystem.PrecacheSound(Q1_SCRAG_PAIN);
    g_SoundSystem.PrecacheSound(Q1_SCRAG_HIT);
  }

  void MonsterIdle() {
    m_iAttackState = ATTACK_NONE;
    m_iAIState = STATE_IDLE;
    SetActivity(ACT_IDLE);
    m_flMonsterSpeed = 0;
  }

  void MonsterSight() {
    g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SCRAG_SIGHT, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf));
  }

  void MonsterWalk() {
    m_iAttackState = ATTACK_NONE;
    m_iAIState = STATE_WALK;
    SetActivity(ACT_WALK);
    m_flMonsterSpeed = 8;
  }

  void MonsterSide() {
    m_iAIState = STATE_RUN;
    SetActivity(ACT_RUN);
    m_flMonsterSpeed = 8;
  }

  void MonsterRun() {
    m_iAIState = STATE_RUN;
    SetActivity(ACT_RUN);
    m_flMonsterSpeed = 16;
  }

  bool MonsterCheckAttack() {
    if (g_Engine.time < m_flAttackFinished)
      return false;

    if (!m_fEnemyVisible)
      return false;

    if (m_iEnemyRange == RANGE_FAR) {
      if (m_iAttackState != ATTACK_STRAIGHT) {
        m_iAttackState = ATTACK_STRAIGHT;
        MonsterRun();
      }
      return false;
    }
      
    CBaseEntity@ pTarg = m_hEnemy;

    // see if any entities are in the way of the shot
    Vector spot1 = self.EyePosition();
    Vector spot2 = pTarg.EyePosition();

    TraceResult tr;
    g_Utility.TraceLine(spot1, spot2, dont_ignore_monsters, dont_ignore_glass, self.edict(), tr);

    if (tr.pHit !is pTarg.edict()) {
      // don't have a clear shot, so move to a side
      if (m_iAttackState != ATTACK_STRAIGHT) {
        m_iAttackState = ATTACK_STRAIGHT;
        MonsterRun();
      }
      return false;
    }

    float chance = 0.0;
    if (m_iEnemyRange == RANGE_MELEE)
      chance = 0.9;
    else if (m_iEnemyRange == RANGE_NEAR)
      chance = 0.6;
    else if (m_iEnemyRange == RANGE_MID)
      chance = 0.2;
    else
      chance = 0.0;

    if (Math.RandomFloat(0, 1) < chance) {
      m_iAttackState = ATTACK_MISSILE;
      return true;
    }

    if (m_iEnemyRange == RANGE_MID) {
      if (m_iAttackState != ATTACK_STRAIGHT) {
        m_iAttackState = ATTACK_STRAIGHT;
        MonsterRun();
      }
    } else {
      if (m_iAttackState != ATTACK_SLIDING)
      {
        m_iAttackState = ATTACK_SLIDING;
        MonsterSide();
      }
    }

    return false;
  }

  void MonsterMissileAttack() {
    m_iAIState = STATE_ATTACK;
    SetActivity(ACT_MELEE_ATTACK1);
  }

  void MonsterAttackFinished() {
    AttackFinished(2);
    if (m_iEnemyRange >= RANGE_MID || !m_fEnemyVisible) {
      m_iAttackState = ATTACK_STRAIGHT;
      MonsterRun();
    } else {
      m_iAttackState = ATTACK_SLIDING;
      MonsterSide();
    }
  }

  void MonsterPain(CBaseEntity@ pAttacker, float flDamage) {
    if (Math.RandomLong(0, 5) < 2)
      g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SCRAG_PAIN, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf));

    if (Math.RandomLong(0, 70) > flDamage)
      return;

    m_iAIState = STATE_PAIN;
    SetActivity(ACT_BIG_FLINCH);
  }

  void MonsterTimedIdleSound() {
    if (self.pev.radsuit_finished < g_Engine.time) {
      g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SCRAG_IDLE, Math.RandomFloat(0.95, 1.0), ATTN_IDLE, 0, 93 + Math.RandomLong(0, 0xf));
      self.pev.radsuit_finished = g_Engine.time + Math.RandomFloat(2, 8);
    }
  }

  void MonsterFastFire() {
    if (!m_hEnemy) return;

    CBaseEntity@ pEnemy = m_hEnemy;
    g_EngineFuncs.MakeVectors(self.pev.angles);

    Vector vecSrc, vecOffset;

    vecSrc = self.pev.origin + Vector(0, 0, 30) + g_Engine.v_forward * 14 + g_Engine.v_right * 14;
    vecOffset = -8 * g_Engine.v_right;
    q1_ScragDelaySpike(self, pEnemy, vecSrc, vecOffset, 0.8);

    vecSrc = self.pev.origin + Vector(0, 0, 30) + g_Engine.v_forward * 14 + g_Engine.v_right * -14;
    vecOffset = 8 * g_Engine.v_right;
    q1_ScragDelaySpike(self, pEnemy, vecSrc, vecOffset, 0.3);
  }

  bool MonsterHasMissileAttack() { return true; }
  bool MonsterHasMeleeAttack() { return false; }
  bool MonsterHasPain() { return true; }

  void MonsterKilled(entvars_t@ pevAttacker, int iGib) {
    if (ShouldGibMonster(iGib)) {
      g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, "quake1/gib.wav", Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf));
      g_EntityFuncs.SpawnRandomGibs(self.pev, 1, 1);
      g_EntityFuncs.Remove(self);
      return;
    }

    self.pev.velocity.x = Math.RandomFloat(-200, 200);
    self.pev.velocity.y = Math.RandomFloat(-200, 200);
    self.pev.velocity.z = Math.RandomFloat(100, 200);
    self.pev.movetype = MOVETYPE_TOSS;
    self.pev.flags &= ~(FL_ONGROUND | FL_FLY);

    // a bit of a hack. If a corpses' bbox is positioned such that being left solid so that
    // it can be attacked will block the player on a slope or stairs, the corpse is made nonsolid. 
    g_EntityFuncs.SetSize(self.pev, Vector(-16, -16, -16), Vector(16, 16, 16));

    g_SoundSystem.EmitSoundDyn(self.edict(), CHAN_VOICE, Q1_SCRAG_DEATH, Math.RandomFloat(0.95, 1.0), ATTN_NORM, 0, 93 + Math.RandomLong(0, 0xf));
  }

  void HandleAnimEvent(MonsterEvent@ pEvent) {
    switch (pEvent.event) {
      case SCRAG_START_ATTACK:
        MonsterFastFire();
        break;

      case SCRAG_END_ATTACK:
        MonsterAttackFinished();
        break;

      case SCRAG_IDLE_SOUND:
        MonsterTimedIdleSound();
        break;

      default:
        BaseClass.HandleAnimEvent(pEvent);
    }
  }
}

void q1_RegisterMonster_SCRAG() {
  g_CustomEntityFuncs.RegisterCustomEntity("monster_qscrag", "monster_qscrag");
}
