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

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

var data = require('imagegrid/data')
var images = require('imagegrid/images')

var core = require('features/core')
var base = require('features/base')
var ui = require('features/ui')



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

// XXX should we rename this to "select"???

// target can be:
// 		'all'
// 		'loaded'
// 		'ribbon'	- current ribbon
// 		ribbon		- specific ribbon (gid)
// 		Array
//
// NOTE: of no data is defined this will not have any effect...
// NOTE: we are not using the vanilla toggler here as it can't handle 
// 		toggling independently multiple elements...
// 
// XXX this is really slow on large sets of images...
function makeTagTogglerAction(tag){
	// get actual target gids...
	var _getTarget = function(target){
		target = target || this.current
		target = (target == 'all' 
					|| target == 'loaded' 
					|| target in this.data.ribbons) ? 
				this.data.getImages(target)
			: target == 'ribbon' ? 
				this.data.getImages('current')
			: target
		return target == null ?
				[]	
			: target instanceof Array ? 
				target 
			: [target]
	}

	// the toggler...
	var _tagToggler = toggler.Toggler('current',
		function(target, action){
			target = _getTarget.call(this, target)
			// get state...
			if(action == null){
				var res = this.data.toggleTag(tag, target, '?')
				return res.length == 1 ? res[0] : res

			} else if(action == 'on'){
				this.tag(tag, target)

			} else if(action == 'off'){
				this.untag(tag, target)
			}
		}, 
		['off', 'on'])

	// the action...
	var action = function(target, action){
		// special case: no data...
		if(this.data == null){
			return action == '??' ? ['off', 'on'] : 'off'

		// special case: multiple targets and toggle action...
		} else if((target == 'all' || target == 'loaded' || target == 'ribbon' 
					|| target instanceof Array) 
				&& (action == null || action == 'next' || action == 'prev' 
					|| action == '!')){
			var res = []
			var that = this
			var on = []
			var off = []
			target = _getTarget.call(this, target)
			target
				.forEach(function(gid){
					if((that.data.getTags(gid).indexOf(tag) < 0) 
							// invert check if action is '!'...
							+ (action == '!' ? -1 : 0)){
						on.push(gid)
						res.push('on')

					} else {
						off.push(gid)
						res.push('off')
					}
				})

			that.tag(tag, on)
			that.untag(tag, off)

			return res.length == 1 ? res[0] : res
		}

		// normal case...
		return _tagToggler.call(this, target, action)
	}

	// cheating a bit...
	action.__proto__ = toggler.Toggler.prototype
	action.constructor = toggler.Toggler
	return action
}


// Build a tag toggler undo set of attrs...
// 
// This will add:
// 	'undoable'		- predicate to check if we need to undo, to handle 
// 						introspection calls correctly...
// 	'undo'			- undo function...
// 	
var undoTag = function(action){
	return {
		// do not journal calls that have no side-effects, e.g. toggler 
		// introspection...
		// XXX should this be a generic predicate???
		undoable: function(a){
			// handle ribbon-wide operations...
			// NOTE: this is specific to .toggleMark(..)
			if(a.args[0] == 'ribbon' && action == 'toggleMark'){
				a.state = this.markedInRibbon()
				return true
			}
			// skip introspection...
			return a.args.indexOf('?') < 0 
				&& a.args.indexOf('??') < 0
		},
		undo: function(a){
			// restore state...
			if(a.state){
				this[action]('ribbon', 'off')
				this[action](a.state, 'on')

			// reverse state...
			} else {
				this[action].apply(this, 
					// XXX is argument handling here too optimistic???
					a.args.map(function(e){ 
						return e == 'on' ? 'off' 
							: e == 'off' ? 'on'
							: e })) 
			}
		},
	} }


