In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

flash on 2011-2-18

Get Adobe Flash player
by wh0 12 Mar 2011

    Talk

    makc3d at 03 Mar 2011 05:18
    development at Driver/complete() at MethodInfo-29() at MethodInfo-11()
    wh0 at 03 Mar 2011 06:55
    Here's a silly story about that. To prevent this from re-running itself while developing, I turned off the auto-compilation. But that also turns off the auto save. One time, the Flash player ran out of memory, so I had to reload the page. So now I have if (DONT_RUN) throw 'development';
    makc3d at 03 Mar 2011 12:35
    " turned off the auto-compilation" - wow, how does one do it?
    wh0 at 03 Mar 2011 20:57
    There's a checkbox labeled "Auto" between the movie and the compilation log, next to the "Stop" and "Reload" buttons.

    Tags

    Embed
/**
 * Copyright wh0 ( http://wonderfl.net/user/wh0 )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/h0Ma
 */

// TODO: %%% complete other TODOs
package {
    import net.hires.debug.Stats;
    import flash.display.Sprite;
    public class FlashTest extends Sprite {
        
        public function FlashTest() {
            addChild(new Stats()).x = stage.stageWidth - 70;
            addChild(new Driver());
        }
        
    }
}

import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.net.*;
import flash.system.*;
import flash.utils.*;
import com.adobe.images.*;
import com.bit101.components.*;
import nochump.util.zip.*;

internal class Console extends Sprite {
    
    public static var instance:Console;
    
    private var flow:Number = 0;
    
    public function Console() {
        instance = this;
        addEventListener(Event.ADDED_TO_STAGE, init);
    }
    
    protected function init(e:Event):void {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        root.loaderInfo.uncaughtErrorEvents.addEventListener(
            UncaughtErrorEvent.UNCAUGHT_ERROR,
            function(e:UncaughtErrorEvent):void { error(e.error); }
        );
    }
    
    private function advance(dy:Number):void {
        flow += dy;
        if (y > stage.stageHeight - flow) y = stage.stageHeight - flow;
    }
    
    public function trace(message:*):Label {
        var l:Label = new Label(this, 0, flow, String(message));
        advance(Math.max(18, l.textField.height + 1));
        return l;
    }
    
    public function error(message:*):void {
        trace(message).textField.textColor = 0xc00040;
    }
    
    protected function progress(name:String, eventDispatcher:EventDispatcher, scale:int, callback:Function=null):ProgressBar {
        var l:Label = new Label(this, 0, flow, name);
        var pb:ProgressBar = new ProgressBar(this, 93, flow + 5);
        pb.width = 6;
        pb.height = 9;
        pb.visible = false;
        advance(18);
        eventDispatcher.addEventListener(ProgressEvent.PROGRESS, function(e:ProgressEvent):void {
            pb.maximum = e.bytesTotal;
            pb.visible = true;
            if (e.bytesTotal > scale * 4) pb.width = int((e.bytesTotal / scale) + 2);
            eventDispatcher.removeEventListener(ProgressEvent.PROGRESS, arguments.callee);
        });
        eventDispatcher.addEventListener(ProgressEvent.PROGRESS, function(e:ProgressEvent):void {
            pb.value = e.bytesLoaded;
        });
        eventDispatcher.addEventListener(Event.COMPLETE, function(e:Event):void {
            pb.value = pb.maximum;
            pb.visible = true;
            if (callback != null) callback(e);
        });
        return pb;
    }
    
}

internal class ResourceLoader extends Console {
    
    private static const BINS:Array = [
        'recipe', 'ingredient', 'restaurant', 'lang_en', 'recipe_rewards',
        'avatar', 'front', 'perk', 'mystery_box'
    ];
    private static const SWFS:Array = [
        'game_asset', 'ingredient_asset', 'indoor_asset', 'indoor_asset2',
        'indoor_asset3', 'indoor_asset4', 'indoor_asset5', 'indoor_asset6',
        'indoor_asset7', 'IGA_asset', 'collectibles', 'avatar_asset',
        'perk_asset', 'recipe_asset'
    ];
    private static const BASE:String = 'http://static-cdn.playfish.com/game/cooking/swf/';
    private static const HERE:LoaderContext = new LoaderContext(
        true,
        ApplicationDomain.currentDomain,
        SecurityDomain.currentDomain
    );
    public static var instance:ResourceLoader;
    
    private var path:String;
    private var remain:int = BINS.length + SWFS.length;
    protected var xmls:Object = {};
    
    public function ResourceLoader() {
        super();
        instance = this;
    }
    
