package com.bitfire.uracer.resources;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.Texture.TextureWrap;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.utils.ObjectMap;
import com.bitfire.uracer.configuration.Config;
import com.bitfire.uracer.configuration.Storage;
import com.bitfire.utils.ShaderLoader;

public final class Art {
	public static TextureRegion[][] debugFont;

	// tileset friction maps
	public static Pixmap frictionMapDesert;

	// 3d
	public static Texture meshMissing;
	public static Texture meshTreeTrunk;
	public static ObjectMap<String, Texture> meshCar;
	public static Texture[] meshTreeLeavesSpring;
	public static Texture meshTrackWall;

	// cars
	public static TextureAtlas cars;
	public static TextureRegion skidMarksFront, skidMarksRear;
	public static Texture wrongWay;

	// fonts
	public static final int DebugFontWidth = 6;
	public static final int DebugFontHeight = 6;
	public static TextureAtlas fontAtlas;

	// post-processor
	// public static ShaderProgram depthMapGen, depthMapGenTransparent;
	public static Texture postXpro;
	public static Texture postLensFlare;

	// screens
	public static Texture scrBackground;
	public static Skin scrSkin;
	public static Texture scrPanel;
	private static TextureAtlas skinAtlas;

	// circle progress
	public static Texture texCircleProgress;
	public static Texture texCircleProgressMask;
	public static Texture texCircleProgressHalf;
	public static Texture texCircleProgressHalfMask;
	public static Texture texRadLinesProgress;

	// particle effects
	public static TextureAtlas particles;

	public static void init () {
		ShaderLoader.BasePath = "data/shaders/";
		loadFonts();
		loadCarGraphics();
		loadParticlesGraphics();
		loadMeshesGraphics(Config.Graphics.EnableMipMapping);
		loadFrictionMaps();
		loadPostProcessorMaps();
		loadScreensData();
		loadCircleProgress();
	}

	public static void dispose () {
		disposeFonts();
		disposeParticlesGraphics();
		disposeCarGraphics();
		disposeMeshesGraphics();
		disposeFrictionMaps();
		disposePostProcessorMaps();
		disposeScreensData();
		disposeCircleProgress();
	}

	//
	// circle progress
	//
	private static void loadCircleProgress () {
		texCircleProgress = Art.newTexture("data/base/progress/circle-progress-full.png", true);
		texCircleProgress.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Linear);

