/*
 * Copyright (C) 2014 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration. All Rights Reserved.
 */
/**
 * @exports GroundProgram
 */
define([
        '../shaders/AtmosphereProgram'
    ],
    function (AtmosphereProgram) {
        "use strict";

        /**
         * Constructs a new program.
         * Initializes, compiles and links this GLSL program with the source code for its vertex and fragment shaders.
         * <p>
         * This method creates WebGL shaders for the program's shader sources and attaches them to a new GLSL program.
         * This method then compiles the shaders and then links the program if compilation is successful. Use the bind
         * method to make the program current during rendering.
         *
         * @alias GroundProgram
         * @constructor
         * @augments AtmosphereProgram
         * @classdesc GroundProgram is a GLSL program that draws the ground component of the atmosphere.
         * @param {WebGLRenderingContext} gl The current WebGL context.
         * @throws {ArgumentError} If the shaders cannot be compiled, or linking of
         * the compiled shaders into a program fails.
         */
        var GroundProgram = function (gl) {
            var vertexShaderSource =
                    'precision mediump int;\n' +

                    'const int FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;\n' +
                    'const int SAMPLE_COUNT = 2;\n' +
                    'const float SAMPLES = 2.0;\n' +

                    'const float PI = 3.141592653589;\n' +
                    'const float Kr = 0.0025;\n' +
                    'const float Kr4PI = Kr * 4.0 * PI;\n' +
                    'const float Km = 0.0015;\n' +
                    'const float Km4PI = Km * 4.0 * PI;\n' +
                    'const float ESun = 15.0;\n' +
                    'const float KmESun = Km * ESun;\n' +
                    'const float KrESun = Kr * ESun;\n' +
                    'const vec3 invWavelength = vec3(5.60204474633241, 9.473284437923038, 19.643802610477206);\n' +
                    'const float rayleighScaleDepth = 0.25;\n' +

                    'uniform int fragMode;\n' +
                    'uniform mat4 mvpMatrix;\n' +
                    'uniform mat3 texCoordMatrix;\n' +
                    'uniform vec3 vertexOrigin;\n' +
                    'uniform vec3 eyePoint;\n' +
                    'uniform float eyeMagnitude;\n' + /* The eye point's magnitude */
                    'uniform float eyeMagnitude2;\n' + /* eyeMagnitude^2 */
                    'uniform vec3 lightDirection;\n' + /* The direction vector to the light source */
                    'uniform float atmosphereRadius;\n' + /* The outer (atmosphere) radius */
                    'uniform float atmosphereRadius2;\n' + /* atmosphereRadius^2 */
                    'uniform float globeRadius;\n' + /* The inner (planetary) radius */
                    'uniform float scale;\n' + /* 1 / (atmosphereRadius - globeRadius) */
                    'uniform float scaleDepth;\n' + /* The scale depth (i.e. the altitude at which
                     the atmosphere's average density is found) */
                    'uniform float scaleOverScaleDepth;\n' + /* fScale / fScaleDepth */

                    'attribute vec4 vertexPoint;\n' +
                    'attribute vec2 vertexTexCoord;\n' +

                    'varying vec3 primaryColor;\n' +
                    'varying vec3 secondaryColor;\n' +
                    'varying vec2 texCoord;\n' +

                    'float scaleFunc(float cos) {\n' +
                    '    float x = 1.0 - cos;\n' +
                    '    return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));\n' +
                    '}\n' +

                    'void sampleGround() {\n' +
                    /* Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the
                     atmosphere) */
                    '    vec3 point = vertexPoint.xyz + vertexOrigin;\n' +
                    '    vec3 ray = point - eyePoint;\n' +
                    '    float far = length(ray);\n' +
                    '    ray /= far;\n' +

                    '    vec3 start;\n' +
                    '    if (eyeMagnitude < atmosphereRadius) {\n' +
                    '        start = eyePoint;\n' +
                    '    } else {\n' +
                    /* Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray
                     passing through the atmosphere) */
                    '        float B = 2.0 * dot(eyePoint, ray);\n' +
                    '        float C = eyeMagnitude2 - atmosphereRadius2;\n' +
                    '        float det = max(0.0, B*B - 4.0 * C);\n' +
                    '        float near = 0.5 * (-B - sqrt(det));\n' +

                    /* Calculate the ray's starting point, then calculate its scattering offset */
                    '        start = eyePoint + ray * near;\n' +
                    '        far -= near;\n' +
                    '}\n' +

                    '    float depth = exp((globeRadius - atmosphereRadius) / scaleDepth);\n' +
                    '    float eyeAngle = dot(-ray, point) / length(point);\n' +
                    '    float lightAngle = dot(lightDirection, point) / length(point);\n' +
                    '    float eyeScale = scaleFunc(eyeAngle);\n' +
                    '    float lightScale = scaleFunc(lightAngle);\n' +
                    '    float eyeOffset = depth*eyeScale;\n' +
                    '    float temp = (lightScale + eyeScale);\n' +

                    /* Initialize the scattering loop variables */
                    '    float sampleLength = far / SAMPLES;\n' +
                    '    float scaledLength = sampleLength * scale;\n' +
                    '    vec3 sampleRay = ray * sampleLength;\n' +
                    '    vec3 samplePoint = start + sampleRay * 0.5;\n' +

                    /* Now loop through the sample rays */
                    '    vec3 frontColor = vec3(0.0, 0.0, 0.0);\n' +
                    '    vec3 attenuate = vec3(0.0, 0.0, 0.0);\n' +
                    '    for(int i=0; i<SAMPLE_COUNT; i++)\n' +
                    '    {\n' +
                    '        float height = length(samplePoint);\n' +
                    '        float depth = exp(scaleOverScaleDepth * (globeRadius - height));\n' +
                    '        float scatter = depth*temp - eyeOffset;\n' +
                    '        attenuate = exp(-scatter * (invWavelength * Kr4PI + Km4PI));\n' +
                    '        frontColor += attenuate * (depth * scaledLength);\n' +
                    '        samplePoint += sampleRay;\n' +
                    '    }\n' +

                    '    primaryColor = frontColor * (invWavelength * KrESun + KmESun);\n' +
                    '    secondaryColor = attenuate;\n' + /* Calculate the attenuation factor for the ground */
                    '}\n' +

                    'void main()\n ' +
                    '{\n' +
                    '    sampleGround();\n' +
                    /* Transform the vertex point by the modelview-projection matrix */
                    '    gl_Position = mvpMatrix * vertexPoint;\n' +
                    '    if (fragMode == FRAGMODE_GROUND_PRIMARY_TEX_BLEND) {\n' +
                    /* Transform the vertex texture coordinate by the tex coord matrix */
                    '        texCoord = (texCoordMatrix * vec3(vertexTexCoord, 1.0)).st;\n' +
                    '    }\n' +
                    '}',
                fragmentShaderSource =
                    'precision mediump float;\n' +
                    'precision mediump int;\n' +

                    'const int FRAGMODE_GROUND_PRIMARY = 2;\n' +
                    'const int FRAGMODE_GROUND_SECONDARY = 3;\n' +
                    'const int FRAGMODE_GROUND_PRIMARY_TEX_BLEND = 4;\n' +

                    'uniform int fragMode;\n' +
                    'uniform sampler2D texSampler;\n' +

                    'varying vec3 primaryColor;\n' +
                    'varying vec3 secondaryColor;\n' +
                    'varying vec2 texCoord;\n' +

                    'void main (void)\n' +
                    '{\n' +
                    '    if (fragMode == FRAGMODE_GROUND_PRIMARY) {\n' +
                    '        gl_FragColor = vec4(primaryColor, 1.0);\n' +
                    '    } else if (fragMode == FRAGMODE_GROUND_SECONDARY) {\n' +
                    '        gl_FragColor = vec4(secondaryColor, 1.0);\n' +
                    '    } else if (fragMode == FRAGMODE_GROUND_PRIMARY_TEX_BLEND) {\n' +
                    '        vec4 texColor = texture2D(texSampler, texCoord);\n' +
                    '        gl_FragColor = vec4(primaryColor + texColor.rgb * (1.0 - secondaryColor), 1.0);\n' +
                    '    }\n' +
                    '}';

            // Call to the superclass, which performs shader program compiling and linking.
            AtmosphereProgram.call(this, gl, vertexShaderSource, fragmentShaderSource, ["vertexPoint", "vertexTexCoord"]);
        };

        /**
         * A string that uniquely identifies this program.
         * @type {string}
         * @readonly
         */
        GroundProgram.key = "WorldWindGroundProgram";

        // Inherit from AtmosphereProgram.
        GroundProgram.prototype = Object.create(AtmosphereProgram.prototype);

        return GroundProgram;
    });