    override protected function init(e:Event):void {
        super.init(e);
        trace('version:');
        var it:InputText = new InputText(this, 40, 0);
        it.height = 18;
        var done:Boolean = false;
        var pb:PushButton = new PushButton(this, 143, 0, 'OK', function(e:MouseEvent):void {
            if (done) return;
            it.enabled = false;
            pb.enabled = false;
            path = BASE + it.text + '/';
            loadResources();
            done = true;
        });
        pb.width = 30;
        pb.height = 18;
    }
    
    private function loadResources():void {
        var v:String;
        for each (v in BINS) {
            loadBIN(v);
        }
        if (Driver.SKIP_IMAGES) {
            remain -= SWFS.length;
        } else {
            for each (v in SWFS) {
                loadSWF(v);
            }
        }
    }
    
    private function loadBIN(name:String):void {
        var ur:URLRequest = new URLRequest(path + name + '.bin');
        var ul:URLLoader = new URLLoader(ur);
        ul.dataFormat = URLLoaderDataFormat.BINARY;
        progress(name, ul, 512, function(e:Event):void {
            ul.data.uncompress();
            xmls[name] = new XML(ul.data);
            if (--remain <= 0) complete();
        });
    }
    
    public function makeLoader(name:String):Loader {
        var ur:URLRequest = new URLRequest(path + name + '.swf');
        var l:Loader = new Loader();
        l.load(ur, HERE);
        return l;
    }
    
    private function loadSWF(name:String):void {
        var l:Loader = makeLoader(name);
        progress(name, l.contentLoaderInfo, 16384, function(e:Event):void {
            if (--remain <= 0) complete();
        });
    }
    
    protected function complete():void {
        trace('all resources loaded');
    }
    
}

internal class Driver extends ResourceLoader {
    
    public static const SKIP_IMAGES:Boolean = false;
    public static const SKIP_PAGES:Boolean = false;
    private static const YIELD_INTERVAL:int = 20;
    
    private var xmlTasks:Array = [];
    private var pageTasks:Array = [];
    private var driving:Boolean = true;
    private var countdown:Label;
    private var summary:Label;
    
    private function makePage(database:String, category:String, group:String, taskClass:Class):void {
        var pageTask:PageTask = new PageTask(database, group);
        PageTask.addNavigation(category, pageTask);
        var items:XMLList = xmls[database].group.(@name == group).item;
        for each (var item:XML in items) {
            var xmlTask:XMLTask = new taskClass(item, pageTask);
            xmlTask.enqueueDependents();
            xmlTasks.push(xmlTask);
        }
        pageTasks.push(pageTask);
    }
    
    private function prepareAll():void {
        if (SKIP_IMAGES) trace('skipping images');
        if (SKIP_PAGES) trace('skipping pages');
        
        for each (var text:XML in xmls['lang_en'].content.(@lang == 'en').text) {
            new LinkTextTask(text).enqueue();
        }
        
        for each (var reward:XML in xmls['recipe_rewards'].reward) {
            new LinkRecipeRewardTask(reward).enqueue();
        }
        
        for each (var item:XML in xmls['perk']..item) {
            new XMLNilTask(item).enqueueDependents();
        }
        
        new RenderRatingTask().enqueue();
        
        makePage('recipe', 'Recipe', 'Starter', XMLRecipeTask);
        makePage('recipe', 'Recipe', 'Main', XMLRecipeTask);
        makePage('recipe', 'Recipe', 'Dessert', XMLRecipeTask);
        makePage('recipe', 'Recipe', 'Drink', XMLRecipeTask);
        makePage('ingredient', 'Ingredient', 'Ingredient', XMLIngredientTask);
        makePage('restaurant', 'Restaurant', 'Wall Decoration', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Door', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Decoration', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Table', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Chair', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Functional', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Floor Tile', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Wallpaper', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Kitchen Appliance', XMLRestaurantTask);
        makePage('restaurant', 'Restaurant', 'Garden', XMLRestaurantTask);
        makePage('avatar', 'Avatar', 'Pants', XMLAvatarTask);
        makePage('avatar', 'Avatar', 'Skirt', XMLAvatarTask);
        makePage('avatar', 'Avatar', 'Shirt', XMLAvatarTask);
        makePage('avatar', 'Avatar', 'Hat', XMLAvatarTask);
        makePage('avatar', 'Avatar', 'Hair', XMLAvatarTask);
        makePage('avatar', 'Avatar', 'Eyes', XMLTextureTask);
        makePage('avatar', 'Avatar', 'Mouth', XMLTextureTask);
        makePage('avatar', 'Avatar', 'Eyebrows', XMLTextureTask);
        makePage('avatar', 'Avatar', 'Miscellaneous', XMLTextureTask);
        makePage('avatar', 'Avatar', 'Facial Hair', XMLTextureTask);
        makePage('front', 'Front', 'Window', XMLFrontTask);
        makePage('front', 'Front', 'Door', XMLFrontTask);
        makePage('front', 'Front', 'Roof', XMLFrontLargeTask);
        makePage('front', 'Front', 'Wall Decoration', XMLFrontTask);
        makePage('front', 'Front', 'Street Decoration', XMLFrontTask);
        makePage('front', 'Front', 'Functional', XMLFrontTask);
        makePage('front', 'Front', 'Tile', XMLFrontTask);
        makePage('front', 'Front', 'Body', XMLFrontLargeTask);
        makePage('front', 'Front', 'Banner', XMLFrontTask);
        makePage('mystery_box', 'MysteryBox', 'MysteryBox', XMLMysteryBoxTask);
        
        for each (var xmlTask:XMLTask in xmlTasks) {
            xmlTask.enqueue();
        }
        
        for each (var pageTask:PageTask in pageTasks) {
            pageTask.enqueue();
        }
    }
    
