/**********************************************************************
* 
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/

var runner = require('lib/types/runner')

var actions = require('lib/actions')
var features = require('lib/features')

var core = require('features/core')

try{
	var sharp = requirejs('sharp')

} catch(err){
	var sharp = null
}

if(typeof(process) != 'undefined'){
	var cp = requirejs('child_process')
	var fse = requirejs('fs-extra')
	var pathlib = requirejs('path')
	var glob = requirejs('glob')
	// XXX migrate to exifreader as it is a bit more flexible...
	// 		...use it in browser mode...
	//var exifReader = requirejs('exifreader')
	var exifReader = requirejs('exif-reader')

	var file = require('imagegrid/file')
}



/*********************************************************************/
// helpers...

if(typeof(process) != 'undefined'){
	var copy = file.denodeify(fse.copy)
	var ensureDir = file.denodeify(fse.ensureDir)
}


//---------------------------------------------------------------------

var normalizeOrientation =
module.normalizeOrientation =
function(orientation){
	return {
		orientation: ({
				0: 0,
				1: 0,
				2: 0,
				3: 180,
				4: 0,
				5: 90,
				6: 90,
				7: 90, 
				8: 270,
			})[orientation],
		flipped: ({
				0: null,
				1: null,
				2: ['horizontal'],
				3: null,
				4: ['vertical'],
				5: ['vertical'],
				6: null,
				7: ['horizontal'],
				8: null,
			})[orientation],
	} }



//---------------------------------------------------------------------
// Convert image metadata from exif-reader output to format compatible 
// with exiftool (features/metadata.js)

// Format:
// 	{
// 		// simple key-key pair...
// 		'path.to.value': 'output-key',
//
// 		// key with value handler...
// 		'path.to.other.value': ['output-key', handler],
//
// 		// alias to handler...
// 		'path.to.yet.another.value': ['output-key', 'path.to.other.value'],
// 	}
//
var EXIF_FORMAT =
module.EXIF_FORMAT = {
	// camera / lens...
	'image.Make': 'make',
	'image.Model': 'cameraModelName',
	'image.Software': 'software',
	'exif.LensModel': 'lensModel',

	// exposure...
	'exif.ISO': 'iso',
	'exif.FNumber': [
		'fNumber', 
		function(v){ 
			return 'f/'+v }],
	'exif.ExposureTime': [
		'exposureTime',
		// NOTE: this is a bit of a brute-fore approach but for shutter 
		// 		speeds this should not matter...
		function(v){
			if(v > 0.5){
				return ''+ v }
			for(var d = 1; (v * d) % 1 != 0; d++){}
			return (v * d) +'/'+ d }],

	// dates...
	'exif.DateTimeOriginal': [
		'date/timeOriginal',
		function(v){
			return v.toShortDate() }],
	'image.ModifyDate': [
		'modifyDate', 
		'exif.DateTimeOriginal'],

	// IPCT...
	'image.Artist': 'artist',
	'image.Copyright': 'copyright',

	// XXX anything else???
}

// NOTE: this only reads the .rating from xmp...
var exifReader2exiftool = 
module.exifReader2exiftool =
function(exif, xmp){
	return Object.entries(EXIF_FORMAT)
		// handle exif...
		.reduce(function(res, [path, to]){
			var handler
			;[to, handler] = to instanceof Array ?
				to
				: [to]
			// resolve handler reference/alias...
			while(typeof(handler) == typeof('str')){
				handler = EXIF_FORMAT[handler][1] }
			// resolve source path...
			var value = path.split(/\./g)
				.reduce(function(res, e){ 
					return res && res[e] }, exif)
			// set the value...
			if(value !== undefined){
				res[to] = handler ?
					handler(value)
					: value }
			return res }, {})
		// handle xmp...
		.run(function(){
			var rating = xmp 
				// NOTE: we do not need the full XML 
				// 		fluff here, just get some values...
				&& parseInt(
					(xmp.toString()
							.match(/(?<match><(xmp:Rating)[^>]*>(?<value>.*)<\/\2>)/i) 
						|| {groups: {}})
					.groups.value)
			rating
				&& (this.rating = rating) }) }




/*********************************************************************/

