#pragma once
#include "Tile.h"
#include "../Assets/Map.h"
#include "../Mechanics/Object.h"
#include "../Assets/Object.h"
#include <Engine/GraphicLayers.h>
#include <Gaming/Map2D.h>
#include <Engine/Animation.h>
#include "../Mechanics/Map.h"
#include "../Mechanics/Townsfolk.h"
#include "../Mechanics/PlayerGame.h"
#include <Resource/ResizableObject.h>
#include <Widgets/WidgetRegistry.h>
#include "../Mechanics/Wildling.h"

namespace graphics
{

	static const gge::graphics::RGBint timeColors[] = {
		0xff4c4c76,
		0xff4c4c76,
		0xff4c5776,
		0xff4c5776,
		0xff8290b7,
		0xff99aad1,
		0xffb3ceec,
		0xffcce4f7,
		0xffe6f9f9,
		0xffffffff,
		0xffffffff,
		0xffffffff,
		0xffffffff,
		0xffffffff,
		0xffffffff,
		0xffffffff,
		0xfffbfdf2,
		0xfff7f6e6,
		0xffebddcc,
		0xffd4b8b3,
		0xffbc99ad,
		0xff9d8096,
		0xff816693,
		0xff4c4c76,
	};

	class Map
	{
	public:
		Map(assets::Map &given_provider);

		static void initialize(const gge::utils::Size &screenSize);

		gge::graphics::RGBint currentTimeColor() const {
			gge::graphics::RGBint c1=timeColors[inGameHour()], c2=timeColors[(inGameHour()+1)%24];
			c2.a=gge::Byte(255*((inGameTime%3600)/3600.f));

			c1.Blend(c2);
			return c1;
		}

		void drawTiles()
		{
			int minx = std::max(0, (int)std::floor(float(scrollPoint.x) / zoomAmount));
			int miny = std::max(0, (int)std::floor(float(scrollPoint.y) / zoomAmount));
			int maxx = std::min(tileMap.GetWidth(), (int)std::ceil((float(scrollPoint.x) + screenSize.Width) / zoomAmount));
			int maxy = std::min(tileMap.GetHeight(), (int)std::ceil((float(scrollPoint.y) + screenSize.Height) / zoomAmount));

			tileLayer.Clear();
			tileLayer.Ambient=currentTimeColor();


			for(int y= miny ; y < maxy ; ++y) 
			{
				for(int x = minx ; x < maxx ; ++x) 
				{
					// tileLayer.SetCurrentColor((mechanics::Map::current().getPointInfoMap()(x, y).buildable) ? 0x77FFFFFF : 0xFFFFFFFF);
					//tileLayer.SetCurrentColor((!bounds.isInside(gge::utils::Point(x, y))) ? 0x44FF9999 : 0xFFFFFFFF);
					tileMap(x,y).getAnimation().DrawStretched(tileLayer, gge::utils::Point(x,y) * zoomAmount, zoomAmount, zoomAmount);
				}
			}

			for(int i=0;i<selectionBoundList.size();i++) {
				if(i==0) {
					selectionDrawing->DrawIn(tileLayer, selectionBoundList[i].TopLeft()*zoomAmount-gge::utils::Point(8,8)+selectionOffsets[i], selectionBoundList[i].GetSize()*zoomAmount+gge::utils::Size(16,16));
					topSelection->DrawIn(overlayer, selectionBoundList[i].TopLeft()*zoomAmount-gge::utils::Point(8,8)+selectionOffsets[i], selectionBoundList[i].GetSize()*zoomAmount+gge::utils::Size(16,16));
				}
				else {
					selectionDrawing->DrawIn(tileLayer, selectionBoundList[i].TopLeft()*zoomAmount-gge::utils::Point(4,4)+selectionOffsets[i], selectionBoundList[i].GetSize()*zoomAmount+gge::utils::Size(8,8));
					topSelection->DrawIn(overlayer, selectionBoundList[i].TopLeft()*zoomAmount-gge::utils::Point(4,4)+selectionOffsets[i], selectionBoundList[i].GetSize()*zoomAmount+gge::utils::Size(8,8));
				}
			}
		}	