    private function save():void {
        var ba:ByteArray = Task.zip();
        stage.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void {
            new FileReference().save(ba, 'db.zip');
        });
    }
    
    private function frame(e:Event):void {
        if (Task.remaining) {
            if (driving) {
                var w:int = getTimer();
                do {
                    var t:Task = Task.dequeue();
                    if (!t) {
                        driving = false;
                        break;
                    }
                    var s:String = t.summary;
                    summary.text = s;
                    try {
                        t.execute();
                    } catch (e:*) {
                        error(e + ' while ' + s);
                    }
                } while (getTimer() - w < YIELD_INTERVAL);
            }
            if (!driving) {
                summary.text = 'joining detached tasks';
            }
        }
        if (Task.remaining) {
            countdown.text = Task.remaining + ' tasks remaining';
        } else {
            removeEventListener(Event.ENTER_FRAME, frame);
            countdown.text = 'done';
            summary.text = 'click to save';
            save();
        }
    }
    
    override protected function complete():void {
        super.complete();
        prepareAll();
        countdown = trace(Task.remaining + ' tasks remaining');
        summary = trace('');
        addEventListener(Event.ENTER_FRAME, frame);
    }
    
}

internal class Task {
    
    private static const archive:ZipOutput = new ZipOutput();
    private static const queue:Array = [];
    private static var index:int = 0;
    private static var detached:int = 0;
    
    public static function finish():ByteArray {
        archive.finish();
        return archive.byteArray;
    }
    
    public static function dequeue():Task {
        if (queue[index]) {
            return queue[index++];
        } else {
            return null;
        }
    }
    
    public static function get remaining():int {
        return queue.length - index + detached;
    }
    
    public static function zip():ByteArray {
        archive.finish();
        return archive.byteArray;
    }
    
    protected function trace(message:*):Label {
        return Console.instance.trace(message);
    }
    
    protected function error(message:*):void {
        Console.instance.error(message);
    }
    
    protected function write(name:String, data:ByteArray):void {
        archive.putNextEntry(new ZipEntry(name));
        archive.write(data);
        archive.closeEntry();
    }
    
    public function get summary():String { return getQualifiedClassName(this); }
    
    public function execute():void {}
    
    public function enqueue():void {
        queue.push(this);
    }
    
    protected function fork():void {
        detached++;
    }
    
    protected function join():void {
        detached--;
    }
    
}

internal class RenderTask extends Task {
    
    private static const nameSet:Object = {}
    
    protected var name:String;
    
    public function RenderTask(name:String) {
        this.name = name;
    }
    
    protected function render(directory:String, mc:MovieClip, scale:Number):void {
        var n:String = directory + '/' + name + '.png';
        var r:Rectangle = mc.getBounds(null);
        var left:Number = Math.floor(r.left * scale);
        var top:Number = Math.floor(r.top * scale);
        var bd:BitmapData = new BitmapData(
            Math.ceil(r.right * scale) - left,
            Math.ceil(r.bottom * scale) - top,
            true, 0x00000000
        );
        bd.draw(mc, new Matrix(scale, 0, 0, scale, -left, -top));
        write(n, PNGEncoder.encode(bd));
        bd.dispose();
    }
    
    protected function hide(mc:MovieClip, variable:String, stop:Boolean=false):void {
        if (mc[variable]) {
            mc[variable].visible = false;
            if (stop) mc[variable].stop();
        }
    }
    
    protected function prepare(mc:MovieClip):void {
        hide(mc, 'mc_rect');
        hide(mc, 'mc_bound');
        hide(mc, 'mc_mask');
    }
    
    protected function get imageScale():Number { return 1.0; }
    
    protected function get iconScale():Number { return 0.5; }
    
    override public function get summary():String { return 'rendering ' + name; }
    
