module('apps.StaticDocGeneration').requires('lively.Helper').toRun(function() {

Object.subclass('HTMLCreator',
'documentation', {
	documentation: 'provides an interface to create HTML, uses XMLConverter',
},
'generation', {
	createAndWriteDoc: function(head, body, url) {
		var webR = new WebResource(url),
			doc = this.htmlTemplate(),
			converter = new lively.Helper.XMLConverter();
		doc.documentElement.appendChild(converter.convertToXML(head, {}, doc));
		doc.documentElement.appendChild(converter.convertToXML(body, {}, doc));
		webR.put(Exporter.stringify(doc));
	},
},
'HTML elements', {
	htmlTemplate: function() { return stringToXML('<html/>').ownerDocument },
	stringOrSpec: function(content) {
		// pass in a string that is then converted to a text element or apss in some
		// complex HTML spec object
		return Object.isString(content) ? this.text(content) : content;
	},
	stringsOrSpecs: function(obj) {
		if (!obj) return [];
		return obj.collect(function(ea) { return this.stringOrSpec(ea) }, this);
	},

	head: function(children) {
		return {tagName: 'head', children: this.stringsOrSpecs(children)};
	},
	body: function(children) {
		return {tagName: 'body', children: this.stringsOrSpecs(children) || []};
	},
	frame: function(src, name) {
		return {
			tagName: 'frame',
			name: name || 'no name',
			src: src || ''
		}
	},
	frameset: function() { },
	list: function(list) {
		return {
			tagName: 'ul',
			children: list.collect(function(ea) {
				return {
					tagName: 'li',
					children: [this.stringOrSpec(ea)]
				}
			}, this)
		}
	},
	a: function(href, content, target) {
		var elem = { tagName: 'a' }
		if (href) elem.href = href;
		if (target) elem.target = target;
		if (content) elem.children = [this.stringOrSpec(content)]
		return elem;
	},
	p: function(content) {
		return {
			tagName: 'p',
			children: (Object.isArray(content) ? this.stringsOrSpecs(content) : [this.stringOrSpec(content)])
		}
	},
	script: function(src, type) {
		return {
			tagName: 'script',
			type: type || 'text/javascript',
			src: src
		}
	},
	link: function(href, type) {
		return {
			tagName: 'link',
			type: type || 'text/css',
			rel: 'stylesheet',
			href: href
		}
	},
	br: function() {
		return {tagName: 'br'}
	},
	fillWithBrs: function(list) {
		return list.inject([], function(all, ea) { return all.concat([ea, this.br()]) }, this);
	},
	text: function(string) {
		return {tagName: 'textNode', data: string}
	},
	pre: function(content, class) {
		return {
			tagName: 'pre',
			children: (Object.isArray(content) ? this.stringsOrSpecs(content) : [this.stringOrSpec(content)]),
			class: class || '',
		}
	},
	cdata: function(string) {
		return {tagName: 'cdata', data: string}
	},
	div: function(content) {
		return {
			tagName: 'div',
			children: (Object.isArray(content) ? this.stringsOrSpecs(content) : [this.stringOrSpec(content)])
		}
	},
	addName: function(name, elemSpec) {
		elemSpec.name = name;
		return elemSpec
	},
	wrap: function(elementToWrap, outer) {
		if (outer.children)
			outer.children.push(elementToWrap)
		else
			outer.children = [this.stringOrSpec(elementToWrap)]
		return outer;
	},
	hr: function() {
		return {tagName: 'hr'};
	},
	fillWithHrs: function(list) {
		return list.inject([], function(all, ea) { return all.concat([ea, this.hr()]) }, this);
	},
});
HTMLCreator.subclass('DocCreator',
'documentation', {
	documentation: 'reading and parsing Lively modules and writing generated documents',
},
'properties', {
	connections: ['readingDone','parsingDone', 'prepareResourcesDone'],
	docURL: new URL('http:\/\/lively-kernel.org/doc/'),
},
'initializing', {
	initialize: function(resources){
		this.resources = resources || null;
	},
	initResources: function(codeDirs) {
		this.resources = {
			codeDirs: codeDirs.collect(function(ea) { return URL.codeBase.withFilename(ea+'/') }),
			sources: {},
			allModules: [],
			allClasses: [],
		};
	},
},
'interface', {
	start: function(codeDirs){
		disconnectAll(this);
		connect(this, 'readingDone', this, 'parseFiles');
		connect(this, 'parsingDone', this, 'astExtraction');
		connect(this, 'prepareResourcesDone', this, 'generate');

		if (!this.resources)
			this.initResources(codeDirs);
		this.readFiles();
		this.generate();
	},
	justParse: function(codeDirs){
		disconnectAll(this);
		connect(this, 'readingDone', this, 'parseFiles');
		connect(this, 'parsingDone', this, 'astExtraction');

		this.initResources(codeDirs);
		this.readFiles()
	},
	create: function() { throw new Error('subclass should overwrite create()!') },

},
'reading and parsing', {
	readFiles: function() {
		var files = this.resources.codeDirs.inject([], function(all, dir) {
			return all.concat(this.srcCtrl().interestingLKFileNames(dir));
		}, this);
		files.forEachShowingProgress(
			this.findProgressBar(),
			function(fn) {
				var url = URL.codeBase.withFilename(fn);
				var source = new WebResource(url).get().content;
				this.resources.sources[fn] = source;
			},
			function(fn) { return 'Reading ' + fn },
			function(fn) { alertOK('Reading done'); this.readingDone = true },
			this);
	},
	parseFiles: function(){
		Properties.all(this.resources.sources).forEachShowingProgress(
			this.findProgressBar(),
			function(fn) { this.srcCtrl().addModule(fn, this.resources.sources[fn]) },
			function(fn) { return 'Parsing ' + fn },
			function(fn) { alertOK('Parsing done'); this.parsingDone = true },
			this
		);
	},
	astExtraction: function() {
		Properties.all(this.resources.sources).forEach(function(file) {
			var moduleName = this.moduleNameFromFileName(file);
			this.resources.allModules.push(moduleName);

			var ast = this.srcCtrl().rootFragmentForModule(file);

			var classDefs = ast.subElements(5/*depth*/).select(function(ea) { return ea.type == 'klassDef' })
			classDefs.forEach(function(ea) { ea.moduleName = moduleName }, this)

			this.resources.allClasses = this.resources.allClasses.concat(classDefs);

		}, this);

		alertOK('AST extraction done');
		this.prepareResourcesDone = true;
	},
},
'generation', {
	generate: function(){
		var creators = this.constructor.allSubclasses().collect(function(klass) {
			return new klass(this.resources)
		}, this);
		for (var i = 0; i < creators.length - 1; i++) {
			connect(creators[i], 'done', creators[i+1], 'create')
		}
		connect(creators.last(), 'done', Global, 'alertOK', {converter: function() { return 'All done!'}});

		creators.first().create();
	},
},
'helper', {
	moduleNameFromFileName: function(fn) {
		var result = fn.substring(0, fn.indexOf('.')); // remove file extension
		result = result.replace(/\//g, '.');
		return result
	},
	fileNameFromModuleName: function(moduleName){
		return new URL(module(moduleName).uri()).filename()
	},
	srcCtrl: function() {
		return lively.ide.startSourceControl();
	},
	findProgressBar: function() {
		var progress = $morph('progress');
		if (!progress) { throw new Error ('No progress bar!') }
		return progress;
	},


});
DocCreator.subclass('ModulesListCreator',
'properties', {
	target: 'allModules.html',
},
'interface', {
	create: function(resources) {
		resources = Object.isArray(resources) ? resources : this.resources;
		// create a list of modules
		var modules = Properties.all(resources.sources).collect(function(path) {
			return this.moduleNameFromFileName(path);
		}, this);
		var elems = modules.sort().collect(function(moduleName) {
			return this.div([
				this.a(moduleName + '_classes.html', moduleName, 'classesFrame'),
				this.text(' '),
				this.a(moduleName + '_source.html', '[source]', 'contentFrame')])
		}, this)

		elems.unshift(this.a('allClasses.html', 'all classes', 'classesFrame'))

		this.createAndWriteDoc(
			this.head(),
			this.body([this.p('Modules')].concat(elems)),
			this.docURL.withFilename('allModules.html'));

		alertOK('Generated module lists');
		this.done = true;
	},
});
DocCreator.subclass('ModulesSourceCreator',
'properties', {
	target: '[moduleName]_source.html',
},
'interface', {
	create: function(resources) {
		resources = Object.isArray(resources) ? resources : this.resources;
		Properties.own(resources.sources).forEachShowingProgress(
			this.findProgressBar(),
			function(fileName) {
				var source = resources.sources[fileName] || 'found no source for ' + fileName,
					moduleName = this.moduleNameFromFileName(fileName);					
				this.createAndWriteDoc(
					this.head(),
					this.body([this.pre(source, 'code')]),
					this.docURL.withFilename(moduleName + '_source.html'));
			},
			function(moduleName) { return 'Generating source html for ' + moduleName },
			function() { alertOK('Generated module sources'); this.done = true; },
			this)
	},
});
DocCreator.subclass('ClassesListCreator',
'properties', {
	target: '[moduleName]_classes.html + allClasses.html',
},
'interface', {
	create: function(resources) {
		resources = Object.isArray(resources) ? resources : this.resources;
		// create a list of classes
		var allClasses = []

		Properties.all(resources.sources).forEachShowingProgress(
			this.findProgressBar(),
			function(file) {
				var ast = this.srcCtrl().rootFragmentForModule(file)
				if (!ast) {
					alert('cannot create class list for ' + file);
					debugger
					return;
				}
				var classDefs = ast.subElements(5/*depth*/)
					.select(function(classDef) { return classDef.type == 'klassDef' })
					.sortBy(function(classDef) { return classDef.name.toLowerCase() })
				var classes = classDefs.collect(function(classDef) {
					return this.div(this.a(classDef.name + '.html', classDef.name, 'contentFrame'));
				}, this)
				allClasses = allClasses.concat(classes)

				var moduleName = this.moduleNameFromFileName(file)

				// write classes list
				this.createAndWriteDoc(
					this.head(),
					this.body([this.p('Classes of ' + file)].concat(classes)),
					this.docURL.withFilename(moduleName + '_classes.html')
				);
			},
			function(file) {
				var moduleName = this.moduleNameFromFileName(file)
				return 'Generating ' + this.docURL.withFilename(moduleName + '_classes.html')
			},
			function() {
				// all classes html
				allClasses = allClasses.sortBy(function(elem) { return elem.children[0].href.toLowerCase() });
				this.createAndWriteDoc(
					this.head(),
					this.body(allClasses),
					this.docURL.withFilename('allClasses.html'));

				alertOK('Generated class lists (' + allClasses.length + ' classes)' );
				this.done = true;
			},
			this)
	},
});
DocCreator.subclass('ClassMembersListCreator',
'properties', {
	target: '[className].html',
},
'interface', {
	create: function(resources) {
		resources = Object.isArray(resources) ? resources : this.resources;
		resources.allClasses.forEachShowingProgress(
			this.findProgressBar(),
			function(classDef) {
				var pageURL = this.docURL.withFilename(classDef.name + '.html');
				this.generateClassDefPage(classDef, pageURL);
			},
			function(classDef) { return this.docURL.withFilename(classDef.name + '.html').toString() },
			function() { alertOK('Generated method and property lists'); this.done = true; },
			this);
	},
},
'generation', {
	generateClassDefPage: function(classDef, pageURL) {
		var superclassMarkup = this.markupSuperclassFrom(classDef),
			classDefShort = this.markupProtoDefShort('definition', classDef),
			classDefSourceListing = this.markupProtoDefSource(classDef),
			extensionsShortAndSource = this.extensionMembersOf(classDef),
			extensionsShort = [], extensionsSource = [];
		for (var i = 0; i < extensionsShortAndSource.length; i++) {
			extensionsShort.push(extensionsShortAndSource[i][0])
			extensionsSource.push(this.list(extensionsShortAndSource[i][1]))
		}

	// -----------------------
		this.createAndWriteDoc(
			this.head(),
			this.body([superclassMarkup, classDefShort, this.hr()]
				.concat(this.fillWithHrs(extensionsShort))
				.concat([this.hr()])
				.concat([this.list(classDefSourceListing), this.hr()])
				.concat(this.fillWithHrs(extensionsSource))
	 		),
			pageURL)
	},
	protoDefsOfClass: function(classDef) {
		var ownerDef = classDef.findOwnerFragment(),
			allDefs = ownerDef.flattened().select(function(def) { return def.name == classDef.name }),
			propertyDefs = allDefs.concat(classDef).uniq().invoke('subElements').flatten(),
			protoDefs = propertyDefs.select(function(def) { return !def.isStatic() });
		return protoDefs
	},

	markupProtoDefSource: function(classDef){
		var protoDefs = classDef.subElements();
		return protoDefs.collect(function(protoDef) {
			var src = protoDef.getSourceCode();
			if (src.endsWith(',')) src = src.substring(0, src.length - 1);
			src = src.substring(src.indexOf(':') + 2, src.length);

			var div = this.div([this.text(protoDef.name), this.pre(src)])
			return this.addName(protoDef.name, this.wrap(div, this.a()));
		}, this);
	},
	markupProtoDefShort: function(definitionOrExtension, classDef) {
		var protoDefs = classDef.subElements();
		var categories = protoDefs.inject({}, function(all, def) {
			if (!all[def.category.name]) all[def.category.name] = [];
			all[def.category.name].push(def);
			return all;
		})
		var categoryDivs = Properties.own(categories).collect(function(catName) {
			var prototDefs = categories[catName],
				protoDivs = prototDefs.collect(function(protoDef) {
					var functionHeader = /function\([^\)]*\)/.exec(protoDef.getSourceCode()),
						type = functionHeader ? functionHeader[0] : ' property';
					return this.div([
						this.a('#' + protoDef.name, protoDef.name, 'contentFrame'),
		 				this.text(' ' + type)
					])
				}, this),
				categoryDiv = this.div([this.text(catName), this.list(protoDivs)]);
			return categoryDiv;
		}, this)
		return this.p([this.subHeader(definitionOrExtension, classDef), this.list(categoryDivs)]);
	},
	extensionMembersOf: function(classDef){
		var className = classDef.name,
			asts = Object.values(this.srcCtrl().modules).collect(function(ea) { return ea.ast() }),
			allExtensions = asts.inject([], function(klassExtDefs, ast) {
				var exts = ast.subElements(5).select(function(ea) { return  ea.type == 'klassExtensionDef' });
				return klassExtDefs.concat(exts);
			}),
			extsOfClass = allExtensions.select(function(ea) { return ea.name == className }),
			markupList = extsOfClass.collect(function(klassExtension) {
				var firstSub = klassExtension.subElements()[0],
					kind = 'extension'; // default
				if (firstSub)
					kind = firstSub.isStatic() ? 'static extension' : 'addMethods';
				return [
					this.markupProtoDefShort(kind, klassExtension),
					this.markupProtoDefSource(klassExtension)
				]
			}, this);
		return markupList;
	},
	markupSuperclassFrom: function(classDef) {
		var sName = classDef.superclassName;
		if (!sName) return this.p('Unknown superclass');
		if (sName == 'Object') return this.p('Superclass: Object');
		return this.p([this.text('Superclass: '), this.a(sName + '.html', sName, 'contentFrame')])
	},
},
'HTML elements', {
	subHeader: function(type, def) {
		return this.text(Strings.format('Class %s of %s in %s:%s', type, def.name, def.fileName, def.startLine()))
	},
});
DocCreator.subclass('IndexPageCreator',
'properties', {
	target: 'index.html + empty.html',
},
'interface', {
	create: function(resources) {
		resources = resources || this.resources;
		this.createEmptyHTML();
		this.createIndexHTML();	
		alertOK('Generated index page');
	},
},
'generation', {
	createEmptyHTML: function() {
		this.createAndWriteDoc(
			this.head(), this.body([this.p('Welcome to the Lively Kernel code documentation!')]),
			this.docURL.withFilename('empty.html'));
	},
	createIndexHTML: function() {
		this.createAndWriteDoc(
			this.head(),
			{
				tagName: 'frameset',
				cols: '20%,80%',
				children: [
					{
						tagName: 'frameset',
						rows: '25%,75%',
						children: [
							this.frame('allModules.html', 'modulesFrame'),
							this.frame('allClasses.html', 'classesFrame')]
					},
					this.frame('empty.html', 'contentFrame')]
			},
			this.docURL.withFilename('index.html'));
	},
});
IndexPageCreator.addMethods({
	m1: function() {},
});
Object.extend(IndexPageCreator, {
	m2: function() {},
});
}) // end of module