using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Windows.Forms;
using System.Drawing;

using System.Xml;
using System.Xml.Serialization;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using D3D = Microsoft.DirectX.Direct3D;
using Vector3 = Microsoft.DirectX.Vector3;
using Vector2 = Microsoft.DirectX.Vector2;

// The components of the data model used for MHS.
namespace MhsModelCSharp
{
	/// <summary>
	/// The steps required to sample a CYG
	/// </summary>
	public enum CygStep 
	{
		/// <summary>
		/// The step where the base point's 3d vector in centimeters is sampled
		/// </summary>
		BASE_POINT		=0,

		/// <summary>
		/// The step where the tip point's 3d vector in centimeters is sampled
		/// </summary>
		TIP_POINT		=1,

		/// <summary>
		/// The step where the base diameter in mm's is sampled
		/// </summary>
		BASE_DIAMETER	=2,

		/// <summary>
		/// 
		/// </summary>
		NONE			=4
	};

	/// <summary>
	/// A struct containing constants that define the model
	/// </summary>
	public struct ModelParameters
	{
		/// <summary>
		/// the numbers of cyg's to sample in a plant patch
		/// </summary>
		public const int NUM_CYGS_PER_PLANTPATCH = 9;

		/// <summary>
		/// The number of steps it takes to measure a CYG. 3: base,tip, diameter.
		/// </summary>
		public const int NUM_STEPS_PER_CYG_SAMPLE = 3;

		/// <summary>
		/// The number of instances of a species that a biologist will sample
		/// at a waypoint
		/// </summary>
		public const int NUM_SAMPLES_PER_SPECIES = 3;

		/// <summary>
		/// The number of depth levels the plant patch is divided into
		/// </summary>
		public const int NUM_DEPTH_LEVELS_PER_PLANTPATCH = 3;

		/// <summary>
		/// Number of random points to find the closest plant patch to
		/// </summary>
		public const int NUM_SAMPLE_POINTS_PER_WAYPOINT = 3;
			
		/// <summary>
		/// The height of each depth level
		/// </summary>
		public const float DEPTH_LEVEL_HEIGHT_CENTIMETERS = 100f;

		/// <summary>
		/// The total hieght of the plant patch sample space. DEPTH_LEVEL_HEIGHT_CENTIMETERS * NUM_DEPTH_LEVELS_PER_PLANTPATCH
		/// </summary>
		public const float PLANTPATCH_HEIGHT_CENTIMETERS = DEPTH_LEVEL_HEIGHT_CENTIMETERS * (float)NUM_DEPTH_LEVELS_PER_PLANTPATCH;

		/// <summary>
		/// The radius in centimeters of the plant patch sample space
		/// TODO jerk: this will have to change to meet the adjustable 
		/// range idea
		/// </summary>
		public const float PLANTPATCH_RADIUS_CENTIMETERS= 30f;

		/// <summary>
		/// The number of centimeters of diameter. 2 * PLANTPATCH_RADIUS_CENTIMETERS. Just handy to precompute.
		/// </summary>
		public const float PLANTPATCH_DIAMETER_CENTIMETERS= 2f * PLANTPATCH_RADIUS_CENTIMETERS;

		/// <summary>
		/// The radius in meters from a gps point that defines a waypoint's sampling area.
		/// </summary>
		public const float WAYPOINT_RADIUS_METERS = 15f;

		/// <summary>
		///	The	diameter in	meters from	a gps point	that defines a waypoint's sampling area. 2*WAYPOINT_RADIUS_METERS. Handy to	precompute.
		/// </summary>
		public const float WAYPOINT_DIAMETER_METERS = 2f * WAYPOINT_RADIUS_METERS;

	}

