Blob Blame Raw
using System;
using System.Collections.Generic;

namespace Assistance {
	public class InputManager: Track.Owner {
		public static readonly Drawing.Pen penPreview = new Drawing.Pen("Dark Green", 1.0, 0.25);

		public class TrackHandler: Track.Handler {
			public readonly List<int> keys = new List<int>();
			public TrackHandler(InputManager owner, Track original, int keysCount = 0):
				base(owner, track)
				{ for(int i = 0; i < keysCount; ++i) keys.Add(0); }
		}
		
		public class KeyPoint {
			public class Holder: IDisposable {
				public readonly KeyPoint keyPoint;
				private bool holded = false;
				
				public Holder(KeyPoint keyPoint)
					{ this.keyPoint = keyPoint; reuse(); }

				public bool available
					{ get { return keyPoint.available; } }
				public bool isHolded
					{ get { return holded; } }
				public bool reuse() {
					if (!holded) ++keyPoint.refCount;
					holded = true;
					return keyPoint.available;
				}
				public void release() {
					if (holded) --keyPoint.refCount;
					holded = false;
				}
					
				public void Dispose()
					{ Dispose(true); GC.SuppressFinalize(this); }
				protected virtual void Dispose(bool disposing)
					{  release(); }
				~Holder()
					{ Dispose(false); }
			}
			
			private int refCount = 0;
			public bool available = true;
			
			public Holder hold()
				{ return new Holder(this); }
			public bool isFree
				{ get { return refCount <= 0; } }
		}
		
		public class Modifier: Track.Owner {
			public virtual void activate() { }
			public virtual void modify(Track tracks, KeyPoint keyPoint, List<Track> outTracks)
				{ }
			public virtual void modify(List<Track> tracks, KeyPoint keyPoint, List<Track> outTracks)
				{ foreach(Track track in tracks) modify(track, keyPoint, outTracks); }
			public virtual void deactivate() { }
		}

		
		public readonly Workarea workarea;
		
		private readonly InputState state = new InputState();

		private Tool tool;
		private readonly List<Modifier> modifiers = new List<Modifier>();

		private readonly List<Track> tracks = new List<Track>();
		private readonly List<KeyPoint> keyPoints = new List<KeyPoint>();
		private int keyPointsSent;

		private List<Track> subTracks = null;
		private readonly List<Track>[] subTracksBuf = new List<Track>[] { new List<Track>(), new List<Track>() };


		InputManager(Workarea workarea)
			{ this.workarea = workarea; }


		private void paintRollbackTo(int keyIndex, List<Track> subTracks) {
			if (keyIndex >= keyPoints.Count)
				return;
			
			int level = keyIndex + 1;
			if (level <= keyPointsSent) {
				if (level <= keyPointsSent)
					tool.paintPop(keyPointsSent - level);
				tool.paintCancel();
				keyPointsSent = level;
			}
			
			foreach(Track track in subTracks) {
				TrackHandler handler = (TrackHandler)track.handler;
				handler.keys.RemoveRange(level, keyPoints.Count - level);
				int index = handler.keys[keyIndex];
				track.wayPointsRemoved = 0;
				track.wayPointsAdded = track.points.Count - index - 1;
			}
			for(int i = level; i < keyPoints.Count; ++i) keyPoints[i].available = false;
			keyPoints.RemoveRange(level, keyPoints.Count - level);
		}
		
		private void paintApply(int count, Dictionary<long, Track> subTracks) {
			if (count <= 0)
				return;
			
			int level = keyPoints.Count - count;
			if (level >= keyPointsSent || tool.paintApply(keyPointsSent - level)) {
				// apply
				foreach(Track track in subTracks)
					((TrackHandler)track.handler).keys.RemoveRange(level, keyPoints.Count - level);
			} else {
				// rollback
				tool.paintPop(keyPointsSent - level);
				foreach(Track track in subTracks) {
					TrackHandler handler = (TrackHandler)track.handler;
					int index = handler.keys[level];
					handler.keys.RemoveRange(level, keyPoints.Count - level);
					track.wayPointsRemoved = 0;
					track.wayPointsAdded = track.points.Count - index - 1;
				}
			}

			for(int i = level; i < keyPoints.Count; ++i) keyPoints[i].available = false;
			keyPoints.RemoveRange(level, keyPoints.Count - level);
			if (level < keyPointsSent)
				keyPointsSent = level;
		}
		