    private function clip():MovieClip {
        var mc:MovieClip = new (ApplicationDomain.currentDomain.getDefinition(name) as Class)();
        prepare(mc);
        return mc;
    }
    
    override public function execute():void {
        var mc:MovieClip = clip();
        if (!isNaN(imageScale)) render('images', mc, imageScale);
        if (!isNaN(iconScale)) render('icons', mc, iconScale);
    }
    
    override public function enqueue():void {
        if (Driver.SKIP_IMAGES || name == 'null') return;
        if (!(name in nameSet)) super.enqueue();
        nameSet[name] = true;
    }
    
    public function debug():MovieClip {
        var s:Stage = Console.instance.stage;
        var mc:MovieClip = clip();
        trace(describeType(mc)..variable.toXMLString());
        mc.x = s.stageWidth >> 1;
        mc.y = (s.stageHeight >> 1) + 100; // %%%
        s.addChild(mc);
        var l:Label = trace('mouseover to inspect');
        mc.addEventListener(MouseEvent.MOUSE_MOVE, function(e:MouseEvent):void {
            var a:Array = mc.getObjectsUnderPoint(new Point(e.stageX, e.stageY));
            if (!a.length) return;
            var d:DisplayObject = a.pop();
            l.text = mc.name + ' / ' + d + ' ' + d.name + ' (' + d.parent.getChildIndex(d) + ' ' + d.parent.name + ')';
        });
        mc.addEventListener(MouseEvent.MOUSE_OUT, function(e:MouseEvent):void {
            l.text = 'mouseover to inspect';
        });
        var r:Rectangle = mc.getBounds(s);
        var sh:Shape = new Shape();
        sh.graphics.lineStyle(0, 0x00ff00);
        sh.graphics.drawRect(r.x, r.y, r.width, r.height);
        s.addChild(sh);
        return mc;
    }
    
}

internal class RenderExternalTask extends RenderTask {
    
    public function RenderExternalTask(name:String) { super(name); }
    
    override public function get summary():String { return 'loading ' + name; }
    
    override public function execute():void {
        if (ApplicationDomain.currentDomain.hasDefinition(name)) {
            super.execute();
            return;
        }
        var l:Loader = ResourceLoader.instance.makeLoader('swf/' + name);
        l.contentLoaderInfo.addEventListener(Event.COMPLETE, complete);
        l.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioError);
        fork();
    }
    
    private function complete(e:Event):void {
        super.execute();
        join();
    }
    
    private function ioError(e:IOErrorEvent):void {
        error('IOError while ' + summary);
        join();
    }
    
}

internal class RenderRecipeTask extends RenderTask {
    
    public function RenderRecipeTask(name:String) { super(name); }
    
    override protected function prepare(mc:MovieClip):void {
        if (mc.mc_plate) mc.mc_plate.gotoAndStop(2);
        super.prepare(mc);
    }
    
    override protected function get imageScale():Number { return 2.75; }
    
    override protected function get iconScale():Number { return 0.75; }
    
}

internal class RenderIngredientTask extends RenderTask {
    
    public function RenderIngredientTask(name:String) { super(name); }
    
    override protected function get imageScale():Number { return 1.5; }
    
}

internal class RenderRatingTask extends RenderTask {
    
    public function RenderRatingTask() { super('StarRating'); }
    
    override protected function get iconScale():Number { return NaN; }
    
    override protected function render(directory:String, mc:MovieClip, scale:Number):void {
        var origName:String = name;
        for (var i:int = 1; i <= 5; i++) {
            name = origName + i;
            mc.gotoAndStop(i);
            super.render(directory, mc, scale);
        }
        name = origName;
    }
    
}

internal class RenderPlantTask extends RenderTask {
    
    public function RenderPlantTask(name:String) { super(name); }
    
    override protected function prepare(mc:MovieClip):void {
        mc.gotoAndStop(mc.totalFrames);
        super.prepare(mc);
    }
    
    override protected function get iconScale():Number { return NaN; }
    
}

internal class RenderRestaurantTask extends RenderTask {
    
    public function RenderRestaurantTask(name:String) { super(name); }
    
    private function loop(mc:MovieClip, variable:String, recurse:Boolean=false):void {
        for (var i:int = 0; mc[variable + i]; i++) {
            if (recurse) {
                prepare(mc[variable + i]);
            } else {
                mc[variable + i].visible = false;
            }
        }
    }
    
    override protected function prepare(mc:MovieClip):void {
        mc.stop();
        hide(mc, 'property_chair', true);
        hide(mc, 'property_table', true);
        hide(mc, 'corner1');
        hide(mc, 'corner2');
        hide(mc, 'corner3');
        hide(mc, 'corner4');
        loop(mc, 'mc_customer');
        hide(mc, 'mc_focus');
        loop(mc, 'mc_walkable');
        loop(mc, 'sub', true);
        loop(mc, 'mc_attach', true);
        hide(mc, 'mc_stove');
        hide(mc, 'mc_drinkItem');
        super.prepare(mc);
    }
    
}

