/*
There are n! (factorial) permutations where n is the number of characters/words.
Only download the permutations for 5! (5x4x3x2x1=120) or more if they are needed.
I stopped at 8 characters/words because 9! is 362,880 permutations:
362880 times (9 digits + 2 double quotes + 1 comma per permutation)
...plus other characters ~ it would be more than 4MB (and my host can't automatically GZIP it)
The file would be huge (the file for 8! is already 434kb... ouch)
*/
var permScripts = [
  {file: "", arr: []},
  {file: "", arr: ["0"]},
  {file: "http://scriptar.com/JavaScript/perm2.js", arr: ["01","10"]},
  {file: "http://scriptar.com/JavaScript/perm3.js", arr: ["012","021","102","120","201","210"]},
  {file: "http://scriptar.com/JavaScript/perm4.js", arr: ["0123","0132","0213","0231","0312","0321","1023","1032","1203","1230","1302","1320","2013","2031","2103","2130","2301","2310","3012","3021","3102","3120","3201","3210"]},
  {file: "http://scriptar.com/JavaScript/perm5.js", arr: []},
  {file: "http://scriptar.com/JavaScript/perm6.js", arr: []},
  {file: "http://scriptar.com/JavaScript/perm7.js", arr: []},
  {file: "http://scriptar.com/JavaScript/perm8.js", arr: []}
];
var permScriptLoading = false;
function loadPermArr (index, arr) {
  permScripts[index].arr = arr;
  permScriptLoading = false;
}

function Permute(str) {
  //public vars
  this.str = (str == null) ? "" : str.toString();
  this.wordSeparator = " ";
  this.doSort = true;
	this.removeDuplicates = false;
  this.perms = [];
  this.permsLoading = false;
  this.timeOut = 30;
  
  //private vars
  var timeoutID = -1,
  numTries = 0,
  origStr = "",
  token = "#",
  textObj = null,
  _this = this;
  
  //priveleged functions
  this.loadArray = function (string, separator, sortResults) {
    //gets the ball rolling perms[] array is not loaded until permsLoading is false
    if (string != null) _this.str = string.toString();
    if (separator != null) _this.wordSeparator = separator.toString();
    if (sortResults != null) _this.doSort = (sortResults) ? true : false;
		_this.origStr = _this.str;
    loadPerms();
  }

  // fills a textbox or textarea (txt) with permutations of a string (str) or an array of strings
	this.loadText = function (txt, token, callback) {
    if (!callback) {
      if (txt != null && txt.type.indexOf("text") != -1) _this.textObj = txt;
      if (_this.textObj == null) return;
      if (token != null) {
        _this.numTries = 0;
        if (token.length == 0) token = "#"; //use # (array syntax) if no other token separator
        if (token == "#") _this.str = _this.str.replace(/"/g, "~"); //replace all double-quotes with tilde if using #
        token = token.replace(/\\n/gi, "\n"); //convert \n escape codes to newlines
        _this.token = token;
      }
      _this.loadArray();
    } else {
      loadPerms();
    }
    if (_this.permsLoading) {
      //not done loading yet, wait and try again in one second (up to 10 times)...
      _this.timeoutID = setTimeout(function(){_this.loadText(null, null, true)}, 1000);
      _this.numTries += 1;
      _this.textObj.value = "Loading... " + _this.numTries;
      if (isNaN(_this.numTries) || _this.numTries >= _this.timeOut) {
        clearTimeout(_this.timeoutID);
        _this.textObj.value = "Unable to load permutation array... (" + _this.numTries + " tries)";
      }
    } else {
      //success! display results
			var p = _this.perms;
			if (_this.doSort) {
			  p = p.sort(compareCaseInsensitive);
				if (_this.removeDuplicates) {
					for (var i = 0, j = 1; i < p.length; i += 1) {
						j = i + 1;
						//scan until you run out of duplicates
						while (p.length > j && p[i] == p[j]) {
							j += 1;
						}
						if (j !== i + 1) {
							p.splice(i + 1, j - i); // remove all duplicates
						}
					}
				}
			}
      if (_this.token == "#") {
        var tmp, tmpStr = "";
        for (var i = 0, pLen = p.length; i < pLen; i++) {
          tmp = p[i];
          tmpStr += "\np[" + i + "] = \"" + tmp + "\";";
          if (tmp == _this.origStr) {
						tmpStr += " //original string";
					}
        }
        _this.textObj.value = tmpStr;
      } else {
        _this.textObj.value = p.join(_this.token);
      }
      _this.numTries = 0;
    }
  };
  
  //private functions  
  function loadPerms () {
    try {
      if (permScriptLoading) {
				return; //exit still waiting for permX.js file to be downloaded
			}
      _this.permsLoading = true; //will be set to false when file downloaded and processed
      var p = []; //local alias _this.perms for faster access without object notation
      var chars = _this.origStr.split(""); //create an array with one element for each char
      var sep = "";
      if (_this.origStr.indexOf(_this.wordSeparator) != -1) {
        //if string contains a word separator, create an array for each word
        chars = _this.origStr.split(_this.wordSeparator);
        sep = _this.wordSeparator; //local alias
      }
      var numChar = chars.length;
      if (numChar >= permScripts.length || permScripts[numChar].file.length == 0) {
        //don't calculate permutations if too many/few characters for .js files (2-8 characters/words)
        p.push(_this.origStr);
        _this.permsLoading = false;
      } else {
        //if array is empty, download... if not, no need to re-download
        if (permScripts[numChar].arr.length == 0) {
          //download permX.js file where X is number of characters or words
          loadPermScript(permScripts[numChar].file);
        } else {
          //permutation array is present, use digits as indexes for array substitutions
          var permArr = permScripts[numChar].arr, paLen = permArr.length;
          var i, j;
          var tmpArr = [], tmpStr = "";
          for (i = 0; i < paLen; i++) {
            tmpArr = permArr[i].split("");
            for (j = 0, tmpStr = ""; j < numChar; j++) {
              tmpStr += ((j == 0) ? "" : sep) + chars[tmpArr[j]];
            }
            p.push(tmpStr);
          }
          _this.permsLoading = false;
        }
      }
      if (!_this.permsLoading) {
				_this.perms = p;
			}
    } catch (e) {
      //don't submit the form if there is an error
    }
  }

  //standard asynchronous JS request which works across domains
  function loadPermScript (file) {
    permScriptLoading = true;
    var head = document.getElementsByTagName("head").item(0);
    var scriptTag = document.getElementById("loadScript");
    if (scriptTag) head.removeChild(scriptTag);
    var script = document.createElement("script");
    script.src = file;
    script.type = "text/javascript";
    script.id = "loadScript";
    head.appendChild(script);
  }

  return this;
}

//used to sort arrays
function compareCaseInsensitive (x, y) {
	x = String(x).toUpperCase()
	y = String(y).toUpperCase();
  return (x > y) ? 1 : (x < y) ? -1 : 0;
}