/**
 * Author: Herve Menager
 * Organization:'Biological Software and Databases' Group, Institut Pasteur, Paris.
 * Distributed under GPLv2 Licence. Please refer to the COPYING.LIB document.
 */

// this piece of code lets you iterate synchronously/asynchronously with a function
// over a list
// stolen and extended from "http://blog.jcoglan.com/2010/08/30/the-potentially-asynchronous-loop/"
Array.prototype.asyncEach = function(iterator, callback) {
  var list    = this,
      n       = list.length,
      i       = -1,
      calls   = 0,
      looping = false;

  var iterate = function() {
    calls -= 1;
    i += 1;
    if (i === n){
      if (callback) callback();
      return;
    }
    iterator(list[i], resume);
  };

  var loop = function() {
    if (looping) return;
    looping = true;
    while (calls > 0) iterate();
    looping = false;
  };

  var resume = function() {
    calls += 1;
    if (typeof setTimeout === 'undefined') loop();
    else setTimeout(iterate, 1);
  };
  resume();
};

if (!("console" in window)){
    window.console = {};
}
var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
for (var i = 0; i < names.length; ++i){
    if(!(names[i] in console))
      window.console[names[i]] = function(){
    }
}

Effect.DefaultOptions.duration = 0.2;

Form.encodingMethods = {
  // these methods are a workaround to the limitations of IE6 (at least) which does not understand the enctype property

  setMultipartEncoding: function(form, options) {
    if (Prototype.Browser.IE) {
      form.encoding = 'multipart/form-data';
    }else{
      form.enctype = 'multipart/form-data';    
    }
  },

  setUrlEncoding: function(form, options) {
    if (Prototype.Browser.IE) {
      form.encoding = 'application/x-www-form-urlencoded';
    }else{
      form.enctype = 'application/x-www-form-urlencoded';    
    }
  }
  
}
Object.extend(Form, Form.encodingMethods);

/**
 * @class Feed is a class that downloads/updates JSON Data from a given URL
 *        and stores it.
 */
var Feed = Class.create({

    initialize: function(url){
        this.url = url;
        this.data = new Object();
        this._datastr = '';
        // kept for comparison sake
        document.observe('item:remove',
        function(e){
            if (e.memo.feed == this){
                this.get();
            }
        }
        .bind(this));
    },

    get: function(callback){
        updateProgressReports();
        $$('.refresh_link').invoke('addClassName','disabled');
        new Ajax.Request(this.url, {
            method: 'get',
            onSuccess: function(transport){
                this._process(transport);
                if (callback){
                    callback();
                }
                $$('.refresh_link').invoke('removeClassName','disabled');
            }
            .bind(this)
        });
    },

    _process: function(transport){
        if (this._datastr != transport.responseText){
            this._datastr = transport.responseText;
            var response = this._datastr.evalJSON();
            this.previousData = this.data;
            this.data = response;
            document.fire('feed:update/' + this.url, {});
        }
    },
    
    wait: function(function_to_call){
      // wait is used to wait for the next feed update, then execute the 
      // function_to_call, stop observing this event and leave.
      this.trigger = function(){ 
        // this.trigger is the reference to the 
        //function which is executed when the feed is updated
            document.stopObserving('feed:update/session_workspace.py',this.trigger);
            function_to_call();        
      }
        document.observe('feed:update/session_workspace.py',this.trigger);
    }
    
});

var updateProgressReports = function(){
  $$('.progressReport').each(
    function(el){
      new Ajax.Request(el.readAttribute('data-url'), 
        {
           method:"get",
           onSuccess: function(transport){
             $(el).value = transport.responseText;
           },
           onComplete: function(transport){
             this.scrollTop=999999;
           }.bind(el)
        }
      );
    }
  );  
};

/**
 * @class FeedView is a class that provides a view to a Feed object which is actually a list
 */
var FeedView = Class.create({

    data: null,
    property: null,
    feed: null,

    initialize: function(el){
        this.el = $(el);
        this.onUpdate();
        document.observe('feed:update/' + this.feed.url,
        function(e){
            this.onUpdate();
        }.bind(this));
    },

    onUpdate: function(){
        //this.el.setLoading();
        this.data = this.extract(this.feed.data);
        this.build();
        //this.el.unsetLoading();
    },

    check: function(){

        },

    build: function(){
        },

    extract: function(data){
        if (this.path){
            this.path.split('.').each(function(property){
                data = data[property];
            });
        }
        return data;
    }
});

/**
 * @class ListView
 */
var ListView = Class.create(FeedView, {

    data: $H(),
    filterFn: null,
    sortFn: null,
    htmlProperties: $A([]),

    extract: function($super, data){
        var data = $H($super(data));
        this.htmlProperties.each(function(prop){
            data.each(function(item){
                if (item.value[prop]){
                    item.value[prop] = item.value[prop].escapeHTML();
                };
            });
        });
        if (this.filterFn && data){
            data.each(function(pair){
                if (!this.filterFn(pair)){
                    data.unset(pair.key);
                }
            }
            .bind(this));
        }
        if (this.sortFn){
            data = data.sortBy(this.sortFn);
        }
        if (!data){
            return $H();
        }
        else{
            return data;
        }
    },

    check: function(){
        }

});

var LinksList = Class.create(ListView, {

    build: function(){
        // remove previous options
        this.el.immediateDescendants().invoke('remove');
        // add html list items
        if (this.data){
            this.data.each(function(item){
                li = new Element('li').update(new Element('a', {
                    'href': this.liHrefT.evaluate(item).gsub('/', '--'),
                    'title': this.liTitleT.evaluate(item),
                    'class': this.liClassT.evaluate(item)
                }).update(this.liTextT.evaluate(item)));
                this.el.appendChild(li);
            }
            .bind(this));
        }
    }

});

/**
 * @class BookmarkLinksList is a View of the list of files It displays the list of
 *        files in an HTML unordered list
 *
 * @extends LinksList
 */
var BookmarkLinksList = Class.create(LinksList, {

    liHrefT: new Template('#data::#{key}'),
    // to add support for bookmark tabs, add "::#{key}"
    liTitleT: new Template('#{value.title}'),
    liClassT: new Template('link'),
    liTextT: new Template('#{value.bioTypes} #{value.dataType}: #{value.userName}'),
    path: 'data',

    sortFn: function(data){
        return data.value.dataType + data.value.userName;
    }


});

/**
 * @class JobLinksList is a View of the list of jobs It displays the list of
 *        jobs in an HTML unordered list
 *
 * @extends LinksList
 */
var JobLinksList = Class.create(LinksList, {
    filterFn: function(pair){
      return !pair.value.owner;
    },
    liHrefT: new Template('#jobs::#{key}'),
    liTitleT: new Template('status: #{value.status}'),
    liClassT: new Template('link jobstatus #{value.status}'),
    liTextT: new Template('#{value.programName} - #{value.jobDate}'),
    path: 'jobs',

    build: function($super){
      // add the remote server name to the displayed job name if not local
      if (this.data){
        this.data.each(function(item){
          if(item.value.server!='local'){
            item.value.programName+='@'+item.value.server;
          }
         });
      }
      // then proceed...
      $super();
    },

    sortFn: function(job){
        return job.value.jobSortDate;
    }

});

/**
 * @class DataTable is a View of the Data class It displays
 *        filtered elements of a list as an HTML table It is the equivalent to a
 *        View in a classical MVC
 *
 */
var DataTable = Class.create(ListView, {
    build: function(){
        // remove previous options
        this.el.childElements().invoke('remove');
        // table headers
        var tr = document.createElement("tr");
        this.columns.each(function(column){
            var th = document.createElement("th");
            th.innerHTML = column.key;
            tr.appendChild(th);
        }
        .bind(this));
        this.el.appendChild(tr);
        // table body (data)
        if (this.data){
            this.data.each(function(item){
                var tr = document.createElement("tr");
                this.columns.each(function(column){
                    var td = document.createElement("td");
                    td.innerHTML = column.value.evaluate(item);
                    tr.appendChild(td);
                }
                .bind(this));
                this.el.appendChild(tr);
            }
            .bind(this));
        }
    }

});

var checkAllCheckBoxes = function(selector,value){
  $$(selector).each(function(el){el.checked=value;});
}

/**
 * @class BookmarksTable is a view of the list of files stored in a
 *        MobyleSession. Additionally, it provides control (removal, renaming)
 *        over these data
 * @extends DataTable
 */
var BookmarksTable = Class.create(DataTable, {

    filter: null,
    columns: $H({
        'Name': new Template('<a class="link" href="#data::#{key}">#{value.userName}</a>'),
        'Type': new Template('#{value.bioTypes} #{value.dataType}'),
        'Format': new Template('#{value.format}'),
        'Size': new Template('#{value.size} KiB'),
        'Text': new Template('#{value.title}'),
        '<label><input name="remove_all" type="checkbox" onclick="checkAllCheckBoxes(\'.data_item\',this.checked);" />select all/none</label>': new Template('<input type="checkbox" class="data_item" name="id" value="#{key}" />')
    }),
    path: 'data',
    htmlProperties: $A(['title']),

    initialize: function($super, el){
        $super(el);
        this.el.observe('submit',
        function(e){
            e.stop();
            new Ajax.Request(e.element().action, {
                method: 'post',
                postBody: e.element().serialize(),
                onCreate: e.element().disable.bind(e.element()),
                onComplete: function(){
                    document.fire('item:remove', {
                        feed: user.workspace
                    })
                }
            });
        });
    },
    
    sortFn: function(data){
        return data.value.dataType + data.value.userName;
    }

});

/**
 * @class JobsTable is a view of the list of jobs stored in a
 *        MobyleSession. Additionally, it provides control (removal)
 *        over these jobs
 * @extends DataTable
 */
var JobsTable = Class.create(DataTable, {

    filterFn: function(pair){
      return !pair.value.owner;
    },
    columns: $H({
        'Name': new Template('<a class="link" href="#jobs::#{key}">#{value.programName}</a>'),
        'Identifier': new Template('<a class="link" href="#jobs::#{key}">#{value.simple_id}</a>'),
        'Date': new Template('<a class="link" href="#jobs::#{key}">#{value.jobDate}</a>'),
        'Status': new Template('<span class="jobstatus #{value.status}">#{value.status}</span>'),
        'Message': new Template('#{value.status_message}'),
        '<label><input name="remove_all" type="checkbox" onclick="checkAllCheckBoxes(\'.job_item\',this.checked);" />select all/none</label>': new Template('<input type="checkbox" class="job_item" name="id" value="#{key}" />')
    }),
    path: 'jobs',
    htmlProperties: $A(['title']),

    initialize: function($super, el){
        $super(el);
        this.el.observe('submit',
        function(e){
            e.stop();
            new Ajax.Request(e.element().action, {
                method: 'post',
                postBody: e.element().serialize(),
                onCreate: e.element().disable.bind(e.element()),
                onComplete: function(){
                    document.fire('item:remove', {
                        feed: user.workspace
                    })
                }
            });
        });
        this.build = this.build.wrap(function(callOriginal){
                        if (this.data){
                          this.data.each(function(item){
                            item.value.simple_id = item.value.jobID.split('.').last();
                          });
                        }
                        callOriginal();
                     }.bind(this));
        $('sortJobsBy').observe('change',function(){
          this.onUpdate();
        }.bind(this));
    },

    sortFn: function(job){
        return job.value[$('sortJobsBy').value];
    }

});


Data = {};

