function Cipher() {
  this.text = "";
  this.decrypt_filter = function(character) {return true};
  this.encrypt_filter = function(character) {return true};

  this.get_text = function(text, filter_func)
  {
    if (text == null)
      text = this.text;
    if (filter_func == null)
      return text
    else
    {
      var filtered_text = "";
      for (i=0;i<text.length;i++)
        if (filter_func(text[i]))
          filtered_text += text[i];
      return filtered_text;
    }
  }

  this.decrypt = function(text) { return this.get_text(text, this.decrypt_filter); }
  this.encrypt = function(text) { return this.get_text(text, this.encrypt_filter); }
}

function CipherSolver () {
  this.cipher = new Cipher();
  this._shortcuts = new Array();
  this._shortcuts["d"] = "display";
  this._settings = new function () {
  
  }();

  this.display = function () {
    return this.cipher.text;
  }
  
  this.plaintext = function() {
    return this.cipher.decrypt();
  }
  
  this.text = function(text) {
    if (text == null)
      return this.cipher.get_text(null,this.cipher.encrypt_filter)
    else
    {
      this.cipher.text = text;
    }
  }
  
  this._print_counts = function(counts) {
    output = "";
    var freqs = new TAFFY([]);
    for (item in counts)
      freqs.insert({key:item,count:counts[item]});
    freqs.orderBy([{"count":"desc"}]);
    freqs.forEach(function(f,n){output += f.key + ":" + f.count + " "});
    if (output == "")
      output = "None found.";
    return output;
  }
  
  this.load = function(data) {
    this._load_(data);
  }
  
  this._load_ = function(data) {
    this.text(data.text);
  }
  
  this.save = function() {
    document.data_store.set("saved_cipher",TAFFY.JSON.stringify(this._save_({})));
  }

  this._save_ = function(data) {
    data.name = this.name;
    data.text = this.cipher.text;
    return data;
  }
}

function Solvers() {
  this.solvers = new Array();
  this.length = 0;
  
  this.length = function() {
    return this.solvers.length;
  }
  
  this.get = function(arg) {
    if (typeof(arg) == 'number')
      return this.solvers[arg]
    else
      for(i=0;i<this.length;i++)
        if (this.solvers[i].name == arg)
          return this.solvers[i];
  }
   
  this.load = function(args,callback) {
    var data = {};
    if (args.length == 2)
    {
      data.name = args[0];
      data.text = args[1];
    }
    else
      data = args[0];
    
    var item = this.get(data.name);
    if (item == null)
      write_error("Solver type not found: " +  data.name)
    else
    {
      document.solver = new item.solver();
      document.solver.name = data.name;
      document.solver.load(data);
      clear_output();
      write_output(data.name + " Solver loaded.");
      if (callback != null)
        callback(document.solver);
    }
  }
  
  this.register = function(name,solver) {
    this.length = this.solvers.push({"name":name,"solver":solver});
  }
}
document.solvers = new Solvers();