		void drawFog()
		{
			int minx = std::max(0, (int)std::floor(float(scrollPoint.x) / zoomAmount));
			int miny = std::max(0, (int)std::floor(float(scrollPoint.y) / zoomAmount));
			int maxx = std::min(tileMap.GetWidth(), (int)std::ceil((float(scrollPoint.x) + screenSize.Width) / zoomAmount));
			int maxy = std::min(tileMap.GetHeight(), (int)std::ceil((float(scrollPoint.y) + screenSize.Height) / zoomAmount));

			fogLayer.Clear();

			for(int y= miny ; y < maxy ; ++y) 
			{
				for(int x = minx ; x < maxx ; ++x) 
				{
					// tileLayer.SetCurrentColor((mechanics::Map::current().getPointInfoMap()(x, y).buildable) ? 0x77FFFFFF : 0xFFFFFFFF);
					if(!mechanics::Map::current().getPointInfoMap()(x,y).revealed)
						unchartedAnim->draw(fogLayer, gge::utils::Point(x,y)*zoomAmount, zoomAmount / TILE_SIZE);
					else if(!mechanics::Map::current().getPointInfoMap()(x,y).inSight)
						fogAnim->draw(fogLayer, gge::utils::Point(x,y)*zoomAmount, zoomAmount / TILE_SIZE);
				}
			}

			//!should be moved
			if(showBuildable) {
				for(int y= miny ; y < maxy ; ++y) 
				{
					for(int x = minx ; x < maxx ; ++x) 
					{
						// tileLayer.SetCurrentColor((mechanics::Map::current().getPointInfoMap()(x, y).buildable) ? 0x77FFFFFF : 0xFFFFFFFF);
						if(!mechanics::Map::current().getPointInfoMap()(x,y).buildable)
							fogAnim->draw(fogLayer, gge::utils::Point(x,y)*zoomAmount, zoomAmount / TILE_SIZE);
					}
				}
			}
		}	

		void drawTownsfolk(gge::graphics::Colorizable2DLayer &objectLayer, mechanics::Townsfolk *item)
		{
			gge::utils::Point l=item->currentLocation()*zoomAmount+item->moverOffset*(float)zoomAmount / TILE_SIZE;
			item->animator     .draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			item->animatorHair .draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			item->animatorCloth.draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
		}


		void drawArtisan(gge::graphics::Colorizable2DLayer &objectLayer, mechanics::Artisan *item)
		{
			gge::utils::Point l=item->currentLocation()*zoomAmount+item->moverOffset*(float)zoomAmount / TILE_SIZE;

			artisanicon      ->Draw(objectLayer, l-gge::utils::Point(-TILE_SIZE, 0));

			item->animator    .draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			item->animatorHair.draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			item->cloth      ->draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
		}

		void drawSoldier(gge::graphics::Colorizable2DLayer &objectLayer, mechanics::Soldier *item)
		{
			gge::utils::Point l=item->currentLocation()*zoomAmount+item->moverOffset*(float)zoomAmount / TILE_SIZE;

			soldiericon      ->Draw(objectLayer, l-gge::utils::Point(-TILE_SIZE, 0));

			item->animator    .draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			item->animatorHair.draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			if(item->inventory().headArmor)
				item->inventory().headArmor->animator.draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			if(item->inventory().bodyArmor)
				item->inventory().bodyArmor->animator.draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			if(item->inventory().mainHand)
				if(item->currentAnim == item->inventory().mainHand->animationType())
					item->inventory().mainHand->animator.draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
			if(item->inventory().offHand)
				item->inventory().offHand  ->animator.draw(objectLayer, l, (float)zoomAmount / TILE_SIZE);
		}

		void drawFloatingLogger(gge::graphics::Colorizable2DLayer &objectLayer, mechanics::FloatingLogger *logger) {
			auto location=dynamic_cast<mechanics::Object*>(logger)->currentLocation();
			location*=zoomAmount;
			location.y-=zoomAmount;
			location.x-=TILE_SIZE/2;

			if(dynamic_cast<mechanics::Mover*>(logger)) {
				location+=dynamic_cast<mechanics::Mover*>(logger)->moverOffset;
			}

			if(!logger->isExpired()) {
				loggerFont.Color.a=logger->alpha();
				loggerShadow.Color.a=loggerFont.Color.a/2;
				loggerShadow.Print(objectLayer, location+gge::utils::Point(1,1), TILE_SIZE*2, logger->log(), gge::TextAlignment::Center);
				loggerFont.Print(objectLayer, location, TILE_SIZE*2, logger->log(), gge::TextAlignment::Center);
			}
		}