Data.View = Class.create({

    view_id: 0,

    initialize: function(source, url, extract, path){
        this.view_id = Data.View.view_id++;          
        this.source = source;
        this.url = url;
        this.extract = extract;
        this.path = path;
        this.build();
        if (this.url){
            document.observe('feed:update/' + this.url, this.build.bindAsEventListener(this));
        }
    },

    build: function(e){
        this.data = this.source;
        if (this.path){
            this.path.split('.').each(function(property){
                this.data = this.data[property];
            }.bind(this));
        }
        this.data = this.extract(this.data);
        if (this.url){
            document.fire('view:build/' + this.url + '/' + this.view_id);
        }
    }
});

Data.extract = function(property, filter, sort){
    return function(data){
        if (property){
            property.split('.').each(function(pitem){
                data = data[pitem];
            }.bind(this));
        }
        data = $H(data);
        if (filter){
            data.each(function(pair){
                if (!filter(pair)){
                    data.unset(pair.key);
                }
            });
        }
        data = sort ? data.sortBy(sort) : data;
        return data ? data: $H();
    }
}

Builder.dataSelect = function(view, tpls, attrs){
    var sel = Builder.node('select', attrs);
    sel.selectedIndex = -1;
    var build = function(){
        // backing up current selected entry before rebuilding it
        var selEntryBackup = sel.selectedIndex && sel.selectedIndex != -1 ? sel.options[sel.selectedIndex] : null;
        // disable and hide control if no data is to be shown
        sel.style.visibility = view.data.size() == 0 ? 'hidden': 'visible';
        // remove previous options
        sel.immediateDescendants().invoke('remove');
        // default blank value
        sel.insert(Builder.node("option", {'selected': 'selected'}));
        // other options
        view.data.each(function(item){
            sel.insert({
                'bottom': Builder.node("option", {
                    'value': tpls['value'].evaluate(item),
                    'title': tpls['title'].evaluate(item)
                },
                tpls['text'].evaluate(item))
            });
        });
        if (selEntryBackup){
            $A(sel.options).each(function(opt){
                if (opt.value == selEntryBackup.value){
                    opt.selected = true;
                }
            }.bind(this));
        }else{
            sel.selectedIndex = -1;
        }
        sel.fire('component:build');
    }
    build();
    if (view.url){
        document.observe('view:build/' + view.url + '/' + view.view_id,
        function(e){
            build();
        }.bind(this));
    }
    return sel;
};

Builder.list = function(view, root, buildItemFn){
    var build = function(){
        root.immediateDescendants().invoke('remove');
        view.data.each(function(item){
            root.insert(buildItemFn(item));
        });
        root.fire('component:build');
    }
    build();
    if (view.url){
        document.observe('view:build/' + view.url,
        function(e){
            build();
        }.bind(this));
    }
    return root;
};

Builder.historySelect = function(pdef, userMode, control){
    var getFilter = function(){
        return function(bookmark){
            var bookmark = bookmark.value;
            if (filterPipe(bookmark.dataType, bookmark.format, bookmark.bioTypes, pdef.datatype, pdef.formats, pdef.biotype, true)){
                return bookmark.userModes.include(userMode);
            }
            return false;
        }
    }
    var sb = Builder.node('input', {
        type: 'button',
        value: 'select'
    });
    var view = new Data.View(user, user.workspace.url, Data.extract('workspace.data.data', getFilter()));
    var ds = Builder.dataSelect(view, {
        'value': new Template('#{key}'),
        'title': new Template('#{value.userName} first line: #{value.title}'),
        'text': new Template('#{value.userName}')
    },
    {
        id: pdef.program + '::' + pdef.name + '::' + userMode + '::history'
    });
    var el = Builder.node('span', [ds, sb]);
    var build = function(){
        sb.style.visibility = ds.style.visibility;
    };
    build();
    ds.observe('component:build', build);
    var load = function(){
        if (ds.value != ''){
            // call to history
            new Ajax.Request('data_get.py', {
                method: 'get',
                parameters: {
                    'id': ds.value
                },
                onSuccess: function(r){
                    pdef.json_load(control, r.responseText);
                },
                onComplete: function(){
                    ds.selectedIndex = 0;
                    ds.enable();
                }
            });
        }
    }
    sb.observe('click', load);
    sb.observe('databox:history_load', load);
    return el;
}

var filterPipe = function(sourceDatatype, sourceFormat, sourceBiotypes, destDatatype, destFormats, destBiotypes, allowConversions){
    var sourceDatatypeHierarchy = portal.properties.datatypes[sourceDatatype];
    if (sourceDatatypeHierarchy && sourceDatatypeHierarchy.ancestorTypes.include(destDatatype)){
        if (sourceBiotypes == null || destBiotypes == null ||
        sourceBiotypes.size() == 0 || destBiotypes.size() == 0 ||
        sourceBiotypes.any(function(sourceBiotype){
            return destBiotypes.include(sourceBiotype)
        })
        ){
            if (destFormats.include(sourceFormat) || destFormats.size() == 0 || sourceFormat == null){
                // formats identical
                return true;
            } else if (allowConversions && portal.properties.conversions.any(function(conversion){
                return sourceDatatypeHierarchy.ancestorTypes.include(conversion.dataType) &&
                conversion.fromFormat == sourceFormat && destFormats.include(conversion.toFormat);
            })){
                // formats compatible using converters
                return true;
            }
        }
    }
    // any incompatibility case
    return false;
}

Builder.pipesSelect = function(datatype, biotypes, format, insertElement){
    var getFilter = function(){
        return function(input){
            var input = input.value;
            return filterPipe(datatype, format, biotypes, input.dataTypeClass, input.dataTypeFormats, input.bioTypes, true);
        }
    }
    var sb = Builder.node('input', {
        type: 'submit',
        value: 'further analysis'
    });
    var sort = function(item){
        return item.value.programName.toLowerCase() + item.value.name.toLowerCase();      
    }
    var view = new Data.View(portal.properties, null, Data.extract(null, getFilter(), sort), 'program_workflow_inputs');
    var ds = Builder.dataSelect(view, {
        'value': new Template('#{value.programPID}|#{value.name}'),
        'title': new Template('#{value.programTitle} - #{value.prompt}'),
        'text': new Template('#{value.programName} (#{value.name})')
    },
    {
        name: 'next',
        style: 'width: 15em;'
    });
    var el = Builder.node('span', [insertElement, ds, sb]);
    var build = function(){
        el.style.visibility = ds.style.visibility;
    };
    build();
    ds.observe('component:build', build);
    return el;
}

Builder.uploadClient = function(pdef, control){
    var file = Builder.node('input', {
        type: 'file',
        name: 'data_input_upload'
    });
    var datatype = Builder.node('input', {
        type: 'hidden',
        name: 'datatype_class',
        value: pdef.datatype,
        className: 'protected'
    });
    var datatype_superclass = Builder.node('input', {
        type: 'hidden',
        name: 'datatype_superclass',
        value: pdef.datatypeSuperClass,
        className: 'protected'
    });
    var target = $('data_upload_target');
    var el = Builder.node('span', {
        className: 'upload'
    },
    [file, datatype, datatype_superclass]);
    var onLoaded = function(){
        file.value = '';
        var iframeDoc;
        if (window.frames && window.frames.data_upload_target &&
        (iframeDoc = window.frames.data_upload_target.document)){
            var iframeBody = iframeDoc.body;
            var txt = iframeBody.innerText ? iframeBody.innerText: iframeBody.textContent;
        }
        pdef.json_load(control, txt);
        target.stopObserving('load');
    }
    var upload = function(){
        var form = el.up('form');
        disList = form.select('select', 'input', 'textarea').filter(function(el){
            return ! el.disabled;
        });
        disList.invoke('disable');
        file.enable();
        datatype.enable();
        datatype_superclass.enable();
        target.observe('load', onLoaded);
        var actionBck = form.getAttribute('action');
        var targetBck = form.getAttribute('target');
        var methodBck = form.getAttribute('method');
        form.setAttribute('action', 'data_upload.py');
        form.setAttribute('target', target.identify());
        form.setAttribute('method', 'POST');
        Form.setMultipartEncoding(form);
        form.submit();
        form.setAttribute('action', actionBck);
        disList.invoke('enable');
        delete form['target'];
        form.setAttribute('method', methodBck);
        Form.setUrlEncoding(form);
    }
    file.observe('change', upload);
    return el;
}

Builder.dbSelect = function(pdef, control){
    var getFilter = function(){
        return function(bank){
            bank = bank.value;
            if (bank.dataType == pdef.datatype){
                // datatype filter
                if (pdef.biotype == null ||
                bank.bioTypes == null ||
                // biotype(s) filter
                pdef.biotype.blank &&
                pdef.biotype.blank() ||
                bank.bioTypes.blank &&
                bank.bioTypes.blank() ||
                pdef.biotype.size &&
                pdef.biotype.size() == 0 ||
                bank.bioTypes.size &&
                bank.bioTypes.size() == 0 ||
                bank.bioTypes.include(pdef.biotype) ||
                pdef.biotype.findAll &&
                pdef.biotype.findAll(function(bt){
                    if (bank.bioTypes.include(bt))
                    return bt;
                }).size() >
                0){
                    return true;
                }
            }
            return false;
        }
    }
    var id = Builder.node('input', {
        type: 'text'
    });
    var sb = Builder.node('input', {
        type: 'button',
        value: 'select'
    });
    var view = new Data.View(portal.properties, null, Data.extract(null, getFilter()), 'banks');
    var ds = Builder.dataSelect(view, {
        'value': new Template('#{key}'),
        'title': new Template('#{value.label}'),
        'text': new Template('#{key}')
    },
    {});
    var el = Builder.node('span', [ds, id, sb]);
    var build = function(){
        id.style.visibility = ds.style.visibility;
        sb.style.visibility = ds.style.visibility;
    };
    build();
    ds.observe('component:build', build);
    sb.observe('click',
    function(e){
        if (ds.value != '' && id.value != ''){
            portal.startWaiting();
            // call to history
            new Ajax.Request('bank_get.py', {
                method: 'get',
                parameters: {
                    'db': ds.value,
                    'id': id.value
                },
                onSuccess: function(r){
                    pdef.json_load(control, r.responseText);
                },
                onComplete: function(){
                    portal.stopWaiting();
                    ds.selectedIndex = -1;
                    id.value = '';
                    ds.enable();
                }
            });
        }
    });
    return el;
}

/**
 * @class DataSelect is a View of the Data class It displays
 *        filtered elements of a list as an HTML select input It is the
 *        equivalent to a View in a classical MVC
 *
 */
var DataSelect = Class.create(ListView, {

    cpEls: [],

    build: function(){
        // backing up current selected entry before rebuilding it
        if (this.el.selectedIndex && this.el.selectedIndex != -1){
            var selEntryBackup = this.el.options[this.el.selectedIndex];
        }
        // set visibility for select, label and button
        if (this.data.size() == 0){
            this.el.disable();
            this.cpEls.each(function(el){
                el = $(el);
                el.hide();
                Form.Element.disable(el);
            });
        }
        else{
            this.el.enable();
            this.cpEls.each(function(el){
                el = $(el);
                el.show();
                Form.Element.enable(el);
            });
        }
        // remove previous options
        this.el.immediateDescendants().invoke('remove');
        // default (blank) option
        this.el.insert({
            'bottom': Builder.node("option", {
                'value': ''
            },
            "< >")
        });
        // add html options
        this.data.each(function(item){
            this.el.insert({
                'bottom': Builder.node("option", {
                    'value': this.optionValue.evaluate(item),
                    'title': this.optionTitle.evaluate(item)
                },
                this.optionText.evaluate(item))
            });
        }
        .bind(this));
        if (selEntryBackup && !selEntryBackup.value.blank()){
            // only restore an entry if one had been selected...
            this.selectEntry(selEntryBackup.value, true);
        }
    },

    selectEntry: function(value, silentChange){
        $A(this.el.options).each(function(option){
            if (option.value == value){
                option.selected = true;
                if (!silentChange){
                    if (this.el && this.el.onchange){
                        this.el.onchange();
                    }
                    if (this.submitEl && this.submitEl.onclick){
                        this.submitEl.onclick();
                    }
                }
            }
        }
        .bind(this));
    }

});

