#include "Fokker.hpp"
#include <play/Car.hpp>

Fokker::Mesh::Prop::Prop(const double size) {
  static Vector v;
  setColor(Color::Cyan).setVector(
    Vector::Y,
    (v = Vector::X).rotateZ(Math::PI / 20)
  ).rotateX(Math::PI / 8) *= size / 4;
  #ifndef NO_LIGHT
    setLight();
  #endif
  {
    ::Mesh* m = clone();
    *this += m->rotateZ(Math::PI);
    delete m;
  }
  round(100);
}

Fokker::Mesh::Mesh(const double size) : CylinderMesh(
  8,
  2,
  false,
  true
), prop(size) {
  rotateX(Math::PIdiv2);
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    Vector* v = *i;
    if (v->z < -1) {
      v->x = 0;
      v->y = 1;
    }
  }
  static const double d = 0.2;
  translate(
    0,
    0,
    1
  ).scale(
    d,
    d,
    1
  ) *= 0.5;
  {
    ConeMesh m = ConeMesh(
      8,
      0
    );
    *this += (
      m.rotateZ(-Math::PIdiv2) *= d / 2
    ).translate(
      0,
      d / 2
    ).rotateX(-Math::PI / 8).translate(
      0,
      d / 2,
      -0.5
    );
  }
  setColor(Color::Magenta);
  #ifndef NO_LIGHT
    setLight(
      Color(Color::Magenta) /= 10
    );
  #endif
  *this += LineMesh(
    0,
    0,
    0.5,
    0,
    0,
    0.5 + d / 5
  );
  {
    TriangleMesh m;
    m.rotateX(-Math::PIdiv2).rotateY(-Math::PIdiv2 * 3 / 2).rotateX(Math::PIdiv2).setColor(Color::Magenta);
    #ifndef NO_LIGHT
      m.setLight();
    #endif
    *this += (
      m.rotateX(-Math::PIdiv2) *= Math::SQRT2 / 3
    ).translate(
      0,
      d / 2,
      Math::Div3 - 0.5
    );
  }
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    Vector* v = *i;
    if (
      std::abs(v->z) > 0.25
    ) {
      v->z += d / 2 * (v->z < 0 ? 1 : -1);
    }
  }
  // Wings
  static const double w = Math::Div3;
  {
    RectMesh m;
    m.scale(
      1,
      w,
      1
    ).setColor(
      Color(Color::Cyan) /= 2
    );
    #ifndef NO_LIGHT
      m.setLight(
        Color(Color::Cyan) /= 10
      );
    #endif
    *this += m.rotateX(-Math::PIdiv2).translate(
      -0.5,
      d * 3 / 2
    );
    for (
      unsigned int i = 0;
      i < 2;
      i++
    ) {
      *this += m.scale(
        0.85,
        1,
        0.95
      ).translate(
        0,
        -d
      );
    }
  }
  // Wheels
  {
    Car::Wheel m;
    *this += (
      (
        m.scale(
          0.5,
          1,
          1
        ) *= d / 2
      ) += LineMesh(
        0.02,
        0,
        0,
        d,
        d,
        0
      )
    ).translate(
      -d,
      -d * 3 / 2,
      w / 2
    );
    *this += m.rotateY(Math::PI).translate(
      0,
      0,
      w
    );
  }
  (
    centralize() *= size
  ).round(100);
  const Box& box = getBox();
  for (
    Vertices::const_iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    const Vector* v = *i;
    if (v->z == box.max.z) {
      propPosition = v;
      break;
    }
  }
}

const Mesh& Fokker::getMesh() const {
  return mesh;
}

const MeshObject& Fokker::renderMesh() const {
  const MeshObject& ret = MeshObject::renderMesh();
  if (mesh.propPosition) {
    static Matrix m;
    static Matrix r;
    Graphics::get()->apply(
      m.setTranslate(*mesh.propPosition) *= r.setZRotation(
        - Math::PI * 10 * Logic::get()->getTimer()
      )
    );
    mesh.prop.render();
  }
  return ret;
}

Fokker::Fokker(const double size) : mesh(size) {
}

const Vector* FokkerEnemy::getAcceleration() const {
  return is(Termination) ? FokkerEnemyParent::getAcceleration() : nullptr;
}

const int FokkerEnemy::getPrize() const {
  return FokkerEnemyParent::getPrize() * 3;
}

const Object::Time FokkerEnemy::getFireTime() const {
  return FokkerEnemyParent::getFireTime() / 3;
}

const Vector& FokkerEnemy::getFireStartDirection() const {
  return hero.isValid ? hero.direction : FokkerEnemyParent::getFireStartDirection();
}

const bool FokkerEnemy::heroIsVisible() const {
  if (
    FirstPerson::Logic::get()->scores > 5
    &&
    FokkerEnemyParent::heroIsVisible()
  ) {
    if (
      const Hero* hero = FirstPerson::Logic::get()->getHero()
    ) {
      return hero->getPosition().y > hero->getRy() * 7 / 2;
    }
  }
  return false;
}

const Vector& FokkerEnemy::calculatePosition() const {
  static Vector ret;
  return ret.set(
    r.x,
    r.y
  ).rotateY(
    startRad + Math::PI * Logic::get()->getTimer() / 10 * (isLeft ? -1 : 1)
  );
}

const Vector& FokkerEnemy::calculateDirection() const {
  static Vector ret;
  return (
    ret = getPosition()
  ).rotateY(
    Math::PIdiv2 * (isLeft ? -1 : 1)
  );
}

const Matrix* FokkerEnemy::getActivationMatrix() {
  for (
    unsigned int i = 0;
    i < 3;
    i++
  ) {
    static Vector v;
    startRad = Math::PImul2 * Math::rand();
    v = calculatePosition();
    std::vector<Object*> collisions;
    if (
      getCollisions(
        collisions,
        v
      ) == 0
    ) {
      static Matrix ret;
      ret.setIdentity();
      ret._vector.position = v;
      ret._vector.direction = calculateDirection();
      return &ret;
    }
  }
  return nullptr;
}

const Vector& FokkerEnemy::getTerminationSpeed() const {
  static Vector ret;
  return (
    ret = getDirection()
  ) *= getRxz() * (
    2 + Math::rand()
  );
}

Object& FokkerEnemy::simulate(
  const EnemyAction action,
  const Object::Time dt
) {
  static Matrix m;
  switch (action) {
    case Termination:
      this->matrix.setXRotation(
        Math::PIdiv2 * (
          1 - getPosition().y / r.y
        )
      ) *= m.setZRotation(
        Math::PI * 3 * getEnemyTimer()
      );
      return *this;
    case Fly:
      setPosition(
        calculatePosition()
      );
      setDirection(
        calculateDirection()
      );
      matrix.setZRotation(
        sin(
          Logic::get()->getTimer() * Math::PIdiv2
        ) * Math::PI / 0x30 + Math::PIdiv4 / 3 * (isLeft ? -1 : 1)
      );
      if (
        heroIsVisible()
      ) {
        fire();
      }
      break;
    case Stop:
      return setAction(Fly);
  }
  return FokkerEnemyParent::simulate(
    action,
    dt
  );
}

FokkerEnemy::FokkerEnemy(
  const Vector& r,
  const bool isLeft
) : r(r), isLeft(isLeft) {
}