var SharpActions = actions.Actions({
	config: {
		'preview-normalized': true,

		// can be:
		// 	'gids'
		// 	'files'
		'preview-progress-mode': 'gids',

		'preview-generate-threshold': 2000,

		// NOTE: this uses 'preview-sizes' and 'preview-path-template' 
		// 		from filesystem.IndexFormat...
	},

	// XXX make backup name pattern configurable...
	// XXX CROP ready for crop support...
	// XXX BUG: generates lots of backups...
	makeResizedImage: ['- Image/',
		core.doc`Make resized image(s)...

			.makeResizedImage(gid, size, path[, options])
			.makeResizedImage(gids, size, path[, options])
				-> promise


		Image size formats:
			500px		- resize to make image's *largest* dimension 500 pixels (default).
			500p		- resize to make image's *smallest* dimension 500 pixels.
			500			- same as 500px


		options format:
			{
				// output image name / name pattern...
				//
				// NOTE: for multiple images this should be a pattern and not an
				// 		explicit name...
				// NOTE: if not given this defaults to: "%n"
				name: null | <str>,

				// image name pattern data...
				//
				// NOTE: for more info on pattern see: .formatImageName(..)
				data: null | { .. },

				// if true and image is smaller than size enlarge it...
				// 
				// default: null / false
				enlarge: null | true,

				// overwrite, backup or skip (default) existing images...
				//
				// default: null / false
				overwrite: null | true | 'backup',

				// if true do not write an image if it's smaller than size...
				// 
				// default: null / false
				skipSmaller: null | true,

				// XXX not implemented...
				transform: ...,
				crop: ...,

				timestamp: ...,
				logger: ...,
,			}


		NOTE: all options are optional.
		NOTE: this will not overwrite existing images.
		`,
		core.queueHandler('Making resized image', 
			// prepare the data for image resizing (session queue)...
			core.sessionQueueHandler('Gathering image data for resizing', 
				// prepare the input index-dependant data in a fast way...
				function(queue, _, images, size, path, options){
					var args = [...arguments].slice(2)
					if(queue == 'sync'){
						args.unshift(_)
						var [images, size, path, options] = args }
					// sanity check...
					if(args.length < 3){
						throw new Error('.makeResizedImage(..): '
							+'need at least: images, size and path.') }
					return [
						(images == null || images == 'all') ? 
								this.data.getImages('all')
							: images == 'current' ? 
								[this.current]
							: images instanceof Array ? 
								images 
							: [images],
						...args.slice(1),
					]},
				// prepare index independent data, this can be a tad slow...
				function(gid, _, path, options={}){
					// special case: we already got the paths...
					if(gid instanceof Array){
						return gid }

					var image = this.images[gid]
					// options...
					var {
						name, 
						data, 
					} = options || {}
					name = name || '%n'
					// skip non-images...
					if(!image || !['image', null, undefined]
							.includes(image.type)){
						return runner.SKIP }
					return [[
						// source...
						this.getImagePath(gid),
						// target...
						pathlib.resolve(
							this.location.path,
							pathlib.join(
								path, 
								// if name is not a pattern do not re-format it...
								name.includes('%') ?
									this.formatImageName(name, gid, data || {})
									: name)),
						// image data...
						// note: we include only the stuff we need...
						{
							orientation: image.orientation,
							flipped: image.flipped,
							// crop...
						},
					]] }),
			// do the actual resizing (global queue)...
			function([source, to, image={}], size, _, options={}){
				// handle skipped items -- source, to and image are undefined...
				if(source == null){
					return undefined }

				// sizing...
				var fit = 
					typeof(size) == typeof('str') ?
						(size.endsWith('px') ?
							'inside'
						: size.endsWith('p') ?
							'outside'
						: 'inside')
					: 'inside'
				size = parseInt(size)
				// options...
				var {
					enlarge,
					skipSmaller,
					overwrite,
					transform, 
					timestamp,
					backupImagePattern,
					//logger, 
				} = options
				// defaults...
				transform = transform === undefined ? 
					true 
					: transform
				timestamp = timestamp || Date.timeStamp()
				// backup by default...
				overwrite = overwrite === undefined ? 
					'backup' 
					: overwrite
				backupImagePattern = 
					(backupImagePattern 
						|| '${PATH}.${TIMESTAMP}${COUNT}.bak')
					.replace(/\${PATH}|$PATH/, to)
					.replace(/\${TIMESTAMP}|$TIMESTAMP/, timestamp)
				// backup...
				// NOTE: we are doing the check at the very last moment and 
				// 		not here to avoid race conditions as much as practical...
				var backupName = function(){
					var i = 0
					var n
					do{
						n = backupImagePattern
							.replace(/\${COUNT}|$COUNT/, i++ ? '.'+i : i)
					} while(fse.existsSync(n))
					return n }

				var img = sharp(source)
				return (skipSmaller ?
						// skip if smaller than size...
						img
							.metadata()
							.then(function(m){
								// skip...
								if((fit == 'inside'
											&& Math.max(m.width, m.height) < size)
										|| (fit == 'outside'
											&& Math.min(m.width, m.height) < size)){
									return }
								// continue...
								return img })
						: Promise.resolve(img))
					// prepare to write...
					.then(function(img){
						return img 
							&& ensureDir(pathlib.dirname(to))
								.then(function(){
									// handle existing image...
									if(fse.existsSync(to)){
										// rename...
										if(overwrite == 'backup'){
											fse.renameSync(to, backupName(to))
										// remove...
										} else if(overwrite){
											fse.removeSync(to)
										// skip...
										} else {
											return Promise.reject('target exists') } }
									// write...
									return img
										.clone()
										// handle transform (.orientation / .flip) and .crop...
										.run(function(){
											if(transform && (image.orientation || image.flipped)){
												image.orientation
													&& this.rotate(image.orientation)
												image.flipped
													&& image.flipped.includes('horizontal')
													&& this.flip() }
												image.flipped
													&& image.flipped.includes('vertical')
													&& this.flop() 
											// XXX CROP
											//if(crop){
											//	// XXX
											//}
										})
										.resize({
											width: size,
											height: size,
											fit: fit,
											withoutEnlargement: !enlarge,
										})
										.withMetadata()
										.toFile(to) 
										.then(function(){
											// XXX what should we return???
											return to }) }) }) })],

	/* XXX don't like that the old code is actualky shorter...
	//		...revise...
	// XXX this does not update image.base_path -- is this correct???
	// XXX BROKEN: this seems not to do anything now...
	// 		....not sure if this needs fixing as it will get removed soon,
	// 		but the finding out the reason it was broken might be useful...
	_makePreviews: ['- Sharp|File/Make image $previews (old)',
		core.doc`Make image previews

			Make previews for all images...
			.makePreviews()
			.makePreviews('all')
				-> promise

			Make previews for current image...
			.makePreviews('current')
				-> promise

			Make previews for specific image(s)...
			.makePreviews(gid)
			.makePreviews([gid, gid, ..])
				-> promise


			Make previews of images, size and at base_path...
			.makePreviews(images, sizes)
			.makePreviews(images, sizes, base_path)
				-> promise

	
		NOTE: if base_path is given .images will not be updated with new 
			preview paths...
		NOTE: currently this is a core.sessionQueueHandler(..) and not a .queueHandler(..)
			mainly because we need to add the preview refs back to the index and this
			would need keeping the index in memory even if we loaded a different index,
			this is possible but needs more thought.
		`,
		core.sessionQueueHandler('Make image previews', 
			function(queue, images, ...args){
				// get/normalize images...
				return [
					(images == null || images == 'all') ? 
							this.data.getImages('all')
						: images == 'current' ? 
							[this.current]
						: images instanceof Array ? 
							images 
						: [images],
					...args,
				] },
			function(gid, sizes, base_path, logger){
				var that = this

				var logger_mode = this.config['preview-progress-mode'] || 'gids'

				// get/normalize sizes....
				var cfg_sizes = this.config['preview-sizes'].slice() || []
				cfg_sizes
					.sort()
					.reverse()
				// XXX revise...
				if(sizes){
					sizes = sizes instanceof Array ? sizes : [sizes]
					// normalize to preview size...
					sizes = (this.config['preview-normalized'] ? 
						sizes
							.map(function(s){ 
								return cfg_sizes.filter(function(c){ return c >= s }).pop() || s })
						: sizes)
							.unique()
				} else {
					sizes = cfg_sizes }

				// partially fill in the template...
				var index_dir = this.config['index-dir'] || '.ImageGrid'
				var path_tpl = that.config['preview-path-template']
					.replace(/\$INDEX|\$\{INDEX\}/g, index_dir)
				var set_hidden_attrib = true

				var img = this.images[gid]
				var base = base_path 
					|| img.base_path 
					|| this.location.path

				return Promise.all(
					sizes
						.map(function(size, i){
							var name = path = path_tpl
								.replace(/\$RESOLUTION|\$\{RESOLUTION\}/g, parseInt(size))
								.replace(/\$GID|\$\{GID\}/g, gid) 
								.replace(/\$NAME|\$\{NAME\}/g, img.name)

							// set the hidden flag on index dir...
							// NOTE: this is done once per image...
							// NOTE: we can't do this once per call as images can
							// 		have different .base_path's...
							set_hidden_attrib 
								&& (process.platform == 'win32' 
									|| process.platform == 'win64')
								&& name.includes(index_dir)
								&& cp.spawn('attrib', ['+h', 
									pathlib.resolve(
										base,
										name.split(index_dir)[0], 
										index_dir)]) 
							set_hidden_attrib = false

							// NOTE: we are 'sync' here for several reasons, mainly because
							// 		this is a small list and in this way we can take 
							// 		advantage of OS file caching, and removing the queue
							// 		overhead, though small makes this noticeably faster...
							return that.makeResizedImage('sync', gid, size, base, { 
									name, 
									skipSmaller: true,
									transform: false,
									overwrite: false,
									logger: logger_mode == 'gids' ? 
										false 
										: logger,
								})
								// XXX handle errors -- rejected because image exists...
								.then(
									function(res){
										// update metadata...
										if(!base_path){
											var preview = img.preview = img.preview || {} 
											preview[parseInt(size) + 'px'] = name
											that.markChanged
												&& that.markChanged('images', [gid]) }
										return [gid, size, name] },
									function(err){
										// XXX erro
										logger 
											&& logger.emit('skipped', `${gid} / ${size}`)
									}) })) })],
	//*/
	// XXX EXPERIMENTAL: need a way to update the index when preview is 
	// 		created (if we did not navigate away)
	// 			- we could abort the update if we go away...
	// 			- we could clone the index and if index.gid does not 
	// 				match the main index use the clone to save....
	// 		...the cloning approach would be quite simple:
	// 			ig.clone().makePreviews()
	// 		or:
	// 			ig.peer.clone().makePreviews() // hypothetical api...
	// 		the only question here is how to manage this...
	// XXX change base_path to target path...
	makePreviews: ['Sharp|File/Make image $previews (experimental)',
		core.doc`Make image previews

			Make previews for all images...
			.makePreviews()
			.makePreviews('all')
				-> promise

			Make previews for current image...
			.makePreviews('current')
				-> promise

			Make previews for specific image(s)...
			.makePreviews(gid)
			.makePreviews([gid, gid, ..])
				-> promise


			Make previews of images, size and at base_path...
			.makePreviews(images, sizes)
			.makePreviews(images, sizes, base_path)
				-> promise

	
		NOTE: if base_path is given .images will not be updated with new 
			preview paths...
		NOTE: currently this is a core.sessionQueueHandler(..) and not a .queueHandler(..)
			mainly because we need to add the preview refs back to the index and this
			would need keeping the index in memory even if we loaded a different index,
			this is possible but needs more thought.
		`,
		core.queueHandler('Make image previews', 
			core.sessionQueueHandler('Getting image data for previews', 
				// prepare the static data...
				function(queue, _, images, sizes){
					// sync mode...
					var args = [...arguments].slice(2)
					if(queue == 'sync'){
						args.unshift(_)
						var [images, sizes, ...args] = args }
					// get/normalize sizes....
					var cfg_sizes = this.config['preview-sizes'].slice() || []
					cfg_sizes
						.sort()
						.reverse()
					if(sizes){
						sizes = sizes instanceof Array ? sizes : [sizes]
						// normalize to preview size...
						sizes = 
							(this.config['preview-normalized'] ? 
								sizes
									.map(function(s){ 
										return cfg_sizes
											.filter(function(c){ 
												return c >= s })
											.pop() || s })
								: sizes)
							.unique()
					} else {
						sizes = cfg_sizes }

					// XXX we should cache this on a previous stage...
					var index_dir = this.config['index-dir'] || '.ImageGrid'

					// get/normalize images...
					return [
						(images == null || images == 'all') ? 
								this.data.getImages('all')
							: images == 'current' ? 
								[this.current]
							: images instanceof Array ? 
								images 
							: [images],
						sizes,
						// name template -- partially filled...
						this.config['preview-path-template']
							.replace(/\$INDEX|\$\{INDEX\}/g, index_dir),
						// NOTE: this is not the most elegant way to go but
						// 		it's better than getting it once per image...
						index_dir,
						...args,
					] },
				// generate image paths...
				function(gid, sizes, path_tpl, index_dir, base_path){
					var that = this
					var img = this.images[gid]
					var base = base_path 
						|| img.base_path 
						|| this.location.path
					return [[
						gid,
						// source...
						this.getImagePath(gid), 
						// targets -- [[size, to], ...]...
						sizes
							.map(function(size){
								var name = path_tpl
									.replace(/\$RESOLUTION|\$\{RESOLUTION\}/g, parseInt(size))
									.replace(/\$GID|\$\{GID\}/g, gid) 
									.replace(/\$NAME|\$\{NAME\}/g, img.name)
								return [
									size,
									pathlib.resolve(
										that.location.path,
										pathlib.join(base, name)),
								] }),
						index_dir,
					]]}),
			// generate the previews...
			// NOTE: this is competely isolated...
			// XXX args/logger is wrong here...
			function([gid, source, targets, index_dir], logger){
				var that = this
				//var logger_mode = this.config['preview-progress-mode'] || 'gids'
				
				// NOTE: if this is false attrib will not be called...
				var set_hidden_attrib = true
				return Promise.all(
					targets
						.map(function([size, target]){
							// set the hidden flag on index dir...
							// NOTE: this is done once per image...
							// NOTE: we can't do this once per call as images can
							// 		have different .base_path's...
							set_hidden_attrib 
								&& (process.platform == 'win32' 
									|| process.platform == 'win64')
								&& target.includes(index_dir)
								&& cp.spawn('attrib', ['+h', 
									pathlib.join(target.split(index_dir)[0], index_dir)]) 
							set_hidden_attrib = false

							// NOTE: we are 'sync' here for several reasons, mainly because
							// 		this is a small list and in this way we can take 
							// 		advantage of OS file caching, and removing the queue
							// 		overhead, though small makes this noticeably faster...
							return that.makeResizedImage('sync', [[source, target]], size, null, { 
									name, 
									skipSmaller: true,
									transform: false,
									overwrite: false,
									//logger: logger_mode == 'gids' ? 
									//	false 
									//	: logger,
								})
								.then(
									function(res){
										// update metadata...
										// XXX do this only if we are in the same index......
										// 		...might be fun to create a session 
										// 		queue for this at the start and if it 
										// 		survives till this point we use it...
										/*
										if(!base_path){
											var preview = img.preview = img.preview || {} 
											preview[parseInt(size) + 'px'] = name
											that.markChanged
												&& that.markChanged('images', [gid]) }
										//*/
										return [gid, size, name] },
									function(err){
										logger 
											&& logger.emit('skipped', `${gid} / ${size}`)
									}) })) })],

	// XXX revise logging and logger passing...
	cacheMetadata: ['- Sharp|Image/',
		core.doc`Cache metadata

			Cache metadata for current image...
			.cacheMetadata()
			.cacheMetadata('current')
				-> promise([ gid | null ])

			Force cache metadata for current image...
			.cacheMetadata(true)
			.cacheMetadata('current', true)
				-> promise([ gid | null ])

			Cache metadata for all images...
			.cacheMetadata('all')
				-> promise([ gid | null, .. ])

			Force cache metadata for all images...
			.cacheMetadata('all', true)
				-> promise([ gid | null, .. ])

			Cache metadata for specific images...
			.cacheMetadata([ gid, .. ])
				-> promise([ gid | null, .. ])

			Force cache metadata for specific images...
			.cacheMetadata([ gid, .. ], true)
				-> promise([ gid | null, .. ])


		This will:
			- quickly reads/caches essential (.orientation and .flipped) metadata
			- quickly read some non-essential but already there values
			- generate priority previews for very large images (only when in index)


		This will overwrite/update if:
			- .orientation and .flipped iff image .orientation AND .flipped 
				are unset or force is true
			- metadata if image .metadata is not set or 
				.metadata.ImageGridMetadata is not set
			- all metadata if force is set to true


		NOTE: this will effectively update metadata format to the new spec...
		NOTE: for info on full metadata format see: .readMetadata(..)
		`,
		core.sessionQueueHandler('Cache image metadata', 
			// XXX timeouts still need tweaking...
			{quiet: true, pool_size: 2, busy_timeout: 400}, 
			//{quiet: true, pool_size: 2, busy_timeout_scale: 10}, 
			// parse args...
			function(queue, image, ...args){
				var that = this
				var force = args[0] == 'force'

				// expand images...
				var images = image == 'all' ?
						this.images.keys()
					: image == 'loaded' ?
						this.data.getImages('loaded')
					: image instanceof Array ?
						image
					: [this.data.getImage(image || 'current')]
				// narrow down the list...
				images = force ? 
					images 
					: images
						.filter(function(gid){
							var img = that.images[gid]
							return img
								// high priority must be preset...
								&& ((img.orientation == null
										&& img.flipped == null)
									// update metadata...
									|| (img.metadata || {}).ImageGridMetadata == null) })
				return [
					images,
					...args,
				] },
			function(image, force, logger){
				var that = this

				// XXX cache the image data???
				var gid = this.data.getImage(image)
				var img = this.images[gid]
				var path = img && that.getImagePath(gid)

				// XXX
				//var base_path = that.location.load == 'loadIndex' ?
				//	null
				//	: tmp
				//var base_path = img && img.base_path
				var base_path
		
				// skip...
				if(!(img && path
						&& (force
							// high priority must be preset...
							|| (img.orientation == null
								&& img.flipped == null)
							// update metadata...
							|| (img.metadata || {}).ImageGridMetadata == null))){
					return }

				// XXX handle/report errors...
				return sharp(that.getImagePath(gid))
					.metadata()
					.then(function(metadata){
						// no metadata...
						if(metadata == null){
							return }

						var o = normalizeOrientation(metadata.orientation)
						;(force || img.orientation == null)
							// NOTE: we need to set orientation to something
							// 		or we'll check it again and again...
							&& (img.orientation = o.orientation || 0)
						;(force || img.flipped == null)
							&& (img.flipped = o.flipped)

						// mark metadata as partially read...
						// NOTE: this will intentionally overwrite the 
						// 		previous reader mark/mode...
						img.metadata =
							Object.assign(
								img.metadata || {}, 
								{ 
									ImageGridMetadataReader: 'sharp/exif-reader/ImageGrid',
									// mark metadata as partial read...
									// NOTE: partial metadata will get reread by 
									// 		the metadata feature upon request...
									ImageGridMetadata: 'partial', 
								})

						// read the metadata...
						var exif = metadata.exif 
							&& exifReader(metadata.exif) 
						exif
							&& Object.assign(
								img.metadata, 
								exifReader2exiftool(exif, metadata.xmp))

						// if image too large, generate preview(s)...
						// XXX EXPERIMENTAL...
						var size_threshold = that.config['preview-generate-threshold']
						if(size_threshold
								&& img.preview == null
								&& Math.max(metadata.width, metadata.height) > size_threshold){
							logger && logger.emit('Image too large', gid)
							// XXX make this more generic...
							// 		...if 'loadImages' should create previews in tmp...
							that.location.load == 'loadIndex'
								&& that.makePreviews(gid, 
									that.config['preview-sizes-priority'] || 1080,
									base_path,
									logger) }

						that.markChanged
							&& that.markChanged('images', [gid])
						that.ribbons
							&& that.ribbons.updateImage(gid) 

						return gid }) })],
	cacheAllMetadata: ['- Sharp/Image/',
		'cacheMetadata: "all" ...'],

	// XXX IDEA: action default context...
	// 		...a way for an action to be run in a context by default with
	// 		a way to override explicitly if needed...
	// 		this will enable action chaining by default...
	// 		now:
	// 			ig
	// 				.someAction()
	// 				.then(function(){
	// 					ig.someOtherAction() })
	// 		target:
	// 			ig
	// 				.someAction()
	// 				.someOtherAction()
	// 		...considering how often this might be useful would be nice
	// 		to make this a constructor/framework feature...

	// XXX EXPERIMENTAL...
	// XXX if we are not careful this may result in some data loss due 
	// 		to unlinking or double edits before save... 
	// 		(REVISE!!!)
	// XXX it is also possible to save the foreground state while the 
	// 		task is running... 
	// 		this should not be destructive unless saving with the exact 
	// 		same timestamp...
	// 		...this however, if some structure is unlinked, can lead to 
	// 		the later background save shadowing some earlier changes in 
	// 		the foreground...
	// XXX move this to features/filesystem.js???
	// 		...or a separate high-level module something like scripts...
	makeIndex: ['- File/',
		core.doc`

			.makeIndex()
			.makeIndex(options)
				-> promise

		options format:
			{
				// if false this will run the actions in the current context...
				//
				// default: true
				linked: <bool>,

				// if true read metadata...
				//
				// default: true
				metadata: <book> | 'full',

				// if true create previews...
				//
				// default: true
				previews: <book>,
			}

		NOTE: this will save the index in the background, this will not affect 
			foreground .changes but will update the foreground data...
			this will allow modifying stuff while the tasks are running and then 
			saving the changes correctly and allow the user to leave the index...
		`,
		function(options={}){
			var context = 
				options.linked === false ? 
					this 
					: this.link()
			return Promise.all([
				// metadata...
				options.metadata !== false
					&& ((options.metadata == 'full' 
							&& context.readAllMetadata) ?
						// full (slow)...
						context.readAllMetadata()
						// partial (fast)...
						: (context.cacheAllMetadata
							&& context.cacheAllMetadata())),
				// previews...
				options.previews !== false
					&& context.makePreviews
					&& context.makePreviews(),
			// save...
			]).then(function(){
				context.saveIndex() }) }],
})


