package {
    import AS3s.Terrain2;
    
    import away3d.animators.PathAnimator;
    import away3d.animators.data.Path;
    import away3d.cameras.*;
    import away3d.cameras.lenses.SphericalLens;
    import away3d.containers.*;
    import away3d.core.base.Mesh;
    import away3d.core.base.Object3D;
    import away3d.core.filter.FogFilter;
    import away3d.core.math.*;
    import away3d.core.render.*;
    import away3d.core.utils.*;
    import away3d.extrusions.*;
    import away3d.materials.*;
    import away3d.primitives.*;
    
    import caurina.transitions.Tweener;
    
    import com.adobe.viewsource.ViewSource;
    import com.as3dmod.ModifierStack;
    import com.as3dmod.modifiers.*;
    import com.as3dmod.plugins.away3d.LibraryAway3d;
    import com.as3dmod.util.ModConstant;
    
    import flash.display.*;
    import flash.events.*;
    import flash.filters.ColorMatrixFilter;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Rectangle;
    import flash.ui.Keyboard;
    import flash.utils.*;
    
    [SWF(backgroundColor="#000000", frameRate="30", quality="LOW", width="800", height="600")]
    
    public class TFO3D_Flash extends Sprite
    {
        [Embed(source="../assets/terrainTexture2.jpg")]
        public static var TerrainTexture:Class;
        
        [Embed(source="../assets/terrainHeightmap2.png")]
        public static var TerrainHeightmap:Class;
        
        [Embed(source="../assets/sky.jpg")]
        private var Sky:Class;
        
        [Embed(source="../assets/signature.swf", symbol="Signature")]
        public static var SignatureSwf:Class;
        
        [Embed(source="../assets/slides.swf", mimeType="application/octet-stream")]
        public static var Slides:Class;
        
        //elevation variables
        private var factorW:Number = 20;// width of resulting mesh elements
        private var factorH:Number = 20;// height of resulting mesh elements
        private var factorD:Number = 10;// scale factor for elevation (0-255)*factor        
        private var precisionX:Number = 15;// horizontal resolution in pixels of the mesh along the x axis of the bitmapData object
        private var precisionY:Number = 15;// vertical resolution in pixels of the mesh along the y axis of the bitmapData object
        
        protected var backCamera:Camera3D;
        protected var cameraObject:Object3D;
        protected var targetObject:Object3D;
        protected var backCameraContainer:ObjectContainer3D;
        protected var foreCamera:Camera3D;
        protected var backView:View3D;
        protected var foreView:View3D;
        
        //signature variables
        private var Signature:Sprite;
        private var SignatureBitmap:Bitmap;
        
        protected var prevCarousel:ObjectContainer3D;
        protected var prevSlideMaterial:MovieMaterial;
        protected var prevSlideFilter:ColorMatrixFilter;
        protected var prevSlide:Plane;
        
        protected var nextCarousel:ObjectContainer3D;
        protected var nextSlideMaterial:MovieMaterial;
        protected var nextSlideFilter:ColorMatrixFilter;
        protected var nextSlide:Plane;
        
        private var prevStack:ModifierStack;
        private var prevPaperMod:Bend;
        //private var prevWindMod:Perlin;
        
        private var nextStack:ModifierStack;
        private var nextPaperMod:Bend;
        //private var nextWindMod:Perlin;
        
        private var skyMaterial:BitmapMaterial;
        
        //material objects
        private var elevationBitmap:BitmapData;
        private var elevationReader:ElevationReader;
        private var colorBitmap:BitmapData;
        private var terrainMaterial:BitmapMaterial;
        
        protected var background:Scene3D;
        protected var foreground:Scene3D;
        
        private var skinExtrude:Mesh;
        private var path:Path;
        private var targetAnimator:PathAnimator;
        private var cameraAnimator:PathAnimator;
        public var cameraPosition:Number = 0;
        public var targetPosition:Number = 0;
        
        private var skysphere:Sphere;
        private var zero:Number3D = new Number3D();
        
        protected var slidesArray:Array = new Array();
        protected var slidesLoader:Loader = new Loader();
        private var renderFlag:Boolean = true;
        private var active:Boolean = true;
        private var currentSlideIndex:int = 0;
        
        public function TFO3D_Flash()
        {
            super();
            
            //take care of stage
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.fullScreenSourceRect = new Rectangle(0,0,800,600);
            
            //stage.showDefaultContextMenu = false;
            stage.stageFocusRect = false;
            ViewSource.addMenuItem(this, "srcview/index.html")
            
            //get slides
            slidesLoader.contentLoaderInfo.addEventListener(Event.INIT, onSlidesReady);
            slidesLoader.loadBytes(new Slides());
        }
                
        private function onSlidesReady(event:Event):void
        {
            var index:Number = 1;
            var symbol:Class;
            var symbolInstance:MovieClip;
            try {
                while (symbol = (slidesLoader.contentLoaderInfo.applicationDomain.getDefinition("Slide" + String(index++)) as Class))
                {
                    symbolInstance = new symbol() as MovieClip;
                    symbolInstance.stop();
                    slidesArray.push(symbolInstance);
                }
            } catch (error:Error) {
                setupScene();
            }
        }
        
        public function setupScene():void
        {
            //setup camera
            backCamera = new Camera3D({zoom:7, focus:200, z:-1000, lens:new SphericalLens()});
            foreCamera = new Camera3D({zoom:3, focus:200, z:-1000});
            
            elevationBitmap = Cast.bitmap(TerrainHeightmap);
            colorBitmap = Cast.bitmap(TerrainTexture);
            var fogBitmap:BitmapData = new BitmapData(colorBitmap.width, colorBitmap.height);
            var fogShape:Shape = new Shape();
            var fogMatrix:Matrix = new Matrix();
            fogMatrix.createGradientBox(2048, 2048, 0, 0, 0);
            var colArray:Array = [0xB6D1FE, 0xB6D1FE, 0xB6D1FE];
            var alphaArray:Array = [0, 0, 0.3];
            var pointArray:Array = [0, 128, 255];
            fogShape.graphics.beginGradientFill(GradientType.RADIAL, colArray, alphaArray, pointArray, fogMatrix);
            fogShape.graphics.drawRect(0, 0, 2048, 2048);
            colorBitmap.draw(fogShape);
            elevationReader = new ElevationReader();
            
            //generate a heightmap gradient at the correct mesh resolution
            var levelsBitmap:BitmapData = new BitmapData(colorBitmap.width, colorBitmap.height);
            levelsBitmap.draw(elevationBitmap, new Matrix(4, 0, 0, 4), new ColorTransform(3, 3, 3, 1, 20, 20, 20, 0));
            elevationReader.traceLevels(levelsBitmap, "av", precisionX, precisionY, factorW, factorH, factorD);
            
            //merge the heightmap gradient with the color texture
            elevationReader.applyHeightGradient(colorBitmap, 0xFFFFFFFF, true, "normal");
            
            //create elevation material with resulting bitmap
            terrainMaterial = new BitmapMaterial(colorBitmap);
            skyMaterial = new BitmapMaterial( Cast.bitmap(Sky) );
            
            //setup background
            background = new Scene3D();
            
            //create background view and add to displaylist
            var fogMaterial:ColorMaterial = new ColorMaterial(0x66FFFFFF, {alpha:0.5});
            var fog:FogFilter = new FogFilter({material:fogMaterial, minZ:1000, maxZ:6000});
            backView = new View3D({camera:backCamera, scene:background});
            backView.clipping.minZ = 150;
            addChild(backView);
            
                                    
            //add signature
            Signature = Sprite(new SignatureSwf());
            SignatureBitmap = new Bitmap(new BitmapData(Signature.width, Signature.height, true, 0));
            stage.quality = StageQuality.HIGH;
            SignatureBitmap.bitmapData.draw(Signature);
            stage.quality = StageQuality.LOW;
            addChild(SignatureBitmap);
            
            //generate the two-dimensional array of points for the extrusion
            var elevate:Elevation = new Elevation();
            var points:Array = elevate.generate(elevationBitmap, "av", precisionX, precisionY, factorW, factorH, factorD);
            
            //create the skinextrude object from the points
            //skinExtrude = new SkinExtrude(points, {material:terrainMaterial, recenter:true, closepath:false, coverall:true, subdivision:1, bothsides:false, flip:false});
            skinExtrude = (new Terrain2({material:terrainMaterial})).meshes[0];
            skinExtrude.material = terrainMaterial;
            //reposition skinextrude object
            //skinExtrude.rotationX = 90;
            skinExtrude.moveTo(0, -1500, 0);
            
            //optional, but helps with sorting when objects are placed on the surface of the extrusion
            skinExtrude.pushback = true;
            
            //create path
            var aPath:Array = [new Number3D(3104.8101,342.4314,-5934.6686),new Number3D(4980.6329,37.0196,-5816.8588),new Number3D(6597.7215,46.2745,-5551.7867),new Number3D(6597.7215,46.2745,-5551.7867),
            new Number3D(8279.4937,157.3333,-5139.4524),new Number3D(9702.5316,397.9608,-4756.5706),new Number3D(9702.5316,397.9608,-4756.5706),
            new Number3D(10672.7848,869.9608,-4196.9741),new Number3D(10996.2025,1702.9020,-3549.0202),new Number3D(10996.2025,1702.9020,-3549.0202),
            new Number3D(10737.4684,2156.3922,-2959.9712),new Number3D(10414.0506,2193.4118,-2429.8271),new Number3D(10414.0506,2193.4118,-2429.8271),
            new Number3D(10284.6835,1665.8824,-1752.4207),new Number3D(10220.0000,981.0196,-1016.1095),new Number3D(10220.0000,981.0196,-1016.1095),
            new Number3D(10155.3165,768.1569,-279.7983),new Number3D(9379.1139,1027.2941,368.1556),new Number3D(9379.1139,1027.2941,368.1556),
            new Number3D(8214.8101,1416.0000,839.3948),new Number3D(6985.8228,1499.2941,1192.8242),new Number3D(6985.8228,1499.2941,1192.8242),
            new Number3D(5886.2025,1656.6275,1664.0634),new Number3D(5304.0506,1999.0588,2370.9222),new Number3D(5304.0506,1999.0588,2370.9222),
            new Number3D(5045.3165,1610.3529,3077.7810),new Number3D(4269.1139,1369.7255,3755.1873),new Number3D(4269.1139,1369.7255,3755.1873),
            new Number3D(3104.8101,1018.0392,4167.5216),new Number3D(1746.4557,351.6863,4579.8559),new Number3D(1746.4557,351.6863,4579.8559),
            new Number3D(582.1519,27.7647,5110.0000),new Number3D(-840.8861,9.2549,5581.2392),new Number3D(-840.8861,9.2549,5581.2392),
            new Number3D(-2263.9241,175.8431,5905.2161),new Number3D(-4010.3797,777.4118,5934.6686),new Number3D(-4010.3797,777.4118,5934.6686),
            new Number3D(-5562.7848,1749.1765,5816.8588),new Number3D(-6727.0886,2063.8431,5080.5476),new Number3D(-6727.0886,2063.8431,5080.5476),
            new Number3D(-7050.5063,897.7255,4314.7839),new Number3D(-7244.5570,138.8235,3549.0202),new Number3D(-7244.5570,138.8235,3549.0202),
            new Number3D(-6921.1392,37.0196,2783.2565),new Number3D(-6144.9367,46.2745,2017.4928),new Number3D(-6144.9367,46.2745,2017.4928),
            new Number3D(-5433.4177,166.5882,1340.0865),new Number3D(-4592.5316,175.8431,662.6801),new Number3D(-4592.5316,175.8431,662.6801),
            new Number3D(-3492.9114,342.4314,44.1787),new Number3D(-2846.0759,388.7059,-633.2277),new Number3D(-2846.0759,388.7059,-633.2277),
            new Number3D(-2587.3418,610.8235,-1340.0865),new Number3D(-2587.3418,990.2745,-2046.9452),new Number3D(-2587.3418,990.2745,-2046.9452),
            new Number3D(-3363.5443,1508.5490,-2812.7089),new Number3D(-4398.4810,1536.3137,-3342.8530),new Number3D(-4398.4810,1536.3137,-3342.8530),
            new Number3D(-5756.8354,1147.6078,-3696.2824),new Number3D(-7244.5570,916.2353,-3872.9971),new Number3D(-7244.5570,916.2353,-3872.9971),
            new Number3D(-8861.6456,934.7451,-3755.1873),new Number3D(-10155.3165,1184.6275,-3283.9481),new Number3D(-10155.3165,1184.6275,-3283.9481),
            new Number3D(-10866.8354,1776.9412,-2606.5418),new Number3D(-10996.2025,1749.1765,-1811.3256),new Number3D(-10996.2025,1749.1765,-1811.3256),
            new Number3D(-10672.7848,1166.1176,-1104.4669),new Number3D(-10025.9494,666.3529,-456.5130),new Number3D(-10025.9494,666.3529,-456.5130),
            new Number3D(-9379.1139,175.8431,368.1556),new Number3D(-8861.6456,0.0000,1104.4669)];

            //cameraObject = new Object3D({z:-1000, rotationY:360*(currentSlideIndex-5)/(slidesArray.length - 1), y:360*Math.sin(2*Math.PI*(currentSlideIndex-5)/(slidesArray.length - 1)) + 50});
            cameraObject = new Object3D();
            targetObject = new Object3D();
            backCameraContainer = new ObjectContainer3D();
            backCameraContainer.scaleY = 1;
            backCameraContainer.scaleX = -0.3;
            backCameraContainer.scaleZ = 0.74;
            backCameraContainer.rotationY = 180;
            backCameraContainer.y = -2000;
            backCameraContainer.z = 420;
            backCameraContainer.addChild(cameraObject);
            backCameraContainer.addChild(targetObject);
            background.addChild(backCameraContainer);
            
            targetPosition = (currentSlideIndex+1)/(slidesArray.length);
            path = new Path(aPath);
            path.smoothPath();
            
            cameraAnimator = new PathAnimator(path, cameraObject);
            targetAnimator = new PathAnimator(path, targetObject);
            
            background.addChild(skinExtrude);
            
            skysphere = new Sphere({material:skyMaterial, radius:50000, rotationX:180, segmentsW:10, segmentsH:12});
            skysphere.scale(-1);
            background.addChild( skysphere );
            
            
            //setup foreground
            prevSlideMaterial = new MovieMaterial(slidesArray[0], {precision:2.5});
            prevSlideFilter = new ColorMatrixFilter();
            prevSlide = new Plane({ownCanvas:true, filters:[prevSlideFilter], material:prevSlideMaterial, z:-9600, width:800, height:600, segmentsW:10, segmentsH:10, bothsides:true, yUp:false});
            prevCarousel = new ObjectContainer3D({z:9000}, prevSlide);
            
            nextSlideMaterial = new MovieMaterial(slidesArray[1], {precision:2.5});
            nextSlideFilter = new ColorMatrixFilter();
            nextSlide = new Plane({ownCanvas:true, filters:[nextSlideFilter], material:nextSlideMaterial, width:800, height:600, segmentsW:10, segmentsH:10, bothsides:true, yUp:false});
            nextCarousel = new ObjectContainer3D({z:9000}, nextSlide);
            
            foreground = new Scene3D(prevCarousel, nextCarousel);
            
            //create foreground view and add to displaylist
            foreView = new View3D({camera:foreCamera, scene:foreground, renderer:new BasicRenderer()});
            addChild(foreView);
            
            
            prevStack = new ModifierStack(new LibraryAway3d(), prevSlide);
            //prevWindMod = new Perlin(.1, 25, 25, 95840);
            prevPaperMod = new Bend(0, .2, .4);
            prevPaperMod.constraint = ModConstant.LEFT;
            prevStack.addModifier(prevPaperMod);
            //prevStack.addModifier(prevWindMod);
            
            nextStack = new ModifierStack(new LibraryAway3d(), nextSlide);
            //nextWindMod = new Perlin(.1, 25, 25, 95840);
            nextPaperMod = new Bend(0, .2, .4);
            nextPaperMod.constraint = ModConstant.LEFT;
            nextStack.addModifier(nextPaperMod);
            //nextStack.addModifier(nextWindMod);
            
            stage.addEventListener(MouseEvent.MOUSE_OVER, onActivate);
            stage.addEventListener(Event.MOUSE_LEAVE, onDeactivate);
            stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            stage.addEventListener(Event.RESIZE, onResize);
            onResize(null);
        }
        
        private function onActivate(event:Event):void
        {
            active = true;
        }
        
        private function onDeactivate(event:Event):void
        {
            active = false;
        }
        
        private function onKeyDown(event:KeyboardEvent):void
        {
            if (!active)
                return;
            
            switch (event.keyCode)
            {
                case Keyboard.ENTER:
                    //new AS3Exporter(skinExtrude, "Terrain2", "AS3s");
                    if (stage.displayState == StageDisplayState.FULL_SCREEN)
                        stage.displayState =StageDisplayState.NORMAL;
                    else 
                        stage.displayState =StageDisplayState.FULL_SCREEN;
                    break;
                case Keyboard.LEFT:
                    onPrevSlide();
                    break;
                case Keyboard.RIGHT:
                    onNextSlide();
                    break;
                case Keyboard.DOWN:
                    (slidesArray[currentSlideIndex] as MovieClip).nextFrame();
                    break;
                case Keyboard.UP:
                    (slidesArray[currentSlideIndex] as MovieClip).prevFrame();
                    break;
                default:
                    if (event.keyCode > 48 && event.keyCode < 59) {
                        clearAll();
                        currentSlideIndex = event.keyCode - 49;
                        prevSlideMaterial.movie = slidesArray[currentSlideIndex];
                        nextSlideMaterial.movie = slidesArray[currentSlideIndex];
                        prevSlideMaterial.interactive = true;
                        nextSlideMaterial.interactive = true;
                    }
            }
        }
        
        public function clearAll():void
        {
            //clear any unfinished tweens
            Tweener.removeAllTweens();
        }
        
        public function onNextSlide():void
        {
            if (currentSlideIndex >= slidesArray.length - 1)
                return;
            
            clearAll();
            currentSlideIndex++;
            renderFlag = true;
            
            prevSlideMaterial.interactive = false;
            nextSlideMaterial.interactive = false;
            
            //setup prev slide
            prevSlideMaterial.movie = slidesArray[currentSlideIndex-1]
            prevSlide.z = -9600;
            prevSlide.rotationX = 0;
            prevSlide.rotationY = 0;
            prevCarousel.rotationY = 0;
            prevPaperMod.force = 0;
            
            //setup next slide
            nextSlideMaterial.movie = slidesArray[currentSlideIndex]
            nextSlide.z = -2000;
            nextSlide.rotationX = 0;
            nextSlide.rotationY = 0;
            nextCarousel.rotationY = -45;
            nextPaperMod.force = 1;
            
            //trigger animation
            Tweener.addTween(prevCarousel, {rotationY:45, time:1.5, transition:"easeinquad"});
            Tweener.addTween(prevSlide, {z:-2000, rotationX:360, rotationY:360, time:1.5, transition:"easeinquad"});
            Tweener.addTween(nextCarousel, {rotationY:0, time:1.5, delay:1, transition:"easeoutquad"});
            Tweener.addTween(nextSlide, {z:-9600, rotationX:360, rotationY:360, time:1.5, delay:1, transition:"easeoutquad", onComplete:setTimeout, onCompleteParams:[disableRenderNext, 200]});
            //Tweener.addTween(cameraObject, {rotationY:360*(currentSlideIndex-5)/(slidesArray.length - 1), y:360*Math.sin(2*Math.PI*(currentSlideIndex-5)/(slidesArray.length - 1)) + 50, time:2.5, transition:"linear"});
            Tweener.addTween(this, {cameraPosition:currentSlideIndex/slidesArray.length, time:2.5, transition:"linear"});
            Tweener.addTween(this, {targetPosition:(currentSlideIndex+1)/slidesArray.length, time:2.5, transition:"linear"});
            Tweener.addTween(prevPaperMod, {force:1, time:1.5, transition:"easeoutquad"});
            Tweener.addTween(nextPaperMod, {force:0, time:1.4, delay:1, transition:"easeinquad"});
        }
        
        public function onPrevSlide():void
        {
            if (currentSlideIndex <= 0)
                return;
            
            clearAll();
            currentSlideIndex--;
            renderFlag = true;
            
            prevSlideMaterial.interactive = false;
            nextSlideMaterial.interactive = false;
            
            //setup prev slide
            prevSlideMaterial.movie = slidesArray[currentSlideIndex]
            prevSlide.z = -2000;
            prevSlide.rotationX = 0;
            prevSlide.rotationY = 0;
            prevCarousel.rotationY = 45;
            prevPaperMod.force = 1;
            
            //setup next slide
            nextSlideMaterial.movie = slidesArray[currentSlideIndex+1]
            nextSlide.z = -9600;
            nextSlide.rotationX = 0;
            nextSlide.rotationY = 0;
            nextCarousel.rotationY = 0;
            nextPaperMod.force = 0;
            
            //trigger animation
            Tweener.addTween(nextCarousel, {rotationY:-45, time:1.5, transition:"easeinquad"});
            Tweener.addTween(nextSlide, {z:-2000, rotationX:360, rotationY:360, time:1.5, transition:"easeinquad"});
            Tweener.addTween(prevCarousel, {rotationY:0, time:1.5, delay:1, transition:"easeoutquad"});
            Tweener.addTween(prevSlide, {z:-9600, rotationX:360, rotationY:360, time:1.5, delay:1, transition:"easeoutquad", onComplete:setTimeout, onCompleteParams:[disableRenderPrev, 200]});
            Tweener.addTween(this, {cameraPosition:currentSlideIndex/slidesArray.length, time:2.5, transition:"linear"});
            Tweener.addTween(this, {targetPosition:(currentSlideIndex+1)/slidesArray.length, time:2.5, transition:"linear"});
            Tweener.addTween(nextPaperMod, {force:1, time:1.5, transition:"easeoutquad"});
            Tweener.addTween(prevPaperMod, {force:0, time:1.5, delay:1, transition:"easeinquad"});
        }
        
        public function disableRenderPrev():void
        {
            renderFlag = false;
            prevSlideMaterial.interactive = true;
            nextSlideMaterial.interactive = false;
            prevStack.mesh.resetGeometry();
            nextStack.mesh.resetGeometry();
            
            foreView.render();
        }
        
        public function disableRenderNext():void
        {
            renderFlag = false;
            prevSlideMaterial.interactive = false;
            nextSlideMaterial.interactive = true;
            prevStack.mesh.resetGeometry();
            nextStack.mesh.resetGeometry();
            
            foreView.render();
        }
        
        internal var tintR:int = 255;
        internal var tintG:int = 255;
        internal var tintB:int = 255;
        
        public function onEnterFrame(event:Event):void
        {
            if (!active)
                return;
            
            //update prev tint
            var prevFraction:Number = (-2000 - prevSlide.z)/7600;
            
            prevSlideFilter.matrix = [1, 0, 0, 0, tintR*(1-prevFraction),
                                        0, 1, 0, 0, tintG*(1-prevFraction),
                                        0, 0, 1, 0, tintB*(1-prevFraction),
                                        0, 0, 0, prevFraction, 0]
                                        
            //update next tint
            var nextFraction:Number = (-2000 - nextSlide.z)/7600;
            nextSlideFilter.matrix = [1, 0, 0, 0, tintR*(1-nextFraction),
                                        0, 1, 0, 0, tintG*(1-nextFraction),
                                        0, 0, 1, 0, tintB*(1-nextFraction),
                                        0, 0, 0, nextFraction, 0]
            foreView.render();
            
            if (!renderFlag && currentSlideIndex)
                return;
            
            cameraAnimator.update(cameraPosition);
            targetAnimator.update(targetPosition);
            backCamera.position = cameraObject.scenePosition;
            backCamera.lookAt(targetObject.scenePosition);
            
            //move skybox
            skysphere.rotationY += 0.05;
            
            //updat modifiers
            prevStack.apply();
            nextStack.apply();
            
            //update views
            backView.render();
        }
        
                
        private function onResize(event:Event):void 
        {
            foreView.x = backView.x = stage.stageWidth / 2;
            foreView.y = backView.y = stage.stageHeight / 2;
            foreView.scaleX = stage.stageWidth/800;
            foreView.scaleY = stage.stageHeight/600;
            
            if (SignatureBitmap)
                SignatureBitmap.y = stage.stageHeight - Signature.height;
        }
    }
}