#include "doorkeeper/doorkeeper.hpp"
#include "doorkeepertestConfig.h"
#include "platform.h"
#include "platformstrings.h"
#include "doorkeeper/helpers/asciimapsource.hpp"
#include "doorkeeper/helpers/tilecoordinates.hpp"
#include "doorkeeper/helpers/maploader.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
#include <ciso646>
#include <utility>
#include <cstring>

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>

template <typename Device, typename Tile>
struct LayerWithData {
	std::unique_ptr<dkh::AsciiMapSource> device;
	dk::Layer<Tile, 2>* layer;
	std::string path;
};

namespace {
	typedef std::unique_ptr<SDL_Renderer, void(*)(SDL_Renderer*)> SDLRendererUPtr;
	typedef std::unique_ptr<SDL_Window, void(*)(SDL_Window*)> SDLWindowUPtr;
	typedef std::unique_ptr<SDL_Texture, void(*)(SDL_Texture*)> SDLTextureUPtr;

	struct SDLSimple {
		SDLSimple ( void );
		~SDLSimple ( void ) noexcept;
		bool initialized;
	};

	void printWelcome ( void );
	void addLayer ( dk::Tyler<2>& parTiler, LayerWithData<dkh::AsciiMapSource, int>& parLayerInfo, const char* parPath );
	void printViewport ( const dk::Viewport<2>& parView, const dk::Layer<int, 2>& parLayer );
	std::pair<int, std::string> GetRenderingDriver ( void );
	void draw_tiles ( SDL_Renderer* parRenderer, SDL_Texture* parTile0, SDL_Texture* parTile1, const dk::Viewport<2>& parView, const dk::Layer<int, 2>& parLayer, int parYOffs );
} //unnamed namespace

