#pragma once

#include <Gaming/AI/Pathing.h>
#include <Utils/Point2D.h>
#include "Object.h"

namespace mechanics {

	class Mover {
	public:
		Mover() : moverOffset(0,0) {}

		void moveTo(gge::utils::Point target, gge::utils::Bounds limits) {
			if(getMoverLocation()==target) {
				return;
			}

			this->limits=limits;

			cancelMovement();

			movementTarget=target;
			originaltarget=movementTarget;

			if(!findTarget())
				return;

			auto &map=Map::current().getPointInfoMap();

			//!should detect somewhere near if the movementTarget is occupied
			path=gaming::ai::pathfinding::Extended4Way(Map::current().getPointInfoMap(), getMoverLocation(), movementTarget, dynamic_cast<Object&>(*this), limits, true);

			if(path.size()) {
				movementTarget=target;

				animSetMove(North, movementSpeed()/(TILE_SIZE/1000.f));
				setMoveAnim();
				lastMovement=currentGameTime;
				walked=0;
			}
		}

		void moveTo(gge::utils::Point target) {
			moveTo(target, gge::utils::Bounds(0,0, Map::current().getPointInfoMap().GetWidth(), Map::current().getPointInfoMap().GetHeight()));
		}

		void setMoveAnim() {
			if(!path.size())
				return;

			if(path.back().x==getMoverLocation().x) {
				if(path.back().y>getMoverLocation().y)
					lastDirection=South;
				else
					lastDirection=North;
			}
			else if(path.back().x<getMoverLocation().x) {
				lastDirection=West;
			}
			else {
				lastDirection=East;
			}

			setMoveDirection(lastDirection);
			Map::current().getPointInfoMap()(path.back()).walkable=false;
			Map::current().getPointInfoMap()(path.back()).object=dynamic_cast<Object*>(this);
		}

		virtual void setMoveDirection(Direction direction)=0;

		virtual void animSetMove(Direction direction, float speed)=0;

		virtual void animStopMove(Direction direction)=0;

		void cancelMovement() {
			moverOffset=gge::utils::Point(0,0);
			if(path.size()) {
				Map::current().getPointInfoMap()(path.back()).walkable=true;
				Map::current().getPointInfoMap()(path.back()).object=NULL;
				path.clear();
			}
		}

		void updateMovement() {
			if(!path.size())
				return;

			switch(lastDirection) {
			case North:
				moverOffset.y=-int(movementSpeed()*(currentGameTime-lastMovement));
				break;
			case South:
				moverOffset.y= int(movementSpeed()*(currentGameTime-lastMovement));
				break;
			case West:
				moverOffset.x=-int(movementSpeed()*(currentGameTime-lastMovement));
				break;
			case East:
				moverOffset.x= int(movementSpeed()*(currentGameTime-lastMovement));
				break;
			}

			//!should check movement speed
			if(currentGameTime-lastMovement>TILE_SIZE/movementSpeed()) {
				lastMovement=currentGameTime+unsigned(TILE_SIZE/movementSpeed()-(currentGameTime-lastMovement));
				moverOffset=gge::utils::Point(0,0);

				setMoverLocation(path.back());
				path.pop_back();

				moveStep();

				if(!path.size()) {
					animStopMove(lastDirection);
					moveFinished();
					return;
				}

				//!should reserve destination instead blocking it
				if(Map::current().getPointInfoMap()(path.back()).walkable && walked<5) {
					//no problem just move
					setMoveAnim();
					walked++;
				}
				else {
					//something blocked our path reperform pathing

					findTarget();

					//!should detect somewhere near if the movementTarget is occupied
					path=gaming::ai::pathfinding::Extended4Way(Map::current().getPointInfoMap(), getMoverLocation(), movementTarget, dynamic_cast<Object&>(*this), limits, true);

					if(!path.size()) {
						animStopMove(South);
					}
					else {
						setMoveAnim();
						walked=0;
					}
				}
			}
		}

		virtual float movementSpeed() const=0;

		virtual void moveFinished() {

		}

		virtual void moveStep() {

		}

		bool isMoving() {
			return path.size()>0;
		}

		gge::utils::Point moverOffset;
		int walked;

	protected:
		bool findTarget() {
			auto &map=Map::current().getPointInfoMap();

			if(!gge::utils::Bounds(0,0, map.GetWidth(), map.GetHeight()).isInside(movementTarget)) return false;

			int xsgn=gge::utils::Sign(movementTarget.x-getMoverLocation().x);
			int ysgn=gge::utils::Sign(movementTarget.y-getMoverLocation().y);

			//center
			if(map.Get(movementTarget).walkable) return true;
			//4-n
			if(map.Get(movementTarget.x-xsgn, movementTarget.y).walkable) {
				movementTarget.x-=xsgn;
				return true;
			}
			if(map.Get(movementTarget.x, movementTarget.y-ysgn).walkable) {
				movementTarget.y-=ysgn;
				return true;
			}
			if(map.Get(movementTarget.x+xsgn, movementTarget.y).walkable) {
				movementTarget.x+=xsgn;
				return true;
			}
			if(map.Get(movementTarget.x, movementTarget.y+ysgn).walkable) {
				movementTarget.y+=ysgn;
				return true;
			}

			while(!map.Get(movementTarget).walkable) {
				if(map.Get(movementTarget.x-xsgn, movementTarget.y).walkable) {
					movementTarget.x=movementTarget.x-xsgn;
					break;
				}
				if(map.Get(movementTarget.x, movementTarget.y-ysgn).walkable) {
					movementTarget.y=movementTarget.y-ysgn;
					break;
				}
				if(movementTarget==getMoverLocation())
					return false;

				movementTarget=movementTarget-gge::utils::Point(xsgn, ysgn);
				xsgn=gge::utils::Sign(movementTarget.x-getMoverLocation().x);
				ysgn=gge::utils::Sign(movementTarget.y-getMoverLocation().y);
			}
			return true;
		}

		virtual void setMoverLocation(gge::utils::Point pnt)=0;
		virtual gge::utils::Point getMoverLocation() const=0;


		gge::utils::Point movementTarget;
		std::vector<gge::utils::Point> path;
		gge::utils::Point originaltarget;
		gge::utils::Bounds limits;

		unsigned lastMovement;
		Direction lastDirection;
	};


}