/*
 *  IEC61850ClientAPI.cs
 *
 *  Copyright 2014 Michael Zillgith
 *
 *  This file is part of libIEC61850.
 *
 *  libIEC61850 is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  libIEC61850 is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with libIEC61850.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  See COPYING file for the complete license text.
 */
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Collections;

using IEC61850.Common;

/// <summary>
/// IEC 61850 API for the libiec61850 .NET wrapper library
/// </summary>
namespace IEC61850
{
    /// <summary>
    /// IEC 61850 client API.
    /// </summary>
	namespace Client
	{

		[StructLayout(LayoutKind.Sequential)]
		public class MmsServerIdentity
		{
			public string vendorName;
			public string modelName;
			public string revision;
		}

		public class MmsConnection 
		{

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern IntPtr MmsConnection_create();

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern void MmsConnection_destroy(IntPtr self);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern IntPtr MmsConnection_identify(IntPtr self, out int mmsError);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern void MmsServerIdentity_destroy(IntPtr self);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern void MmsConnection_setLocalDetail (IntPtr self, Int32 localDetail);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern Int32 MmsConnection_getLocalDetail (IntPtr self);


			private IntPtr self = IntPtr.Zero;
			private bool selfDestroy = false;

			public MmsConnection() {
				selfDestroy = true;

				self = MmsConnection_create();
			}

			internal MmsConnection(IntPtr mmsConnection) {
				self = mmsConnection;
			}

			~MmsConnection ()
            {
				if (selfDestroy)
                	if (self != IntPtr.Zero)
                   		MmsConnection_destroy(self);
            }

			public MmsServerIdentity GetServerIdentity ()
			{
				int mmsError;

				if (self == IntPtr.Zero) {
					throw new IedConnectionException("Pointer is Zero!");
				}

				IntPtr identity = MmsConnection_identify(self, out mmsError);

				MmsServerIdentity serverIdentity = (MmsServerIdentity) 
					Marshal.PtrToStructure(identity, typeof(MmsServerIdentity));

				MmsServerIdentity_destroy(identity);

				return serverIdentity;
			}

			public void SetLocalDetail(int localDetail) {
				MmsConnection_setLocalDetail (self, localDetail);
			}

			public int GetLocalDetail() {
				return MmsConnection_getLocalDetail (self);
			}

		}

		/// <summary>
		/// This class acts as the entry point for the IEC 61850 client API. It represents a single
		/// (MMS) connection to a server.
		/// </summary>
		public partial class IedConnection
		{
			/*************
            * MmsValue
            *************/
			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern IntPtr MmsValue_toString (IntPtr self);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern float MmsValue_toFloat (IntPtr self);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            [return: MarshalAs(UnmanagedType.I1)]
			static extern bool MmsValue_getBoolean (IntPtr self);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern UInt32 MmsValue_getBitStringAsInteger (IntPtr self);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern int MmsValue_getType (IntPtr self);
        
			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern void MmsValue_delete (IntPtr self);

			/****************
            * IedConnection
            ***************/

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_create ();