	/// <summary>
	/// A base class that allows articulate enumeration of the available
	/// properties (accessors). It allows us to hide some properties from the 
	/// property grid and define how the properties ought to be custom sorted.
	/// It's all a boiler plate implementation except for get properties
	/// which uses the VisibleProperties string array to determine
	/// which properties to show the user/PropertyGrid and how to sort them.
	/// All child classes must implement VisibleProperties accessor and probably
	/// a visibleProperties private string array member.
	/// All the core model classes: waypoint, plantpatch, cyg inherit from this.
	/// </summary>
	public abstract class FilterableProperties :
		ICustomTypeDescriptor
	{

		/// <summary>
		/// Properties that reflection will show the user/PropertyGrid.
		/// Must be implemented in subclasses. Used by GetProperties()
		/// as a filter.
		/// </summary>
		protected abstract string[] VisibleProperties
		{
			get;
		}

		#region ICustomTypeDescriptor Boiler plate implementation of ICustomDescriptor

		object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
		{
			return this;
		}

		AttributeCollection ICustomTypeDescriptor.GetAttributes()
		{
			return TypeDescriptor.GetAttributes(this, true);
		}

		string ICustomTypeDescriptor.GetClassName()
		{
			return TypeDescriptor.GetClassName(this, true);
		}

		string ICustomTypeDescriptor.GetComponentName()
		{
			return TypeDescriptor.GetComponentName(this, true);
		}

		TypeConverter ICustomTypeDescriptor.GetConverter()
		{
			return TypeDescriptor.GetConverter(this,true);
		}

		EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
		{
			return TypeDescriptor.GetDefaultEvent(this,true);
		}

		EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
		{
			return TypeDescriptor.GetEvents(this,true);
		}

		EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
		{
			return TypeDescriptor.GetEvents(this,attributes,true);
		}


		PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
		{
			return TypeDescriptor.GetDefaultProperty(this,true);
		}

		object ICustomTypeDescriptor.GetEditor(Type t)
		{
			return TypeDescriptor.GetEditor(this,t,true);
		}

		PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
		{
			return ((ICustomTypeDescriptor)this).GetProperties(null);
		}
		#endregion

		/// <summary>
		/// Uses VisibleProperties[] to filter the properties you want to show
		/// to the user.
		/// </summary>
		/// <param name="attributes"></param>
		/// <returns></returns>
		PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
		{
			PropertyDescriptorCollection props = new PropertyDescriptorCollection(null);

			if( VisibleProperties != null && VisibleProperties.Length > 0 )
			{
				PropertyDescriptorCollection allProps = TypeDescriptor.GetProperties(this, attributes, true);

				// fetch out the properties we want
				foreach(PropertyDescriptor prop in allProps) 
				{
					if( Array.IndexOf(VisibleProperties, prop.Name ) > -1 )
					{
						props.Add(prop);
					}			
				}
				
			}
			else
			{
				props = TypeDescriptor.GetProperties(this, attributes, true);
			}

			return props.Sort( VisibleProperties );
		}

	}


