var SearchToken = new Class({

Implements: [ Options, Events ],
initialize: function(wrapper, options) {
    this.wrapper = wrapper;
    this.setOptions(options);
    this.element = new Element("div",   { 'class': 'token' });
    this.input   = new Element("input", { 'class': 'token', 'autocomplete': 'off' });

    this.element.grab(this.input);
    // Trigger the data collection callback.

    this.setupInputEvents();
    this.setupFinalizedEvents();
},
value: function() {
    return this.element.get('text')
},
setupFinalizedEvents: function() {
    this.element.addEvent('click', function(e) {
        e.stop();

        this.wrapper.tokens.each(function(token) {
            $(token).removeClass('selected');
        });
        var token = e.target;
        if (token.hasClass('finalized')) {
            token.addClass('selected');
            var input = token.getElement('input.delete');
            if (!input) {
                var input = new Element('input', {
                    'class': 'delete',
                    'styles': {
                        'width':   '0px',
                        'height':  '0px',
                        'padding': '0px',
                        'margin':  '0px',
                        'z-index': '-1',
                        'position': 'absolute',
                        'left':     '-100px'
                    },
                    'events': {
                        'keyup': function(e) {
                            e.stop();
                            if (["backspace", "delete"].contains(e.key)) {
                                this.destroy();
                                this.wrapper.tokens[this.wrapper.tokens.length - 1].takeFocus();
                            }
                        }.bind(this)
                    }
                })
                token.grab(input);
            }
            input.focus()
        }
    }.bind(this));
},
setupInputEvents: function() {
    this.input.addEvent('focus', function(e) {
        this.options.data.pass(null,this)();
    }.bind(this));

       Autocomplete menu   
    this.input.addEvent('keyup', function(e) {
           These keys are trigger actions on the autocomplete menu.   
        var reservedKeys = [ "down", "up",
                             "enter",
                             "pageup", "pagedown",
                             "esc" ];
        if (reservedKeys.contains(e.key)) { return };

        /* signal to destroyPreviousToken() if this input has been edited */
        if (e.target.get('value').length > 0) {
            e.target.addClass('edited')
        }

        var query = e.target.get('value');
        this.showResults(query);

    }.bind(this));

    /* Stop webkit from paging up/down */
    this.input.addEvent('keydown', function(e) {
        var reservedKeys = [ "pageup", "pagedown" ];
        if (reservedKeys.contains(e.key)) { e.stop() };
    });

    /* Tab == enter for autocomplete */
    this.input.addEvent('blur', function(e) {
            var input = e.target.get('value');
            if (input.length > 0) {
                e.stop();
                this.select();
            }
    }.bind(this));

    /* Autocomplete menu navigation */
    this.input.addEvent('keyup', function(e) {
        switch(e.key) {
            case "down":
                if (this.input.get('value').length != 0) {
                    this.down();
                } else {
                    if (this.resultSet().getChildren("li").length > 0) {
                        this.down();
                    } else {
                        var query = e.target.get('value');
                        this.showResults(query);
                    }
                }
                break;
            case "up":
                this.up();
                break;
            case "enter":
                this.select();
                break;
            case "pageup":
                this.up('top');
                break;
            case "pagedown":
                this.down('bottom');
                break;
            case "esc":
                this.hideResults();
                break;
            case "backspace":
                this.destroyPreviousToken();
                break;
        }

    }.bind(this));

},
showResults: function(query) {
    var resultSet = this.resultSet(),
        data      = this.data,
        existing  = this.wrapper.tokens.map(function(token) { return token.value() }),
        results   = data.filter(function(item) {
            return (item.test(query, 'i') && !existing.contains(item) )
        }).sort();

    /* Build the result set to display */
    resultSet.empty();
    results.each(function(host, index) {
        var result = new TokenSearchResult({'html': host});
        if (index == 0) { result.active() };
        resultSet.grab(result);
    });
    /* Catchall entry */
    if (results.length > 1) {
        var all = new TokenSearchResult({
            'html': '↑ all of the above',
            'class': 'result all',
        });
        resultSet.grab(all);
    }
},
toElement: function() {
   return this.element;
},
setValue: function(value) {
    this.element.set('html', value);
},
finalize: function() {
    this.element.addClass('finalized');
    this.rehashURL({add: this.value()})
},
takeFocus: function() {
    this.input.focus();
},
resultSet: function() {
    return this.element.getParent('div.search').getElement('ul.results');
},
getActive: function() {
    return this.resultSet().getElement('li.active');
},
down: function(position) {
    var resultSet = this.resultSet();
        active    = this.getActive();

    if (position == "bottom") {
        down = resultSet.getLast('li.result');
    } else {
        down = active.getNext('li.result');
    }

    if (down) {
        active.toggleClass('active');
        down.toggleClass('active');
    }
},
up: function(position) {
    var resultSet = this.resultSet(),
        active    = this.getActive();

    if (position == "top") {
        up = resultSet.getFirst('li.result');
    } else {
        up = active.getPrevious('li.result');
    }

    if (up) {
        active.toggleClass('active');
        up.toggleClass('active');
    }
},
destroy: function() {
    this.wrapper.tokens.erase(this);
    this.wrapper.destroyToken(this);
    this.wrapper.resize();

    this.rehashURL({remove: this.value()})
},
destroyPreviousToken: function() {
    var input = this.input.get('value');

    /* Only delete the previous token if:
        - the active TokenInput is empty,
        - and was empty on the last keystroke.
      /
    if ((input.length == 0 && this.previousInputLength > 0)
        || input.length > 0
        || this.input.hasClass('edited')
       ) {
        this.previousInputLength = input.length;
        return
    } else {
        var token = this.wrapper.tokens[this.wrapper.tokens.length - 2];
        if (token) {
            token.destroy();
            this.wrapper.destroyToken(token);
            this.wrapper.resize();
            this.hideResults();
        };
    }

},
hideResults: function() {
    var results = this.resultSet();
    results.empty();
},
select: function() {
    var resultSet  = this.resultSet(),
        selected   = this.getActive();

    if ($chk(selected) && selected.hasClass('all')) {
        var token = this;
        this.wrapper.destroyToken(token);

        /* Create a token for each result. */
        resultSet.getElements('li.result').each(function(result) {
                if (result.hasClass('all')) { return };

                var text  = result.get('html');
                var token = this.wrapper.newToken()
                token.setValue(text);
                token.finalize();
        }, this);
    } else {
        var token = this.element,
            input = this.input,
            text  = selected.get('html');

        input.destroy();
        this.setValue(text);
        this.finalize();
    }

    // IDEA: do selected.destroy() to remove just the entry?
    resultSet.empty();

    this.wrapper.newToken();

    this.wrapper.resize();
},
rehashURL: function(options) {
    // Setup the URL
    var parameters = window.location.hash.slice(1).split('|');
    if (parameters.length == 1) {
        var parameters = ["hosts=", "metrics="]
    }

    var parameters = parameters.map(function(parameter) {
        if (!$chk(parameter)) { return parameter }

        var parts  = parameter.split('='),
            key    = parts[0],
            values = parts[1].split(','),
            value  = options.add || options.remove;

        if (value.contains('/') && key == "hosts")    { return parameter }
        if (!value.contains('/') && key == "metrics") { return parameter }

        if (options.add) {
            values.include(value)
        } else {
            values.erase(value)
        }
        values.erase("")

        var string = key + '=' + values.join(',');
        return string
    }.bind(this));

    var hash = parameters.join('|');
    window.location.hash = hash;
},

});