// Shift marked image action constructor...
// 
// 	Shift marked images up/down
// 	shiftMarked('up')
// 	shiftMarked('down')
// 		-> action
// 
// The resulting action affects only images in current ribbon...
// 
// NOTE: this specific to marked/selected images...
var shiftMarked = function(direction){
	return function(ribbon){
		var that = this
		var marked = this.markedInRibbon(ribbon)
		var next 

		// need to shift focus...
		if(marked.indexOf(this.current) >= 0){
			var d = this.direction == 'right' ? 'next' : 'prev'

			var getNext = function(direction){
				var next = that.data.getImage(direction)
				while(next != null && marked.indexOf(next) >= 0){
					next = that.data.getImage(next, direction)
				}
				return next
			}

			next = getNext(d) 
				|| getNext(d == 'next' ? 'prev' : 'next')

			next != null 
				&& this.data.focusImage(next)
		}

		// shift the image...
		this.data['shiftImage'+ direction.capitalize()](marked)

		// obey the shiftImage protocol...
		this.shiftImage.apply(this, marked)
	}
}


// Shift undo function constructor...
// 
// NOTE: this is specific to shiftMarkedUp/shiftMarkedDown...
var undoShift = function(undo){
	return function(a){ 
		this[undo](this.data.getRibbon(
			undo == 'shiftMarkedUp' ? 'next' : 'prev',
			a.args.length == 0 ? a.current : a.args[0])) }}



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

var ImageMarkActions = actions.Actions({

	// a shorthand...
	// NOTE: this will return a copy...
	//
	// XXX should we add a caching scheme here???
	// 		...it would require invalidation on tagging...
	// 		the problem is that on large sets this may take up quite a 
	// 		chunk of memory...
	get marked(){
		if(this.data == null 
				|| this.data.tags == null
				|| !('marked' in this.data.tags)){
			return []
		}
		//return this.data.tags['marked'].slice()
		return this.data.getImages(this.data.tags['marked'])
	},

	markedInRibbon: ['- Mark|Ribbon/',
		function(ribbon){
			var ribbon = this.data.getRibbon(ribbon)
			var images = this.data.makeSparseImages(this.data.getImages(ribbon))

			return this.data.makeSparseImages(this.marked)
				// NOTE: this will also filter out undefined positions...
				.filter(function(img, i){ return images[i] != null })
		}],

	prevMarked: ['Mark|Navigate/Previous marked image',
		{browseMode: function(target){ 
			return this.data.getImage('current', 'before', this.marked) == null && 'disabled' }},
		function(mode){ this.prevTagged('marked', mode) }],
	nextMarked: ['Mark|Navigate/Next marked image',
		{browseMode: function(target){ 
			return this.data.getImage('current', 'after', this.marked) == null && 'disabled' }},
		function(mode){ this.nextTagged('marked', mode) }],

	cropMarked: ['Mark|Crop/Crop $marked images',
		{browseMode: function(target){ 
			return this.marked.length == 0 && 'disabled' }},
		function(flatten){ this.cropTagged('marked', 'any', flatten) }],

	removeMarkedFromCrop: ['Mark|Crop/Remove marked from crop',
		{browseMode: function(target){ 
				return (this.marked.length == 0 || !this.cropped) && 'disabled' }},
		'removeFromCrop: marked'],

	rotateMarkedCW: ['Mark/Rotate marked clockwise',
		{browseMode: 'cropMarked'},
		'rotateCW: marked'],
	rotateMarkedCCW: ['Mark/Rotate marked counterclockwise',
		{browseMode: 'cropMarked'},
		'rotateCCW: marked'],
	flipMarkedVertical: ['Mark/Flip marked vertically',
		{browseMode: 'cropMarked'},
		'flipVertical: marked'],
	flipMarkedHorizontal: ['Mark/Flip marked horizontally',
		{browseMode: 'cropMarked'},
		'flipHorizontal: marked'],
})


// NOTE: this is usable without ribbons...
var ImageMarks = 
module.ImageMarks = core.ImageGridFeatures.Feature({
	title: '',
	doc: '',

	tag: 'image-marks',

	depends: [
		'base',
	],
	suggested: [
		'image-marks-edit',
		'ui-image-marks',
		'image-marks-groups',
	],

	actions: ImageMarkActions,
})


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

