

function ClassFactory(Prototype) {
    var Constructor = function() {
        if ('init' in this) {
            this.init.apply(this, arguments);
        }
    }
    Constructor.prototype = Prototype;
    return Constructor;
}

var Controller = ClassFactory({

    init: function() {
        this.model = new Model();
    },

    pretty_colors: [
        '#000',
        '#440',
        '#880',
        '#FF0',
    ],

    name_to_delimiter: {
        'slash': '/',
        'dash': '-',
        'underscore': '_'
    },

    delimiter_to_name: {
        '/': 'slash',
        '-': 'dash',
        '_': 'underscore'
    },

    name_to_suffix: {
        'html': '.html',
        'php': '.php',
        'none': ''
    },

    suffix_to_name: {
        '.html': 'html',
        '.php': 'php',
        '': 'none'
    },

    tabs: [
        'cgi-parameters',
        'prefix-parameters',
        'delimiters-parameters',
        'suffix-parameters'
    ],

    ajax_current: 0,
    ajax_running: false,

    get_value: function(id) {
        return this.get_element(id).value;
    },

    set_value: function(id, value) {
        this.get_element(id).value = value;
    },

    set_radio: function(id, name) {
        this.get_element(id+'-'+name).checked = true;
    },

    get_element: function(id) {
        var element = document.getElementById(id);
        if (!element) {
            alert('element "'+id+'" not found');
        }
        return element;
    },


    hide: function(id) {
        this.get_element(id).style.display = 'none';
    },

    show: function(id) {
        this.get_element(id).style.display = 'block';
    },

    update_all: function() {
        this.set_value('long-url', this.model.get_long_url());
        this.update_cgi_parameters();
        this.set_value('prefix', this.model.get_prefix());
        this.set_radio('delimiter', this.delimiter_to_name[this.model.get_delimiter()]);
        var suffix = this.model.get_suffix();
        if (suffix in this.suffix_to_name) {
            this.set_radio('suffix', this.suffix_to_name[suffix]);
            this.set_value('custom-suffix', '');
        }
        else {
            this.set_radio('suffix', 'custom');
            this.set_value('custom-suffix', suffix);
        }
        this.update_test();
        this.update_results();
        this.show_tab('cgi-parameters');
    },

    update_cgi_parameters: function() {
        var parameters = this.model.get_parameters();
        var template = this.get_element('cgi-parameter-template').innerHTML;
        var content = "";
        for (i in parameters) {
            var parameter = parameters[i];
            var value = (parameter.name_present ? '1' : '0') + ',' +
                (parameter.value_present ? '1' : '0');
            content += this.prepare_cgi_parameter(template, parameter, 1*i+1, value);
        }
        var table = this.get_element('cgi-parameters-table-template').innerHTML;
        table = table.replace(/<tbody><\/tbody>/i, '<tbody>'+content+'</tbody>');
        this.get_element('cgi-parameters-table').innerHTML = table;
    },

    prepare_cgi_parameter: function(template, parameter, index, value) {
        var content = template;
        content = content.replace('parameter-name', 
            parameter.name.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'));
        content = content.replace(/parameter-0/g, 'parameter-'+index);
        content = content.replace('"'+value+'"', '"'+value+'" checked');
        content = content.replace('='+value+' ', '='+value+' checked ');
        return content;
    },

    update_test: function() {
        this.set_value('test-long-url', '');
        this.set_value('test-short-url', '');
        this.hide('test-long-url-valid');
        this.hide('test-short-url-valid');
    },

    update_results: function() {
        this.set_value('short-url', this.model.get_short_url());
        this.update_pretty_urls();
        this.set_value('htaccess-rule', this.model.get_htaccess_rule());
        this.set_value('done-htaccess-rule', this.model.get_htaccess_rule());
    },

    update_pretty_urls: function() {
        this.update_pretty_url('pretty-long-url', this.model.get_pretty_long_url());
        this.update_pretty_url('pretty-short-url', this.model.get_pretty_short_url());
    },

    update_pretty_url: function(id, url) {
        var element = this.get_element(id);
        while (element.lastChild) {
            element.removeChild(element.lastChild);
        }
        for (i in url) {
            var text = url[i].text;
            var color = url[i].color;
            if (color) {
                color = (color % (this.pretty_colors.length-1))+1;
            }
            var span = document.createElement('SPAN');
            span.appendChild(document.createTextNode(text));
            span.style.color = this.pretty_colors[color];
            element.appendChild(span);
        }
    },

    show_tab: function(id) {
        for (i in this.tabs) {
            var tab = this.tabs[i];
            if (tab != id) {
                this.hide(tab);
                this.get_element(tab+'-link').className = 'unselected-tab';
            }
        }
        this.show(id);
        this.get_element(id+'-link').className = 'selected-tab';
    },

    on_click_start: function() {
        this.model.set_long_url(this.get_value('starting-long-url'));
        this.model.set_prefix('');
        this.model.set_delimiter('/');
        this.model.set_suffix('.html');
        this.hide('order-page');
        this.hide('done-page');
        this.hide('starting-page');
        this.update_all();
        this.show('wizard-page');
    },

    on_ajax_rotate: function() {
        if (this.ajax_current) {
            this.get_element('ajax-'+this.ajax_current).className = "";
        }
        this.ajax_current += 1;
        if (this.ajax_current > 4) {
            this.ajax_current = 0;
        }
        if (this.ajax_current) {
            this.get_element('ajax-'+this.ajax_current).className = "ajax-colored";
        }
    },

    on_click_order: function() {
        this.hide('done-page');
        this.hide('starting-page');
        this.hide('wizard-page');
        this.show('order-page');
        if (!this.ajax_running) {
            window.setInterval("controller.on_ajax_rotate()", 400);
            this.ajax_running = true;
        }
    },

    on_change_prefix: function() {
        this.model.set_prefix(this.get_value('prefix'));
        this.update_results();
    },

    on_change_delimiter: function(name) {
        this.model.set_delimiter(this.name_to_delimiter[name]);
        this.update_results();
    },

    on_change_suffix: function(name) {
        if (name in this.name_to_suffix) {
            this.model.set_suffix(this.name_to_suffix[name]);
        }
        else {
            this.model.set_suffix(this.get_value('custom-suffix'));
        }
        this.update_results();
    },

    on_change_custom_suffix: function() {
        this.set_radio('suffix', 'custom');
        this.model.set_suffix(this.get_value('custom-suffix'));
        this.update_results();
    },

    on_change_test_long_url: function() {
        var test_long_url = this.get_value('test-long-url');
        if (test_long_url) {
            var test_short_url = this.model.get_test_short_url(test_long_url);
            if (test_short_url) {
                this.hide('test-long-url-valid');
                this.hide('test-short-url-valid');
                this.set_value('test-short-url', test_short_url);
            }
            else {
                this.show('test-long-url-valid');
            }
        }
        else {
            this.hide('test-long-url-valid');
        }
    },

    on_change_test_short_url: function() {
        var test_short_url = this.get_value('test-short-url');
        if (test_short_url) {
            var test_long_url = this.model.get_test_long_url(test_short_url);
            if (test_long_url) {
                this.hide('test-short-url-valid');
                this.hide('test-long-url-valid');
                this.set_value('test-long-url', test_long_url);
            }
            else {
                this.show('test-short-url-valid');
            }
        }
        else {
            this.hide('test-short-url-valid');
        }
    },

    on_click_done: function() {
        this.hide('starting-page');
        this.hide('wizard-page');
        this.show('done-page');
    },

    on_click_back: function() {
        this.hide('starting-page');
        this.hide('done-page');
        this.show('wizard-page');
    },

    on_click_start_over: function() {
        this.hide('wizard-page');
        this.hide('done-page');
        this.show('starting-page');
    },

    on_click_parameter: function(index, name_present, value_present) {
        this.model.set_parameter(index-1, name_present, value_present);
        this.update_results();
    }

});

var Model = ClassFactory({

    init: function() {
        this.protocol = '';
        this.domain = '';
        this.path = '';
        this.parameters = [];
        this.prefix = '';
        this.delimiter = '';
        this.suffix = '';
    },

    set_long_url: function(long_url) {
        var match = long_url.match(/^(?:(https?):\/\/)?([0-9a-zA-Z-.]+)([^?]*)(?:\?(.*))?$/i);
        if (!match) {
            alert("Invalid URL");
            return;
        }
        this.protocol = match[1];
        if (!this.protocol) {
            this.protocol = 'http';
        }
        this.domain = match[2];
        this.path = match[3];
        if (!this.path) {
            this.path = '/';
        }
        var query = match[4];
        if (!query) {
            alert("Query is empty");
            return;
        }
        var pairs = query.split('&');
        this.parameters = [];
        for (i in pairs) {
            var items = pairs[i].split('=');
            if (items.length != 2) {
                alert("Invalid query parameter");
                return;
            }
            this.parameters.push({ 
                    name: items[0], name_present: false,
                    value: items[1], value_present: true
            });
        }
        return true;
    },

    set_prefix: function(prefix) {
        this.prefix = prefix;
    },

    set_delimiter: function(delimiter) {
        this.delimiter = delimiter;
    },

    set_suffix: function(suffix) {
        this.suffix = suffix;
    },

    set_parameter: function(index, name_present, value_present) {
        this.parameters[index].name_present = name_present;
        this.parameters[index].value_present = value_present;
    },

    filter: function(array, callback) {
        var result = [];
        for (i in array) {
            result.push(callback(array[i]));
        }
        return result;
    },

    get_long_url: function() {
        return this.protocol + '://' + this.domain + this.path + '?' +
            (this.filter(this.parameters, function (p) { return p.name+'='+p.value })).join('&');
    },

    get_present_parameters: function() {
        var present = [];
        for (i in this.parameters) {
            var parameter = this.parameters[i];
            if (parameter.name_present) {
                present.push(parameter.name);
            }
            if (parameter.value_present) {
                present.push(parameter.value);
            }
        }
        return present;
    },

    get_short_url: function() {
        return this.protocol + '://' + this.domain + '/' + this.prefix +
            (this.get_present_parameters()).join(this.delimiter) + this.suffix;
    },

    get_prefix: function() {
        return this.prefix;
    },

    get_delimiter: function() {
        return this.delimiter;
    },

    get_suffix: function() {
        return this.suffix;
    },

    get_parameters: function() {
        return this.parameters;
    },

    get_pretty_long_url: function() {
        var url = [];
        url.push({ 
            text: this.protocol + '://' + this.domain + this.path,
            color: 0
        });
        var current_color = 1;
        for (i in this.parameters) {
            var parameter = this.parameters[i];
            url.push({
                text: 1*i ? '&' : '?',
                color: 0
            });
            url.push({
                text: parameter.name,
                color: parameter.name_present ? current_color++ : 0
            });
            url.push({
                text: '=',
                color: 0
            });
            url.push({
                text: parameter.value,
                color: parameter.value_present ? current_color++ : 0
            });
        }
        return url;
    },

    get_pretty_short_url: function() {
        var url = [];
        url.push({ 
            text: this.protocol + '://' + this.domain + '/' + this.prefix,
            color: 0
        });
        var current_color = 1;
        var present_parameters = this.get_present_parameters();
        for (i in present_parameters) {
            var parameter = present_parameters[i];
            if (1*i) {
                url.push({
                    text: this.delimiter,
                    color: 0
                });
            }
            url.push({
                text: parameter,
                color: current_color++
            });
        }
        url.push({
            text: this.suffix,
            color: 0
        });
        return url;
    },

    get_test_long_url: function(short_url) {
        var pattern;
        var index = 0;
        var delimiter = '';
        pattern = '^' + this.escape_pattern(this.protocol+'://'+this.domain+'/'+this.prefix);
        for (var i in this.parameters) {
            var parameter = this.parameters[i];
            if (parameter.name_present) {
                pattern += delimiter + this.escape_pattern(parameter.name);
                delimiter = this.escape_pattern(this.delimiter)
            }
            if (parameter.value_present) {
                pattern += delimiter + '([^'+this.delimiter+']*)';
                delimiter = this.escape_pattern(this.delimiter)
            }
        }
        pattern += this.escape_pattern(this.suffix) + '$';
        expression = new RegExp(pattern);
        match = expression.exec(short_url);
        if (!match) {
            return;
        }
        index = 0;
        var long_url = this.protocol+'://'+this.domain+this.path;
        for (var i in this.parameters) {
            var parameter = this.parameters[i];
            long_url += (1*i ? '&' : '?') +parameter.name + '=';
            if (parameter.value_present) {
                long_url += match[++index];
            }
            else {
                long_url += parameter.value;
            }
        }
        return long_url;
    },

    get_test_short_url: function(long_url) {
        var pattern;
        var delimiter = '';
        pattern = '^' + this.escape_pattern(this.protocol+'://'+this.domain+this.path);
        for (var i in this.parameters) {
            var parameter = this.parameters[i];
            pattern += (1*i ? '&' : '\\?') +
                this.escape_pattern(parameter.name) + '=';
            if (parameter.value_present) {
                pattern += '([^&]*)';
            }
            else {
                pattern += this.escape_pattern(parameter.value);
            }
        }
        pattern += '$';
        expression = new RegExp(pattern);
        match = expression.exec(long_url);
        if (!match) {
            return;
        }
        var index = 0;
        var short_url = this.protocol+'://'+this.domain+'/'+this.prefix;
        for (var i in this.parameters) {
            var parameter = this.parameters[i];
            if (parameter.name_present) {
                short_url += delimiter + parameter.name;
                delimiter = this.delimiter;
            }
            if (parameter.value_present) {
                short_url += delimiter + match[++index];
                delimiter = this.delimiter;
            }
        }
        short_url += this.suffix;
        return short_url;
    },
    escape_pattern: function(str) {
        return str.replace(/[^a-zA-Z0-9_\/-]/g, '\\$&');
    },
    escape_destination: function(str) {
        return str.replace(/[$%\\ ]/g, '\\$&');
    },
    get_htaccess_rule: function() {
        var pattern;
        var destination;
        var index = 0;
        var delimiter = '';
        pattern = '^' + this.escape_pattern(this.prefix);
        destination = this.escape_destination(this.path);
        for (var i in this.parameters) {
            var parameter = this.parameters[i];
            if (parameter.name_present) {
                pattern += delimiter + this.escape_pattern(parameter.name);
                delimiter = this.escape_pattern(this.delimiter)
            }
            if (parameter.value_present) {
                pattern += delimiter + '([^'+this.delimiter+']*)';
                delimiter = this.escape_pattern(this.delimiter)
            }
            destination += (1*i ? '&' : '?') +
                this.escape_destination(parameter.name) + '=';
            if (parameter.value_present) {
                destination += '$'+(++index);
            }
            else {
                destination += this.escape_destination(parameter.value);
            }
        }
        pattern += this.escape_pattern(this.suffix) + '$';
        return 'RewriteEngine On\nRewriteRule '+pattern+' '+destination+' [L]\n';
    }

});

var controller = new Controller();


