// ==UserScript==
// @name          switchUSER
// @namespace     net.moeffju.dA
// @description	  Easier management of multiple user accounts (e.g. groups)
// @include       http://*.deviantart.com/*
// @exclude       http://chat.deviantart.com/chat/*
// ==/UserScript==

/*
 * ý 2005 Matthias Bauer <http://moeffju.net/> <http://moeffju.deviantart.com/>
 * Permission granted to use and modify this code, but you may not redistribute changed versions.
 * (This will probably change when the script is tested, but for now, don't.)
 */

/* constants and variables ***/

var SCRIPT_VERSION = '<strong>&beta;</strong> 1.44';

var MULTIUSER_ACCOUNT_SEPARATOR = '\n'; // don't touch
var MULTIUSER_FIELD_SEPARATOR = '\t';   // don't touch

var BROAD_COOKIE_DOMAIN = 'deviantart.com';
var BROAD_COOKIE_PATH = '/';
var BROAD_COOKIE_DURATION = 365 * 24 * 60 * 60 * 1000;

var accounts = {}; // list of all accounts, format username => userinfo (cookie)
var selected_account; // currently selected account
var current_account = {}; // actually logged-in account

var widget = null; // holder
var mouseOver = false; // widget mouseover state // XXX ugly?

var messages_info = {}; // XXX unused
var messages_html = ''; // XXX unused

/* ** functions ** */

/* helpers */
var xpath = function (query, contextNode, resultType) {
  if (null == contextNode) contextNode = document;
  if (null == resultType) resultType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
  return document.evaluate(query, contextNode, null, resultType, null);
}

/* persistence */
var load = function () {
  var t = GM_getValue('accounts');
  if (!t) return;
  
  var s = t.split(MULTIUSER_ACCOUNT_SEPARATOR);
  for (var i = 0; i < s.length; i++) {
    var up = s[i].split(MULTIUSER_FIELD_SEPARATOR);
    accounts[up[0]] = up[1];
  }
  
  var c = GM_getValue('selected-account');
  if (c) selected_account = c;
}

var save = function () {
  var a = new Array();
  for (var key in accounts) {
    a.push(key + MULTIUSER_FIELD_SEPARATOR + accounts[key]);
  }
  GM_setValue('accounts', a.join(MULTIUSER_ACCOUNT_SEPARATOR));
  GM_setValue('selected-account', selected_account);
}

/* cookie helpers */
var setCookie = function (name, value, expires, path, domain) {
  var expirationDate = new Date();
  expirationDate.setTime(expirationDate.getTime() + expires);
  
  document.cookie = name + "=" + escape(value) +
    ((expires) ? "; expires=" + expirationDate.toGMTString() : "") +
    ((path) ? "; path=" + path : "") +
    ((domain) ? "; domain=" + domain : "");
}

var setBroadCookie = function (name, value) {
  setCookie(name, value, BROAD_COOKIE_DURATION, BROAD_COOKIE_PATH, BROAD_COOKIE_DOMAIN);
}

var deleteCookie = function (name, path, domain) {
  if (getCookie(name)) {
    document.cookie = name + "=" + 
      ((path) ? "; path=" + path : "") + 
      ((domain) ? "; domain=" + domain : "") + 
      "; expires=Thu, 01-Jan-70 00:00:01 GMT";
  }
}

var deleteBroadCookie = function (name) {
  deleteCookie(name, BROAD_COOKIE_PATH, BROAD_COOKIE_DOMAIN);
}

var getCookie = function (name) {
  var cookies = document.cookie.split(/; /);
  
  for (var i = 0; i < cookies.length; i++) {
    var d = cookies[i].split(/=/);
    if (d[0] == name)
      return unescape(d[1]);
  }
  
  return null;
}

/* dA helpers */
var getDeviantMETA = function() {
  if (0 == document.getElementsByName("deviantMETA").length) return {};
  eval("var dm = " + document.getElementsByName("deviantMETA")[0].getAttribute('content'));
  return dm;
}

var getCurrentAccount = function () {
  if (getDeviantMETA() && getDeviantMETA().username)
    return current_account.account = getDeviantMETA().username;
}

var getCurrentAuthtoken = function () {
  var userinfo = getCookie('userinfo');
  if (!userinfo) return;
  
  userinfo = unescape(userinfo);
  userinfo = userinfo.substring(5, userinfo.length-2);
  
  var userinfo_split = userinfo.split(/;/);
  var userinfo_parsed = {};
  
  var last_was_key = false;
  var last_key;
  
  for (var i = 0; i < userinfo_split.length; i++) {
    var d = userinfo_split[i].split(/:/);

    var type = d[0].substring(1,d[0].length-1);
    var val = d[d.length-1].substring(1,d[d.length-1].length-1);
    
    if (last_was_key) userinfo_parsed[last_key] = val;
    else last_key = val;
    
    last_was_key = !last_was_key;
  }

  return current_account.authtoken = userinfo_parsed.authtoken;
}