internal class RenderAvatarTask extends RenderTask {
    
    public function RenderAvatarTask(name:String) { super(name); }
    
    override protected function get imageScale():Number { return 1.5; }
    
}

internal class RenderFrontTask extends RenderExternalTask {
    
    public function RenderFrontTask(name:String) { super(name); }
    
    override protected function get imageScale():Number { return 1.5; }
    
    override protected function get iconScale():Number { return 0.4; }
    
}

internal class RenderFrontLargeTask extends RenderFrontTask {
    
    public function RenderFrontLargeTask(name:String) { super(name); }
    
    override protected function get imageScale():Number { return 1.0; }
    
    override protected function get iconScale():Number { return 0.2; }
    
}

internal class RenderNilTask extends RenderTask {
    
    public function RenderNilTask(name:String) { super(name); }
    
    override protected function get imageScale():Number { return NaN; }
    
    override protected function get iconScale():Number { return 0.25; }
    
}

internal class RenderMysteryBoxTask extends RenderTask {
    
    public function RenderMysteryBoxTask(name:String) { super(name); }
    
    override protected function get iconScale():Number { return 1.0 / 6.0; }
    
    override protected function prepare(mc:MovieClip):void {
        hide(mc, 'mc_crate');
        hide(mc, 'tf_count');
        mc.getChildAt(0).visible = false;
        super.prepare(mc);
    }
    
}

internal class XMLTask extends Task {
    
    public static const NOTHING:XML = XML('');
    public static var idMap:Object = {};
    public static var classMap:Object = {};
    
    protected var item:XML;
    private var pageTask:PageTask;
    public var itemDescription:XML = <span class='text -link-itemDescription' />;
    public var itemFunction:XML = <span class='text -link-itemFunction' />;
    public var fromMysteryBoxes:XML = listUl('-link-fromMysteryBoxes');
    
    public function XMLTask(item:XML, pageTask:PageTask) {
        this.item = item;
        this.pageTask = pageTask;
        idMap[item.@id] = this;
        classMap[item.attribute(classAttribute)] = this;
    }
    
    protected function name():XML {
        return (<h2>{item.@name}</h2>);
    }
    
    protected function get classAttribute():String { return 'className'; }
    
    protected function image(attributeName:String, force:Boolean=false):XML {
        if (force || ('@' + attributeName) in item) {
            var s:String = item.attribute(attributeName);
            return (
                <span class={'image ' + attributeName}>
                    <img src={'images/' + s + '.png'} alt={item.@name} title={item.@name} />
                </span>
            );
        }
        return NOTHING;
    }
    
    protected function link(icon:Boolean=true):XML {
        if (icon) {
            return (
                <a href={pageTask.path + '#item' + item.@id}>
                    <img src={'icons/' + item.attribute(classAttribute) + '.png'} alt={item.@name} title={item.@name} />
                </a>
            );
        } else {
            return (<a href={pageTask.path + '#item' + item.@id}>{item.@name}</a>);
        }
    }
    
    protected function nonEmpty(xml:XML):XML {
        if (xml.children().length()) return xml;
        return NOTHING;
    }
    
    protected function listUl(attributeName:String):XML {
        return (<ul class={'list ' + attributeName} />);
    }
    
    public function listLi(icon:Boolean=true):XML {
        return (<li>{link(icon)}</li>);
    }
    
    protected function literal(attributeName:String):XML {
        if (('@' + attributeName) in item) {
            var s:String = item.attribute(attributeName);
            return (<span class={'literal ' + attributeName}>{s}</span>);
        }
        return NOTHING;
    }
    
    protected function date(attributeName:String):XML {
        if (('@' + attributeName) in item) {
            var s:String = item.attribute(attributeName);
            s = s.substr(6, 4) + '/' + s.substr(3, 2) + '/' + s.substr(0, 2);
            return (<span class={'date ' + attributeName}>{s}</span>);
        }
        return NOTHING;
    }
    
    protected function flag(attributeName:String):XML {
        if (('@' + attributeName) in item) {
            var s:String = item.attribute(attributeName).toLowerCase();
            if (s != 'true' && s != '1' && s != 'yes' && s != 'on') return NOTHING;
            return (<span class={'flag ' + attributeName} />);
        }
        return NOTHING;
    }
    
    protected function enum(attributeName:String):XML {
        if (('@' + attributeName) in item) {
            var s:String = item.attribute(attributeName);
            return (<span class={'enum ' + attributeName + ' -enum-' + s} />);
        }
        return NOTHING;
    }
    
