/*global defineSuite*/
defineSuite([
        'Scene/Primitive',
        'Core/BoundingSphere',
        'Core/BoxGeometry',
        'Core/Cartesian3',
        'Core/ColorGeometryInstanceAttribute',
        'Core/ComponentDatatype',
        'Core/defined',
        'Core/Ellipsoid',
        'Core/Geometry',
        'Core/GeometryAttribute',
        'Core/GeometryInstance',
        'Core/GeometryInstanceAttribute',
        'Core/Matrix4',
        'Core/PolygonGeometry',
        'Core/PrimitiveType',
        'Core/Rectangle',
        'Core/RectangleGeometry',
        'Core/RuntimeError',
        'Core/ShowGeometryInstanceAttribute',
        'Core/Transforms',
        'Scene/Camera',
        'Scene/MaterialAppearance',
        'Scene/OrthographicFrustum',
        'Scene/PerInstanceColorAppearance',
        'Scene/SceneMode',
        'Specs/BadGeometry',
        'Specs/createFrameState',
        'Specs/createScene',
        'Specs/pollToPromise'
    ], function(
        Primitive,
        BoundingSphere,
        BoxGeometry,
        Cartesian3,
        ColorGeometryInstanceAttribute,
        ComponentDatatype,
        defined,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryInstance,
        GeometryInstanceAttribute,
        Matrix4,
        PolygonGeometry,
        PrimitiveType,
        Rectangle,
        RectangleGeometry,
        RuntimeError,
        ShowGeometryInstanceAttribute,
        Transforms,
        Camera,
        MaterialAppearance,
        OrthographicFrustum,
        PerInstanceColorAppearance,
        SceneMode,
        BadGeometry,
        createFrameState,
        createScene,
        pollToPromise) {
    "use strict";

    var scene;
    var context;

    var ellipsoid;

    var rectangle1;
    var rectangle2;

    var rectangleInstance1;
    var rectangleInstance2;

    var primitive;

    beforeAll(function() {
        scene = createScene();
        scene.primitives.destroyPrimitives = false;
        context = scene.context;
        ellipsoid = Ellipsoid.WGS84;
    });

    afterAll(function() {
        scene.destroyForSpecs();
    });

    beforeEach(function() {
        scene.morphTo3D(0);

        rectangle1 = Rectangle.fromDegrees(-80.0, 20.0, -70.0, 30.0);
        rectangle2 = Rectangle.fromDegrees(70.0, 20.0, 80.0, 30.0);

        var translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rectangle1)), new Cartesian3()), 2.0, new Cartesian3());
        rectangleInstance1 = new GeometryInstance({
            geometry : new RectangleGeometry({
                vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT,
                ellipsoid : ellipsoid,
                rectangle : rectangle1
            }),
            modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()),
            id : 'rectangle1',
            attributes : {
                color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0),
                show : new ShowGeometryInstanceAttribute(true)
            }
        });

        translation = Cartesian3.multiplyByScalar(Cartesian3.normalize(ellipsoid.cartographicToCartesian(Rectangle.center(rectangle2)), new Cartesian3()), 3.0, new Cartesian3());
        rectangleInstance2 = new GeometryInstance({
            geometry : new RectangleGeometry({
                vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT,
                ellipsoid : ellipsoid,
                rectangle : rectangle2
            }),
            modelMatrix : Matrix4.fromTranslation(translation, new Matrix4()),
            id : 'rectangle2',
            attributes : {
                color : new ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 1.0),
                show : new ShowGeometryInstanceAttribute(true)
            }
        });
    });

    afterEach(function() {
        scene.primitives.removeAll();
        primitive = primitive && !primitive.isDestroyed() && primitive.destroy();
    });

    it('default constructs', function() {
        primitive = new Primitive();
        expect(primitive.geometryInstances).not.toBeDefined();
        expect(primitive.appearance).not.toBeDefined();
        expect(primitive.modelMatrix).toEqual(Matrix4.IDENTITY);
        expect(primitive.show).toEqual(true);
        expect(primitive.vertexCacheOptimize).toEqual(false);
        expect(primitive.interleave).toEqual(false);
        expect(primitive.compressVertices).toEqual(true);
        expect(primitive.releaseGeometryInstances).toEqual(true);
        expect(primitive.allowPicking).toEqual(true);
        expect(primitive.cull).toEqual(true);
        expect(primitive.asynchronous).toEqual(true);
        expect(primitive.debugShowBoundingVolume).toEqual(false);
    });

    it('Constructs with options', function() {
        var geometryInstances = {};
        var appearance = {};
        var modelMatrix = Matrix4.fromUniformScale(5.0);

        primitive = new Primitive({
            geometryInstances : geometryInstances,
            appearance : appearance,
            modelMatrix : modelMatrix,
            show : false,
            vertexCacheOptimize : true,
            interleave : true,
            compressVertices : false,
            releaseGeometryInstances : false,
            allowPicking : false,
            cull : false,
            asynchronous : false,
            debugShowBoundingVolume : true
        });

        expect(primitive.geometryInstances).toEqual(geometryInstances);
        expect(primitive.appearance).toEqual(appearance);
        expect(primitive.modelMatrix).toEqual(modelMatrix);
        expect(primitive.show).toEqual(false);
        expect(primitive.vertexCacheOptimize).toEqual(true);
        expect(primitive.interleave).toEqual(true);
        expect(primitive.compressVertices).toEqual(false);
        expect(primitive.releaseGeometryInstances).toEqual(false);
        expect(primitive.allowPicking).toEqual(false);
        expect(primitive.cull).toEqual(false);
        expect(primitive.asynchronous).toEqual(false);
        expect(primitive.debugShowBoundingVolume).toEqual(true);
    });

    it('releases geometry instances when releaseGeometryInstances is true', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            releaseGeometryInstances : true,
            asynchronous : false
        });

        expect(primitive.geometryInstances).toBeDefined();
        scene.primitives.add(primitive);
        scene.renderForSpecs();
        expect(primitive.geometryInstances).not.toBeDefined();
    });

    it('does not release geometry instances when releaseGeometryInstances is false', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            releaseGeometryInstances : false,
            asynchronous : false
        });

        expect(primitive.geometryInstances).toBeDefined();
        scene.primitives.add(primitive);
        scene.renderForSpecs();
        expect(primitive.geometryInstances).toBeDefined();
    });

    it('adds afterRender promise to frame state', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            releaseGeometryInstances : false,
            asynchronous : false
        });

        scene.primitives.add(primitive);
        scene.renderForSpecs();

        return primitive.readyPromise.then(function(param) {
            expect(param.ready).toBe(true);
        });
    });

    it('does not render when geometryInstances is an empty array', function() {
        primitive = new Primitive({
            geometryInstances : [],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        primitive.update(frameState);
        expect(frameState.commandList.length).toEqual(0);
    });

    it('does not render when show is false', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        primitive.update(frameState);
        expect(frameState.commandList.length).toBeGreaterThan(0);

        frameState.commandList.length = 0;
        primitive.show = false;
        primitive.update(frameState);
        expect(frameState.commandList.length).toEqual(0);
    });

    it('does not render other than for the color or pick pass', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        frameState.passes.render = false;
        frameState.passes.pick = false;

        primitive.update(frameState);
        expect(frameState.commandList.length).toEqual(0);
    });

    it('does not render when scene3DOnly is true and the scene mode is SCENE2D', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        frameState.mode = SceneMode.SCENE2D;
        frameState.scene3DOnly = true;

        primitive.update(frameState);
        expect(frameState.commandList.length).toEqual(0);
    });

    it('does not render when scene3DOnly is true and the scene mode is COLUMBUS_VIEW', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        frameState.mode = SceneMode.COLUMBUS_VIEW;
        frameState.scene3DOnly = true;

        primitive.update(frameState);
        expect(frameState.commandList.length).toEqual(0);
    });

    it('renders in two passes for closed, translucent geometry', function() {
        primitive = new Primitive({
            geometryInstances : new GeometryInstance({
                geometry : BoxGeometry.fromDimensions({
                    vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT,
                    dimensions : new Cartesian3(500000.0, 500000.0, 500000.0)
                }),
                id : 'box',
                attributes : {
                    color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 0.5)
                }
            }),
            appearance : new PerInstanceColorAppearance({
                closed : true,
                translucent : true
            }),
            asynchronous : false
        });

        var frameState = createFrameState(context);

        // set scene3DOnly to true so that the geometry is not split due to the IDL
        frameState.scene3DOnly = true;
        primitive.update(frameState);
        expect(frameState.commandList.length).toEqual(2);
    });

    function verifyPrimitiveRender(primitive, rectangle) {
        scene.primitives.removeAll();
        if (defined(rectangle)){
            scene.camera.setView({ destination : rectangle });
        }
        expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]);

        scene.primitives.add(primitive);
        expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]);
    }

    it('renders in Columbus view when scene3DOnly is false', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.morphToColumbusView(0);

        verifyPrimitiveRender(primitive, rectangle1);
        verifyPrimitiveRender(primitive, rectangle2);
    });

    it('renders in 2D when scene3DOnly is false', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.morphTo2D(0);
        verifyPrimitiveRender(primitive, rectangle1);
        verifyPrimitiveRender(primitive, rectangle2);
    });

    it('renders RTC', function() {
        var dimensions = new Cartesian3(400.0, 300.0, 500.0);
        var positionOnEllipsoid = Cartesian3.fromDegrees(-105.0, 45.0);
        var boxModelMatrix = Matrix4.multiplyByTranslation(
                Transforms.eastNorthUpToFixedFrame(positionOnEllipsoid),
                new Cartesian3(0.0, 0.0, dimensions.z * 0.5), new Matrix4());

        var boxGeometry = BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
            vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT,
            dimensions : dimensions
        }));

        var positions = boxGeometry.attributes.position.values;
        var newPositions = new Float32Array(positions.length);
        for (var i = 0; i < positions.length; ++i) {
            newPositions[i] = positions[i];
        }
        boxGeometry.attributes.position.values = newPositions;
        boxGeometry.attributes.position.componentDatatype = ComponentDatatype.FLOAT;

        BoundingSphere.transform(boxGeometry.boundingSphere, boxModelMatrix, boxGeometry.boundingSphere);

        var boxGeometryInstance = new GeometryInstance({
            geometry : boxGeometry,
            attributes : {
                color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 0.5)
            }
        });

        var primitive = new Primitive({
            geometryInstances : boxGeometryInstance,
            appearance : new PerInstanceColorAppearance({
                closed: true
            }),
            asynchronous : false,
            allowPicking : false,
            rtcCenter : boxGeometry.boundingSphere.center
        });

        // create test camera
        var camera = scene.camera;
        var testCamera = new Camera(scene);
        testCamera.viewBoundingSphere(boxGeometry.boundingSphere);
        scene._camera = testCamera;

        scene.frameState.scene3DOnly = true;
        verifyPrimitiveRender(primitive);

        scene._camera = camera;
    });

    it('RTC throws with more than one instance', function() {
        expect(function() {
            return new Primitive({
                geometryInstances : [rectangleInstance1, rectangleInstance2],
                appearance : new PerInstanceColorAppearance({
                    closed: true
                }),
                asynchronous : false,
                allowPicking : false,
                rtcCenter : Cartesian3.ZERO
            });
        }).toThrowDeveloperError();
    });

    it('RTC throws if the scene is not 3D only', function() {
        var primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance({
                closed: true
            }),
            asynchronous : false,
            allowPicking : false,
            rtcCenter : Cartesian3.ZERO
        });

        expect(function() {
            verifyPrimitiveRender(primitive);
        }).toThrowDeveloperError();
    });

    it('updates model matrix for one instance in 3D', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        primitive.update(frameState);
        var commands = frameState.commandList;
        expect(commands.length).toEqual(1);
        expect(commands[0].modelMatrix).toEqual(Matrix4.IDENTITY);

        var modelMatrix = Matrix4.fromUniformScale(10.0);
        primitive.modelMatrix = modelMatrix;

        commands.length = 0;
        primitive.update(frameState);
        expect(commands.length).toEqual(1);
        expect(commands[0].modelMatrix).toEqual(modelMatrix);
    });

    it('updates model matrix for more than one instance in 3D with equal model matrices in 3D only scene', function() {
        var modelMatrix = Matrix4.fromUniformScale(2.0);
        rectangleInstance1.modelMatrix = modelMatrix;
        rectangleInstance2.modelMatrix = modelMatrix;

        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        frameState.scene3DOnly = true;

        primitive.update(frameState);
        var commands = frameState.commandList;
        expect(commands.length).toEqual(1);
        expect(commands[0].modelMatrix).toEqual(modelMatrix);

        modelMatrix = Matrix4.fromUniformScale(10.0);
        primitive.modelMatrix = modelMatrix;

        commands.length = 0;
        primitive.update(frameState);
        expect(commands.length).toEqual(1);
        expect(commands[0].modelMatrix).toEqual(modelMatrix);
    });

    it('computes model matrix when given one for a single instance and for the primitive in 3D only', function() {
        var instanceModelMatrix = Matrix4.fromUniformScale(2.0);

        var dimensions = new Cartesian3(400000.0, 300000.0, 500000.0);
        var positionOnEllipsoid = Cartesian3.fromDegrees(-105.0, 45.0);
        var primitiveModelMatrix = Matrix4.multiplyByTranslation(
            Transforms.eastNorthUpToFixedFrame(positionOnEllipsoid),
            new Cartesian3(0.0, 0.0, dimensions.z * 0.5), new Matrix4());

        var boxGeometry = BoxGeometry.fromDimensions({
            vertexFormat : PerInstanceColorAppearance.VERTEX_FORMAT,
            dimensions : dimensions
        });
        var boxGeometryInstance = new GeometryInstance({
            geometry : boxGeometry,
            modelMatrix : instanceModelMatrix,
            attributes : {
                color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
            }
        });
        primitive = new Primitive({
            geometryInstances : boxGeometryInstance,
            modelMatrix : primitiveModelMatrix,
            appearance : new PerInstanceColorAppearance({
                translucent : false,
                closed: true
            }),
            asynchronous : false
        });

        var expectedModelMatrix = Matrix4.multiplyTransformation(primitiveModelMatrix, instanceModelMatrix, new Matrix4());

        var frameState = createFrameState(context);
        frameState.scene3DOnly = true;

        primitive.update(frameState);
        var commands = frameState.commandList;
        expect(commands.length).toEqual(1);
        expect(commands[0].modelMatrix).toEqual(expectedModelMatrix);
    });

    it('update model matrix throws in Columbus view', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        frameState.mode = SceneMode.COLUMBUS_VIEW;

        primitive.update(frameState);
        var commands = frameState.commandList;
        expect(commands.length).toEqual(1);
        expect(commands[0].modelMatrix).toEqual(Matrix4.IDENTITY);

        var modelMatrix = Matrix4.fromUniformScale(10.0);
        primitive.modelMatrix = modelMatrix;

        commands.length = 0;
        expect(function() {
            primitive.update(frameState);
        }).toThrowDeveloperError();
    });

    it('update model matrix throws in 2D', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        frameState.mode = SceneMode.SCENE2D;

        var commands = frameState.commandList;
        primitive.update(frameState);
        expect(commands.length).toEqual(1);
        expect(commands[0].modelMatrix).toEqual(Matrix4.IDENTITY);

        var modelMatrix = Matrix4.fromUniformScale(10.0);
        primitive.modelMatrix = modelMatrix;

        commands.length = 0;
        expect(function() {
            primitive.update(frameState);
        }).toThrowDeveloperError();
    });

    it('renders bounding volume with debugShowBoundingVolume', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false,
            debugShowBoundingVolume : true
        });

        scene.primitives.add(primitive);
        scene.camera.setView({ destination : rectangle1 });
        var pixels = scene.renderForSpecs();
        expect(pixels[0]).not.toEqual(0);
        expect(pixels[1]).toBeGreaterThanOrEqualTo(0);
        expect(pixels[2]).toBeGreaterThanOrEqualTo(0);
        expect(pixels[3]).toEqual(255);
    });

    it('transforms to world coordinates', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        verifyPrimitiveRender(primitive, rectangle1);
        verifyPrimitiveRender(primitive, rectangle2);
        expect(primitive.modelMatrix).toEqual(Matrix4.IDENTITY);
    });

    it('does not transform to world coordinates', function() {
        rectangleInstance2.modelMatrix = Matrix4.clone(rectangleInstance1.modelMatrix);
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.frameState.scene3DOnly = true;
        verifyPrimitiveRender(primitive, rectangle1);
        verifyPrimitiveRender(primitive, rectangle2);
        expect(primitive.modelMatrix).not.toEqual(Matrix4.IDENTITY);
        scene.frameState.scene3DOnly = true;
    });

    it('get common per instance attributes', function() {
        rectangleInstance2.attributes.not_used = new GeometryInstanceAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 1,
            value : [0.5]
        });

        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.primitives.add(primitive);
        scene.renderForSpecs();

        var attributes = primitive.getGeometryInstanceAttributes('rectangle1');
        expect(attributes.color).toBeDefined();
        expect(attributes.show).toBeDefined();

        attributes = primitive.getGeometryInstanceAttributes('rectangle2');
        expect(attributes.color).toBeDefined();
        expect(attributes.show).toBeDefined();
        expect(attributes.not_used).not.toBeDefined();
    });

    it('modify color instance attribute', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.camera.setView({ destination : rectangle1 });
        scene.primitives.add(primitive);
        var pixels = scene.renderForSpecs();
        expect(pixels).not.toEqual([0, 0, 0, 255]);

        var attributes = primitive.getGeometryInstanceAttributes('rectangle1');
        expect(attributes.color).toBeDefined();
        attributes.color = [255, 255, 255, 255];

        var newPixels = scene.renderForSpecs();
        expect(newPixels).not.toEqual([0, 0, 0, 255]);
        expect(newPixels).not.toEqual(pixels);
    });

    it('modify show instance attribute', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.primitives.add(primitive);
        scene.camera.setView({ destination : rectangle1 });
        var pixels = scene.renderForSpecs();
        expect(pixels).not.toEqual([0, 0, 0, 255]);

        var attributes = primitive.getGeometryInstanceAttributes('rectangle1');
        expect(attributes.show).toBeDefined();
        attributes.show = [0];

        var newPixels = scene.renderForSpecs();
        expect(newPixels).toEqual([0, 0, 0, 255]);
    });

    it('get bounding sphere from per instance attribute', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        verifyPrimitiveRender(primitive, rectangle1);

        var attributes = primitive.getGeometryInstanceAttributes('rectangle1');
        expect(attributes.boundingSphere).toBeDefined();
    });

    it('getGeometryInstanceAttributes returns same object each time', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        verifyPrimitiveRender(primitive, rectangle1);

        var attributes = primitive.getGeometryInstanceAttributes('rectangle1');
        var attributes2 = primitive.getGeometryInstanceAttributes('rectangle1');
        expect(attributes).toBe(attributes2);
    });

    it('picking', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        verifyPrimitiveRender(primitive, rectangle1);

        var pickObject = scene.pickForSpecs();
        expect(pickObject.primitive).toEqual(primitive);
        expect(pickObject.id).toEqual('rectangle1');

        verifyPrimitiveRender(primitive, rectangle2);

        pickObject = scene.pickForSpecs();
        expect(pickObject.primitive).toEqual(primitive);
        expect(pickObject.id).toEqual('rectangle2');
    });

    it('does not pick when allowPicking is false', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1],
            appearance : new PerInstanceColorAppearance(),
            allowPicking : false,
            asynchronous : false
        });

        verifyPrimitiveRender(primitive, rectangle1);

        var pickObject = scene.pickForSpecs();
        expect(pickObject).not.toBeDefined();
    });

    it('does not cull when cull is false', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false,
            cull : false
        });

        var frameState = createFrameState(context);
        primitive.update(frameState);
        expect(frameState.commandList[0].cull).toEqual(false);
    });

    it('update throws when geometry primitive types are different', function() {
        primitive = new Primitive({
            geometryInstances : [
                new GeometryInstance({
                    geometry : new Geometry({
                        attributes : {
                            position : new GeometryAttribute({
                                componentDatatype : ComponentDatatype.FLOAT,
                                componentsPerAttribute : 3,
                                values : new Float32Array([1.0, 2.0, 3.0, 4.0])
                            })
                        },
                        primitiveType : PrimitiveType.LINES
                    })
                }),
                new GeometryInstance({
                    geometry : new Geometry({
                        attributes : {
                            position : new GeometryAttribute({
                                componentDatatype : ComponentDatatype.FLOAT,
                                componentsPerAttribute : 3,
                                values : new Float32Array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
                            })
                        },
                        primitiveType : PrimitiveType.TRIANGLES
                    })
                })
            ],
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);

        expect(function() {
            primitive.update(frameState);
        }).toThrowDeveloperError();
    });

    it('failed geometry rejects promise and throws on next update', function() {
        primitive = new Primitive({
            geometryInstances : [new GeometryInstance({
                geometry : new BadGeometry()
            })],
            appearance : new MaterialAppearance({
                materialSupport : MaterialAppearance.MaterialSupport.ALL
            }),
            compressVertices : false
        });

        var frameState = createFrameState(context);

        return pollToPromise(function() {
            if (frameState.afterRender.length > 0) {
                frameState.afterRender[0]();
                return true;
            }
            primitive.update(frameState);
            return false;
        }).then(function() {
            return primitive.readyPromise.then(function() {
                fail('should not be called');
            }).otherwise(function(e) {
                expect(e).toBe(primitive._error);
                expect(function() {
                    primitive.update(frameState);
                }).toThrowRuntimeError();
            });
        });
    });

    it('internally invalid asynchronous geometry resolves promise and sets ready', function() {
        primitive = new Primitive({
            geometryInstances : [new GeometryInstance({
                geometry : PolygonGeometry.fromPositions({
                    positions : []
                })
            })],
            appearance : new MaterialAppearance({
                materialSupport : MaterialAppearance.MaterialSupport.ALL
            }),
            compressVertices : false
        });

        var frameState = createFrameState(context);

        return pollToPromise(function() {
            if (frameState.afterRender.length > 0) {
                frameState.afterRender[0]();
                return true;
            }
            primitive.update(frameState);
            return false;
        }).then(function() {
            return primitive.readyPromise.then(function(arg) {
                expect(arg).toBe(primitive);
                expect(primitive.ready).toBe(true);
            });
        });
    });

    it('internally invalid synchronous geometry resolves promise and sets ready', function() {
        primitive = new Primitive({
            geometryInstances : [new GeometryInstance({
                geometry : PolygonGeometry.fromPositions({
                    positions : []
                })
            })],
            appearance : new MaterialAppearance({
                materialSupport : MaterialAppearance.MaterialSupport.ALL
            }),
            asynchronous : false,
            compressVertices : false
        });

        var frameState = createFrameState(context);

        return pollToPromise(function() {
            if (frameState.afterRender.length > 0) {
                frameState.afterRender[0]();
                return true;
            }
            primitive.update(frameState);
            return false;
        }).then(function() {
            return primitive.readyPromise.then(function(arg) {
                expect(arg).toBe(primitive);
                expect(primitive.ready).toBe(true);
            });
        });
    });

    it('shader validation', function() {
        primitive = new Primitive({
            geometryInstances : [rectangleInstance1, rectangleInstance2],
            appearance : new MaterialAppearance({
                materialSupport : MaterialAppearance.MaterialSupport.ALL
            }),
            asynchronous : false,
            compressVertices : false
        });

        var frameState = createFrameState(context);

        expect(function() {
            primitive.update(frameState);
        }).toThrowDeveloperError();
    });

    it('setting per instance attribute throws when value is undefined', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        var frameState = createFrameState(context);
        primitive.update(frameState);
        var attributes = primitive.getGeometryInstanceAttributes('rectangle1');

        expect(function() {
            attributes.color = undefined;
        }).toThrowDeveloperError();
    });

    it('can disable picking when asynchronous', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : true,
            allowPicking : false
        });

        var frameState = createFrameState(context);

        return pollToPromise(function() {
            primitive.update(frameState);
            if (frameState.afterRender.length > 0) {
                frameState.afterRender[0]();
            }
            return primitive.ready;
        }).then(function() {
            var attributes = primitive.getGeometryInstanceAttributes('rectangle1');
            expect(function() {
                attributes.color = undefined;
            }).toThrowDeveloperError();
        });
    });

    it('getGeometryInstanceAttributes throws without id', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.primitives.add(primitive);
        scene.renderForSpecs();

        expect(function() {
            primitive.getGeometryInstanceAttributes();
        }).toThrowDeveloperError();
    });

    it('getGeometryInstanceAttributes throws if update was not called', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        expect(function() {
            primitive.getGeometryInstanceAttributes('rectangle1');
        }).toThrowDeveloperError();
    });

    it('getGeometryInstanceAttributes returns undefined if id does not exist', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance(),
            asynchronous : false
        });

        scene.primitives.add(primitive);
        scene.renderForSpecs();

        expect(primitive.getGeometryInstanceAttributes('unknown')).not.toBeDefined();
    });

    it('isDestroyed', function() {
        primitive = new Primitive();
        expect(primitive.isDestroyed()).toEqual(false);
        primitive.destroy();
        expect(primitive.isDestroyed()).toEqual(true);
    });

    it('renders when using asynchronous pipeline', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance({
                flat : true
            })
        });

        var frameState = createFrameState(context);

        return pollToPromise(function() {
            primitive.update(frameState);
            if (frameState.afterRender.length > 0) {
                frameState.afterRender[0]();
            }
            return primitive.ready;
        }).then(function() {
            verifyPrimitiveRender(primitive, rectangle1);
        });
    });

    it('destroy before asynchonous pipeline is complete', function() {
        primitive = new Primitive({
            geometryInstances : rectangleInstance1,
            appearance : new PerInstanceColorAppearance()
        });

        var frameState = createFrameState(context);
        primitive.update(frameState);

        primitive.destroy();
        expect(primitive.isDestroyed()).toEqual(true);
    });
}, 'WebGL');