var Sharp = 
module.Sharp = core.ImageGridFeatures.Feature({
	title: '',
	doc: '',

	tag: 'sharp',
	depends: [
		'location',
		'index-format',
	],

	actions: SharpActions, 

	isApplicable: function(){ return !!sharp },

	handlers: [
		// NOTE: this is about as fast as filtering the images and 
		// 		calling only on the ones needing caching...
		// 		...but this is not a no-op, especially on very large 
		// 		indexes...
		// XXX this needs to be run in the background...
		// XXX this is best done in a thread 
		[['loadIndex',
				'loadImages', 
				'loadNewImages'],
			'cacheMetadata: "all"'],

		// set orientation if not defined...
		// NOTE: progress on this is not shown so as to avoid spamming 
		// 		the UI...
		['updateImage',
			function(_, gid){
				var that = this
				// NOTE: as this directly affects the visible lag, this 
				// 		must be as fast as possible...
				// NOTE: running .cacheMetadata(..) in sync mode here forces
				// 		the image to update before it gets a change to get
				// 		drawn...
				;((this.images[gid] || {}).metadata || {}).ImageGridMetadata
					|| this.cacheMetadata('sync', gid, false) }],

		// XXX need to:
		// 		- if image too large to set the preview to "loading..."
		// 		- create previews...
		// 		- update image...
		/*
		['updateImage.pre',
			function(gid){
				var that = this
				if(this.images[gid].preview == null){
					sharp(this.getImagePath(gid))
						.metadata()
						.then(function(metadata){
							// current image is larger than any of the previews...
							if(Math.max(metadata.width, metadata.height) 
									> Math.max.apply(Math, that.config['preview-sizes'])){
								// create the currently needed preview first...
								that.makePreviews(gid, that.ribbons.getVisibleImageSize())
									.then(function(){
										// load the created preview...
										that.ribbons.updateImage(gid)

										// create the rest...
										that.makePreviews(gid) }) } }) } }]
		//*/
	],
})




/**********************************************************************
* vim:set ts=4 sw=4 :                               */ return module })
