var hotdog = false;
var tom_and_jerry = false;
var coolcat = false;

(function() {
	if(location.pathname == "/" || location.pathname == "") return;
  var factory = null;

  if (typeof GearsFactory != 'undefined') {
    factory = new GearsFactory();
  } else {
    try {
      factory = new ActiveXObject('Gears.Factory');
    } catch (e) {
      if (navigator.mimeTypes["application/x-googlegears"]) {
        factory = document.createElement("object");
        factory.style.display = "none";
        factory.width = 0;
        factory.height = 0;
        factory.type = "application/x-googlegears";
        document.documentElement.appendChild(factory);
      }
    }
  }

  if (factory) {

		var _v = factory.getBuildInfo();
		_v = _v.split(";");
		var _version = _v[0].replace(".", "");
		if(Number(_version) < Number("0250")) {
			// New version of Gears available
		}
		// GearsFactory installed
		hotdog = true;

		if (!window.google) window.google = {};
		if (!google.gears) google.gears = {factory: factory};

		var tail_cutted_url = location.protocol + "//" + location.hostname + location.pathname;

		if(/conflict\/?$/.test(tail_cutted_url)) tail_cutted_url = tail_cutted_url.replace(/\/?conflict\/?$/, '');

		try {
			//
			var local_server = google.gears.factory.create('beta.localserver', '1.0');

			// Allowed to create local server. means current host belongs to apply list.
			coolcat = true;
			
			if (local_server.canServeLocally(tail_cutted_url))
				// offline activated.
				tom_and_jerry = true;

			return;
		} 
		catch(e) {
			// no need to set any flag.
		}
	}
})();


var Implement = function(_source, _target) {
	for(var idx in _target) {
		_source[idx] = _target[idx];
	}

	GMS.publish(_source.identify_name, 'complete_implement');
};


//
// Tamako DB
//
var TamakoDB = function(_table_list, _scheme_set, _database) {
	this.identify_name = "TamakoDB";

	var database = _database || "synki";
	this.open_database(database);
	this.old_scheme = this.extract_columns_from_current_table();
	this.table_list = _table_list;
	this.scheme_set = _scheme_set;
	this.prepare_tables();
	this.expand_table();
};

TamakoDB.prototype.database = function() {
	if(!this.factory_database) {
		this.factory_database = google.gears.factory.create('beta.database', '1.0');
	}

	return this.factory_database;
};

TamakoDB.prototype.open_database = function(database) {
	this.database().close();
	this.database().open(database);
};

TamakoDB.prototype.execute = function(_statement, _values, _single_result) {
	if(!_statement) return false;
	this.database().execute("begin");
	try {
		var _rs = this.database().execute(_statement, _values)
		var _ret = this.result_from_gears_result_set(_rs);
		this.database().execute("commit");

		if(_single_result) _ret = _ret[0];
		return _ret;
	} catch(e) {
		// alert(e.message);
		this.database().execute("rollback");
	}
};

TamakoDB.prototype.extract_columns_from_current_table = function() {
	var _ret = new Object();
	var _query = "SELECT tbl_name, sql, type FROM sqlite_master";
	var _sqls = this.execute(_query);

	for(var i=0; i<_sqls.length; i++) {
		if(_sqls[i].type != "table") continue;
		var _table = _sqls[i].tbl_name;
		var _row = _sqls[i].sql;

		_ret[_table] = new Object();
		_row = _row.replace(/^[^(]+\(/, '');
		_row = _row.replace(/\)([^)]+?)?$/, '');
		_row = _row.split(",");

		for(var j=0; j<_row.length; j++) {
			var name_and_type = /^([^\s]+)\s+([^\s]+)/.exec(Trim(_row[j]));
			if(name_and_type) {
				var column_name = name_and_type[1];
				var column_type = name_and_type[2];
				_ret[_table][column_name] = column_type.toLowerCase();
			}
		};
	};

	return _ret;
};

TamakoDB.prototype.prepare_tables = function() {
	for(var i=0; i<this.table_list.length; i++) {
		var _table = this.table_list[i]
		this[_table] = new TamakoTable(_table, this.scheme_set[_table]);

		var _old = $H(this.old_scheme[_table]);

		if(_old.keys().join().toLowerCase() != this[_table].columns.join().toLowerCase()
			|| _old.values().join().toLowerCase() != this[_table].types.join().toLowerCase()) {

				// oldway to reconstruct when schema has been changed.
				// this.execute("drop table if exists " + _table).
		}

		this.execute(this[_table].create_query);
	}
};

TamakoDB.prototype.expand_table = function() {
	for(var i=0; i<this.table_list.length; i++) {
		var _table = this.table_list[i]

		// ToDo: require general flexible CallBack
		// FixMe: Refactoring we throw two argument to use SQLite auto escape. but can't see arguments directly by below source.
		this[_table].query = CallBackWithArguments(this, function(_statement, _values, _single_result) {
			return this.execute(_statement, _values, _single_result);
		});

		//
		// save(insert)
		this[_table].save = function(_data) {
			var _query_set = this.generate_query_to_insert(_data);

			return this.query(_query_set.statement, _query_set.values);
		};

		//
		// update
		this[_table].update = function(_data, _where) {
			var _query_set = this.generate_query_to_update(_data, _where);

			return this.query(_query_set.statement, _query_set.values);
		};

		//
		// delete
		this[_table].remove = function(_where) {
			var _query = this.generate_query_to_remove(_where);

			return this.query(_query);
		};

		//
		// delete all
		this[_table].delete_all = function() {
			var _query = this.generate_query_to_delete_all();

			return this.query(_query);
		};

		//
		// drop table
		this[_table].destroy = function() {
			var _query = this.generate_query_to_destroy();

			return this.query(_query);
		};

		//
		// find
		this[_table].find = function(_condition, _extra_tails) {
			var _query_set = this.generate_query_to_select(_condition, _extra_tails);

			return this.query(_query_set.statement, _query_set.values);
		};

		//
		// find_one
		this[_table].find_one = function(_condition, _extra_tails) {
			var _query_set = this.generate_query_to_select(_condition, _extra_tails);

			return this.query(_query_set.statement, _query_set.values, true);
		};
	}
};

TamakoDB.prototype.result_from_gears_result_set = function(_result_set) {
	if(!_result_set) return false
	var _results = new Array();
	while(_result_set.isValidRow()) {
		var _row = new Object();
		for(var i=0; i<_result_set.fieldCount(); i++) {
			_row[_result_set.fieldName(i)] = _result_set.field(i);
		}
		_results.push(_row);
		_result_set.next();
	}
	_result_set.close();

	return _results;
};

TamakoDB.prototype.onmessage = function(_sender, _message) {
};





//
// Tamako Table
//
var TamakoTable = function(_table_name, _scheme) {
	this.primary_key = null;
	this.columns = new Array();
	this.types = new Array();
	this.identify_name = this.table_name = _table_name;
	this.scheme = _scheme;
	this.create_query = this.generate_query_to_create(); 
	this.primary_key_double_check();
};

TamakoTable.prototype.primary_key_double_check = function() {
	if(!this.primary_key) this.primary_key = this.columns[0];
};

TamakoTable.prototype.generate_query_to_insert = function(_data) {
	var _columns = new Array();
	var _values = new Array();
	var _questions = new Array();

	for(var i=0; i<this.columns.length; i++) {
		if(_data[this.columns[i]] || _data[this.columns[i]] == 0) {
			_columns.push(this.columns[i]);
			_values.push(_data[this.columns[i]]);
			_questions.push("?");
		}
	}

	if(_columns.length == 0) { return {"statement": null, "values": null}; }
	var _queries = new Object();
	_queries["statement"] = "INSERT INTO " + this.table_name + " ( " + _columns.join(", ") + " ) VALUES ( " + _questions.join(", ") + " )";
	_queries["values"] = _values;
	return _queries;
};

