/**
 * TODO: add js header
 */

define([
    "dojo",
    "dojo/cookie",
    "dojo/json",
    "dojo/ready",
    "coweb/main",
    "rcolor",
], function(dojo, cookie, json, ready, coweb, RColor) {
	
    var CoRenkan = function() {
    };

    var proto = CoRenkan.prototype;
       
    proto.init = function() {
    	console.log("ready callback", dojo.config.corenkanConfig);
    	
    	this.renkan = dojo.config.corenkanConfig.renkan;
    	this.project = dojo.config.corenkanConfig.renkan.project;

    	this.initCollab(dojo.config.corenkanConfig.projectId);
    	
    	
    	var that = this;
    	
    	ready(function() {
    		
	    	var sess = coweb.initSession();
	    	that.session = sess;
	
	    	sess.prepare({userDefined: {project_id:dojo.config.corenkanConfig.projectId}, collab: true}).then(function(data) {
	    		console.log("Prepare ok : ", data);
	        	that.setObjects();
	    	});

	    	sess.onStatusChange = function(status) {
	    	    console.log(status);
	    	    that.onInternalStatusChange(status);
	    	    if(typeof that.onStatusChange === "function") {
	    	    	that.onStatusChange(status);
	    	    }
	    	};
	    	    	
    	});
                
    };
    
    proto.initCollab = function(id) {
    	
    	console.log("init collabbs objects " + "users_" + id + ", " + "renkan_" + id);
    	
    	this.users_collab = coweb.initCollab({id: "users_" + id});
    	
    	this.users_collab.subscribeReady(this, "onLocalJoin");        
        this.users_collab.subscribeSiteJoin(this, 'onRemoteJoin');
        this.users_collab.subscribeSiteLeave(this, 'onRemoteLeave');
    	this.users_collab.subscribeSync("roster", this, "onRemoteRosterChange");

    	this.users_collab.subscribeStateResponse(this, "onUsersStateResponse");

    	
    	this.collab = coweb.initCollab({id : "renkan_" + id});    	    	    	
    	
    	this.collab.subscribeSync("project", this, "onRemoteProjectChange");
    	this.collab.subscribeSync("user", this, "onRemoteUserChange");
    	this.collab.subscribeSync("node", this, "onRemoteNodeChange");
    	this.collab.subscribeSync("edge", this, "onRemoteEdgeChange");

    	this.collab.subscribeStateResponse(this, "onStateResponse");    	
    	
    };
    
    proto.onLocalJoin = function(params) {
    	console.log("Local join", params);
    	this.current_site = params.site;
    	if(typeof this.renkan !== "undefined" && this.renkan != null && typeof this.renkan.current_user != "undefined") {
    		this.renkan.current_user.set("site_id", params.site);
    	}
    };
    
    
    proto.onRemoteJoin = function(params) {
    	console.log("Remote join", params);
    	// do nothing
    };
    
    proto.onRemoteLeave = function(params) {
    	console.log("Remote leave", params);
    	
    	// remove remote site from current_user_list
    	if(typeof this.renkan === "undefined" || this.renkan == null || typeof this.renkan.current_user_list === "undefined" || this.renkan.current_user_list == null) {
    		return;
    	}
    	for ( var user in this.renkan.current_user_list.filter(function(u) { return u.get("site_id") == params.site; })) {
			this.renkan.current_user_list.remove(user);
		}
    	
    };
    
    proto.onUsersStateResponse = function(state) {
    	
    	user_list = json.parse(state);
    	console.log("Users State response", user_list);
    	_.each(user_list, function(user, i, l) {
    		user['_id'] = user['id'];
    	});    	
    	this.renkan.current_user_list.reset(user_list, {silent: true});
    };
    
    proto.onStateResponse = function(state) {
    	obj = json.parse(state);
    	console.log("State response", obj);
    	obj['_id'] = obj['id'];
    	this.project.set(obj, {validate: true});
    	this.renkan.renderer.autoScale();
    };
    
    proto.onInternalStatusChange = function(status) {
    	if(status == "ready") {
    	    this.renkan.read_only = false;
    		this.renkan.onStatusChange();
    	}
    	else {
            this.renkan.read_only = true;
            this.renkan.onStatusChange();                    		
    	}
    };
    
    function prepareValues(obj,c) {
		values = {};
		for(var fieldname in c.changes) {
			if(c.changes[fieldname]) {
				values[fieldname] = obj.get(fieldname);
			}    			
		}
		return values;
    }
    
    proto.addObjectBind = function(type, obj, c, options, collab) {
		console.log("add " + type,obj, c, options);
		if(this.project == null) {
			console.log("null project exiting");
			return;
		}
		var values = obj.toJSON();
		var new_values = {
			id: obj.id,
			_type: type,
			_index: options.index,
			_project_id : obj.get("project").get("_id"),
			_user_id : (this.project.current_user!=null)?this.project.current_user.id:null
		};
		for(var k in new_values) {
			values[k] = new_values[k];
		}
		console.log("add values : ", values);
		collab.sendSync(type, values, "insert", options.index);
    };
    
    proto.removeObjectBind = function(type, obj, c, options, collab) {
		console.log("delete " + type,obj, c, options);
		var values = {
    	    id: obj.id,
    	    _type: type,
    	    _index: options.index,
    	    _project_id : obj.get("project").id,
    	    _user_id : (this.project.current_user!=null)?this.project.current_user.id:null
    	};
		collab.sendSync(type, values, "delete", options.index);    	
    };
    
    proto.updateObjectBind = function(type, obj, options, collab) {
		console.log("change " + type,obj, options);
		if(typeof obj != "undefined" && obj.hasChanged()) {
			var values = {
			    id: obj.id,
	    	    _type: type,
	    	    _project_id : obj.get("project").id,
	    	    _user_id : (this.project.current_user!=null)?this.project.current_user.id:null
	    	};
			_.extend(values,obj.changed);		
	    	collab.sendSync(type, values);
		}
    };
    
    /**
     * Called when an abject is changed
     * 
     */
    proto.objectChange = function(event, model, collection, options) {
    	
		console.log("project change all ", event, model, collection, options);
		// check that current user is in user list of the project
		
		if(this.project == null || this.project.current_user == null) {
			return;
		}
		var current_user = this.project.current_user;
		
		if(this.project.get("users").get(current_user.id) == null) {
			var props = current_user.toJSON();
			this.project.addUser(props);
		}
		
	};

    
    proto.setObjects = function() {
    	
    	console.log("Cookie BAYEUX_BROWSER : " + cookie("BAYEUX_BROWSER"));
    	var renkan = this.renkan;
    	var project = renkan.project;
    	this.setProject(project);
    	this.setRenkan(renkan);
    	this.setUser(renkan);

    };
    
    proto.setRenkan = function(renkan) {
    	
    	console.log("Set Renkan");
    		
    	var that = this;
    	
    	renkan.current_user_list.bind("add", function(obj, c, options) {
    		that.addObjectBind("roster", obj, c, options, that.users_collab);
    	});
    	//renkan.current_user_list.bind("remove", function(obj, c, options) {
    	//	that.removeObjectBind("_roster", obj, c, options, that.users_collab);
    	//});
    	renkan.current_user_list.bind("change", function(obj, options) {
    		that.updateObjectBind("roster", obj, options, that.users_collab);
    	});
    	
    	renkan.current_user_list.bind("change", function(obj, options) {
    		console.log("update roster",obj, options);
    		// get user in project
    		project = obj.get("project");
    		if(project == null) {
    			console.log("null project return");
    			return;
    		}
    		user = project.get("users").get(obj.id);
    		if(user == null) {
    			console.log("user " + obj.id + " not in project. return");
    			return;
    		}
    		for(att in obj.changed) {
    			new_val = obj.changed[att];
    			if(user.get("att") != new_val) {
    				user.set(att, new_val);
    			}
    		}
    	});
    	    	
    };
    
    proto.setUser = function(renkan) {
    	console.log("set user : " + cookie("BAYEUX_BROWSER"));
    	
    	if(typeof renkan === "undefined" || typeof renkan.project === "undefined" || renkan.project == null) {
    		return;
    	}
    	
    	var user_id = cookie("BAYEUX_BROWSER");
    	var project = renkan.project;
    	
    	var puser = project.get("users").get(user_id);
    	var puser_def = null;
    	if(puser == null) {
    		color = new RColor();
    		puser_def = {
    		    id: user_id,
                title: "anonymous",
                project: project,
                color: color.get(true, 0.5, 0.8),
                site_id: this.current_site
            };
    	}
    	else {
    		puser_def = puser.toJSON();
    		puser_def.project = project;
    	}
    	puser = renkan.current_user_list.push(puser_def);
    	
    	project.current_user = puser;
    	renkan.current_user = puser.id;
    	
    	var that = this;
    	
    	project.once("all", function(event, model, collection, options){
    		that.objectChange(event, model, collection, options);
    	});
    	project.get("nodes").once("all", function(event, model, collection, options){
    		that.objectChange(event, model, collection, options);
    	});
    	project.get("edges").once("all", function(event, model, collection, options){
    		that.objectChange(event, model, collection, options);
    	});

    	
    };
    
    proto.setProject = function(project) {
    	
    	console.log("project", project);
    	    	
    	var projectFields = ["title", "description", "uri"];
    	var that = this;
    	    	
    	for(var fieldIndex in projectFields) {
    		(function(fi){
	    		var field = projectFields[fi];
	        	project.bind("change:"+field, function(obj, c) {
	        		console.log(c);
	        		values = {
	        		    id: obj.id,
	        		    _type: "project",
	        		    _project_id: obj.id,
	        		    _user_id : (that.project.current_user!=null)?that.project.current_user.id:null
	        		};
	        		values[field] = c;
	        		that.collab.sendSync("project", values);
	        	});
    		})(fieldIndex);
    	}
    	
    	
    	project.get("nodes").bind("add", function(obj, c, options) {    		
    		that.addObjectBind("node", obj, c, options, that.collab);
    	});
    	
    	project.get("nodes").bind("remove", function(obj, c, options) {
    		that.removeObjectBind("node", obj, c, options, that.collab);
    	});
    	
    	project.get("nodes").bind("change", function(obj, options) {
    		that.updateObjectBind("node", obj, options, that.collab);
    	});

    	project.get("users").bind("add", function(obj, c, options) {    		
    		that.addObjectBind("user", obj, c, options, that.collab);
    	});
    	
    	project.get("users").bind("remove", function(obj, c, options) {
    		that.removeObjectBind("user", obj, c, options, that.collab);
    	});
    	
    	project.get("users").bind("change", function(obj, options) {
    		that.updateObjectBind("user", obj, options, that.collab);
    	});

    	project.get("edges").bind("add", function(obj, c, options) {    		
    		that.addObjectBind("edge", obj, c, options, that.collab);
    	});
    	
    	project.get("edges").bind("remove", function(obj, c, options) {
    		that.removeObjectBind("edge", obj, c, options, that.collab);
    	});
    	
    	project.get("edges").bind("change", function(obj, options) {
    		that.updateObjectBind("edge", obj, options, that.collab);
    	});
    	
    };
    
    
    /**
     * Called when a remote data store for project changes in some manner. Dispatches to
     * local methods for insert, update, delete handling.
     * TODO: manage project list change on server
     * @param args Cooperative web event
     */
    proto.onRemoteProjectChange = function(args) {    	
    	console.log("Remote project change", args);
    	if (args.type === "update") {
            this.onRemoteProjectUpdate(args.value, args.position);
    	}
    };
    

    /**
     * Called when a remote data store for nodes changes in some manner. Dispatches to
     * local methods for insert, update, delete handling.
     * @param args Cooperative web event
     */
    proto.onRemoteObjectChange = function(field, args) {
    	
    	console.log("Remote "+ field +" change",args);
        if (args.type === "insert") {
            this.onRemoteObjectInsert(field, args.value, args.position);
        } else if (args.type === "update") {
            this.onRemoteObjectUpdate(field, args.value, args.position);
        } else if (args.type === "delete") {
            this.onRemoteObjectDelete(field, args.position);
        }
    };

    
    /**
     * Called when a remote data store for nodes changes in some manner. Dispatches to
     * local methods for insert, update, delete handling.
     * @param args Cooperative web event
     */
    proto.onRemoteNodeChange = function(args) {
    	this.onRemoteObjectChange("nodes", args);
    };


    /**
     * Called when a remote data store for nodes changes in some manner. Dispatches to
     * local methods for insert, update, delete handling.
     * @param args Cooperative web event
     */
    proto.onRemoteUserChange = function(args) {    	
    	this.onRemoteObjectChange("users", args);
    };

    /**
     * Called when a remote data store for nodes changes in some manner. Dispatches to
     * local methods for insert, update, delete handling.
     * @param args Cooperative web event
     */
    proto.onRemoteEdgeChange = function(args) {    	
    	this.onRemoteObjectChange("edges", args);
    };
    
    /**
     * Called when a remote data store for nodes changes in some manner. Dispatches to
     * local methods for insert, update, delete handling.
     * @param args Cooperative web event
     */
    proto.onRemoteRosterChange = function(args) {    	
    	this.onRemoteObjectChange(this.renkan.current_user_list, args);
    };
    
    /**
     * Called when a project attribute changes value in a remote data store.
     * Updates the attribute value of the item with the same id in the local
     * data store.
     *
     * @param value Item data sent by remote data store
     * @param position Which item to update.
     */
    proto.onRemoteProjectUpdate = function(values, position) {
    	var project_id = values['id'];
    	if(typeof(project_id) === "undefined") {
    		return;
		}
    	
    	if(this.project != null && project_id == this.project.id) {
    		for(var fieldname in values) {
    			if(fieldname != "id" && fieldname != "type") {
    				this.project.set(fieldname, values[fieldname]);
    			}
    		}
    	}
    	
    };    

    /**
     * Called when an object is inserted in a remote data store.
     *
     * @param field_coll A collection or a string for one of the project collection
     * @param value Item data sent by remote data store
     * @param position Which item to update.
     */
    proto.onRemoteObjectInsert = function(field_coll, values, position) {
    	
    	console.log("Remote ", field_coll ," insert values ", values, "position", position);
    	
    	var coll = null;    	
    	if(typeof field_coll === "string") {
    		coll = this.project.get(field_coll);
    	}
    	else {
    		coll = field_coll;
    	}

    	var object_id = values['id'];    	
    	
    	var obj = coll.get(object_id);
    	
    	if(obj != null) {
    		this.onRemoteObjectUpdate(field_coll, values, coll.indexOf(obj));
    	}
    	else {
    		var add_values = {};
    		for(var fieldname in values) {
    			if(fieldname == "_id" || fieldname[0] !== '_' ) {
    				add_values[fieldname] = values[fieldname];
    			}
    		}
    		switch(field_coll) {
    		case "nodes":
    			this.project.addNode(add_values, {at:position});
    			break;
    		case "edges":
    			this.project.addEdge(add_values, {at:position});
    			break;
    		case "users":
    			this.project.addUser(add_values, {at:position});
    			break;
    		default:
    			add_values.project = this.project;
    			coll.push(add_values, {at:position});
    			break;
    		}
    		
    	}
    	
    };
    
    /**
     * Called when a object attribute changes value in a remote data store.
     * Updates the attribute value of the item with the same id in the local
     * data store.
     *
     * @param field_coll A collection or a string for one of the project collection
     * @param value Item data sent by remote data store
     * @param position Which item to update.
     */
    proto.onRemoteObjectUpdate = function(field_coll, values, position) {

    	console.log("Remote ", field_coll ," update values ", values, "position", position);

    	var coll = null;    	
    	if(typeof field_coll === "string") {
    		coll = this.project.get(field_coll);
    	}
    	else {
    		coll = field_coll;
    	}
    	
    	var object_id = values['id'];
    	
    	if(this.project != null) {
    		var obj = coll.get(object_id);
    		if(obj != null) {
    			var changed_val = {};
	    		for(var fieldname in values) {
	    			if(fieldname != "id" && fieldname != "type") {
	    				changed_val[fieldname] = values[fieldname];
	    			}
	    		}
	    		obj.set(changed_val);
    		}
    	}
    };
    
    /**
     * Called when a object is deleted in a remote data store.
     *
     * @param field_coll A collection or a string for one of the project collection
     * @param position Which item to update.
     */
    proto.onRemoteObjectDelete = function(field_coll, position) {
    	console.log("Remote ", field_coll," delete position", position);
    	var coll = null;    	
    	if(typeof field_coll === "string") {
    		coll = this.project.get(field);
    	}
    	else {
    		coll = field_coll;
    	}

    	coll.remove(coll.at(position));
    };
    
        
    var app = new CoRenkan();
    dojo.ready(function() {
        app.init();
    });
    
    
    return {
    	app: app
    };
});