var FilesGauge = Class.create(ListView, {

    path: 'data',

    initialize: function($super, el){
        this.gaugeSize = parseFloat($('sessionLimit').value);
        $super(el);
    },

    build: function(){
        // loading the data from the model
        this.level = 0.0;
        if (this.data){
            this.data.each(function(item){
                this.level = this.level + parseFloat(item.value.size);
            }
            .bind(this));
        }
        this.usage = this.level / this.gaugeSize * 100;
        this.el.style.width = this.usage + "%";
    }

});

var User = Class.create({

    initialize: function(){
        this.workspace = new Feed('session_workspace.py');
        this.updateFromCookie();
    },

    setEmail: function(email){
        editCookieValue('email', email);
        this.updateFromCookie();
    },

    updateFromCookie: function(){
        if (document.cookie.toString() == ''){
            $('nocookie').show();
        }
        else{
            $('nocookie').hide();
        }
        var sessionKey = readCookieValue("sessionKey");
        if (sessionKey != this.sessionKey){
            this.sessionKey = sessionKey;
            document.fire('user:sessionKey');
            this.workspace.get();
            if (typeof _gaq != 'undefined'){ // GA set session key
              _gaq.push(['_setCustomVar',
                    1,                   
                    'session_key',
                    this.sessionKey,
                    3
                 ]);
            }
        }
        var email = readCookieValue("email");
        if (email != this.email){
            this.email = email;
            document.fire('user:email');
            $("userEmail").innerHTML = this.email;
            if (typeof _gaq != 'undefined'){ // GA set e-mail
              _gaq.push(['_setCustomVar',
                    2,                   
                    'e-mail',
                    this.email,
                    3
                 ]);
            }
        }
        var activated = (readCookieValue("activated") == "True");
        if (activated != this.activated){
            this.activated = activated;
            document.fire('user:activated');
            if (!this.activated){
                $$("[href='#user::activate']").each(function(aEl){aEl.up().show();});
                $("userEmail").addClassName('inactive');
                $("userStatus").addClassName('inactive');
            }
            else{
                $$("[href='#user::activate']").each(function(aEl){aEl.up().hide();});
                $("userEmail").removeClassName('inactive');
                $("userStatus").removeClassName('inactive');
            }
            if (typeof _gaq != 'undefined'){ // GA set session activation state
              _gaq.push(['_setCustomVar',
                    3,                   
                    'session_activation_state',
                    this.activated,
                    3
                 ]);
            }
        }
        var authenticated = (readCookieValue("authenticated") == "True");
        if (authenticated != this.authenticated){
            this.authenticated = authenticated;
            document.fire('user:authenticated');
            if (this.authenticated){
                $$("[href='#user::signin']").each(function(aEl){aEl.up().hide();});
                $("userStatus").innerHTML = '(registered)';
                if ($("userOpenIdsignInOpen")){
                    $("userOpenIdsignInOpen").up().hide();
                }
            }
            else{
                $$("[href='#user::signin']").each(function(aEl){aEl.up().show();});
                $("userStatus").innerHTML = options['anonymousSession'] ? '(guest)': '(register to submit)';
                if ($("userOpenIdsignInOpen")){
                    $("userOpenIdsignInOpen").up().show();
                }
            }
            if (typeof _gaq != 'undefined'){ // GA set session type
              _gaq.push(['_setCustomVar',
                    4,                   
                    'session_type',
                    this.authenticated ? 'registered' : 'guest',
                    3
                 ]);
            }
        }
    }

});

var methods = $H({
    'forms': new Template('form.py?id=#{id}'),
    'jobs': new Template('job_view.py?id=#{id}'),
    'data': new Template('data_view.py?id=#{id}'),
    'tutorials': new Template('#{id}'),
    'viewer': new Template('viewer_view.py?#{parameters}')
});

// list of non-databox types
var simpleDatatypes = ['Choice', 'MultipleChoice', 'Boolean', 'Integer', 'Float', 'String', 'Filename'];

var Databox = Class.create({

    initialize: function(paramEl){
        this.paramEl = paramEl;
        this.form = paramEl.up('form');
        this.name = paramEl.readAttribute('data-parametername');
        this.program = this.form.readAttribute('id');
        this.datatype = paramEl.readAttribute('data-datatype');
        this.datatypeSuperClass = paramEl.readAttribute('data-datatype-superclass');
        this.biotype = paramEl.readAttribute('data-biotype').strip().split(' ').without('');
        this.formats = paramEl.readAttribute('data-formats').strip().split(' ').without('');
        this.control = this.form[this.name];
        this.input = paramEl.select('[name="' + this.name + '"]')[0];
        this.databoxType = this.input.type && this.input.type == 'file' ? 'binary': 'text';
        if (this.databoxType == 'binary'){
            this.input.type = 'text';
        }
        var ip = this.input.clone();
        ip.setAttribute('class', 'databox_paste');
        ip.observe('change',onFormParameterModification);
        var id = this.input.clone();
        //id.disable();
        id.setAttribute('readonly','readonly');
        id.setAttribute('class', 'databox_db');
        var iu = this.input.clone();
        iu.disable();
        //iu.setAttribute('readonly','readonly');
        iu.setAttribute('class', 'databox_upload');
        var ir = this.input.clone();
        ir.disable();
        //ir.setAttribute('readonly','readonly');
        ir.setAttribute('class', 'databox_result');
        var irp = Builder.node('input', {
            type: 'hidden',
            className: 'bookmark_ref databox_paste',
            name: this.name + '.ref'
        });
        irp.store('databox', this);
        var ird = irp.clone();
        ird.setAttribute('class', 'bookmark_ref databox_db');
        ird.store('databox', this);
        var iru = irp.clone();
        iru.setAttribute('class', 'bookmark_ref databox_upload');
        iru.store('databox', this);
        var irr = irp.clone();
        irr.setAttribute('class', 'bookmark_ref databox_result');
        irr.store('databox', this);
        var inp = Builder.node('input', {
            type: 'hidden',
            name: this.name + '.name'
        });
        var ind = inp.clone();
        var inu = inp.clone();
        var inr = inp.clone();
        var hsp = Builder.historySelect(this, 'paste', {
            'ref': irp,
            'value': ip,
            'name': inp
        });
        var hsd = Builder.historySelect(this, 'db', {
            'ref': ird,
            'value': id,
            'name': ind
        });
        var hsu = Builder.historySelect(this, 'upload', {
            'ref': iru,
            'value': iu,
            'name': inu
        });
        var hsr = Builder.historySelect(this, 'result', {
            'ref': irr,
            'value': ir,
            'name': inr
        });
        var dbs = Builder.dbSelect(this, {
            'ref': ird,
            'value': id,
            'name': ind
        });
        var upu = Builder.uploadClient(this, {
            'ref': iru,
            'value': iu,
            'name': inu
        });
        var pastePanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', ['Enter your data below:']), Builder.node('td', {
            },
        hsp)])])), ip, irp, inp]);
        var dbPanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', dbs), Builder.node('td', {
            },
        hsd)])])), id, ird, ind]);
        var uploadPanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', upu), Builder.node('td', {
            },
        hsu)])])), iu, iru, inu]);
        var resultPanel = Builder.node('div', [Builder.node('table', {
            style: 'width: 100%;'
        },
        Builder.node('tbody', [Builder.node('tr', [Builder.node('td', {
            },
        hsr)])])), ir, irr, inr]);
        var choices = $H({
            'paste': pastePanel,
            'db': dbPanel,
            'upload': uploadPanel,
            'result': resultPanel
        });
        var def = 'paste';
        if (this.databoxType == 'binary'){
            def = 'upload';
            choices.unset('paste');
        }
        var pairs = $H(choices).collect(function(choice){
            var inputModeAttrs = {
                    type: 'radio',
                    name: this.name + '.mode',
                    value: choice.key
              };
            if(choice.key == def){
              inputModeAttrs.checked = true;
            }
            var pair = {
                'choice': Builder.node('li', 
                            {className: 'menu ' + (choice.key == def ? 'selected': '')},
                            Builder.node('label', [choice.key, Builder.node('input', inputModeAttrs)])),
                'content': Builder.node('div', {
                             style: choice.key == def ? 'display:visible': 'display:none'
                             },
                           choice.value)
            };
            pair.choice.writeAttribute('data-choicefor', pair.content.identify());
            var build = function(){
                // "build" disables a choice if it contains only invisible or disabled inputs
                if (pair.content.select('input', 'select', 'textarea').detect(function(el){
                    return ((el.style.visibility == 'visible' || !el.style.visibility) && !el.disabled && !el.readOnly && !(el.type == 'hidden'));
                })){
                    pair.choice.show();
                }
                else{
                    pair.choice.hide();
                }
            };
            build();
            pair.content.observe('component:build', build);
            return pair;
        }.bind(this));
        if (this.databoxType == 'binary'){
            this.ebtn = '';
        } else{
            this.ebtn = Builder.node('button', 'EDIT');
            // edit button
            this.ebtn.observe('click',
            function(e){
                var value = this.el.select('[name="' + this.name + '"]').pluck('value').join('');
                pairs[0].choice.fire('databox:switch')
                ip.value = value;
            }.bind(this));
        }
        var cbtn = Builder.node('button', 'CLEAR');
        // clear button
        cbtn.observe('click',
        function(e){
            this.clear();
            e.stop();
        }.bind(this));
        var handles = Builder.node('ul', {
            className: 'handlesList'
        },
        [pairs.pluck('choice'), Builder.node('li', {
            className: 'ctrl menu'
        },
        [this.ebtn, cbtn])]);
        handles.observe('click', this.choose.bind(this));
        handles.observe('databox:switch', this.choose.bind(this));
        var panels = Builder.node('div', {
            className: 'panelsList'
        },
        pairs.pluck('content'));
        this.el = Builder.node('div', [handles, panels]);
    },

    choose: function(e){
        var h = e.findElement().hasAttribute('data-choicefor') ? e.findElement() : e.findElement().up('li[data-choicefor]');
        if (h){
            var p = $(h.readAttribute('data-choicefor'));
            // "choose" shows a choice and hides and resets values of all the others
            h.down('input').checked = 'true';
            h.addClassName('selected');
            h.siblings().invoke('removeClassName', 'selected');
            p.show();
            p.siblings().invoke('hide');
            this.clear();
        }
        e.stop();
    },

    clear: function(){
        var els = this.el.select('li[data-choicefor]');
        var targets = els.collect(function(el){return $(el.readAttribute('data-choicefor'));});
        targets.invoke('select', 'select', 'input', 'textarea').flatten().each(function(input){
            if (!input.hasClassName('protected')){
                input.value = input.defaultValue;
            }
        });
        targets.invoke('select', 'input[type="hidden"]').flatten().each(function(input){
            if (!input.hasClassName('protected')){
                input.value = '';
                //hidden inputs do not have a significant default value, we assume it is blank...
            }
        });
        if ($(this.ebtn)){
          this.ebtn.disabled = false;
        }      
        this.paramEl.removeClassName('modified');
    },

    json_load: function(target, responseText){
        target.value.up('div').removeAllMessages();
        var txt = responseText.strip();
        if (txt != ''){
            if (txt.isJSON()){
                user.workspace.get();
                response = txt.evalJSON();
                if (response.content != null){
                    if ($(this.ebtn) && response.headFlag=='HEAD'){
                      this.ebtn.disabled = true;
                    }else{
                      this.ebtn.disabled = false;
                    }
                    if (this.databoxType == 'binary'){
                      target.value.value = response.userName;
                    } else{
                      target.value.value = response.content;
                    }
                    target && response.safeFileName ? target.ref.value = response.safeFileName: null;
                    target && response.userName ? target.name.value = response.userName: null;
                    this.paramEl.addClassName('modified');
                    // fire event that tells "simple form display" may have to adapt
                    this.paramEl.fire('form:parameter_load');
                }
                if (response.errormsg){
                    //target.ref.retrieve('databox').el.setMessage(response.errormsg);
                    target.value.up('div').setMessage(response.errormsg);
                }
            }
            else{
                target.value.up('div').setMessage('request failed, please check your data');
            }
        }
    }

});