	/// <summary>
	/// CurrentYearGrowth (CYG) is a model class to describe new growth on a tree
	/// (sticks, twigs, branches). The representation is the 3d point of the twig's base
	/// , the 3d point of the tip, and the diameter of the twig's base. All other 
	/// values are derived from those three. It is mainly a data container.
	/// CYG's are the most atomic model unit of the MHS model.
	/// </summary>
	public class CurrentYearGrowth :
		FilterableProperties
	{
		/// <summary>
		/// A shared random number generator
		/// </summary>
		private static Random rand = new Random();

		/// <summary>
		/// Properties filter
		/// </summary>
		private string[] visibleProperties = new string[7] {"Name","ReferencePoint","BasePoint","TipPoint","BaseDiameter","Length","DistanceFromReferencePoint"};

		/// <summary>
		/// Properties filter accessor
		/// </summary>
		protected override string[] VisibleProperties
		{
			get { return this.visibleProperties; }
		}


		// TODO JERK
		// this "haveAccessed" variables don't belong in the model
		// they should be in the controller. Use the steps enum
		// to build a tracker class. Then you can wizard it too.


		/// <summary>
		/// Accessor to determine of the tip has been set yet
		/// Tracking variable to indicate if the tip 3d point has been set
		/// </summary>
		public bool TipPointIsSet
		{
			get { return isTipPositionSet; }
		}
		private bool isTipPositionSet = false;

		/// <summary>
		/// Accessor to determine of the tip has been set yet
		/// Tracking variable to indicate if the base 3d point has been set
		/// </summary>
		public bool BasePointIsSet
		{
			get { return isBasePositionSet; }
		}
		private bool isBasePositionSet = false;


		/// <summary>
		/// Accessor to determine of the tip has been set yet
		/// Tracking variable to indicate if the base diameter has been set
		/// </summary>
		public bool BaseDiameterIsSet
		{
			get { return isBaseDiameterSet; }
		}
		private bool isBaseDiameterSet = false;


		/// <summary>
		/// Accessor for the name
		/// </summary>
		[Editor(typeof(StringInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("CYG's friendly identifier, nickname")]
		public string Name 
		{
			get { return name; } 
			set { name = value; }
		}
		private string name				= "Cyg";

		
		/// <summary>
		/// Accessor for the the reference point. Setting the value
		/// updates the CYG's distance from the reference point.
		/// </summary>
		[Editor(typeof(Vector3InkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[TypeConverter(typeof(D3DVector3TypeConverter ))]
		[Description("3D point randomly chosen as a starting reference")]
		public Vector3 ReferencePoint
		{
			get	{ return ref3dPoint; } 
			set 
			{ 
				ref3dPoint.X = value.X;
				ref3dPoint.Y = value.Y;
				ref3dPoint.Z = value.Z;
				distFromRef = Vector3.Length( base3dPoint - ref3dPoint);
			} 
		}
		private Vector3 ref3dPoint		= new Vector3(0.0f,0.0f,0.0f);

		/// <summary>
		/// Accessor for the CYG's base 3d point. Setting the value
		/// updates the lies-on vector, distance from reference point,
		/// and cyg's length
		/// </summary>
		[Editor(typeof(Vector3InkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[TypeConverter(typeof(D3DVector3TypeConverter ))]
		[Description("CYG's base's 3D coordinates in centimeters (cm)")]
		public Vector3 BasePoint
		{
			get	{ return base3dPoint; } 
			set 
			{ 
				base3dPoint.X = value.X;
				base3dPoint.Y = value.Y;
				base3dPoint.Z = value.Z;
				liesOnVector= tip3dPoint - base3dPoint;
				distFromRef = Vector3.Length( base3dPoint - ref3dPoint);
				length = Vector3.Length( liesOnVector );
				isBasePositionSet = true;
			} 
		}
		private Vector3 base3dPoint		= new Vector3(0.0f,0.0f,0.0f);

		/// <summary>
		/// Accessor for the CYG's tip 3d point. Setting the value
		/// updates the liesOnVector and cyg's length
		/// </summary>
		[Editor(typeof(Vector3InkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[TypeConverter(typeof(D3DVector3TypeConverter ))]
		[Description("CYG's tip's 3D coordinates in centimeters (cm)")]
		public Vector3 TipPoint
		{
			get	{ return tip3dPoint; } 
			set 
			{ 
				tip3dPoint.X = value.X;
				tip3dPoint.Y = value.Y;
				tip3dPoint.Z = value.Z;
				liesOnVector= tip3dPoint - base3dPoint;
				length = Vector3.Length( liesOnVector );
				isTipPositionSet = true;
			} 
		}
		private Vector3 tip3dPoint		= new Vector3(0.0f,0.0f,0.0f);

		/// <summary>
		/// Accessor to retrieve the lies on vector
		/// </summary>
		[Description("The CYG core vector formed between the base and tip points")]
		public Vector3 Vector
		{
			get	{ return liesOnVector; } 
		}
		private Vector3 liesOnVector	= new Vector3(0.0f,0.0f,0.0f);

		/// <summary>
		/// Accessor for the CYG's base diameter.
		/// </summary>
		[Editor(typeof(FloatInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("The diameter of this CYG's base in millimeters (mm)")]
		public float BaseDiameter
		{
			get { return baseDiameter; } 
			set 
			{ 
				baseDiameter = value; 
				isBaseDiameterSet = true;
			} 
		}
		private float baseDiameter		= 0.0f;

		/// <summary>
		/// Accessor for the distance from the CYG's base to the 3d reference point
		/// </summary>
		[Description("Distance from CYG's base to random reference point in centimeters (cm)")]
		public float DistanceFromReferencePoint
		{
			get
			{
				return distFromRef;
			}
		}
		private float distFromRef		= 0.0f;

		/// <summary>
		/// Accessor for the length of the CYG
		/// </summary>
		[Description("The CYG's length from base to tip in centimeters (cm)")]
		public float Length
		{
			get 
			{
				return length;
			}
		}
		private float length			= 0.0f;
		
		/// <summary>
		/// Default constructor. Empty. Don't use it.
		/// </summary>
		public CurrentYearGrowth(){}

		/// <summary>
		/// Create a new CYG with the specified index (out of the total number
		/// of cyg's per plant patch). The number, starting at one, is
		/// indictivate of the dept level. Use arithmetic to determine the depth
		/// level. Given a depth level. Generate some random points.
		/// </summary>
		/// <param name="suffixNum">number from 1 to CYGS_PER_PATCH</param>
		public CurrentYearGrowth(int suffixNum)
		{
			// switch to zero based and truncate divide by the
			// number of depth levels to determine our depth level
			int depth = (suffixNum-1) / ModelParameters.NUM_DEPTH_LEVELS_PER_PLANTPATCH;

			// set the reference point, depth is 0 to NUM_LEVELS-1
			ReferencePoint = RandomPointAtDepthLevel(depth);

			// name it with the number as as suffix
			Name = "Cyg" + suffixNum;
		}


		/// <summary>
		/// find random point in volume using model paramenters
		/// </summary>
		/// <param name="_depthlevel">0 to NUM_LEVELS-1</param>
		/// <returns></returns>
		public static Vector3 RandomPointAtDepthLevel(int _depthlevel)
		{
			return CurrentYearGrowth.RandomPointAtDepthLevel(_depthlevel,ModelParameters.PLANTPATCH_RADIUS_CENTIMETERS,ModelParameters.DEPTH_LEVEL_HEIGHT_CENTIMETERS);
		}

		/// <summary>
		/// use 3d cylindrical coordinates to determine a random point
		/// in the sampling cylinder. loosely coupled version.
		/// </summary>
		/// <param name="_depthlevel">0 to NUM_LEVELS-1</param>
		/// <param name="_radius"></param>
		/// <param name="_depthlevelheight"></param>
		/// <returns></returns>
		public static Vector3 RandomPointAtDepthLevel(int _depthlevel, float _radius, float _depthlevelheight)
		{
			Vector3 randVec = new Vector3();

			// a random angle between 0 and 2 PI
			double theta = rand.NextDouble() * 2D * Math.PI;

			// a random distance from the cylinder origin between 0 and radius
			double radius = rand.NextDouble() * _radius;

			// convert polar coordinates  to cartesian coordinates on the xy plane
			randVec.X = (float) ( radius * Math.Cos(theta) );
			randVec.Y = (float) ( radius * Math.Sin(theta) );

			// now translate that to the center of the cylinder so it is correct
			// in model space. TODO jerk, shouldn't this be in the renderer?
			randVec.X += _radius;
			randVec.Y += _radius;

			// lift the xy point to the right depth level
			randVec.Z = (float)(_depthlevel * _depthlevelheight);

			// a random height between 0 and depthlevel height 
			// to put it somewhere inside the depth level
			randVec.Z += (float) rand.NextDouble() * _depthlevelheight;

			return randVec;
		}
	} // END class CurrentYearGrowth


	/// <summary>
	/// A PlantPatch is a container for CurrentYearGrowths (CYGs).
	/// It is divided into three depth levels and contains a fixed
	/// number of CYGs (9 in this implementation). In addition,
	/// to containing CYGs a PlantPatch contains polar coordinate
	/// information relative to the waypoint of which it is a member.
	/// The plant patches shape is defined as an ellipse using a long
	/// axis and the axis perpindicular to the long axis.
	/// TODO JERK
	/// Modify this so that it's sample space is adjustable...
	/// </summary>
	public class PlantPatch :
		FilterableProperties
	{


		/// <summary>
		/// The name of the PlantPatch
		/// </summary>
		private string name = "PlantPatch";

		/// <summary>
		/// The species of the PlantPatch
		/// </summary>
		private string species = "NoSpecies";

		/// <summary>
		/// The distance from the Waypoint to the nearest edge of this
		/// plant patch.
		/// </summary>
		private float distance = 0.0f;

		/// <summary>
		/// The bearing (clockwise from north) of the plant patch
		/// </summary>
		private float bearing = 0.0f;

		/// <summary>
		/// The length of the long axis of the ellipse that bounds the plant patch
		/// </summary>
		private float longDiameter = 0.0f;

		/// <summary>
		/// The bearing of the long axis of the ellipse in degrees
		/// </summary>
		private float longAxisBearing = 0.0f;

		/// <summary>
		/// The length of the "short" axis of the ellipse that is perpindicular 
		/// to the long axis.
		/// </summary>
		private float perpendicularToLongDiameter = 0.0f;

		/// <summary>
		/// Polar coordinates (r,theta) where theta is from north in the waypoint
		/// clockwise
		/// </summary>
		public Vector2 ReferencePoint
		{
			get { return referencePoint; }
			set { referencePoint = value; }
		}
		private Vector2 referencePoint = new Vector2();

		/// <summary>
		/// propterties filter
		/// </summary>
		private string[] visibleProperties = new string[7] {"Name","Species","Distance","Bearing","LongAxisBearing","LongAxisDiameter","ShortAxisDiameter"};

		/// <summary>
		/// Properties filter accessor
		/// </summary>
		protected override string[] VisibleProperties
		{
			get { return this.visibleProperties; }
		}

		/// <summary>
		/// The container for CYG's
		/// </summary>
		private CurrentYearGrowthCollection cygs = new CurrentYearGrowthCollection();


		/// <summary>
		/// Constructor
		/// </summary>
		public PlantPatch()
		{
			referencePoint = Waypoint.RandomPoint();
		}

		/// <summary>
		/// Constructor
		/// </summary>
		public PlantPatch(Vector2 refPt)
		{
			referencePoint = refPt;
		}

		/// <summary>
		/// Accessor for the PlantPatch's name
		/// </summary>
		[Editor(typeof(StringInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("PlantPatch's friendly identifier, nickname")]
		public string Name 
		{
			get { return name; } 
			set 
			{ name = value; } 
		}

		/// <summary>
		/// Accessor for the PlantPatch's species
		/// </summary>
		[TypeConverter(typeof(PlantPatchSpeciesStringConverter))]
		[Description("PlantPatch's species chosen from the species list")]
		public string Species 
		{
			get { return species; } 
			set 
			{ species = value; } 
		}


		/// <summary>
		/// Accessor for the PlantPatch's distance from the GPS Waypoint
		/// </summary>
		[Editor(typeof(FloatInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("PlantPatch's distance from the waypoint in meters (m)")]
		public float Distance
		{
			get { return distance; } 
			set 
			{ distance = value; } 
		}

		/// <summary>
		/// Accessor for the clockwise from north bearing of the PlantPatch in degrees
		/// </summary>
		[Editor(typeof(FloatInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("PlantPatch's bearing from the waypoint relative to north in degrees")]
		public float Bearing
		{
			get { return bearing; } 
			set { bearing = value; } 
		}

		/// <summary>
		/// Accessor for the length of the long diameter of the PlantPatch's
		/// bounding ellipse in centimeters
		/// </summary>
		[Editor(typeof(FloatInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("PlantPatch's longest diameter in centimeters (cm)")]
		public float LongAxisDiameter
		{
			get { return longDiameter; } 
			set 
			{ longDiameter = value; } 
		}

		/// <summary>
		/// Accessor for the long axis' bearing from north in degrees
		/// </summary>
		[Editor(typeof(FloatInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("PlantPatch's long axis bearing from the PlantPatch's center relative to north in degrees")]
		public float LongAxisBearing
		{
			get { return longAxisBearing; }
			set { longAxisBearing = value; }
		}

		/// <summary>
		/// Accessor for the length of the short diameter of the PlantPatch's
		/// bounding ellipse in meters
		/// </summary>
		[Editor(typeof(FloatInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("PlantPatch's diameter that is perpindicular to the LongDiameter in centimeters (cm)")]
		public float ShortAxisDiameter
		{
			get { return perpendicularToLongDiameter; } 
			set 
			{ perpendicularToLongDiameter = value; } 
		}

		/// <summary>
		/// cygs collection accessor 
		/// </summary>
		public CurrentYearGrowthCollection Cygs
		{
			get { return cygs; }
			set { cygs = value; }
		}


	} // END class PlantPatch


	/// <summary>
	/// A Waypoint is a GPS location in UTM that contains 0 or more plant patches.
	/// It comes from an Agency and has a coarse habitat type. It contains
	/// a collection of plant patches. It is a circular of specific radius around
	/// a specifict GPS waypoint.
	/// </summary>
	public class Waypoint : 
		FilterableProperties
	{	
		/// <summary>
		/// A shared random number generator
		/// </summary>
		private static Random rand = new Random();
		
		/// <summary>
		/// waypoint's friendly name
		/// </summary>
		private string name = "Waypoint";

		/// <summary>
		/// Waypoint's Habitat Type
		/// </summary>
		private string habitat = "NoHabitat";

		/// <summary>
		/// waypoints GPS coordinates in UTM
		/// </summary>
		private Vector2 gpsCoord = new Vector2(0.0f,0.0f);

		/// <summary>
		/// The plant patches sampled at this waypoint
		/// </summary>
		private PlantPatchCollection plantpatches = new PlantPatchCollection();

		/// <summary>
		/// properties filter
		/// </summary>
		private string[] visibleProperties = new string[5] {"Name","Habitat","Latitude","Longitude","ReferencePoints"};

		/// <summary>
		/// The random points associated with this waypoint
		/// </summary>
		public Vector2[] ReferencePoints
		{
			get { return referencePoints; }
		}
		private Vector2[] referencePoints = new Vector2[ModelParameters.NUM_SAMPLE_POINTS_PER_WAYPOINT];

		/// <summary>
		/// properties filter accessor
		/// </summary>
		protected override string[] VisibleProperties
		{
			get { return this.visibleProperties; }
		}

		/// <summary>
		/// plant patches accesser collection
		/// </summary>
		public PlantPatchCollection PlantPatches
		{
			get { return plantpatches; }
			set { plantpatches = value; }
		}


		/// <summary>
		/// THE REGULAR EXPRESSION for reading a waypoint line from a file
		/// it's a comma separated list with lat, lon, name, and habitat type
		/// <code>
		/// begin line
		/// (optional) whitespace
		/// a decimal floating point number
		/// (optional) whitespace
		/// comma
		/// (optional) whitespace
		/// a decimal floating point number
		/// (optional) whitespace
		/// comma
		/// (optional) whitespace
		/// one or more non-comma characters
		/// (optional) whitespace
		/// (optional) comma
		/// (optional) whitespace
		/// (optional) one or more non-comma characters
		/// (optional) whitespace
		/// end line
		/// </code>
		/// </summary>
		public const string REGEX_IMPORT_LINE = "^" +// beginning
												REGEX_FLOAT + // lat, float number 
												REGEX_COMMA + // comma
												REGEX_FLOAT +  // lon, float number
												"(" +				// habitat & name
													REGEX_COMMA +	// 0 to 2 comma & text
													REGEX_TEXT +
												"){0,2}" +
												"$";			// end of line				
		/// <summary>
		/// 
		/// </summary>
		public const string REGEX_FLOAT = REGEX_WS + "\\d*\\.\\d*" + REGEX_WS;
		
		/// <summary>
		/// 
		/// </summary>
		public const string REGEX_COMMA = REGEX_WS + "," + REGEX_WS;
		
		/// <summary>
		/// 
		/// </summary>
		public const string REGEX_TEXT  = REGEX_WS + "[^,]*" + REGEX_WS;

		/// <summary>
		/// Whitespace
		/// </summary>
		public const string REGEX_WS = "\\s*";

		/// <summary>
		/// For trimming leading whitespace
		/// </summary>
		public const string REGEX_WHITESPACE_LEADING = "^\\s*";

		/// <summary>
		/// For trimming trailing white space
		/// </summary>
		public const string REGEX_WHITESPACE_TRAILING = "\\s*$";
		
		/// <summary>
		/// For matching emptly lines
		/// </summary>
		public const string REGEX_WHITESPACE_EMPTYLINE = "^\\s*$";
		
		/// <summary>
		/// For matching comment lines
		/// </summary>
		public const string REGEX_IMPORT_COMMENT = "^\\s*(#|//).*$";

		/// <summary>
		/// Comma separated values with optional whitespace
		/// </summary>
		public const string REGEX_IMPORT_SEPARATOR = "\\s*,\\s*";

		/// <summary>
		/// empty default destructor, does nothing
		/// </summary>
		public Waypoint()
		{
			GeneratRandomNumbers();
		}

		private void GeneratRandomNumbers()
		{
			for( int i=0;i<referencePoints.Length;i++ )
			{
				referencePoints[i] = RandomPoint();
			}
		}
		/// <summary>
		/// Creates a waypoint from a string that is an import file line
		/// </summary>
		/// <param name="importLine">line from an agency import file</param>
		/// <param name="number">an optional number indicating the index in the field trip that this waypoin it. 1 based. Appended to name as a suffix.</param>
		public Waypoint(string importLine, int number )
		{
			GeneratRandomNumbers();

			//1.	latitude – UTM latitude of a Waypoint
			//2.	longitude – UTM longitude of waypoint
			//3.	habitat type – a text string with the agency’s coarse description of the habitat type
			//4.	 [name] – optionally, an agency friendly string describing the waypoint

			string[] columns = Regex.Split(importLine,Waypoint.REGEX_IMPORT_SEPARATOR);

			//1.	latitude – UTM latitude of a Waypoitit
			if( columns[0] != null )
			{
				try
				{
					gpsCoord.X = float.Parse(columns[0]);
				}
				catch(Exception)
				{
					throw new FieldTripImportFileLineCorruptException();
				}
			}
			else
			{
				throw new FieldTripImportFileLineCorruptException();
			}

			//2.	longitude – UTM longitude of waypoint
			if( columns[1] != null )
			{
				try
				{
					gpsCoord.Y = float.Parse(columns[1]);
				}
				catch(Exception)
				{
					throw new FieldTripImportFileLineCorruptException();
				}
			}
			else
			{
				throw new FieldTripImportFileLineCorruptException();
			}

			//3.	habitat type – a text string with the agency’s coarse description of the habitat type
			if( columns.Length >= 3 && columns[2] != null && columns[2].Length > 0)
			{
				string trimmed = Regex.Replace(columns[2],REGEX_WHITESPACE_LEADING,"");
				trimmed		   = Regex.Replace(trimmed,REGEX_WHITESPACE_TRAILING,"");
				habitat = trimmed;
			}
			else
			{
				//StringBuilder sb = new StringBuilder();
				//sb.Append("Agency Didn't Specify");
				habitat = ""; //sb.ToString();
			}

			//4.	 [name] – optionally, an agency friendly string describing the waypoint
			if( columns.Length==4 && columns[3] != null  && columns[3].Length > 0 )
			{
				string trimmed = Regex.Replace(columns[3],REGEX_WHITESPACE_LEADING,"");
				trimmed = Regex.Replace(trimmed,REGEX_WHITESPACE_TRAILING,"");
				name = trimmed;
			}
			else
			{
				//StringBuilder sb = new StringBuilder("Waypoint");
				//sb.Append(number);
				StringBuilder sb = new StringBuilder();
				sb.Append(gpsCoord.X.ToString() + " , " + gpsCoord.Y.ToString());
				name = sb.ToString();
			}
		}

		/// <summary>
		/// Accessor for the Waypoint's name
		/// </summary>
		[Editor(typeof(StringInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("Waypoint's friendly identifier, nickname")]
		public string Name 
		{
			get { return name; } 
			set 
			{ name = value; } 
		}

		/// <summary>
		/// Accessor for the Waypoint's habitat type
		/// </summary>
		[Editor(typeof(StringInkEditor), typeof(System.Drawing.Design.UITypeEditor))]
		[Description("The agency's description of the habitat at this waypoint")]
		public string Habitat
		{
			get { return habitat; } 
			set { habitat= value; }
		}

		/// <summary>
		/// Accessor for the Waypoint's GPS coordinate (Vector2)
		/// </summary>
		public Vector2 GpsCoordinate
		{
			get	{ return gpsCoord; } 
		}

		/// <summary>
		/// Accessor for the Waypoint's GPS latitude
		/// </summary>
		[Description("Waypoint's GPS Latitude in UTM")]
		public float Latitude
		{
			get	{ return gpsCoord.X; } 
			set { gpsCoord.X = value; }
		}


		/// <summary>
		/// Accessor for the Waypoint's GPS longitude
		/// </summary>
		[Description("Waypoint's GPS longitude in UTM")]
		public float Longitude
		{
			get	{ return gpsCoord.Y; } 
			set { gpsCoord.Y = value; }
		}

		/// <summary>
		/// Use model to find random point
		/// </summary>
		/// <returns></returns>
		public static Vector2 RandomPoint()
		{
			return Waypoint.RandomPoint(ModelParameters.WAYPOINT_RADIUS_METERS);
		}

		/// <summary>
		/// use 3d cylindrical coordinates to determine a random point
		/// in the sampling cylinder. loosely coupled version.
		/// </summary>
		/// <param name="_radius"></param>
		/// <returns></returns>
		public static Vector2 RandomPoint(float _radius)
		{
			Vector2 randVec = new Vector2();

			// a random angle between 0 and 2 PI
			double theta = rand.NextDouble() * 2D * Math.PI;

			// a random distance from the cylinder origin between 0 and radius
			double radius = rand.NextDouble() * _radius;

			return randVec;
		}


	} // END class Waypoint


	/// <summary>
	/// FieldTrip is a very simple container class for Waypoints.
	/// Because of the public accessors in Waypoint and PlantPatch
	/// it is a facade into the entire model. It also contains
	/// the list of biologists participating.
	/// </summary>
	public class FieldTrip
	{

		private WaypointCollection waypoints = new WaypointCollection();

		/// <summary>
		/// Waypoints collection accessor
		/// </summary>
		public WaypointCollection Waypoints
		{
			get { return waypoints; }
			set { waypoints = value; }
		}

		/// <summary>
		/// Read a FieldTrip from a file. Destructive to 
		/// existing field trip.
		/// </summary>
		/// <param name="filename"></param>
		public void BuildFromImportFile( string filename)
		{
			string line;
			ArrayList tmpList = new ArrayList();
			int lineNo=1;
			try
			{
				StreamReader reader = new StreamReader(filename);

				int i=1; // number to append to name
				bool skip=false,valid=false;
				while( (line = reader.ReadLine()) != null )
				{	
					skip = Regex.IsMatch(line,Waypoint.REGEX_IMPORT_COMMENT) 
							|| Regex.IsMatch(line,Waypoint.REGEX_WHITESPACE_EMPTYLINE);
					valid = Regex.IsMatch(line,Waypoint.REGEX_IMPORT_LINE);
					if( ! skip && valid )
					{
						Waypoint w = new Waypoint(line,i);
						tmpList.Add((Object) w);
						i++;
					}
					else if( ! skip && ! valid )
					{
						throw new FieldTripImportFileLineCorruptException();
					}
					lineNo++;
				}
				reader.Close();

				// we made it without exceptions
				// empty the ourselves
				waypoints.Clear();

				// load ourselves with waypoints
				for( i=0;i<tmpList.Count;i++)
				{
					waypoints.Add( (Waypoint) tmpList[i]);
				}
			}
			catch(FieldTripImportFileLineCorruptException)
			{
				MessageBox.Show("Bad waypoint line (" + lineNo + 
								") while importing file (" + filename + "). " + 
								"Please edit the file and try again.","Import File Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
			}
			catch(Exception)
			{
				MessageBox.Show("Error of unknown type while importing file (" + filename + ").","Import File Error",MessageBoxButtons.OK,MessageBoxIcon.Error );
			}
		}

		/// <summary>
		/// Dumps the current model to a file as CSV
		/// </summary>
		/// <param name="filename"></param>
		public void ExportToFile( string filename )
		{
			//1.	lattitude – UTM lattitude of Waypoint
			//2.	longitude – UTM longitude of Waypoint
			//3.	habitat type – Agency’s coarse description
			//4.	waypoint name – Waypoint’s textual name (or possibly agency’s friendly name)
			//5.	date stamp – The finish time of the PlantPatch
			//6.	plant patch name – The textual name of the PlantPatch
			//7.	plant patch species – The species of the PlantPatch
			//8.	CurrentYearsGrowth1dist – The CurrentYearsGrowth’s bases’ distance from the random point at that level
			//9.	CurrentYearsGrowth1length – The CurrentYearsGrowth’s length from base to tip
			//10.	CurrentYearsGrowth1diameter – The CurrentYearsGrowth’s base diameter
			//11.	CurrentYearsGrowth1level – The CurrentYearsGrowth’s depth level
			//[]...
			StringBuilder sb = new StringBuilder();

			foreach( Waypoint wp in waypoints )
			{
				foreach( PlantPatch pp in wp.PlantPatches )
				{
					foreach( CurrentYearGrowth cyg in pp.Cygs )
					{
						sb.Append( wp.Latitude + ",");
						sb.Append( wp.Longitude + ",");
						sb.Append( wp.Habitat + ",");
						sb.Append( wp.Name + ",");
						sb.Append( pp.Name + ",");
						sb.Append( pp.Species + ",");
						sb.Append( pp.Distance + ",");
						sb.Append( pp.Bearing + ",");
						sb.Append( pp.LongAxisDiameter + ",");
						sb.Append( pp.ShortAxisDiameter + ",");
						sb.Append( cyg.DistanceFromReferencePoint + ",");
						sb.Append( cyg.Length + ",");
// TODO JERK						sb.Append( cyg.DepthLevel+ "\n");
					}
				}
			}	
			StreamWriter writer = new StreamWriter(filename);
			writer.Write(sb.ToString());
			writer.Close();
			return;
		}
	
	} // END class FieldTrip


} // END namespace MhsModelCSharp