		private void paintTracks() {
			bool allFinished = true;
			foreach(Track track in tracks)
				if (!track.isFinished)
					{ allFinished = false; break; }

			while(true) {
				// run modifiers
				KeyPoint newKeyPoint = new KeyPoint();
				subTracks = tracks;
				int i = 0;
				foreach(Modifier modifier in modifiers) {
					List<Track> outTracks = subTracksBuf[i];
					modifier.modify(subTracks, keyEvent, outTracks);
					subTracks = outTracks;
					i = 1 - i;
				}
				
				// create handlers	
				foreach(Track track in subTracks)
					if (track.handler == null)
						track.handler = new TrackHandler(this, track, keyPoints.Count);
	
				if (keyPoints.Count > 0) {
					// rollback
					int rollbackIndex = keyPoints.Count;
					foreach(Track track in subTracks) {
						if (track.wayPointsRemoved > 0) {
							int count = track.points.Count - track.wayPointsAdded + track.wayPointsRemoved;
							TrackHandler handler = (TrackHandler)track.handler;
							while(rollbackIndex > 0 && (rollbackIndex >= keyPoints.Count || handler.keys[rollbackIndex] > count))
								--rollbackIndex;
						}
					}
					paintRollbackTo(rollbackIndex, subTracks);
	
					// apply
					int applyCount = 0;
					while(applyCount < keyPoints.Count && keyPoints[keyPoints.Count - applyCount - 1].isFree)
						++applyCount;
					paintApply(applyCount, subTracks);
				}
				
				// send to tool
				if (keyPointsSent == keyPoints.Count)
					tool.paintTracks(subTracks);
				
				// is paint finished?
				if (newKeyPoint.isFree) {
					if (allFinished) {
						paintApply(keyPoints.Count, subTracks);
						tracks.Clear();
						this.subTracks = null;
					} else {
						this.subTracks = subTracks;
					}
					break;
				}
				
				// create key point
				if (tool.paintPush()) ++keyPointsSent;
				keyPoints.Add(newKeyPoint);
				foreach(Track track in subTracks)
					((TrackHandler)track.handler).keys.Add(track.points.Count);
			}
		}
		
		private int trackCompare(Track track, Gdk.Device device, long touchId) {
			if (track.device < device) return -1;
			if (track.device > device) return 1;
			if (track.touchId < touchId) return -1;
			if (track.touchId > touchId) return 1;
			return 0;
		}
		
		private Track createTrack(int index, Gdk.Device device, long touchId, long ticks) {
			Track track = new Track(
				device,
				touchId,
				state.keyHistoryHolder(ticks),
				state.buttonHistoryHolder(device, ticks) );
			tracks.Insert(index, track);
			return track;
		}
		
		private Track getTrack(Gdk.Device device, long touchId, long ticks) {
			int cmp;
			
			int a = 0;
			cmp = trackCompare(tracks[a], device, touchId);
			if (cmp == 0) return tracks[a];
			if (cmp < 0) return createTrack(a, device, touchId, ticks);
			
			int b = tracks.Count - 1;
			cmp = trackCompare(tracks[b], device, touchId);
			if (cmp == 0) return tracks[b];
			if (cmp > 0) return createTrack(b+1, device, touchId, ticks);
			
			// binary search: tracks[a] < tracks[c] < tracks[b]
			while(true) {
				int c = (a + b)/2;
				if (a == c) break;
				cmp = trackCompare(tracks[c], device, touchId);
				if (cmp < 0) b = c; else
					if (cmp > 0) a = c; else
						return tracks[c];
			}
			return createTrack(b, device, touchId, ticks);
		}
		
