/*****************************************************************************************
 *                                                                                       *
 * OpenSpace                                                                             *
 *                                                                                       *
 * Copyright (c) 2014                                                                    *
 *                                                                                       *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this  *
 * software and associated documentation files (the "Software"), to deal in the Software *
 * without restriction, including without limitation the rights to use, copy, modify,    *
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to    *
 * permit persons to whom the Software is furnished to do so, subject to the following   *
 * conditions:                                                                           *
 *                                                                                       *
 * The above copyright notice and this permission notice shall be included in all copies *
 * or substantial portions of the Software.                                              *
 *                                                                                       *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,   *
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A         *
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT    *
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF  *
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE  *
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                                         *
 ****************************************************************************************/

#ifndef TEXTURETILEMAPPING_HGLSL
#define TEXTURETILEMAPPING_HGLSL

#include <${MODULE_GLOBEBROWSING}/shaders/tile.hglsl>
#include <${MODULE_GLOBEBROWSING}/shaders/blending.hglsl>

// First layer type from LayerShaderManager is height map
#define NUMLAYERS_HEIGHTMAP #{lastLayerIndexHeightLayers} + 1
#define USE_HEIGHTMAP #{useHeightLayers}
#define HEIGHTMAP_BLENDING_ENABLED #{blendHeightLayers}

// Second layer type from LayerShaderManager is color texture
#define NUMLAYERS_COLORTEXTURE #{lastLayerIndexColorLayers} + 1
#define USE_COLORTEXTURE #{useColorLayers}
#define COLORTEXTURE_BLENDING_ENABLED #{blendColorLayers}

#define NUMLAYERS_GRAYSCALETEXTURE #{lastLayerIndexGrayScaleLayers} + 1
#define USE_GRAYSCALETEXTURE #{useGrayScaleLayers}
#define GRAYSCALETEXTURE_BLENDING_ENABLED #{blendGrayScaleLayers}

 // Third layer type from LayerShaderManager is water mask
#define NUMLAYERS_WATERMASK #{lastLayerIndexWaterMasks} + 1
#define USE_WATERMASK #{useWaterMasks}
#define WATERMASK_BLENDING_ENABLED #{blendWaterMasks}

// Fourth layer type from LayerShaderManager is night texture
#define NUMLAYERS_NIGHTTEXTURE #{lastLayerIndexNightLayers} + 1
#define USE_NIGHTTEXTURE #{useNightLayers}
#define NIGHTTEXTURE_BLENDING_ENABLED #{blendNightLayers}

// Fifth layer type from LayerShaderManager is overlay
#define NUMLAYERS_OVERLAY #{lastLayerIndexColorOverlays} + 1
#define USE_OVERLAY #{useColorOverlays}
#define OVERLAY_BLENDING_ENABLED #{blendColorOverlays}

// Sixth layer type from LayerShaderManager is grayscale overlay
#define NUMLAYERS_GRAYSCALE_OVERLAY #{lastLayerIndexGrayScaleColorOverlays} + 1
#define USE_GRAYSCALE_OVERLAY #{useGrayScaleColorOverlays}
#define GRAYSCALE_OVERLAY_BLENDING_ENABLED #{blendGrayScaleColorOverlays}

// Global constants
#define CHUNK_DEFAULT_HEIGHT #{defaultHeight}

// Other key value pairs used for settings
#define USE_ATMOSPHERE #{useAtmosphere}
#define PERFORM_SHADING #{performShading}
#define SHOW_CHUNK_EDGES #{showChunkEdges}
#define SHOW_HEIGHT_RESOLUTION #{showHeightResolution}
#define SHOW_HEIGHT_INTENSITIES #{showHeightIntensities}

float performLayerSettings(float currentValue, const LayerSettings settings) {
	float newValue = currentValue;

	newValue = sign(newValue) * pow(abs(newValue), settings.gamma);
	newValue = newValue * settings.multiplier;
	newValue = newValue * settings.opacity;

	return newValue;
}


vec4 performLayerSettings(vec4 currentValue, const LayerSettings settings) {
	vec4 newValue = vec4(
		performLayerSettings(currentValue.r, settings),
		performLayerSettings(currentValue.g, settings),
		performLayerSettings(currentValue.b, settings),
		performLayerSettings(currentValue.a, settings));

	return newValue;
}