var Filebox = Class.create({

    initialize: function(dataEl, options){
        this.options = options ? options : {
                'position': 'after',
                'components': ['FullScreen']
            };
        this.dataEl = dataEl;
        this.metaEl = dataEl.up('[data-datatype]') || this.dataEl;
        this.name = this.metaEl.readAttribute('data-parametername');
        this.datatype = this.metaEl.readAttribute('data-datatype');
        this.datatypeSuperclass = this.metaEl.readAttribute('data-datatype-superclass');
        this.biotypes = this.metaEl.readAttribute('data-biotype').split(' ').without('');
        this.format = this.metaEl.readAttribute('data-format').split(' ');
        this.inputModes = this.metaEl.readAttribute('data-inputmodes').split(' ');
        this.src = this.dataEl.readAttribute('data-src');
        this.fileName = this.dataEl.readAttribute('data-filename');
        this.components = $A();
        // components requested in options
        this.options.components.each(
          function(componentName){
            this.components.push(this['build'+componentName]());
          }.bind(this));
        var iD = $H();
        iD.set(this.options.position, Builder.node('div', {
            className: 'controlbox'
        },
        [this.components]));
        this.dataEl.insert(iD.toObject());
    },

    buildFullScreen: function(){
      // return full screen button
      return Builder.node('button',{onclick: 'window.open("'+this.src+'","_blank")'}, ['full screen']);
    },
    
    buildWidgets: function(){
      // return the list of suitable widgets (viewers)
        var dataSrc = this.src;
        var view = new Data.View(portal.properties, null, Data.extract('viewer_inputs', this.pipeFilter.bind(this)));
        return Builder.list(view, Builder.node('span'),
          function(item){
              var vHref = '#viewer::program=' + item.value.programPID + '&parameter=' + item.value.name + '|' + dataSrc;
              return Builder.node('a', {
                  href: vHref,
                  className: 'modalLink',
                  'data-viewername': item.value.programPID
              },
              [Builder.node('button', [item.value.programName])])
          });        
    },
    
    buildBookmarkPipeCommon: function(){
      // return bookmark and pipe common elements button
      var r = [Builder.node('input', {type: 'hidden',name: 'id',value: this.fileName
        }), Builder.node('input', {type: 'hidden',name: 'inputMode',value: 'result'
            // first input mode available will be selected...
        }), Builder.node('input', {type: 'hidden',name: 'parameter',value: this.name
        }), Builder.node('input', {type: 'hidden',name: 'datatype_class',value: this.datatype
        }), Builder.node('input', {type: 'hidden',name: 'datatype_superclass',value: this.datatypeSuperclass
        }),
        this.biotypes.collect(function(str){
          return Builder.node('input', {
              type: 'hidden',
              name: 'biotype',
              value: str
          })
        }),
        this.format.collect(function(str){
          return Builder.node('input', {
              type: 'hidden',
              name: 'format',
              value: str
          })
        })];
      var jobEl = this.dataEl.up('.job');
      if(jobEl){
          var jobid = jobEl.readAttribute('data-jobid');        
        r.push(Builder.node('input', {type: 'hidden',name: 'job',value: jobid}));
      }
      return r;
    },    

    buildBookmark: function(){
      // return the bookmark form
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        return Builder.node('form', {
                action: 'data_bookmark.py',
                className: 'bookmark'
            },
            [this.buildBookmarkPipeCommon(),
            Builder.node('input', {
                type: 'submit',
                value: 'bookmark'
            }), ' as ', Builder.node('input', {
                type: 'text',
                name: 'userName',
                size: '8',
                value: this.fileName
            })]);
    },

    buildPipes: function(){
      // return the pipes selection form    
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        return Builder.node('form', {
                className: 'bookmark further'
            },
            [this.buildBookmarkPipeCommon(),
             this.pipesSelect]);
    },    
    
    buildBookmarkPlusPipes: function(){
      // return the bookmark and/or pipes selection controls in a single form 
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        return Builder.node('form', {
                action: 'data_bookmark.py',
                className: 'bookmark further'
            },
            [this.buildBookmarkPipeCommon(),
            Builder.node('input', {
                type: 'submit',
                value: 'bookmark'
            }), ' as ', Builder.node('input', {
                type: 'text',
                name: 'userName',
                size: '8',
                value: this.fileName
            }),' or ',
            this.pipesSelect]);
    },

    buildWidgetBookmarkPlusPipes: function(){
      var userFileName = this.fileName;
      if (this.metaEl.readAttribute('data-extension')){
        userFileName += '.' + this.metaEl.readAttribute('data-extension');
      }
      // return the bookmark and/or pipes selection controls in a single form 
        this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
        var bn = Builder.node('form', {
                className: 'bookmark further',
                action:'data_upload.py'
            },
            [
            Builder.node('em',[this.metaEl.readAttribute('data-prompt')]),
            Builder.node('textarea',{'style':'display: none;','name':'data_input_upload'}),
            this.buildBookmarkPipeCommon(),
            Builder.node('input', {
                type: 'submit',
                value: 'bookmark'
            }), Builder.node('input', {
                type: 'hidden',
                name: 'base64encoded',
                value: this.metaEl.readAttribute('data-base64encoded')
            }), ' as ', Builder.node('input', {
                type: 'text',
                name: 'data_input_upload.name',
                size: '8',
                value: userFileName
            }),' or ',
            this.pipesSelect]);
        bn.onOk = function(data){
          var msgText = "Your data ("+data.userName+") is now accessible <a href='#data::"+data.safeFileName+"'><strong>here</strong></a>";
          bn.insert({'before': msgText});
        }
        bn.observe('submit',
          function(){
            bn.data_input_upload.value = eval(this.metaEl.readAttribute('data-portcode'));
          }.bindAsEventListener(this));
        return bn;
    },

    buildRemoveBookmark: function(){
      // return the remove bookmark button
      return Builder.node('a',{href:'#user::bookmarkremove::'+this.fileName,className:'modalLink'},
                Builder.node('button',{type:'button'},['remove bookmark'])
              );
    },
    
    buildRenameBookmark: function(){
      // return the rename bookmark ??
    },

    build: function(){
        //this.pipesSelect = Builder.pipesSelect(this.datatype, this.biotypes, this.format, this.insertElement);
    },

    pipeFilter: function(input){
        var input = input.value;
        return filterPipe(this.datatype, this.format, this.biotypes, input.dataTypeClass, input.dataTypeFormats, input.bioTypes, false);
    }

});

var JobStatus = Class.create({

    initialize: function(initMessage){
      this.lastMessage = initMessage;
      this.pid = initMessage.pid;
      this.id = initMessage.id;
      this.delay = 2.5;
      this.times = 0;
      this.maxTimes = 4;
      portal.startWaiting();
      portal.hideAllModal();
      dhtmlHistory.add("jobs::" + this.pid);
      // setting the dhtmlHistory here is hacky, but it allows to directly display
      // the job status upon refresh.
      if(this.running()){
        portal.startWaiting();
        this.trigger();
      }else{
        this.show();
      }
    },

    running: function(){
      return ['submitted','pending','running'].include(this.lastMessage.status);
    },

    trigger: function(){
      if(this.times==this.maxTimes){
        this.show();
      }else{
        setTimeout(this.check.bind(this),this.delay*1000);
        this.times += 1;        
      }
    },
    
    show: function(){
      user.workspace.get();
      portal.go("jobs::" + this.pid);      
    },
    
    check: function(){
      new Ajax.Request(
        'job_status.py', {
          method: 'GET',
          parameters: $H({'jobId':this.id}),
          onSuccess: function(resp){
            this.lastMessage = resp.responseText.evalJSON();
            if(this.running()){
              this.trigger();
            }else{
              this.show();
            }
          }.bind(this)}
      );
    }

});

/**
 * tabMethods is the set of methods that manage tabbed panels manipulation
 */
