#ifndef Logic_hpp
#define Logic_hpp

#include <graphics/Graphics.hpp>
#include "Enemy.hpp"
#include "Hero.hpp"
#include <chrono>

struct Logic : Graphics {
  static const Vector g;
  static Logic* create();
  static Logic* get();
  const Object::Time getTimer() const;
  Logic(
    const char* name,
    const double R,
    const Color& color = Color::Black
  );
  ~Logic();
protected:
  Object::Time seconds = 0;
  virtual Logic& simulate(const Object::Time dt);
  virtual Graphics& renderObjects();
  Graphics& render() override;
private:
  std::chrono::high_resolution_clock::time_point past;
  Object::Time timer = 0;
  Graphics& onLoop() override;
};

namespace FirstPerson {

  struct HeroFire : Fire {};

  typedef Gunner<
    HeroFire,
    7,
    ::Hero
  > HeroParent;

  struct Hero : HeroParent {
    Time seconds = 0;
    unsigned int fires = 0;
    Object& setActive(const bool active) override;
    Input& onKey(
      const Input::Key key,
      const bool isDown
    ) override;
  private:
    Time timer = 0;
    bool reset = false;
    bool isReadyToFire = true;
    const double getFireMistake() const override;
    Object& simulate(const Object::Time dt) override;
    Object& onFire() override;
  };

  struct Logic : ::Logic {
    static Logic* get();
    unsigned int scores = 0;
    Hero* getHero();
    const unsigned int applyScores(const int value = 0);
    Logic(
      const char* name,
      const double R,
      Hero* hero,
      const Color& color = Color::Black
    );
  protected:
    virtual Vector& getFrom(Vector& from) const;
    virtual Vector& getTo(Vector& to) const;
  private:
    Hero* hero;
    Input& onKey(
      const Key key,
      const bool isDown
    ) override;
    ::Logic& simulate(const Object::Time dt) override;
    Graphics& render() override;
  };

  template <typename parentType>
  struct Enemy : parentType {
    using parentType::parentType;
  protected:
    struct {
      bool isValid;
      bool isFront;
      bool isLeft;
      bool isTop;
      Vector direction;
      double xzDistance;
      double yDistance;
    } hero;
    virtual const int getPrize() const {
      return 1;
    }
    virtual const bool heroIsVisible() const {
      return hero.isValid && hero.isFront;
    }
    Object& simulate(const Object::Time dt) override {
      hero.isValid = false;
      if (
        this->isActive()
        &&
        !this->is(parentType::Termination)
        &&
        FirstPerson::Logic::get()->getHero()
        &&
        FirstPerson::Logic::get()->getHero()->isActive()
      ) {
        hero.isValid = true;
        (
          hero.direction = FirstPerson::Logic::get()->getHero()->getPosition()
        ) -= this->getPosition();
        hero.yDistance = std::abs(hero.direction.y);
        hero.isTop = hero.direction.y > 0;
        hero.direction.y = 0;
        hero.xzDistance = hero.direction.getLength();
        hero.direction.normalize();
        hero.isFront = this->isFront(
          FirstPerson::Logic::get()->getHero()->getPosition()
        );
        hero.isLeft = this->isLeft(
          FirstPerson::Logic::get()->getHero()->getPosition()
        );
      }
      return parentType::simulate(dt);
    }
    using parentType::simulate;
    Object& onCollision(Object* object) override {
      if (object != nullptr) {
        if (
          object == Logic::get()->getHero()
        ) {
          Logic::get()->getHero()->setActive(false);
        } else if (
          dynamic_cast<const HeroFire*>(object)
        ) {
          Logic::get()->applyScores(
            getPrize()
          );
          return this->setAction(this->Termination);
        }
      }
      return parentType::onCollision(object);
    }
  };

  class EnemyFire : public Fire {
    Object& onCollision(Object* object) override;
  };

  template <typename parentType>
  struct Gunner : parentType {
    using parentType::parentType;
  protected:
    const double getFireMistake() const override {
      return 1 - (std::min)(
        Logic::get()->scores / 100.0,
        1.0
      );
    }
    const Object::Time getFireTime() const override {
      return 1 + Math::rand() * getFireMistake();
    }
    virtual const bool heroIsAtGunPoint() const {
      return this->heroIsVisible() && this->hero.direction.getDistance(
        this->getDirection()
      ) < 0.1;
    }
  };
}

#endif
