/**
 * This override adds diagnostics to the {@link Ext.layout.Context} class.
 */
Ext.define('Ext.diag.layout.Context', {

    override: 'Ext.layout.Context',

    requires: [
        'Ext.perf.Monitor'
    ],

    logOn: {
        //boxParent: true,
        //calculate: true,
        //cancelComponent: true,
        //cancelLayout: true,
        //doInvalidate: true,
        //flush: true,
        //flushInvalidate: true,
        //invalidate: true,
        //initItem: true,
        //layoutDone: true,
        //queueLayout: true,
        //resetLayout: true,
        //runCycle: true,
        //setProp: true,
        0:0
    },

    //profileLayoutsByType: true,

    //reportOnSuccess: true,

    cancelComponent: function (comp) {
        if (this.logOn.cancelComponent) {
            Ext.log('cancelCmp: ', comp.id);
        }
        this.callParent(arguments);
    },

    cancelLayout: function (layout) {
        if (this.logOn.cancelLayout) {
            Ext.log('cancelLayout: ', this.getLayoutName(layout));
        }
        this.callParent(arguments);
    },

    callLayout: function (layout, methodName) {
        var accum = this.accumByType[layout.type],
            frame = accum && accum.enter();

        this.callParent(arguments);

        if (accum) {
            frame.leave();
        }
    },

    checkRemainingLayouts: function () {
        var me       = this,
            expected = 0,
            key, layout;

        for (key in me.layouts) {
            layout = me.layouts[key];

            if (me.layouts.hasOwnProperty(key) && layout.running) {
                ++expected;
            }
        }

        if (me.remainingLayouts != expected) {
            Ext.Error.raise({
                msg: 'Bookkeeping error me.remainingLayouts'
            });
        }
    },

    flush: function () {
        if (this.logOn.flush) {
            var items = this.flushQueue;
            Ext.log('--- Flush ', items && items.getCount());
        }

        return this.callParent(arguments);
    },

    flushInvalidates: function () {
        if (this.logOn.flushInvalidate) {
            Ext.log('>> flushInvalidates');
        }

        var ret = this.callParent(arguments);

        if (this.logOn.flushInvalidate) {
            Ext.log('<< flushInvalidates');
        }

        return ret;
    },

    getCmp: function (target) {
        var ret = this.callParent(arguments);
        if (!ret.wrapsComponent) {
            Ext.Error.raise({
                msg: target.id + ' is not a component'
            });
        }
        return ret;
    },

    getEl: function (parent, target) {
        var ret = this.callParent(arguments);
        if (ret && ret.wrapsComponent) {
            Ext.Error.raise({
                msg: parent.id + '/' + target.id + ' is a component (expected element)'
            });
        }
        return ret;
    },

    getLayoutName: function (layout) {
        return layout.owner.id + '<' + layout.type + '>';
    },

    layoutDone: function (layout) {
        var me = this,
            name = me.getLayoutName(layout);

        if (me.logOn.layoutDone) {
            Ext.log('layoutDone: ', name, ' ( ', me.remainingLayouts, ' running)');
        }

        if (!layout.running) {
            Ext.Error.raise({
                msg: name + ' is already done'
            });
        }
        if (!me.remainingLayouts) {
            Ext.Error.raise({
                msg: name + ' finished but no layouts are running'
            });
        }

        me.callParent(arguments);
    },

    layoutTreeHasFailures: function (layout, reported) {
        var me = this;

        function hasFailure (lo) {
            var failure = !lo.done,
                key, childLayout;

            if (lo.done) {
                for (key in me.layouts) {
                    if (me.layouts.hasOwnProperty(key)) {
                        childLayout = me.layouts[key];

                        if (childLayout.owner.ownerLayout === lo) {
                            if (hasFailure(childLayout)) {
                                failure = true;
                            }
                        }
                    }
                }
            }

            return failure;
        }

        if (hasFailure(layout)) {
            return true;
        }

        function markReported (lo) {
            var key, childLayout;

            reported[lo.id] = 1;

            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    childLayout = me.layouts[key];

                    if (childLayout.owner.ownerLayout === lo) {
                        markReported(childLayout);
                    }
                }
            }
        }

        markReported(layout);
        return false;
    },

    queueLayout: function (layout) {
        if (layout.done || layout.blockCount || layout.pending) {
            Ext.Error.raise({
                msg: this.getLayoutName(layout) + ' should not be queued for layout'
            });
        }
        if (this.logOn.queueLayout) {
            Ext.log('Queue ', this.getLayoutName(layout));
        }
        return this.callParent(arguments);
    },

    reportLayoutResult: function (layout, reported) {
        var me           = this,
            owner        = layout.owner,
            ownerContext = me.getCmp(owner),
            blockedBy    = [],
            triggeredBy  = [],
            key, value, i, length, childLayout,
            item, setBy, info;

        reported[layout.id] = 1;

        for (key in layout.blockedBy) {
            if (layout.blockedBy.hasOwnProperty(key)) {
                blockedBy.push(layout.blockedBy[key]);
            }
        }
        blockedBy.sort();

        for (key in me.triggersByLayoutId[layout.id]) {
            if (me.triggersByLayoutId[layout.id].hasOwnProperty(key)) {
                value = me.triggersByLayoutId[layout.id][key];
                triggeredBy.push({ name: key, info: value });
            }
        }
        triggeredBy.sort(function (a, b) {
            return a.name < b.name ? -1 : (b.name < a.name ? 1 : 0);
        });

        Ext.log({indent: 1}, (layout.done ? '++' : '--'), me.getLayoutName(layout),
            (ownerContext.isBoxParent ? ' [isBoxParent]' : ''),
            (ownerContext.boxChildren ? ' - boxChildren: ' + ownerContext.state.boxesMeasured + '/' + ownerContext.boxChildren.length : ''),
            ownerContext.boxParent ? (' - boxParent: ' + ownerContext.boxParent.id) : '',
            ' - size: ', ownerContext.widthModel.name, '/', ownerContext.heightModel.name);

        if (!layout.done || me.reportOnSuccess) {
            if (blockedBy.length) {
                ++Ext.log.indent;
                Ext.log({indent: 1}, 'blockedBy:  count=',layout.blockCount);

                length = blockedBy.length;
                for (i = 0; i < length; i++) {
                    Ext.log(blockedBy[i]);
                }

                Ext.log.indent -= 2;
            }
            if (triggeredBy.length) {
                ++Ext.log.indent;
                Ext.log({indent: 1}, 'triggeredBy: count='+layout.triggerCount);

                length = triggeredBy.length;
                for (i = 0; i < length; i++) {
                    info = value.info || value;
                    item  = info.item;
                    setBy = (item.setBy && item.setBy[info.name]) || '?';

                    value = triggeredBy[i];

                    Ext.log(
                        value.name,
                        ' (',
                        item.props[info.name],
                        ') dirty: ',
                        (item.dirty ? !!item.dirty[info.name] : false),
                        ', setBy: ',
                        setBy
                    );
                }

                Ext.log.indent -= 2;
            }
        }

        for (key in me.layouts) {
            if (me.layouts.hasOwnProperty(key)) {
                childLayout = me.layouts[key];

                if (!childLayout.done && childLayout.owner.ownerLayout === layout) {
                    me.reportLayoutResult(childLayout, reported);
                }
            }
        }

        for (key in me.layouts) {
            if (me.layouts.hasOwnProperty(key)) {
                childLayout = me.layouts[key];

                if (childLayout.done && childLayout.owner.ownerLayout === layout) {
                    me.reportLayoutResult(childLayout, reported);
                }
            }
        }

        --Ext.log.indent;
    },

    resetLayout: function (layout) {
        var me = this,
            type = layout.type,
            name = me.getLayoutName(layout),
            accum = me.accumByType[type],
            frame;

        if (me.logOn.resetLayout) {
            Ext.log('resetLayout: ', name, ' ( ', me.remainingLayouts, ' running)');
        }

        if (!me.state) { // if (first time ... before run)
            if (!accum && me.profileLayoutsByType) {
                me.accumByType[type] = accum = Ext.Perf.get('layout_' + layout.type);
            }
            me.numByType[type] = (me.numByType[type] || 0) + 1;
        }

        frame = accum && accum.enter();
        me.callParent(arguments);
        if (accum) {
            frame.leave();
        }

        me.checkRemainingLayouts();
    },

    round: function (t) {
        return Math.round(t * 1000) / 1000;
    },

    run: function () {
        var me = this,
            ret, time, key, value, i, layout,
            boxParent, children, n,
            reported, unreported,
            calcs, total,
            calcsLength, calc;

        me.accumByType = {};
        me.calcsByType = {};
        me.numByType = {};
        me.timesByType = {};
        me.triggersByLayoutId = {};

        Ext.log.indentSize = 3;
        Ext.log('==================== LAYOUT ====================');

        time = Ext.perf.getTimestamp();
        ret = me.callParent(arguments);
        time = Ext.perf.getTimestamp() - time;

        if (me.logOn.boxParent && me.boxParents) {
            for (key in me.boxParents) {
                if (me.boxParents.hasOwnProperty(key)) {
                    boxParent = me.boxParents[key];
                    children  = boxParent.boxChildren;
                    n         = children.length;

                    Ext.log('boxParent: ', boxParent.id);
                    for (i = 0; i < n; ++i) {
                        Ext.log(' --> ', children[i].id);
                    }
                }
            }
        }

        if (ret) {
            Ext.log('----------------- SUCCESS -----------------');
        } else {
            Ext.log(
                {level: 'error' },
                '----------------- FAILURE -----------------'
            );
        }

        for (key in me.layouts) {
            if (me.layouts.hasOwnProperty(key)) {
                layout = me.layouts[key];

                if (layout.running) {
                    Ext.log.error('Layout left running: ', me.getLayoutName(layout));
                }
                if (layout.ownerContext) {
                    Ext.log.error('Layout left connected: ', me.getLayoutName(layout));
                }
            }
        }

        if (!ret || me.reportOnSuccess) {
            reported = {};
            unreported = 0;

            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    layout = me.layouts[key];

                    if (me.items[layout.owner.el.id].isTopLevel) {
                        if (me.reportOnSuccess || me.layoutTreeHasFailures(layout, reported)) {
                            me.reportLayoutResult(layout, reported);
                        }
                    }
                }
            }

            // Just in case we missed any layouts...
            for (key in me.layouts) {
                if (me.layouts.hasOwnProperty(key)) {
                    layout = me.layouts[key];

                    if (!reported[layout.id]) {
                        if (!unreported) {
                            Ext.log('----- Unreported!! -----');
                        }
                        ++unreported;
                        me.reportLayoutResult(layout, reported);
                    }
                }
            }
        }

        Ext.log('Cycles: ', me.cycleCount, ', Flushes: ', me.flushCount,
            ', Calculates: ', me.calcCount, ' in ', me.round(time), ' msec');

        Ext.log('Calculates by type:');
        /*Ext.Object.each(me.numByType, function (type, total) {
            Ext.log(type, ': ', total, ' in ', me.calcsByType[type], ' tries (',
                Math.round(me.calcsByType[type] / total * 10) / 10, 'x) at ',
                me.round(me.timesByType[type]), ' msec (avg ',
                me.round(me.timesByType[type] / me.calcsByType[type]), ' msec)');
        });*/
        calcs = [];
        for (key in me.numByType) {
            if (me.numByType.hasOwnProperty(key)) {
                total = me.numByType[key];

                calcs.push({
                        type       : key,
                        total      : total,
                        calcs      : me.calcsByType[key],
                        multiple   : Math.round(me.calcsByType[key] / total * 10) / 10,
                        calcTime   : me.round(me.timesByType[key]),
                        avgCalcTime: me.round(me.timesByType[key] / me.calcsByType[key])
                    });
            }
        }


        calcs.sort(function (a,b) {
            return b.calcTime - a.calcTime;
        });

        calcsLength = calcs.length;
        for (i=0; i<calcsLength; i++) {
            calc = calcs[i];

            Ext.log(
                calc.type,
                ': ',
                calc.total,
                ' in ',
                calc.calcs,
                ' tries (',
                calc.multiple,
                'x) at ',
                calc.calcTime,
                ' msec (avg ',
                calc.avgCalcTime,
                ' msec)'
            );
        }

        return ret;
    },

    runCycle: function () {
        if (this.logOn.runCycle) {
            Ext.log('>>> Cycle ', this.cycleCount, ' (queue length: ', this.layoutQueue.length, ')');
        }

        return this.callParent(arguments);
    },

    runLayout: function (layout) {
        var me = this,
            type = layout.type,
            accum = me.accumByType[type],
            frame, ret, time;

        if (me.logOn.calculate) {
            Ext.log('-- calculate ', this.getLayoutName(layout));
        }

        frame = accum && accum.enter();

        time = Ext.perf.getTimestamp();
        ret = me.callParent(arguments);
        time = Ext.perf.getTimestamp() - time;
        if (accum) {
            frame.leave();
        }

        me.calcsByType[type] = (me.calcsByType[type] || 0) + 1;
        me.timesByType[type] = (me.timesByType[type] || 0) + time;

        /*  add a / to the front of this line to enable layout completion logging
        if (layout.done) {
            var ownerContext = me.getCmp(layout.owner),
                props = ownerContext.props;

            if (layout.isComponentLayout) {
                Ext.log('complete ', layout.owner.id, ':', type, ' w=',props.width, ' h=', props.height);
            } else {
                Ext.log('complete ', layout.owner.id, ':', type, ' cw=',props.contentWidth, ' ch=', props.contentHeight);
            }
        }**/

        return ret;
    }
});
