context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  
  using Android.App;
  using Android.Content;
  using Android.Graphics;
  using Android.OS;
  using Android.Runtime;
  using Android.Views;
  using Android.Widget;
  
  using Java.Lang;
  
  namespace android {
  	class RangeSeekBar<T> : ImageView where T : Number {
  		private Paint paint = new Paint(PaintFlags.AntiAlias);
  		private Bitmap thumbImageLeft;
          private Bitmap thumbImageRight;
          private Bitmap thumbPressedImageLeft;
          private Bitmap thumbPressedImageRight;
          private float thumbWidth;
  		private float thumbHalfWidth;
  		private float thumbHalfHeight;
  		private float lineHeight;
  		private float padding;
  		private T absoluteMinValue, absoluteMaxValue;
          private NumberType numberType;
          private double absoluteMinValuePrim, absoluteMaxValuePrim;
  		private double normalizedMinValue = 0d;
  		private double normalizedMaxValue = 1d;
  		private Thumb pressedThumb = Thumb.NULL;
  		private bool notifyWhileDragging = false;
  		private IOnRangeSeekBarChangeListener<T> listener;
  
  		/**
           * Default color of a {@link RangeSeekBar}, #FF33B5E5. This is also known as "Ice Cream Sandwich" blue.
           */
  		public static int DEFAULT_COLOR = Color.Argb(0xFF, 0x33, 0xB5, 0xE5);
  
  		/**
           * An invalid pointer id.
           */
  		public static int INVALID_POINTER_ID = 255;
  
  		// Localized constants from MotionEvent for compatibility
  		// with API < 8 "Froyo".
  		public static int ACTION_POINTER_UP = 0x6, ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8;
  
  		private float mDownMotionX;
  		private int mActivePointerId = INVALID_POINTER_ID;
  
  		/**
           * On touch, this offset plus the scaled value from the position of the touch will form the progress value. Usually 0.
           */
  		float mTouchProgressOffset;
  
  		private int mScaledTouchSlop;
  		private bool mIsDragging;
  
  		public RangeSeekBar(T absoluteMinValue, T absoluteMaxValue, Context context) : base(context) {
  			this.absoluteMinValue = absoluteMinValue;
  			this.absoluteMaxValue = absoluteMaxValue;
  			this.absoluteMinValuePrim = absoluteMinValue.DoubleValue();
  			this.absoluteMaxValuePrim = absoluteMaxValue.DoubleValue();
  			this.numberType = this.fromNumber(absoluteMinValue);
  
  			// make RangeSeekBar focusable. This solves focus handling issues in case EditText widgets are being used along with the RangeSeekBar within ScollViews.
  			this.Focusable = true;
  			this.FocusableInTouchMode = true;
  			this.init();
  			this.padding = this.thumbHalfWidth;
  		}
  		public RangeSeekBar(T absoluteMinValue, T absoluteMaxValue, float padding, Context context) : base(context) {
  			this.absoluteMinValue = absoluteMinValue;
  			this.absoluteMaxValue = absoluteMaxValue;
  			this.absoluteMinValuePrim = absoluteMinValue.DoubleValue();
  			this.absoluteMaxValuePrim = absoluteMaxValue.DoubleValue();
  			this.numberType = this.fromNumber(absoluteMinValue);
  
  			// make RangeSeekBar focusable. This solves focus handling issues in case EditText widgets are being used along with the RangeSeekBar within ScollViews.
  			this.Focusable = true;
  			this.FocusableInTouchMode = true;
  			this.padding = padding;
  			this.init();
  		}
  		private void init() {
  			thumbImageLeft = BitmapFactory.DecodeResource(Resources, Resource.Drawable.select_left);
  			thumbImageRight = BitmapFactory.DecodeResource(Resources, Resource.Drawable.select_right);
  			thumbPressedImageLeft = BitmapFactory.DecodeResource(Resources, Resource.Drawable.select_left);
  			thumbPressedImageRight = BitmapFactory.DecodeResource(Resources, Resource.Drawable.select_right);
  			thumbWidth = thumbImageLeft.Width;
  			thumbHalfWidth = 0.5f * thumbWidth;
  			thumbHalfHeight = 0.5f * thumbImageLeft.Height;
  			lineHeight = 0.1337f * thumbHalfHeight;
  			mScaledTouchSlop = ViewConfiguration.Get(Context).ScaledTouchSlop;
  		}
  		public bool isNotifyWhileDragging() {
  			return notifyWhileDragging;
  		}
  
  		/**
           * Should the widget notify the listener callback while the user is still dragging a thumb? Default is false.
           * 
           * @param flag
           */
  		public void setNotifyWhileDragging(bool flag) {
  			this.notifyWhileDragging = flag;
  		}
  
  		/**
           * Returns the absolute minimum value of the range that has been set at construction time.
           * 
           * @return The absolute minimum value of the range.
           */
  		public T getAbsoluteMinValue() {
  			return absoluteMinValue;
  		}
  
  		/**
           * Returns the absolute maximum value of the range that has been set at construction time.
           * 
           * @return The absolute maximum value of the range.
           */
  		public T getAbsoluteMaxValue() {
  			return absoluteMaxValue;
  		}
  
  		/**
           * Returns the currently selected min value.
           * 
           * @return The currently selected min value.
           */
  		public T getSelectedMinValue() {
  			return normalizedToValue(normalizedMinValue);
  		}
  		public void setSelectedMinValue(T value) {
  			// in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
  			if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
  				setNormalizedMinValue(0d);
  			} else {
  				setNormalizedMinValue(valueToNormalized(value));
  			}
  		}
  
  		/**
           * Returns the currently selected max value.
           * 
           * @return The currently selected max value.
           */
  		public T getSelectedMaxValue() {
  			return normalizedToValue(normalizedMaxValue);
  		}
  
  		/**
           * Sets the currently selected maximum value. The widget will be invalidated and redrawn.
           * 
           * @param value
           *            The Number value to set the maximum value to. Will be clamped to given absolute minimum/maximum range.
           */
  		public void setSelectedMaxValue(T value) {
  			// in case absoluteMinValue == absoluteMaxValue, avoid division by zero when normalizing.
  			if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
  				setNormalizedMaxValue(1d);
  			} else {
  				setNormalizedMaxValue(valueToNormalized(value));
  			}
  		}
  
  		/**
           * Registers given listener callback to notify about changed selected values.
           * 
           * @param listener
           *            The listener to notify about changed selected values.
           */
  		public void setOnRangeSeekBarChangeListener(IOnRangeSeekBarChangeListener<T> listener) {
  			this.listener = listener;
  		}
  		/**
           * Handles thumb selection and movement. Notifies listener callback on certain events.
           */
  		public override bool OnTouchEvent(MotionEvent ev) {
  
  			if (!this.Enabled)
  				return false;
  
  			int pointerIndex;
  
  			var action = ev.Action;
  			switch (action & MotionEventActions.Mask) {
  
  			case MotionEventActions.Down:
  				// Remember where the motion event started
  				mActivePointerId = ev.GetPointerId(ev.PointerCount - 1);
  				pointerIndex = ev.FindPointerIndex(mActivePointerId);
  				this.mDownMotionX = ev.GetX(pointerIndex);
  
  				this.pressedThumb = evalPressedThumb(mDownMotionX);
  
  				// Only handle thumb presses.
  				if (pressedThumb == Thumb.NULL)
  					return base.OnTouchEvent(ev);
  
  				this.Pressed = true;
  				this.Invalidate();
  				this.onStartTrackingTouch();
  				this.trackTouchEvent(ev);
  				this.attemptClaimDrag();
  
  				break;
  			case MotionEventActions.Move:
  				if (pressedThumb != null) {
  
  					if (mIsDragging) {
  						this.trackTouchEvent(ev);
  					} else {
  						// Scroll to follow the motion event
  						pointerIndex = ev.FindPointerIndex(mActivePointerId);
  						float x = ev.GetX(pointerIndex);
  
  						if (Java.Lang.Math.Abs(x - this.mDownMotionX) > this.mScaledTouchSlop) {
  							this.Pressed = true;
  							this.Invalidate();
  							this.onStartTrackingTouch();
  							this.trackTouchEvent(ev);
  							this.attemptClaimDrag();
  						}
  					}
  
  					if (this.notifyWhileDragging && this.listener != null) {
  						this.listener.onRangeSeekBarValuesChanged(this, this.getSelectedMinValue(), this.getSelectedMaxValue());
  					}
  				}
  				break;
  			case MotionEventActions.Up:
  				if (mIsDragging) {
  
  					this.trackTouchEvent(ev);
  
  					this.onStopTrackingTouch();
  
  					this.Pressed = false;
  				} else {
  					// Touch up when we never crossed the touch slop threshold
  					// should be interpreted as a tap-seek to that location.
  					this.onStartTrackingTouch();
  
  					this.trackTouchEvent(ev);
  
  					this.onStopTrackingTouch();
  				}
  
  				this.pressedThumb = Thumb.NULL;
  
  				this.Invalidate();
  				if (this.listener != null) {
  					this.listener.onRangeSeekBarValuesChanged(this, this.getSelectedMinValue(), this.getSelectedMaxValue());
  				}
  				break;
  			case MotionEventActions.PointerDown: {
  					int index = ev.PointerCount - 1;
  					// final int index = ev.getActionIndex();
  					this.mDownMotionX = ev.GetX(index);
  					this.mActivePointerId = ev.GetPointerId(index);
  					this.Invalidate();
  					break;
  				}
  			case MotionEventActions.PointerUp:
  
  				this.onSecondaryPointerUp(ev);
  
  				this.Invalidate();
  				break;
  			case MotionEventActions.Cancel:
  				if (mIsDragging) {
  
  					this.onStopTrackingTouch();
  
  					this.Pressed = false;
  				}
  
  				this.Invalidate(); // see above explanation
  				break;
  			}
  			return true;
  		}
  		private void onSecondaryPointerUp(MotionEvent ev) {
  			int pointerIndex = ((int)ev.Action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
  
  			int pointerId = ev.GetPointerId(pointerIndex);
  			if (pointerId == mActivePointerId) {
  				// This was our active pointer going up. Choose
  				// a new active pointer and adjust accordingly.
  				// TODO: Make this decision more intelligent.
  				int newPointerIndex = pointerIndex == 0 ? 1 : 0;
  				mDownMotionX = ev.GetX(newPointerIndex);
  				mActivePointerId = ev.GetPointerId(newPointerIndex);
  			}
  		}
  
  		private void trackTouchEvent(MotionEvent ev) {
  			int pointerIndex = ev.FindPointerIndex(mActivePointerId);
  			float x = ev.GetX(pointerIndex);
  
  			if (Thumb.MIN.Equals(pressedThumb)) {
  				this.setNormalizedMinValue(screenToNormalized(x));
  			} else if (Thumb.MAX.Equals(pressedThumb)) {
  				this.setNormalizedMaxValue(screenToNormalized(x));
  			}
  		}
  
  		/**
           * Tries to claim the user's drag motion, and requests disallowing any ancestors from stealing events in the drag.
           */
  		private void attemptClaimDrag() {
  			if (this.Parent != null) {
  				this.Parent.RequestDisallowInterceptTouchEvent(true);
  			}
  		}
  
  		/**
           * This is called when the user has started touching this widget.
           */
  		void onStartTrackingTouch() {
  			mIsDragging = true;
  		}
  
  		/**
           * This is called when the user either releases his touch or the touch is canceled.
           */
  		void onStopTrackingTouch() {
  			mIsDragging = false;
  		}
  
  		/**
           * Ensures correct size of the widget.
           */
  		protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  			int width = 200;
  			if (MeasureSpecMode.Unspecified != MeasureSpec.GetMode(widthMeasureSpec)) {
  
gt: 				width = MeasureSpec.GetSize(widthMeasureSpec);