var tabMethods = {

    /**
   *
   * @param {Element}
   *            element container
   * @param {Data}
   * insert a tabs container inside any given container
   *            ajaxData [optional] the list to which this tabsContainer registers as a view
   */
    insertTabsContainer: function(element){
        if (!$(element).down('.handlesList')){
            $(element).insert({
                'bottom': "<ul class='handlesList'></ul><div class='panelsList'></ul>"
            });
        }
    },

    /**
   * insert a tab in a given tabs container.
   *
   * @param {Element}
   *            element the element containing the tabs (handles+panels)
   * @param {String}
   *            id the id of the tab to be created
   * @param {String}
   *            label the label of the tab to be created (displayed in the
   *            handle)
   * @param {boolean}
   *            close indicates wether or not a close control should be
   *            created
   */
    insertTab: function(element, id, label, close, handleClassName){
        if (! ($(element).down('ul.handlesList') &&
        $(element).down('div.panelsList'))){
            $(element).insertTabsContainer();
        }
        // create tab html if it does not exist
        if (!$(id)){
            // insert handle
            $(element).down('ul.handlesList').__insertHandle(id, label, close, handleClassName);
            // insert panel
            return $(element).down('div.panelsList').__insertPanel(id);
        }else{
          return $(id);
        }
        //$('ms-' + id).showTab();
    },

    /**
   * insert the html for a tabbed panel
   *
   * @param {Element}
   *            element the tab panels container
   * @param {String}
   *            id the id of the created tab
   */
    __insertPanel: function(element, id){
        return $(element).insert(Builder.node('div', {
            className: 'tabPanel',
            id: 'ms-' + id
        }));
    },

    /**
   * insert the html for a tab handle
   *
   * @param {Element}
   *            element the tab handles container
   * @param {String}
   *            id the id of the tab to be created
   * @param {String}
   *            label the label of the tab to be created (displayed in the
   *            handle)
   * @param {boolean}
   *            close indicates wether or not a close control should be
   *            created
   * @param {className}
   *            className is the value of the class HTML attribute for the handle
   */
    __insertHandle: function(element, id, label, close, className){
        var closeLink = close ? Builder.node('a', {
            href: '#' + id,
            className: 'closeTab',
            title: 'close this tab'
        },
        'x') : "";
        var handle = Builder.node('li', {className:'menu '+className},
        [Builder.node('a', {
            href: '#' + id,
            className: 'link'
        },
        label), closeLink]);
        return $(element).insert(handle);
    },

    updateHandleLabel: function(element, id, label){
        $$('li a.link[href="#' + id + '"]').invoke('update',label);
    },

    /**
   * display a tab
   *
   * @param {Element}
   *            element the panel of the element that should be displayed
   */
    showTab: function(element){
        if ($(element).hasClassName('tabPanel')){
            var handleId = $$(
            '.handlesList .link[href="#' + element.id.substr(3) + '"]',
            '.handlesList .link[href="' + location.href.split('#').first() + '#' + element.id.substr(3) + '"]'
            //this line is for ie7 compatibility
            )[0];
            var element = $(handleId);
        }
        else{
            throw Error("please ask for a tab (" + element.id + ")");
        }
        //element.style.backgroundImage="url(/portal/css/../images/add_stat.gif)";
        //if (element.next()) {element.next().style.backgroundImage="url(/portal/css/../images/add_stat.gif)";}
        var liEl = $(element.up('li'));
        liEl.siblings().each(function(el){
            if (el.down('a.link')){
                //el.down('a').style.backgroundImage="";
                //if (el.down('a').next()) {el.down('a').next().style.backgroundImage="";}
                el.removeClassName('selected');
                var panelId = el.down('a').readAttribute('href').split('#').last();
                $('ms-' + panelId).hide();
            }
        });
        liEl.addClassName('selected');
        var panel = $('ms-' + element.readAttribute('href').split('#').last());
        panel.show();
        panel.setStyle({borderTopColor: element.up('li').getStyle('backgroundColor')});
        // recursively show the parent tabs of the shown tab
        if ($(element).up('.tabPanel') && !panel.up('.tabPanel').visible()){
            $(element).up('.tabPanel').showTab();
        }
        if (panel.down('ul.handlesList') && panel.down('ul.handlesList').down('li')){
            if (!panel.down('ul.handlesList').down('li.selected')){
                panel.down('.tabPanel').showTab();
            }
        }
    },

    /**
   * hides an element this method overrides prototypejs', because display:none
   * only sometimes is not always sufficient to hide a panel in IE
   *
   * @param {Element}
   *            element the element to hide
   */
    hide: function(element){
        $(element).style.display = 'none';
        $(element).style.visibility = 'hidden';
        return element;
    },

    /**
   * show an element this method overrides prototypejs', because display:none only
   * sometimes is not always sufficient to hide a panel in IE
   *
   * @param {Element}
   *            element the element to show
   */
    show: function(element){
        $(element).style.display = '';
        $(element).style.visibility = '';
        return element;
    },

    setLoading: function(element){
        $(element).addClassName('loading');
    },

    unsetLoading: function(element){
        $(element).removeClassName('loading');
    },

    /**
   * remove a tab
   *
   * @param {String}
   *            id the id of the element that should be removed
   */
    removeTab: function(element){
        var id = $(element).id;
        var tab = $$(
        '.handlesList a[href="#' + id.substr(3) + '"]',
        '.handlesList a[href="' + location.href.split('#').first() + '#' + id.substr(3) + '"]'
        ).first().up('li');
        if (tab.match('.selected')){
            var nextTab = tab.next() ? tab.next() : tab.previous();
            if (nextTab){
                var nextId = nextTab.down('a.link').readAttribute('href').substr(1);
                portal.go(nextId);
            }
        }
        element.remove();
        tab.remove();
    }

};

/**
 * formMethods is the set of methods that manage form-related operations
 */
var formMethods = {
    /**
   * sets an error message corresponding to the element
   *
   * @param {element}
   *            element the DOM element
   * @param {message}
   *            message the message to display
   */
    setMessage: function(element, message, formSummary){
        var id = element.identify();
        var m = Builder.node('div', {
            className: 'errormsg',
            'data-messagefor': id
        },
        message);
        if((element.tagName == 'FORM')){
          element.insert({'top': m});          
        }else{
          element.insert({'before': m});          
        }
        if (formSummary){
            var s = Builder.node('div', {
                className: 'errormsg'
            },
            [element.readAttribute('data-parametername'),
            ':',
            message.clone(true)]);
            element.up('form').insert({
                'top': s
            });
        }
    },

    /**
   * removes every error message corresponding to the control
   *
   * @param {element}
   *            element the DOM element
   */
    removeMessage: function(element){
        if (element.id){
            $$('[data-messagefor="#' + element.id + '"]').invoke('remove');
        }
    },

    /**
     * removes every error message contained in the element's descendants
     *
     * @param {element}
     *            element the DOM element
     */
    removeAllMessages: function(element){
        element.select('.errormsg').invoke('remove');
    },

    /**
     * submits the form using the AJAX wrapper
     *
     * @param {form}
     *            element the DOM form element
     */
    _submit: function(form){
        var action = form.readAttribute('action');
        if (form.hasClassName('program')){
            form.onOk = function(data){
                if (data.emailNeeded){
                    document.observe('user:email',
                    function(e){
                        document.stopObserving('user:email');
                        this._submit();
                    }.bind(form));
                    document.observe('user:email:cancel',
                    function(e){
                        document.stopObserving('user:email');
                    }.bind(form));
                    portal.go('user::email');
                    return;
                }
                if (data.activationNeeded){
                    document.observe('user:activated',
                    function(e){
                        document.stopObserving('user:activated');
                        this._submit();
                    }.bind(form));
                    portal.go('user::activate');
                    return;
                }
                new JobStatus(data);
            }
        }
        if (form.hasClassName('further')){
            if (form.next && !form.next.value.blank()){
                form.onOk = function(response){
                    var program = form.next.value.split('|')[0];
                    var param = form.next.value.split('|')[1];
                    var callback = function(){
                        var getFormAndLoadHistory = function(){
                            try{
                                // switch the databox to "result" mode
                                $(program).select('[name="' + param + '.mode"][value="' + form.inputMode.value + '"]')[0].fire('databox:switch');
                                // select appropriate value in results history to load in the databox
                                $(program + '::' + param + '::' + form.inputMode.value + '::history').value = response.safeFileName;
                                // load data
                                $(program + '::' + param + '::' + form.inputMode.value + '::history').next('input[type="button"]').fire('databox:history_load');
                            } catch(e){
                                logError(e);
                            }
                        }
                        user.workspace.get(getFormAndLoadHistory);
                    }
                    portal.go("forms::" + program, callback);
                }
            }
        }
        if (form.hasClassName('help')){
           form.onOk = function(response){
             //portal.go('welcome');
             var response = response;
             var display_sent_message = function(){
               $('help_request_confirmation_message').innerHTML = response.msg;
             }
             portal.go('user::helpsent',display_sent_message);
           }
        }
        if (action!=null){
            new Ajax.Request(action, {
                method: form.getAMethod(),
                parameters: form.serialize({hash:true}),
                onCreate: function(resp){
                    this.removeAllMessages();
                }.bind(form),
                onFailure: function(resp){
                    portal.stopWaiting();
                  logError(resp);
                }.bind(form),
                onSuccess: function(resp){
                  var okCallbackEnroute = false;
                    if (resp.responseText && resp.responseText.isJSON()){
                        data = resp.responseText.evalJSON();
                        if (data.errormsg){
                            if (data.errorparam && this.down('[data-parametername="' + data.errorparam + '"]')){
                                var error_msg = Builder.node('span',[data.errormsg,
                                    Builder.node('a',{className:'modalLink',href:'#user::help::'+data.id+'::'+data.errormsg+'::'+data.errorparam},[
                                        Builder.node('button','get help')])]);
                                this.down('[data-parametername="' + data.errorparam + '"]').setMessage(error_msg, true);
                            } else{
                                this.setMessage(data.errormsg);
                            }
                        }
                        else{
                            if (form.hasClassName('modal')){
                              portal.hideAllModal();
                            }
                            if (this.onOk){
                                this.onOk(data);
                                okCallbackEnroute = true;
                            }
                            this.onOk = null;
                        }
                    }
                  if (!okCallbackEnroute) portal.stopWaiting();
                    user.updateFromCookie();
                    // GA event tracking
                    try{
                      var gCategory = ''; gAction = ''; gLabel = '';
                      switch (action){
                        case 'data_upload.py':
                          gCategory = 'bookmark';
                          gAction = 'upload';
                          break;
                        case 'session_job_submit.py':
                          gCategory = 'service';
                          gAction = 'run';
                          gLabel = form.readAttribute('id');
                          break;
                        case 'programs_list.py':
                          gCategory = 'services_list';
                          gAction = 'display';
                          gLabel = form.serialize();
                          break;
                        case 'data_remove.py':
                          gCategory = 'bookmark';
                          gAction = 'remove';
                          break;
                        case 'session_job_remove.py':
                          gCategory = 'job';
                          gAction = 'remove';
                          break;
                        case 'data_bookmark.py':
                          gCategory = 'bookmark';
                          gAction = 'create_from_result';
                          break;
                        case 'session_setemail.py':
                          gCategory = 'session';
                          gAction = 'set_email';
                          gLabel = user.email;
                          break;
                        case 'session_signin.py':
                          gCategory = 'session';
                          gAction = 'signin';
                          gLabel = user.sessionKey;
                          break;
                        case 'session_signout.py':
                          gCategory = 'session';
                          gAction = 'signout';
                          gLabel = user.sessionKey;
                          break;
                        case 'session_register.py':
                          gCategory = 'session';
                          gAction = 'register';
                          gLabel = user.sessionKey;
                          break;
                        case 'session_activate.py':
                          gCategory = 'session';
                          gAction = 'activate';
                          gLabel = user.sessionKey;
                          break;
                        case 'help_request.py':
                          gCategory = 'session';
                          gAction = 'ask_help';
                          gLabel = form.serialize();
                          break;
                      }
                      gTrack(gCategory, gAction, gLabel);
                    }catch(e){
                      console.log("error while tracking form submission");
                    }
                }.bind(form)
            });
        }
        else{
            if (form.onOk){
              form.onOk(form.serialize({
                  hash: true
              }));
              form.onOk = null;
            }
        }
    },
    
    /**
     * get the real HTTP method which will be used for AJAX calls
     *
     * @param {form}
     *            element the DOM form element
     */
    'getAMethod': function(form){
      //  in Mobyle,
      // where POST is the default
      var man = form.getAttributeNode('method');
      return (man && man.specified && man.value!='') ? man.value : 'POST';
    }

};

Element.addMethods(tabMethods);
Element.addMethods(formMethods);