		private void addTrackPoint(Track track, Track.Point point, double time, bool final) {
			// fix time
			if (track.points.Count > 0)
				time = Math.Max(time, track.getLast().time + Timer.step);
			
			// calc length
			double length = track.points.Count > 0
			              ? (point.position - track.getLast().point.position).lenSqr() + track.getLast().length
			              : 0.0;
			
			// add
			track.points.Add( new Track.WayPoint(
				point,
				new Track.Point(),
				(double)track.points.Count,
				time,
				length,
				track.points.Count,
				final ));
			++track.wayPointsAdded;
		}
		
		private void touchTracks(bool finish = false) {
			foreach(Track track in tracks)
				if (!track.isFinished() && track.points.Count > 0)
					addTrackPoint(track, track.getLast().point, track.getLast().time, finish);
			paintTracks();
		}
		
		private void finishTracks()
			{ touchTracks(true); }
		
		
		public void trackEvent(long touchId, Gdk.Device device, Track.Point point, bool final, long ticks) {
			if (tool != null) {
				Track track = getTrack(touchId, device, ticks);
				if (!track.isFinished) {
					double time = (double)(ticks - track.keyHistory.ticks)*Timer.step - track.keyHistory.timeOffset;
					addTrackPoint(track, point, time, final);
					paintTracks();
				}
			}
		}

		public void keyEvent(bool press, Gdk.Key key, long ticks) {
			state.keyEvent(press, key, ticks);
			if (tool != null) {
				tool.keyEvent(press, key, state);
				touchTracks();
			}
		}
		
		public void buttonEvent(bool press, Gdk.Device device, uint button, long ticks) {
			state.buttonEvent(press, device, button, ticks);
			if (tool != null) {
				tool.buttonEvent(press, device, button, state);
				touchTracks();
			}
		}
	
		
		public Tool getTool()
			{ return tool; }
		
		public void setTool(Tool tool) {
			if (this.tool == tool) {
				if (this.tool != null) {
					finishTracks();
					this.tool.deactivate();
				}
				
				this.tool = tool;
				
				if (this.tool != null)
					this.tool.activate();
			}
		}
		
		public int getModifiersCount()
			{ return modifiers.Count; }
		public Modifier getModifier(int index)
			{ return modifiers[index]; }

		public void insertModifier(int index, Modifier modifier) {
			if (this.tool != null) finishTracks();
			modifiers.Insert(index, modifier);
			modifier.activate();
		}
		public void addModifier(Modifier modifier)
			{ insertModifier(getModifiersCount(), modifier); }
		
		public void removeModifier(int index) {
			if (this.tool != null) finishTracks();
			modifiers[i].deactivate();
			modifiers.RemoveAt(index);
		}
		public void removeModifier(Modifier modifier) {
			for(int i = 0; i < getModifiersCount(); ++i)
				if (getModifier(i) == modifier)
					{ removeModifier(i); break; }
		}
		public void clearModifiers() {
			while(getModifiersCount() > 0)
				removeModifier(0);
		}
	
		
		public void draw(Cairo.Context context) {
			// paint not sent sub-tracks
			if (subTracks != null && keyPointsSent < keyPoints.Count) {
				context.Save();
				penPreview.apply(context);
				foreach(Track track in subTracks) {
					TrackHandler handler = (TrackHandler)track.handler;
					int start = handler.keys[keyPointsSent];
					if (start < track.points.Count) {
						context.MoveTo(track.points[start].point.position.x, track.points[start].point.position.y);
						for(int i = start + 1; i < track.points.Count; ++i)
							context.MoveTo(track.points[i].point.position.x, track.points[i].point.position.y);
					}
				}
				context.Stroke();
				context.Restore();
			}
		}
	}
}