<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Phenotype Demo</title>
        <meta name="description" content="Phenotype Demo">
        <link rel="stylesheet" href="style.css">
        <script src="phenotype.js" type="application/javascript"></script>
    </head>
    <body>
        <a href="https://github.com/benjamine/phenotype" id="fork_me">
            <img alt="Fork me on GitHub" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
        </a>
        <h1>Phenotype Demo</h1>

        <script type="text/javascript">

    var Trait = phenotype.Trait;

    var Walker = new Trait('Walker', {
        goTo: function(location) {
            phenotype.pending();
        }
    });

    var Swimmer = new Trait('Swimmer', {
        goTo: function(location) {
            phenotype.pending();
        }
    });

    var Flyer = new Trait('Flyer', {
        goTo: function(location) {
            phenotype.pending();
        }
    });

    var Duck = new Trait('Duck', Walker, Swimmer, Flyer);
    try {
        Duck.create();
    } catch(err) {
        // conflict, goTo is defined in Walker, Swimmer and Flyer
        console.error(err);
    }

    // updating

    var jet = Flyer.create();

    // add a trait to existing object
    var Vehicle = new Trait('Vehicle', { seats: 3 });
    Vehicle.addTo(jet);

    // logs 3
    console.log(jet.seats);

    // modify existing Trait
    Vehicle.add({ seats: 4, wings: 2 }, new Trait({ pilot: true }));

    // logs 4 2 true
    console.log(jet.seats, jet.wings, jet.pilot);

    // using "required"

    var Retriever = new Trait('Retriever', {
        goTo: phenotype.member.required,
        grab: phenotype.member.required,
        retrieve: function(thing) {
            this.goTo(thing.location);
            this.grab(thing);
            this.goTo(this.previousLocation);
        }
    });

    try {
        var retriever = Retriever.create();
    } catch(err) {
        // goTo is required by Retriever, this is an abstract Trait
        console.error(err);
    }

    var Dog = new Trait('Dog', Walker, Retriever, {
        grab: function(thing) {
            phenotype.pending();
        }
    });

    var dog = Dog.create();

    try {
        var ball = {};
        dog.retrieve(ball);
    } catch(err) {
        // throws "pending" error from grab method above
        console.error(err);
    }

    // using mixin (allows to apply a Trait to a preexistent object)

    var parrot = { name: 'pepe' };

    try {
        Retriever.mixin(parrot);
    } catch(err) {
        // goTo is required by Retriever, and parrot doesn't have it
        console.log(err);
    }

    parrot.goTo = phenotype.pending;
    parrot.grab = phenotype.pending;
    // this time parrot provides all required methods
    Retriever.mixin(parrot);

    // using "aliasOf" and "from"

    var Bird = new Trait('Bird', Walker, Flyer, {
        walkTo: phenotype.member.aliasOf(Walker, 'goTo'),
        goTo: phenotype.member.from(Flyer)
    });

    var Hawk = new Trait('Hawk', Bird, Retriever, {
        grab: function(thing) {
            phenotype.pending();
        }
    });

    var Capibara = new Trait('Capibara', Walker, Swimmer, {
        walkTo: phenotype.member.aliasOf(Walker, 'goTo'),
        swimTo: phenotype.member.aliasOf(Swimmer, 'goTo'),
        goTo: function(location) {
            location.isOnWater() ? this.swimTo(location) : this.walkTo(location);
        }
    });

    // using ancestors

    var Electric = new Trait('Electric', {
        shutdown: function() {
            console.log('disconnected power');
        }
    });

    var CombustionEngine = new Trait('CombustionEngine', {
        shutdown: function() {
            console.log('disconnected fuel injection');
        }
    });

    var Car = new Trait('Car', Electric, CombustionEngine, {
        open: function(all){
            console.log('doors unlocked');
        },
        shutdown: phenotype.member.ancestors().then(function() {
            console.log('doors locked');
        })
    });

    var RetractableRoof = new Trait('RetractableRoof', {
        openRoof: function() {
            console.log('roof retracted');
        },
        shutdown: function() {
            console.log('roof extended');
        }
    });

    var ConvertibleCar = new Trait('ConvertibleCar', Car, RetractableRoof, {
        open: phenotype.member.ancestors().wrap(function(inner, base){
            return function(all){
                inner.call(this, all);
                if (all) {
                    this.openRoof();
                }
            }
        }),
        shutdown: phenotype.member.ancestors()
    });

    // using pipe and async

    var Peeler = new Trait('Peeler', {
        process: function(err, thing) {
            console.log('peeling ' + thing);
            // peeling takes time, but timeout at 1500ms
            var async = phenotype.async(1500);
            setTimeout(function(){
                async.done('peeled ' + thing);
            }, 1000);
            return async;
        }
    });

    Peeler.create().process(null, "apple").then(function(err, result){
        if (err) {
            console.error('error peeling apple');
            return;
        }
        // logs "peeled apple"
        console.log('result:', result);
    });

    var Chopper = new Trait('Chopper', {
        process: function(err, thing) {
            console.log('chopping ' + thing);
            return 'chopped ' + thing;
        }
    });

    var Mixer = new Trait('Mixer', {
        process: function(err, thing) {
            console.log('mixing ' + thing);
            // mixing takes time
            var async = phenotype.async(3000);
            setTimeout(function(){
                async.done('mixed ' + thing);
            }, 1200);
            return async;
        }
    });

    var Oven = new Trait('Oven', {
        process: function(err, thing) {
            console.log('baking ' + thing);
            return 'baked ' + thing;
        }
    });

    var CookingMachine = new Trait('CookingMachine', Peeler, Chopper, Mixer, Oven, {
        process: phenotype.member.ancestors().pipe({continueOnError: true})
        .then(function(err, thing) {
            if (err) {
                console.error('cooking failed');
                console.error(err);
                return;
            }
            console.log('finished cooking:', thing);
            return thing;
        })
    });

    var machine = CookingMachine.create();

    machine.process(null, "vegetables").then(function(err, result){
        if (err) {
            console.error('error, no result');
            return;
        }
        // logs "result: baked mixed chopped peeled vegetables"
        console.log('result:', result);
    });

    // properties & events

    var Labrador = new Trait('Labrador', Dog, Retriever, phenotype.HasEvents, {
        name: phenotype.member.property(),        
        initial: phenotype.member.property(function(){
            return this.name().substr(0, 1).toUpperCase();
        }),
    });

    var spike = Labrador.create();
    spike.name('Spike');

    // logs "Spike"
    console.log(spike.name());
    // logs "S"
    console.log(spike.initial());

    spike.on({
        bark: function(e, volume) {
            console.log(e.source.name(), 'barked', volume);
        },
        namechanged: function(e, data) {
            console.log(data.property.name, 'changed from', data.previousValue, 'to', data.value);
        },
        initialchanged: function(e, data) {
            console.log(data.property.name, 'changed from', data.previousValue, 'to', data.value);
        }
    });

    // logs "Spikey barked loud"
    spike.emit('bark', 'loud');

    spike.off('bark');

    spike.emit('bark', 'louder');

    // logs "name changed from Spike to Spikey"
    spike.name('Spikey');


        </script>

        <section id="main">
            <div>Not much to see here,<br/>check output on browser console</div>
        </section>

        <footer>
            <a href="https://github.com/benjamine/phenotype">Download Phenotype</a><br>
            <p class="credits">developed by <a href="http://twitter.com/beneidel">Benjamín Eidelman</a></p>
        </footer>
    </body>
</html>