    protected function availability():XMLList {
        return (
            date('availableDate') +
            date('expireDate') +
            flag('invisible')
        );
    }
    
    protected function description():XMLList {
        return (
            nonEmpty(itemDescription) +
            nonEmpty(itemFunction)
        );
    }
    
    protected function content():XML {
        var div:XML =
            <div id={'item' + item.@id} class="item">
                {image(classAttribute, true)}
                {name()}
            </div>;
        div.normalize();
        return div;
    }
    
    override public function get summary():String { return 'creating entry ' + item.@name; }
    
    override public function execute():void {
        pageTask.appendChild(content());
    }
    
    public function enqueueDependents():void {
        new RenderTask(item.attribute(classAttribute)).enqueue();
    }
    
    override public function enqueue():void {
        if (!Driver.SKIP_PAGES) super.enqueue();
    }
    
}

internal class XMLRecipeTask extends XMLTask {
    
    public var ingredients:XML = listUl('ingredients');
    public var unlockRecipes:XML = listUl('unlockRecipes');
    public var rewards:XML = listUl('-link-rewards');
    
    public function XMLRecipeTask(item:XML, pageTask:PageTask) { super(item, pageTask); }
    
    override protected function content():XML {
        var div:XML = super.content();
        div.appendChild(
            ingredients +
            description() +
            (
                <div class="misc">
                    {flag('isLimited')}
                    {availability()}
                    {flag('foodKingFeed')}
                </div>
            ) +
            nonEmpty(unlockRecipes) +
            nonEmpty(rewards) +
            nonEmpty(fromMysteryBoxes)
        );
        div.normalize();
        return div;
    }
    
    override public function enqueueDependents():void {
        new LinkRecipeTask(item, this).enqueue();
        new RenderRecipeTask(item.@className).enqueue();
    }
    
}

internal class XMLIngredientTask extends XMLTask {
    
    public static var nameMap:Object = {};
    
    public var recipes:XML = listUl('-link-recipes');
    
    public function XMLIngredientTask(item:XML, pageTask:PageTask) {
        super(item, pageTask);
        nameMap[item.@name] = this;
    }
    
    override protected function content():XML {
        var div:XML = super.content();
        div.appendChild(
            enum('rarity') +
            literal('cash') +
            description() +
            recipes +
            (
                <div class="misc">
                    {flag('isNew')}
                    {availability()}
                    {flag('foodKingFeed')}
                    {flag('fanPageFeed')}
                    {flag('noDaily')}
                    {flag('noFirstTimeVisit')}
                    {flag('noQuiz')}
                    {flag('noCoinShop')}
                    {literal('initial')}
                    {literal('freeGiftUnlockLevel')}
                </div>
            ) +
            image('plantClassName') +
            nonEmpty(fromMysteryBoxes)
        );
        div.normalize();
        return div;
    }
    
    override public function enqueueDependents():void {
        new RenderIngredientTask(item.@className).enqueue();
        if ('@plantClassName' in item) new RenderPlantTask(item.@plantClassName).enqueue();
    }
    
}

internal class XMLRestaurantTask extends XMLTask {
    
    public var fromRecipes:XML = listUl('-link-fromRecipes');
    
    public function XMLRestaurantTask(item:XML, pageTask:PageTask) { super(item, pageTask); }
    
    override protected function content():XML {
        var div:XML = super.content();
        div.appendChild(
            literal('cost') +
            literal('cash') +
            description() +
            (
                <div class="misc">
                    {literal('unlockLevel')}
                    {availability()}
                    {flag('foodKingFeed')}
                </div>
            ) +
            nonEmpty(fromRecipes) +
            nonEmpty(fromMysteryBoxes)
        );
        div.normalize();
        return div;
    }
    
    override public function enqueueDependents():void {
        new RenderRestaurantTask(item.@className).enqueue();
    }
    
}

internal class XMLAvatarTask extends XMLTask {
    
    public function XMLAvatarTask(item:XML, pageTask:PageTask) { super(item, pageTask); }
    
    override protected function get classAttribute():String { return 'iconName'; }
    
    override protected function content():XML {
        var div:XML = super.content();
        div.appendChild(
            literal('cost') +
            literal('cash') +
            description() +
            (
                <div class="misc">
                    {availability()}
                </div>
            ) +
            nonEmpty(fromMysteryBoxes)
        );
        div.normalize();
        return div;
    }
    
    override public function enqueueDependents():void {
        if ('@iconName' in item) new RenderAvatarTask(item.@iconName).enqueue();
    }
    
}

internal class XMLTextureTask extends XMLAvatarTask {
    
    public function XMLTextureTask(item:XML, pageTask:PageTask) { super(item, pageTask); }
    
