//
// Copyright 2011 Nemesys Ltd.
//

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

//TTT
function togglePageLogin() { new Effect.toggle('page-login-popup', 'slide',{duration:0.5}); }
function showPageLogin() { if (elem("page-login-popup").style.display == "none") togglePageLogin(); }
function hidePageLogin() { if (elem("page-login-popup").style.display != "none") togglePageLogin(); }


var requested_popup = 0;
var requested_popup_threshold = 0;
var last_requested_popup = 0;
var current_popup = 0;
var current_popup_shown = 0;

function onAnimationTimer() {
  current_popup_shown++;
  if (last_requested_popup == requested_popup) {
    requested_popup_threshold++;
  } else {
    requested_popup_threshold = 0;  
  }
  
  if (current_popup == 0 && requested_popup != 0 && requested_popup_threshold > 2) {

    current_popup = requested_popup;
    current_popup_shown = 0;
    new Effect.Appear($(current_popup), {queue: { position:'end', scope:"asdf"}, duration:0.4});
    
  } else if (current_popup != requested_popup && current_popup_shown > 8 && requested_popup_threshold > 2) {
    
    if (current_popup) new Effect.Fade($(current_popup), {duration:0.4});

    current_popup = 0;
    current_popup_shown = 0;    
  }
  setTimeout(onAnimationTimer, 100);
  last_requested_popup = requested_popup;
}

nemAddEventListener(window, "load", onAnimationTimer);

function onPageSearchFocus() {
  if (elem("page-search-input").value == "Pikahaku..." || elem("page-search-input").value == "Quick search...") {
    elem("page-search-input").value = "";
    elem("page-search-input").style.color = "black"; 
  }
}

function onPageSearchDoIt(url) {
  if (
    elem("page-search-input").value == "Pikahaku..." || 
    elem("page-search-input").value == "Quick search..." ||
    elem("page-search-input").value == ""
  ) return;
  document.location= url+ "#q=" + escape(elem("page-search-input").value);
}

function onPopupTriggerMouseOver(id) {
  requested_popup = id;
}

function onPopupTriggerMouseOut() {
  requested_popup = 0;
}

function onPopupMouseOver() {
  requested_popup = current_popup;
}

function onPopupMouseOut() {
  requested_popup = 0;
}


function rtrim(str) { return (str==null ? "" : str.replace(/( |\t|\n|\r)*$/m,"")); }
function ltrim(str) { return (str==null ? "" : str.replace(/^( |\t|\n|\r)*/m,"")); }
function trim(str) { return rtrim(ltrim(str)); }

function elem(id) { 
  var node = document.getElementById(id);
  return node; 
}
function elemShowIf(id, condition) { elem(id).style.display = (condition ? "" : "none"); }

function elemSetText(node_or_id, str) {
  if (typeof node_or_id == "string") node_or_id = elem(node_or_id);
  while (node_or_id.hasChildNodes()) node_or_id.removeChild(node_or_id.childNodes[0]);
  node_or_id.appendChild(document.createTextNode(str));
};

function nemAddEventListener(elem, event, fn) {
  if (elem.addEventListener) {
    elem.addEventListener(event,fn, false);
  } else if (elem.attachEvent) {
    elem.attachEvent("on" + event, fn);
  } else {
    alert("Sorry, your browser is not supported");
  }
}

function nemCheckEmail(email) {
  var emailregexp=/^[A-Za-z0-9_\/`{|}~=?^!#$%&'*+-][A-Za-z0-9_\/`{|}~=?^!#$%&'*+-.]{0,63}@\w+([\.-]?\w+)*(\.\w{1,8})+$/;
  return emailregexp.test(email);
}

function nemCheckMobile(number) {
  var u = /^\+?\d[\d ]{5,30}$/;
  return u.test(number); 
}

function nemWeekOfYear(date) {
  var d = new Date(date);
  d.setDate(d.getDate() - (d.getDay() + 6) % 7 + 3);
  var val = d.valueOf();
  d.setMonth(0, 4);
  return Math.round((val - d.valueOf()) / (604800000)) + 1;
}

function nemWeekday(date, lang) {
  if (!lang) lang = "fi";
  var weekdays = {
    fi: {0:"Sunnuntai", 1:"Maanantai", 2:"Tiistai", 3:"Keskiviikko", 4:"Torstai", 5:"Perjantai", 6:"Lauantai"},
    en: {0:"Sunday", 1:"Monday", 2:"Tuesday", 3:"Wednesday", 4:"Thursday", 5:"Friday", 6:"Saturday"}
 };
  return weekdays[lang][date.getDay()]; 
}

function nemWeekdayShort(date, lang) {
  return nemWeekday(date, lang).substr(0,(lang=="en" ? 3 : 2));
}

// Tries to guess year and month if not entered
// Accepts: d, d.m d.m.y, ddmm, ddmmyy, ddmmyyyy
function nemParseMeteliEventDate(str) {
  var year = new Date().getFullYear();
  var assumeYear = 1;
  var month = 0;
  var day = 0;
  var items = trim(str).split(/\D+/);
  if (items.length == 0) return undefined;
  if (items.length >= 3) {
    day = items[0];
    month = items[1];
    year = items[2];
    assumeYear = 0;
  }
  if (items.length == 2) {
    day = nemParseInt(items[0]);
    month = nemParseInt(items[1]);
  }
  if (items.length == 1) {
    var str2 = items[0];
    day = items[0].substr(0,2);
    month = items[0].substr(2,2);
    year = items[0].substr(4);
    assumeYear = 0;
  }

  if (!year.length  || year.length == 0) {
    year = new Date().getFullYear();
    assumeYear = 1;
  } else if (year.length < 3) {
    year = "20" + year;
  }

  if (day.length && day[0] == "0") day = day.substr(1);
  if (month.length && month[0] == "0") month = month.substr(1);

  var date = new Date(year, month-1, day);

  // If no year was entered, try if next year would be closer?
  if (assumeYear) {
    var diff = Math.abs(new Date() - date);
    var dateNextYear = new Date(year+1, month-1, day);
    var diffNextYear = Math.abs(new Date() - dateNextYear);
    var threemonths = 91 * 24 * 3600 * 1000;
    if (diffNextYear - threemonths < diff + threemonths) {
      date = dateNextYear;
      year = year + 1;
    }
  }
     
  // Did we get a valid date ??
  if (date.getFullYear() != nemParseInt(year) ||
      date.getMonth()+1 != nemParseInt(month) ||
      date.getDate() != nemParseInt(day)) {
     return undefined;
  }
 
  return date;
}

// Fix for bug parseInt("08") = 0 but parseInt("07") = 7, strange ??
function nemParseInt(obj) {
  while (typeof(obj) == "string" && obj.length > 1 && obj.substr(0,1) == "0") {
    obj = obj.substr(1);
  }
  return parseInt(obj);
}

function nemFormatSqlDate(date) {
  if (!date) return "0000-00-00 00:00:00";
  var d = {
    yyyy: date.getFullYear(), mm: date.getMonth()+1, dd: date.getDate(), 
    hh: date.getHours(), min: date.getMinutes(), ss: date.getSeconds()
  };
  if (d.dd < 10) d.dd = "0" + d.dd;
  if (d.mm < 10) d.mm = "0" + d.mm; 
  if (d.hh < 10) d.hh = "0" + d.hh;
  if (d.min < 10) d.min = "0" + d.min;
  if (d.ss < 10) d.ss = "0" + d.ss; 
  return d.yyyy + "-" + d.mm + "-" + d.dd + " " + d.hh + ":" + d.min + ":" + d.ss;
}

// Returns: HH:MM or ""
function nemFormatDateHHMM(date) {
  if (!date) return "";
  return (date.getHours() < 10 ? "0" :"") + date.getHours() 
    + ":" + (date.getMinutes() < 10 ? "0" :"") + date.getMinutes();
}

//Returns: dd.mm.yyyy
function nemFormatDateDDMMYYYY(date) {
  if (!date) return "";
  var d = {dd: date.getDate(), mm: date.getMonth()+1, yyyy: date.getFullYear()};
  if (d.dd < 10) d.dd = "0" + d.dd;
  if (d.mm < 10) d.mm = "0" + d.mm; 
  return d.dd + "." + d.mm + "." + d.yyyy;
}

//Returns: d.m.yyyy
function nemFormatDateDMYYYY(date) {
  if (!date) return "";
  return date.getDate() + "." + (date.getMonth()+1) + "." + date.getFullYear();
}

//Returns: d.m.y hh:hh
function nemFormatDateTime(date) {
  return trim(nemFormatDateDDMMYYYY(date) + " " + nemFormatDateHHMM(date));
}

//Convert javascript date to mysql date (without time)
function nemFormatSqlDateNoTime(date) {
  return nemFormatSqlDate(date).substr(0,10);
}

//Parses sql datetime
//Accepts: Y-M-D h:m:s
function nemParseSqlDateTime(str) {
var m = str.match(/^\s*(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2}):?(\d{1,2})?\s*$/)
if (!m) return undefined;

var d = { 
 year: nemParseInt(m[1]), month: nemParseInt(m[2]), day : nemParseInt(m[3]), 
 hour: nemParseInt(m[4]), min: nemParseInt(m[5]), sec: (m[6] != undefined ? nemParseInt(m[6]) : 0)
};

var date = new Date(d.year, d.month-1, d.day, d.hour, d.min, d.sec, 0); 

// Did we get a valid date ??
if (date.getFullYear() != d.year || date.getMonth()+1 != d.month || date.getDate() != d.day ||
   date.getHours() != d.hour || date.getMinutes() != d.min || date.getSeconds() != d.sec) {
 return undefined;
}    
  
return date;
}