var ImageMarkEditActions = actions.Actions({
	// Common use-cases:
	// 	Toggle mark on current image
	// 	.toggleMark()
	//
	// 	Mark current ribbon
	// 	.toggleMark('ribbon', 'on')
	//
	// 	Unmark all loaded images
	// 	.toggleMark('loaded', 'off')
	//
	// 	Invert marks on current ribbon
	// 	.toggleMark('ribbon')
	//
	toggleMark: ['Mark|Image/Image $mark',
		undoTag('toggleMark'),
		makeTagTogglerAction('marked')],
	toggleMarkBlock: ['Mark/Invert $block marks',
		core.doc`A block is a set of adjacent images either marked on unmarked
		in the same way
		`,
		function(target){
			target = this.data.getImage(target)

			var ribbon = this.data.makeSparseImages(this.data.getImages(target))
			var marked = this.data.makeSparseImages(this.markedInRibbon(target))

			var c = ribbon.indexOf(target)
			var state = !!marked[c]

			var block = [target]

			// pre block...
			var i = c-1
			while(i >= 0 
					// NOTE: we are avoiding mixing up a tag not set condition
					// 		with image i not in ribbon...
					&& (!ribbon[i] || !!marked[i] == state)){
				ribbon[i] 
					&& block.splice(0, 0, ribbon[i])
				i--
			}

			// post block...
			var i = c+1
			while(i < ribbon.length 
					// NOTE: we are avoiding mixing up a tag not set condition
					// 		with image i not in ribbon...
					&& (!ribbon[i] || !!marked[i] == state)){
				ribbon[i] 
					&& block.push(ribbon[i])
				i++
			}

			// do the marking...
			return this.toggleMark(block, state ? 'off' : 'on')
		}],
	toggleMarkRibbon: ['Mark/$Invert ribbon marks', 
		'toggleMark: "ribbon" ...' ],
	toggleMarkLoaded: ['Mark/Invert marks', 
		'toggleMark: "loaded" ...' ],

	markRibbon: ['Mark/Mark $ribbon', 
		'toggleMark: "ribbon" "on"' ],
	markLoaded: ['Mark/Mark $all', 
		'toggleMark: "loaded" "on"' ],

	markTagged: ['- Mark/Mark images by tags',
		function(tags, mode){
			var selector = mode == 'any' ? 'getTaggedByAny' : 'getTaggedByAll'

			var that = this
			this.data[selector](tags).forEach(function(gid){
				that.toggleMark(gid, 'on')
			})
		}],

	shiftMarkedUp: ['Mark/Shift marked u$p',
		{undo: undoShift('shiftMarkedDown'),
			browseMode: 'cropMarked'},
		shiftMarked('up')],
	shiftMarkedDown: ['Mark/Shift marked $down',
		{undo: undoShift('shiftMarkedUp'),
			browseMode: 'cropMarked'},
		shiftMarked('down')],

	// XXX undo...
	shiftMarkedAfter: ['Mark|Image/Shift marked $after',
		{browseMode: 'cropMarked'},
		function(target){
			this.shiftImageTo(this.marked, target || 'current', 'after') }],
	// XXX undo...
	shiftMarkedBefore: ['Mark|Image/Shift marked $b$efore',
		{browseMode: 'cropMarked'},
		function(target){
			this.shiftImageTo(this.marked, target || 'current', 'before') }],

	unmarkRibbon: ['Mark/Unmark ribbon',
		{browseMode: 'cropMarked'},
		'toggleMark: "ribbon" "off"'],
	unmarkLoaded: ['Mark/$Unmark all',
		{browseMode: 'cropMarked'},
		'toggleMark: "loaded" "off"'],
})

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

	tag: 'image-marks-edit',

	depends: [
		'tags-edit',
	],
	suggested: [
	],

	actions: ImageMarkEditActions,
})


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

