package {
    import cmodule.FluidSolver.CLibInit;
    
    import com.adobe.viewsource.ViewSource;
    
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;
    import flash.text.TextField;
    import flash.utils.ByteArray;
    import flash.utils.getTimer;
    
    import net.hires.debug.Stats;
    
    [SWF(width="512", height="512", frameRate="120")]
    public class AlchemySmoke extends Sprite
    {
        private var _cLib : CLibInit;
        private var _cMethods : Object;
        
        private var _width : int = 100,
                    _height : int = 100;
        private var _bitmapData : BitmapData;
        private var _bitmap : Bitmap;
        private var _destPosition : int;
        
        private var _cRAM : ByteArray;
    
        private var _rect : Rectangle;
        
        private var _prevTime : int;
        
        private var _mouseDown : Boolean;
        
        private var _oldMouseX : Number = -1;
        private var _oldMouseY : Number = -1;
        
        private var _textField : TextField;
        
        public function AlchemySmoke()
        {
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            
            ViewSource.addMenuItem(this, "srcview/");
            
            initCLib();
            initView();
            
            _prevTime = getTimer();
            stage.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
            stage.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
            addEventListener(Event.ENTER_FRAME, handleEnterFrame);
        }
        
        private function initCLib() : void
        {
            var ns : Namespace = new Namespace( "cmodule.FluidSolver");
            _cLib = new CLibInit();
            
            // initialize library, returns exposed functions
            _cMethods = _cLib.init();
            
            // retrieve "RAM" memory for the FluidSolver lib
            _cRAM = (ns::gstate).ds;
            
            // initialize buffers used for the simulation. Returns address to the visual memory buffer
            _destPosition = _cMethods.initBuffers(_width, _height, 0);
            
            // sets properties for air and smoke behaviour
            _cMethods.setAdvectionProperties(150, 150, 150, 150);
        }
        
        /**
         * Create view assets
         */
        private function initView() : void
        {
            _bitmapData = new BitmapData(_width, _height, false, 0x000000);
            _rect = new Rectangle(0, 0, _width, _height);
            _bitmap = new Bitmap(_bitmapData);
            _bitmap.scaleY = _bitmap.scaleX = Math.min(stage.stageWidth/_bitmapData.width, stage.stageHeight/_bitmapData.height);
            _bitmap.smoothing = true;
            addChild(_bitmap);
            addChild(new Stats());
            
            _textField = new TextField();
            _textField.textColor = 0xffffff;
            _textField.multiline = true;
            _textField.text =     "Mouse down - add smoke\n"+
                                "Mouse move - move air";
            _textField.width = 400;
            _textField.height = _textField.textHeight+10;
            _textField.y = stage.stageHeight-_textField.textHeight-10;
            addChild(_textField);
        }
        
        private function handleMouseDown(event : MouseEvent) : void
        {
            _mouseDown = true;
        }
        
        private function handleMouseUp(event : MouseEvent) : void
        {
            _mouseDown = false;
        }
        
        /**
         * Calculates time between two frames
         */
        private function get timeStep() : Number
        {
            var dt : Number;
            var currentTime : int = getTimer();
            dt = (currentTime-_prevTime)/1000;
            _prevTime = currentTime;
            return dt;
        }
        
        private function handleEnterFrame(event : Event) : void
        {
            var dx : Number, dy : Number;
            if (_oldMouseX == -1) _oldMouseX = _bitmap.mouseX;
            if (_oldMouseY == -1) _oldMouseY = _bitmap.mouseY;
            
            // creates a movement force on the velocity field following the mouse movement
            // brute force approach, not the best way, but it doesn't really impact frameRate
            _cMethods.addForceLine(_oldMouseX, _oldMouseY, _bitmap.mouseX, _bitmap.mouseY, 0.02);
            _cMethods.addForceLine(_oldMouseX-1, _oldMouseY, _bitmap.mouseX-1, _bitmap.mouseY, 0.01);
            _cMethods.addForceLine(_oldMouseX+1, _oldMouseY, _bitmap.mouseX+1, _bitmap.mouseY, 0.01);
            _cMethods.addForceLine(_oldMouseX, _oldMouseY+1, _bitmap.mouseX, _bitmap.mouseY+1, 0.01);
            _cMethods.addForceLine(_oldMouseX+1, _oldMouseY+1, _bitmap.mouseX+1, _bitmap.mouseY+1, 0.01);
            _cMethods.addForceLine(_oldMouseX-1, _oldMouseY+1, _bitmap.mouseX-1, _bitmap.mouseY+1, 0.01);
            _cMethods.addForceLine(_oldMouseX, _oldMouseY-1, _bitmap.mouseX, _bitmap.mouseY-1, 0.01);
            _cMethods.addForceLine(_oldMouseX+1, _oldMouseY-1, _bitmap.mouseX+1, _bitmap.mouseY-1, 0.01);
            _cMethods.addForceLine(_oldMouseX-1, _oldMouseY-1, _bitmap.mouseX-1, _bitmap.mouseY-1, 0.01);
            
            // adds ink at mouse position if mouse is down
            if (_mouseDown) _cMethods.addInkLine(_oldMouseX, _oldMouseY, _bitmap.mouseX, _bitmap.mouseY, 2);
            
            _oldMouseX = _bitmap.mouseX;
            _oldMouseY = _bitmap.mouseY;
            
            // update the simulation
            _cMethods.updateSimulation(Math.min(timeStep, .025));
            
            // set bytearray position and draw visual memory buffer
            _cRAM.position = _destPosition;
            _bitmapData.setPixels(_rect, _cRAM);
        }
    }
}