
// SCViewHolder makes it possible to add more capabilities by holding an SCView, not subclassing it
SCViewHolder {

	classvar <>consumeKeyDowns = false;// should the view by default consume keydowns

	var <view;

	view_ { arg v;
		// subclasses need to ALWAYS use this method to set the view
		view = v;
		view.onClose = { this.viewDidClose; };
	}
	viewDidClose { view = nil; }
	remove {
		if(view.notNil,{
			view.remove;// will cause view.onClose
			//view = nil;
		});
	}

	action { ^view.action }
	action_ { arg f; view.action_(f) }
	doAction { view.doAction }
	keyDownAction_ { arg f;
		view.keyDownAction_(f);
	}
	keyDownResponder { ^nil }
	enableKeyDowns { this.keyDownAction = this.keyDownResponder }

	asView { ^view }
	bounds { ^view.bounds }
	bounds_ { arg b; view.bounds_(b) }
	resize_ { arg r; view.resize_(r) }
	enabled { ^view.enabled }
	enabled_ { |b| view.enabled_(b) }
	refresh { view.refresh }
	background { ^view.background }
	background_ { arg b; view.background_(b) }
	focus { arg flag=true; view.focus(flag) }
	visible_ { arg boo; view.visible = boo }

	isClosed { ^(view.isNil or: {view.isClosed}) }
	// should move lower
	font_ { arg f;
		view.font = f;
	}

		// allow all messages the view understands to delegate to the view
	doesNotUnderstand { |selector ... args|
		var	result;
		view.respondsTo(selector).if({
			result = view.performList(selector, args);
			^(result === view).if({ this }, { result });
		}, {
			DoesNotUnderstandError(this, selector, args).throw;
		});
	}
}

// a decorator for use with composite views that are set to relativeOrigin = false
// bounds can then be expressed relative to the parent's
RelativeToParent {
	var <>bounds;
	*new { arg bounds;
		^super.newCopyArgs(bounds)
	}
	place { arg view;
		view.bounds = view.bounds.moveBy(bounds.left,bounds.top)
	}
}

SCViewAdapter : SCViewHolder {}

/**
  * a non-visible view that keeps the place in a flow layout
  * to mark where a row was started.  then when the view is reflowed due to resizing etc.
  * the layout can do a new row when this is encountered.
  * this is only added by FlowView.startRow
  */
StartRow : SCViewHolder {
	*new { arg parent,bounds;
		var new;
		new = super.new;
		if(parent.notNil,{ new.view_(parent) });
		^new
	}
	bounds { ^Rect(0,0,0,0) }
	bounds_ {}
	prClose {}
	remove {
		super.remove;
		if(view.notNil,{
			view.prRemoveChild(this)
		});
	}
}

/**
  * a composite view with a FlowLayout as its decorator
  */