int main() {
	typedef dk::Tyler<2>::coords coords2;
	using dkh::AsciiMapSource;

	SDLSimple sdl_init;
	if (not sdl_init.initialized)
		return 1;

	printWelcome();

	SDLWindowUPtr sdl_window(
		SDL_CreateWindow("DoorKeeper test", 100, 100, 128 * 6, 128 * 4, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE),
		&SDL_DestroyWindow
	);
	const auto rendererDriver = GetRenderingDriver();
	SDLRendererUPtr sdl_renderer(
		SDL_CreateRenderer(sdl_window.get(), rendererDriver.first, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
		&SDL_DestroyRenderer
	);

	dk::Tyler<2> tiler(coords2(64));

	LayerWithData<AsciiMapSource, int> bottomLayer;
	addLayer(tiler, bottomLayer, DATA_PATH"/test.map");

	LayerWithData<AsciiMapSource, int> topLayer;
	addLayer(tiler, topLayer, DATA_PATH"/test_2.map");

	printViewport(dk::Viewport<2>(tiler, coords2(10, 6), coords2(0)), *bottomLayer.layer);
	printViewport(dk::Viewport<2>(tiler, coords2(4, 4), coords2(0)), *bottomLayer.layer);

#if !defined(NDEBUG)
	std::cout << "Map size: " << tiler.map_size() << '\n';
#endif
	std::cout << "Total tiles: " << tiler.tiles_count() << '\n';

	//Load resources
	SDLTextureUPtr tile_0(nullptr, &SDL_DestroyTexture), tile_1(nullptr, &SDL_DestroyTexture);
	{
		tile_0 = SDLTextureUPtr(IMG_LoadTexture(sdl_renderer.get(), "tile_0.png"), &SDL_DestroyTexture);
		tile_1 = SDLTextureUPtr(IMG_LoadTexture(sdl_renderer.get(), "tile_1.png"), &SDL_DestroyTexture);
	}

	//Main loop
	bool running = true;
	int y = 0;
	dk::Viewport<2> viewport(tiler, coords2(10, 8), coords2(0));
	coords2 tile_size;
	SDL_QueryTexture(tile_0.get(), nullptr, nullptr, &tile_size.x(), &tile_size.y());
	do {
		SDL_RenderClear(sdl_renderer.get());
		viewport.setFrom(coords2(0, y / tile_size.y()));
		draw_tiles(sdl_renderer.get(), tile_0.get(), tile_1.get(), viewport, *bottomLayer.layer, -(y % tile_size.y()));
		//++y;
		if (tile_size.y() * bottomLayer.layer->mapSize().y() == y)
			y = 0;
		SDL_RenderPresent(sdl_renderer.get());

		SDL_Delay(1000 / 60);
		SDL_Event eve;
		while (SDL_PollEvent(&eve)) {
			switch (eve.type) {
			case SDL_KEYDOWN:
				////eve.key.keysym.sym
				//parInput.NotifyKeyAction(InputDevice_Keyboard, eve.key.keysym.scancode, true);
				break;

			case SDL_KEYUP:
				//parInput.NotifyKeyAction(InputDevice_Keyboard, eve.key.keysym.scancode, false);
				break;

			case SDL_QUIT:
				running = false;

			case SDL_WINDOWEVENT:
				if (SDL_WINDOWEVENT_RESIZED == eve.window.event) {
					//parSdlMain->SetResolution(ushort2(static_cast<uint16_t>(eve.window.data1), static_cast<uint16_t>(eve.window.data2)));
				}
				else if (SDL_WINDOWEVENT_CLOSE == eve.window.event) {
					running = false;
				}
				break;
			}
		}
	} while(running);
	return 0;
}

namespace {
	void printWelcome() {
		std::cout << "Welcome to " << APP_NAME << ' ' << DK_DEVICE_STRING << " version for " << DK_OS_STRING << ' ' << DK_ARCH_STRING << ' ' << DK_BIT_STRING;
#if defined(DK_ARM)
		std::cout << ' ' << DK_ARM_FAMILY_STRING << ' ' << DK_ARM_ARCH_STRING;
#endif
		std::cout << '\n';
	}

	void addLayer (dk::Tyler<2>& parTiler, LayerWithData<dkh::AsciiMapSource, int>& parLayerInfo, const char* parPath) {
		typedef dkh::AsciiMapSource::coords coords;

		parLayerInfo.path = parPath;
		parLayerInfo.device.reset(new dkh::AsciiMapSource(parLayerInfo.path, coords(10, 8), dk::MapType_IsometricStaggered, coords(64, 64)));
		parLayerInfo.layer = &parTiler.push_layer<int>(parLayerInfo.device.get(), 0);
	}

	void printViewport (const dk::Viewport<2>& parView, const dk::Layer<int, 2>& parLayer) {
		int col = 0;
		const auto tilecount = parView.count();
		for (auto itTile = parView.begin(parLayer), itTileEND = parView.end(parLayer); itTile != itTileEND; ++itTile) {
			std::cout << *itTile;
			++col;
			if (col == tilecount.x()) {
				col = 0;
				std::cout << '\n';
			}
		}
	}

	SDLSimple::SDLSimple() {
		if (SDL_Init(SDL_INIT_EVERYTHING) == -1) {
			std::cerr << "Error initializing SDL" << std::endl;
			initialized = false;
		}
		else {
			initialized = true;
		}
	}

	SDLSimple::~SDLSimple() noexcept {
		if (initialized) {
			initialized = false;
			SDL_Quit();
		}
	}

	std::pair<int, std::string> GetRenderingDriver() {
		typedef std::pair<int, std::string> RetPairType;

		const int count = SDL_GetNumRenderDrivers();
		int opengles = -1;
		int opengles2 = -1;
		int opengl = -1;
		SDL_RendererInfo info;
		for (int z = 0; z < count; ++z) {
			const int ret = SDL_GetRenderDriverInfo(z, &info);
			if (0 == ret) {
				if (std::strcmp("opengles", info.name) == 0)
					opengles = z;
				else if (std::strcmp("opengles2", info.name) == 0)
					opengles2 = z;
				else if (std::strcmp("opengl", info.name) == 0)
					opengl = z;
			}
		}
		if (opengl > -1)
			return RetPairType(opengl, "opengl");
		if (opengles2 > -1)
			return RetPairType(opengles2, "opengles2");
		if (opengles > -1)
			return RetPairType(opengles, "opengles");

		return RetPairType(-1, "default");
	}

	void draw_tiles (SDL_Renderer* parRenderer, SDL_Texture* parTile0, SDL_Texture* parTile1, const dk::Viewport<2>& parView, const dk::Layer<int, 2>& parLayer, int parYOffs) {
		typedef dk::Tyler<2>::coords coords2;
		SDL_Rect rect_src;
		rect_src.x = rect_src.y = 0;
		coords2 original_size;
		SDL_QueryTexture(parTile0, nullptr, nullptr, &original_size.x(), &original_size.y());
		rect_src.w = original_size.x();
		rect_src.h = original_size.y();
		const coords2 tilesize(original_size.x(), original_size.y() - 47);
		const int offsets[2] = {5, 0};

		for (auto itTile = parView.begin(parLayer), itTileEND = parView.end(parLayer); itTile != itTileEND; ++itTile) {
			SDL_Rect rect_dst;
			const auto pixel_pos = dkh::get_diamond_coordinates(itTile, tilesize);
			rect_src.y = offsets[*itTile];
			rect_src.h = original_size.y() - offsets[*itTile];

			rect_dst.x = pixel_pos.x();
			rect_dst.y = pixel_pos.y() + parYOffs;
			rect_dst.w = rect_src.w;
			rect_dst.h = rect_src.h;
			SDL_Texture* const curr_texture = (1 == *itTile ? parTile1 : parTile0);
			SDL_RenderCopy(parRenderer, curr_texture, &rect_src, &rect_dst);
		}
	}
} //unnamed namespace