var Portal = Class.create({

    initialize: function(el){
        this.el = $(el);
        this.properties = portalProperties;
        this.filesList = new(Class.create(BookmarkLinksList, {
            feed: user.workspace
        }))('filesListUl');
        this.forms = $H();
        // setting up the data management page
        this.dataManagement = new(Class.create(BookmarksTable, {
            feed: user.workspace
        }))('dataManagement');
        this.dataGauge = new(Class.create(FilesGauge, {
            feed: user.workspace
        }))('sessionUsage');
        this.jobsList = new(Class.create(JobLinksList, {
            feed: user.workspace
        }))('jobsListUl');
        this.jobsManagement = new(Class.create(JobsTable, {
            feed: user.workspace
        }))('jobsManagement');
        new ToolTipManager();
        if ($('autoRefreshFreq').value > 0){
            new PeriodicalExecuter(function(){
                user.workspace.get();
            },
            $('autoRefreshFreq').value);
        }
        // in the case of jobs, set up a workspace listener which will update or remove the job tab
        // if its status has changed or if it has been removed
        document.observe('feed:update/' + user.workspace.url,
        function(){
            $$('.job').each(function(el){
                var jobPid = el.readAttribute('data-jobpid');
                var jobStatus = el.readAttribute('data-jobstatus');
                var jobMessage = el.readAttribute('data-jobmessage');
                var id = "jobs::" + jobPid;
                if (!user.workspace.data.jobs[jobPid]){
                    // remove if job no longer in the workspace
                    this.closeTab(id);
                } else{
                    if (user.workspace.previousData &&
                    user.workspace.previousData.jobs &&
                    user.workspace.previousData.jobs[jobPid] &&
                    (user.workspace.data.jobs[jobPid].status != user.workspace.previousData.jobs[jobPid].status ||
                    user.workspace.data.jobs[jobPid].status != jobStatus ||
                    user.workspace.data.jobs[jobPid].status_message != user.workspace.previousData.jobs[jobPid].status_message ||
                    user.workspace.data.jobs[jobPid].status_message != jobMessage)){
                      var id = 'jobs::' + jobPid;
                      // update job if its status has changed
                      this._get(id, null, true);
                    }
                }
            }.bind(this));
        }.bind(this));
        // in the case of data, set up a workspace listener which will remove the data tab if it has been removed
        document.observe('feed:update/' + user.workspace.url,
        function(){
            $$('.bookmark').each(function(el){
                var dataPid = el.readAttribute('data-bookmarkpid');
                var id = "data::" + dataPid;
                if (!user.workspace.data.data[dataPid]){
                    // remove if data no longer in the workspace
                    this.closeTab(id);
                } else{
                    var format = el.readAttribute('data-format');
                    var datatype = el.readAttribute('data-datatype');
                    var biotype = el.readAttribute('data-biotype');
                    var inputmodes = el.readAttribute('data-inputmodes');
                    var username = el.readAttribute('data-username');
                    if (user.workspace.data.data[dataPid].format != format ||
                    user.workspace.data.data[dataPid].dataType != datatype ||
                    user.workspace.data.data[dataPid].bioTypes != biotype ||
                    user.workspace.data.data[dataPid].userModes != inputmodes ||
                    user.workspace.data.data[dataPid].userName != username
                    ){
                        // update bookmark if one of its properties has changed
                        this._get(id, null, true);
                    }
                }
            }.bind(this));
            $$('.bookmark_ref[value!=""]').each(function(el){
                var dataPid = el.readAttribute('value');
                if (!user.workspace.data.data[dataPid]){
                    // clear loaded databox value if it is no longer in the workspace
                    el.retrieve('databox').clear();
                }
            });
        }.bind(this));
        // here we process preloaded form values to load them into the requested forms 
        var preload_forms = $('mobyle_load').select('input').invoke('readAttribute','data-servicename').uniq();
        preload_forms.each(function(service_name){
          this.go("forms::" + service_name,function(){
            var parameter_names = $('mobyle_load').select('input[data-servicename="'+service_name+'"]').invoke('readAttribute','data-parametername').uniq();
            parameter_names.each(function(parameter_name){
              var parameter_values = $('mobyle_load').select('input[data-servicename="'+service_name+'"][data-parametername="'+parameter_name+'"]').invoke('readAttribute','value');
              var formParam = $(service_name)[parameter_name]
              if(formParam && formParam.value!=null){
                //non-databox parameter
                formParam.value = parameter_values;
              }else{
                //databox parameter
                // switch the databox to "paste" mode
                $(service_name).select('[name="' + parameter_name + '.mode"][value="paste"]')[0].fire('databox:switch');
                // load data
                $(service_name).select('[name="' + parameter_name + '"][class="databox_paste"]')[0].value = parameter_values;                  
              }
            });
          });
        }.bind(this));
    },

    closeTab: function(id){
        if ($('ms-' + id)){
            $('ms-' + id).removeTab();
            dhtmlHistory.add(where());
        }
    },

    _get: function(id, callback, update){
        var path = id.strip().split('::');
        var request = {
            'method': 'get',
            parameters: {},
            update: update,
            id: id
        };
        switch (path[0]){
        case 'user':
            request.insert = function(response, request){
                $("userPopupOverlay").insert(Builder.node('span', {
                    id: 'modalId'
                }));
                $('modalId').insert(response.responseText);
                if(Prototype.Browser['IE']==true){
                  $('modalId').down('.modal').insert(Builder.node("iframe", {}));
                }
            }
            switch (path[1]){
            case 'signin':
                request.url = 'session_signin_form.py';
                break;
            case 'signout':
                request.url = 'session_signout_form.py';
                break;
            case 'register':
                request.url = 'session_register_form.py';
                break;
            case 'activate':
                request.url = 'session_activate_form.py';
                break;
            case 'email':
                request.url = 'session_email_form.py';
                break;
            case 'help':
                request.url = 'session_job_help_form.py';
                request.parameters['id'] = path[2];
                request.parameters['message'] = path[3];
                request.parameters['param'] = path[4];
                break;
            case 'helpsent':
                request.url = htDir + 'session_job_help_confirm.html';
                request.parameters['message'] = path[2];
                break;
            case 'jobremove':
                request.url = 'session_job_remove_form.py';
                request.parameters['id'] = path[2];
                break;
            case 'bookmarkremove':
                request.url = 'data_remove_form.py';
                request.parameters['id'] = path[2];
                break;
            }
            break;
        case 'welcome':
        case 'forms':
        case 'data':
        case 'jobs':
        case 'tutorials':
            request.parent = $('ms-' + path.slice(0,-1).join('::'));
            request.parameters['id'] = path.slice(1).join('::');
            request.insert = function(response, request){
                // if an error is sent back display it as a modal window ...
                if (response.headerJSON && response.headerJSON.error){
                    portal.stopWaiting();
                  $("userPopupOverlay").show();
                    $("userPopupOverlay").insert(Builder.node('span', {
                      id: 'modalId'
                    }));
                    $('modalId').insert(response.responseText);
                    return;
                }
                // ... otherwise go on
                if (response.headerJSON && response.headerJSON.title){
                    var title = response.headerJSON.title;
                }
                else if ($$('*[href=#"' + id + '"]')){
                    var title = $$('*[href="#' + id + '"]')[0].title;
                }
                else{
                    var title = id;
                }
                var contents= null;
                if(! request.update){
                  contentsId = 'ms-' + id + '-contents';
                  request.parent.insertTab(id, title, true, path[0]);
                  $('ms-' + id).insert({top:Builder.node('div',{id:contentsId})});
                  contents = $(contentsId);
                }else{
                  request.parent.updateHandleLabel(id, title);
                  contents = $$('[data-pid="'+request.parameters['id']+'"]').first();                    
                }
                contents.update(response.responseText);
                contents.select('textarea').each(
                // empty textareas which contain only a whitespace (XSL limitation)
                function(el){
                    el.value == ' ' ? el.value = '': null;
                }
                );
                // workflow graph events handling
                contents.select('.workflow_graph').each(function(objectEl){
                    objectEl.onload = function(e){
                      var svgdoc = e.element().contentDocument;
                      svgdoc.onclick = function(e){
                        e = Event.extend(e);
                        var el = Event.element(e);
                        var ancs = Element.ancestors(el);
                        var el = ancs.find(function(el){
                          if(el.tagName=='svg:a'){
                            return el;
                          }                          
                        });
                        if(el){
                          var destination = el.getAttributeNS("http://www.w3.org/1999/xlink",'href').substr(1);
                          var jobPid = destination.split('::').last();
                          portal.go(id+ '::' + jobPid);
                        }
                        Event.stop(e);
                      };
                    }.bind(this);
                }.bind(this));
            }
            switch (path[0]){
            case 'forms':
                request.url = 'form.py';
                request.callback = function(id){
                    if(!$('ms-' + id)) return;
                    // when a form is loaded, we create the databoxes dynamically
                    new FormParametersManager($('ms-' + id + '-contents').down('form'));
                    if(portal.properties.simple_forms_active){
                      new FormSimpleParametersToggleManager($('ms-' + id + '-contents').down('form'));                      
                    }
                }
                break;
            case 'jobs':
                request.url = 'job_view.py';
                request.parameters['pid'] = path.slice(1).join('::');
                if(!user.workspace.data.jobs){
                  user.workspace.wait(function(){this._get(id,callback,update)}.bind(this));
                    return;
                }
                request.callback = function(id){
                    if(!$('ms-' + id + '-contents')) return;
                    // when a job is loaded, we create the resultboxes dynamically
                    $('ms-' + id + '-contents').select('.job_results [data-filename]').each(function(el){
                        new Filebox(el,{
                            'position': 'after',
                            'components': ['FullScreen','Widgets','BookmarkPlusPipes']
                        });
                    });
                    updateProgressReports();
                    // if the job is "imported" by opening a portal link, refresh workspace after
                    // job view added the job to it.
                    if(!user.workspace.data.jobs[request.parameters['pid']]){
                      user.workspace.get();
                    }
                }
                break;
            case 'data':
                request.url = 'data_view.py';
                request.callback = function(id){
                    // when a data bookmark is loaded, we create the bookmarkboxes dynamically
                    $('ms-' + id + '-contents').select('[data-filename]').each(function(el){
                        new Filebox(el,{
                            'position': 'before',
                            'components': ['FullScreen','RemoveBookmark','Widgets','Pipes']
                        });
                    });
                }
                break;
            case 'tutorials':
                request.url = this.properties.htbase + 'help/' + path[1] + '.html';
                request.parameters = {};
                break;
            }
            break;
        case 'viewer':
            request.url = 'viewer_view.py';
            request.callback = function(id){
                // when a data bookmark is loaded, we create the bookmarkboxes dynamically
                $('modalId').select('[data-viewerport]').each(function(el){
                    new Filebox(el,{
                        'position': 'after',
                        'components': ['WidgetBookmarkPlusPipes']
                    });
                });
            }
            request.parameters = path[1].toQueryParams();
            request.insert = function(response, request){
                var box = Builder.node("div", {
                    className: 'modal viewer'
                });
                var vHref = request.url + '?' + $H(request.parameters).toQueryString();
                box.insert(Builder.node("div", {
                    className: 'controls',
                    style: 'text-align: right; padding-top: 2px; z-index:100001;'
                },
                [
                Builder.node('a', {
                    href: vHref,
                    title: "open in a new tab",
                    target: '_blank',
                    className: 'detachModal'
                },
                ['>']), Builder.node('a', {
                    className: 'closeModal',
                    title: "close this view",
                    href: '#'
                },
                ['X'])]));
                box.insert(response.responseText);
                $("userPopupOverlay").insert(Builder.node('span', {
                    id: 'modalId'
                }));
                $('modalId').insert(box);
            }
            break;
        }
        if (callback){
            if (request.callback){
                request.callback = request.callback.wrap(function(callOriginal, id){
                    callOriginal(id);
                    callback(id);
                });
            } else{
                request.callback = callback;
            }
        }
        if(request.url==null){
          gTrack('error','report',request);
          portal.go('welcome');
          return;
        }
        new Ajax.Request(request.url, {
            method: request.method,
            parameters: request.parameters,
            onSuccess: function(response){
                request.insert(response, request);
                if (request.callback){
                    request.callback(request.id);
                }
            },
            onFailure: function(response){
              portal.stopWaiting();
              callback = dhtmlHistory.add(where());              
            },
            onException: function(response){
              portal.stopWaiting();
              callback = dhtmlHistory.add(where());              
            }
        });
    },

    go: function(id,callback, update){
        var info = id.split('::');
        gTrack(info[0], 'display',info[1]);
        this.hideAllModal();
        var path = id.strip().split('::');
        var idlist = path.inject($A(), function(list, value, index) {
          if(list.length==0){
            list[list.length]=value;
          }else{
            list[list.length]=list[list.length-1]+'::'+value;
          }
          return list;
        });
        idlist = idlist.without('user','viewer');
        idlist.asyncEach(function(id, resume){this._go(id,resume);}.bind(this),callback);
    },

    _go: function(id, callback, update){
          if(update || !this._exists(id)){
              portal.startWaiting();
              if(callback){
                  callback = callback.wrap(function(callOriginal, id){
                      callOriginal(id);
                      portal._show(id);
                  });
              } else{
                  callback = portal._show.bind(this, id);
              }
              this._get(id, callback, update);
          }else{
              this._show(id);
              if(callback){
                callback(id);                
              }
          }          
    },

    _exists: function(id, callback){
        switch (id){
        case 'user':
        case 'viewer':
            return false;
        default:
            return $('ms-' + id);
        }
    },

    _show: function(id, callback){
        var path = id.split('::').without('');
        portal.stopWaiting();
        switch (path[0]){
        case 'user':
        case 'viewer':
            $("userPopupOverlay").show();
            process_autofocus();
            return;
        default:
            if($('ms-' + id)){
              $('ms-' + id).showTab();              
            }
        }
        dhtmlHistory.add(where());
    },

    hideAllModal: function(element){
        $('modalId') ? $('modalId').remove() : null;
        $('userPopupOverlay').hide();
    },
    
    startWaiting: function(){
      $('waitOverlay').show();
    },

    stopWaiting: function(){
      $('waitOverlay').hide();
    }
    
});