//Parses sql date
//Accepts: Y-M-D
function nemParseSqlDate(str) {
return nemParseSqlDateTime(str + " 00:00:00");
}

function nemDateTodays(date) {
  var d = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0,0,0,0);
  return Math.round(d.getTime() / 3600000 / 24);
}

function nemFormatPrice(price) { return price.toFixed(2).replace(".", ","); }

// Original implementation in C: PNPOLY - Point Inclusion in Polygon Test W. Randolph Franklin (WRF)
// Note: exact hit returns either false or true (but always the same)
function nemPointInPolygon(pt, pts) {
  if (typeof(pts) == "string") {
    var p = pts.split(",");
    pts = [];
    for(var i=0;i<p.length;) pts.push({x:parseInt(p[i++]), y:parseInt(p[i++])});
  }
  var c=0, j= pts.length-1;
  for(var i=0; i < pts.length; j = i++) {
    if ((((pts[i].y<=pt.y) && (pt.y<pts[j].y)) || ((pts[j].y<=pt.y) && (pt.y<pts[i].y))) &&
        (pt.x < (pts[j].x - pts[i].x) * (pt.y - pts[i].y) / (pts[j].y - pts[i].y) + pts[i].x)) c = !c;
  }
  return c ? 1 : 0;
}

// NOTE! Duplicated in 2*javascript.js and tiketti_constants
function tikettiPriceGroupColor(pg) {
//  var bg = { X: "#ffffff", A: "#ff0000", B: "#adff2f", C: "#ffa500", D: "#b22222", E: "#8b008b", F: "#f8f8ff", G: "#556b2f", H: "#2e8b57", I: "#cd853f", J: "#b8860b"};
//  var fg = { X: "#000000", A: "#000000", B: "#000000", C: "#000000", D: "#ffffff", E: "#ffffff", F: "#000000", G: "#ffffff", H: "#ffffff", I: "#ffffff", J: "#ffffff"};
  var bg = { X: "#ffffff", A: "#3731e7", B: "#fa9b1b", C: "#c5c8c8", D: "#735986", E: "#fe5efc", F: "#66d6fe", G: "#88705a", H: "#22b9be", I: "#f1f82d", J: "#b8860b"};
  var fg = { X: "#000000", A: "#ffffff", B: "#000000", C: "#000000", D: "#ffffff", E: "#ffffff", F: "#000000", G: "#ffffff", H: "#ffffff", I: "#ffffff", J: "#ffffff"};
  return {bg: (bg[pg] ? bg[pg] : bg.X), fg: (fg[pg] ? fg[pg] : fg.X)}; 
}


// Never trust this only 100%, but most normal objects should be ok, including js dates
function nemDeepCopy(obj) {
var type = typeof obj;
var newObj;
if (type == "boolean") {
   newObj = (obj ? true : false);
} else if (type == "function") {
   newObj = obj; // ?? 
} else if (type == "number") { 
  newObj = new Number(obj) + 0;
} else if (type == "object" && obj === null) {
  newObj = null;
} else if (type == "object" && obj.constructor.toString().substr(0,15) == "function Date()") {
  newObj = new Date(obj);
} else if (nemIsArray(obj)) {
  newObj = new Array();
  for(var i=0;i<obj.length;i++) newObj.push(nemDeepCopy(obj[i]));
} else if (type == "object") {
   newObj = new Object();
   for (var i in obj) {
     newObj[i] = nemDeepCopy(obj[i]);
   }
} else if (type == "string") {   
  newObj = new String(obj) + "";
} else if (type == "undefined") {
  newObj = undefined;
}
return newObj;
}

// Try was added because of a bug? in ff 1.5.0.9 where an object didn't have a constructor property
function nemIsArray(obj) {
  try {
    return (typeof obj == "object" &&  obj.constructor.toString().indexOf("Array") > -1)
  } catch (e) {}
  return false;
}

function nemIsEmptyObject(obj) {
  if (typeof obj == "object") {
    for (var i in obj) {
      if (typeof obj[i] != "function") {
        return false;
      }
    }
    return true;
  }
  return false;
}

// TODO: Not all types are supported, dates for example 
function nemEquals(obj1, obj2) {
   var i;
   if (typeof obj1 != typeof obj2) return false;
   var type = typeof obj1;
   if (type == "boolean" || type=="string" || type=="number") {
     return (obj1 == obj2 ? true : false);
   } else if (type == "function") {
      return false; // ?? 
   } else if (nemIsArray(obj1) && nemIsArray(obj2)) {
     if (obj1.length != obj2.length) return false;
     for (var i=0;i<obj1.length;i++) if (!nemEquals(obj1[i], obj2[i])) return false;
     return true;
   } else if (type == "object") {
      var items1 = [];
      var items2 = [];
      for (i in obj1) items1.push(i);
      for (i in obj2) items2.push(i);
      if (!nemEquals(items1, items2)) return false;
      for (i in obj1) {
        if (!nemEquals(obj1[i],obj2[i])) return false;
      }
      return true;
   } else if (type == "undefined") {
     return true;
   }
   alert("equals: unknown type");
   return false;
}

// Return all object keys (same as array_keys in php)
function nemObjectKeys(hash) { var keys = []; for(i in hash) keys.push(i); return keys;}

function nemTBodySetInnerHTML(tbody, html) {
  if (html == "") {
    for(var i = 0; i < tbody.rows.length; i++) tbody.deleteRow(i-1);
    return;
  }
  var temp = document.createElement('div');
  temp.innerHTML = '<table>' + html + '</table>';
  temp.firstChild.firstChild.id = tbody.id; // Remember tbody id
  tbody.parentNode.replaceChild(temp.firstChild.firstChild, tbody);
}

function nemGetIEVersion() {
  var rv = 0; // Return value assumes failure.
  if (navigator.appName == 'Microsoft Internet Explorer') {
    var ua = navigator.userAgent;
    var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
    if (re.exec(ua) != null)
      rv = parseFloat( RegExp.$1 );
  }
  return rv;
}