var TokenSearchResult = new Class({

Implements: [ Options, Events ],
options: {
    'class': 'result',
    'events': {
        'mouseenter': function(e) {
            var element       = e.target,
                currentActive = element.getParent('ul').getElement('li.active');

            if (currentActive) {
                currentActive.removeClass('active');
            }
            element.addClass('active');
        },
        'mouseleave': function(e) {
            var element = e.target;
            element.removeClass('active');
        },
    }
},
initialize: function(options) {
    this.setOptions(options);
    this.element = new Element('li', this.options);
},
// http://mootools.net/blog/2010/03/19/a-better-way-to-use-elements/
toElement: function() {
   return this.element;
},
active: function() {
    this.element.addClass('active');
}

});

var TokenSearch = new Class({

Implements: [ Options, Events ],
options: {
    focus: true
},
initialize: function(parent, options) {
    this.setOptions(options);
    this.parent  = $(parent);

    this.element = new Element('div', {'class': 'tokenWrapper'});
    this.results = new Element('ul',  {'class': 'results'});
    this.parent.grab(this.element);
    this.parent.grab(this.results);
    this.tokens  = [];

    if (this.options.tokens) {
        this.options.tokens.each(function(text) {
            var token = this.newToken()
            token.setValue(text);
            token.finalize();
        }.bind(this));
    }
    this.newToken(this.options.focus);

    /* Clicks within the contain focus input on the editable token. */
    this.element.addEvent('click', function() {
        this.tokens.getLast().takeFocus();
    }.bind(this));
},
toElement: function() {
    return this.element;
},
newToken: function(focus) {
    var token = new SearchToken(this, { 'data': this.options.data });

    this.tokens.include(token);
    this.element.grab(token);

    if (focus != false) {
        token.takeFocus();
    }

    this.resize();

    return token;
},
destroyToken: function(token) {
    this.tokens.erase(token);
    $(token).destroy()
},
tokenValues: function() {
    return this.tokens.map(function(t) {
        return t.value()
    }).slice(0,-1);
},
resize: function() {
    var firstToken      = $(this.tokens[0]),
        lastToken       = $(this.tokens[this.tokens.length - 1]),
        lastTokenHeight = lastToken.getDimensions().height,
        baseY           = this.element.getPosition().y,
        minY            = firstToken.getPosition().y,
        maxY            = lastToken.getPosition().y;

    if (minY != maxY) {
        var newHeight = maxY - baseY + lastTokenHeight;
    } else {
        var newHeight = minY - baseY + lastTokenHeight;
    }
    this.element.setStyle('height', newHeight);
},

});