            [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
            static extern void IedConnection_destroy (IntPtr self);

            [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
            static extern void IedConnection_setConnectTimeout(IntPtr self, UInt32 timeoutInMs);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern void IedConnection_connect (IntPtr self, out int error, string hostname, int tcpPort);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern void IedConnection_abort (IntPtr self, out int error);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern void IedConnection_release(IntPtr self, out int error);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			private static extern void IedConnection_close(IntPtr self);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_readObject (IntPtr self, out int error, string objectReference, int fc);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern void IedConnection_writeObject (IntPtr self, out int error, string dataAttributeReference, int fc, IntPtr value);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_getDataDirectory (IntPtr self, out int error, string dataReference);

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern IntPtr IedConnection_getDataDirectoryByFC (IntPtr self, out int error, string dataReference, FunctionalConstraint fc);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_getDataDirectoryFC (IntPtr self, out int error, string dataReference);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_getLogicalNodeDirectory (IntPtr self, out int error, string logicalNodeReference, int acsiClass);
        
			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_getServerDirectory (IntPtr self, out int error, bool getFileNames);

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern void IedConnection_getDeviceModelFromServer(IntPtr self, out int error);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_getLogicalDeviceDirectory (IntPtr self, out int error, string logicalDeviceName);

			[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_getVariableSpecification(IntPtr self, out int error, string objectReference, int fc);

            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			private delegate void InternalConnectionClosedHandler (IntPtr parameter,IntPtr Iedconnection);

			public delegate void ConnectionClosedHandler (IedConnection connection);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern void IedConnection_installConnectionClosedHandler (IntPtr self, InternalConnectionClosedHandler handler, IntPtr parameter);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_readDataSetValues (IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_createDataSet (IntPtr self, out int error, [MarshalAs(UnmanagedType.LPStr)] string dataSetReference, IntPtr dataSet);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern bool IedConnection_deleteDataSet (IntPtr self, out int error, string dataSetReference);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern IntPtr IedConnection_getDataSetDirectory(IntPtr self, out int error, string dataSetReference, [MarshalAs(UnmanagedType.I1)] out bool isDeletable);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr IedConnection_getMmsConnection (IntPtr self);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr MmsConnection_getIsoConnectionParameters(IntPtr mmsConnection);

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern IntPtr IedConnection_getFileDirectory(IntPtr self, out int error, string directoryName);

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern void IedConnection_deleteFile(IntPtr self, out int error, string fileName);

            /********************
            * FileDirectoryEntry
            *********************/
            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern void FileDirectoryEntry_destroy(IntPtr self);

			/****************
        	* LinkedList
         	***************/
			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr LinkedList_getNext (IntPtr self);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr LinkedList_getData (IntPtr self);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern void LinkedList_destroy (IntPtr self);

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern void LinkedList_destroyStatic(IntPtr self);

            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
			private delegate void LinkedListValueDeleteFunction(IntPtr pointer);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern void LinkedList_destroyDeep(IntPtr list, LinkedListValueDeleteFunction valueDeleteFunction);

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern IntPtr LinkedList_create ();

			[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
			static extern void LinkedList_add (IntPtr self, IntPtr data);

			private IntPtr connection = IntPtr.Zero;
			private InternalConnectionClosedHandler connectionClosedHandler;
			private ConnectionClosedHandler userProvidedHandler = null;

			public IedConnection ()
			{
				connection = IedConnection_create ();
			}

			/// <summary>
			/// Releases all resource used by the <see cref="IEC61850.Client.IedConnection"/> object.
			/// </summary>
			/// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="IEC61850.Client.IedConnection"/>. The
			/// <see cref="Dispose"/> method leaves the <see cref="IEC61850.Client.IedConnection"/> in an unusable state. After
			/// calling <see cref="Dispose"/>, you must release all references to the <see cref="IEC61850.Client.IedConnection"/>
			/// so the garbage collector can reclaim the memory that the <see cref="IEC61850.Client.IedConnection"/> was occupying.</remarks>
			public void Dispose()
			{
				if (connection != IntPtr.Zero) {
					cleanupRCBs ();

					IedConnection_destroy (connection);

					connection = IntPtr.Zero;
				}
			}

            ~IedConnection ()
            {
				if (connection != IntPtr.Zero) {
					cleanupRCBs ();

					IedConnection_destroy (connection);
				}
            }

			public IsoConnectionParameters GetConnectionParameters ()
			{
				IntPtr mmsConnection = IedConnection_getMmsConnection(connection);

				IntPtr parameters = MmsConnection_getIsoConnectionParameters(mmsConnection);

				return new IsoConnectionParameters(parameters);
			}

			private void FreeHGlobaleDeleteFunction (IntPtr pointer)
			{
				Marshal.FreeHGlobal(pointer);
			}


			private UInt32 connectTimeout = 10000;

			public UInt32 ConnectTimeout {
				get {
					return connectTimeout;
				}
				set {
					connectTimeout = value;
				}
			}


			public MmsConnection GetMmsConnection ()
			{
				IntPtr mmsConnectionPtr = IedConnection_getMmsConnection(connection);

				return new MmsConnection(mmsConnectionPtr);
			}

			/// <summary>Establish an MMS connection to a server</summary>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public void Connect (string hostname, int tcpPort)
			{
				int error;

                IedConnection_setConnectTimeout(connection, connectTimeout);

				IedConnection_connect (connection, out error, hostname, tcpPort);

				if (error != 0)
					throw new IedConnectionException ("Connect to " + hostname + ":" + tcpPort + " failed", error);
			}

			/// <summary>Establish an MMS connection to a server.</summary>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error.</exception>
			public void Connect (string hostname)
			{
				Connect (hostname, 102);
			}

			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public ControlObject CreateControlObject (string objectReference)
			{
				ControlObject controlObject = new ControlObject (objectReference, connection, this);

				return controlObject;
			}

            public void UpdateDeviceModel()
            {
                int error;

                IedConnection_getDeviceModelFromServer(connection, out error);

                if (error != 0)
                    throw new IedConnectionException("UpdateDeviceModel failed", error);
            }

			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public List<string> GetServerDirectory (bool fileDirectory = false)
			{
				int error;

				IntPtr linkedList = IedConnection_getServerDirectory (connection, out error, fileDirectory);

				if (error != 0)
					throw new IedConnectionException ("GetDeviceDirectory failed", error);

				List<string> newList = new List<string> ();

				if (fileDirectory == false) {

					IntPtr element = LinkedList_getNext (linkedList);

					while (element != IntPtr.Zero) {
						string ld = Marshal.PtrToStringAnsi (LinkedList_getData (element));

						newList.Add (ld);

						element = LinkedList_getNext (element);
					}

					LinkedList_destroy (linkedList);
				} 
				else {

					IntPtr element = LinkedList_getNext(linkedList);

	                while (element != IntPtr.Zero)
	                {
	                    IntPtr elementData = LinkedList_getData(element);

	                    FileDirectoryEntry entry = new FileDirectoryEntry(elementData);

						newList.Add(entry.GetFileName());

	                    FileDirectoryEntry_destroy(elementData);

	                    element = LinkedList_getNext(element);
	                }

	                LinkedList_destroyStatic(linkedList);
				}

				return newList;
			}

			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public List<string> GetLogicalDeviceDirectory (string logicalDeviceName)
			{
				int error;

				IntPtr linkedList = IedConnection_getLogicalDeviceDirectory (connection, out error, logicalDeviceName);

				if (error != 0)
					throw new IedConnectionException ("GetLogicalDeviceDirectory failed", error);

				IntPtr element = LinkedList_getNext (linkedList);

				List<string> newList = new List<string> ();

				while (element != IntPtr.Zero) {
					string ln = Marshal.PtrToStringAnsi (LinkedList_getData (element));

					newList.Add (ln);

					element = LinkedList_getNext (element);
				}

				LinkedList_destroy (linkedList);

				return newList;
			}

			/// <summary>Get the directory of a logical node (LN)</summary>
			/// <description>This function returns the directory contents of a LN. Depending on the provided ACSI class
			/// The function returns either data object references, or references of other objects like data sets,
			/// report control blocks, ...</description>
			/// <param name="logicalNodeName">The object reference of a DO, SDO, or DA.</param>
			/// <param name="acsiClass">the ACSI class of the requested directory elements.</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public List<string> GetLogicalNodeDirectory (string logicalNodeName, ACSIClass acsiClass)
			{
				int error;

				IntPtr linkedList = IedConnection_getLogicalNodeDirectory (connection, out error, logicalNodeName, (int)acsiClass);

				if (error != 0)
					throw new IedConnectionException ("GetLogicalNodeDirectory failed", error);

				IntPtr element = LinkedList_getNext (linkedList);

				List<string> newList = new List<string> ();

				while (element != IntPtr.Zero) {
					string dataObject = Marshal.PtrToStringAnsi (LinkedList_getData (element));

					newList.Add (dataObject);

					element = LinkedList_getNext (element);
				}

				LinkedList_destroy (linkedList);

				return newList;
			}

			/// <summary>Get a list of attributes (with functional constraints) of a DO, SDO, or DA</summary>
			/// <param name="dataReference">The object reference of a DO, SDO, or DA.</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public List<string> GetDataDirectory (string dataReference)
			{
				int error;

				IntPtr linkedList = IedConnection_getDataDirectory (connection, out error, dataReference);

				if (error != 0)
					throw new IedConnectionException ("GetDataDirectory failed", error);

				IntPtr element = LinkedList_getNext (linkedList);

				List<string> newList = new List<string> ();

				while (element != IntPtr.Zero) {
					string dataObject = Marshal.PtrToStringAnsi (LinkedList_getData (element));

					newList.Add (dataObject);

					element = LinkedList_getNext (element);
				}

				LinkedList_destroy (linkedList);

				return newList;
			}

            /// <summary>Get the list of attributes with the specified FC of a DO, SDO, or DA</summary>
            /// <param name="dataReference">The object reference of a DO, SDO, or DA.</param>
            /// <param name="fc">Functional constraint</param>
            /// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
            public List<string> GetDataDirectory (string dataReference, FunctionalConstraint fc)
            {
                int error;

                IntPtr linkedList = IedConnection_getDataDirectoryByFC (connection, out error, dataReference, fc);

                if (error != 0)
                    throw new IedConnectionException ("GetDataDirectory failed", error);

                IntPtr element = LinkedList_getNext (linkedList);

                List<string> newList = new List<string> ();

                while (element != IntPtr.Zero) {
                    string dataObject = Marshal.PtrToStringAnsi (LinkedList_getData (element));

                    newList.Add (dataObject);

                    element = LinkedList_getNext (element);
                }

                LinkedList_destroy (linkedList);

                return newList;
            }

			/// <summary>Get a list of attributes (with functional constraints) of a DO, SDO, or DA</summary>
			/// <description>This function is similar to the GetDataDirectory except that the returned element names
			/// have the functional contraint (FC) appended.</description>
			/// <param name="dataReference">The object reference of a DO, SDO, or DA.</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public List<string> GetDataDirectoryFC (string dataReference)
			{
				int error;

				IntPtr linkedList = IedConnection_getDataDirectoryFC (connection, out error, dataReference);

				if (error != 0)
					throw new IedConnectionException ("GetDataDirectoryFC failed", error);

				IntPtr element = LinkedList_getNext (linkedList);

				List<string> newList = new List<string> ();

				while (element != IntPtr.Zero) {
					string dataObject = Marshal.PtrToStringAnsi (LinkedList_getData (element));

					newList.Add (dataObject);

					element = LinkedList_getNext (element);
				}

				LinkedList_destroy (linkedList);

				return newList;
			}


			/// <summary>Read the variable specification (type description of a DA or FDCO</summary>
			/// <param name="objectReference">The object reference of a DA or FCDO.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public MmsVariableSpecification GetVariableSpecification (string objectReference, FunctionalConstraint fc)
			{
				int error;

				IntPtr varSpecPtr = IedConnection_getVariableSpecification(connection, out error, objectReference, (int) fc);

				if (error != 0)
					throw new IedConnectionException ("GetVariableSpecification failed", error);

				return new MmsVariableSpecification(varSpecPtr, true);
			}

			private IntPtr readObjectInternal (string objectReference, FunctionalConstraint fc)
			{
				int error;

				IntPtr mmsValue = IedConnection_readObject (connection, out error, objectReference, (int)fc); 

				if (error != 0)
					throw new IedConnectionException ("Reading value failed", error);

				if (mmsValue.ToInt32 () == 0)
					throw new IedConnectionException ("Variable not found on server", error);

				return mmsValue;
			}

			/// <summary>Read the value of a data attribute (DA) or functional constraint data object (FCDO).</summary>
			/// <param name="objectReference">The object reference of a DA or FCDO.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <returns>the received value as an MmsValue instance</returns>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public MmsValue ReadValue (String objectReference, FunctionalConstraint fc)
			{
				var value = readObjectInternal (objectReference, fc);

				return new MmsValue (value, true);
			}

			/// <summary>Read the value of a basic data attribute (BDA) of type boolean.</summary>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <returns>the received boolean value</returns>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public bool ReadBooleanValue (string objectReference, FunctionalConstraint fc)
			{
				var mmsValue = ReadValue (objectReference, fc);

				if (mmsValue.GetType () == MmsType.MMS_BOOLEAN)
					return mmsValue.GetBoolean ();
				else
					throw new IedConnectionException ("Result is not of type timestamp (MMS_UTC_TIME)", 0);
			}

			/// <summary>Read the value of a basic data attribute (BDA) of type float.</summary>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public float ReadFloatValue (string objectReference, FunctionalConstraint fc)
			{
				IntPtr mmsValue = readObjectInternal (objectReference, fc);

				if (MmsValue_getType (mmsValue) != (int)MmsType.MMS_FLOAT)
					throw new IedConnectionException ("Result is not of type float", 0);

				float value = MmsValue_toFloat (mmsValue);

				MmsValue_delete (mmsValue);

				return value;
			}

			/// <summary>Read the value of a basic data attribute (BDA) of type string (VisibleString or MmsString).</summary>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public string ReadStringValue (string objectReference, FunctionalConstraint fc)
			{
				IntPtr mmsValue = readObjectInternal (objectReference, fc);

				if (!((MmsValue_getType (mmsValue) == (int)MmsType.MMS_VISIBLE_STRING) || (MmsValue_getType (mmsValue) == (int)MmsType.MMS_STRING))) {
					MmsValue_delete (mmsValue);
					throw new IedConnectionException ("Result is not of type string", 0);
				}

				IntPtr ptr = MmsValue_toString (mmsValue);

				string returnString = Marshal.PtrToStringAnsi (ptr);

				MmsValue_delete (mmsValue);

				return returnString;
			}

			/// <summary>Read the value of a basic data attribute (BDA) of type quality.</summary>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public Quality ReadQualityValue (string objectReference, FunctionalConstraint fc)
			{
				IntPtr mmsValue = readObjectInternal (objectReference, fc);

				if (MmsValue_getType (mmsValue) == (int)MmsType.MMS_BIT_STRING) {
					int bitStringValue = (int)MmsValue_getBitStringAsInteger (mmsValue);

					MmsValue_delete (mmsValue);
					return new Quality (bitStringValue);
				} else {
					MmsValue_delete (mmsValue);
					throw new IedConnectionException ("Result is not of type bit string(Quality)", 0);
				}
			}

			/// <summary>Read the value of a basic data attribute (BDA) of type bit string.</summary>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public int ReadBitStringValue (string objectReference, FunctionalConstraint fc)
			{
				IntPtr mmsValue = readObjectInternal (objectReference, fc);

				if (MmsValue_getType (mmsValue) == (int)MmsType.MMS_BIT_STRING) {
					int bitStringValue = (int)MmsValue_getBitStringAsInteger (mmsValue);

					MmsValue_delete (mmsValue);
					return bitStringValue;
				} else {
					MmsValue_delete (mmsValue);
					throw new IedConnectionException ("Result is not of type bit string", 0);
				}
			}

			/// <summary>Read the value of a basic data attribute (BDA) of type timestamp (MMS_UTC_TIME).</summary>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public UInt64 ReadTimestampValue (string objectReference, FunctionalConstraint fc)
			{
				var mmsValuePtr = readObjectInternal (objectReference, fc);

				var mmsValue = new MmsValue (mmsValuePtr, true);

				if (mmsValue.GetType () == MmsType.MMS_UTC_TIME)
					return mmsValue.GetUtcTimeInMs ();
				else
					throw new IedConnectionException ("Result is not of type timestamp (MMS_UTC_TIME)", 0);
			}

			/// <summary>Read the value of a basic data attribute (BDA) of type integer (MMS_INTEGER).</summary>
			/// <description>This function should also be used if enumerations are beeing read. Because
			/// enumerations are mapped to integer types for the MMS mapping</description>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public Int64 ReadIntegerValue (string objectReference, FunctionalConstraint fc)
			{
				var mmsValuePtr = readObjectInternal (objectReference, fc);

				var mmsValue = new MmsValue (mmsValuePtr, true);

				if (mmsValue.GetType () == MmsType.MMS_INTEGER)
					return mmsValue.ToInt64 ();
				else
					throw new IedConnectionException ("Result is not of type integer (MMS_INTEGER)", 0);
			}

			/// <summary>Write the value of a data attribute (DA) or functional constraint data object (FCDO).</summary>
			/// <description>This function can be used to write simple or complex variables (setpoints, parameters, descriptive values...)
			/// of the server.</description>
			/// <param name="objectReference">The object reference of a BDA.</param>
			/// <param name="fc">The functional constraint (FC) of the object</param>
			/// <param name="value">MmsValue object representing asimple or complex variable data</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public void WriteValue (string objectReference, FunctionalConstraint fc, MmsValue value)
			{
				int error;

				IedConnection_writeObject (connection, out error, objectReference, (int)fc, value.valueReference);

				if (error != 0)
					throw new IedConnectionException ("Write value failed", error);
			}

            /// <summary>Read the content of a file directory.</summary>
            /// <param name="directoryName">The name of the directory.</param>
            /// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
            public List<FileDirectoryEntry> GetFileDirectory(string directoryName)
            {
                int error;

                IntPtr fileEntryList = IedConnection_getFileDirectory(connection, out error, directoryName);

                if (error != 0)
                    throw new IedConnectionException("Reading file directory failed", error);

                List<FileDirectoryEntry> fileDirectory = new List<FileDirectoryEntry>();

                IntPtr element = LinkedList_getNext(fileEntryList);

                while (element != IntPtr.Zero)
                {
                    IntPtr elementData = LinkedList_getData(element);

                    FileDirectoryEntry entry = new FileDirectoryEntry(elementData);

                    fileDirectory.Add(entry);

                    FileDirectoryEntry_destroy(elementData);

                    element = LinkedList_getNext(element);
                }

                LinkedList_destroyStatic(fileEntryList);

                return fileDirectory;
            }

			[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
            private delegate bool InternalIedClientGetFileHandler(IntPtr parameter, IntPtr buffer, UInt32 bytesRead);

            private bool iedClientGetFileHandler(IntPtr parameter, IntPtr buffer, UInt32 bytesRead)
            {
                GCHandle handle = GCHandle.FromIntPtr(parameter);

                GetFileCallback getFileCallback = (GetFileCallback) handle.Target;

                byte[] bytes = new byte[bytesRead];

                Marshal.Copy(buffer, bytes, 0, (int) bytesRead);

                return getFileCallback.handler(getFileCallback.parameter, bytes);
            }

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            static extern UInt32 IedConnection_getFile(IntPtr self, out int error, string fileName, InternalIedClientGetFileHandler handler,
                IntPtr handlerParameter);


            public delegate bool GetFileHandler(object parameter, byte[] data);

            private class GetFileCallback
            {
                public GetFileCallback(GetFileHandler handler, object parameter)
                {
                    this.handler = handler;
                    this.parameter = parameter;
                }

                public GetFileHandler handler;
                public object parameter;
            }

			/// <summary>
			/// Download a file from the server.
			/// </summary>
			/// <param name='fileName'>
			/// File name of the file (full path)
			/// </param>
			/// <param name='handler'>
			/// Callback handler that is invoked for each chunk of the file received
			/// </param>
			/// <param name='parameter'>
			/// User provided parameter that is passed to the callback handler
			/// </param>
            public void GetFile(string fileName, GetFileHandler handler, object parameter)
            {
                int error;

                GetFileCallback getFileCallback = new GetFileCallback(handler, parameter);

                GCHandle handle = GCHandle.Alloc(getFileCallback);

                IedConnection_getFile(connection, out error, fileName, new InternalIedClientGetFileHandler(iedClientGetFileHandler), 
                    GCHandle.ToIntPtr(handle));

                if (error != 0)
                    throw new IedConnectionException("Error reading file", error);

                handle.Free();
            }

			/// <summary>
			/// Abort (close) the connection.
			/// </summary>
			/// <description>This function will send an abort request to the server. This will immediately interrupt the
			/// connection.</description>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public void Abort ()
			{
				int error;

				IedConnection_abort (connection, out error);

				if (error != 0)
					throw new IedConnectionException ("Abort failed", error);
			}

			/// <summary>
			/// Release (close) the connection.
			/// </summary>
			/// <description>This function will send an release request to the server. The function will block until the
			/// connection is released or an error occured.</description>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public void Release ()
			{
				int error;

				IedConnection_release(connection, out error);

				if (error != 0)
					throw new IedConnectionException ("Release failed", error);
			}

			/// <summary>
			/// Immediately close the connection.
			/// </summary>
			/// <description>This function will close the connnection to the server by closing the TCP connection.
			/// The client will not send an abort or release request as required by the specification!</description>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public void Close ()
			{
				IedConnection_close(connection);
			}

			private void MyConnectionClosedHandler (IntPtr parameter, IntPtr self)
			{
				if (userProvidedHandler != null)
					userProvidedHandler (this);
			}

			/// <summary>
			/// Install a callback handler that will be invoked if the connection is closed.
			/// </summary>
			/// <description>The handler is called when the connection is closed no matter if the connection was closed
			/// by the client or by the server. Any new call to this function will replace the callback handler installed
			/// by a prior function call.</description>
			/// <param name="handler">The user provided callback handler</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public void InstallConnectionClosedHandler (ConnectionClosedHandler handler)
			{
				connectionClosedHandler = new InternalConnectionClosedHandler (MyConnectionClosedHandler);

				userProvidedHandler = handler;

				IedConnection_installConnectionClosedHandler (connection, connectionClosedHandler, connection);
			}

            /// <summary>
            /// Read the values of a data set (GetDataSetValues service).
            /// </summary>
            /// <description>This function will invoke a readDataSetValues service and return a new DataSet value containing the
            /// received values.</description>
            /// <param name="dataSetReference">The object reference of the data set</param>

            /// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
            public DataSet GetDataSetValues (string dataSetReference)
            {
                return ReadDataSetValues(dataSetReference, null);
            }

			/// <summary>
			/// Read the values of a data set (GetDataSetValues service).
			/// </summary>
			/// <description>This function will invoke a readDataSetValues service and return a new DataSet value containing the
			/// received values. If an existing instance of DataSet is provided to the function the existing instance will be
			/// updated by the new values.</description>
			/// <param name="dataSetReference">The object reference of the data set</param>
			/// <param name="dataSet">The object reference of an existing data set instance or null</param>
			/// <returns>a DataSet instance containing the received values</returns>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public DataSet ReadDataSetValues (string dataSetReference, DataSet dataSet)
			{
				IntPtr nativeClientDataSet = IntPtr.Zero;

				if (dataSet != null)
					nativeClientDataSet = dataSet.getNativeInstance ();
     
				int error;

				nativeClientDataSet = IedConnection_readDataSetValues (connection, out error, dataSetReference, nativeClientDataSet);
                    
				if (error != 0)
					throw new IedConnectionException ("Reading data set failed", error);

				if (dataSet == null)
					dataSet = new DataSet (nativeClientDataSet);

				return dataSet;
			}

			/// <summary>
			/// Create a new data set.
			/// </summary>
			/// <description>This function creates a new data set at the server. The data set consists of the members defined
			/// by the list of object references provided.</description>
			/// <param name="dataSetReference">The object reference of the data set</param>
			/// <param name="dataSetElements">A list of object references of the data set elements</param>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public void CreateDataSet (string dataSetReference, List<string> dataSetElements)
			{
				IntPtr linkedList = LinkedList_create ();

				foreach (string dataSetElement in dataSetElements) {
					IntPtr handle = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi (dataSetElement);

					LinkedList_add (linkedList, handle);
				}

				int error;

				IedConnection_createDataSet (connection, out error, dataSetReference, linkedList);

				LinkedList_destroyDeep(linkedList, new LinkedListValueDeleteFunction(FreeHGlobaleDeleteFunction));

				if (error != 0)
					throw new IedConnectionException ("Failed to create data set", error);

			}

			/// <summary>
			/// Delete a data set.
			/// </summary>
			/// <description>This function will delete a data set at the server. This function may fail if the data set is not
			/// deletable.</description>
			/// <param name="dataSetReference">The object reference of the data set</param>
			/// <returns>true if data set has been deleted, false otherwise</returns>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public bool DeleteDataSet (string dataSetReference)
			{
				int error;

				bool isDeleted = IedConnection_deleteDataSet (connection, out error, dataSetReference);

				if (error != 0)
					throw new IedConnectionException ("Failed to delete data set", error);

				return isDeleted;
			}

            /// <summary>
            /// Get the directory of the data set.
            /// </summary>
            /// <description>This function returns a list of object references with appended functional constraints (FC) of the data set elemenents.</description>
            /// <param name="dataSetReference">The object reference of the data set</param>
            /// <returns>the list of object references</returns>
            /// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
            public List<string> GetDataSetDirectory(string dataSetReference)
            {
                bool isDeletable;

                return GetDataSetDirectory(dataSetReference, out isDeletable);
            }

            /// <summary>
            /// Get the directory of the data set.
            /// </summary>
            /// <description>This function returns a list of object references with appended functional constraints (FC) of the data set elemenents.</description>
            /// <param name="dataSetReference">The object reference of the data set</param>
            /// <param name="isDeletable">Indication if this data set is permanent or deletable.</param>
            /// <returns>the list of object references</returns>
			/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
			public List<string> GetDataSetDirectory (string dataSetReference, out bool isDeletable)
			{
				int error;

				IntPtr linkedList = IedConnection_getDataSetDirectory (connection, out error, dataSetReference, out isDeletable);

				if (error != 0)
					throw new IedConnectionException ("getDataSetDirectory failed", error);

				IntPtr element = LinkedList_getNext (linkedList);

				List<string> newList = new List<string> ();

				while (element != IntPtr.Zero) {
					string dataObject = Marshal.PtrToStringAnsi (LinkedList_getData (element));

					newList.Add (dataObject);

					element = LinkedList_getNext (element);
				}

				LinkedList_destroy (linkedList);

				return newList;
			}
		}

		public class IedConnectionException : Exception
		{

			private int errorCode;

			public IedConnectionException (string message, int errorCode) : base(message)
			{
				this.errorCode = errorCode;
			}

			public IedConnectionException (string message) : base(message)
			{
				this.errorCode = 0;
			}

			public int GetErrorCode ()
			{
				return this.errorCode;
			}

			public IedClientError GetIedClientError ()
			{
				return (IedClientError)this.errorCode;
			}
		}

        public class FileDirectoryEntry
        {
            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr FileDirectoryEntry_getFileName(IntPtr self);

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            private static extern UInt32 FileDirectoryEntry_getFileSize(IntPtr self);

            [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
            private static extern UInt64 FileDirectoryEntry_getLastModified(IntPtr self);

            private string fileName;
            private UInt32 fileSize;
            private UInt64 lastModified;

            internal FileDirectoryEntry(IntPtr nativeFileDirectoryEntry)
            {
                fileName = Marshal.PtrToStringAnsi(FileDirectoryEntry_getFileName(nativeFileDirectoryEntry));
                fileSize = FileDirectoryEntry_getFileSize(nativeFileDirectoryEntry);
                lastModified = FileDirectoryEntry_getLastModified(nativeFileDirectoryEntry);
            }

            public string GetFileName() 
            {
                return fileName;
            }

            public UInt32 GetFileSize()
            {
                return fileSize;
            }

            public UInt64 GetLastModified()
            {
                return lastModified;
            }
        }

		public enum IedClientError
		{
			/* general errors */

			/** No error occurred - service request has been successful */
			IED_ERROR_OK = 0,

			/** The service request can not be executed because the client is not yet connected */
			IED_ERROR_NOT_CONNECTED = 1,

			/** Connect service not execute because the client is already connected */
			IED_ERROR_ALREADY_CONNECTED = 2,

			/** The service request can not be executed caused by a loss of connection */
			IED_ERROR_CONNECTION_LOST = 3,

			/** The service or some given parameters are not supported by the client stack or by the server */
			IED_ERROR_SERVICE_NOT_SUPPORTED = 4,

			/** Connection rejected by server */
			IED_ERROR_CONNECTION_REJECTED = 5,

			/* client side errors */

			/** API function has been called with an invalid argument */
			IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT = 10,

			IED_ERROR_ENABLE_REPORT_FAILED_DATASET_MISMATCH = 11,

			/** The object provided object reference is invalid (there is a syntactical error). */
			IED_ERROR_OBJECT_REFERENCE_INVALID = 12,

			/** Received object is of unexpected type */
			IED_ERROR_UNEXPECTED_VALUE_RECEIVED = 13,

			/* service error - error reported by server */

			/** The communication to the server failed with a timeout */
			IED_ERROR_TIMEOUT = 20,

			/** The server rejected the access to the requested object/service due to access control */
			IED_ERROR_ACCESS_DENIED = 21,

			/** The server reported that the requested object does not exist */
			IED_ERROR_OBJECT_DOES_NOT_EXIST = 22,

			/** The server reported that the requested object already exists */
			IED_ERROR_OBJECT_EXISTS = 23,

			/** The server does not support the requested access method */
			IED_ERROR_OBJECT_ACCESS_UNSUPPORTED = 24,

			/** The server expected an object of another type */
			IED_ERROR_TYPE_INCONSISTENT = 25,

            /** The object or service is temporarily unavailable */
            IED_ERROR_TEMPORARILY_UNAVAILABLE = 26,

            /** The specified object is not defined in the server (returned by server) */
            IED_ERROR_OBJECT_UNDEFINED = 27,

            /** The specified address is invalid (returned by server) */
            IED_ERROR_INVALID_ADDRESS = 28,

            /** Service failed due to a hardware fault (returned by server) */
            IED_ERROR_HARDWARE_FAULT = 29,

            /** The requested data type is not supported by the server (returned by server) */
            IED_ERROR_TYPE_UNSUPPORTED = 30,

            /** The provided attributes are inconsistent (returned by server) */
            IED_ERROR_OBJECT_ATTRIBUTE_INCONSISTENT = 31,

            /** The provided object value is invalid (returned by server) */
            IED_ERROR_OBJECT_VALUE_INVALID = 32,

            /** The object is invalidated (returned by server) */
            IED_ERROR_OBJECT_INVALIDATED = 33,

			/* unknown error */
			IED_ERROR_UNKNOWN = 99
		}  
	}

	namespace Common
	{
	
		[Flags]
		public enum TriggerOptions {
			NONE = 0,
            /** send report when value of data changed */
			DATA_CHANGED = 1,
            /** send report when quality of data changed */
			QUALITY_CHANGED = 2,
            /** send report when data or quality is updated */
			DATA_UPDATE = 4,
            /** periodic transmission of all data set values */
			INTEGRITY = 8,
            /** general interrogation (on client request) */
			GI = 16
		}

		[Flags]
		public enum ReportOptions {
			NONE = 0,
			SEQ_NUM = 1,
			TIME_STAMP = 2,
			REASON_FOR_INCLUSION = 4,
			DATA_SET = 8,
			DATA_REFERENCE = 16,
			BUFFER_OVERFLOW = 32,
			ENTRY_ID = 64,
			CONF_REV = 128,
			ALL = 255
		}

		public enum Validity
		{
			GOOD = 0,
			RESERVED = 1,
			INVALID = 2,
			QUESTIONABLE = 3
		}

        /// <summary>
        /// The quality of a data object.
        /// </summary>
		public class Quality
		{

			private UInt16 value;

			public Quality (int bitStringValue)
			{
				value = (UInt16)bitStringValue;
			}

			public Quality ()
			{
				value = 0;
			}

			public Validity GetValidity ()
			{
				int qualityVal = value & 0x3;

				return (Validity)qualityVal;
			}

			public void SetValidity (Validity validity)
			{
				value = (UInt16)(value & 0xfffc);

				value += (ushort)validity;
			}
		}

		public enum ACSIClass
		{
            /** data objects */
			ACSI_CLASS_DATA_OBJECT,
            /** data sets (container for multiple data objects) */
			ACSI_CLASS_DATA_SET,
            /** buffered report control blocks */
			ACSI_CLASS_BRCB,
            /** unbuffered report control blocks */
			ACSI_CLASS_URCB,
            /** log control blocks */
			ACSI_CLASS_LCB,
            /** logs (journals) */
			ACSI_CLASS_LOG,
            /** setting group control blocks */
			ACSI_CLASS_SGCB,
            /** GOOSE control blocks */
			ACSI_CLASS_GoCB,
            /** GSE control blocks */
			ACSI_CLASS_GsCB,
            /** multicast sampled values control blocks */
			ACSI_CLASS_MSVCB,
            /** unicast sampled values control blocks */
			ACSI_CLASS_USVCB
		}

		public enum FunctionalConstraint
		{
			/** Status information */
			ST = 0,
			/** Measurands - analog values */
			MX = 1,
			/** Setpoint */
			SP = 2,
			/** Substitution */
			SV = 3,
			/** Configuration */
			CF = 4,
			/** Description */
			DC = 5,
			/** Setting group */
			SG = 6,
			/** Setting group editable */
			SE = 7,
			/** Service response / Service tracking */
			SR = 8,
			/** Operate received */
			OR = 9,
			/** Blocking */
			BL = 10,
			/** Extended definition */
			EX = 11,
			/** Control */
			CO = 12,
			/** Unicast SV */
			US = 13,
			/** Multicast SV */
			MS = 14,
			/** Unbuffered report */
			RP = 15,
			/** Buffered report */
			BR = 16,

			/** All FCs - wildcard value */
			ALL = 99,
			NONE = -1
		}

		public enum ControlAddCause {
		    ADD_CAUSE_UNKNOWN = 0,
		    ADD_CAUSE_NOT_SUPPORTED = 1,
		    ADD_CAUSE_BLOCKED_BY_SWITCHING_HIERARCHY = 2,
		    ADD_CAUSE_SELECT_FAILED = 3,
		    ADD_CAUSE_INVALID_POSITION = 4,
		    ADD_CAUSE_POSITION_REACHED = 5,
		    ADD_CAUSE_PARAMETER_CHANGE_IN_EXECUTION = 6,
		    ADD_CAUSE_STEP_LIMIT = 7,
		    ADD_CAUSE_BLOCKED_BY_MODE = 8,
		    ADD_CAUSE_BLOCKED_BY_PROCESS = 9,
		    ADD_CAUSE_BLOCKED_BY_INTERLOCKING = 10,
		    ADD_CAUSE_BLOCKED_BY_SYNCHROCHECK = 11,
		    ADD_CAUSE_COMMAND_ALREADY_IN_EXECUTION = 12,
		    ADD_CAUSE_BLOCKED_BY_HEALTH = 13,
		    ADD_CAUSE_1_OF_N_CONTROL = 14,
		    ADD_CAUSE_ABORTION_BY_CANCEL = 15,
		    ADD_CAUSE_TIME_LIMIT_OVER = 16,
		    ADD_CAUSE_ABORTION_BY_TRIP = 17,
		    ADD_CAUSE_OBJECT_NOT_SELECTED = 18,
		    ADD_CAUSE_OBJECT_ALREADY_SELECTED = 19,
		    ADD_CAUSE_NO_ACCESS_AUTHORITY = 20,
		    ADD_CAUSE_ENDED_WITH_OVERSHOOT = 21,
		    ADD_CAUSE_ABORTION_DUE_TO_DEVIATION = 22,
		    ADD_CAUSE_ABORTION_BY_COMMUNICATION_LOSS = 23,
		    ADD_CAUSE_ABORTION_BY_COMMAND = 24,
		    ADD_CAUSE_NONE = 25,
		    ADD_CAUSE_INCONSISTENT_PARAMETERS = 26,
		    ADD_CAUSE_LOCKED_BY_OTHER_CLIENT = 27
		} 
	
        /// <summary>
        /// Object reference. Helper function to handle object reference strings.
        /// </summary>
		public static class ObjectReference {

            /// <summary>
            /// Get the name part of an object reference with appended FC
            /// </summary>
            /// <returns>
            /// The element name.
            /// </returns>
            /// <param name='objectReferenceWithFc'>
            /// Object reference with appended fc.
            /// </param>
			public static string getElementName (string objectReferenceWithFc)
			{
				int fcPartStartIndex = objectReferenceWithFc.IndexOf('[');

				if (fcPartStartIndex == -1)
					return objectReferenceWithFc;

				return objectReferenceWithFc.Substring(0, fcPartStartIndex);
			}

            /// <summary>
            /// Get the FC of an object reference with appended FC.
            /// </summary>
            /// <returns>
            /// The FC
            /// </returns>
            /// <param name='objectReferenceWithFc'>
            /// Object reference with FC.
            /// </param>
			public static FunctionalConstraint getFC (string objectReferenceWithFc)
			{
				int fcPartStartIndex = objectReferenceWithFc.IndexOf('[');

				if (fcPartStartIndex == -1)
					return FunctionalConstraint.NONE;

				string fcString = objectReferenceWithFc.Substring(fcPartStartIndex + 1 , 2);

				try
				{
				   return (FunctionalConstraint) Enum.Parse(typeof(FunctionalConstraint), fcString);
				}
				catch(ArgumentException)
				{
					return FunctionalConstraint.NONE;
				}
			}
		}

	}
}