float calculateUntransformedHeight(
	vec2 uv,
	LevelWeights levelWeights,
	const Layer HeightTextures[NUMLAYERS_HEIGHTMAP]) {

	float height = 0;

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !HEIGHTMAP_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // HEIGHTMAP_BLENDING_ENABLED
	
	
	#for i in 0..#{lastLayerIndexHeightLayers}
	{
		height = getTexVal(HeightTextures[#{i}].pile, levelWeights, uv).r;

		height = performLayerSettings(height, HeightTextures[#{i}].settings);
	}
	#endfor
	return height;
}

float calculateHeight(
	vec2 uv,
	LevelWeights levelWeights,
	const Layer HeightTextures[NUMLAYERS_HEIGHTMAP]) {

	float height = 0;

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !HEIGHTMAP_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // HEIGHTMAP_BLENDING_ENABLED

	
	#for i in 0..#{lastLayerIndexHeightLayers}
	{
		float untransformedHeight = getTexVal(HeightTextures[#{i}].pile, levelWeights, uv).r;

		float heightSample = getTransformedTexVal(HeightTextures[#{i}].depthTransform, untransformedHeight);
		if (heightSample > -100000) {
			heightSample = performLayerSettings(heightSample, HeightTextures[#{i}].settings);
			height = heightSample;
		}
	}
	#endfor
	return height;
}

vec4 calculateColor(
	const vec2 uv,
	LevelWeights levelWeights,
	const Layer ColorLayers[NUMLAYERS_COLORTEXTURE]) {

	vec4 color = vec4(0);

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !COLORTEXTURE_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // COLORTEXTURE_BLENDING_ENABLED

	#for i in 0..#{lastLayerIndexColorLayers}
	{
		vec4 colorSample = getTexVal(ColorLayers[#{i}].pile, levelWeights, uv);

		colorSample = performLayerSettings(colorSample, ColorLayers[#{i}].settings);

		color = blendOver(color, colorSample);
	}
	#endfor

	return color;	
}

vec4 calculateGrayScale(
	const vec4 currentColor,
	const vec2 uv,
	LevelWeights levelWeights,
	const Layer GrayScaleLayers[NUMLAYERS_GRAYSCALETEXTURE]) {

	vec4 colorGrayScale = currentColor;

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !GRAYSCALETEXTURE_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // GRAYSCALE_OVERLAY_BLENDING_ENABLED

	#for i in 0..#{lastLayerIndexGrayScaleLayers}
	{
		vec4 colorSample = getTexVal(GrayScaleLayers[#{i}].pile, levelWeights, uv);

		colorSample = vec4(colorSample.r, colorSample.r, colorSample.r, 1);
		colorSample = performLayerSettings(colorSample, GrayScaleLayers[#{i}].settings);

		colorGrayScale = blendOver(colorGrayScale, colorSample);
	}
	#endfor

	return colorGrayScale;
}

float gridDots(vec2 uv, vec2 gridResolution){
	vec2 uvVertexSpace = fract((gridResolution) * uv) + 0.5;

	vec2 uvDotSpace = abs(2*(uvVertexSpace-0.5));
	return 1-length(1-uvDotSpace);
}

vec4 calculateDebugColor(vec2 uv, vec4 fragPos, vec2 vertexResolution){
	vec2 uvVertexSpace = fract(vertexResolution * uv);
	vec3 colorUv = vec3(0.3*uv.x, 0.3*uv.y, 0);
	vec3 colorDistance = vec3(0, 0, min( 0.4*log(fragPos.w) - 3.9, 1));
	vec3 colorVertex = (1.0-length(uvVertexSpace)) * vec3(0.5);
	vec3 colorSum = colorUv + colorDistance + colorVertex;
	return vec4(0.5 * colorSum, 1);
}

float tileResolution(vec2 tileUV, const ChunkTile chunkTile){
	vec2 heightResolution = textureSize(chunkTile.textureSampler, 0);
	vec2 uv = TileUVToTextureSamplePosition(chunkTile, tileUV);
	return gridDots(uv, heightResolution);
}


vec4 calculateNight(
	const vec4 currentColor,
	const vec2 uv,
	LevelWeights levelWeights,
	const Layer NightLayers[NUMLAYERS_NIGHTTEXTURE],
	const vec3 ellipsoidNormalCameraSpace,
	const vec3 lightDirectionCameraSpace) {

	vec4 nightColor = vec4(0,0,0,0);

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !NIGHTTEXTURE_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // NIGHTTEXTURE_BLENDING_ENABLED

	#for i in 0..#{lastLayerIndexNightLayers}
	{
		vec4 colorSample = getTexVal(NightLayers[#{i}].pile, levelWeights, uv);
		colorSample = performLayerSettings(colorSample, NightLayers[#{i}].settings);

		nightColor = blendOver(nightColor, colorSample);
	}
	#endfor


	vec3 n = normalize(ellipsoidNormalCameraSpace);
	vec3 l = lightDirectionCameraSpace;
	
	float cosineFactor = clamp(dot(l, normalize(n + 0.15 * l)) * 3 , 0, 1);
	
	// Blend shaded color with base color
	vec4 color = vec4(currentColor.rgb + (cosineFactor) * nightColor.xyz, currentColor.a);

	return color;
}

vec4 calculateShadedColor(
	const vec4 currentColor,
	const vec3 ellipsoidNormalCameraSpace,
	const vec3 lightDirectionCameraSpace) {

	vec3 shadedColor = currentColor.rgb * 0.05;

	vec3 n = normalize(ellipsoidNormalCameraSpace);
	vec3 l = lightDirectionCameraSpace;
	
	float cosineFactor = pow(clamp(dot(-l, n), 0, 1), 0.8);
	
	// Blend shaded color with base color
	vec4 color = vec4(cosineFactor * currentColor.xyz + (1 - cosineFactor) * shadedColor, currentColor.a);
	return color;
}

vec4 calculateOverlay(
	const vec4 currentColor,
	const vec2 uv,
	LevelWeights levelWeights,
	const Layer ColorOverlays[NUMLAYERS_OVERLAY]) {

	vec4 color = currentColor;

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !OVERLAY_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // OVERLAY_BLENDING_ENABLED

	#for i in 0..#{lastLayerIndexColorOverlays}
	{
		vec4 colorSample = getTexVal(ColorOverlays[#{i}].pile, levelWeights, uv);
		colorSample = performLayerSettings(colorSample, ColorOverlays[#{i}].settings);

		color = blendOver(color, colorSample);
	}
	#endfor

	return color;
}

vec4 calculateGrayScaleOverlay(
	const vec4 currentColor,
	const vec2 uv,
	LevelWeights levelWeights,
	const Layer GrayScaleColorOverlays[NUMLAYERS_GRAYSCALE_OVERLAY]) {

	vec4 colorGrayScale = currentColor;

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !GRAYSCALE_OVERLAY_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // GRAYSCALE_OVERLAY_BLENDING_ENABLED

	#for i in 0..#{lastLayerIndexGrayScaleColorOverlays}
	{
		vec4 colorSample = getTexVal(GrayScaleColorOverlays[#{i}].pile, levelWeights, uv);

		colorSample = vec4(colorSample.r, colorSample.r, colorSample.r, colorSample.g);
		colorSample = performLayerSettings(colorSample, GrayScaleColorOverlays[#{i}].settings);

		colorGrayScale = blendOver(colorGrayScale, colorSample);
	}
	#endfor

	// HSV blending
	vec3 hsvCurrent = rgb2hsv(currentColor.rgb);
	vec3 hsvNew = vec3(hsvCurrent.x, hsvCurrent.y, colorGrayScale.r);
	vec3 rgbNew = hsv2rgb(hsvNew);
/*
	// HSL blending
	vec3 hslCurrent = rgb2hsl(currentColor.rgb);
	//hslCurrent.y = hslCurrent.z > 0.7 ? 0 : hslCurrent.y;
	vec3 hslNew = vec3(hslCurrent.x, hslCurrent.y, colorGrayScale.r);
	vec3 rgbNew = hsl2rgb(hslNew);
*/
	// No color blending, use grayscale
	//vec3 rgbNew = colorGrayScale.rgb;
	vec4 color = blendOver(currentColor, vec4(rgbNew, colorGrayScale.a));

	return color;
}

vec4 calculateWater(
	const vec4 currentColor,
	const vec2 uv,
	LevelWeights levelWeights,
	const Layer WaterMasks[NUMLAYERS_WATERMASK],
	const vec3 ellipsoidNormalCameraSpace,
	const vec3 lightDirectionCameraSpace,
	const vec3 positionCameraSpace) {

	vec4 waterColor = vec4(0,0,0,0);

	// The shader compiler will remove unused code when variables are multiplied by
	// a constant 0
#if !WATERMASK_BLENDING_ENABLED
	levelWeights = getDefaultLevelWeights();
#endif // WATERMASK_BLENDING_ENABLED

	#for i in 0..#{lastLayerIndexWaterMasks}
	{
		vec4 colorSample = getTexVal(WaterMasks[#{i}].pile, levelWeights, uv);

		colorSample = performLayerSettings(colorSample, WaterMasks[#{i}].settings);

		waterColor = blendOver(waterColor, colorSample);
	}
	#endfor

	vec3 directionToFragmentCameraSpace = normalize(positionCameraSpace - vec3(0, 0, 0));
	vec3 reflectionDirectionCameraSpace = reflect(lightDirectionCameraSpace, ellipsoidNormalCameraSpace);
	float cosineFactor = clamp(dot(-reflectionDirectionCameraSpace, directionToFragmentCameraSpace), 0, 1);
	cosineFactor = pow(cosineFactor, 100);

	vec3 specularColor = vec3(1, 1, 1);
	float specularIntensity = 0.4;

	vec3 specularTotal = specularColor * cosineFactor * specularIntensity * waterColor.a;

	//return blendOver(currentColor, waterColor);
	return currentColor + vec4(specularTotal, 1);
}

#endif // TEXTURETILEMAPPING_HGLSL