		void drawObjects()
		{
			objectLayer.Clear();
			objectLayer.Ambient=currentTimeColor();

			overlayer.Clear();
			overlayer.Ambient=currentTimeColor();
			overlayer.SetCurrentColor(0x80ffffff);
			for(auto it = mechanics::Map::current().getObjects().First(); it.IsValid(); it.Next())
			{
				if(dynamic_cast<mechanics::Soldier*>(it.CurrentPtr())) 
				{
					drawSoldier(objectLayer, dynamic_cast<mechanics::Soldier*>(it.CurrentPtr()));
					drawSoldier(overlayer, dynamic_cast<mechanics::Soldier*>(it.CurrentPtr()));
				} 

				else if(dynamic_cast<mechanics::Townsfolk*>(it.CurrentPtr())) 
				{
					drawTownsfolk(objectLayer, dynamic_cast<mechanics::Townsfolk*>(it.CurrentPtr()));
					drawTownsfolk(overlayer, dynamic_cast<mechanics::Townsfolk*>(it.CurrentPtr()));
				}
				else if(dynamic_cast<mechanics::Artisan*>(it.CurrentPtr())) 
				{
					drawArtisan(objectLayer, dynamic_cast<mechanics::Artisan*>(it.CurrentPtr()));
					drawArtisan(overlayer, dynamic_cast<mechanics::Artisan*>(it.CurrentPtr()));
				}
				else if(dynamic_cast<mechanics::Wildling*>(it.CurrentPtr())) 
				{
					if(mechanics::Map::current().getPointInfoMap()(it->currentLocation()).inSight) {
						it->animator.draw(objectLayer, it->currentLocation()*zoomAmount+dynamic_cast<mechanics::Wildling*>(it.CurrentPtr())->moverOffset, (float)zoomAmount / TILE_SIZE);
						it->animator.draw(overlayer, it->currentLocation()*zoomAmount+dynamic_cast<mechanics::Wildling*>(it.CurrentPtr())->moverOffset, (float)zoomAmount / TILE_SIZE);
					}
				}
				else if(dynamic_cast<mechanics::Mover*>(it.CurrentPtr()))
				{
					it->animator.draw(objectLayer, it->currentLocation()*zoomAmount+dynamic_cast<mechanics::Mover*>(it.CurrentPtr())->moverOffset*(float)zoomAmount / TILE_SIZE, (float)zoomAmount / TILE_SIZE);
				}
				else
				{
					it->animator.draw(objectLayer, it->currentLocation()*zoomAmount, (float)zoomAmount / TILE_SIZE);
				}

				if(dynamic_cast<mechanics::FloatingLogger*>(it.CurrentPtr())) {
					drawFloatingLogger(overlayer, dynamic_cast<mechanics::FloatingLogger*>(it.CurrentPtr()));
				}
			}

			if(buildingFloaterAnim) {
				if(showAsNonBuildable)
					overlayer.SetCurrentColor(0xa0ff8080);
				else
					overlayer.SetCurrentColor(0xa0ffffff);

				buildingFloaterAnim->draw(overlayer, buildingFloaterPos*zoomAmount, (float)zoomAmount / TILE_SIZE);
			}

		}

		void setBuildingFloater(Animation *animation) {
			gge::utils::CheckAndDelete(buildingFloaterAnim);
			buildingFloaterAnim=animation;
		}

		void scrollTo(const gge::utils::Point &location);
		void scrollBy(const gge::utils::Point &scrollAmount);
		void zoom(const int given_zoomAmount){ zoomAmount = given_zoomAmount; }
		int zoom() const { return zoomAmount; }
		static gge::LayerBase &getBaseLayer() { return baseLayer; }
		gge::utils::Point currentScroll() const { return scrollPoint; }
		static Map &current() { return *currentMap; }
		static void current(Map &given_map) { 
			gge::utils::CheckAndDelete(currentMap);
			currentMap = &given_map; 
		}

		bool showBuildable;
		bool showAsNonBuildable;
		gge::utils::Point buildingFloaterPos;

		std::vector<gge::utils::Bounds> selectionBoundList;
		std::vector<gge::utils::Point> selectionOffsets;

	private:
		graphics::Animation *buildingFloaterAnim;
		int zoomAmount;
		gge::animation::Timer timer;
		assets::Map &provider;
		gaming::Map2D<Tile> tileMap;
		gge::utils::Point scrollPoint;
		std::vector<assets::AnimationProvider*> animations;

		gge::resource::ResizableObject *selectionDrawing, *topSelection;
		static gge::animation::Basic2DAnimation *artisanicon, *soldiericon;

		gge::Font loggerFont, loggerShadow;

		static Map *currentMap;
		static gge::utils::Size screenSize;
		static gge::graphics::Colorizable2DLayer baseLayer;
		static gge::graphics::Colorizable2DLayer tileLayer;
		static gge::graphics::Colorizable2DLayer fogLayer;
		static gge::graphics::Colorizable2DLayer objectLayer;
		static gge::graphics::Colorizable2DLayer overlayer;
	};
}