var where = function(){
    if ($('userPopupOverlay').visible()){
        var el = $('userPopupOverlay').select('modal').detect(function(el){
            return el.visible()
        });
        return el && el.id ? el.id.substr(3) : null;
    }
    else{
        var stateIds = $A(['user', 'welcome', 'forms', 'data', 'jobs', 'tutorials']);
        var els = $('portalMain').select('.tabPanel').findAll(function(el){
            var parent = el.id.substr(3).split('::').first();
            return (((el.offsetHeight + el.offsetWidth) > 0) && stateIds.include(parent));
        });
        var id = els.pluck('id').sortBy(function(s){
            return s.length;
        }).last();
        return id.substr(3);
    }
}

var ResizeHandle = Class.create({

    initialize: function(container, handle, type){
        this.container = $(container);
        var handle = $(handle);
        /* Add property to container to store position variables */
        this.container.moveposition = {
            x: 0,
            y: 0
        };
        this.moveListener = function(event){
            /* Calculate how far the mouse moved */
            var moved = {
                x: (event.pointerX() - this.container.moveposition.x),
                y: (event.pointerY() - this.container.moveposition.y)
            };
            /* Reset container's x/y utility property */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };

            /* Update container's size */
            var height = parseFloat(this.container.getStyle('height').gsub('px', ''));
            var width = parseFloat(this.container.getStyle('width').gsub('px', ''));
            var new_h = (!type || type == 'y') ? height + moved.y: height;
            var new_w = (!type || type == 'x') ? width + moved.x: width;
            this.container.setStyle({
                height: new_h + 'px',
                width: new_w + 'px'
            });
        }.bindAsEventListener(this);

        /* Listen for 'mouse down' on handle to start the move listener */
        handle.observe('mousedown',
        function(event){
            /* Set starting x/y */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };
            /* Start listening for mouse move on body */
            Event.observe(document.body, 'mousemove', this.moveListener);
        }.bindAsEventListener(this));

        /* Listen for 'mouse up' to cancel 'move' listener */
        Event.observe(document.body, 'mouseup',
        function(event){
            Event.stopObserving(document.body, 'mousemove', this.moveListener);
        }.bindAsEventListener(this));
    }
});

var ToolTipManager = Class.create({

    initialize: function(){
        this.initializeBehaviour();
    },

    initializeBehaviour: function(el){
        document.observe('mouseover',
        function(e){
            var element = e.element();
            // if a tooltip exists for this program, hide it and show it only
            // when mouse is over it
            if (element && typeof element.next == "function" &&
            element.next() &&
            element.next().hasClassName('tooltip')){
                var tooltipEl = element.next();
                this.showTooltip(this, tooltipEl);
                element.onmouseover = this.showTooltip.bindAsEventListener(this, tooltipEl);
                element.onmouseout = this.hideTooltip.bindAsEventListener(this, tooltipEl, element);
                tooltipEl.onmouseout = this.hideTooltip.bindAsEventListener(this, tooltipEl, element);
                element.onmousemove = this.moveTooltip.bindAsEventListener(this, tooltipEl);
                tooltipEl.onclick = element.onclick;
            }
        }
        .bind(this));
    },

    showTooltip: function(e, tooltipEl){
        if (this.timeOutTooltipId){
            clearTimeout(this.timeOutTooltipId);
        }
        this.timeOutTooltipId = setTimeout((function(){
            Element.show(tooltipEl)
        }).bind(this), 500);
    },

    hideTooltip: function(e, tooltipEl, element){
        if (e.relatedTarget != tooltipEl && e.relatedTarget != element){
            // do not accept this event if we leave to enter the tooltip element
            if (this.timeOutTooltipId){
                clearTimeout(this.timeOutTooltipId);
            }
            Element.hide(tooltipEl);
        }
    },

    moveTooltip: function(e, tooltipEl){
        if (e.currentTarget != null){
            Element.setStyle(tooltipEl, {
                position: "absolute",
                top: (Event.pointerY(e) - e.currentTarget.offsetParent.offsetTop) +
                "px",
                left: (Event.pointerX(e) - e.currentTarget.offsetParent.offsetLeft) +
                "px"
            });
        }
    }
});

function readCookieValue(key){
    var regex = new RegExp(key + '\s*="?(.*?)"?(;|$)');
    var cookie = document.cookie.toString();
    var match = cookie.match(regex);
    return match ? unescape(match[1]) : '';
}

function editCookieValue(key, value){
    var regex = new RegExp(key + '\s*="?(.*?)"?(;|$)');
    var repl = key + '=' + encodeURIComponent(value) + '';
    var cookie = document.cookie.toString();
    document.cookie = repl;
    return;
}

function readUrlParameterValue(name){
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.href);
    if (results == null)
    return "";
    else
    return results[1];
}

//firebug logging
function logError(err){
    console.group("Mobyle error");
    console.dir(err);
    console.trace();
    console.groupEnd();
    gTrack('error','report',err);
}

Ajax.Responders.register({
    // this hack forces to avoid caching throughout every ajax request,
    // because IE tends to force cache when using GET requests.
    onCreate: function(o){
        o.options.requestHeaders = $H({
            "Cache-Control": "post-check=1,pre-check=2,no-cache,must-revalidate",
            "If-Modified-Since": "Sat, 1 Jan 2000 00:00:00 GMT",
            "X-MobylePortalName": portalProperties.portal_name
        });
    },
    // this traces any ajax exception that might be raised
    onException: function(req, err){
        logError(err);
    },
    onComplete: function(response){
        // because IE does not include dynamically loaded html in its observed code,
        // we need to re-register the form event capture each time HTML is loaded.
        init_observers();
        // refresh user space when modifying session contents...
        switch (response.url){
        case "data_bookmark.py":
        case "data_remove.py":
        case "data_upload.py":
        case "session_job_remove.py":
        case "session_job_submit.py":
            user.workspace.get();
            break;
        }
    }
});

var process_autofocus = function(){
  // this function provides a patch that simulates the autofocus for browsers that do not support HTML5 autofocus attribute.
  //if (!("autofocus" in document.createElement("input"))){
      var autofoc_els = $$('*[style~="z-index:"] *[autofocus]');
      if(autofoc_els.length==0){
          autofoc_els = $$('*[autofocus]');
      }
       autofoc_els.first().focus();
  //}
}

// this wraps any event handling function to catch raised exceptions
Event.observe = Event.observe.wrap(function(proceed, element, eventName, handler){
    handler = handler.wrap(function(proceed, e){
        try{
            return proceed(e);
        } catch(err){
            logError(err);
        }
    });
    return proceed(element, eventName, handler);
});

Event.observe(window, 'load',
function(){

    options = {
        'anonymousSession': ($F('anonymousSession') == "True"),
        'authenticatedSession': ($F('authenticatedSession') == "True")
    };

    user = new User();

    portal = new Portal("mainContainer");
    if(dhtmlHistory.isSafari){
      dhtmlHistory.add = function(hash){console.log(hash);};
      // this desactivates rsh altogether in Safari, to avoid the bugs in this specific browser
    }else{
      dhtmlHistory.initialize(historyListener);
    }
    var initialState = document.location.hash.substr(1);
    initialState = initialState.blank() ? 'welcome' : initialState;
    if(dhtmlHistory.isFirstLoad()){
      portal.go(initialState);
    }

    new ProgramsListForm();

    init_observers();
    process_autofocus();
    
    new ResizeHandle("navbar","navbarResize","x");
});

var ProgramsListForm = Class.create({

    initialize: function(dataEl){
        this.form = $("programSearch");
        this.displayList();
        this.form.observe('submit',
        function(e){
            this.displayList();
            e.stop();
        }.bind(this));
        $('listAllSubmit').observe('click', $('searchString').clear.bind($('searchString')));
        var eventtowatch = Prototype.Browser['IE']==true ? 'click' : 'change';
        $A(this.form.serviceTypeSort).each(function(el){
            el.observe(eventtowatch, this.displayList.bind(this));
        }.bind(this));
        $A(this.form.classifyBy).each(function(el){
            el.observe(eventtowatch, this.displayList.bind(this));
        }.bind(this));
    },

    displayList: function(){
        this.form.request({
            onCreate: function(response){
                this.form.disable();
            }.bind(this),
            onSuccess: function(response){
                $('programs_list_channel').update(response.responseText);
                var el = $('listAllSubmitBlock');
                if (this.form.searchString.value.blank()){
                    el.style.visibility = "hidden";
                }
                else{
                    el.style.visibility = "visible";
                }
            }.bind(this),
            onComplete: function(response){
                this.form.enable();
            }.bind(this)
        });
    }


});

