package {
    import com.hurlant.eval.ByteLoader;
    import com.hurlant.eval.CompiledESC;
    import com.hurlant.eval.dump.ABCDump;
    import com.hurlant.jsobject.JSObject;
    import com.hurlant.util.Hex;
    
    import flash.display.Sprite;
    import flash.external.ExternalInterface;
    import flash.utils.ByteArray;
    import flash.utils.getDefinitionByName;
    import flash.utils.setTimeout;

    public class ScreamingDonkey extends Sprite
    {
        private var esc:CompiledESC = new CompiledESC;
        private var queue:Array = [];
        
        private static var instance:ScreamingDonkey; // XXX stop being goddamn lazy, henri.
        // linkage hack, to make a few things accessible from scripts
        private var jsobject:JSObject;
        private var hex:Hex;
        private var abcdump:ABCDump;
        
        public function ScreamingDonkey()
        {
            if (ExternalInterface.available) {
                ExternalInterface.marshallExceptions=true;
                ExternalInterface.addCallback("eval", eval);
                ExternalInterface.addCallback("enqueueScript", enqueueScript);
                ExternalInterface.addCallback("runQueue", runQueue);
                ExternalInterface.addCallback("trace", trace); // for debugging purposes.
                // notify the js side as soon as ESC is up.
                checkForEsc();
            }
            instance = this;
        }
        
        private function checkForEsc():void {
            try {
                var compile:Function = getDefinitionByName("ESC::compile") as Function;
            } catch(e:Error) {
                setTimeout(checkForEsc, 100);
                return;
            }
            // initialize a few things
            firstScript();
            // let the Js side know we're open for business
            ExternalInterface.call("HURLANT_donkey_escReady");
        }
        
        private function firstScript():void {
            // I don't want to copy every obscure window members by default.
            // it starts java on firefox, and has god knows how many other weird side effects.
            // so limit ourselves to the basics. 
            // programs can always go fish for stuff on window if they need to.
            var globals:Array = [ "document", "top", "parent", "self", "history", "navigator", "screen", "opener", "location", "frames",
                "alert", "confirm", "prompt", "XMLHttpRequest" ];
            var pre:String =  
                  "namespace DOM = 'com.hurlant.jsobject';\n" +
                  "use namespace DOM;\n" +
                  "ESC function eval_hook(){};\n" + // needed to allow later override by eval()..
                  "var window = JSObject.getWindow();\n" +
                  "var eval2 = ScreamingDonkey.eval2;\n";
            for each (var n:String in globals) {
                pre += "var "+n+" = window."+n+";\n";
            }
            enqueueScript(pre, 0, "scopeSetter"); // XXX we rely on 0 being unused (because there's at least <script src=donkey.js></script>)
        }
        
        /**
         * Plain "eval"-like method, who doesn't try to hard:
         * - no scopes, no callback. no worries
         *  
         * @param script
         * 
         */
        private function eval(script:String):void {
            try {
                var bytes:ByteArray = esc.compile(script);
                ByteLoader.loadBytes(bytes, true);
            } catch (e:Error) {
                trace("Error in eval: "+e);
                throw e;
            }
        }
        

        /**
         * More full-featured eval. Still not synchronous, but we make up
         * for it with a user-defined scope array and a callback.
         * Scopes are implemented with "with", because it's easy and I'm cheap.
         * 
         * @param script
         * @param scopes
         * @param callback
         * @return 
         * 
         */
        public static function eval2(script:String, scopes:Array = null, callback:Function = null):* {
            return instance.esc.evaluateInScopeArray(script, scopes, callback);
        }
        
        
        
        private function enqueueScript(script:String, index:int, name:String):void {
            try {
                // add flash.utils in scope, to get setTimeout and setInterval.
                script = "namespace _flash_utils = 'flash.utils';\n" +
                         "use namespace _flash_utils;\n" +
                         script;
                // compile
                var bytes:ByteArray = esc.compile(script, name);
                // don't load yet.
                queue[index] = bytes;
            } catch (e:Error) {
                trace("Error in eval: "+e);
                throw e;
            }
        }
        
        private function runQueue():void {
            var q:Array = [];
            for (var i:int=0;i<queue.length;i++) {
                if (queue[i] is ByteArray) {
                    q.push(queue[i]);
                }
            }
            queue =[]; // empty queue
            ByteLoader.loadBytes(q, true);
        }
    }
}