function nemJSONRequest(url, object, callback, responseIsHTML, params) {
  
  var use_get = 0;
  if (!params) params = {};
  
  // From jibbering.com 
  var xmlhttp=false;
  /*@cc_on @*/
  /*@if (@_jscript_version >= 5)
  // JScript gives us Conditional compilation, we can cope with old IE versions.
  // and security blocked creation of the objects.
  try {
     xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
    try {
      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (E) {
      xmlhttp = false;
    }
  }
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
    xmlhttp = new XMLHttpRequest();
  }

  xmlhttp.open((params.use_get ? "GET": "POST"), url, true);
  //xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8');
  xmlhttp.setRequestHeader('Content-Type','text/plain');  
  
  if (responseIsHTML && responseIsHTML == 1) {
    xmlhttp.onreadystatechange=function() {
      if (xmlhttp.readyState==4) {
        callback(xmlhttp.responseText);
      }
    }    
  } else {
    xmlhttp.onreadystatechange=function() {
      if (xmlhttp.readyState==4) {
        var responseObject = null;
        try {
          var responseObject = JSON.parse(xmlhttp.responseText);
        } catch(e) {
          // console.log("Error in xmlhttp");
        }
        callback(responseObject);
      }
    }
  }

  xmlhttp.send(JSON.stringify(object));

}

/*
Copyright (c) 2005 JSON.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

var JSON = {
    org: 'http://www.JSON.org',
    copyright: '(c)2005 JSON.org',
    license: 'http://www.crockford.com/JSON/license.html',
    stringify: function(arg) {
        var c; var i; var l; var s = ''; var v;
        switch (typeof arg) {
        case 'object':
            if (arg) {
                if (arg.constructor == Array) {
                    for (i = 0; i < arg.length; ++i) {
                        v = this.stringify(arg[i]);
                        if (s) {
                            s += ',';
                        }
                        s += v;
                    }
                    return '[' + s + ']';
                } else if (typeof arg.toString != 'undefined') {
                    for (i in arg) {
                        v = arg[i];
                        if (typeof v != 'undefined' && typeof v != 'function') {
                            v = this.stringify(v);
                            if (s) {
                                s += ',';
                            }
                            s += this.stringify(i) + ':' + v;
                        }
                    }
                    return '{' + s + '}';
                }
            }
            return 'null';
        case 'number':
            return isFinite(arg) ? String(arg) : 'null';
        case 'string':
            l = arg.length;
            s = '"';
            for (i = 0; i < l; i += 1) {
                c = arg.charAt(i);
                if (c == "\u20AC") { // Fix for euro character
                  s += String.fromCharCode(128);
                } else if (c >= ' ') {
                    if (c == '\\' || c == '"') {
                        s += '\\';
                    }
                    s += c;
                } else {
                    switch (c) {
                        case '\b':
                            s += '\\b';
                            break;
                        case '\f':
                            s += '\\f';
                            break;
                        case '\n':
                            s += '\\n';
                            break;
                        case '\r':
                            s += '\\r';
                            break;
                        case '\t':
                            s += '\\t';
                            break;
                        default:
                            c = c.charCodeAt();
                            s += '\\u00' + Math.floor(c / 16).toString(16) +
                                (c % 16).toString(16);
                    }
                }
            }
            return s + '"';
        case 'boolean':
            return String(arg);
        default:
            return 'null';
        }
    },
    parse: function (text) {
        var at = 0;
        var ch = ' ';

        function error(m) {
            throw {
                name: 'JSONError',
                message: m,
                at: at - 1,
                text: text
            };
        }

        function next() {
            ch = text.charAt(at);
            at += 1;
            return ch;
        }

        function white() {
            while (ch) {
                if (ch <= ' ') {
                    next();
                } else if (ch == '/') {
                    switch (next()) {
                        case '/':
                            while (next() && ch != '\n' && ch != '\r') {}
                            break;
                        case '*':
                            next();
                            for (;;) {
                                if (ch) {
                                    if (ch == '*') {
                                        if (next() == '/') {
                                            next();
                                            break;
                                        }
                                    } else {
                                        next();
                                    }
                                } else {
                                    error("Unterminated comment");
                                }
                            }
                            break;
                        default:
                            error("Syntax error");
                    }
                } else {
                    break;
                }
            }
        }

        function string() {
            var i, s = '', t, u;

            if (ch == '"') {
outer:          while (next()) {
                    if (ch == '"') {
                        next();
                        return s;
                    } else if (ch == '\\') {
                        switch (next()) {
                        case 'b':
                            s += '\b';
                            break;
                        case 'f':
                            s += '\f';
                            break;
                        case 'n':
                            s += '\n';
                            break;
                        case 'r':
                            s += '\r';
                            break;
                        case 't':
                            s += '\t';
                            break;
                        case 'u':
                            u = 0;
                            for (i = 0; i < 4; i += 1) {
                                t = parseInt(next(), 16);
                                if (!isFinite(t)) {
                                    break outer;
                                }
                                u = u * 16 + t;
                            }
                            s += String.fromCharCode(u);
                            break;
                        default:
                            s += ch;
                        }
                    } else {
                        s += ch;
                    }
                }
            }
            error("Bad string");
            return null;
        }

        function array() {
            var a = [];

            if (ch == '[') {
                next();
                white();
                if (ch == ']') {
                    next();
                    return a;
                }
                while (ch) {
                    a.push(value());
                    white();
                    if (ch == ']') {
                        next();
                        return a;
                    } else if (ch != ',') {
                        break;
                    }
                    next();
                    white();
                }
            }
            error("Bad array");
            return null;
        }

        function object() {
            var k, o = {};

            if (ch == '{') {
                next();
                white();
                if (ch == '}') {
                    next();
                    return o;
                }
                while (ch) {
                    k = string();
                    white();
                    if (ch != ':') {
                        break;
                    }
                    next();
                    o[k] = value();
                    white();
                    if (ch == '}') {
                        next();
                        return o;
                    } else if (ch != ',') {
                        break;
                    }
                    next();
                    white();
                }
            }
            error("Bad object");
            return null;
        }

        function number() {
            var n = '', v;

            if (ch == '-') {
                n = '-';
                next();
            }
            while (ch >= '0' && ch <= '9') {
                n += ch;
                next();
            }
            if (ch == '.') {
                n += '.';
                while (next() && ch >= '0' && ch <= '9') {
                    n += ch;
                }
            }
            v = +n;
            if (!isFinite(v)) {
                error("Bad number");
            } else {
                return v;
            }
            return null;
        }

        function word() {
            switch (ch) {
                case 't':
                    if (next() == 'r' && next() == 'u' && next() == 'e') {
                        next();
                        return true;
                    }
                    break;
                case 'f':
                    if (next() == 'a' && next() == 'l' && next() == 's' &&
                            next() == 'e') {
                        next();
                        return false;
                    }
                    break;
                case 'n':
                    if (next() == 'u' && next() == 'l' && next() == 'l') {
                        next();
                        return null;
                    }
                    break;
            }
            error("Syntax error");
            return null;
        }

        function value() {
            white();
            switch (ch) {
                case '{':
                    return object();
                case '[':
                    return array();
                case '"':
                    return string();
                case '-':
                    return number();
                default:
                    return ch >= '0' && ch <= '9' ? number() : word();
            }
        }

        return value();
    }
};

//Lightbox v2.04
//by Lokesh Dhakar - http://www.lokeshdhakar.com
//Last Modification: 2/9/08
//Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
//- Free for use in both personal and commercial projects
//- Attribution requires leaving author name, author link, and the license info intact.
//Thanks: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.com), and Thomas Fuchs(mir.aculo.us) for ideas, libs, and snippets.
//Artemy Tregubenko (arty.name) for cleanup and help in updating to latest ver of proto-aculous.

LightboxOptions = Object.extend({
fileLoadingImage:    '/img/lightbox/loading.gif',   
fileBottomNavCloseImage: '/img/lightbox/closelabel.gif',
overlayOpacity: 0.8,  // controls transparency of shadow overlay
animate: true,     // toggles resizing animations
resizeSpeed: 7,    // controls the speed of the image resizing animations (1=slowest and 10=fastest)
borderSize: 10,     //if you adjust the padding in the CSS, you will need to update this variable
labelImage: "Image",
labelOf: "of"
}, window.LightboxOptions || {});

var Lightbox = Class.create();

Lightbox.prototype = {
imageArray: [],
activeImage: undefined,

initialize: function() {  
 
 this.updateImageList();
 
 this.keyboardAction = this.keyboardAction.bindAsEventListener(this);

 if (LightboxOptions.resizeSpeed > 10) LightboxOptions.resizeSpeed = 10;
 if (LightboxOptions.resizeSpeed < 1) LightboxOptions.resizeSpeed = 1;

 this.resizeDuration = LightboxOptions.animate ? ((11 - LightboxOptions.resizeSpeed) * 0.15) : 0;
 this.overlayDuration = LightboxOptions.animate ? 0.2 : 0; 

 var size = (LightboxOptions.animate ? 250 : 1) + 'px';
 
 var objBody = $$('body')[0];

objBody.appendChild(Builder.node('div',{id:'overlay'}));

 objBody.appendChild(Builder.node('div',{id:'lightbox'}, [
   Builder.node('div',{id:'outerImageContainer'}, 
     Builder.node('div',{id:'imageContainer'}, [
       Builder.node('img',{id:'lightboxImage'}), 
       Builder.node('div',{id:'hoverNav'}, [
         Builder.node('a',{id:'prevLink', href: '#' }),
         Builder.node('a',{id:'nextLink', href: '#' })
       ]),
       Builder.node('div',{id:'loading'}, 
         Builder.node('a',{id:'loadingLink', href: '#' }, 
           Builder.node('img', {src: LightboxOptions.fileLoadingImage})
         )
       )
     ])
   ),
   Builder.node('div', {id:'imageDataContainer'},
     Builder.node('div',{id:'imageData'}, [
       Builder.node('div',{id:'imageDetails'}, [
         Builder.node('span',{id:'caption'}),
         Builder.node('span',{id:'numberDisplay'})
       ]),
       Builder.node('div',{id:'bottomNav'},
         Builder.node('a',{id:'bottomNavClose', href: '#' },
           Builder.node('img', { src: LightboxOptions.fileBottomNavCloseImage })
         )
       )
     ])
   )
 ]));


$('overlay').hide().observe('click', (function() { this.end(); }).bind(this));
$('lightbox').hide().observe('click', (function(event) { if (event.element().id == 'lightbox') this.end(); }).bind(this));
$('outerImageContainer').setStyle({ width: size, height: size });
$('prevLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage - 1); }).bindAsEventListener(this));
$('nextLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage + 1); }).bindAsEventListener(this));
$('loadingLink').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
$('bottomNavClose').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));

 var th = this;
 (function(){
   var ids = 
     'overlay lightbox outerImageContainer imageContainer lightboxImage hoverNav prevLink nextLink loading loadingLink ' + 
     'imageDataContainer imageData imageDetails caption numberDisplay bottomNav bottomNavClose';  
   $w(ids).each(function(id){ th[id] = $(id); });
 }).defer();
},

updateImageList: function() {  
 this.updateImageList = Prototype.emptyFunction;

 document.observe('click', (function(event){
   var target = event.findElement('a[rel^=lightbox]') || event.findElement('area[rel^=lightbox]');
   if (target) {
     event.stop();
     this.start(target);
   }
 }).bind(this));
},

start: function(imageLink) {  

 $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'hidden' });

 var arrayPageSize = this.getPageSize();
 $('overlay').setStyle({ width: arrayPageSize[0] + 'px', height: arrayPageSize[1] + 'px' });

 new Effect.Appear(this.overlay, { duration: this.overlayDuration, from: 0.0, to: LightboxOptions.overlayOpacity });

 this.imageArray = [];
 var imageNum = 0;    

 if ((imageLink.rel == 'lightbox')){
   this.imageArray.push([imageLink.href, imageLink.title]);     
 } else {
   this.imageArray = 
     $$(imageLink.tagName + '[href][rel="' + imageLink.rel + '"]').
     collect(function(anchor){ return [anchor.href, anchor.title]; }).
     uniq();
   
   while (this.imageArray[imageNum][0] != imageLink.href) { imageNum++; }
 }

 var arrayPageScroll = document.viewport.getScrollOffsets();
 var lightboxTop = arrayPageScroll[1] + (document.viewport.getHeight() / 10);
 var lightboxLeft = arrayPageScroll[0];
 this.lightbox.setStyle({ top: lightboxTop + 'px', left: lightboxLeft + 'px' }).show();
 
 this.changeImage(imageNum);
},

changeImage: function(imageNum) {  
 
 this.activeImage = imageNum; // update global var

 if (LightboxOptions.animate) this.loading.show();
 this.lightboxImage.hide();
 this.hoverNav.hide();
 this.prevLink.hide();
 this.nextLink.hide();
 this.imageDataContainer.setStyle({opacity: .0001});
 this.numberDisplay.hide();   
 
 var imgPreloader = new Image();
 
 imgPreloader.onload = (function(){
   this.lightboxImage.src = this.imageArray[this.activeImage][0];
   this.resizeImageContainer(imgPreloader.width, imgPreloader.height);
 }).bind(this);
 imgPreloader.src = this.imageArray[this.activeImage][0];
},

resizeImageContainer: function(imgWidth, imgHeight) {

 var widthCurrent = this.outerImageContainer.getWidth();
 var heightCurrent = this.outerImageContainer.getHeight();
 var widthNew = (imgWidth + LightboxOptions.borderSize * 2);
 var heightNew = (imgHeight + LightboxOptions.borderSize * 2);

 // scalars based on change from old to new
 var xScale = (widthNew / widthCurrent) * 100;
 var yScale = (heightNew / heightCurrent) * 100;

 // calculate size difference between new and old image, and resize if necessary
 var wDiff = widthCurrent - widthNew;
 var hDiff = heightCurrent - heightNew;

 if (hDiff != 0) new Effect.Scale(this.outerImageContainer, yScale, {scaleX: false, duration: this.resizeDuration, queue: 'front'}); 
 if (wDiff != 0) new Effect.Scale(this.outerImageContainer, xScale, {scaleY: false, duration: this.resizeDuration, delay: this.resizeDuration}); 

 // if new and old image are same size and no scaling transition is necessary, 
 // do a quick pause to prevent image flicker.
 var timeout = 0;
 if ((hDiff == 0) && (wDiff == 0)){
   timeout = 100;
   if (Prototype.Browser.IE) timeout = 250;  
 }

 (function(){
   this.prevLink.setStyle({ height: imgHeight + 'px' });
   this.nextLink.setStyle({ height: imgHeight + 'px' });
   this.imageDataContainer.setStyle({ width: widthNew + 'px' });

   this.showImage();
 }).bind(this).delay(timeout / 1000);
},

showImage: function(){
 this.loading.hide();
 new Effect.Appear(this.lightboxImage, { 
   duration: this.resizeDuration, 
   queue: 'end', 
   afterFinish: (function(){ this.updateDetails(); }).bind(this) 
 });
 this.preloadNeighborImages();
},

updateDetails: function() {

 // if caption is not null
 if (this.imageArray[this.activeImage][1] != ""){
   this.caption.update(this.imageArray[this.activeImage][1]).show();
 }
 
 // if image is part of set display 'Image x of x' 
 if (this.imageArray.length > 1){
   this.numberDisplay.update( LightboxOptions.labelImage + ' ' + (this.activeImage + 1) + ' ' + LightboxOptions.labelOf + ' ' + this.imageArray.length).show();
 }

 new Effect.Parallel(
   [ 
     new Effect.SlideDown(this.imageDataContainer, { sync: true, duration: this.resizeDuration, from: 0.0, to: 1.0 }), 
     new Effect.Appear(this.imageDataContainer, { sync: true, duration: this.resizeDuration }) 
   ], 
   { 
     duration: this.resizeDuration, 
     afterFinish: (function() {
       // update overlay size and update nav
       var arrayPageSize = this.getPageSize();
       this.overlay.setStyle({ height: arrayPageSize[1] + 'px' });
       this.updateNav();
     }).bind(this)
   } 
 );
},

updateNav: function() {

 this.hoverNav.show();        

 // if not first image in set, display prev image button
 if (this.activeImage > 0) this.prevLink.show();

 // if not last image in set, display next image button
 if (this.activeImage < (this.imageArray.length - 1)) this.nextLink.show();
 
 this.enableKeyboardNav();
},

enableKeyboardNav: function() {
 document.observe('keydown', this.keyboardAction); 
},

disableKeyboardNav: function() {
 document.stopObserving('keydown', this.keyboardAction); 
},

keyboardAction: function(event) {
 var keycode = event.keyCode;

 var escapeKey;
 if (event.DOM_VK_ESCAPE) { // mozilla
   escapeKey = event.DOM_VK_ESCAPE;
 } else { // ie
   escapeKey = 27;
 }

 var key = String.fromCharCode(keycode).toLowerCase();
 
 if (key.match(/x|o|c/) || (keycode == escapeKey)){ // close lightbox
   this.end();
 } else if ((key == 'p') || (keycode == 37)){ // display previous image
   if (this.activeImage != 0){
     this.disableKeyboardNav();
     this.changeImage(this.activeImage - 1);
   }
 } else if ((key == 'n') || (keycode == 39)){ // display next image
   if (this.activeImage != (this.imageArray.length - 1)){
     this.disableKeyboardNav();
     this.changeImage(this.activeImage + 1);
   }
 }
},

preloadNeighborImages: function(){
 var preloadNextImage, preloadPrevImage;
 if (this.imageArray.length > this.activeImage + 1){
   preloadNextImage = new Image();
   preloadNextImage.src = this.imageArray[this.activeImage + 1][0];
 }
 if (this.activeImage > 0){
   preloadPrevImage = new Image();
   preloadPrevImage.src = this.imageArray[this.activeImage - 1][0];
 }

},

end: function() {
 this.disableKeyboardNav();
 this.lightbox.hide();
 new Effect.Fade(this.overlay, { duration: this.overlayDuration });
 $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'visible' });
},

getPageSize: function() {
   
  var xScroll, yScroll;

if (window.innerHeight && window.scrollMaxY) {  
xScroll = window.innerWidth + window.scrollMaxX;
yScroll = window.innerHeight + window.scrollMaxY;
} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
xScroll = document.body.scrollWidth;
yScroll = document.body.scrollHeight;
} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
xScroll = document.body.offsetWidth;
yScroll = document.body.offsetHeight;
}

var windowWidth, windowHeight;

if (self.innerHeight) { // all except Explorer
if(document.documentElement.clientWidth){
windowWidth = document.documentElement.clientWidth; 
} else {
windowWidth = self.innerWidth;
}
windowHeight = self.innerHeight;
} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
windowWidth = document.documentElement.clientWidth;
windowHeight = document.documentElement.clientHeight;
} else if (document.body) { // other Explorers
windowWidth = document.body.clientWidth;
windowHeight = document.body.clientHeight;
} 

// for small pages with total height less then height of the viewport
if(yScroll < windowHeight){
pageHeight = windowHeight;
} else { 
pageHeight = yScroll;
}

// for small pages with total width less then width of the viewport
if(xScroll < windowWidth){  
pageWidth = xScroll;  
} else {
pageWidth = windowWidth;
}

return [pageWidth,pageHeight];
}
}


if(!/msie|MSIE 6/.test(navigator.userAgent) && !/msie|MSIE 7/.test(navigator.userAgent) && !/msie|MSIE 8/.test(navigator.userAgent)) {
  document.observe('dom:loaded', function () { new Lightbox(); });
}

if (/msie|MSIE 8/.test(navigator.userAgent)) {
  nemAddEventListener(window, "load", function() { new Lightbox(); });
}


function tikettiUpdateShoppingCartTicketCount(count, price) {
  if (count && elem("page-header-cart").style.display == "none") {
    elem("page-header-search").className = "page-header-search-moved";
  } else if (!count && elem("page-header-cart").style.display != "none") {
    elem("page-header-search").className = "page-header-search-normal";
  }
  elemShowIf("page-header-cart", count);
  elemShowIf("shopping-cart-icon", count);
  elem("shopping-cart-ticket-count").innerHTML = count;
  elem("shopping-cart-ticket-price").innerHTML = nemFormatPrice(price);
}

function nemScramble(value, key, direction) {
  var ret = "";
  for (var i = 0; i < value.length; i++)
    ret += String.fromCharCode(value.charCodeAt(i) + (direction ? -1 : 1) * key.charCodeAt(i % key.length));
  return ret; 
}

function nemGetCookie(c_name) {
  if (document.cookie.length > 0) {
    c_start = document.cookie.indexOf(c_name + "=");
    if (c_start != -1) { 
      c_start = c_start + c_name.length + 1; 
      c_end = document.cookie.indexOf(";", c_start);
      if (c_end == -1) c_end = document.cookie.length;
      return unescape(document.cookie.substring(c_start, c_end));
    } 
  }
  return "";
}

// NOTE! Using domain/secure breaks IE, also setting path to anything but /, TODO: investigate further
function nemSetCookie(name, value, expires_minutes, path, domain, secure) 
{
  var today = new Date();
  today.setTime(today.getTime());
  var expires_date = new Date(today.getTime() + expires_minutes * 60 * 1000);
  document.cookie = name + "=" + escape(value) + 
    ";expires=" + expires_date.toUTCString() +
    (path ? ";path=" + path : ""); 
    //(domain ? ";domain=" + domain : "") +
    //(secure ? ";secure" : "");
}    

// Quick hack to get a getElementsByName function working on IE
function nemGetElementsByNameIEfix(tag, name) {
  var e = document.getElementsByTagName(tag);
  var ar = new Array();
  for(i = 0,iar = 0; i < e.length; i++) {
    if (e[i].getAttribute("name") == name) ar[iar++] = e[i];
  }
  return ar;
}


function TikettiSelector(jsid, texts, initial_data) {

  var me = this;
  
  this.empty_event = {
    eventID: 0,
    sales_status: "pending", // pending, empty, ok + normal statuses
    max_tickets_per_order: 0,
    sections: {},
    categories: {},
    price_groups: {}   
  }; 
  
  this.parent = {
    eventID: 0,
    sales_status: "pending", // pending, empty, ok + normal statuses
    subevents: {}
  };
  this.event = nemDeepCopy(this.empty_event);
  this.tickets = {};
  
  this.current_section = -1;
  this.current_price_group = -1;
  this.add_request_status = ""; // "", "pending", "done"
  this.current_section_map_eventID = 0;
  
  this.initEvent = function(event_data_or_reset) {
    if (!event_data_or_reset) {
      // Reset
      this.event = nemDeepCopy(this.empty_event);
      this.event.sales_status = "empty";
    } else {
      // Set event data
      this.event = event_data_or_reset; 
      if (this.event.auto_select_possible) this.current_section = 0;
      this.current_price_group = -1;
      if (nemObjectKeys(this.event.price_groups).length==1) this.current_price_group = nemObjectKeys(this.event.price_groups)[0];
    }
  };
  
  if (initial_data) {
    if (initial_data.sales_status == "ok_is_parent") {
      this.parent = initial_data;
      if (nemIsArray(this.parent.subevents)) this.parent.subevents = {}; // Fix for JSON empty hash becomes array
      this.parent.sales_status = "ok";
      this.initEvent(0);
    } else {
      this.initEvent(initial_data);
      this.parent.sales_status = "empty";
    }
  }
  
  // Constructor  
  
  var ie = nemGetIEVersion();
  if (ie /*&& ie <=7*/) {
    nemAddEventListener(window, "load", function() {me.init();});
  } else {
    document.observe("dom:loaded", function () {me.init();});
  }
  
  
  this.init = function(){
    
    // Add classes for browser detection
    var browser_class = " ts-browser-modern";
    var ie = nemGetIEVersion();
    if (ie && ie < 8) browser_class = " ts-browser-old";
    elem(jsid + "-ticket-selector").className += browser_class;
    
    //setTimeout(function() {me.updateUI();}, 500);
    this.updateUI();
    elem(jsid + "-ticket-selector").style.visibility="";
    
  };
   
  this.updateUI = function() {

    var error_message = "";
    var mode = 0;
    
    // Check parent or event error messages
    var sales_status = (this.parent.eventID ? this.parent.sales_status : this.event.sales_status);
    var is_product = (this.parent.eventID ? this.parent.is_product : this.event.is_product);
    
    switch (sales_status) {
    case "not_yet_for_sale": 
      mode = "error";
      var net_sales_starts = nemParseSqlDateTime(this.parent.eventID ? this.parent.activation_date_net : this.event.activation_date_net);
      error_message = _t('for_sale') + ' ' + nemFormatDateDMYYYY(net_sales_starts) + " " + nemFormatDateHHMM(net_sales_starts);
      break;
    case "pending":  
    case "cancelled": 
    case "sold_out":
      mode = "error";
      error_message = _t(sales_status);
      break;
    case "no_longer_for_sale_on_internet":
    case "no_longer_for_sale":
    case "nothing_to_sell":
    case "fully_booked": 
      mode = "error";
      error_message = _t(sales_status + (is_product ? '_merchandise' : ''));
      if (this.event.eventID==11870) error_message = _t('for_sale');
    }
    
    if (mode == 0 && this.add_request_status == "done") mode = "order_done";
    if (mode == 0 && this.add_request_status == "pending") mode = "pending";
    if (mode == 0 && this.event.sales_status == "ok") mode = "event";
    if (mode == 0 && this.parent.sales_status == "ok") mode = "event_list";
    
    var sections = {"error" : 0, "add-2-order": 0, "pending": 0, "title": 0, "event-list": 0, "selector": 0, "shopping-cart": 0, "summary": 0, "map": 0}; // Enable sections by changing values here
    var hidden_tickets = nemDeepCopy(this.tickets); // Keep track of tickets items not visible in item list and show them in shopping vart section

    switch(mode) {
    case "error":
      elem(jsid + "-section-error").innerHTML = error_message;
      sections["error"] = 1;
      break;
    
    case "order_done":
      sections["add-2-order"] = 1;
      elem(jsid + "-add-2-order-title").innerHTML = _t(is_product ? 'merchandise-added-2-order' : 'tickets-added-2-order');
      break;
    
    case "pending":
      sections["pending"] = 1;
      break;
      
    case "event_list":
      
      elem(jsid + "-back-button").style.display = "none";
      elem(jsid + "-pretitle").style.display = "none";
      elem(jsid + "-posttitle").style.display = "none";
      sections["title"] = 1;
      sections["event-list"] = 1;

      var has_subevent_names = false;
      for(id in this.parent.subevents) {
        has_subevent_names = has_subevent_names || this.parent.subevents[id].subevent_name != "";
      }
      
      var trs = [];
      for(id in this.parent.subevents) {
        var e = this.parent.subevents[id];
        var text = (has_subevent_names ? e.date_interval + " &ndash; " + e.subevent_name : e.date_interval);
        if (e.is_product) text = e.subevent_name; // Don't show dates for products
        if (text == "") text = "&ndash;"; // Just in case to ensure text is not completely empty

        var message = false;
        switch(e.sales_status) {
        case "no_longer_for_sale_on_internet": 
        case "no_longer_for_sale": 
        case "sold_out": 
        case "cancelled": 
          message = _t(e.sales_status);
          break;
        case "fully_booked": 
        case "not_yet_for_sale": 
          message = _t(e.sales_status + (e.is_product ? '_merchandise' : ''));
        }
        
        var button = '<div onclick="' + jsid + '.onChangeEvent(' + e.eventID + ');" class="' + (e.sales_status == "ok" ? 'ts-button' : 'ts-button-disabled') + '">' + _t("choose") + ' &raquo;</div>'; 
        var tds = "";
        tds += '<td>' + _t('sales-indicator-' + e.tickets_indicator + (e.is_product ? '-merchandise' : '')) + '&nbsp;</td>';
        tds += '<td>' + text + ' ' + (message ? '<b> &ndash; ' + message + '</b>' : '') + '</td>';
        tds += '<td width="100" align="right">' + button + '</td>';
        trs.push('<tr ' + (trs.length % 2 ? 'class="ts-tr-dark"': '') + '>' + tds + '</tr>');
      }
      
      if (this.parent.is_product) elem(jsid + "-title").innerHTML =  _t("choose-merchandise-type");
      else elem(jsid + "-title").innerHTML =  _t(has_subevent_names ? "choose-ticket-type" : "choose-event");
      
      nemTBodySetInnerHTML(elem(jsid + "-event-list-tbody"), trs.join(""));
      
      /*var event = selector_data.events[eID];
      
      if (use_radio_buttons) {
        items += '<div><input '  + (event.status != "ok" ? "disabled" : "") + ' onclick="' + jsid + '.setEvent(' + eID + ');" type="radio" id="' + jsid + '-event-' + eID + '" name="' + jsid + '-event" ' + (eID==selector_data.eventID ? "checked" : "") + ' value="' + eID + '">';
        if (event.status != "ok") text = '<span style="color:#cccccc">' + text + '</span>';
        items += '<label for="' + jsid + '-event-' + eID + '">' + text + (event.warning ? " - <b>" + event.warning + "</b>" : "") + " " + event.tickets_indicator + '<label></div>';
      } else {
        items += '<option ' + (event.status != "ok" ? "disabled" : "") + ' ' + (eID==selector_data.eventID ? "selected" : "") + ' value="' + eID + '">' + text + " " + (event.warning ? " - " + event.warning : "") + ' &nbsp; </option>';
      */
      break;
      
    case "event":
      
      var ev = this.event;
      
      sections["title"] = 1;
      sections["selector"] = 1;
      if (ev.section_map_data) sections["map"] = 1;
      
      // Figure out possible sections and price_groups (either 1) best seats sections, or 2) from selected section or 3) everything
      var active_sections = {};
      var possible_price_groups = {};
      if (this.current_section == 0) {
        for(var sID in ev.sections) {
          if (ev.sections[sID].auto_select) active_sections[sID] = 1;
        }
        for(pgID in ev.price_groups) if(ev.price_groups[pgID].auto_select) possible_price_groups[pgID] = 1;
      } else if (this.current_section > 0) {
        active_sections[this.current_section] = 1;
        possible_price_groups = ev.sections[this.current_section].price_groups;
      } else {
        active_sections = ev.sections;
        possible_price_groups = ev.price_groups;
      }
      
      var active_price_groups = nemDeepCopy(possible_price_groups);
      if (this.current_price_group != -1) {
        var active_price_groups = {};
        active_price_groups[this.current_price_group] = 1;
      }
        
      // Finally.. get active categories (in original sort order)
      var active_categories = {}; 
      for(var cID_sort in ev.categories) {
        for(var pgID in active_price_groups) {
          for(var cID in ev.price_groups[pgID].categories) if (cID==cID_sort) active_categories[cID] = 1;
        }
      }
      
      // Section select
      if (ev.show_section_select) {
        var options = "";  
        if (ev.auto_select_possible) {
          options += '<div onclick="' + jsid + '.onSectionSelect(0);" class="' + (this.current_section == 0 ? "selected" : "") + '">' + _t('sales-indicator-' + this.event.tickets_indicator) + ' ' + _t('best-available-seats') + '</div><hr>';   // (this.current_section == 0 ? "selected" : "")
        }
        for(sID in ev.sections) {
          options += '<div onclick="' + jsid + '.onSectionSelect('  + sID + ');" class="' + (sID == this.current_section ? "selected" : "") + '" value="' + sID + '">' + _t('sales-indicator-' + ev.sections[sID].tickets_indicator) + ' ' + ev.sections[sID].name + '</div>';
        }
        elem(jsid + "-section-dropdown").innerHTML = options;
        var text = _t("choose...");
        if (this.current_section == 0) text = _t('sales-indicator-' + this.event.tickets_indicator) + " " + _t('best-available-seats');
        if (this.current_section > 0) text = _t('sales-indicator-' + ev.sections[sID].tickets_indicator) + ' ' + ev.sections[this.current_section].name;
        elem(jsid + "-section-select-text").innerHTML = text;
        
        // Reload section canvas
        var section_canvas = window[jsid + '_section_canvas'];
        if (ev.section_map_data && ev.eventID != this.current_section_map_eventID) {
          console.log("SELECTOR: canvas load");
          section_canvas.load({ sections: ev.sections, section_map_data: ev.section_map_data, lang: 'fi'});
          this.current_section_map_eventID = ev.eventID;
        }
        
        if (ev.section_map_data) {
          console.log("SELECTOR: set canvas section");
          section_canvas.setSection(this.current_section);
        }
        
        
      }
      elem(jsid + "-section-select-div").style.display = ev.show_section_select ? "" : "none";
      
      // Price group select
      if (ev.show_price_group_select) {
        var options = "";  
        for(pgID in possible_price_groups) {
          options += '<div onclick="' + jsid + '.onPriceGroupSelect(\''  + pgID + '\');" class="' + (pgID == this.current_price_group ? "selected" : "") + '" value="' + pgID + '"> ' + ev.price_groups[pgID].name + '</div>';
        }
        elem(jsid + "-price-group-dropdown").innerHTML = options;
        var text = _t("choose...");
        if (this.current_price_group != -1) text = ev.price_groups[this.current_price_group].name;
        elem(jsid + "-price-group-select-text").innerHTML = text;
      }
      elem(jsid + "-price-group-select-div").style.display = ev.show_price_group_select ? "" : "none";
      
      // Pretitle
      var subtitle = "";
      if (this.parent.eventID) {
        if (this.event.is_product) {
          elem(jsid + "-pretitle").innerHTML = (this.event.subevent_name != "" ? this.event.subevent_name : "&ndash;");
        } else {
          elem(jsid + "-pretitle").innerHTML = (this.event.subevent_name != "" ? this.event.date_interval + " &ndash; " + this.event.subevent_name : this.event.date_interval);
        }
      }
      elem(jsid + "-pretitle").style.display = (this.parent.eventID ? "" : "none");
      
      // Title
      elem(jsid + "-title").innerHTML = ev.is_product ? _t("choose-type-and-amount-merchandise") : _t("choose-type-and-amount");

      // Posttitle (max items + sales ends)
      var str = _t(ev.is_product ? "max-merchandise" : "max-tickets").replace("[]", ev.max_tickets_per_order);
      var net_sales_ends = nemParseSqlDateTime(ev.net_sales_ends);
      if (this.parent.eventID) str += "<br>" + _t("sales_ends").replace("[pvm]", nemFormatDateDMYYYY(net_sales_ends) + " " + nemFormatDateHHMM(net_sales_ends)); 
      
      elem(jsid + "-posttitle").innerHTML = str
      elem(jsid + "-posttitle").style.display = "";

      //elem(jsid + "-back-button").style.display = (this.parent.eventID ? "" : "none");
      
      /*
      console.log("Current section: " + this.current_section);
      console.log("Current price_group " + this.current_price_group);
      console.log("Active sections: " + nemObjectKeys(active_sections));
      console.log("Active price_groups: " + nemObjectKeys(active_price_groups));
      console.log("Active categories: " + nemObjectKeys(active_categories));
      */
      
      var trs = [];
      if (
        (this.current_section == 0 && this.current_price_group < 0) // Don't render items if using best available seats user has to choose price group 
        ||
        (ev.show_price_group_select && this.current_price_group < 0) // Don't render items if price group select is show and no price group is selected
        ||
        (ev.show_section_select && this.current_section == -1) // Don't render items if section select is shown nothing is selected (not even best seats)
      ) {
        // Skip items
        //console.log("skip items");
      } else {
        var trs = [];
        var seen_categories = {};
        for(var sID in active_sections) {
          var section = ev.sections[sID];
          for(var pgID in active_price_groups) {
            if (section.price_groups[pgID] && (this.current_section != 0 || section.tickets_indicator != 'black')) { // sold out sections are not possible for best seats 
              for(var cID in active_categories) {
                // Show if category exists, avoid rendering same category many times when using best seats
                if (ev.price_groups[pgID].categories[cID] && !(this.current_section == 0 && seen_categories[cID])) {  
                  var category = ev.categories[cID];
                  var price = ev.price_groups[pgID].categories[cID].price;
                  
                  // Show indicator if there are more than one section and no section is currently selected
                  var indicator = "";
                  if (nemObjectKeys(this.event.sections).length > 1 && this.current_section < 0) {
                    indicator = _t('sales-indicator-' + section.tickets_indicator);                  
                  }
                  
                  var texts = [];
                  if (this.current_section < 0 && section.name != "") {
                    texts.push(section.name);
                  }
                  texts.push(category.name);
                  var key = this.event.eventID+'|'+sID+'|'+pgID+'|'+cID;
                  delete hidden_tickets[key];
                  var tickets_found = this.tickets[key] ? this.tickets[key].amount : 0;
                  var add_button = '<div style="margin-right:5px;" class="ts-button" onclick="' + jsid + '.addTickets('+this.event.eventID+','+sID+',\''+pgID+'\','+cID+');"><b>+</b> ' + _t("add") + '</div>';
                  var remove_button = '<div class="' + (!tickets_found ? "ts-button-disabled" : "ts-button-delete") + '" onclick="' + jsid + '.deleteTickets('+this.event.eventID+','+sID+',\''+pgID+'\','+cID+');"><b>-</b> ' + _t("remove") + '</div>';
                  var tds = "";
                  if (indicator != "") tds += '<td >' + indicator + '</td>';
                  tds += '<td ' + (indicator == "" ? 'colspan="2"' : "") + '>' + texts.join(" / ") + '</td>';
                  tds += '<td align="right">' + nemFormatPrice(price) + ' &euro;</td>';
                  tds += '<td align="right">' + (tickets_found ? tickets_found + " "  + (tickets_found > 1 ? _t("unit_pluralis"+(this.event.is_product ? "_merchandise" : "")) : _t("unit_singularis"+(this.event.is_product ? "_merchandise" : ""))) : "") + '</td>';
                  tds += '<td align="right">' + add_button + remove_button + '</td>';
                  trs.push('<tr ' + (trs.length % 2 ? 'class="ts-tr-dark"': '') + '>' + tds + '</tr>');
                }
                seen_categories[cID] = 1;
              }
            }
          }
        }
      }

      nemTBodySetInnerHTML(elem(jsid + "-item-list-tbody"), trs.join(""));
      
      elem(jsid + "-back-button").style.display = (this.parent.eventID ? "" : "none");
      elemShowIf(jsid + "-accept-non-adjacent-tickets-div", this.event.has_seats);
      
      break; /* end: event case */
    }

    // Shopping cart and summary, show all stuff not visible in items section
    if (mode == "event" || mode == "event_list") {
      
      // DISABLED var trs = "";
      for(var key in hidden_tickets) {
        delete this.tickets[key];
       
        /* DISABLED
        var t = hidden_tickets[key];
        
        var add_button = '<div class="ts-button" onclick="' + jsid + '.addTickets('+t.eID+','+t.sID+',\''+t.pgID+'\','+t.cID+');"><b>+</b> ' + _t("add") + '</div>';
        var remove_button = '<div class="ts-button-delete ' + (!t.amount ? "ts-button-disabled" : "") + '" onclick="' + jsid + '.deleteTickets('+t.eID+','+t.sID+',\''+t.pgID+'\','+t.cID+');"><b>-</b> ' + _t("remove") + '</div>';
        
        var name = (t.event_name != "" ? t.event_name + "<br>" : "");
        var texts = [];
        if (t.section_name != "") texts.push(t.section_name);
        if (t.category_name != "") texts.push(t.category_name);
        if (t.price_group_name != "") texts.push(t.price_group_name);
        
        var tds = "";
        tds += '<td>' + name + texts.join(" / ") + '</td>';
        tds += '<td align="right">' + nemFormatPrice(t.price) + ' &euro;</td>';
        tds += '<td align="right">' + (t.amount ? t.amount + " "  + (t.amount > 1 ? _t("unit_pluralis") : _t("unit_singularis")) : "") + '</td>';
        tds += '<td align="right">' + add_button + remove_button + '</td>';
        trs += '<tr>' + tds + '</tr>';
        */

      }
      // DISABLED if (trs != "") sections["shopping-cart"] = 1;
      // DISABLED nemTBodySetInnerHTML(elem(jsid + "-shopping-cart-tbody"), trs);
      
      // Calculate order totals
      var total_sum = 0;
      var total_amount = 0;
      for(var key in this.tickets) {
        total_sum += this.tickets[key].price * this.tickets[key].amount;
        total_amount += this.tickets[key].amount;
      }
      
      if (total_amount || mode == "event") sections["summary"] = 1;
      
      elem(jsid + "-total-price").innerHTML = (total_amount ? nemFormatPrice(total_sum) + " &euro;": "");
      elem(jsid + "-total-amount").innerHTML = (total_amount ? total_amount + " "  + (total_amount > 1 ? _t("unit_pluralis"+(this.event.is_product ? "_merchandise" : "")) : _t("unit_singularis"+(this.event.is_product ? "_merchandise" : ""))) : "");
      elem(jsid + "-add-to-cart").className = (total_amount ? "ts-button" : "ts-button-disabled ts-button-add-to-cart-disabled") + " ts-button-add-to-cart" ;
    
    }
    
    for(var i in sections) {
      elem(jsid + "-section-" + i).style.display = (sections[i] ? "" : "none");
    }
    
    if(nemGetIEVersion()) elem(jsid + "-ticket-selector").fireEvent("onmove");
    
  };
  
  this.onSectionSelect = function(sID) {
    if (sID != this.current_section) {
      this.current_section = sID;
      this.current_price_group = -1;
      if (sID > 0 && nemObjectKeys(this.event.sections[sID].price_groups).length == 1) {
        this.current_price_group = nemObjectKeys(this.event.sections[sID].price_groups)[0]; // If a section is selected and there is only one price group, select it
      }
      if (sID == 0 && nemObjectKeys(this.event.price_groups).length == 1) {
        this.current_price_group = nemObjectKeys(this.event.price_groups)[0]; // If best seats is selected and there is only one price group, select it
      }
      this.updateUI();
    }
    this.dropdown.hideNow();    
  };
  
  this.onChangeEvent = function(eID) {
    nemJSONRequest('/event/d/' + eID, {action : "selector"}, function(data) {me.onChangeEventCallback(data);});
  };
  
  this.onChangeEventCallback = function(data) {
    if (!data || !data.sales_status) { alert(_t("connection-error")); return; }
    if (data.sales_status != "ok") { alert(_t("open-event-error")); return; }
    this.initEvent(data);
    this.updateUI();
  };
  
  this.onPriceGroupSelect = function (pgID) {
    if (pgID != this.current_price_group) {
      this.current_price_group = pgID;
      this.updateUI();
    }
    this.dropdown.hideNow();    
    
  };
  
  this.onBack = function() {
    this.initEvent(0);
    this.updateUI();
  };
  
  this.onCheckout = function(continue_ordering) {
    if (continue_ordering) {
      this.tickets = {};
      this.current_section = -1;
      this.current_price_group = -1;
      this.add_request_status = "";
      if (this.parent.eventID) this.initEvent();
      this.updateUI();
    } else {
      document.location = '/tilaa';
    }
  };
  
  this.addTickets = function(eID, sID, pgID, cID, amount) {
    if (!amount) amount = 1;
    var key = eID + "|" + sID + "|" + pgID + "|" + cID;
    var event_ticket_count = 0;
    for(var key2 in this.tickets) if (this.tickets[key2].eID == eID)  event_ticket_count += this.tickets[key2].amount;
    if (event_ticket_count + amount > this.event.max_tickets_per_order) {
      alert(_t(this.event.is_product ? "warning-max-merchandise" : "warning-max-tickets").replace("[max]", this.event.max_tickets_per_order));
      return;
    }
    if (!this.tickets[key]) {
      var event_name = "";
      if (this.parent.eventID) {
        var subev = this.parent.subevents[this.event.eventID];
        var event_name = (subev.subevent_name != "" ? subev.date_interval + " &ndash; " + subev.subevent_name : subev.date_interval);
      }
      this.tickets[key] = {
        eID: eID,
        event_name: event_name, 
        pgID: pgID, 
        price_group_name: (nemObjectKeys(this.event.price_groups).length > 1 ? this.event.price_groups[pgID].name : ""),
        cID: cID, 
        category_name: this.event.categories[cID].name,
        sID: sID, 
        section_name: (sID == 0 ? _t("best-available-seats") : this.event.sections[sID].name),
        price: this.event.price_groups[pgID].categories[cID].price, // per item 
        amount: 0
      };
    }
    this.tickets[key].amount += amount;
    this.updateUI();
  };
  
  this.deleteTickets = function(eID, sID, pgID, cID, amount) {
    if (!amount) amount = 1;
    var key = eID + "|" + sID + "|" + pgID + "|" + cID;
    if (!this.tickets[key]) return;
    this.tickets[key].amount -= amount;
    if (this.tickets[key].amount <= 0) delete this.tickets[key];
    this.updateUI();
  };
  
  this.dropdown = {
    timerID: 0,
    next: 0,
    current: 0,
    show: function(id, action) {
      var dd = me.dropdown;
      if(dd.timerID) clearTimeout(dd.timerID);
      if (action == "click") {
        if (id != 0 && id == dd.current) id = 0;
        dd.timerID = setTimeout(dd.onTimer, 5);
        dd.next = id;
      }
      if (action == "over") {
        if (id != "text") {
          dd.timerID = setTimeout(dd.onTimer, 200);
          if (dd.current != "") dd.next = id;
        }
      }
      if (action == "out") {
        if (id != "text") {
          dd.timerID = setTimeout(dd.onTimer, 400);
          dd.next = 0;
        }
      }
    },
    hideNow: function() {
      this.next = 0;
      this.onTimer();
    },

    onTimer: function() {
      var dd = me.dropdown;
      if (dd.next != dd.current) {
        if (dd.current) elem(jsid + "-"+ dd.current + "-dropdown").style.display="none";
        if (dd.next) elem(jsid + "-"+ dd.next + "-dropdown").style.display="";        
        dd.current = dd.next;
      }
      dd.timerID = 0;
    }
  };
  
  this.add2Cart = function() {
    
    if (this.add_request_status == "pending") return;
    
    if (!nemObjectKeys(this.tickets).length) {
      alert(_t("no-tickets-warning"));
      return;
    }
    
    var request = {action: "find", list: [], accept_non_adjacent: elem(jsid  + '-accept-non-adjacent-tickets').checked ? 1: 0};
    for(key in this.tickets) {
      var t = this.tickets[key];
      if (t.amount > 0) {
        request.list.push({eID: t.eID, pgID: t.pgID, sID: t.sID, cID: t.cID, a: t.amount});
      }
    }
      
    this.add_request_status = "pending";
    this.add_request_start = (new Date()).getTime();
    nemJSONRequest('/event/d/0', request, function(data) {me.add2CartCallback(data);});
    this.updateUI();
      
  };
  
  this.add2CartCallback = function(data) {
    
    if ((new Date).getTime() - this.add_request_start < 1000) {
      setTimeout(function() { me.add2CartCallback(data);}, 50);
      return;
    }
    
    // Replace message id with the right text
    var message = "";
    if (data && data.text_id) {
      message = _t(data.text_id).replace("[message]", data.text_message);
    }
    
    if (data && data.status == "error") {
      this.add_request_status = "";
      alert(message);
      this.updateUI();
      return;
    }

    if (data && (data.status == "ok" || data.status == "ok_show_message")) {
      tikettiUpdateShoppingCartTicketCount(data.ticket_count, data.total_price);
      var min_left = Math.max(0,Math.floor(data.order_time_seconds_left / 60));
      elem(jsid + "-order-ok-info-message").innerHTML =  _t("order-ok-info-message").replace("[MIN]", min_left);
      this.add_request_status = "done";
      if (data.status == "ok_show_message") alert(message);
      this.updateUI();
      return;
    }
    
    alert(_t("connection-error"));
    this.add_request_status = "";
    this.updateUI();
    
  };
  
  function _t(id) { return texts[id]; }
};