/* The Meat */
var createControl = function () {
  var x = document.getElementById('bar-deviant-ctrl2');
  if (!x) return; // this is fishy, we better quit
  var t = xpath("id('bar-deviant-ctrl3')/DIV[1]/DIV[1]", document, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
  
  var holder = document.createElement('div');
  holder.style.position = 'fixed';
  holder.style.left = '10px';
  holder.style.top = '0px';
  holder.style.MozBorderRadiusBottomright = '6px';
  holder.style.MozBorderRadiusBottomleft = '6px';
  holder.style.background = '#BBC2BB';
  holder.style.border = '1px solid #546359';
  holder.style.borderTop = 'none';
  holder.style.padding = '2px';
  holder.style.margin = 'auto';
  holder.style.textAlign = 'center';
  holder.style.zIndex = '300';
  holder.innerHTML = '<span style="font-weight:bold;border-right:1px solid #546359;padding:4px;"><a href="http://moeffju.net/dA/hack/js/switchUSER/">switchUSER</a> <span title="script version" style="font-weight:normal;">'+SCRIPT_VERSION+'</span></span>'; // XXX ugly

  var form = document.createElement('form');
  form.addEventListener('submit', function (e) { e.preventDefault(); switchUser(sel); }, false);
  form.style.display = 'inline';
  
  var s = document.createElement('span'); s.textContent = ' Switch to '; form.appendChild(s);
  
  var sel = document.createElement('select');
  sel.id = 'quickSUBMIT-username';
  sel.name = 'username';
  
  for (var k in accounts) {
    var o = document.createElement('option');
    o.textContent = k;
    o.selected = (k == current_account.account);
    sel.options[sel.options.length] = o;
  }
  
  form.appendChild(sel);
  
  var s = document.createElement('span'); s.textContent = ' ';
  form.appendChild(s);
  
  var ok = document.createElement('input');
  ok.className = 'button';
  ok.type = 'submit';
  ok.value = 'Go!';
  form.appendChild(ok);
  
  if (isLoggedIn()) {
    var s = document.createElement('span'); s.textContent = ' | ';
    form.appendChild(s);
    
    var a = document.createElement('input');
    a.className = 'button';
    a.type = 'button';
    a.value = 'Save this login';
    a.addEventListener('click', addCurrentAccount, false);
    form.appendChild(a);
  
    var b = document.createElement('input');
    b.className = 'button';
    b.type = 'button';
    b.value = 'Logout';
    b.addEventListener('click', function() { deleteBroadCookie('userinfo'); showLogoutNotice(); }, false);
    t.appendChild(b);
  }
  
  holder.appendChild(form);
  document.body.appendChild(holder);
  
  holder.addEventListener('mouseover', function() { mouseOver = true; }, true);
  holder.addEventListener('mouseout', function() { mouseOver = false; }, true);
  setTimeout(function() { updateWidgetOpacity(holder, 0.15, 275); }, 1000);
}

var updateWidgetOpacity = function(holder, minOpacity, minPos) {
  var opacity;
  if (mouseOver) {
    opacity = 0.9999999999999;
  } else {
    var pageY = document.documentElement.scrollTop;
    opacity = 1.0 - Math.min(1.0, pageY / minPos);
  }
  if (opacity != holder.style.opacity) holder.style.opacity = opacity;
  
  setTimeout(function() { updateWidgetOpacity(holder, minOpacity, minPos); }, 25);
}

var changeOldBehavior = function () {
  var a;
  if (document.getElementById('creditcards')) // dA shop is defined by that!
    a = xpath("id('bar-deviant-ctrl3')/DIV[1]/DIV[1]/A[4]", document, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
  else
    a = xpath("id('bar-deviant-ctrl3')/DIV[1]/DIV[1]/A[3]", document, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
  if (a) a.parentNode.removeChild(a); // Logout
  
  if (!a) {
    var f = xpath("id('bar-deviant-core')/DIV[1]/FORM[1]", document, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
    if (!f) return;
    var i = document.createElement('input');
    i.type = 'hidden';
    i.name = 'reusetoken';
    i.value = '1';
    f.appendChild(i);
    // so we don't clobber the authtoken
  }
}

var isLoggedIn = function () {
  return getCookie('userinfo');
}

var addCurrentAccount = function () {
  var userinfo = getCookie('userinfo');
  
  if (userinfo) {
    var dm = getDeviantMETA();
    
    if (dm && dm.username) {
      var newacc = !(accounts[dm.username]);
      
      accounts[dm.username] = userinfo;
      selected_account = dm.username;
      save();
      
      alert((newacc?'Saved':'Updated')+' data for account "'+selected_account+'".');
      if (!newacc) return;
        
      var sel = document.getElementById('quickSUBMIT-username');
      var o = document.createElement('option');
      o.textContent = selected_account;
      o.selected = (selected_account == current_account.account);
      sel.options[sel.options.length] = o;
      
      return;
    }
  }
  
  alert('You must be logged in and on a deviantART page to save an account. (This should never occur.)');
}

var switchUser = function (sel) {
  var userinfo_old = getCookie('userinfo');
  var nu = sel.value;
  
  deleteBroadCookie('userinfo');
  setBroadCookie('userinfo', accounts[nu]);
  
  document.location.reload();
}

var showLogoutNotice = function() {
  document.location.href = 'http://www.deviantart.com/users/loggedout';
}

// XXX unused and probably unneeded as long as we reload
var updateDeviantBar = function() {
  var d = xpath("id('bar-deviant-ctrl3')/DIV[1]/DIV[1]", document, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;
  alert(current_account);
}

// to update message count without reloading
// TODO finish
var gotMessagesXML = function(resp) {
  if (response.status == 200) {
    messages_info = parseMessagesXML(resp.responseText);
    messages_html = formatMessagesInfo(messages_info);
    updateDeviantBar();
  }
}

var parseMessagesXML = function(str) {
  var re = /\babbrev="(\w+)">(\d+)</g;
  var matches = [];
  var res = {'total':0};
  
  res['for'] = str.match(/<messages for="(\w+)">/)[1];
  while (matches = re.exec(str)) {
    res[matches[1]] = matches[2];
    res['total'] += parseInt(matches[2]);
  }
  res['cm'] = res['total'] - res['D'] - res['N'];
  
  return res;
}

var formatMessagesInfo = function(a) {
  var res = [];
  var key_order = ['D', 'H', 'M', 'HD', 'C', 'F', 'J', 'P', 'N'];
  var key_desc = {'D':'deviantWatch deviations', 'H':'hot topics', 'M':'miscellaneous messages', 'HD':'replies to help desk cases you are associated with',
    'C':'replies to comments you wrote', 'F':'replies to a message you wrote in the forum', 'J':'journals', 'P':'journal polls', 'N':'notes'};

  if (a['total'] > 0) {
    var sub = [];
    var totalFmt = a['total'];
    
    var re = /(\d+)(\d{3})/;
    while (re.test(totalFmt)) { totalFmt = totalFmt.replace(re, '$1,$2'); }

    res.push(['<a href="http://my.deviantart.com/messages/"><span id="bar-num" class="num">', totalFmt, '</span> new messages</a> <span id="bar-elab" class="elab">( '].join(''));
    if (a['D'] > 0)  sub.push(['<a href="http://my.deviantart.com/devwatch/" id="bar-elab-d" title="', a['D'], ' deviantWatch deviations">', a['D'], 'D</a>'].join(''));
    if (a['cm'] > 0) {
      res.push(sub[0], ', <a href="http://my.deviantart.com/messages/">');
      sub = [];
    }
    for (var k, i = 0; k = key_order[i]; i++) {
      if (k == 'D' || k == 'N') continue;
      if (a[k] > 0) sub.push(['<span id="bar-elab-', k, '" title="', a[k], key_desc[k], '">', a[k], k, '</span>'].join(''));
    }
    res.push(sub.join(', '), '</a>');
    if (a['N'] > 0) {
      if (a['cm'] > 0) res.push(', ');
      res.push(['<a href="http://my.deviantart.com/notes/" id="bar-elab-n" title="', a['N'], ' note">', a['N'], 'N</a>'].join(''));
    }
    res.push(' )');
  }
  
  return res.join('');
}

// update deviant meta (for EasyReply)
var updateDeviantMETA = function() {
  return alert('NOT IMPLEMENTED YET! (You should never see this.)');
}

/* ** init & events ** */

try {
  load();
  
  getCurrentAccount();
  getCurrentAuthtoken();
  
  changeOldBehavior();
  createControl();
} catch(e) {
  GM_log('error initializing (check the JS Console)');
}