		texCircleProgressHalf = Art.newTexture("data/base/progress/circle-progress-half.png", true);
		texCircleProgressHalf.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Linear);

		texCircleProgressHalfMask = Art.newTexture("data/base/progress/circle-progress-half-mask.png", true);
		texCircleProgressHalfMask.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Linear);

		texRadLinesProgress = Art.newTexture("data/base/progress/radlines-progress-full.png", true);
		texRadLinesProgress.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Linear);

		texCircleProgressMask = Art.newTexture("data/base/progress/circle-progress-mask.png", true);
		texCircleProgressMask.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Linear);
	}

	private static void disposeCircleProgress () {
		texCircleProgress.dispose();
		texCircleProgressMask.dispose();
		texRadLinesProgress.dispose();
		texCircleProgressHalf.dispose();
		texCircleProgressHalfMask.dispose();
	}

	//
	// screens
	//
	public static void loadScreensData () {
		scrBackground = newTexture("data/base/titlescreen.png", true);

		// the skin will automatically search and load the same filename+".atlas" extension

		// skinAtlas = new TextureAtlas("data/ui/skin/skin.atlas");
		// if (ScaleUtils.PlayWidth < 1280) {
		// scrSkin = new Skin(Gdx.files.internal(Storage.UI + "skin/skin-small.json"), skinAtlas);
		// } else if (ScaleUtils.PlayWidth >= 1280) { // && ScaleUtils.PlayWidth < 1440) {
		// scrSkin = new Skin(Gdx.files.internal(Storage.UI + "skin/skin-big.json"), skinAtlas);
		// }
		// old
		// else if (ScaleUtils.PlayWidth >= 1440) {
		// scrSkin = new Skin(Gdx.files.internal(Storage.UI + "skin-big.json"), skinAtlas);
		// }

		// // holo
		// String skinName = "Holo-dark-ldpi";
		// String skinPath = Storage.UI + "holo/" + skinName;
		// skinAtlas = new TextureAtlas(Gdx.files.internal(skinPath + ".atlas"));
		// scrSkin = new Skin(Gdx.files.internal(skinPath + ".json"), skinAtlas);

		// kenney
		String skinPath = Storage.UI + "kenney/";
		skinAtlas = new TextureAtlas(Gdx.files.internal(skinPath + "pack.atlas"));
		scrSkin = new Skin(Gdx.files.internal(skinPath + "kenney.json"), skinAtlas);

		// brushed texture
		scrPanel = newTexture("data/base/panel.png", false);
	}

	public static void disposeScreensData () {
		scrSkin.dispose();
		scrBackground.dispose();
		skinAtlas.dispose();
		scrPanel.dispose();
	}

	//
	// post processor maps
	//

	private static void loadPostProcessorMaps () {
		postXpro = newTexture("data/base/xpro-lut.png", false);
		postXpro.setWrap(TextureWrap.ClampToEdge, TextureWrap.ClampToEdge);

		postLensFlare = newTexture("data/base/lenscolor.png", false);
		postLensFlare.setWrap(TextureWrap.ClampToEdge, TextureWrap.ClampToEdge);

		// depthMapGen = ShaderLoader.fromFile( "depth", "depth" );
		// depthMapGenTransparent = ShaderLoader.fromFile( "depth-transparent",
		// "depth-transparent" );
	}

	private static void disposePostProcessorMaps () {
		postXpro.dispose();
		postLensFlare.dispose();
		// depthMapGenTransparent.dispose();
		// depthMapGen.dispose();
	}

	//
	// friction maps
	//

	private static void loadFrictionMaps () {
		// friction maps
		frictionMapDesert = new Pixmap(Gdx.files.internal("data/levels/tileset/desert-friction-easy.png"));
	}

	private static void disposeFrictionMaps () {
		frictionMapDesert.dispose();
	}

	//
	// meshes
	//

	private static void loadMeshesGraphics (boolean mipmap) {
		meshTrackWall = newTexture("data/track/wall_4.png", mipmap);
		meshTrackWall.setWrap(TextureWrap.ClampToEdge, TextureWrap.ClampToEdge);

		meshMissing = newTexture("data/3d/textures/missing-mesh.png", mipmap);

		// car textures
		meshCar = new ObjectMap<String, Texture>();
		meshCar.put("car", newTexture("data/3d/textures/car.png", mipmap));
		meshCar.put("car_yellow", newTexture("data/3d/textures/car_yellow.png", mipmap));
		// meshCar.get("car").setFilter(TextureFilter.Linear, TextureFilter.Linear);
		// meshCar.get("car_yellow").setFilter(TextureFilter.Linear, TextureFilter.Linear);

		// trees
		meshTreeTrunk = newTexture("data/3d/textures/trunk_6_col.png", mipmap);
		meshTreeLeavesSpring = new Texture[7];
		for (int i = 0; i < 7; i++) {
			meshTreeLeavesSpring[i] = newTexture("data/3d/textures/leaves_" + (i + 1) + "_spring_1.png", mipmap);
		}
	}

	private static void disposeMeshesGraphics () {
		meshMissing.dispose();
		meshTrackWall.dispose();

		// car textures
		for (Texture t : meshCar.values()) {
			t.dispose();
		}
		meshCar.clear();

		// trees
		for (int i = 0; i < 7; i++) {
			meshTreeLeavesSpring[i].dispose();
		}

		meshTreeTrunk.dispose();
	}

	//
	// cars
	//

	private static void loadCarGraphics () {
		cars = new TextureAtlas("data/cars/pack.atlas");

		skidMarksFront = cars.findRegion("skid-marks-front");
		skidMarksRear = cars.findRegion("skid-marks-rear");

		wrongWay = newTexture("data/base/wrong-way.png", false);
		wrongWay.setFilter(TextureFilter.Linear, TextureFilter.Linear);
	}

	private static void disposeCarGraphics () {
		wrongWay.dispose();
		cars.dispose();
	}

	//
	// particles
	//

	private static void loadParticlesGraphics () {
		particles = new TextureAtlas("data/partfx/textures/pack.atlas");
	}

	private static void disposeParticlesGraphics () {
		particles.dispose();
	}

	//
	// fonts
	//

	private static void loadFonts () {
		// debug font, no need to scale it
		debugFont = split("data/base/debug-font.png", DebugFontWidth, DebugFontHeight, false);
		debugFont[0][0].getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);

		// game fonts
		fontAtlas = new TextureAtlas("data/font/pack.atlas");
	}

	private static void disposeFonts () {
		debugFont[0][0].getTexture().dispose();
		fontAtlas.dispose();
	}

	//
	// flags
	//

	public static Texture getFlag (String countryCode) {
		String filename = countryCode + ".png";
		FileHandle zip = Gdx.files.internal("data/flags.zip");
		ZipInputStream zin = new ZipInputStream(zip.read());
		ZipEntry ze = null;
		try {
			while ((ze = zin.getNextEntry()) != null) {
				if (ze.getName().equals(filename)) {
					ByteArrayOutputStream streamBuilder = new ByteArrayOutputStream();
					int bytesRead;
					byte[] tempBuffer = new byte[8192 * 2];
					while ((bytesRead = zin.read(tempBuffer)) != -1) {
						streamBuilder.write(tempBuffer, 0, bytesRead);
					}

					Pixmap px = new Pixmap(streamBuilder.toByteArray(), 0, streamBuilder.size());

					streamBuilder.close();
					zin.close();

					boolean mipMap = false;
					Texture t = new Texture(px);

					if (mipMap) {
						t.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Nearest);
					} else {
						t.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
					}

					px.dispose();
					return t;
				}
			}
		} catch (IOException e) {
		}

		return null;
	}

	//
	// helpers
	//

	private static TextureRegion[][] split (String name, int width, int height, boolean mipMap) {
		return split(name, width, height, false, true, mipMap);
	}

	private static TextureRegion[][] split (String name, int width, int height, boolean flipX, boolean flipY, boolean mipMap) {
		Texture texture = newTexture(name, mipMap);
		int xSlices = texture.getWidth() / width;
		int ySlices = texture.getHeight() / height;
		TextureRegion[][] res = new TextureRegion[xSlices][ySlices];
		for (int x = 0; x < xSlices; x++) {
			for (int y = 0; y < ySlices; y++) {
				res[x][y] = new TextureRegion(texture, x * width, y * height, width, height);
				res[x][y].flip(flipX, flipY);
			}
		}
		return res;
	}

	public static Texture newTexture (String name, boolean mipMap) {
		Texture t = new Texture(Gdx.files.internal(name), Format.RGBA8888, mipMap);

		if (mipMap) {
			t.setFilter(TextureFilter.MipMapLinearNearest, TextureFilter.Nearest);
		} else {
			t.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
		}

		return t;
	}

	// hide constructor
	private Art () {
	}

}