TamakoTable.prototype.generate_query_to_update = function(_data, _where) {
	var _columns = new Array();
	var _values = new Array();
	var _con_key = new String();
	var _con_val = new String();

	for(var i=0; i<this.columns.length; i++) {
		if(_data[this.columns[i]]) {
			if(this.columns[i] == this.primary_key) {
				_con_key = this.columns[i] + " = ?";
				_con_val = _data[this.columns[i]];
			} else {
				_columns.push(this.columns[i] + " = ?");
				_values.push(_data[this.columns[i]]);
			}
		}
	}

	if(_where) {
		var _wheres = new Array()
		for(var _key in _where) {
			if(!_where[_key]) continue;
			_wheres.push(_key + "='" + _where[_key] + "'");
		}
	}

	if(!_wheres && _con_key.length == 0) { return {"statement": null, "values": null}; }
	var _queries = new Object();
	_queries["statement"] = "UPDATE " + this.table_name + " SET " + _columns.join(", ") + " WHERE " + (_wheres ? _wheres.join(" AND ") : _con_key);
	_queries["values"] = (_wheres) ? _values : _values.concat(_con_val);

	return _queries;
};

TamakoTable.prototype.generate_query_to_remove = function(_where) {
	var _condition = null;
	if(/^[0-9]+$/.test(_where)) {
		_condition = this.primary_key + "=" + _where;
	} else if(typeof(_where) == "object") {
		_condition = this.primary_key + "=" + _where[this.primary_key];
	} else {
		_condition = _where;
	}
	var _query = "DELETE FROM " + this.table_name + " WHERE " + _condition;

	return _query;
};

TamakoTable.prototype.generate_query_to_delete_all = function() {
	var _query = "DELETE FROM " + this.table_name;

	return _query;
};

TamakoTable.prototype.generate_query_to_destroy = function() {
	var _query = "DROP TABLE IF EXISTS " + this.table_name;

	return _query;
};