    override protected function get classAttribute():String { return 'texture'; }
    
    override public function enqueueDependents():void {
        if ('@texture' in item) new RenderTask(item.@texture).enqueue();
    }
    
}

internal class XMLFrontTask extends XMLTask {
    
    public function XMLFrontTask(item:XML, pageTask:PageTask) { super(item, pageTask); }
    
    override protected function content():XML {
        var div:XML = super.content();
        div.appendChild(
            literal('cost') +
            literal('cash') +
            description() +
            (
                <div class="misc">
                    {literal('unlockLevel')}
                    {availability()}
                    {flag('foodKingFeed')}
                </div>
            ) +
            nonEmpty(fromMysteryBoxes)
        );
        div.normalize();
        return div;
    }
    
    override public function enqueueDependents():void {
        new RenderFrontTask(item.@className).enqueue();
    }
    
}

internal class XMLFrontLargeTask extends XMLFrontTask {
    
    public function XMLFrontLargeTask(item:XML, pageTask:PageTask) { super(item, pageTask); }
    
    override public function enqueueDependents():void {
        new RenderFrontLargeTask(item.@className).enqueue();
    }
    
}

internal class XMLNilTask extends XMLTask {
    
    public function XMLNilTask(item:XML) { super(item, null); }
    
    override protected function link(icon:Boolean=true):XML {
        if (icon) {
            return (
                <img src={'icons/' + item.attribute(classAttribute) + '.png'} alt={item.@name} title={item.@name} />
            );
        } else {
            return XML(item.@name);
        }
    }
    
    override public function enqueue():void {}
    
    override public function enqueueDependents():void {
        if ('@className' in item) new RenderNilTask(item.@className).enqueue();
    }
    
}

internal class XMLMysteryBoxTask extends XMLTask {
    
    public var contentItems:XML = listUl('contentItems');
    
    public function XMLMysteryBoxTask(item:XML, pageTask:PageTask) { super(item, pageTask); }
    
    private function pricing():XML {
        var div:XML = <div class="pricing" />;
        for each (var priceItem:XML in item.pricing.child('price-item')) {
            // no mystery boxes are available for coins...
            div.appendChild(
                <span class="price-item">
                    <span class="quantity">{priceItem.@quantity}</span>
                    <span class="cash">{priceItem.@cash}</span>
                </span>
            );
        }
        return div;
    }
    
    override protected function content():XML {
        var div:XML = super.content();
        div.appendChild(
            pricing() +
            description() +
            (
                <div class="misc">
                    {availability()}
                </div>
            ) +
            nonEmpty(contentItems) +
            nonEmpty(fromMysteryBoxes)
            // TODO: unlocks
        );
        div.normalize();
        return div;
    }
    
    override public function enqueueDependents():void {
        new LinkMysteryBoxTask(item, this).enqueue();
        new RenderMysteryBoxTask(item.@className).enqueue();
    }
    
}

internal class PageTask extends Task {
    
    private static var navigation:XML = <ul id="navigation" />;
    private static var category:String = null;
    
    public static function addNavigation(category:String, pageTask:PageTask):void {
        var li:XML =
            <li>
                <a href={pageTask.path}>{pageTask.group}</a>
            </li>;
        if (category == PageTask.category) {
            var lis:XMLList = navigation.li;
            lis[lis.length() - 1].ul.appendChild(li);
        } else {
            PageTask.category = category;
            navigation.appendChild(
                <li class="database">
                    <span>{category}</span>
                    <ul>{li}</ul>
                </li>
            );
        }
    }
    
    private var database:String;
    private var group:String;
    private var wrapper:XML = <div id="wrapper" />;
    
    public function PageTask(database:String, group:String):void {
        this.database = database;
        this.group = group;
    }
    
    public function get groupId():String {
        return group.replace(/ /g, '_');
    }
    
    public function get name():String {
        return database + '-' + groupId;
    }
    
    public function get path():String {
        return name + '.xhtml';
    }
    
    public function appendChild(child:Object):void {
        wrapper.appendChild(child);
    }
    
    override public function get summary():String { return 'compiling page ' + path; }
    
    override public function execute():void {
        var html:XML = 
            <html>
                <head>
                    <title>{group}</title>
                    <link rel="stylesheet" type="text/css" href="static/db.css" />
                </head>
                <body id={name}>
                    <h1>{group}</h1>
                    {navigation}
                    {wrapper}
                </body>
            </html>;
        html.@xmlns = 'http://www.w3.org/1999/xhtml';
        var ba:ByteArray = new ByteArray();
        ba.writeUTFBytes(html.toXMLString());
        write(path, ba);
    }
    
    override public function enqueue():void {
        if (!Driver.SKIP_PAGES) super.enqueue();
    }
    
}