FlowView : SCViewHolder {
	
	classvar <>relativeOrigin = true;

	var	<parent;
	var	autoRemoves,prevMaxHeight,prevMaxRight;

	*layout { arg f,bounds;
		var v;
		v = this.new(nil,bounds);
		f.value(v);
		^v
	}
	*viewClass { ^GUI.compositeView }
	*new { arg parent, bounds,margin,gap;
		^super.new.init(parent, bounds,margin,gap);
	}
	init { arg argParent, bounds,margin,gap;
		var w,parentView;
		parent = argParent ?? { GUI.window.new("",bounds).front };
		parentView = parent.asView;
		if(bounds.notNil,{
			bounds = bounds.asRect
		},{
			bounds = parentView.bounds;//.insetAll(2,2,2,2);
			if(parentView.tryPerform(\relativeOrigin) ? false) {
				bounds = bounds.setOriginRelativeTo(parent);
			};
		});
		// this adds the composite view to the parent composite view
		view = this.class.viewClass.new(parentView, bounds);
		view.tryPerform(\relativeOrigin_, FlowView.relativeOrigin);
		// now a tricky hack... the parent needs the FlowView as a child, not the composite view
		// so I will replace the last-added child with THIS
		if(parentView.children[parentView.children.size-1] === view,{
			parentView.children[parentView.children.size-1] = this;
		},{
			Error("FlowView unexpected result : parent's last child is not my view").throw;
		});

		// the parent might be a vertical, horizontal or flow
		// and might now have placed me, so get the bounds
		bounds = view.bounds;
		if(view.tryPerform(\relativeOrigin) ? false) {
			bounds = bounds.moveTo(0, 0);
		};
		//view.decorator = FlowLayout(bounds,2@2/*GUI.skin.margin*/,4@4);
		view.decorator = FlowLayout(bounds,margin ? 2@0/*GUI.skin.margin*/,gap ? 4@4);
		autoRemoves = IdentitySet.new;
	}
	startRow {
		view.add(StartRow.new); //won't really put a view in there yet
		this.decorator.nextLine
	}
	removeOnClose { arg updater;
		autoRemoves.add(updater);
	}
	hr { arg color,height=3,borderStyle=1; // html joke
		this.startRow;
		// should fill all and still return a minimal bounds
		GUI.staticText.new(this,Rect(0,0,this.decorator.innerBounds.width - (2 * 4), height,0))
				.string_("").background_(color ? Color(1,1,1,0.3) ).resize_(2)
	}

	innerBounds { ^this.decorator.innerBounds }
	bounds_ { arg b, reflow = true;
		if(b != view.bounds,{
			view.bounds = b;
			if(this.decorator.notNil,{
				if(view.tryPerform(\relativeOrigin) ? false) {
					this.decorator.bounds = b.moveTo(0, 0)
				} {
					this.decorator.bounds = b
				};
				reflow.if({ this.reflowAll; });
			})
		});
	}
	indentedRemaining { ^this.decorator.indentedRemaining }
	used { ^this.decorator.used }

	reflowAll {
		this.decorator.reset;
		view.children.do({ |widget|
			if(widget.isKindOf( StartRow ),{
				this.decorator.nextLine
			},{
				this.decorator.place(widget);
			})
		});
	}
	resizeToFit { arg reflow = false,tryParent = false;
		var used,new;

		if(reflow,{ this.reflowAll; });

		used = this.decorator.used;

		// should respect any settings !
		//used.width = used.width.clip(this.getProperty(\minWidth),this.getProperty(\maxWidth));
		//used.height = used.height.clip(this.getProperty(\minHeight),this.getProperty(\maxHeight));

		new = view.bounds.resizeTo(used.width,used.height);
		view.bounds = new;

		// ? but if relativeOrigin is true, then the decorator is already relative
		if(view.tryPerform(\relativeOrigin) ? false) {
			this.decorator.bounds = new.moveTo(0, 0);
		} {
			this.decorator.bounds = new; // if the left/top moved this buggers it
		};
		if(reflow,{ this.reflowAll; });
		if(tryParent,{
			this.parent.tryPerform(\resizeToFit,reflow,tryParent);
		});
		^new
	}
	// i've been trying since 2003 to do this
	reflowDeep {
		this.allChildren.reverseDo({ |view|
			if(view.isKindOf(FlowView),{
				view.bounds = view.bounds.resizeTo(1000,1000);
				view.reflowAll.resizeToFit;
			});
		});
		// sometimes this doesn't work, but if you do it twice it usually does
		
//		best way:
//		enlarge the view to full bounds within its parent.
// 		this can only be done if you are the last or only child
//		call deepReflow on shallow children in reverse order
//		resize self to fit
	}


	wouldExceedBottom { arg aBounds; ^this.decorator.wouldExceedBottom(aBounds) }
	anyChildExceeds {
		var r;
		r = view.bounds;
		^view.children.any({ arg c;
			r.containsRect( c.bounds ).not
		});
	}

	// if the view you are adding is unsure how much space it is going to take
	// then take everything
	allocateRemaining {
		prevMaxHeight = this.decorator.maxHeight;
		prevMaxRight = this.decorator.maxRight;
		^this.decorator.indentedRemaining
	}
	// and afterwards
	// state what you actually used.
	// see FlowView-flow
	didUseAllocated { arg vbounds;
		if(prevMaxHeight.isNil,{
			Error("didUseAllocated called without prior call to allocateRemaining").throw;
		});
		this.decorator.left = vbounds.right + this.decorator.margin.x;
		// maxRight is max right of all rows ever laid
		// but maxHeight is the max of this row only
		// but they both have to be rescinded
		this.decorator.maxRight = max( prevMaxRight, vbounds.right );
		this.decorator.maxHeight = max( prevMaxHeight,vbounds.height);
	}

	// to replace PageLayout
	layRight { arg x,y;
		^Rect(0,0,x,y)
	}
	remove {
		autoRemoves.do({ |updater| updater.remove });
		autoRemoves = nil;
		// am I still alive in the window?
		view.notClosed.if({
			// since this is in the parent's children array, view.remove is not enough by itself
			this.parent.prRemoveChild(this);
			view.remove;
		});
	}
	viewDidClose {
		autoRemoves.do({ arg u; u.remove });
		autoRemoves = nil;
		view.viewDidClose;
	}

	// mimic SCLayoutView interface
	children { ^view.children }
	decorator { ^view.decorator }
	decorator_ { |dec| view.decorator = dec }
	add { |child|
		view.add(child);
	}
	removeAll {
		view.removeAll;
		// SCContainerView removeAll is a bit odd
		// it relies on the children to remove themselves
		// but a StartRow doesn't ever know its parent
		// so it doesn't remove itself
		view.children.do({ |child|
			if(child.isKindOf(StartRow),{
				view.prRemoveChild(child)
			})
		});

		this.decorator.reset;
	}

	//private
	prRemoveChild { |child|
		view.prRemoveChild(child);
	}
	prClose { view.prClose }
}

