'use strict';

/*@
Ox.DocPanel <f> Documentation Panel
    options <o> Options object
        collapsible <b|false> If true, the list can be collabsed
        element <e> Default content
        expanded <b> If true, modules and sections are expanded in the list
        files <a|[]> Files to parse for docs (alternative to items option)
        getModule <f> Returns module for given item
        getSection <f> Returns section for given item
        items <a|[]> Array of doc items (alternative to files option)
        path <s|''> Path prefix
        references <r|null> RegExp to find references
        replace <[[]]|[]> See Ox.SyntaxHighlighter
        resizable <b|true> If true, list is resizable
        resize <a|[128, 256, 384]> List resize positions
        runTests <b|false> If true, run tests
        selected <s|''> Id of the selected item
        showLoading <b|false> If true, show loading icon when parsing files
        showTests <b|false> If true, show test results in list
        showTooltips <b|false> If true, show test result tooltips in list
        size <s|256> Default list size
        stripComments <b|false> If true, strip comments in source code
    self <o> Shared private variable
    ([options[, self]]) -> <o:Ox.SplitPanel> Documentation Panel
        load <!> Fires once all docs are loaded
            items [o] Array of doc items
        tests <!> Fires once all tests finished
            results [o] Array of results
        select <!> select
@*/

Ox.DocPanel = function(options, self) {

    self = self || {};
    var that = Ox.Element({}, self)
        .defaults({
            collapsible: false,
            element: null,
            examples: [],
            examplesPath: '',
            expanded: false,
            files: [],
            getModule: function(item) {
                return item.file.replace(self.options.path, '');
            },
            getSection: function(item) {
                return item.section;
            },
            items: [],
            path: '',
            references: null,
            replace: [],
            resizable: false,
            resize: [128, 256, 384],
            results: null,
            runTests: false,
            selected: '',
            showLoading: false,
            showTests: false,
            showTooltips: false,
            size: 256,
            stripComments: false
        })
        .options(options || {})
        .update({
            selected: function() {
                selectItem(self.options.selected);
            }
        });

    self.$list = Ox.Element();
    self.$toolbar = Ox.Bar({size: 24}).css({textAlign: 'center'});
    self.$sidebar = Ox.SplitPanel({
        elements: [
            {element: Ox.Element()},
            {element: Ox.Element(), size: 24}
        ],
        orientation: 'vertical'
    });
    self.$page = Ox.Element();

    self.$testsStatus = $('<div>')
        .css({marginTop: '5px', textAlign: 'center'})
        .appendTo(self.$toolbar);
    if (!self.options.results) {
        self.options.results = {};
        self.$testsButton = Ox.Button({title: Ox._('Run Tests')})
            .css({margin: '4px auto'})
            .bindEvent({click: runTests})
            .appendTo(self.$toolbar);
        self.$testsStatus.hide();
    } else {
        self.$testsStatus.html(formatResults());
    }

    that.setElement(
        self.$panel = Ox.SplitPanel({
            elements: [
                {
                    collapsible: self.options.collapsible,
                    element: self.$sidebar,
                    resizable: self.options.resizable,
                    resize: self.options.resize,
                    size: self.options.size
                },
                {
                    element: self.$page
                }
            ],
            orientation: 'horizontal'
        })
    );

    if (self.options.files && self.options.files.length) {
        setTimeout(function() {
            self.options.showLoading && showLoadingScreen();
            self.options.files = Ox.makeArray(self.options.files);
            self.options.items = Ox.doc(self.options.files.map(function(file) {
                return self.options.path + file;
            }), function(docItems) {
                self.options.items = docItems;
                getExamples(function() {
                    self.options.showLoading && hideLoadingScreen();
                    self.$sidebar.replaceElement(1, self.$toolbar);
                    renderList();
                    self.options.runTests && runTests();
                    that.triggerEvent('load', {items: self.options.items});
                });
            });
        }, 250); // 0 timeout may fail in Firefox
    } else {
        getExamples(function() {
            self.$sidebar.replaceElement(1, self.$toolbar);
            renderList();
            self.options.runTests && runTests();
        });
    }

    function formatResults() {
        var results = self.options.results[''],
            tests = results.passed + results.failed;
        return tests + ' test' + (tests == 1 ? '' : 's') + ', '
            + results.passed + ' passed, ' + results.failed + ' failed';
    }

    function getExamples(callback) {
        var i = 0;
        if (self.options.examples && self.options.examples.length) {
            self.options.examples.forEach(function(example) {
                var path = self.options.examplesPath + example;
                Ox.get(path + '/index.html', function(html) {
                    var match = html.match(/<title>(.+)<\/title>/),
                        title = match ? match[1] : Ox._('Untitled');
                    Ox.get(path + '/js/example.js', function(js) {
                        var references = js.match(self.options.references);
                        if (references) {
                            Ox.unique(references).forEach(function(reference) {
                                var item = getItemByName(reference);
                                if (item) {
                                    item.examples = (
                                        item.examples || []
                                    ).concat({
                                        id: example.split('/').pop(),
                                        title: title
                                    });
                                }
                            });
                        }
                        if (++i == self.options.examples.length) {
                            self.options.items.forEach(function(item) {
                                if (item.examples) {
                                    item.examples.sort(function(a, b) {
                                        a = a.title.toLowerCase();
                                        b = b.title.toLowerCase();
                                        return a < b ? -1 : a > b ? 1 : 0;
                                    });
                                }
                            });
                            callback();
                        }
                    });
                });
            });
        } else {
            callback();
        }
    }

    function getIcon(id, expanded) {
        var $icon = null, results = self.options.results[id];
        if (!Ox.isEmpty(self.options.results)) {
            $icon = Ox.Theme.getColorImage(
                'symbol' + (expanded === true ? 'Down' : expanded === false ? 'Right' : 'Center'),
                !results ? '' : results.failed === 0 ? 'passed' : 'failed',
                self.options.showTooltips ? getTooltip(results) : null
            );
        } else if (!Ox.endsWith(id, '/')) {
            $icon = $('<img>').attr({src: Ox.UI.getImageURL('symbolCenter')});
        }
        return $icon;
    }

    function getItemByName(name) {
        var item = null;
        Ox.forEach(self.options.items, function(v) {
            if (v.name == name) {
                item = v;
                return false; // break
            }
        });
        return item;
    }

    function getTooltip(results) {
        return results
            ? results.passed + ' test'
                + (results.passed == 1 ? '' : 's') + ' passed'
                + (results.failed
                    ? ', ' + results.failed + ' test'
                        + (results.failed == 1 ? '' : 's') + ' failed'
                    : ''
                )
            : 'No tests';
    }

    function hideLoadingScreen() {
        self.$loadingIcon.stop().remove();
        self.$loadingText.remove();
    }

    function parseFiles(callback) {
        var counter = 0,
            docItems = [],
            length = self.options.files.length;
        self.options.files.forEach(function(file) {
            Ox.doc(self.options.path + file, function(fileItems) {
                docItems = docItems.concat(fileItems);
                ++counter == length && callback(docItems);
            });
        });
    }

    function renderList() {
        var treeItems = [];
        self.options.items.forEach(function(docItem) {
            var moduleIndex, results, sectionIndex;
            docItem.module = self.options.getModule(docItem);
            moduleIndex = Ox.getIndexById(treeItems, docItem.module + '/');
            if (moduleIndex == -1) {
                treeItems.push({
                    id: docItem.module + '/',
                    items: [],
                    title: docItem.module
                });
                moduleIndex = treeItems.length - 1;
            }
            docItem.section = self.options.getSection(docItem);
            if (docItem.section) {
                sectionIndex = Ox.getIndexById(
                    treeItems[moduleIndex].items,
                    docItem.module + '/' + docItem.section + '/'
                );
                if (sectionIndex == -1) {
                    treeItems[moduleIndex].items.push({
                        id: docItem.module + '/' + docItem.section + '/',
                        items: [],
                        title: docItem.section
                    });
                    sectionIndex = treeItems[moduleIndex].items.length - 1;
                }
            }
            (
                docItem.section
                ? treeItems[moduleIndex].items[sectionIndex]
                : treeItems[moduleIndex]
            ).items.push({
                id: docItem.module + '/' + (
                    docItem.section ? docItem.section + '/' : ''
                ) + docItem.name,
                title: docItem.name
            });
        });
        treeItems.sort(sortByTitle);
        treeItems.forEach(function(item) {
            item.items.sort(sortByTitle);
            item.items.forEach(function(subitem) {
                subitem.items.sort(sortByTitle);
            });
        });
        self.$list = Ox.TreeList({
                expanded: self.options.expanded,
                icon: self.options.showTests
                    ? getIcon
                    : Ox.UI.getImageURL('symbolCenter'),
                items: treeItems,
                selected: self.options.selected ? [self.options.selected] : '',
                width: self.options.size
            })
            .bindEvent({
                select: function(data) {
                    if (!data.ids[0] || !Ox.endsWith(data.ids[0], '/')) {
                        selectItem(
                            data.ids[0] ? data.ids[0].split('/').pop() : ''
                        );
                    }
                }
            });
        self.$sidebar.replaceElement(0, self.$list);
        selectItem(self.options.selected);
    }

    function runTests() {
        self.$testsButton.remove();
        self.$testsStatus.html('Running Tests...').show();
        Ox.load({Geo: {}, Image: {}, Unicode: {}}, function() {
            Ox.test(self.options.items, function(results) {
                results.forEach(function(result) {
                    var item = getItemByName(result.name),
                        passed = result.passed ? 'passed' : 'failed';
                    item.tests[Ox.indexOf(item.tests, function(test) {
                        return test.statement == result.statement;
                    })] = result;
                    ['', item.module + '/'].concat(
                        item.section ? item.module + '/' + item.section + '/' : [],
                        item.module + '/' + (item.section ? item.section + '/' : '') + item.name
                    ).forEach(function(key) {
                        self.options.results[key] = self.options.results[key] || {passed: 0, failed: 0};
                        self.options.results[key][passed]++;
                    });
                });
                self.$testsStatus.html(formatResults());
                renderList();
                that.triggerEvent('tests', {results: self.options.results});
            });
        });
    }

    function selectItem(id) {
        var item = id ? getItemByName(id) : null;
        if (item) {
            self.options.selected = id;
            self.$list.options({
                selected: [item.module + '/' + (
                    item.section ? item.section + '/' : ''
                ) + item.name]
            });
            self.$page = Ox.DocPage({
                    item: item,
                    replace: self.options.replace,
                    stripComments: self.options.stripComments
                })
                .bindEvent({
                    close: function() {
                        selectItem();
                    },
                    example: function(data) {
                        that.triggerEvent('example', data);
                    }
                });
            self.$panel.replaceElement(1, self.$page);
        } else {
            self.options.selected = '';
            self.$list.options({selected: []});
            self.$page.empty().append(self.options.element || $('<div>'));
        }
        that.triggerEvent('select', {id: self.options.selected});
    }

    function showLoadingScreen() {
        self.$loadingIcon = Ox.LoadingIcon({size: 16})
            .css({
                position: 'absolute',
                left: (self.$page.width() - self.options.size) / 2 - 8,
                top: self.$page.height() / 2 - 20
            })
            .appendTo(self.$page)
            .start();
        self.$loadingText = $('<div>')
            .addClass('OxLight')
            .css({
                position: 'absolute',
                left: (self.$page.width() - self.options.size) / 2 - 128,
                top: self.$page.height() / 2 + 4,
                width: 256,
                textAlign: 'center'
            })
            .html(Ox._('Generating Documentation...'))
            .appendTo(self.$page);
    }

    function sortByTitle(a, b) {
        var a = Ox.stripTags(a.title).toLowerCase(),
            b = Ox.stripTags(b.title).toLowerCase();
        return a < b ? -1 : a > b ? 1 : 0;
    }

    return that;

};