<commit_before>define([ 
    "backbone",
    "activity/Activity",
    "comment/Comment",
    "comment/Comments",
    "corpus/CorpusMask",
    "confidentiality_encryption/Confidential",
    "datum/DatumField",
    "datum/DatumFields",
    "datum/DatumState",
    "datum/DatumStates",
    "data_list/DataList",
    "data_list/DataLists",
    "user/Consultants",
    "lexicon/Lexicon",
    "permission/Permission",
    "permission/Permissions",
    "datum/Session",
    "datum/Sessions",
    "user/Team",
    "user/User",
    "user/Users",
    "user/UserMask",
    "glosser/Glosser",
    "libs/Utils"
], function(
    Backbone, 
    Activity,
    Comment, 
    Comments,
    CorpusMask,
    Confidential,
    DatumField,
    DatumFields, 
    DatumState,
    DatumStates,
    DataList,
    DataLists,
    Consultants,
    Lexicon,
    Permission,
    Permissions,
    Session,
    Sessions,
    Team,
    User,
    Users,
    UserMask
) {
  var Corpus = Backbone.Model.extend(
  /** @lends Corpus.prototype */
  {
    /**
     * @class A corpus is like a git repository, it has a remote, a title
     *        a description and perhaps a readme When the user hits sync
     *        their "branch" of the corpus will be pushed to the central
     *        remote, and we will show them a "diff" of what has
     *        changed.
     * 
     * The Corpus may or may not be a git repository, so this class is
     * to abstract the functions we would expect the corpus to have,
     * regardless of how it is really stored on the disk.
     * 
     * 
     * @property {String} title This is used to refer to the corpus, and
     *           what appears in the url on the main website eg
     *           http://fieldlinguist.com/Sapir/SampleFieldLinguisticsCorpus
     * @property {String} description This is a short description that
     *           appears on the corpus details page
     * @property {String} remote The git url of the remote eg:
     *           <EMAIL>:Sapir/SampleFieldLinguisticsCorpus.git
     *           
     * @property {Consultants} consultants Collection of consultants who contributed to the corpus
     * @property {DatumStates} datumstates Collection of datum states used to describe the state of datums in the corpus 
     * @property {DatumFields} datumfields Collection of datum fields used in the corpus
     * @property {Sessions} sessions Collection of sessions that belong to the corpus
     * @property {DataLists} datalists Collection of data lists created under the corpus
     * @property {Permissions} permissions Collection of permissions groups associated to the corpus 
     * 
     *           
     * @property {Glosser} glosser The glosser listens to
     *           orthography/utterence lines and attempts to guess the
     *           gloss.
     * @property {Lexicon} lexicon The lexicon is a list of morphemes,
     *           allomorphs and glosses which are used to index datum, and
     *           also to gloss datum.
     * 
     * @description The initialize function probably checks to see if
     *              the corpus is new or existing and brings it down to
     *              the user's client.
     * 
     * @extends Backbone.Model
     * @constructs
     */
    initialize : function() {
      Utils.debug("CORPUS INIT");
      if(!this.get("confidential")){
        this.set("confidential", new Confidential() )
      }
      
      if(!this.get("publicSelf")){
        this.set("publicSelf", new CorpusMask({
          "couchConnection" : this.get("couchConnection"),
          "pouchname" : this.get("pouchname")
        }));
      }
      if(!this.get("publicCorpus")){
        this.set("publicCorpus", "Private");
      }
      this.bind("change:publicCorpus",this.changeCorpusPublicPrivate,this);
      
      if(typeof(this.get("datumStates")) == "function"){
        this.set("datumStates", new DatumStates([ 
          new DatumState({
            state : "Checked",
            color : "success",
            selected: "selected"
          }),
          new DatumState({
            state : "To be checked",
            color : "warning"
          }),
          , new DatumState({
            state : "Deleted",
            color : "important",
            showInSearchResults:  ""
          }),
        ]));
      }//end if to set datumStates
      
      if(typeof(this.get("datumFields")) == "function"){
        this.set("datumFields", new DatumFields([ 
          new DatumField({
            label : "judgement",
            size : "3",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "Use this field to establish your team's gramaticality/acceptablity judgements (*,#,? etc)"
          }),
          new DatumField({
            label : "utterance",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "Use this as Line 1 in your examples for handouts (ie, either Orthography, or phonemic/phonetic representation)"
          }),
          new DatumField({
            label : "morphemes",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "This line is used to determine the morpheme segmentation to generate glosses, it also optionally can show up in your LaTeXed examples if you choose to show morpheme segmentation in addtion ot line 1, gloss and translation."
          }),
          new DatumField({
            label : "gloss",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "This line appears in the gloss line of your LaTeXed examples, we reccomend Leipzig conventions (. for fusional morphemes, - for morpehem boundaries etc) The system uses this line to partially help you in glossing. "
          }),
          new DatumField({
            label : "translation",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "Use this as your primary translation. It does not need to be English, simply a language your team is comfortable with. If your consultant often gives you multiple languages for translation you can also add addtional translations in the customized fields. For example, your Quechua informants use Spanish for translations, then you can make all Translations in Spanish, and add an additional field for English if you want to generate a handout containing the datum. "
          })
        ]));
      }//end if to set datumFields
      
      if(typeof(this.get("sessionFields")) == "function"){
        this.set("sessionFields", new DatumFields([ 
           new DatumField({
             label : "goal",
             shouldBeEncrypted: "",
             userchooseable: "disabled",
             help: "This describes the goals of the session."
           }),  
          new DatumField({
            label : "consultants",
            shouldBeEncrypted: "",
            userchooseable: "disabled"
          }),
          new DatumField({
            label : "dialect",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "You can use this field to be as precise as you would like about the dialect of this session."
          }),
          new DatumField({
            label : "language",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "This is the langauge (or language family) if you would like to use it."
          }),
          new DatumField({
            label : "dateElicited",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "This is the date in which the session took place."
          }),
          new DatumField({
            label : "user",
            shouldBeEncrypted: "",
            userchooseable: "disabled"
          }),
          new DatumField({
            label : "dateSEntered",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "This is the date in which the session was entered."
          }),
        ]));
        
      }//end if to set sessionFields
      
      
      // If there are no comments, create models
      if (!this.get("comments")) {
        this.set("comments", new Comments());
      }
      
      if (!this.get("dataLists")) {
        this.set("dataLists", new DataLists());
      }
      
      if (!this.get("sessions")) {
        this.set("sessions", new Sessions());
      }
      //this.loadPermissions();
      
    },
    loadPermissions: function(){
      if (!this.get("team")){
        //If app is completed loaded use the user, otherwise put a blank user
        if(window.appView){
          this.set("team", window.app.get("authentication").get("userPublic"));
//          this.get("team").id = window.app.get("authentication").get("userPublic").id;
        }else{
//          this.set("team", new UserMask({pouchname: this.get("pouchname")}));
        }
      }
      this.permissions = new Permissions();
      var admins = new Users();
      if(this.get("team")){
        admins.models.push(this.get("team"));
      }
      this.permissions.add(new Permission({
        users: admins,
        role: "admin",
        pouchname: this.get("pouchname")
      }));
      
      this.permissions.add(new Permission({
        users: new Users(),
        role: "contributor",
        pouchname: this.get("pouchname")
      }));
      this.permissions.add(new Permission({
        users: new Users(), //"ifieldpublicuser"
        role: "collaborator",
        pouchname: this.get("pouchname")
      }));
      //TODO load the permissions in from the server.
    },
    
    defaults : {
      title : "Untitled Corpus",
      titleAsUrl :"UntitledCorpus",
      description : "This is an untitled corpus, created by default.",
//      confidential :  Confidential,
      consultants : Consultants,
      datumStates : DatumStates,
      datumFields : DatumFields, 
      sessionFields : DatumFields,
      searchFields : DatumFields,
      couchConnection : JSON.parse(localStorage.getItem("mostRecentCouchConnection")) || Utils.defaultCouchConnection()
    },
    
    // Internal models: used by the parse function
    model : {
      confidential :  Confidential,
      consultants : Consultants,
      datumStates : DatumStates,
      datumFields : DatumFields, 
      sessionFields : DatumFields,
      searchFields : DatumFields,
      sessions : Sessions, 
      dataLists : DataLists, 
      publicSelf : CorpusMask,
      comments: Comments,
      team: UserMask
    },
//    glosser: new Glosser(),//DONOT store in attributes when saving to pouch (too big)
    lexicon: new Lexicon(),//DONOT store in attributes when saving to pouch (too big)
    changePouch : function(couchConnection, callback) {
      if (couchConnection == null || couchConnection == undefined) {
        couchConnection = this.get("couchConnection");
      }
      if (this.pouch == undefined) {
        this.pouch = Backbone.sync
        .pouch(Utils.androidApp() ? Utils.touchUrl
            + couchConnection.pouchname : Utils.pouchUrl
            + couchConnection.pouchname);
      }

      if (typeof callback == "function") {
        callback();
      }
    }, 
    /**
     * Accepts two functions to call back when save is successful or
     * fails. If the fail callback is not overridden it will alert
     * failure to the user.
     * 
     * - Adds the corpus to the corpus if it is in the right corpus, and wasn't already there
     * - Adds the corpus to the user if it wasn't already there
     * - Adds an activity to the logged in user with diff in what the user changed. 
     * 
     * @param successcallback
     * @param failurecallback
     */
    saveAndInterConnectInApp : function(successcallback, failurecallback){
      Utils.debug("Saving the Corpus");
      var self = this;
      var newModel = false;
      if(!this.id){
        newModel = true;
        
        if(!this.get("pouchname")){
          this.set("pouchname", this.get("team").get("username")
              +"-"+this.get("title").replace(/[^a-zA-Z0-9-._~ ]/g,"") ) ;
        }
        if(!this.get("couchConnection")){
          this.get("couchConnection").pouchname = this.get("team").get("username")
          +"-"+this.get("title").replace(/[^a-zA-Z0-9-._~ ]/g,"") ;
        }
      }
      var oldrev = this.get("_rev");
      
      /*
       * For some reason the corpus is getting an extra state that no one defined in it. this gets rid of it when we save.
       */
      try{
        var ds = this.get("datumStates").models;
        for (var s in ds){
          if(ds[s].get("state") == undefined){
            this.get("datumStates").remove(ds[s]);
          }
        }
      }catch(e){
        Utils.debug("Removing empty states work around failed some thing was wrong.",e);
      }
      
      
      this.changePouch(null,function(){
        self.save(null, {
          success : function(model, response) {
            Utils.debug('Corpus save success');
            var title = model.get("title");
            var differences = "#diff/oldrev/"+oldrev+"/newrev/"+response._rev;
            //TODO add privacy for corpus in corpus
//            if(window.app.get("corpus").get("keepCorpusDetailsPrivate")){
//              title = "";
//              differences = "";
//            }
            //save the corpus mask too
            var publicSelfMode = model.get("publicSelf");
            publicSelfMode.changePouch( model.get("couchConnection"), function(){
              publicSelfMode.saveAndInterConnectInApp();
            });
            
            if(window.appView){
              window.appView.toastUser("Sucessfully saved corpus: "+ title,"alert-success","Saved!");
              window.appView.addSavedDoc(model.id);
            }
            var verb = "updated";
            if(newModel){
              verb = "added";
            }
            var teamid = model.get("team").id; //Works if UserMask was saved
            if(!teamid){
              //TODO test this, this is to protect from the case wher the id of the team is not set yet.
//              window.app.get("authentication").get("userPublic").saveAndInterConnectInApp(function(){
//                window.app.get("corpus").set("team", window.app.get("authentication").get("userPublic"));
//                window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
//                    new Activity({
//                      verb : verb,
//                      directobject : "<a href='#corpus/"+window.app.get("corpus").id+"'>corpus "+title+"</a> ",
//                      indirectobject : "owned by <a href='#user/"+window.app.get("corpus").get("team").id+"'>"+window.app.get("corpus").get("team").get("username")+"</a>",
//                      context : differences+" via Offline App."
//                    }));
//              });
              teamid = model.get("team")._id; //Works if UserMask came from a mongodb id
              if(!teamid){
                if(model.get("team").get("username") == window.app.get("authentication").get("userPrivate").get("username")){
                  teamid = window.app.get("authentication").get("userPrivate").id; //Assumes the user private and team are the same user...this is dangerous
                }
              }
              /**
               * The idea of the masks in the activity
               * is that the teams/users can make a
               * public activity feed, which they create
               * a special widget user for, and if the
               * widget user asks for activities, the
               * map reduce function returns only the
               * masks, whcih means that the original
               * activities are protected. so if the
               * activity is soemthing that you might
               * want to appear in a really public feed,
               * then add a mask to it, and it will
               * automatically appear. this can probably
               * be done for all activities later. Right
               * now its only in the syncing aspect so
               * at least we can test the map reduce
               * function.
               */
              window.app.get("currentCorpusUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "personal"
                  }));
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
            }else{
              window.app.get("currentCorpusUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "personal"
                  }));
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
            }
            
            //make sure the corpus is in the history of the user
            if(window.app.get("authentication").get("userPrivate").get("corpuses").indexOf(model.get("couchConnection")) == -1){
              window.app.get("authentication").get("userPrivate").get("corpuses").unshift(model.get("couchConnection"));
            }
            /*
             * Save the default data list, in case it has changed without being saved in pouch. 
             */
            if(model.get("dataLists").length > 0){
              var defaultDatalist = model.get("dataLists").models[model.get("dataLists").length - 1];

              if(defaultDatalist.needsSave){
//                defaultDatalist.changePouch(null, function(){
//                  Utils.debug("Saving the default datalist because it was changed by adding datum, and it wasn't the current data list so it is was the 'active' defualt datalist.");
//                  defaultDatalist.save();
                //TODO uncomment this
//                });
              }
            }
            if(newModel){
              //TODO something similar to saving the current dashboard so the user can go back.
//              window.app.saveAndInterConnectInApp(function(){
               
              self.setAsCurrentCorpus(function(){

                if(model.get("sessions").models.length == 0 && model.get("dataLists").models.length == 0){
                  //create the first session for this corpus.
                  var s = new Session({
                    pouchname : model.get("pouchname"),
                    sessionFields : model.get("sessionFields").clone()
                  }); //MUST be a new model, other wise it wont save in a new pouch.
                  s.get("sessionFields").where({label: "user"})[0].set("mask", window.app.get("authentication").get("userPrivate").get("username") );
                  s.get("sessionFields").where({label: "consultants"})[0].set("mask", "AA");
                  s.get("sessionFields").where({label: "goal"})[0].set("mask", "To explore the app and try entering/importing data");
                  s.get("sessionFields").where({label: "dateSEntered"})[0].set("mask", new Date());
                  s.get("sessionFields").where({label: "dateElicited"})[0].set("mask", "A few months ago, probably on a Monday night.");
                  s.set("pouchname", model.get("pouchname"));
                  s.saveAndInterConnectInApp(function(){
                    s.setAsCurrentSession(function(){
                      
                      //create the first datalist for this corpus.
                      var dl = new DataList({
                        pouchname : model.get("pouchname")}); //MUST be a new model, other wise it wont save in a new pouch.
                      dl.set({
                        "title" : "All Data",
                        "dateCreated" : (new Date()).toDateString(),
                        "description" : "This list contains all data in this corpus. " +
                        "Any new datum you create is added here. " +
                        "Data lists can be used to create handouts, prepare for sessions with consultants, " +
                        "export to LaTeX, or share with collaborators.",
                        "pouchname" : model.get("pouchname")
                      });
                      dl.saveAndInterConnectInApp(function(){
                        dl.setAsCurrentDataList(function(){
                          window.app.router.showDashboard();                          window.appView.toastUser("Created a new session and datalist, and loaded them into the dashboard. This might not have worked perfectly.<a href='goback'>Go Back</a>");
                          window.app.get("authentication").saveAndInterConnectInApp();
                          if(typeof successcallback == "function"){
                            successcallback();
                          }
                        });
                      });
                    });
                  });
                }else{
                  if(typeof successcallback == "function"){
                    successcallback();
                  }
                }
              });
            }else{
              //if an existing corpus
              window.app.get("authentication").saveAndInterConnectInApp();
              if(typeof successcallback == "function"){
                successcallback();
              }
            }
          },
          error : function(e) {
            if(typeof failurecallback == "function"){
              failurecallback();
            }else{
              alert('Corpus save error' + e);
            }
          }
        });
      });
    },
    /**
     * Accepts two functions success will be called if successful,
     * otherwise it will attempt to render the current corpus views. If
     * the corpus isn't in the current corpus it will call the fail
     * callback or it will alert a bug to the user. Override the fail
     * callback if you don't want the alert.
     * 
     * @param successcallback
     * @param failurecallback
     */
    setAsCurrentCorpus : function(successcallback, failurecallback){
      //TODO think about how to switch corpuses... maybe take the most recent session and data list and set those at the same time, it should be okay.
//      if( window.app.get("corpus").get("pouchname") != this.get("pouchname") ){
//        if (typeof failurecallback == "function") {
//          failurecallback();
//        }else{
//          alert("This is a bug, cannot load the corpus you asked for, it is not in this corpus. This will make the app reload.");
//        }
//        return;
//      }else{
        if (window.app.get("corpus").id != this.id ) {
          window.app.set("corpus", this);
        }
        window.app.get("authentication").get("userPrivate").get("mostRecentIds").corpusid = this.id;
        window.app.get("authentication").saveAndInterConnectInApp();
        
        try{
          window.appView.setUpAndAssociateViewsAndModelsWithCurrentCorpus(function() {
            if (typeof successcallback == "function") {
              successcallback();
            }else{
              window.appView.toastUser("Sucessfully connected all views up to corpus: "+ this.id,"alert-success","Connected!");
//            window.appView.renderEditableCorpusViews();
//            window.appView.renderReadonlyCorpusViews();
            }
          });
        }catch(e){
          if (typeof failurecallback == "function") {
            failurecallback();
          }else{
            alert("This is probably a bug. There was a problem rendering the current corpus's views after resetting the current session.");
          }
        }
    },
    /**
     * Synchronize the server and local databases. First to, then from.
     */
    replicateCorpus : function(couchConnection, successcallback, failurecallback) {
      var self = this;
      this.replicateToCorpus(couchConnection, function(){
        
        //if to was successful, call the from.
        self.replicateFromCorpus(couchConnection, successcallback, failurecallback );
        
      },function(){
        alert("Replicate to corpus failure");
        if(typeof fromcallback == "function"){
          fromcallback();
        }
      });
    },
    /**
     * Synchronize to server and from database.
     */
    replicateToCorpus : function(couchConnection, replicatetosuccesscallback, failurecallback) {
      var self = this;
      
      if(couchConnection == null || couchConnection == undefined){
        couchConnection = self.get("couchConnection");
      }
      this.changePouch(couchConnection, function(){
        self.pouch(function(err, db) {
          var couchurl = couchConnection.protocol+couchConnection.domain;
          if(couchConnection.port != null){
            couchurl = couchurl+":"+couchConnection.port;
          }
          couchurl = couchurl +"/"+ couchConnection.pouchname;
          
          db.replicate.to(couchurl, { continuous: false }, function(err, response) {
            Utils.debug("Replicate to " + couchurl);
            Utils.debug(response);
            Utils.debug(err);
            if(err){
              if(typeof failurecallback == "function"){
                failurecallback();
              }else{
                alert('Corpus replicate to error' + JSON.stringify(err));
                Utils.debug('Corpus replicate to error' + JSON.stringify(err));
              }
            }else{
              Utils.debug("Corpus replicate to success", response);
              window.appView.allSyncedDoc();
//              var teamid = "";
//              var teamname = "";
//              try{
//                teamid = model.get("team")._id; //Works if UserMask came from a mongodb id
//                teamname = model.get("team").get("username");
//              }catch(e){
//                Utils.debug("Problem getting team details for the activity", e);
//              }
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "to the team server",
                    indirectobjectmask : "to its team server",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
              window.app.get("currentCorpusUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "to the team server",
                    indirectobjectmask : "to its team server",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "personal"
                  }));
              
              
              
              //Replicate the team's activity feed, then call the sucess callback
              window.appView.activityFeedUserView.model.replicateToActivityFeed(null, function(){
                if(typeof replicatetosuccesscallback == "function"){
//                  window.appView.renderActivityFeedViews();
                  replicatetosuccesscallback();
                }else{
                  Utils.debug("ActivityFeed replicate to success");
                }
              });
            }
          });
        });
      });
    },
    /**
     * Synchronize from server to local database.
     */
    replicateFromCorpus : function(couchConnection, successcallback, failurecallback) {
      var self = this;
      
      if(couchConnection == null || couchConnection == undefined){
        couchConnection = self.get("couchConnection");
      }
      this.changePouch(couchConnection, function(){
        self.pouch(function(err, db) {
          var couchurl = couchConnection.protocol+couchConnection.domain;
          if(couchConnection.port != null){
            couchurl = couchurl+":"+couchConnection.port;
          }
          couchurl = couchurl +"/"+ couchConnection.pouchname;
          
          
          //We can leave the to and from replication async, and make two callbacks. 
          db.replicate.from(couchurl, { continuous: false }, function(err, response) {
            Utils.debug("Replicate from " + couchurl);
            Utils.debug(response);
            Utils.debug(err);
            if(err){
              if(typeof failurecallback == "function"){
                failurecallback();
              }else{
                alert('ActivityFeed replicate to error' + JSON.stringify(err));
                Utils.debug('ActivityFeed replicate to error' + JSON.stringify(err));
              }
            }else{
              Utils.debug("Corpus replicate from success", response);

              //This was a valid connection, lets save it into localstorage.
              localStorage.setItem("mostRecentCouchConnection",JSON.stringify(couchConnection));
              
              if(typeof successcallback == "function"){
                successcallback();
              }
              
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "from the team server",
                    indirectobjectmask : "from its team server",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
              window.app.get("currentCorpusUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "from the team server",
                    indirectobjectmask : "from its team server",<commit_msg>found a bug in the currentUserActivityFeed<commit_after>define([ 
    "backbone",
    "activity/Activity",
    "comment/Comment",
    "comment/Comments",
    "corpus/CorpusMask",
    "confidentiality_encryption/Confidential",
    "datum/DatumField",
    "datum/DatumFields",
    "datum/DatumState",
    "datum/DatumStates",
    "data_list/DataList",
    "data_list/DataLists",
    "user/Consultants",
    "lexicon/Lexicon",
    "permission/Permission",
    "permission/Permissions",
    "datum/Session",
    "datum/Sessions",
    "user/Team",
    "user/User",
    "user/Users",
    "user/UserMask",
    "glosser/Glosser",
    "libs/Utils"
], function(
    Backbone, 
    Activity,
    Comment, 
    Comments,
    CorpusMask,
    Confidential,
    DatumField,
    DatumFields, 
    DatumState,
    DatumStates,
    DataList,
    DataLists,
    Consultants,
    Lexicon,
    Permission,
    Permissions,
    Session,
    Sessions,
    Team,
    User,
    Users,
    UserMask
) {
  var Corpus = Backbone.Model.extend(
  /** @lends Corpus.prototype */
  {
    /**
     * @class A corpus is like a git repository, it has a remote, a title
     *        a description and perhaps a readme When the user hits sync
     *        their "branch" of the corpus will be pushed to the central
     *        remote, and we will show them a "diff" of what has
     *        changed.
     * 
     * The Corpus may or may not be a git repository, so this class is
     * to abstract the functions we would expect the corpus to have,
     * regardless of how it is really stored on the disk.
     * 
     * 
     * @property {String} title This is used to refer to the corpus, and
     *           what appears in the url on the main website eg
     *           http://fieldlinguist.com/Sapir/SampleFieldLinguisticsCorpus
     * @property {String} description This is a short description that
     *           appears on the corpus details page
     * @property {String} remote The git url of the remote eg:
     *           <EMAIL>:Sapir/SampleFieldLinguisticsCorpus.git
     *           
     * @property {Consultants} consultants Collection of consultants who contributed to the corpus
     * @property {DatumStates} datumstates Collection of datum states used to describe the state of datums in the corpus 
     * @property {DatumFields} datumfields Collection of datum fields used in the corpus
     * @property {Sessions} sessions Collection of sessions that belong to the corpus
     * @property {DataLists} datalists Collection of data lists created under the corpus
     * @property {Permissions} permissions Collection of permissions groups associated to the corpus 
     * 
     *           
     * @property {Glosser} glosser The glosser listens to
     *           orthography/utterence lines and attempts to guess the
     *           gloss.
     * @property {Lexicon} lexicon The lexicon is a list of morphemes,
     *           allomorphs and glosses which are used to index datum, and
     *           also to gloss datum.
     * 
     * @description The initialize function probably checks to see if
     *              the corpus is new or existing and brings it down to
     *              the user's client.
     * 
     * @extends Backbone.Model
     * @constructs
     */
    initialize : function() {
      Utils.debug("CORPUS INIT");
      if(!this.get("confidential")){
        this.set("confidential", new Confidential() )
      }
      
      if(!this.get("publicSelf")){
        this.set("publicSelf", new CorpusMask({
          "couchConnection" : this.get("couchConnection"),
          "pouchname" : this.get("pouchname")
        }));
      }
      if(!this.get("publicCorpus")){
        this.set("publicCorpus", "Private");
      }
      this.bind("change:publicCorpus",this.changeCorpusPublicPrivate,this);
      
      if(typeof(this.get("datumStates")) == "function"){
        this.set("datumStates", new DatumStates([ 
          new DatumState({
            state : "Checked",
            color : "success",
            selected: "selected"
          }),
          new DatumState({
            state : "To be checked",
            color : "warning"
          }),
          , new DatumState({
            state : "Deleted",
            color : "important",
            showInSearchResults:  ""
          }),
        ]));
      }//end if to set datumStates
      
      if(typeof(this.get("datumFields")) == "function"){
        this.set("datumFields", new DatumFields([ 
          new DatumField({
            label : "judgement",
            size : "3",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "Use this field to establish your team's gramaticality/acceptablity judgements (*,#,? etc)"
          }),
          new DatumField({
            label : "utterance",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "Use this as Line 1 in your examples for handouts (ie, either Orthography, or phonemic/phonetic representation)"
          }),
          new DatumField({
            label : "morphemes",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "This line is used to determine the morpheme segmentation to generate glosses, it also optionally can show up in your LaTeXed examples if you choose to show morpheme segmentation in addtion ot line 1, gloss and translation."
          }),
          new DatumField({
            label : "gloss",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "This line appears in the gloss line of your LaTeXed examples, we reccomend Leipzig conventions (. for fusional morphemes, - for morpehem boundaries etc) The system uses this line to partially help you in glossing. "
          }),
          new DatumField({
            label : "translation",
            shouldBeEncrypted: "checked",
            userchooseable: "disabled",
            help: "Use this as your primary translation. It does not need to be English, simply a language your team is comfortable with. If your consultant often gives you multiple languages for translation you can also add addtional translations in the customized fields. For example, your Quechua informants use Spanish for translations, then you can make all Translations in Spanish, and add an additional field for English if you want to generate a handout containing the datum. "
          })
        ]));
      }//end if to set datumFields
      
      if(typeof(this.get("sessionFields")) == "function"){
        this.set("sessionFields", new DatumFields([ 
           new DatumField({
             label : "goal",
             shouldBeEncrypted: "",
             userchooseable: "disabled",
             help: "This describes the goals of the session."
           }),  
          new DatumField({
            label : "consultants",
            shouldBeEncrypted: "",
            userchooseable: "disabled"
          }),
          new DatumField({
            label : "dialect",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "You can use this field to be as precise as you would like about the dialect of this session."
          }),
          new DatumField({
            label : "language",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "This is the langauge (or language family) if you would like to use it."
          }),
          new DatumField({
            label : "dateElicited",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "This is the date in which the session took place."
          }),
          new DatumField({
            label : "user",
            shouldBeEncrypted: "",
            userchooseable: "disabled"
          }),
          new DatumField({
            label : "dateSEntered",
            shouldBeEncrypted: "",
            userchooseable: "disabled",
            help: "This is the date in which the session was entered."
          }),
        ]));
        
      }//end if to set sessionFields
      
      
      // If there are no comments, create models
      if (!this.get("comments")) {
        this.set("comments", new Comments());
      }
      
      if (!this.get("dataLists")) {
        this.set("dataLists", new DataLists());
      }
      
      if (!this.get("sessions")) {
        this.set("sessions", new Sessions());
      }
      //this.loadPermissions();
      
    },
    loadPermissions: function(){
      if (!this.get("team")){
        //If app is completed loaded use the user, otherwise put a blank user
        if(window.appView){
          this.set("team", window.app.get("authentication").get("userPublic"));
//          this.get("team").id = window.app.get("authentication").get("userPublic").id;
        }else{
//          this.set("team", new UserMask({pouchname: this.get("pouchname")}));
        }
      }
      this.permissions = new Permissions();
      var admins = new Users();
      if(this.get("team")){
        admins.models.push(this.get("team"));
      }
      this.permissions.add(new Permission({
        users: admins,
        role: "admin",
        pouchname: this.get("pouchname")
      }));
      
      this.permissions.add(new Permission({
        users: new Users(),
        role: "contributor",
        pouchname: this.get("pouchname")
      }));
      this.permissions.add(new Permission({
        users: new Users(), //"ifieldpublicuser"
        role: "collaborator",
        pouchname: this.get("pouchname")
      }));
      //TODO load the permissions in from the server.
    },
    
    defaults : {
      title : "Untitled Corpus",
      titleAsUrl :"UntitledCorpus",
      description : "This is an untitled corpus, created by default.",
//      confidential :  Confidential,
      consultants : Consultants,
      datumStates : DatumStates,
      datumFields : DatumFields, 
      sessionFields : DatumFields,
      searchFields : DatumFields,
      couchConnection : JSON.parse(localStorage.getItem("mostRecentCouchConnection")) || Utils.defaultCouchConnection()
    },
    
    // Internal models: used by the parse function
    model : {
      confidential :  Confidential,
      consultants : Consultants,
      datumStates : DatumStates,
      datumFields : DatumFields, 
      sessionFields : DatumFields,
      searchFields : DatumFields,
      sessions : Sessions, 
      dataLists : DataLists, 
      publicSelf : CorpusMask,
      comments: Comments,
      team: UserMask
    },
//    glosser: new Glosser(),//DONOT store in attributes when saving to pouch (too big)
    lexicon: new Lexicon(),//DONOT store in attributes when saving to pouch (too big)
    changePouch : function(couchConnection, callback) {
      if (couchConnection == null || couchConnection == undefined) {
        couchConnection = this.get("couchConnection");
      }
      if (this.pouch == undefined) {
        this.pouch = Backbone.sync
        .pouch(Utils.androidApp() ? Utils.touchUrl
            + couchConnection.pouchname : Utils.pouchUrl
            + couchConnection.pouchname);
      }

      if (typeof callback == "function") {
        callback();
      }
    }, 
    /**
     * Accepts two functions to call back when save is successful or
     * fails. If the fail callback is not overridden it will alert
     * failure to the user.
     * 
     * - Adds the corpus to the corpus if it is in the right corpus, and wasn't already there
     * - Adds the corpus to the user if it wasn't already there
     * - Adds an activity to the logged in user with diff in what the user changed. 
     * 
     * @param successcallback
     * @param failurecallback
     */
    saveAndInterConnectInApp : function(successcallback, failurecallback){
      Utils.debug("Saving the Corpus");
      var self = this;
      var newModel = false;
      if(!this.id){
        newModel = true;
        
        if(!this.get("pouchname")){
          this.set("pouchname", this.get("team").get("username")
              +"-"+this.get("title").replace(/[^a-zA-Z0-9-._~ ]/g,"") ) ;
        }
        if(!this.get("couchConnection")){
          this.get("couchConnection").pouchname = this.get("team").get("username")
          +"-"+this.get("title").replace(/[^a-zA-Z0-9-._~ ]/g,"") ;
        }
      }
      var oldrev = this.get("_rev");
      
      /*
       * For some reason the corpus is getting an extra state that no one defined in it. this gets rid of it when we save.
       */
      try{
        var ds = this.get("datumStates").models;
        for (var s in ds){
          if(ds[s].get("state") == undefined){
            this.get("datumStates").remove(ds[s]);
          }
        }
      }catch(e){
        Utils.debug("Removing empty states work around failed some thing was wrong.",e);
      }
      
      
      this.changePouch(null,function(){
        self.save(null, {
          success : function(model, response) {
            Utils.debug('Corpus save success');
            var title = model.get("title");
            var differences = "#diff/oldrev/"+oldrev+"/newrev/"+response._rev;
            //TODO add privacy for corpus in corpus
//            if(window.app.get("corpus").get("keepCorpusDetailsPrivate")){
//              title = "";
//              differences = "";
//            }
            //save the corpus mask too
            var publicSelfMode = model.get("publicSelf");
            publicSelfMode.changePouch( model.get("couchConnection"), function(){
              publicSelfMode.saveAndInterConnectInApp();
            });
            
            if(window.appView){
              window.appView.toastUser("Sucessfully saved corpus: "+ title,"alert-success","Saved!");
              window.appView.addSavedDoc(model.id);
            }
            var verb = "updated";
            if(newModel){
              verb = "added";
            }
            var teamid = model.get("team").id; //Works if UserMask was saved
            if(!teamid){
              //TODO test this, this is to protect from the case wher the id of the team is not set yet.
//              window.app.get("authentication").get("userPublic").saveAndInterConnectInApp(function(){
//                window.app.get("corpus").set("team", window.app.get("authentication").get("userPublic"));
//                window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
//                    new Activity({
//                      verb : verb,
//                      directobject : "<a href='#corpus/"+window.app.get("corpus").id+"'>corpus "+title+"</a> ",
//                      indirectobject : "owned by <a href='#user/"+window.app.get("corpus").get("team").id+"'>"+window.app.get("corpus").get("team").get("username")+"</a>",
//                      context : differences+" via Offline App."
//                    }));
//              });
              teamid = model.get("team")._id; //Works if UserMask came from a mongodb id
              if(!teamid){
                if(model.get("team").get("username") == window.app.get("authentication").get("userPrivate").get("username")){
                  teamid = window.app.get("authentication").get("userPrivate").id; //Assumes the user private and team are the same user...this is dangerous
                }
              }
              /**
               * The idea of the masks in the activity
               * is that the teams/users can make a
               * public activity feed, which they create
               * a special widget user for, and if the
               * widget user asks for activities, the
               * map reduce function returns only the
               * masks, whcih means that the original
               * activities are protected. so if the
               * activity is soemthing that you might
               * want to appear in a really public feed,
               * then add a mask to it, and it will
               * automatically appear. this can probably
               * be done for all activities later. Right
               * now its only in the syncing aspect so
               * at least we can test the map reduce
               * function.
               */
              window.app.get("currentUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "personal"
                  }));
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
            }else{
              window.app.get("currentUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>"+model.get("team").get("username")+"</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "personal"
                  }));
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "<a href='"+differences+"'>"+verb+"</a> ",
                    verbmask : verb,
                    directobject : "<a href='#corpus/"+model.id+"'>corpus: "+title+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    indirectobject : "owned by <a href='#user/"+teamid+"'>this team</a>",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
            }
            
            //make sure the corpus is in the history of the user
            if(window.app.get("authentication").get("userPrivate").get("corpuses").indexOf(model.get("couchConnection")) == -1){
              window.app.get("authentication").get("userPrivate").get("corpuses").unshift(model.get("couchConnection"));
            }
            /*
             * Save the default data list, in case it has changed without being saved in pouch. 
             */
            if(model.get("dataLists").length > 0){
              var defaultDatalist = model.get("dataLists").models[model.get("dataLists").length - 1];

              if(defaultDatalist.needsSave){
//                defaultDatalist.changePouch(null, function(){
//                  Utils.debug("Saving the default datalist because it was changed by adding datum, and it wasn't the current data list so it is was the 'active' defualt datalist.");
//                  defaultDatalist.save();
                //TODO uncomment this
//                });
              }
            }
            if(newModel){
              //TODO something similar to saving the current dashboard so the user can go back.
//              window.app.saveAndInterConnectInApp(function(){
               
              self.setAsCurrentCorpus(function(){

                if(model.get("sessions").models.length == 0 && model.get("dataLists").models.length == 0){
                  //create the first session for this corpus.
                  var s = new Session({
                    pouchname : model.get("pouchname"),
                    sessionFields : model.get("sessionFields").clone()
                  }); //MUST be a new model, other wise it wont save in a new pouch.
                  s.get("sessionFields").where({label: "user"})[0].set("mask", window.app.get("authentication").get("userPrivate").get("username") );
                  s.get("sessionFields").where({label: "consultants"})[0].set("mask", "AA");
                  s.get("sessionFields").where({label: "goal"})[0].set("mask", "To explore the app and try entering/importing data");
                  s.get("sessionFields").where({label: "dateSEntered"})[0].set("mask", new Date());
                  s.get("sessionFields").where({label: "dateElicited"})[0].set("mask", "A few months ago, probably on a Monday night.");
                  s.set("pouchname", model.get("pouchname"));
                  s.saveAndInterConnectInApp(function(){
                    s.setAsCurrentSession(function(){
                      
                      //create the first datalist for this corpus.
                      var dl = new DataList({
                        pouchname : model.get("pouchname")}); //MUST be a new model, other wise it wont save in a new pouch.
                      dl.set({
                        "title" : "All Data",
                        "dateCreated" : (new Date()).toDateString(),
                        "description" : "This list contains all data in this corpus. " +
                        "Any new datum you create is added here. " +
                        "Data lists can be used to create handouts, prepare for sessions with consultants, " +
                        "export to LaTeX, or share with collaborators.",
                        "pouchname" : model.get("pouchname")
                      });
                      dl.saveAndInterConnectInApp(function(){
                        dl.setAsCurrentDataList(function(){
                          window.app.router.showDashboard();                          window.appView.toastUser("Created a new session and datalist, and loaded them into the dashboard. This might not have worked perfectly.<a href='goback'>Go Back</a>");
                          window.app.get("authentication").saveAndInterConnectInApp();
                          if(typeof successcallback == "function"){
                            successcallback();
                          }
                        });
                      });
                    });
                  });
                }else{
                  if(typeof successcallback == "function"){
                    successcallback();
                  }
                }
              });
            }else{
              //if an existing corpus
              window.app.get("authentication").saveAndInterConnectInApp();
              if(typeof successcallback == "function"){
                successcallback();
              }
            }
          },
          error : function(e) {
            if(typeof failurecallback == "function"){
              failurecallback();
            }else{
              alert('Corpus save error' + e);
            }
          }
        });
      });
    },
    /**
     * Accepts two functions success will be called if successful,
     * otherwise it will attempt to render the current corpus views. If
     * the corpus isn't in the current corpus it will call the fail
     * callback or it will alert a bug to the user. Override the fail
     * callback if you don't want the alert.
     * 
     * @param successcallback
     * @param failurecallback
     */
    setAsCurrentCorpus : function(successcallback, failurecallback){
      //TODO think about how to switch corpuses... maybe take the most recent session and data list and set those at the same time, it should be okay.
//      if( window.app.get("corpus").get("pouchname") != this.get("pouchname") ){
//        if (typeof failurecallback == "function") {
//          failurecallback();
//        }else{
//          alert("This is a bug, cannot load the corpus you asked for, it is not in this corpus. This will make the app reload.");
//        }
//        return;
//      }else{
        if (window.app.get("corpus").id != this.id ) {
          window.app.set("corpus", this);
        }
        window.app.get("authentication").get("userPrivate").get("mostRecentIds").corpusid = this.id;
        window.app.get("authentication").saveAndInterConnectInApp();
        
        try{
          window.appView.setUpAndAssociateViewsAndModelsWithCurrentCorpus(function() {
            if (typeof successcallback == "function") {
              successcallback();
            }else{
              window.appView.toastUser("Sucessfully connected all views up to corpus: "+ this.id,"alert-success","Connected!");
//            window.appView.renderEditableCorpusViews();
//            window.appView.renderReadonlyCorpusViews();
            }
          });
        }catch(e){
          if (typeof failurecallback == "function") {
            failurecallback();
          }else{
            alert("This is probably a bug. There was a problem rendering the current corpus's views after resetting the current session.");
          }
        }
    },
    /**
     * Synchronize the server and local databases. First to, then from.
     */
    replicateCorpus : function(couchConnection, successcallback, failurecallback) {
      var self = this;
      this.replicateToCorpus(couchConnection, function(){
        
        //if to was successful, call the from.
        self.replicateFromCorpus(couchConnection, successcallback, failurecallback );
        
      },function(){
        alert("Replicate to corpus failure");
        if(typeof fromcallback == "function"){
          fromcallback();
        }
      });
    },
    /**
     * Synchronize to server and from database.
     */
    replicateToCorpus : function(couchConnection, replicatetosuccesscallback, failurecallback) {
      var self = this;
      
      if(couchConnection == null || couchConnection == undefined){
        couchConnection = self.get("couchConnection");
      }
      this.changePouch(couchConnection, function(){
        self.pouch(function(err, db) {
          var couchurl = couchConnection.protocol+couchConnection.domain;
          if(couchConnection.port != null){
            couchurl = couchurl+":"+couchConnection.port;
          }
          couchurl = couchurl +"/"+ couchConnection.pouchname;
          
          db.replicate.to(couchurl, { continuous: false }, function(err, response) {
            Utils.debug("Replicate to " + couchurl);
            Utils.debug(response);
            Utils.debug(err);
            if(err){
              if(typeof failurecallback == "function"){
                failurecallback();
              }else{
                alert('Corpus replicate to error' + JSON.stringify(err));
                Utils.debug('Corpus replicate to error' + JSON.stringify(err));
              }
            }else{
              Utils.debug("Corpus replicate to success", response);
              window.appView.allSyncedDoc();
//              var teamid = "";
//              var teamname = "";
//              try{
//                teamid = model.get("team")._id; //Works if UserMask came from a mongodb id
//                teamname = model.get("team").get("username");
//              }catch(e){
//                Utils.debug("Problem getting team details for the activity", e);
//              }
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "to the team server",
                    indirectobjectmask : "to its team server",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
              window.app.get("currentUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "to the team server",
                    indirectobjectmask : "to its team server",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "personal"
                  }));
              
              
              
              //Replicate the team's activity feed, then call the sucess callback
              window.appView.activityFeedUserView.model.replicateToActivityFeed(null, function(){
                if(typeof replicatetosuccesscallback == "function"){
//                  window.appView.renderActivityFeedViews();
                  replicatetosuccesscallback();
                }else{
                  Utils.debug("ActivityFeed replicate to success");
                }
              });
            }
          });
        });
      });
    },
    /**
     * Synchronize from server to local database.
     */
    replicateFromCorpus : function(couchConnection, successcallback, failurecallback) {
      var self = this;
      
      if(couchConnection == null || couchConnection == undefined){
        couchConnection = self.get("couchConnection");
      }
      this.changePouch(couchConnection, function(){
        self.pouch(function(err, db) {
          var couchurl = couchConnection.protocol+couchConnection.domain;
          if(couchConnection.port != null){
            couchurl = couchurl+":"+couchConnection.port;
          }
          couchurl = couchurl +"/"+ couchConnection.pouchname;
          
          
          //We can leave the to and from replication async, and make two callbacks. 
          db.replicate.from(couchurl, { continuous: false }, function(err, response) {
            Utils.debug("Replicate from " + couchurl);
            Utils.debug(response);
            Utils.debug(err);
            if(err){
              if(typeof failurecallback == "function"){
                failurecallback();
              }else{
                alert('ActivityFeed replicate to error' + JSON.stringify(err));
                Utils.debug('ActivityFeed replicate to error' + JSON.stringify(err));
              }
            }else{
              Utils.debug("Corpus replicate from success", response);

              //This was a valid connection, lets save it into localstorage.
              localStorage.setItem("mostRecentCouchConnection",JSON.stringify(couchConnection));
              
              if(typeof successcallback == "function"){
                successcallback();
              }
              
              window.app.get("currentCorpusTeamActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "from the team server",
                    indirectobjectmask : "from its team server",
                    context : " via Offline App.",
                    contextmask : "",
                    teamOrPersonal : "team"
                  }));
              window.app.get("currentUserActivityFeed").get("activities").unshift(
                  new Activity({
                    verb : "sycned",
                    verbmask : "synced",
                    directobject : "<a href='#corpus/"+self.id+"'>"+self.get('title')+"</a>",
                    directobjectmask : "a corpus",
                    indirectobject : "from the team server",
                    indirectobjectmask : "from its team server",
{'fraction_non_alphanumeric': 0.1194923626415381, 'fraction_numerical': 0.0005167565924462345, 'mean_word_length': 1.7650025214321734, 'pattern_counts': {'":': 4, '<': 97, '<?xml version=': 0, '>': 99, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5967534', 'n_tokens_mistral': 17281, 'n_tokens_neox': 16612, 'n_words': 4508}