define([
        '../Core/Cartesian2',
        '../Core/Check',
        '../Renderer/PixelDatatype',
        './ClippingPlaneCollection'
    ], function(
        Cartesian2,
        Check,
        PixelDatatype,
        ClippingPlaneCollection) {
    'use strict';

    var textureResolutionScratch = new Cartesian2();
    /**
     * Gets the GLSL functions needed to retrieve clipping planes from a ClippingPlaneCollection's texture.
     *
     * @param {ClippingPlaneCollection} clippingPlaneCollection ClippingPlaneCollection with a defined texture.
     * @param {Context} context The current rendering context.
     * @returns {String} A string containing GLSL functions for retrieving clipping planes.
     * @private
     */
    function getClippingFunction(clippingPlaneCollection, context) {
        //>>includeStart('debug', pragmas.debug);
        Check.typeOf.object('clippingPlaneCollection', clippingPlaneCollection);
        Check.typeOf.object('context', context);
        //>>includeEnd('debug');
        var unionClippingRegions = clippingPlaneCollection.unionClippingRegions;
        var clippingPlanesLength = clippingPlaneCollection.length;
        var usingFloatTexture = ClippingPlaneCollection.useFloatTexture(context);
        var textureResolution = ClippingPlaneCollection.getTextureResolution(clippingPlaneCollection, context, textureResolutionScratch);
        var width = textureResolution.x;
        var height = textureResolution.y;

        var functions = usingFloatTexture ? getClippingPlaneFloat(width, height) : getClippingPlaneUint8(width, height);
        functions += '\n';
        functions += unionClippingRegions ? clippingFunctionUnion(clippingPlanesLength) : clippingFunctionIntersect(clippingPlanesLength);
        return functions;
    }

    function clippingFunctionUnion(clippingPlanesLength) {
        var functionString =
            'float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)\n' +
            '{\n' +
            '    vec4 position = czm_windowToEyeCoordinates(fragCoord);\n' +
            '    vec3 clipNormal = vec3(0.0);\n' +
            '    vec3 clipPosition = vec3(0.0);\n' +
            '    float clipAmount;\n' + // For union planes, we want to get the min distance. So we set the initial value to the first plane distance in the loop below.
            '    float pixelWidth = czm_metersPerPixel(position);\n' +
            '    bool breakAndDiscard = false;\n' +

            '    for (int i = 0; i < ' + clippingPlanesLength + '; ++i)\n' +
            '    {\n' +
            '        vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);\n' +

            '        clipNormal = clippingPlane.xyz;\n' +
            '        clipPosition = -clippingPlane.w * clipNormal;\n' +

            '        float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n' +
            '        clipAmount = czm_branchFreeTernary(i == 0, amount, min(amount, clipAmount));\n' +

            '        if (amount <= 0.0)\n' +
            '        {\n' +
            '           breakAndDiscard = true;\n' +
            '           break;\n' + // HLSL compiler bug if we discard here: https://bugs.chromium.org/p/angleproject/issues/detail?id=1945#c6
            '        }\n' +
            '    }\n' +

            '    if (breakAndDiscard) {\n' +
            '        discard;\n' +
            '    }\n' +
            '    return clipAmount;\n' +
            '}\n';
        return functionString;
    }

    function clippingFunctionIntersect(clippingPlanesLength) {
        var functionString =
            'float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix)\n' +
            '{\n' +
            '    bool clipped = true;\n' +
            '    vec4 position = czm_windowToEyeCoordinates(fragCoord);\n' +
            '    vec3 clipNormal = vec3(0.0);\n' +
            '    vec3 clipPosition = vec3(0.0);\n' +
            '    float clipAmount = 0.0;\n' +
            '    float pixelWidth = czm_metersPerPixel(position);\n' +

            '    for (int i = 0; i < ' + clippingPlanesLength + '; ++i)\n' +
            '    {\n' +
            '        vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix);\n' +

            '        clipNormal = clippingPlane.xyz;\n' +
            '        clipPosition = -clippingPlane.w * clipNormal;\n' +

            '        float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n' +
            '        clipAmount = max(amount, clipAmount);\n' +

            '        clipped = clipped && (amount <= 0.0);\n' +
            '    }\n' +

            '    if (clipped)\n' +
            '    {\n' +
            '        discard;\n' +
            '    }\n' +

            '    return clipAmount;\n' +
            '}\n';
        return functionString;
    }

    function getClippingPlaneFloat(width, height) {
        var pixelWidth = 1.0 / width;
        var pixelHeight = 1.0 / height;

        var pixelWidthString = pixelWidth + '';
        if (pixelWidthString.indexOf('.') === -1) {
            pixelWidthString += '.0';
        }
        var pixelHeightString = pixelHeight + '';
        if (pixelHeightString.indexOf('.') === -1) {
            pixelHeightString += '.0';
        }

        var functionString =
            'vec4 getClippingPlane(sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)\n' +
            '{\n' +
            '    int pixY = clippingPlaneNumber / ' + width + ';\n' +
            '    int pixX = clippingPlaneNumber - (pixY * ' + width + ');\n' +
            '    float u = (float(pixX) + 0.5) * ' + pixelWidthString + ';\n' + // sample from center of pixel
            '    float v = (float(pixY) + 0.5) * ' + pixelHeightString + ';\n' +
            '    vec4 plane = texture2D(packedClippingPlanes, vec2(u, v));\n' +
            '    return czm_transformPlane(plane, transform);\n' +
            '}\n';
        return functionString;
    }

    function getClippingPlaneUint8(width, height) {
        var pixelWidth = 1.0 / width;
        var pixelHeight = 1.0 / height;

        var pixelWidthString = pixelWidth + '';
        if (pixelWidthString.indexOf('.') === -1) {
            pixelWidthString += '.0';
        }
        var pixelHeightString = pixelHeight + '';
        if (pixelHeightString.indexOf('.') === -1) {
            pixelHeightString += '.0';
        }

        var functionString =
            'vec4 getClippingPlane(sampler2D packedClippingPlanes, int clippingPlaneNumber, mat4 transform)\n' +
            '{\n' +
            '    int clippingPlaneStartIndex = clippingPlaneNumber * 2;\n' + // clipping planes are two pixels each
            '    int pixY = clippingPlaneStartIndex / ' + width + ';\n' +
            '    int pixX = clippingPlaneStartIndex - (pixY * ' + width + ');\n' +
            '    float u = (float(pixX) + 0.5) * ' + pixelWidthString + ';\n' + // sample from center of pixel
            '    float v = (float(pixY) + 0.5) * ' + pixelHeightString + ';\n' +

            '    vec4 oct32 = texture2D(packedClippingPlanes, vec2(u, v)) * 255.0;\n' +
            '    vec2 oct = vec2(oct32.x * 256.0 + oct32.y, oct32.z * 256.0 + oct32.w);\n' +

            '    vec4 plane;\n' +
            '    plane.xyz = czm_octDecode(oct, 65535.0);\n' +
            '    plane.w = czm_unpackFloat(texture2D(packedClippingPlanes, vec2(u + ' + pixelWidthString + ', v)));\n' +

            '    return czm_transformPlane(plane, transform);\n' +
            '}\n';
        return functionString;
    }

    return getClippingFunction;
});
