
/*
The following examples explain the common design pattern for OxJS UI widgets: an
inheritance model that is neither classical nor prototypal, but "parasitical" (a
term coined by <a
href="http://www.crockford.com/javascript/inheritance.html">Douglas
Crockford</a>). In a nutshell, "instances" are created by augmenting other
instances, but in addition to private members (`var foo`) and public members
(`that.bar`), they can have shared private members (`self.baz`). `self` cannot
be accessed from outside, but since `self` itself is an argument of the
"constructor", an instance can inherit its parent's `self` by passing its own
`self`.
*/
'use strict';

/*
Load the UI module.
*/
Ox.load('UI', function() {

    /*
    Create our own namespace.
    */
    Ox.My = {};

    /*
    First, lets build the most basic Box widget. A widget is a "constructor"
    function that takes two (optional) arguments, `options` and `self`, and
    returns a widget object. It's not a constructor in JavaScript terms though:
    It doesn't have to be called with `new`, and doesn't return an `instanceof`
    anything. It just enhances another widget object and returns it.
    */
    Ox.My.Box = function(options, self) {

        /*
        This is how every widget "constructor" begins. `self` is the widget's
        shared private object.
        */
        self = self || {};
        /*
        `that` is the widget itself, its public object, or, in JavaScript terms,
        its `this`. Every widget "inherits" from another widget by simple
        assignment. All public properties of the "super" widget, i.e. all
        properties of its `that`, will be present on our own `that`. In this
        case, we use Ox.Element, the "root" widget at the end of the inheritance
        chain, and pass an empty options object. But we always pass our own
        `self`, which means that any property that Ox.Element (or any other
        widget in the inheritance chain) adds to `self` will be present on our
        own `self`. Then we call the public `defaults`, `options` and `update`
        methods of Ox.Element. `defaults` assigns the defaults object to
        `self.defaults` and copies it to `self.options`, `options` extends
        `self.options` with the options object, and `update` adds one or more
        callbacks that are invoked whenever, by way of calling the `options`
        method, a property of `self.options` is modified or added.
        */
        var that = Ox.Element({}, self)
            .defaults({
                color: [128, 128, 128],
                size: 128
            })
            .options(options || {})
            .update({
                color: setColor,
                size: setSize
            });

        /*
        The second part of the "constructor" function can be thought of as the
        "initializer", and contains everything needed to set up the "instance".
        In this case, we just define a minimum and maximum size and then set the
        widget's color and size. We could have used `var minSize` and `var
        maxSize` here, but by using `self` for private variables that we want to
        be accessible across all the widget's methods, we can be sure that
        inside such methods, any local `var` is actually local to the method.
        */
        self.minSize = 128;
        self.maxSize = 384;

        setColor();
        setSize();

        /*
        Third, we declare the widget's private methods. These are just function
        declarations, hoisted to the top of the "constructor".
        */
        function setColor() {
            /*
            To interact with the DOM, Ox.Element (and any widget derived from
            it) wraps jQuery. If you type Ox.Element() in the console, you
            will get something like `[<div class="OxElement"></div>]`, and the
            widget's prototype has all the methods of a `$('<div>')`, with
            proper chaining. If you have `var $d = $('<div>'), $e =
            Ox.Element();`, then `$d.appendTo($e)` returns `$d`, and
            `$e.append($d)` returns `$e`.
            */
            that.css({
                backgroundColor: 'rgb(' + self.options.color.join(', ') + ')',
            });
        }

        function setSize() {
            /*
            Before setting the size, we make sure the value is between `minSize`
            and `maxSize`.
            */
            self.options.size = Ox.limit(
                self.options.size, self.minSize, self.maxSize
            );
            that.css({
                height: self.options.size + 'px',
                width: self.options.size + 'px'
            });
        }

        /*
        Next, we define the widgets public methods, as properties of `that`.
        (Note that unlike private methods, they are not hoisted.)
        */
        that.showOptions = function() {
            /*
            As there isn't much to do yet, this method just displays the
            widget's options.
            */
            that.html(JSON.stringify(self.options).replace(/([,:])/g, '$1 '));
            /*
            Public methods should return `that`, for chaining.
            */
            return that;
        };

        /*
        And finally, at the very end of the "constructor", we return `that`. And
        that's it.
        */
        return that;
        
    };

    /*
    <pre>
    Ox.My.Box({color: [255, 0, 0]})
        .appendTo(Ox.$body)
        .options({size: 256})
        .showOptions();
    </pre>
    */

    Ox.My.RoundedBox = function(options, self) {

        self = self || {};
        var that = Ox.My.Box({}, self);
        that.defaults(Ox.extend(that.defaults(), {
                radius: 16
            }))
            .options(options || {})
            .update(function(key, value) {
                if (key == 'radius') {
                    setRadius();
                }
            });

        setRadius();

        function setRadius() {
            that.css({
                MozBorderRadius: self.options.radius + 'px',
                OBorderRadius: self.options.radius + 'px',
                WebkitBorderRadius: self.options.radius + 'px'
            });
        }

        return that;

    };


    window.myBox = Ox.My.Box().appendTo(Ox.$body);
    window.myRoundedBox = Ox.My.RoundedBox().appendTo(Ox.$body);
    return;

    Ox.My.Box = function(options, self) {

        self = self || {};
        var that = Ox.Element('<div>', self)
            .defaults({
                color: [128, 128, 128],
                size: 128
            })
            .options(options || {})
            .addClass('OxMyBox OxMonospace')
            .css({
                color: 'white',
                padding: '16px',
                textShadow: '1px 1px 1px black'
            })
            .bindEvent({
                anyclick: focus,
                key_0: reset,
                key_equal: function() {
                    Ox.print('+', self.options.size)
                    setSize(self.options.size + 16, true);
                },
                key_minus: function() {
                    setSize(self.options.size - 16, true);
                }
            });

        self.initialSize = self.options.size;
        self.minSize = 128;
        self.maxSize = 384;

        setColor();
        setSize(self.options.size);
        showOptions();

        function focus(e) {
            Ox.print(e.target);
            if (e.target == that.$element[0]) {
                that.gainFocus();
            }
        }

        function setColor() {
            that.css({
                backgroundColor: 'rgb(' + self.options.color.join(', ') + ')',
            });
        }

        function setSize(size, animate) {
            var css;
            self.options.size = Ox.limit(size, self.minSize, self.maxSize);
            css = {
                height: self.options.size + 'px',
                width: self.options.size + 'px',
            };
            Ox.print('SET SIZE', size, animate, self.minSize, self.maxSize, css)
            if (animate) {
                that.stop().animate(css, 100, showOptions);
            } else {
                that.css(css);
            }
        }

        function showOptions() {
            try {
                that.html(JSON.stringify(self.options).replace(/([,:])/g, '$1 '));
            } catch(e) {}
        }

        function reset() {
            self.options = self.initialOptions;
            showOptions();
        }

        self.setOption = function(key, value) {
            if (key == 'color') {
                setColor();
            } else if (key == 'size') {
                setSize(value);
            }
            showOptions();
        };

        return that;

    };

    Ox.My.RoundedBox = function(options, self) {

        self = self || {};
        var that = Ox.My.Box({}, self);
        that.defaults(Ox.extend(that.defaults(), {
                radius: 16
            }))
            .options(options || {});

        setRadius();

        function setRadius() {
            that.css({
                MozBorderRadius: self.options.radius + 'px',
                OBorderRadius: self.options.radius + 'px',
                WebkitBorderRadius: self.options.radius + 'px'
            });
        }

        self.setSuperOption = self.setOption;

        self.setOption = function(key, value) {
            if (key == 'radius') {
                setRadius();
            } else {
                self.setSuperOption(key, value);
            }
        }

        return that;

    };

    Ox.My.MetaBox = function(options, self) {

        self = self || {};
        Ox.print('MB', options)
        var that = Ox.My.Box({}, self);
        that.defaults(Ox.extend(that.defaults(), {
                boxes: []
            }))
            .options(options || {});

        self.options.boxes.forEach(function(box) {
            that.append(box);
        });

        return that;

    };

    Ox.My.MetaBox({
            boxes: [
                Ox.My.Box({
                    color: [64, 128, 255]
                }),
                Ox.My.RoundedBox({
                    color: [255, 128, 64]
                })
            ],
            size: 384
        })
        .appendTo(Ox.$body);

});