var observers = $H({
    'links': {
        'event': 'click',
        'selector': 'body',
        'fn': function(e){
            var linkEl = e.element();
            var aas = linkEl.ancestors().concat([linkEl]);
            var l = Selector.findElement(aas, '.link, .modalLink');
            if (l && l.hash){
                portal.go(l.hash.substr(1));
                e.stop();
                return;
            }
            var l = Selector.findElement(aas, '.closeTab');
            if (l && l.hash){
                $('ms-' + l.hash.substr(1)).removeTab();
                e.stop();
                dhtmlHistory.add(where());
                return;
            }
            var l = Selector.findElement(aas, '.closeModal');
            if (l){
                portal.hideAllModal();
                e.stop();
                dhtmlHistory.add(where());
                return;
            }
            var l = Selector.findElement(aas, '.detachModal');
            if (l){
                portal.hideAllModal();
                dhtmlHistory.add(where());
                return;
            }
        }
    },
    'clear_messages': {
        'event': 'change',
        'selector': 'body',
        'fn': function(e){
            e.element().removeMessage();
        }
    },
    'blinds, examples, refreshs, minimizers': {
        'event': 'click',
        'selector': 'body',
        'fn': function(e){
            var el = e.element();
            // managing blinds for comments, drawers, programs tree, etc.
            if (el.hasClassName('blindLink')){
                var tg = $(e.element().readAttribute('href').split('#').last());
                if (tg.visible()){
                    el.addClassName('closed');
                }
                else{
                    el.removeClassName('closed');
                }
                Effect[Element.visible(tg) ? 'BlindUp': 'BlindDown'](tg);
                e.stop();
            }
            // managing examples
            if (el.hasClassName('exampleLink')){
                var tg = $(e.element().readAttribute('href').substr(1));
                var program = tg.up('form').id;
                var param = tg.readAttribute('data-forparameter');
                var formParam = tg.up('form')[tg.readAttribute('data-forparameter')];
                e.stop(); // stop the event before, just in case anything goes wrong...
                if(formParam.value){
                  //non-databox parameter
                  formParam.value = tg.down('pre').innerHTML.unescapeHTML();
                }else{
                  //databox parameter
                  // switch the databox to "paste" mode
                  $(program).select('[name="' + param + '.mode"][value="paste"]')[0].fire('databox:switch');
                  // load data
                  $(program).select('[name="' + param + '"][class="databox_paste"]')[0].value = tg.down('pre').innerHTML.unescapeHTML();                  
                }
            }
            // managing refresh links
            if (el.hasClassName('refresh_link') || el.up('a.refresh_link')){
                user.workspace.get();
                e.stop();
            }
            // managing minimizable fieldsets
            if (el.tagName == 'LEGEND' && el.up('fieldset').hasClassName('minimizable')){
                var tgA = el.nextSiblings().select(function(el){el.hasClassName("commentText")});
                tgA.each(function(tg){
                    Effect[el.up('fieldset').hasClassName('minimized') ? 'BlindDown': 'BlindUp'](tg, {
                        queue: 'end'
                    });
                });
                Element[el.up('fieldset').hasClassName('minimized') ? 'removeClassName': 'addClassName'](el.up('fieldset'), 'minimized');
                e.stop();
            }
        }
    },
    'submit': {
        'event': 'submit',
        'selector': 'form',
        'fn': function(e){
            var form = e.element();
            e.stop();
            if(form.getAMethod()!='get'){
                portal.startWaiting();
            }
            form._submit();
        }
    },
    'click_download': {
        'event': 'click',
        'selector': 'a button',
        'fn': function(e){
            // this handler fires the onclick event not triggered in IE when a 
            // button is clicked inside an anchor element
            var a = e.element().up('a');
            if (a && a.click){
                e.element().up('a').click();
                e.stop();
            }
        }
    }
});

var init_observers = function(){
    observers.each(function(pair){
        $$(pair.value.selector).each(function(el){
            el.stopObserving(pair.value.event, pair.value.fn);
            el.observe(pair.value.event, pair.value.fn);
        });
    });
}

// RSH initialization
window.dhtmlHistory.create({
    toJSON: function(o){
        return Object.toJSON(o);
    },
    fromJSON: function(s){
        return s.evalJSON();
    },
    'blankURL': portalProperties.htbase + 'js/blank.html'
});

var historyListener = function(newLocation, historyData){
    if(newLocation=='undefined'|| newLocation== 'null'|| newLocation.blank()) return; // safari bug
    portal.go(newLocation);
}

Form.Element.disable = Form.Element.disable.wrap(function(proceed, el){
    el = proceed(el);
    if (el.match('.tabPanel')){
        var cpnts = el.select('input', 'select', 'textarea').reject(function(el){
            return el.disabled == true
        });
        if (cpnts.size() == 0){
            el.hide();
            $$('.handlesList .link[href="#' + el.id.substr(3) + '"]')[0].up('li').hide();
        }
    }
});

Form.Element.enable = Form.Element.enable.wrap(function(proceed, el){
    el = proceed(el);
    if (el.match('.tabPanel') && !(Form.serialize(el, false).blank())){
        $$('.handlesList .link[href="#' + el.id.substr(3) + '"]')[0].up('li').show();
    }
});

//GA event tracking wrapper function
var gTrack = function(category,action,label){
  if (typeof _gaq != 'undefined'){
     _gaq.push(['_trackEvent', category, action, label]);
  }
}


var bookmark_upload = function(value,userName,datatypeClass,datatypeSuperclass,dataFormat,dataBiotype,whereToPost,base64encoded){
    $('bookmark_upload')['data_input_upload'].value = value;
    $('bookmark_upload')['data_input_upload.name'].value = userName;
    $('bookmark_upload')['datatype_class'].value = datatypeClass;
    $('bookmark_upload')['datatype_superclass'].value = datatypeSuperclass;
    $('bookmark_upload')['data_format'].value = dataFormat;
    $('bookmark_upload')['data_biotype'].value = dataBiotype;
    if(base64encoded==true){
      $('bookmark_upload')['base64encoded'].value = 'true';
    }
    $('bookmark_upload')['inputMode'].value = 'result';
    $('bookmark_upload').onOk = function(data){
        var msgText = "Your data ("+data.userName+") is now accessible <a href='#data::"+data.safeFileName+"'><strong>here</strong></a>";
        if ($(whereToPost)){
        $(whereToPost).update(msgText);
        }
    }
  $('bookmark_upload')._submit();
  $('bookmark_upload')['base64encoded'].value = 'false';
}


var ResizeHandle = Class.create({

    initialize: function(container, handle, type){
        this.container = $(container);
        var handle = $(handle);
        /* Add property to container to store position variables */
        this.container.moveposition = {
            x: 0,
            y: 0
        };
        this.moveListener = function(event){
            /* Calculate how far the mouse moved */
            var moved = {
                x: (event.pointerX() - this.container.moveposition.x),
                y: (event.pointerY() - this.container.moveposition.y)
            };
            /* Reset container's x/y utility property */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };

            /* Update container's size */
            var height = parseFloat(this.container.getStyle('height').gsub('px', ''));
            var width = parseFloat(this.container.getStyle('width').gsub('px', ''));
            var new_h = (!type || type == 'y') ? height + moved.y: height;
            var new_w = (!type || type == 'x') ? width + moved.x: width;
            this.container.setStyle({
                height: new_h + 'px',
                width: new_w + 'px'
            });
        }.bindAsEventListener(this);

        /* Listen for 'mouse down' on handle to start the move listener */
        handle.observe('mousedown',
        function(event){
            /* Set starting x/y */
            this.container.moveposition = {
                x: event.pointerX(),
                y: event.pointerY()
            };
            /* Start listening for mouse move on body */
            Event.observe(document.body, 'mousemove', this.moveListener);
        }.bindAsEventListener(this));

        /* Listen for 'mouse up' to cancel 'move' listener */
        Event.observe(document.body, 'mouseup',
        function(event){
            Event.stopObserving(document.body, 'mousemove');
        }.bindAsEventListener(this));
    }
});

var FormSimpleParametersToggleManager = Class.create({

    initialize: function(formEl){
      if(formEl.select('.parameter[data-issimple="true"]').size()>0){
        this.simple = null; //"simple form" mode toggle 
        this.formEl = formEl;
        this.showAdvanced = Builder.node('button',['advanced options']);
        this.showAdvanced.observe('click',
          function(ev){
            this.showAll();
            ev.stop();
          }.bindAsEventListener(this));
        this.formEl.select('.formCtrl').first().insert(this.showAdvanced);
        this.showSimple = Builder.node('button',['only simple options']);
        this.showSimple.observe('click',
          function(ev){
            this.showOnlySimple();
            ev.stop();
          }.bindAsEventListener(this));
        this.formEl.observe('form:parameter_load',function(event){
          if(this.simple){
            // if simple form toggle, reinitialize simple form display
            // to display modified parameters
            this.showAll();
            this.showOnlySimple();
          }
        }.bind(this));
        this.formEl.select('.formCtrl').first().insert(this.showSimple);
        this.showOnlySimple();
      }      
    },
    
    showOnlySimple: function(event){
      // hide advanced parameters unless value!=default
      this.formEl.select('.parameter').each(
        function(pEl){
          if(pEl.readAttribute('data-issimple')!='true' && !pEl.hasClassName('modified')){
            pEl.hide();            
          }
        }.bind(this)
      );
      // hide "hidden parameters" container paragraphs
      this.formEl.select('[data-paragraphname]').each(
        function(pgEl){
          if(pgEl.select('.parameter.modified').size()==0 && pgEl.select('.parameter[data-issimple="true"]').size()==0){
            pgEl.hide();
          }
        }
      )
      this.showSimple.hide();
      this.showAdvanced.show();
      this.simple = true;
    },
    
    showAll: function(){
      this.formEl.select('.parameter').each(
        function(pEl){
          if(pEl.readAttribute('data-issimple')!='true'){
            pEl.show();
          }
        }
      );
      this.formEl.select('[data-paragraphname]').each(
        function(pgEl){
          if(pgEl.select('.parameter[data-issimple="true"]').size()==0){
            pgEl.show();
          }
        }
      )
      this.showAdvanced.hide();
      this.showSimple.show();
      this.simple = false;
    }      
});

var FormParametersManager = Class.create({

    initialize: function(formEl){
      this.formEl = formEl;
      this.formEl.select('.parameter').each(
        function(pEl){
          this.createParameter(pEl);
        }.bind(this)
      );
      this.formEl.observe('reset',
        function(event){
          this.formEl.select('.modified').invoke('removeClassName','modified');
      }.bind(this));
    },
    
    createParameter: function(el){
      var datatype = el.readAttribute('data-datatype');
      var name = el.readAttribute('data-parametername');
      if (!datatype.blank() && simpleDatatypes.indexOf(datatype) == -1){
        this.createDatabox(el,name);
      }else{
        this.createSimpleParameter(el);
      }
      el.select('input,select,textarea').each(
      function(ctrlEl){
          ctrlEl.observe('reset',onFormParameterModification);
      });
    },

    createDatabox: function(el,name){
      var inputs = el.select('[name=' + name + ']')
      var db = new Databox(el);
      el.insert({
          bottom: db.el
      });
      inputs.invoke('remove');
    },
    
    createSimpleParameter: function(el){
      el.select('input,select,textarea').each(
        function(ctrlEl){
          ctrlEl.observe('change',onFormParameterModification);
      }.bind(this));
    }        
});

var onFormParameterModification = function(event){
  // this function tests wether a control value is different from its corresponding
  // parameter value (based on data-defaultvalue attribute), and depending on the
  // result adds or removes the "modified" css class.
  var el = event.findElement();
  var form = el.up('form');
  var parEl = el.up('[data-parametername]');
  var value = form[parEl.readAttribute('data-parametername')].value || $A(form[parEl.readAttribute('data-parametername')]).pluck('value').reject(function(val){return val==''});
  value = (value=='') ? undefined : value;
  var defaultValue = parEl.readAttribute('data-default-value');
  defaultValue = (defaultValue=='') ? undefined : defaultValue;
  console.log('value='+value);
  console.log('defaultValue='+defaultValue);  
  if(value!=defaultValue){
    parEl.addClassName('modified');
  }else{
    parEl.removeClassName('modified');
  }  
};