//
// ToDo: refactoring, too many duplicate exists.
// 
TamakoTable.prototype.generate_query_to_select = function(_condition, _extra_tails) {
	if(typeof(_condition) == "object") {
		var con_key = new Array();
		var con_val = new Array();
		for(var _key in _condition) {
			con_key.push(_key + "=?");
			con_val.push(_condition[_key].replace(/(^['"]|['"]$)/g, ''));
		}

		var _con =  "WHERE " + con_key.join(" AND ");
		var _query = this.extract_query_from_extra_tails(_extra_tails, _con);

		var _queries = new Object();
		_queries["statement"] = _query.join(" ");
		_queries["values"] = con_val;

	} else if(/^[0-9]+$/.test(_condition)) {
		var con_key = this.primary_key;
		var con_val = _condition;

		var _con =  "WHERE " + con_key + " = ?";
		var _query = this.extract_query_from_extra_tails(_extra_tails, _con);

		var _queries = new Object();
		_queries["statement"] = _query.join(" ");
		_queries["values"] = [con_val];

	} else if(_condition) { 
		var _kv = _condition.split(/=/);
		var con_key = Trim(_kv[0]);
		var con_val = Trim(_kv[1]).replace(/(^['"]|['"]$)/g, '');

		var _con =  "WHERE " + con_key + " = ?";
		var _query = this.extract_query_from_extra_tails(_extra_tails, _con);

		var _queries = new Object();
		_queries["statement"] = _query.join(" ");
		_queries["values"] = [con_val];
	} else {
		var _queries = new Object();
		_queries["statement"] = "SELECT * FROM " + this.table_name;
		_queries["values"] = null;
	}

	return _queries;
};

TamakoTable.prototype.extract_query_from_extra_tails = function(_extra_tails, _con) {
	var _query = new Array();
	_query.push(this.extract_sql_from_extra_tails("select", _extra_tails));
	_query.push("FROM " + this.table_name);
	_query.push(_con);
	_query.push(this.extract_sql_from_extra_tails("condition", _extra_tails));
	_query.push(this.extract_sql_from_extra_tails("order", _extra_tails));
	_query.push(this.extract_sql_from_extra_tails("limit", _extra_tails));

	return _query;
};

TamakoTable.prototype.extract_sql_from_extra_tails = function(_case, _tails) {
	var _correct_case = { select: "SELECT", condition: "AND", order: "ORDER BY", limit: "LIMIT" };
	var _replacement = { select: "*" };
	var _q = [_correct_case[_case]];
	var _e = (_tails && _tails[_case]) ? _tails[_case] : _replacement[_case];
	if(!_e) return null;
	else _q.push(_e);

	return _q.join(" ");
};

TamakoTable.prototype.generate_query_to_create = function() {
	var _query = new String();
	_query += "CREATE TABLE IF NOT EXISTS " + this.table_name + " (";
	_query += this.extract_column_query_from_scheme();
	_query += ")";

	return _query;
};

TamakoTable.prototype.extract_column_query_from_scheme = function() {
	var _column_query = new Array()
	for(var _column_name in this.scheme) {
		// detect primary key
		if(/primary_key/gi.test(this.scheme[_column_name])) this.primary_key = _column_name;
		// collect columns name;
		this.columns.push(_column_name);
		var scheme = "this.scheme_anatomy(\"" + this.scheme[_column_name] + "\")";
		_column_query.push([_column_name, eval(scheme)].join(" "));
	}
	return _column_query.join(", ");
};

TamakoTable.prototype.scheme_anatomy = function(_scheme) {
	var _type = this.correct_type[/^\w+/.exec(_scheme)];
	// collect columns type;
	this.types.push(_type);

	// fetch max_length
	if(/max_length/gi.test(_scheme)) {
		var _max_length = this.fetch_max_length(_scheme);
		if(_max_length) _type = _type + "(" + _max_length + ")";
	}

	var _options = /(?:\()([^)]+)(?:\))/.exec(_scheme);
	if(_options && _options[1]) _options = _options[1];

	var _query = [_type].concat(this.attach_extra_options(_options));

	return _query.join(" ");
};

TamakoTable.prototype.correct_type = {
	Integer: "INTEGER",
	Varchar: "VARCHAR",
	Timestamp: "TIMESTAMP",
	Text: "TEXT"
};

TamakoTable.prototype.fetch_max_length = function(_scheme) {
	var _ml = /max_length:['"]?(\d+)['"]?/.exec(_scheme) || [];
	var _maxlength = null;
	for(var i=0; i<_ml.length; i++) {
		if(/[0-9]+/.test(Number(_ml[i]))) _maxlength = _ml[i];
	}

	return _maxlength || false;
};

TamakoTable.prototype.attach_extra_options = function(_options) {
	_options = eval("(" + _options + ")");

	var _default_value = (_options) ? "DEFAULT " + _options.default_value : null;
	var _keys = {
		primary_key: "PRIMARY KEY", 
		not_null: "NOT NULL", 
		default_value: _default_value,
		unique: "UNIQUE"
	};

	var _ret = new Array();
	for(var idx in _keys) {
		if(_options && _options[idx]) _ret.push(_keys[idx]);
	}

	return _ret;
};

TamakoDB.prototype.onmessage = function(_sender, _message) {
};





//
// Tamako Resourced Store
//
var TamakoStore = function(_store_name, _store_type) {
	this.type = _store_type || "rstore";
	this.identify_name = this.name = _store_name;
	this["create_" + this.type]();
}

TamakoStore.prototype = {
	on: function() {
		this.store.enabled = true;
	},

	off: function() {
		this.store.enabled = false;
	},

	toggle: function() {
		if(this.store.enabled) this.off();
		else this.on();
	},

	enabled: function() {
		return this.store.enabled;
	}
};

TamakoStore.prototype.localserver = function() {
	if(!this.factory_localserver) {
		this.factory_localserver = google.gears.factory.create('beta.localserver', '1.0');
	}

	return this.factory_localserver;
};

TamakoStore.prototype.timer = function() {
	if(!this.factory_timer) {
		// block gears timer before 0.2.4.0 fully opened
		// this.factory_timer = google.gears.factory.create('beta.timer', '1.0');
		this.factory_timer = window;
	}

	return this.factory_timer;
};

TamakoStore.prototype.create_rstore = function() {
	Implement(this, new RStore(this.localserver().createStore(this.name)));
};

TamakoStore.prototype.create_mstore = function() {
	Implement(this, new MStore(this.localserver().createManagedStore(this.name)));
};

TamakoStore.prototype.onmessage = function(_message) {
};

//
// Resouce Store
//
var RStore = function(_store) {
	this.store = _store;
	this.store.enabled = true;
};

RStore.prototype = {

	capture: function(_urls) {
		return this.store.capture(_urls, CallBackWithArguments(this, this.capture_callback));
	},

	cached: function(_url) {
		return this.store.isCaptured(_url);
	},

	move: function(_src_url, _dst_url) {
		return (!this.store.rename(_src_url, _dst_url));
	},

	copy: function(_src_url, _dst_url) {
		return (!this.store.copy(_src_url, _dst_url));
	},

	remove: function(_url) {
		return (!this.store.remove(_url));
	},

	destroy: function() {
		return (!this.localserver().removeStore(this.store.name));
	}
};

RStore.prototype.capture_callback = function(_url, _success, _captured_id) {
	if(_success) {
		GMS.publish(this.identify_name, 'capture_complete:' + _url);
	} else {
		GMS.publish(this.identify_name, 'capture_failed:' + _url);
		// Log("RStore failed :" + _url);
	}
};

//
// Managed Store
//
var MStore = function(_store) {
	this.store = _store;
	this.store.enabled = true;
}

MStore.prototype = {
	capture: function(_manifest) {
		this.store.manifestUrl = _manifest;
		// this.store.enabled = true;
		this.update();

		//capture complete checkup
		this.capture_callback_id = this.timer().setInterval(CallBack(this, this.capture_callback), 1000);
	},

	update: function() {
		try {
		this.store.checkForUpdate();
		} catch(e) {
			// Log(e.message);
		}
	},

	update_status: function() {
		return this.store.updateStatus;
	},

	version: function() {
		return this.store.currentVersion;
	},

	destroy: function() {
		return (!this.localserver().removeManagedStore(this.store.name));
	}
};

MStore.prototype.capture_callback = function() {
	if(this.update_status() == 3) {
		this.timer().clearInterval(this.capture_callback_id);
		this.capture_callback_id = null;
		destroy_stores();
		GMS.publish(this.identify_name, "capture_failed");
	}

	try {
		if(this.update_status() == 0) {
			this.timer().clearInterval(this.capture_callback_id);
			this.capture_callback_id = null;
			GMS.publish(this.identify_name, "capture_complete");
		}
	} catch(e) {
		this.timer().clearInterval(this.capture_callback_id);
		this.capture_callback_id = null;
		destroy_stores();
		GMS.publish(this.identify_name, "capture_failed");
	}
};

//
// Version Checker
//
var VersionChecker = function(_versions, _db) {

	var result = new Object;

	$H(_versions).each(function(_v) {
		var _table = _v.key;
		var server_version = Number(_v.value);

		var _local = _db[_table].find_one();
		var local_version = (_local) ? Number(_local.version) : Number(server_version);

		if(server_version != local_version) {
			result[_table] = true;
		};
	});

	this.result = result;
};

VersionChecker.prototype.need_to_reconstruct = function() {
	for(var i in this.result) {
		if(this.result[i]) {
			return i;
		}
	}

	return null;
};

//
// Build Local database and stores
//
var LocalBuilder = function(_models, _workspace_id) {
	var workspace_id = (_workspace_id) ? _workspace_id : ExtractIdFromUrl("workspace");

	// downloader can create stores without _models.
	this.models = _models;

	this.mts = "m_templates_" + workspace_id;
	this.rts = "r_templates_" + workspace_id;
	this.mfs = "m_files_" + workspace_id;
	this.rds = "r_data_" + workspace_id;
	this.database = "db_" + workspace_id;

	this.masters = new TamakoDB(["ws"], {"ws": this.schema_of_masters}, "MASTERS");
	this.stores = new Object();
	this.tables = new Object();

	this.create_stores();
	this.create_db();

};

LocalBuilder.prototype.create_stores = function() {
	this.stores[this.mfs] = new TamakoStore(this.mfs, "mstore");
	this.stores[this.rds] = new TamakoStore(this.rds, "rstore");
	this.stores[this.mts] = new TamakoStore(this.mts, "mstore");
	this.stores[this.rts] = new TamakoStore(this.rts, "rstore");
};

LocalBuilder.prototype.turnoff_stores = function() {
	for(var i in this.stores) {
		this.stores[i].off();
	}
};

LocalBuilder.prototype.destroy_stores = function() {
	// if(ObjectLength(this.stores) == 0) return;

	for(var i in this.stores) {
		this.stores[i].destroy();
	}
};

LocalBuilder.prototype.create_db = function() {
	var _tables_and_schema = this.generate_scheme_set();
	
	this.tables = new TamakoDB(_tables_and_schema[0], _tables_and_schema[1], this.database);
};

LocalBuilder.prototype.destroy_db = function() {
	// if(ObjectLength(this.tables) == 0) return;
	
	for(var i in this.models) {
    this.tables[i].destroy();
	};
};

LocalBuilder.prototype.generate_scheme_set = function() {
	var _table_list = new Array();
	var _scheme_set = new Object();

	var schema = $H(this.models);

	schema.each(function(p) {
		_table_list.push(p.key);
		_scheme_set[p.key] = new Object();

		var scheme = new Hash();
		scheme.update(p.value);

		scheme.each(function(_p) {
			_scheme_set[p.key][_p.key] = _p.value
		})
	});

	return [_table_list, _scheme_set];
};

LocalBuilder.prototype.schema_of_masters = {
	"id": "Integer({primary_key:true})",
	"is_offline": "Varchar({default_value:'false'})"
};

//
// Downloader
//
var Downloader = function(_ws_id, _models, _progress) {
	this.identify_name = "downloader";
	this.workspace_id = _ws_id;
	this.fetch_failures = new Array;
	this.max_retry = 3;

	// this progress is instance of ProgressInterface()
	this.progress = _progress;
	this.self_subscribe();
	this.urls = this.generate_urls(_ws_id);

	// Progress
	this.progress.watcher("SHAKE", 1);
	Implement(this, new LocalBuilder(_models, _ws_id));
};

Downloader.prototype.self_subscribe = function() {
	GMS.subscribe(this, this.identify_name);
};

Downloader.prototype.subscribe_stores = function() {
	GMS.subscribe(this, this.stores[this.mfs].identify_name);
	GMS.subscribe(this, this.stores[this.rds].identify_name);
	GMS.subscribe(this, this.stores[this.mts].identify_name);
	GMS.subscribe(this, this.stores[this.rts].identify_name);

	this.check_integrity_of_local_database();
};

Downloader.prototype.check_integrity_of_local_database = function() {
	var local_workspace = this.tables.workspaces.find_one(this.worksapce_id);
	if(local_workspace && local_workspace.downloaded != 'false') {
		// ToDo: MSG
		new Shout().confirm({msg:MSG[557]}, CallBack(this, function() {
			this.destroy_db();
			this.create_db();
			this.destroy_stores();
			this.create_stores();
			this.cleanup_cacheable_models_and_touched_flags();
		}));
	}
	else {
		this.cleanup_cacheable_models_and_touched_flags();
	}
};

Downloader.prototype.cleanup_cacheable_models_and_touched_flags = function() {

	// delete cacheable model's data.
	this.tables.tags.delete_all();
	this.tables.page_tags.delete_all();
	this.tables.watched_pages.delete_all();
	this.tables.attachments.delete_all();
	this.tables.permissions.delete_all();

	// delete old user data.
	// FixMe: is this best way to delete? (i mean row level query);
	$H(this.context_of_models).each(CallBackWithArguments(this, function(p) {
		this.tables[p.key].remove("local_created='true' and upload_integrity='true'");

		var _qry = "update " + p.key + " set local_updated='false', local_deleted='false', local_created='false', uploaded='false', upload_integrity='false' where (local_updated='true' or local_deleted='true') and upload_integrity='true'";
		this.tables[p.key].query(_qry);
	}));

	// FixMe: what a exception..
	this.tables.revisions.remove("local_only='true'");

	this.req_takeoff();
};

// FixMe: is downloader require this context_of_models?
Downloader.prototype.context_of_models = {
	pages: {
	},
	page_tags: {
	},
	watched_pages: {
	}
};

Downloader.prototype.req_takeoff = function() {
	jQuery.ajax({ type:"GET", url: this.urls.takeoff, cache:false, dataType:"json",
		complete: CallBackWithArguments(this, function(_data) {
			if(_data.status == 503) {
				new Shout().error({msg:MSG[58]}, function() {
					location.reload();
				});
				return;
			}
			else if(_data.status == 403) {
				new Shout().error({msg:MSG[163]}, function() {
					location.href = location.protocol + "//" + location.hostname + "/login";
				});
				return;
			} 
			else {
				var _takeoff_data = eval("(" + _data.responseText + ")");
				this.pick_out_updated_and_droped_data(_takeoff_data);
			}
		})
	})
};

Downloader.prototype.pick_out_updated_and_droped_data = function(_takeoff_data) {

	for(var _key in _takeoff_data) {
		if(this["pick_out_updated_" + _key]) this["culled_" + _key] = this["pick_out_updated_" + _key](_takeoff_data[_key]);

		if(this["droped_" + _key]) this["dropout_" + _key] = this["droped_" + _key](_takeoff_data[_key]);
	}

	this.count_download_task(_takeoff_data);
};

Downloader.prototype.droped_pages = function(_takeoff_pages) {
	var _exists_pages = this.tables.pages.find();
	var _local_page_ids = _exists_pages.collect(function(p) { return p.id; });
	var _remote_page_ids = _takeoff_pages.collect(function(p) { return p[0]; });

	var dropout = _local_page_ids._without(_remote_page_ids);

	var qry = "delete from pages where id in (" + dropout.join(" ,") + ")";
	this.tables.pages.query(qry);
};

Downloader.prototype.pick_out_updated_pages = function(_takeoff_pages) {
	var updated_pages = new Array();

	for(var i=0; i<_takeoff_pages.length; i++) {
		var _page_id = _takeoff_pages[i][0];
		var _revision = _takeoff_pages[i][1];

		var _exist_page = this.tables.pages.find_one(_page_id);
		if(!_exist_page || _exist_page.revisions_count != _revision) updated_pages.push(_page_id);
	}

	return updated_pages;
};

Downloader.prototype.count_download_task = function(_takeoff_data) {

	this.takeoff_data = _takeoff_data;

	var total_task = Number(this.culled_pages.length) + Number(this.takeoff_data.pages.length);
	this.progress.setJobsCount("DATA", total_task);

	var _versions = { 
		schema_version: this.takeoff_data.schema, 
		application_version: this.takeoff_data.application 
	};

	this.verifying_local_versions(_versions);
};


Downloader.prototype.verifying_local_versions = function(_versions) {
	var vchecker = new VersionChecker(_versions, this.tables);

	var changed = vchecker.need_to_reconstruct();
	if(changed) {
		this.reconstruct(changed, _versions);
		return;
	}

	this.fetch_templates_manifest();
};

Downloader.prototype.reconstruct = function(_table, _versions) {
	var target = (_table == "schema_version") ? "db" : "stores";

	// ToDo: MSG
	// reflexive call. that's why use confirm instead of Shout().confirm().
	if(confirm(target + " " + MSG[558])) {
		this["destroy_" + target]();
		this["create_" + target]();
		this.tables[_table].update({"version": _versions[_table]}, {id: 1});

		// this.verifying_local_versions(_versions);
		this.req_takeoff();
	} else {
		this.destroy_stores();
		var container = document.getElementById("div_gears_progress");
		if(container) document.body.removeChild(container);
		location.reload();
	}
};

Downloader.prototype.fetch_templates_manifest = function() {
	// Progress
	this.progress.watcher("APP", 1);
	this.stores[this.mts].capture(this.urls.templates_manifest);
};

Downloader.prototype.fetch_common_manifest = function() {
	this.stores[this.mfs].capture(this.urls.common_manifest);
};

Downloader.prototype.fetch_dynamic_templates = function() {
	this.stores[this.rts].capture(this.urls.workspace_template);
	this.stores[this.rts].capture(this.urls.page_template);
	this.stores[this.rts].capture(this.urls.revision_template);
	this.stores[this.rts].capture(this.urls.revision_show_template);
	this.stores[this.rts].capture(this.urls.workspace_tags_template);
	this.stores[this.rts].capture(this.urls.print_template);
};

Downloader.prototype.fetch_workspace_data = function() {
	jQuery.ajax({ type:"GET", url: this.urls.ws_root + "?format=json", cache:false,
		complete: CallBackWithArguments(this, function(_data) {
			if(!this.stores[this.rts].copy(this.urls.workspace_template, this.urls.ws_root)) {
				// ToDo: error, binding workspace url, require rollback
			}

			var data = eval("(" + _data.responseText + ")");
			this.workspace = data;
			this.createOrUpdate(data);

			this.sync_attachments();
		})
	});
};

Downloader.prototype.sync_attachments = function() {
	
	var fetched = ATTACH.db.attachments.find({"workspace_id": String(this.workspace.id)});
	for(var i=0; i<fetched.length; i++) {
		var attachment = fetched[i];
		attachment.user_id = this.takeoff_data["user"].id;
		attachment.user_login = this.takeoff_data["user"].login;
		this.tables.attachments.save(attachment);
	}

	this.shake_data();
};

Downloader.prototype.shake_data = function() {

	for(var key in this.takeoff_data) {
		var data = this.takeoff_data[key];

		try {
			this["shake_" + key + "_data"](data);
		} catch(e) {
			// no need to shake.
		}
	};
};

/////////////////////////////////////////////////////////
// shake data																					 //
/////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////
// shake schema_version
Downloader.prototype.shake_schema_data = function(_version) {
	var data = new Object;
	data.prural_name = "SchemaVersion";
	data.id = 1;
	data.version = _version;
	this.createOrUpdate(data);
};

/////////////////////////////////////////////////////////
// shake application_version
Downloader.prototype.shake_application_data = function(_version) {
	var data = new Object;
	data.prural_name = "ApplicationVersion";
	data.id = 1;
	data.version = _version;
	this.createOrUpdate(data);
};

/////////////////////////////////////////////////////////
// shake page
Downloader.prototype.shake_pages_data = function(data) {
	var dataset = new Array();

	// bind and collect page data
	for(var i=0; i<data.length; i++) {
		var _page_id = data[i][0];
		var _revision= data[i][1];

		var _page_url = this.urls.ws_root + "/pages/" + _page_id;
		var _page_json_url = _page_url + "?format=json";

		var _revision_url = _page_url + "/revisions";
		var _revision_show_url = _revision_url + "/" + _revision;

		var _page_tags_json_url = _page_url + "/tags.json"

		var _print_url = _page_url + "/print";

		if(!this.stores[this.rts].copy(this.urls.page_template, _page_url) || 
			!this.stores[this.rts].copy(this.urls.revision_template, _revision_url) ||
			!this.stores[this.rts].copy(this.urls.revision_show_template, _revision_show_url) ||
			!this.stores[this.rts].copy(this.urls.print_template, _print_url)
			) {
			// ToDo: what can i do?
			// fail copy url
			// Log("copying failed!");
		}

		// build list of data that will be collected
		// culled_{MODEL} is updated (so need to fetch fresh data) data table
		// even there's no updated pages, still require shake_pages_data. (binding url)
		// when i did separate bind url and listup fetching data then we can find more general way.
		if(this.culled_pages.contains(_page_id)) dataset.push(_page_json_url);
		dataset.push(_page_tags_json_url);
	}

	this.stores[this.rds].capture(dataset);
};

/////////////////////////////////////////////////////////
// shake watched pages
Downloader.prototype.shake_watched_pages_data = function(data) {
	for(var i=0; i<data.length; i++) {
		var watched = new Object;
		watched.prural_name = "WatchedPages";
		watched.id = data[i][0];
		watched.page_id = data[i][1];
		watched.workspace_id = this.workspace.id;
		watched.user_id = this.takeoff_data["user"].id;
		watched.user_login = this.takeoff_data["user"].login;

		this.createOrUpdate(watched);
	}
};

/////////////////////////////////////////////////////////
// shake revision
Downloader.prototype.shake_revisions_data = function(data) {
	this.createOrUpdate(data);
};

/////////////////////////////////////////////////////////
// shake user
Downloader.prototype.shake_user_data = function(data) {
	this.createOrUpdate(data);
};

/////////////////////////////////////////////////////////
// shake page permission
Downloader.prototype.shake_page_permissions_data = function(data) {
	for(var i=0; i<data.length; i++) {
		var permission = new Object();
		permission.type = "page";
		permission.resource_id = data[i][0];
		permission.permission = data[i][1];

		this.tables.permissions.save(permission);
	}
};

/////////////////////////////////////////////////////////
// shake workspace permission
Downloader.prototype.shake_workspace_permission_data = function(data) {
	var permission = new Object();
	permission.type = "workspace";
	permission.resource_id = data[0];
	permission.permission = data[1];

	this.tables.permissions.save(permission);
};

/////////////////////////////////////////////////////////
// shake page tags
Downloader.prototype.shake_page_tags_data = function(data) {
	for(var i=0; i<data.length; i++) {
		var _tag_url = this.urls.ws_root + "/tags/" + data[i].tag_id;

		this.stores[this.rts].copy(this.urls.workspace_tags_template, _tag_url);
	}
};

/////////////////////////////////////////////////////////
// shake workspace tags
Downloader.prototype.shake_workspace_tags_data = function(data) {
	for(var i=0; i<data.length; i++) {
		var tag = new Object;
		tag.id = data[i][0];
		tag.name = data[i][1];
		tag.prural_name = "Tags";
		this.createOrUpdate(tag);
		//this.tables.tags.save(tag);
	}
};


Downloader.prototype.generate_revision_from_page_data = function(data) {
	var revision = new Object;
	revision["prural_name"] = "Revisions";
	revision["workspace_id"] = data.workspace_id;
	revision["version"] = data.revisions_count;
	revision["content"] = data.content;
	revision["page_id"] = data.id;
	revision["user_id"] = data.last_user_id;
	revision["user_login"] = data.last_user_login;
	revision["created_at"] = data.updated_at;

	revision["id"] = data.id + "_" + data.revisions_count;

	return revision;
};

Downloader.prototype.createOrUpdate = function(_data) {
	var data = (_data.length > 0) ? _data : [_data];

		for(var i=0; i<data.length; i++) {
			try {
				var table = data[i].prural_name.toSnakeCase(); //toLowerCase();
				var mode = this["switch_insert_action_of_" + table](data[i]);

				if(mode) this.tables[table][mode](data[i]);
			} catch(e) {
				// null data
			}
		}

	return table;
};

Downloader.prototype.switch_insert_action_of_schema_version = function(data) {
	var exists_data = this.tables.schema_version.find_one(data.id);
	if(!exists_data) return "save";
	else return "update";

	return null;
};

Downloader.prototype.switch_insert_action_of_application_version = function(data) {
	var exists_data = this.tables.application_version.find_one(data.id);
	if(!exists_data) return "save";
	else return "update";

	return null;
};

Downloader.prototype.switch_insert_action_of_workspaces = function(data) {
	var exists_data = this.tables.workspaces.find_one(data.id);
	if(!exists_data) return "save";
	if(
			exists_data["updated_at"] != data["updated_at"] ||
			exists_data["index_page_id"] != data["index_page_id"]
		) return "update";

	return null;
}

Downloader.prototype.switch_insert_action_of_pages = function(data) {
	var exists_data = this.tables.pages.find_one(data.id, {"condition": "workspace_id=" + data.workspace_id});
	if(!exists_data) return "save";
	if(exists_data["revisions_count"] != data["revisions_count"]) return "update";

	return null;
};

Downloader.prototype.switch_insert_action_of_watched_pages = function(data) {
	var exists_data = this.tables.watched_pages.find_one("page_id=" + data.page_id, {"condition": "user_id=" + data.user_id});
	if(!exists_data) return "save";
	if(exists_data["id"] != data["id"]) return "update";

	return null;
};

Downloader.prototype.switch_insert_action_of_revisions = function(data) {
	var exists_data = this.tables.revisions.find_one("page_id=" + data.page_id, {"order": "version desc"});
	if(exists_data) this.tables.revisions.remove("page_id=" + data.page_id);

	return "save";
};

Downloader.prototype.switch_insert_action_of_page_tags = function(data) {
	var exists_data = this.tables.page_tags.find_one("page_id=" + data.page_id, {"condition": "workspace_id= " + data.workspace_id + " and tag_id=" + data.tag_id});

	if(!exists_data || (exists_data["id"] != data["id"])) return "save";
	if(exists_data["tag_name"] != data["tag_name"]) return "update";

	return null;
};

Downloader.prototype.switch_insert_action_of_tags = function(data) {
	var exists_data = this.tables.tags.find_one(data.id);
	if(!exists_data || (exists_data["id"] != data["id"])) return "save";
	if(exists_data["name"] != data["name"]) return "update";

	return null;
}

Downloader.prototype.switch_insert_action_of_users = function(data) {
	var exists_data = this.tables.pages.find_one(data.id);
	if(!exists_data) return "save";
	if(exists_data["login"] != data["login"]) return "update";

	return null;
};

Downloader.prototype.extract_data_from_captured_dataset = function(_url) {
	// insert into local db
	jQuery.ajax({ type:"GET", url: _url, cache:false,
		complete: CallBackWithArguments(this, function(_xmlRequest) {

			try {
				var data = eval("(" + _xmlRequest.responseText + ")")

				switch (this.createOrUpdate(data)) {
					case "pages":
						this.shake_revisions_data(this.generate_revision_from_page_data(data));
						break;

					case "page_tags":
						this.shake_page_tags_data(data);
						break;
				}
			}
			// If probs on evaluate. means wrong data we've get.
			catch(e) {
				// push to fetch-retry stack
				this.fetch_failures.push(_url);
			}

			this._complete_extract_data();
		}, _url)
	});
};

Downloader.prototype._complete_extract_data = function() {
	// Progress
	var _result = this.progress.watcher("DATA");
	if(_result.section_complete) this.check_integrity_of_task_completion();
};

Downloader.prototype.check_integrity_of_task_completion = function() {

	if(this.fetch_failures.length == 0) this.finalize_download();
	else {
		alert(this.fetch_failures.length + " " + MSG[542]);

		this.progress.setJobsCount("DATA", this.fetch_failures.length);
		
		// limit
		if(this.max_retry <= 0) {
			// ToDo: MSG
			// new Shout().confirm({msg: this.fetch_failures.length + " data failed to fetch 3times over. Now, force go offline."}, CallBack(this, function() {
			this.finalize_download();
			// }));

			return;
		}

		var _retry_dataset = new Array();
		// do retry
		for(var i=0; i<this.fetch_failures.length; i++) {
			var _url = this.fetch_failures[i];
			// delete fetched data
			this.stores[this.rds].remove(_url);
			_retry_dataset.push(_url);
		}

		// refetch data (calls extract_data_from_captured_dataset when fetch completed)
		this.stores[this.rds].capture(_retry_dataset);
		this.fetch_failures = new Array();
		this.max_retry--;
	}
};

Downloader.prototype.finalize_download = function() {
	try {
		this.stores[this.rds].destroy();
		this.tables.workspaces.update({"id": this.workspace_id, "downloaded": "true"});
		this.remark_to_masters();
	} catch(e) {
		// Log(e.message);
		return;
	}

	this.progress.watcher("FINALIZE", 1);
};

Downloader.prototype.remark_to_masters = function() {
	var _masters = new Object;
	_masters["id"] = this.workspace_id;
	_masters["is_offline"] = "true";

	var _exist = this.masters.ws.find_one(this.workspace_id);
	var _command = (_exist) ? "update" : "save";
	this.masters.ws[_command](_masters);
};

Downloader.prototype.generate_urls = function(_ws_id) {
	// ToDo: store require full url 
	// temporory wiki url
	var wiki = location.protocol + "//" + location.hostname;
	var ws = _ws_id;
	var ret = new Object();
	ret["tardis"] =  wiki + "/tardis";
	ret["ws_tardis"] = wiki + "/tardis/" + ws;
	ret["ws_root"] = wiki + "/workspaces/" + ws;

	// Gears Manifests
	ret["common_manifest"] = wiki + "/tardis/common_manifest/" + ws;
	ret["templates_manifest"] = wiki + "/tardis/templates_manifest/" + ws;

	// TakeOff
	ret["takeoff"] = wiki + "/tardis/takeoff/" + ws;

	// Dynamic Templates
	ret["workspace_template"] = wiki + "/tardis/tp_workspace/" + ws;
	ret["page_template"] = wiki + "/tardis/tp_page/" + ws;

	ret["revision_template"] = wiki + "/tardis/tp_revision/" + ws;
	ret["revision_show_template"] = wiki + "/tardis/tp_revision_show/" + ws;

	ret["workspace_tags_template"] = wiki + "/tardis/tp_workspace_tags/" + ws;

	ret["print_template"] = wiki + "/tardis/tp_page_print/" + ws;

	ret["pick_rose"] = wiki + "/tardis/" + encodeURI(ws) + "/pick_rose";
	ret["dataset"] = wiki + "/tardis/" + encodeURI(ws) + "/dataset";

	return ret
};

Downloader.prototype.require_templates = ["workspace", "page", "revision", "show", "tags", "print"];

Downloader.prototype.onmessage = function(_sender, _message) {
	if(_sender == this.identify_name && _message == "complete_implement") {

		// Progress
		this.progress.watcher("SHAKE", 2);
		this.subscribe_stores();
	}
	else if(_sender == this.identify_name && _message == "injection_complete") {
		//Log("DONE");
	}
	else if(_sender == this.mts && _message == "capture_complete") { // static templates captured
		// Progress
		this.fetch_common_manifest();
	}
	else if(_sender == this.mfs && _message == "capture_complete") { // js, css, stylesheets captured
		// Progress
		this.progress.watcher("APP", 2);
		this.fetch_dynamic_templates();
	}
	else if(_sender == this.rds && /complete/.test(_message)) { // fetch json data
		for(var i=0; i<_message.length; i++) {
			var _url = /http(s)?:\/\/.+$/.exec(_message[i]);
			if(_url) this.extract_data_from_captured_dataset(_url[0]);
		}
	}
	else if(_sender == this.rds && /failed/.test(_message)) { // fetch failed
		for(var i=0; i<_message.length; i++) {
			var _url = /http(s)?:\/\/.+$/.exec(_message[i]);
			// if(_url) this.fetch_failures.push(_url);
			if(_url) this.extract_data_from_captured_dataset(_url[0]);
		}
	}
	else if(_sender == this.rts && /complete/.test(_message)) { // dynamin templates captured
		for(var i=0; i<_message.length; i++) {
			var _name = /[^_]+?(?=\/[0-9]+$)/.exec(_message[i]);
			var _idx = null;
			if(_idx = array_search(this.require_templates, _name)) {
				this.require_templates.splice(_idx, 1);
				if(this.require_templates.length == 0) {
					this.fetch_workspace_data();
				}
			} 
		}
	}

	// ToDo: 에러처리 좀 잘해보자.
	else if(/failed/.test(_message)) {
		this.error_free = false;
		new Shout().error({msg:MSG[165]}, function() {
			location.reload();
		});
		return;
	}
};

//
// uploader
//
var Uploader = function(_ws_id, _models, _progress) {
	this.identify_name = "Uploader";
	this.workspace_id = _ws_id;
	this.error_free = false;
	this.touched_stack = new Object;

	this.ub = new URLBuilder();

	this.wiki_url = location.protocol + "//" + location.hostname;

	this.models = _models;
	this.progress = _progress;
  this.self_subscribe();

	Implement(this, new LocalBuilder(_models, _ws_id));
};

Uploader.prototype.check_before = function() {
	var _url = location.protocol + "//" + location.hostname + "/tardis/landing?format=json";
	var _res = null;
	try {
		_res = http_get(_url, true)
	}
	catch(e) {
		this.error_free = false;
		new Shout().error({msg:MSG[165]}, function() {
			location.reload();
		});
		return;
	}

	if (_res.status == 0 || _res.status == 503 || _res.status == 500) {
		this.error_free = false;
		new Shout().error({msg:MSG[165]}, function() {
			location.reload();
		});
		return;
	}
	else {
		if(/required/.test(_res.responseText)) {
			this.error_free = false;
			new Shout().error({msg:MSG[163]}, function() {
				location.href = location.protocol + "//" + location.hostname + "/login";
			});
			return;
		}
		this.error_free = true;
		this.doUpload();
		return;
	}
	
	// ToDo: check logic later...
	this.error_free = false;
//	new Shout().error({msg:MSG[165]}, function() {
//		location.reload();
//	});
	return;
};

Uploader.prototype.self_subscribe = function() {
	GMS.subscribe(this, this.identify_name);
};

Uploader.prototype.doUpload = function() {
	if(this.error_free) {
		this.touched_stack = this.rake_touched_contents();
		var total_task = this.count_task(this.touched_stack);

		this.progress.setJobsCount("UPLOAD", total_task);

		if(total_task == 1) {
			this.check_integrity_of_upload();
			return;
		}

		this.chop_touched_contents(); 
	}
};

Uploader.prototype.rake_touched_contents = function(select_all) {
	var touched_contents = new Object;
	$H(this.context_of_models).each(CallBackWithArguments(this, function(p) {
		var _model = p.key;
		var _context = this.context_of_models[_model] || null;

		var _touched = this._touched_contents(_model, _context, select_all);
		$H(_touched).each(CallBackWithArguments(this, function(_p) {
			touched_contents[_p.key + "_" + _model] = _p.value;
		}));
	}));

	return touched_contents;
};

Uploader.prototype._touched_contents = function(_model, _context, select_all) {
	var _conditions = (select_all) ? {"select": "*, '" + _model + "' as table_name"} : _context.columns;
	_conditions["condition"] = "upload_integrity='false'";

	var _created = this.tables[_model].find("local_created='true'", _conditions)
	var _updated = this.tables[_model].find("local_updated='true'", _conditions)
	var _deleted = this.tables[_model].find("local_deleted='true'", _conditions)

	var _touched = { created: _created, updated: _updated, deleted: _deleted }

	if(_model == "pages") return this.pages_require_contents(_touched);
	else return _touched;
};

Uploader.prototype.pages_require_contents = function(_source) {
	var _touched = _source;

	$H(_touched).each(CallBackWithArguments(this, function(p) {
		for(var i=0; i<p.value.length; i++) {
			var _revision = this.tables.revisions.find_one("page_id=" + p.value[i].id, {"order": "version desc"});
			if(_revision && _revision.content) p.value[i].content = _revision.content;
		}
	}));

	return _touched;
};

Uploader.prototype.count_task = function(touched) {
	var count_task = 1; // this is magic number for sure complete upload
	$H(touched).each(CallBackWithArguments(this, function(p) {
		count_task += Number(touched[p.key].length);
	}));

	return count_task;
};

Uploader.prototype.set_total_task_to_progress = function(total_task) {
};

Uploader.prototype.chop_touched_contents = function() {
	for(var i in this.touched_stack) {
	
		if(this.touched_stack[i].length == 0) continue; 

		var _type = /^[^_]+/.exec(i);
		var _table = i.replace(_type + '_', '');
		var _touched = this.touched_stack[i].shift()

		if(_touched) {
			this.spruce_touched_contents(_type, _table, _touched);
			return;
		}
	}

	this.check_integrity_of_upload();
};

Uploader.prototype.spruce_touched_contents = function(_type, _table, _touched) {
	if(!_table) return false;
	var _content = Tamako.tables[_table].find_one(_touched.id)
	if(_content && _content.uploaded == "true") {
		this.rolling_progress();
		this.chop_touched_contents();
	}
	else {
		var action_url = this.context_of_models[_table].action(_type, _touched);
		var post_data = this.context_of_models[_table].postData(_type, _touched);

		this.send_touched_contents(action_url, post_data, _touched, _table);
	}
};

Uploader.prototype.conflict_resolver = function(_touched) {
	var _form = document.createElement("form");
	_form.method = "POST";
	_form.action = this.ub.page_path(_touched, false, "conflict");
	_form.id = "conflict_form";
	_form.style.visibility = "hidden";

	var _title = document.createElement("input");
	_title.type = "text";
	_title.name = _title.id = "page[title]"
	_title.value = _touched.title;

	var _content = document.createElement("input");
	_content.type = "textarea";
	_content.name = _content.id = "page[content]"
	_content.value = _touched.content;

	var _original_version = document.createElement("input");
	_original_version.name = _original_version.id = "page[original_version]";
	_original_version.value = _touched.revisions_count;

	_form.appendChild(_title);
	_form.appendChild(_content);
	_form.appendChild(_original_version);

	document.getElementsByTagName("body")[0].appendChild(_form);

	_form.submit();
};

Uploader.prototype.send_touched_contents = function(_url, _data, _touched, _model) {
  var request = jQuery.ajax({ type:"POST", url: _url, cache:false, dataType: "json", data: _data,
    complete: CallBackWithArguments(this, function(xmlhttp, succ_or_fail) {

			try {
				var response = eval("(" + xmlhttp.responseText + ")");
			} catch(e) {
				var response = xmlhttp.responseText;
				if(response == "Conflict") {
					this.conflict_resolver(_touched);
					return;
				}
				else if(response != "OK") {
					this.chop_touched_contents();
					return;
				};
			}

			_touched.uploaded = "true";
			this.tables[_model].update(_touched);

			if(_model == "pages" && response.id != _touched.id) {
				this.apply_fresh_page_id(response.id);
			}

      this.rolling_progress();

			this.chop_touched_contents();
    }, _touched, _model)
  });
};

Uploader.prototype.apply_fresh_page_id = function(_new_page_id) {
	$H(this.touched_stack).each(CallBackWithArguments(this, function(p) {
		for(var i=0; i<p.value.length; i++) {
			var row = p.value[i];
			if(row.page_id) {
				row.page_id = _new_page_id;
			}
		}
	}));
};

Uploader.prototype.rolling_progress = function() {
	this.progress.watcher("UPLOAD");
};

Uploader.prototype.check_integrity_of_upload = function() {
	var _omitted = new Array;
	// find touched again.
	var _touched = this.rake_touched_contents(true);
	// verifying all touched contents has uploaded flag.

	for(var i in _touched) {
		if(typeof _touched[i] == "function") continue;
		for(var p in _touched[i]) {
			var row = _touched[i][p];
			if(typeof row == "function") continue;
			// omitted
			if(row.uploaded == "false") {
				_omitted.push(row);
			} 
			// committed
			else {
				row.upload_integrity = "true";
				this.tables[row.table_name].update(row);
			}
		}
	}

	if(_omitted.length == 0) this.finalize_upload();
	else {
		new Shout().confirm({msg: _omitted.length + " " + MSG[556]}, CallBack(this, function() {
			this.doUpload();
		}));

		// cancel 하면 어떻게??
		// 1. 서버에 미완데이터만을 모으는 모델을 만든다. (string json 타입으로 저장하고 evaluate로 특정한 Recovery 템플릿에서 작성되었던 데이터를 확인 할 수 있도록 한다.)
		// 2. 서버에서 보다 자세한 에러코드를 내려주고, 이 곳에서 판단하여 치명적인 데이터가 아니라면 드랍옵션을 준다.
	}
};

Uploader.prototype.finalize_upload = function() {
	// ToDo: delete all data of cacheable models.
	// delete permissions
	// this delete process going move to downloader.
	// this.tables.permissions.delete_all();

	// make "downloaded" flag set to false.
	// means "go offline" risk-free
	
	try {
		this.tables.workspaces.update({"id": this.workspace_id, "downloaded": "false"});
		this.remark_to_masters();
	} catch(e) {
		// Log(e.message);
		return;
	}

	this.turnoff_stores();
	this.rolling_progress("UPLOAD");
};

Uploader.prototype.remark_to_masters = function() {
	var _masters = new Object;
	_masters["id"] = this.workspace_id;
	_masters["is_offline"] = "false";

	var _exist = this.masters.ws.find_one(this.workspace_id);
	var _command = (_exist) ? "update" : "save";
	this.masters.ws[_command](_masters);
};

Uploader.prototype.context_of_models = {
	pages: {
		columns: { "select": "id, title, workspace_id, revisions_count, owner_id, owner_login, last_user_id, last_user_login" },
		action: function(_type, _touched) {
			var _action = location.protocol + "//" + location.hostname;
			if(_type == "created") var _path = new URLBuilder().page_path(_touched, true, "?format=json");
			else if(_type == "updated") var _path = new URLBuilder().page_path(_touched, false, "?format=json"); 

			return _action + _path;
		},
		postData: function(_type, _touched) {
			var _post_data = toPostQueryString("page", _touched);
			if(_type == "updated") {
				_post_data += "&_method=PUT";
				_post_data += "&id="+_touched.id;
				_post_data += "&original_version="+_touched.revisions_count;
			}

			return _post_data;
		}
	},

	page_tags:{
		columns: { "select": "id, workspace_id, page_id, tag_name as tag" },
		action: function(_type, _touched) {
			var _action = location.protocol + "//" + location.hostname;
			if(_type == "created") var _path = new URLBuilder().page_tag_path(_touched, true);
			else if(_type == "deleted") var _path = new URLBuilder().page_tag_path(_touched, false); 

			return _action + _path;
		},
		postData: function(_type, _touched) {
			if(_type == "created") var _post_data = _touched;
			else if(_type == "deleted") {
				var _post_data = toPostQueryString("page", _touched);
				_post_data += "&_method=DELETE";
			}

			return _post_data;
		}
	},

	watched_pages:{
		columns: { "select": "id, user_id, page_id, workspace_id, wiki_id" },
		action: function(_type, _touched) {
			var _action = location.protocol + "//" + location.hostname;
			if(_type == "created") var _path = new URLBuilder().watched_page_path(_touched, true);
			else if(_type == "deleted") var _path = new URLBuilder().watched_page_path(_touched, true, _touched.page_id); 

			return _action + _path;
		},
		postData: function(_type, _touched) {
			if(_type == "created") var _post_data = _touched;
			else if(_type == "deleted") var _post_data = "_method=DELETE";

			return _post_data;
		}
	}
};

Uploader.prototype.onmessage = function(_sender, _message) {
	if(_sender == this.identify_name && /complete/.test(_message)) {
		if(typeof _message == "string") _message = [_message];
		for(var i=0; i<_message.length; i++) {
			var _msg = _message[i];
			switch(_msg) {
				case "complete_implement":
					this.check_before();
					break;
			}
		}
	}
};




//
// Message Service by gears timer (current using window timer directly)
//

var GMS = new function() {
	this.identify_name = "GMS";
	this.rolling = false;
	this.subscribers = new Object();
	this.sandbox = new Object();

	this.subscribe = function(_subscriber, _maggazine) {
		if(!this.subscribers[_maggazine]) this.subscribers[_maggazine] = new Array();
		this.subscribers[_maggazine].push(_subscriber);

		return true;
	};

	this.publish = function(_sender, _message) {
		if(!this.sandbox[_sender]) this.sandbox[_sender] = new Array();
		this.sandbox[_sender].push(_message);

		return true;
	};

	this.timer = function() {
		// gears installed & allowed
		if (hotdog && coolcat && 1 == 2) {
			if (!this.factory_timer) this.factory_timer = google.gears.factory.create('beta.timer', '1.0');
		}
		// NO gears: use window setInterval() instead
		else {
			this.factory_timer = window;
		}

		return this.factory_timer;
	};

	this.roll = function(_sec) {
		this.id = this.timer().setInterval(CallBack(this, this.core), Number(_sec) * 1000);
		this.rolling = true;
	};

	//
	// where's algorithm!?
	this.core = function() {
		try {
			for(var _sender in this.sandbox) {
				for(var _maggazine in this.subscribers) {
					if(_sender != _maggazine) continue;
					for(var i=0; i<this.subscribers[_maggazine].length; i++) {
						if(this.sandbox[_sender]) this.subscribers[_maggazine][i].onmessage(_sender, this.sandbox[_sender]);
					} // call onmessage of subscriber

					this.sandbox[_sender] = null; // clear message
				} // subscribers iteration end.
			} // sandbox iteration end.
		} catch(e) {
			//
			// ToDo:
			// GMS can catch most of exceptions.
			// so, wide and strong control required
			
			if("Failed to start update task.".match(e.message)) {
				alert(MSG[546])
				// alert("Some store of gears got twisted.\r\nYour need to restart whole your browser to get offline\r\nSorry"); // MSG
			} else {
				// alert("GMS fail: " + e.message);
			}
			this.timer().clearInterval(this.id);
			this.rolling = false;
		}
	};

	this.roll(1);
};




//
// minify workerpool
//
var WorkerPool = function(_candidate, _msg) {
	this.identify_name = "WorkerPool";
	this.worker = this.training_center(this.basic_practice, _candidate);
	this.workerpool().onmessage = this.onmessage; 

	//
	// create worker
	this.worker_id = this.workerpool().createWorker(this.worker);
	
	//
	// order
	this.workerpool().sendMessage(_msg, this.worker_id);
};

WorkerPool.prototype.workerpool = function() {
	if(!this.factory_workerpool) this.factory_workerpool = google.gears.factory.create('beta.workerpool', '1.0');

	return this.factory_workerpool;
};

WorkerPool.prototype.onmessage = function(order, sender, message) {
};

WorkerPool.prototype.basic_practice = function() {
	google.gears.workerPool.onmessage = function(order, sender, message) {
		var result = [[code]];
		google.gears.workerPool.sendMessage(result, sender);
	}
};

WorkerPool.prototype.training_center = function(_practice, _candidate) {
	var _code = String(_practice);
	_code = _code.replace(/^[^\r\n]+/,'');
	_code = _code.replace(/[^\r\n]+$/,'');
	_code = _code.replace(/\[\[\w+\]\]/gi, "(" + String(_candidate) + ")()");

	return _code;
};




function turnoff_stores() {
	var _ls = google.gears.factory.create('beta.localserver', '1.0')
	var workspace_id = ExtractIdFromUrl("workspace");
	var mfs = "m_files_" + workspace_id;
	var rds = "r_data_" + workspace_id;
	var mts = "m_templates_" + workspace_id;
	var rts = "r_templates_" + workspace_id;

	_ls.openStore(rds).enabled = false;
	_ls.openStore(rts).enabled = false;
	_ls.openManagedStore(mfs).enabled = false;
	_ls.openManagedStore(mts).enabled = false;
};

function destroy_stores() {
	var _ls = google.gears.factory.create('beta.localserver', '1.0')
	var workspace_id = ExtractIdFromUrl("workspace");
	var mfs = "m_files_" + workspace_id;
	var rds = "r_data_" + workspace_id;
	var mts = "m_templates_" + workspace_id;
	var rts = "r_templates_" + workspace_id;

	_ls.removeStore(rds);
	_ls.removeStore(rts);
	_ls.removeManagedStore(mfs);
	_ls.removeManagedStore(mts);
};

function clear_db() {
	var workspace_id = ExtractIdFromUrl("workspace");
	var database = "db_" + workspace_id;

	var _db = google.gears.factory.create('beta.database', '1.0');
	_db.close();
	_db.open(database);

	var _ts = new Hash();
	_ts.update(Synki_LocalDB_Schema);
	_ts.each(function(p) {
		if(dirtyQueryToRemove[p.key]) {
			var qry = "delete from " + p.key + " where " + dirtyQueryToRemove[p.key] + "=" + workspace_id
			_db.execute(qry);
		}
	});
};

function destroy_db() {
	var workspace_id = ExtractIdFromUrl("workspace");
	var database = "db_" + workspace_id;

	var _db = google.gears.factory.create('beta.database', '1.0');
	_db.close();
	_db.open(database);

	var _ts = new Hash();
	_ts.update(Synki_LocalDB_Schema);
	_ts.each(function(p) {
		_db.execute("DROP TABLE IF EXISTS " + p.key);
	});
};

function DestroyGears() {
	destroy_stores();
	destroy_db();
};

//
// temporary sync http request.
function http_get(url, _request) {
  if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
  }

  xmlhttp.open('GET', url, false);
  xmlhttp.send(null);

	if(_request) {
		return xmlhttp;
	} else {
		return xmlhttp.responseText;
	}
};