var ChartBuilder = new Class({

Implements: [ Options, Events ],
initialize: function(element, options) {
    this.setOptions(options);
    this.builder   = $(element);

    var parameters = window.location.hash.slice(1).split('|');

    parameters.each(function(parameter) {
        if (!$chk(parameter)) { return }

        var parts  = parameter.split('='),
            key    = parts[0],
            values = parts[1].split(',');

        values.erase("")
        this.options[key] = values;
    }.bind(this));

    this.searchers = new Object;
    this.setupHostSearch();
    this.setupMetricSearch();
    this.setupPercentileSelection();
    this.setupShow();

    /* Display graphs if hosts + metrics have been selected */
    if (this.options.hosts && this.options.metrics) {
        this.showGraphs();
    }
},
setupHostSearch: function() {
    var container = this.builder.getElement("div#hosts div.search"),
        searcher  = new TokenSearch(container, {
            'tokens':   this.options.hosts,
            'data':     this.getHosts
        });
    this.searchers.host = searcher;
},
setupMetricSearch: function() {
    var container = this.builder.getElement("div#metrics div.search"),
        searcher  = new TokenSearch(container, {
            'tokens':   this.options.metrics,
            'data':     this.getMetrics,
            'focus':    false
        });
    this.searchers.metric = searcher;
},
setupPercentileSelection: function() {
    var container = this.builder.getElement("div#profile-options div.percentiles");
    if (this.options.percentiles) {
        if (this.options.percentiles.length > 0) {
            this.options.percentile95 = true;
        }
    }
    if (container) {
        var percentileSelector = new Element('input', {
            'type': 'checkbox',
            'id':   this.parentElement + '-percentile95',
            'name': 'percentile_95',
            'checked': this.options.percentile95,
            'events': {
                'click': function() {
                    this.options.percentile95 = !this.options.percentile95
                }.bind(this)
            },
            'styles': {
                'margin-right': '4px',
                'cursor': 'pointer'
            }
        });
        container.grab(percentileSelector);
    }

},
setupSave: function() {
    if (!this.save) {
        var profile_name = this.profile_name = new Element('input', {
            'id':    'profile_name',
            'type':  'text',
            'class': 'text',
            'value': $('name') ? $('name').get('text') : ''
        });

        var show = this.builder.getElement('input#show');
        var save = this.save = new Element('input', {
            'id':    'save',
            'type':  'button',
            'class': 'button',
            'value': 'Save profile',
            'events': {
                'click': function() {
                    var hosts   = this.searchers.host.tokenValues(),
                        metrics = this.searchers.metric.tokenValues();
                        percentiles   = [];
                        percentile95 = this.options.percentile95;

                    if (percentile95) {
                        percentiles.push(95);
                    }
                    var jsonRequest = new Request.JSON({
                        method:    'post',
                        url:       '/builder',
                        onSuccess: function(response) {
                            new Message({
                                title:    'Profile saved',
                                message:  '"' + profile_name.get('value') + '"',
                                top:      true,
                                iconPath: '/images/',
                                icon:     'ok.png',
                            }).say();
                        },
                    }).send({'data': {
                        'hosts':        hosts,
                        'metrics':      metrics,
                        'profile_name': profile_name.get('value'),
                        'percentiles':  percentiles
                    }});

                }.bind(this)
            }
        });

        save.fade('hide')
        show.grab(save, 'after');

        profile_name.fade('hide')
        save.grab(profile_name, 'after');
    }
},
setupShow: function() {
    var show = this.builder.getElement('input#show');

    /* Button to save profile */
    show.addEvent('click', function(e) {
        e.stop();

        this.setupSave();
    }.bind(this));

    /* Display the graphs */
    show.addEvent('click', function(e) {
        e.stop();

        var hosts   = $(this.searchers.host).getElements("div.token.finalized"),
            metrics = $(this.searchers.metric).getElements("div.token.finalized"),
            percentiles   = [];
            percentile95 = this.options.percentile95;

        if (percentile95) {
            percentiles.push(95);
        }
        this.options.percentiles = percentiles;

        if (hosts.length > 0 && metrics.length > 0) {
            this.showGraphs();
            // Fade the save button once graphs have been rendered.
            this.save.fade.delay(1500, this.save, 'in')
            this.profile_name.fade.delay(1500, this.profile_name, 'in')
        } else {
            new Message({
                title:    'Oops!',
                message:  'You need to specify some hosts and metrics before you can show graphs.',
                top:      true,
                iconPath: '/images/',
                icon:     'caution.png',
            }).say();
        }

    }.bind(this));

},
showGraphs: function() {
    window.Graphs = [];

    var hosts   = $(this.searchers.host).getElements("div.token.finalized"),
        metrics = $(this.searchers.metric).getElements("div.token.finalized");

    var hosts   = hosts.map(function(el) { return el.get('text') }),
        metrics = metrics.map(function(el) { return el.get('text') }),
        graphs  = $('graphs'),
        save    = this.save,
        profile_name = this.profile_name,
        percentiles  = this.options.percentiles;

    graphs.empty();
    hosts.each(function(host) {
        /* Separate each plugin onto its own graph */
        var plugins = {};
        metrics.each(function(m) {
            var parts  = m.split('/'),
                plugin = parts[0],
                metric = parts[1];

            if (! plugins[plugin]) {
                plugins[plugin] = []
            }

            plugins[plugin].push(metric)
        });

        /* Create the graphs */
        $each(plugins, function(metrics, plugin) {
            var element = new Element('div', {'class': 'graph'});
            graphs.grab(element);

            var graph = new VisageGraph(element, host, plugin, {
                pluginInstance: metrics.join(','),
                percentiles: percentiles
            });

            window.Graphs.include(graph);
        });
    }.bind(this));
},
getHosts: function() {
    var request = new Request.JSONP({
        url:    "/data",
        method: "get",
        onRequest:  function(json) {
            this.data = [];
        },
        onComplete: function(json) {
            this.data = json.hosts;
        }.bind(this)
    }).send();

    return request;
},
getMetrics: function(hosts) {
    var builder = $(this.wrapper).getParent('div#chart-builder'),
        tokens  = builder.getElement("div#hosts div.tokenWrapper"),
        hosts   = tokens.getElements("div.token.finalized").map(function(token) {
            return token.get('text');
        });

    var url     = "/data/" + hosts.join(',');
    var request = new Request.JSONP({
        url:    url,
        method: "get",
        onComplete: function(json) {
            this.data = json.metrics;
        }.bind(this)
    }).send();

    return request;
}

});