#include "Logic.hpp"
#include <sstream>
#include <iomanip>
#include <thread>

Logic* Logic::get() {
  return static_cast<Logic*>(
    Graphics::get()
  );
}

const Object::Time Logic::getTimer() const {
  return timer;
}

Logic& Logic::simulate(const Object::Time dt) {
  for (
    std::vector<Object*>::iterator i = Object::objects.begin();
    i < Object::objects.end();
    i++
  ) {
    (*i)->simulate(dt);
  }
  return *this;
}

Graphics& Logic::onLoop() {
  static const auto sleep = std::chrono::milliseconds(20);
  const auto now = std::chrono::high_resolution_clock::now();
  const double duration = std::chrono::duration<double>(now - past).count();
  const double dt = (std::min)(
    duration,
    0.1
  );
  if (dt > 0) {
    simulate(dt);
    seconds += duration;
    timer += dt;
    past = now;
  }
  std::this_thread::sleep_for(sleep);
  return Graphics::onLoop();
}

const Vector Logic::g(
  0,
  - 9.8,
  0
);

Graphics& Logic::renderObjects() {
  for (
    std::vector<Object*>::const_iterator i = Object::objects.begin();
    i < Object::objects.end();
    i++
  ) {
    if (
      (*i)->active
    ) {
      push().apply(
        (*i)->matrix
      );
      (*i)->render();
      pop();
    }
  }
  return Graphics::render();
}

Graphics& Logic::render() {
  return renderObjects();
}

Logic::Logic(
  const char* name,
  const double R,
  const Color& color
) : Graphics(
  name,
  R,
  color
) {
  std::srand(
    std::clock() * std::time(NULL)
  );
  past = std::chrono::high_resolution_clock::now();
}

Logic::~Logic() {
  std::vector<Object*>::iterator i = Object::objects.begin();
  while (
    i < Object::objects.end()
  ) {
    delete *i;
    i = Object::objects.begin();
  }
}

FirstPerson::Logic* FirstPerson::Logic::get() {
  return static_cast<Logic*>(
    Graphics::get()
  );
}

FirstPerson::Logic::Logic(
  const char* name,
  const double R,
  Hero* hero,
  const Color& color
) : ::Logic(
  name,
  R,
  color
), hero(hero) {
}

FirstPerson::Hero* FirstPerson::Logic::getHero() {
  return hero;
}

const unsigned int FirstPerson::Logic::applyScores(const int value) {
  if (
    value > 0 || scores >= std::abs(value)
  ) {
    if (
      (scores += value) > 0
    ) {
      std::ostringstream s;
      s << std::fixed << std::setprecision(2);
      s << "[ ";
      s << scores;
      if (
        getHero()
      ) {
        if (
          getHero()->fires > 0
        ) {
          s << " : ";
          s << static_cast<double>(scores) / getHero()->fires;
        }
        if (
          getHero()->seconds > 0
        ) {
          s << " : ";
          s << scores / getHero()->seconds;
        }
      }
      if (
        seconds > 0
      ) {
        s << " : ";
        s << frames / seconds;
      }
      s << " ]" << std::ends;
      setText(
        s.str().c_str()
      );
    } else {
      setText();
    }
  }
  return scores;
}

Vector& FirstPerson::Logic::getFrom(Vector& from) const {
  return (
    (from = Vector::Y) -= Vector::X
  ) *= R * 3 / 2;
}

Vector& FirstPerson::Logic::getTo(Vector& to) const {
  return (
    (to = Vector::Y) *= R / 3
  );
}

Graphics& FirstPerson::Logic::render() {
  static Vector from;
  static Vector to;
  if (
    getHero() != nullptr && getHero()->isActive()
  ) {
    (
      to = (
        from = getHero()->getPosition()
      )
    ) += getHero()->getDirection();
  } else {
    getFrom(from).rotateY(
      Math::PIdiv4 * std::sin(
        getTimer() / 3
      )
    ).y *= (
      (
        14 + std::sin(
          getTimer()
        )
      ) / 15
    );
    getTo(to);
  }
  setView(
    from,
    to
  );
  return ::Logic::render();
}

Logic& FirstPerson::Logic::simulate(const Object::Time dt) {
  (
    textColor = getHero() != nullptr && getHero()->isActive() ? Color::Cyan : Color::White
  ) *= (
    (
      7 + std::sin(
        Math::PImul2 * getTimer() / 3
      )
    ) / 8
  );
  return ::Logic::simulate(dt);
}

Input& FirstPerson::Logic::onKey(
  const Key key,
  const bool isDown
) {
  if (
    getHero() != nullptr
  ) {
    getHero()->onKey(
      key,
      isDown
    );
  }
  return *this;
}

Object& FirstPerson::EnemyFire::onCollision(Object* object) {
  if (
    object != nullptr && object == Logic::get()->getHero()
  ) {
    object->setActive(false);
  }
  return ::Fire::onCollision(object);
}

const double FirstPerson::Hero::getFireMistake() const {
  return 0.125;
}

Object& FirstPerson::Hero::setActive(const bool active) {
  if (!active) {
    Logic::get()->applyScores(-1);
    Sound::alarm();
  }
  return HeroParent::setActive(active);
}

Object& FirstPerson::Hero::simulate(const Object::Time dt) {
  if (
    isActive()
  ) {
    seconds += dt;
  } else {
    if (
      (timer -= dt) <= 0 && reset
    ) {
      (timer = 3) /= 2;
      reset = false;
      setPosition(
        Vector(
          - getRxz() * 10,
          getRy()
        )
      ).setDirection(Vector::X).setActive();
    }
  }
  return HeroParent::simulate(dt);
}

Object& FirstPerson::Hero::onFire() {
  fires++;
  return HeroParent::onFire();
}

Input& FirstPerson::Hero::onKey(
  const Input::Key key,
  const bool isDown
) {
  if (key == Input::Fire) {
    if (isDown) {
      if (
        isActive()
      ) {
        if (isReadyToFire) {
          fire();
          isReadyToFire = false;
        }
      } else if (timer <= 0) {
        reset = true;
      }
    } else {
      isReadyToFire = true;
    }
  } else {
    return HeroParent::onKey(
      key,
      isDown
    );
  }
  return *this;
}
