module('lively.PartsBin').requires('lively.Traits').toRun(function() {

Object.subclass('lively.PartsBin.PartItem',
'initializing', {
	initialize: function($super, partOrName, partsSpaceName) {
		this.partsSpaceName = partsSpaceName
		if (Object.isString(partOrName)) {
			this.name = partOrName;
			this.part = null;
		} else {
			this.name = partOrName.name;
			this.part = partOrName;
		}
		this.json = null;
	},

},
'accessing', {
	getLogoURL: function() {
		return this.getPartsSpace().getURL().withFilename(this.name + ".svg")
	},
	getFileURL: function() {
		return this.getPartsSpace().getURL().withFilename(this.name + ".json")
	},
	getPartsSpace: function() {
		return lively.PartsBin.partsSpaceNamed(this.partsSpaceName);
	},

	setPartFromJSON: function(json)	{
		var part = this.deserializePart(json);
		this.setPart(part);
	},
	setPart: function(part) {
		this.part = part;
	},


},
'naming', {
	makeUpPartName: function() {
		if ($morph(this.targetName)){
			var i = 2
			while($morph(this.targetName + i)) { i++}
			return this.targetName + i;
		} else {
			return this.targetName;
		}
	},
},
'serialization', {
	getSerializer: function() {
		return Config.isNewMorphic ? 
			ObjectGraphLinearizer.forNewLivelyCopy() : 
			ObjectGraphLinearizer.forLivelyCopy();
	},
	deserializePart: function(json) {
		// FIXME cleanup
		var jso = JSON.parse(json),
			modulesForDeserialization = lively.persistence.Serializer.sourceModulesIn(jso);
		modulesForDeserialization.forEach(function(ea) { var m = module(ea); if (m != Global && !m.isLoaded()) m.load(true) });

		var serializer = this.getSerializer();
		serializer.showLog = false;
		var part = serializer.deserializeJso(jso),
			requiredModules = part.partInfo && part.partInfo.requiredModules;
		if (requiredModules)
			requiredModules.forEach(function(ea) { var m = module(ea); if (m != Global && !m.isLoaded()) m.load(true) });

		if (part.onLoadFromPartsBin)
			part.onLoadFromPartsBin();

		return part;
	},
	serializePart: function(part) {

		// if (!(part instanceof Morph)) throw new Error('Cannot serialize non-Morph as part!');
		var json, logoMorph,
			oldPos = part.getPosition();
		// use ContextJS instead?
		var serializer = this.getSerializer();
		serializer.showLog = false;
		part.setPosition(pt(0,0));
		try {
			json = serializer.serialize(part);
			svgLogo = part.asSVGLogo();
		} catch(e){
			throw e 
		} finally {
			part.setPosition(oldPos);
		}

		return {json: json, logo: svgLogo};
	},

},
'upload and download', {
	load: function(isAsync, rev) {
		var webR = new WebResource(this.getFileURL()).forceUncached();
		if (isAsync) webR.beAsync();
		connect(webR, 'content', this, 'json');
		webR.get(rev);
		return this;
	},
	loadPart: function(isAsync, optCached, rev) {
		if (optCached) {
			this.setPartFromJSON(this.json);
			return this;
		}
		connect(this, 'json', this, 'setPartFromJSON')
		this.load(isAsync, rev);
		return this;
	},
	loadPartVersions: function(isAsync) {
		var webR = new WebResource(this.getFileURL());
		if (isAsync) webR.beAsync();		
		connect(webR, 'versions', this, 'partVersions');
		webR.getVersions();
		return this;
	},
	loadRevision: function(isAsync, rev) {
		return this.loadPart(isAsync, undefined, rev)
	},

	copyToPartsSpace: function(partsSpace) {
		this.partsSpaceName = partsSpace.getName();
		if (!this.part) this.load().loadPart();
		this.uploadPart();
		partsSpace.createPartItemNamed(this.name);
	},
	moveToPartsSpace: function(partsSpace) {
		this.load().loadPart(); // so part is local
		try {
			this.del(); // important to delete before copying because partsSpace get set in copy
		} finally {
			this.copyToPartsSpace(partsSpace);
		}
	},
	del: function() {
debugger
		new WebResource(this.getLogoURL()).del();	
		new WebResource(this.getFileURL()).del();	
		this.getPartsSpace().removePartItemNamed(this.name);
	},
	uploadPart: function() {
		if (!this.part) {
			alert('Cannot upload part item ' + this.name + ' because there is no part!')
			return;
		}
		this.part.getPartsBinMetaInfo().setPartsSpace(this.getPartsSpace());
		var serialized = this.serializePart(this.part);
		new WebResource(this.getFileURL()).put(serialized.json);
		new WebResource(this.getLogoURL()).put(serialized.logo);
		alertOK('Copied ' + this.part.name + ' to PartsBin ' + this.getPartsSpace().getURL());
	},

},
'converting', {
	asPartsBinItem: function() {
		return new lively.Scripting.PartPinItem(this.getPartsSpace().getURL(), this.name, this)
	},
});

Object.subclass('lively.PartsBin.PartsBinMetaInfo',
'accessing', {

	setURL: function(url) {
		var name = lively.PartsBin.partsSpaceWithURL(url.getDirectory()).getName();
		this.setPartsSpaceName(name);
	},

	getName: function() {
		if (!this.url) return undefined;
		return this.url.filename().replace(".json", "")
	},

	getPartsSpaceURL: function() { return this.getPartsSpace().getURL() },
	setPartsSpaceName: function(name) { this.partsSpaceName = name },
	getPartsSpaceName: function() { return this.partsSpaceName || 'PartsBin/' },
	setPartsSpace: function(partsSpace) { this.setPartsSpaceName(partsSpace.getName()) },
	getPartsSpace: function() { return lively.PartsBin.partsSpaceNamed(this.partsSpaceName) },
});

Object.subclass('lively.PartsBin.PartsSpace',
'documentation', {
	documentation: 'A Namespace for parts of the parts bin. Usually points to a URL (directory) with serialized parts. Parts are morphs or might also be real objects. PartItems are wrapper for Parts used here in the PartsSpace.',
},
'initializing', {
	initialize: function(name) {
		this.name = name;
		this.partItems = {};
	},
	createPartItemNamed: function(name) {
		return new lively.PartsBin.PartItem(name, this.name);
	},

},
'accessing', {
	getPartNames: function() {
		return Properties.own(this.partItems);
	},
	getPartItemNamed: function(name) {
		if (!this.partItems[name])
			this.partItems[name] = this.createPartItemNamed(name);
		return this.partItems[name]
	},
	removePartItemNamed: function(name) {
		delete this.partItems[name];
	},


	getURL: function() { return URL.ensureAbsoluteCodeBaseURL(this.name).asDirectory() },
	getPartItems: function() {
		return Properties.ownValues(this.partItems);
	},
	setPartItemsFromURLList: function(listOfUrls) {
		// listOfUrls are urls of serialized parts
		var names = listOfUrls
			.invoke('filename')
			.select(function(ea){ return  ea.match(/(.+)\.json$/)})
			.collect(function(ea){ return ea.replace(".json", "")});
		var items = {};
		names.forEach(function(name) { items[name] = this.createPartItemNamed(name) }, this);
		this.partItems = items;
	},
	getName: function() { return this.name },
},
'loading', {
	load: function(async) {
		var webR = new WebResource(this.getURL());
		if (async) webR.beAsync();
		// ask for the files of a directory and update so that the partItems correspond to the files found
		connect(webR, 'subDocuments', this, 'setPartItemsFromURLList', {
			converter: function(webResources) { return webResources.invoke('getURL') }})
		webR.getSubElements();
		return this;
	},
	ensureExistance: function() {
		var webR = new WebResource(this.getURL());
		if (!webR.exists()) webR.ensureExistance();
	},
},
'debugging', {
	toString: function() { return this.constructor.name + '(' + this.name + ')' },
});
Object.extend(lively.PartsBin, {
	partSpaces: {},
	addPartsSpace: function(space) {
		this.partSpaces[space.name] = space;
	},
	removePartsSpace: function(name) {
		delete this.partSpaces[name];
	},
	partsSpaceNamed: function(name) {
		if (!this.partSpaces[name])
			this.addPartsSpaceNamed(name);
		return this.partSpaces[name];
	},
	partsSpaceWithURL: function(url) {
		var name = url.isIn(URL.codeBase) ? url.relativePathFrom(URL.codeBase) : url.toString();
		return this.partsSpaceNamed(name);
	},

	addPartsSpaceNamed: function(name) {
		var space = new lively.PartsBin.PartsSpace(name);
		this.addPartsSpace(space);
		return space;
	},
	getPart: function(partName, optPartsSpaceName) {
		var partItem = this.getPartItem(partName, optPartsSpaceName);
		return partItem ? partItem.load().loadPart().part : null;
	},
	getPartItem: function(partName, optPartsSpaceName) {
		// PartsBin -> PartsSpace -> PartItem -> Part
		var partsSpaceName = optPartsSpaceName || 'PartsBin',
			partsSpace = this.partsSpaceNamed(partsSpaceName),
			partItem = partsSpace.getPartItemNamed(partName);
		return partItem;
	},
});

Trait('lively.PartsBin.PartTrait', { 
	copyToPartsBin: function() {
		if (!this.name) {
			alert('cannot copy to partsBin without a name');
			rteturn;
		}
		if (this.getPartsBinMetaInfo().partsSpaceName && 		
			! this.getPartsBinMetaInfo().partsSpaceName.startsWith("PartsBin")) {
			alertOK("resetting partsSpaceName of " + this)
			delete this.getPartsBinMetaInfo().partsSpaceName
		}

		this.getPartItem().uploadPart();
	},
	copyToMyPartsBin: function() {
		// FIXME this code was not yet refactored to work with the new PartsSpace/PartItem model
		var userName = localStorage.livelyUserName;
		if (!userName) throw Error('Cannot copyToMyPartsBin without userName')
	
		var userDir = URL.codeBase.withFilename(userName + '/MyPartsBin/');
		var wr = new WebResource(userDir);
		if (!wr.exists()) {
			alert("created " + userDir)
			 wr.create();
		}

		var partsBinUrl = URL.codeBase.withFilename(userName +  '/MyPartsBin/');
		wr = new WebResource(partsBinUrl);
		if (!wr.exists()) {
			alert("created " + partsBinUrl)
			wr.create();
		}

		this.copyToPartsBinUrl(partsBinUrl);
	},

	interactiveCopyToMyPartsBin: function() {
		if (!localStorage.livelyUserName)
			this.world().askForUserName();
		if (!this.name) {
			this.world().promt('cannot copy to partsBin without a name', function(name) {
				if (name == this.toString()) {
					alert('Cannot copy '+this.toString() + 'to MyPartsBin without a name ');
					return;
				}
				this.name = name;
				this.copyToMyPartsBin()
			}.bind(this), this.toString())
		} else {
			this.copyToMyPartsBin()
		}
	},

	copyToPartsBinUrl: function(partsBinURL) {
		var partsSpace = lively.PartsBin.partsSpaceWithURL(partsBinURL);
		this.copyToPartsSpace(partsSpace);
	},
	copyToPartsSpace: function(partsSpace) {
		this.getPartsBinMetaInfo().setPartsSpace(partsSpace)
		this.getPartItem().uploadPart();
	},

	getPartsBinMetaInfo: function() {
		if (!this.partsBinMetaInfo)
			this.partsBinMetaInfo = new lively.PartsBin.PartsBinMetaInfo();
		return this.partsBinMetaInfo
	},
	getPartItem: function() {
		return new lively.PartsBin.PartItem(this, this.getPartsBinMetaInfo().getPartsSpaceName())
	},

	asSVGLogo: function() {
		var oldPos = this.getPosition();
		this.setPosition(pt(0,0))
		var logoMorph = this.asLogo()
		this.setPosition(oldPos)
		// width="2000pt" height="2000pt"
		return '<?xml version="1.0" encoding="UTF-8"?>\n'+
		'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
		'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" '+
		'xmlns:ev="http://www.w3.org/2001/xml-events" version="1.1" baseProfile="full" >\n' +
			Exporter.stringify(logoMorph.rawNode) + 
		'</svg>';
	},
});


}) // end of module