internal class LinkTask extends Task {
    
    protected var xml:XML;
    
    public function LinkTask(xml:XML) {
        this.xml = xml;
    }
    
    override public function enqueue():void {
        if (!Driver.SKIP_PAGES) super.enqueue();
    }
    
    protected function split(attributeName:String):Array {
        if (!('@' + attributeName in xml)) return [];
        return xml.attribute(attributeName).split(/\s*,\s*/);
    }
    
}

internal class LinkRecipeTask extends LinkTask {
    
    private var recipeTask:XMLRecipeTask;
    
    public function LinkRecipeTask(item:XML, recipeTask:XMLRecipeTask) {
        super(item);
        this.recipeTask = recipeTask;
    }
    
    override public function get summary():String { return 'linking recipe ' + xml.@name; }
    
    override public function execute():void {
        var ingredientTask:XMLIngredientTask;
        var ingredientName:String;
        var ingredientSet:Object = {};
        for each (ingredientName in split('ingredients')) {
            ingredientTask = XMLIngredientTask.nameMap[ingredientName];
            recipeTask.ingredients.appendChild(ingredientTask.listLi());
            ingredientSet[ingredientName] = true;
        }
        for (ingredientName in ingredientSet) {
            ingredientTask = XMLIngredientTask.nameMap[ingredientName];
            ingredientTask.recipes.appendChild(recipeTask.listLi());
        }
        for each (var recipeId:String in split('unlockRecipes')) {
            var recipeTask2:XMLRecipeTask = XMLTask.idMap[recipeId];
            recipeTask.unlockRecipes.appendChild(recipeTask2.listLi());
        }
    }
    
}

internal class LinkTextTask extends LinkTask {
    
    private static const prefixMap:Object = {
        'ItemDescription': 'itemDescription',
        'ItemFunction': 'itemFunction'
    };
    
    private var className:String;
    private var field:String;
    
    public function LinkTextTask(text:XML):void { super(text); }
    
    override public function get summary():String { return 'linking text ' + xml.@id; }
    
    override public function execute():void {
        var task:XMLTask = XMLTask.classMap[className];
        if (!task) return;
        var s:String = xml.toString();
        s = s.replace(/\\n/g, '\n');
        s = s.replace(/\\%/g, '%');
        s = s.replace(/<[^>]*>/g, '');
        // no description text contains meta replacements...
        task[field].appendChild(s);
    }
    
    override public function enqueue():void {
        var id:String = xml.@id;
        for (var prefix:String in prefixMap) {
            if (id.substr(0, prefix.length) == prefix) {
                className = id.substr(prefix.length);
                field = prefixMap[prefix];
                super.enqueue();
                break;
            }
        }
    }
    
}

internal class LinkRecipeRewardTask extends LinkTask {
    
    public function LinkRecipeRewardTask(reward:XML) { super(reward); }
    
    override public function get summary():String { return 'linking reward ' + xml.@name; }
    
    override public function execute():void {
        var restaurantTask:XMLRestaurantTask = XMLTask.idMap[xml.@id];
        for each (var recipe:XML in xml.recipe) {
           var recipeTask:XMLRecipeTask = XMLTask.idMap[recipe.@id];
           restaurantTask.fromRecipes.appendChild(recipeTask.listLi());
           recipeTask.rewards.appendChild(restaurantTask.listLi());
        }
    }
    
}

internal class LinkMysteryBoxTask extends LinkTask {
    
    private var mysteryBoxTask:XMLMysteryBoxTask;
    
    public function LinkMysteryBoxTask(item:XML, mysteryBoxTask:XMLMysteryBoxTask) {
        super(item);
        this.mysteryBoxTask = mysteryBoxTask;
    }
    
    override public function get summary():String { return 'linking mystery box ' + xml.@name; }
    
    override public function execute():void {
        var task:XMLTask;
        var itemId:String;
        var contentItemSet:Object = {};
        for each (var itemGroup:XML in xml.itemGroup) {
            var ul:XML = <ul class="itemGroup" />;
            var weight:int = parseInt(itemGroup.@weight, 10);
            for each (var contentItem:XML in itemGroup.child('content-item')) {
                itemId = contentItem.@id;
                task = XMLTask.idMap[itemId];
                var li:XML = task.listLi(false);
                li.prependChild(
                    <span class="weight">{weight * parseInt(contentItem.@weight, 10)}</span>
                );
                ul.appendChild(li);
                contentItemSet[itemId] = true;
            }
            mysteryBoxTask.contentItems.appendChild(
                <li>{ul}</li>
            );
        }
        for (itemId in contentItemSet) {
            task = XMLTask.idMap[itemId];
            task.fromMysteryBoxes.appendChild(mysteryBoxTask.listLi());
        }
    }
    
}