var ImageMarkGroupActions = actions.Actions({
	// NOTE: this will only group loaded images...
	groupMarked: ['Group|Mark/-70:Group loaded marked images', 
		{journal: true,
			browseMode: 'cropMarked'}, 
		function(){ 
			this.group(this.data.getImages(this.marked)) }],
})

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

	tag: 'image-marks-groups',

	depends: [
		'image-marks-edit',
		'image-group-edit',
	],
	suggested: [
	],

	actions: ImageMarkGroupActions,
})


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

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

	tag: 'ui-image-marks',

	depends: [
		'ui',
		'image-marks',
	],

	handlers: [
		// XXX is a full reload a good thing here???
		[[
			'shiftMarkedUp',
			'shiftMarkedDown',
		], 
			function(ribbon){ this.reload(true) }],

		// XXX is this the right way to go???
		['updateImage', function(_, gid, img){
			// NOTE: we are not using .toggleMark(..) here as this 
			// 		does not need to depend on the 'edit' feature...
			this.ribbons
				&& this.ribbons
					.toggleImageMark(
						gid, 
						'marked', 
						this.data.hasTag(gid, 'marked') ? 'on' : 'off')
		}],
	],
})


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

var ImageBookmarkActions = actions.Actions({

	// a shorthand...
	//
	// NOTE: this will return a copy...
	//
	// XXX should we add a caching scheme here???
	// 		...it would require invalidation on tagging...
	// 		the problem is that on large sets this may take up quite a 
	// 		chunk of memory...
	get bookmarked(){
		if(this.data == null 
				|| this.data.tags == null
				|| !('bookmark' in this.data.tags)){
			return []
		}
		//return this.data.tags['bookmark'].slice()
		return this.data.getImages(this.data.tags['bookmark'])
	},

	prevBookmarked: ['Bookmark|Navigate/Previous bookmarked image',
		{browseMode: function(target){ 
			return this.data.getImage('current', 'before', this.bookmarked) == null && 'disabled' }},
		function(mode){ this.prevTagged('bookmark', mode) }],
	nextBookmarked: ['Bookmark|Navigate/Next bookmarked image',
		{browseMode: function(target){ 
			return this.data.getImage('current', 'after', this.bookmarked) == null && 'disabled' }},
		function(mode){ this.nextTagged('bookmark', mode) }],

	cropBookmarked: ['Bookmark|Crop/Crop $bookmarked images',
		{browseMode: function(target){ 
			return this.bookmarked.length == 0 && 'disabled' }},
		function(flatten){ this.cropTagged('bookmark', 'any', flatten) }],
})

// NOTE: this is usable without ribbons...
var ImageBookmarks = 
module.ImageBookmarks = core.ImageGridFeatures.Feature({
	title: '',
	doc: '',

	tag: 'image-bookmarks',

	depends: [
		'base',
	],
	suggested: [
		'image-bookmarks-edit',
		'ui-image-bookmarks',
	],

	actions: ImageBookmarkActions,

})


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

var ImageBookmarkEditActions = actions.Actions({
	toggleBookmark: ['Bookmark|Image/Image $bookmark',
		undoTag('toggleBookmark'),
		makeTagTogglerAction('bookmark')],
	// action can be:
	// 	'on'	- toggle all on
	// 	'off'	- toggle all off
	// 	'next'	- toggle each image to next state
	toggleBookmarkOnMarked: ['Bookmark|Mark/-70:Toggle bookmark on maked images',
		{browseMode: 'cropMarked'},
		function(action){ 
			return this.toggleBookmark(this.data.getTaggedByAny('marked'), action) 
		}],
})

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

	tag: 'image-bookmarks-edit',

	depends: [
		'tags-edit',
	],
	suggested: [
		'ui-image-bookmarks',
	],

	actions: ImageBookmarkEditActions,
})


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

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

	tag: 'ui-image-bookmarks',

	depends: [
		'ui',
		'image-bookmarks',
	],

	handlers: [
		// XXX is this the right way to go???
		['updateImage', function(_, gid, img){
			this.ribbons
				&& this.ribbons
					.toggleImageMark(
						gid, 
						'bookmark', 
						this.data.hasTag(gid, 'bookmark') ? 'on' : 'off')
		}],
	],
})



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

core.ImageGridFeatures.Feature('marks', [
	'image-marks',
	'image-bookmarks',
])



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