// abstract
SCButtonAdapter : SCViewHolder {

	makeView { arg layout,x,y;
		var rect;
		if((layout.isNil or: { layout.isKindOf(MultiPageLayout) }),{ layout = layout.asFlowView; });
		this.view = GUI.button.new(layout,Rect(0,0,x,y ? GUI.skin.buttonHeight));
		if(consumeKeyDowns,{ this.view.keyDownAction_({nil}) });
	}
	flowMakeView { arg layout,x,y;
		this.view = GUI.button.new(layout.asFlowView,Rect(0,0,x,y ? GUI.skin.buttonHeight));
		if(consumeKeyDowns,{ this.view.keyDownAction_({nil}); });
	}

	makeViewWithStringSize { arg layout,optimalWidth,minWidth,minHeight;
		this.makeView( layout,(optimalWidth + 10).max(minWidth?20),(minHeight ) )
	}
	initOneState { arg name,textcolor,backcolor;
		view.states_([[name,textcolor ? Color.black, backcolor ? Color.white]])
	}
	// sets all states
	label_ { arg string;
		view.states = view.states.collect({ arg st;
			st.put(0,string.asString);
			st
		});
	}
	// assumes 1 state
	background_ { arg color;
		var s;
		s = view.states;
		s.at(0).put(2,color);
		view.states = s;
	}
	labelColor_ { arg color;
		var s;
		s = view.states;
		s.at(0).put(1,color);
		view.states = s;
	}

	// bw compat
	*defaultHeight { ^GUI.skin.buttonHeight }

}

// abreviation for a one state button
ActionButton : SCButtonAdapter {

	var <action;

	*new { arg layout,title,function,minWidth=20,minHeight,color,backcolor,font;
		^super.new.init(layout,title,function,minWidth,minHeight,color,backcolor,font)
	}
	init { arg layout,title,function,minWidth=20,minHeight,color,backcolor,font;
		var environment,optimalWidth;
		title = title.asString;
		if(title.size > 40,{ title = title.copyRange(0,40) });
		if(font.isNil,{ font = GUI.font.new(*GUI.skin.fontSpecs) });
		optimalWidth = title.bounds(font).width;
		this.makeViewWithStringSize(layout,optimalWidth,minWidth,minHeight);
		view.states_([[title,color ?? {Color.black},
			backcolor ?? {Color.new255(205, 201, 201)}]]);
		view.font_(font);
		view.action_(function);
		if(consumeKeyDowns,{ this.keyDownAction = {nil}; });
	}
}


ToggleButton : SCButtonAdapter {

	var <state,<>onFunction,<>offFunction;

	*new { arg layout,title,onFunction,offFunction,init=false,minWidth=20,minHeight;
			^super.new.init(layout,init, title,minWidth,minHeight)
				.onFunction_(onFunction).offFunction_(offFunction)
	}
	value { ^state }
	toggle { arg way,doAction = true;
		if(doAction,{
			this.prSetState(way ? state.not)
		},{
			state = way ? state.not;
		});
		view.setProperty(\value,state.binaryValue);
	}
	// private
	init { arg layout,init,title,minWidth,minHeight;
		var font;
		font = GUI.font.new(*GUI.skin.fontSpecs);
		this.makeViewWithStringSize(layout,title.bounds(font).width,minWidth,minHeight);
		view.states = [
			[title,GUI.skin.fontColor,GUI.skin.offColor],
			[title,GUI.skin.fontColor,GUI.skin.onColor]
		];
		state=init;
		view.setProperty(\value,state.binaryValue);
		view.action_({this.prSetState(state.not)});
		view.font = font;
	}
	prSetState { arg newstate;
		state = newstate;
		if(state,{
			onFunction.value(this)
		},{
			(offFunction ? onFunction).value(this)
		});
	}
}
