//
// A very incomplete wrapper for lcms
//

using System;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;

#if ENABLE_NUNIT
using NUnit.Framework;
#endif

namespace Cms {
	public enum Format : uint {
		Rgb8  = 262169,
		Rgba8 = 262297,
		Rgba8Planar = 266393,
		Gbr8  = 263193,
		Rgb16 = 262170,
		Rgb16Planar = 266266,
		Rgba16 = 262298,
		Rgba16se = 264436,
		Rgb16se = 264218,
		Lab8 = 655385,
	        Lab16 = 655386,
		Xyz16 = 589858,
		Yxy16 = 917530
	}

	public enum IccColorSpace : uint {
		XYZ                        = 0x58595A20,  /* 'XYZ ' */
		Lab                        = 0x4C616220,  /* 'Lab ' */
		Luv                        = 0x4C757620,  /* 'Luv ' */
		YCbCr                      = 0x59436272,  /* 'YCbr' */
		Yxy                        = 0x59787920,  /* 'Yxy ' */
		Rgb                        = 0x52474220,  /* 'RGB ' */
		Gray                       = 0x47524159,  /* 'GRAY' */
		Hsv                        = 0x48535620,  /* 'HSV ' */
		Hls                        = 0x484C5320,  /* 'HLS ' */
		Cmyk                       = 0x434D594B,  /* 'CMYK' */
		Cmy                        = 0x434D5920,  /* 'CMY ' */
		Color2                     = 0x32434C52,  /* '2CLR' */
		Color3                     = 0x33434C52,  /* '3CLR' */
		Color4                     = 0x34434C52,  /* '4CLR' */
		Color5                     = 0x35434C52,  /* '5CLR' */
		Color6                     = 0x36434C52,  /* '6CLR' */
		Color7                     = 0x37434C52,  /* '7CLR' */
		Color8                     = 0x38434C52,  /* '8CLR' */
		Color9                     = 0x39434C52,  /* '9CLR' */
		Color10                    = 0x41434C52,  /* 'ACLR' */
		Color11                    = 0x42434C52,  /* 'BCLR' */
		Color12                    = 0x43434C52,  /* 'CCLR' */
		Color13                    = 0x44434C52,  /* 'DCLR' */
		Color14                    = 0x45434C52,  /* 'ECLR' */
		Color15                    = 0x46434C52,  /* 'FCLR' */
	}

	public enum Intent {
		Perceptual           = 0,
		RelativeColorimetric = 1,
		Saturation           = 2,
		AbsoluteColorimetric = 3
	}

	public struct ColorCIExyY {
		public double x;
		public double y;
		public double Y;
		
		public ColorCIExyY (double x, double y, double Y)
		{
			this.x = x;
			this.y = y;
			this.Y = Y;
		}

		public static ColorCIExyY WhitePointFromTemperature (int temp)
		{
			double x, y;
			CctTable.GetXY (temp, out x, out y);
			return new ColorCIExyY (x, y, 1.0);
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern bool cmsWhitePointFromTemp(int TempSrc,  out ColorCIExyY white_point);

		public static ColorCIExyY WhitePointFromTemperatureCIE (int temp)
		{
			ColorCIExyY wp;
			cmsWhitePointFromTemp (temp, out wp);
			return wp;
		}

		public static ColorCIExyY WhitePointFromTemperatureResource (int temp, string name)
		{
			ColorCIExyY wp;
			//const int line_size = 0x1e;

			using (Stream stream = Assembly.GetExecutingAssembly ().GetManifestResourceStream (name)) {
				StreamReader reader = new StreamReader (stream, System.Text.Encoding.ASCII);
				string line = null;
				for (int i = 0; i <= temp - 1000; i++) {
					line = reader.ReadLine ();
				}
				
				//System.Console.WriteLine (line);
				string [] subs = line.Split ('\t');
				int ptemp = int.Parse (subs [0]);
				if (ptemp != temp)
					throw new System.Exception (String.Format ("{0} != {1}", ptemp, temp));
				
				double x = double.Parse (subs [1]);
				double y = double.Parse (subs [2]);
				wp = new ColorCIExyY (x, y, 1.0);
				return wp;
			}			
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsxyY2XYZ (out ColorCIEXYZ dest, ref ColorCIExyY src);

		public ColorCIEXYZ ToXYZ ()
		{
			ColorCIEXYZ dest;
			cmsxyY2XYZ (out dest, ref this);
			
			return dest;
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsD50_xyY();

		public static ColorCIExyY D50 {
			get {
				IntPtr ptr = cmsD50_xyY ();
				return (ColorCIExyY) Marshal.PtrToStructure (ptr, typeof (ColorCIExyY));
			}
		}

		public ColorCIELab ToLab (ColorCIExyY wp)
		{
			return this.ToXYZ ().ToLab (wp);
		}

		public ColorCIELab ToLab (ColorCIEXYZ wp)
		{
			return this.ToXYZ ().ToLab (wp);
		}

		public override string ToString ()
		{
			return String.Format ("(x={0}, y={1}, Y={2})", x, y, Y);
		}
		
#if ENABLE_NUNIT
		[TestFixture]
		public class Tests {
			[Test]
			public void TestTempTable1000 ()
			{
				ColorCIExyY wp = WhitePointFromTemperature (1000);
				Assert.AreEqual (0.652756059, wp.x);
				Assert.AreEqual (0.344456906, wp.y);
			}

			[Test]
			public void TestTempReader ()
			{
				for (int i = 1000; i <= 25000; i += 10000)
					WhitePointFromTemperature (i);
			}
			
			[Test]
			public void TestTempTable10000 ()
			{
				ColorCIExyY wp = WhitePointFromTemperature (10000);
				Assert.AreEqual (0.280635904, wp.x);
				Assert.AreEqual (0.288290916, wp.y);
			}
		}
#endif
	}

	public struct ColorCIEXYZ {
		public double x;
		public double y;
		public double z;
		
		public ColorCIEXYZ (double x, double y, double z)
		{
			this.x = x;
			this.y = y;
			this.z = z;
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsXYZ2xyY (out ColorCIExyY dest, ref ColorCIEXYZ source);
		
		public ColorCIExyY ToxyY ()
		{
			ColorCIExyY dest;
			cmsXYZ2xyY (out dest, ref this);
			
			return dest;
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsXYZ2Lab (ref ColorCIEXYZ wp, out ColorCIELab lab, ref ColorCIEXYZ xyz);

		public ColorCIELab ToLab (ColorCIEXYZ wp)
		{
			ColorCIELab lab;
			cmsXYZ2Lab (ref wp, out lab, ref this);

			return lab;
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsD50_XYZ();

		public static ColorCIEXYZ D50 {
			get {
				IntPtr ptr = cmsD50_XYZ ();
				return (ColorCIEXYZ) Marshal.PtrToStructure (ptr, typeof (ColorCIEXYZ));
			}
		}

		public ColorCIELab ToLab (ColorCIExyY wp)
		{
			return ToLab (wp.ToXYZ ());
		}

		public override string ToString ()
		{
			return String.Format ("(x={0}, y={1}, z={2})", x, y, z);
		}


	}

	public struct ColorCIELab {
		public double L;
		public double a;
		public double b;

		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsLab2LCh (out ColorCIELCh lch, ref ColorCIELab lab);

		public ColorCIELCh ToLCh ()
		{
			ColorCIELCh lch;
			cmsLab2LCh (out lch, ref this);

			return lch;
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsLab2XYZ (ref ColorCIEXYZ wp, out ColorCIEXYZ xyz, ref ColorCIELab lab);
		
		public ColorCIEXYZ ToXYZ (ColorCIEXYZ wp)
		{
			ColorCIEXYZ xyz;
			cmsLab2XYZ (ref wp, out xyz, ref this);

			return xyz;
		}

		public override string ToString ()
		{
			return String.Format ("(L={0}, a={1}, b={2})", L, a, b);
		}
	}

	public struct ColorCIELCh {
		public double L;
		public double C;
		public double h;
		
		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsLCh2Lab (out ColorCIELab lab, ref ColorCIELCh lch);
		
		public ColorCIELab ToLab ()
		{
			ColorCIELab lab;
			cmsLCh2Lab (out lab, ref this);

			return lab;
		}
	}

	public struct ColorCIExyYTriple {
		public ColorCIExyY Red;
		public ColorCIExyY Green;
		public ColorCIExyY Blue;

		public ColorCIExyYTriple (ColorCIExyY red, ColorCIExyY green, ColorCIExyY blue)
		{
			Red = red;
			Green = green;
			Blue = blue;
		}
	}

	public struct ColorCIEXYZTriple {
		public ColorCIEXYZ Red;
		public ColorCIEXYZ Blue;
		public ColorCIEXYZ Green;

		ColorCIEXYZTriple (ColorCIEXYZ red, ColorCIEXYZ green, ColorCIEXYZ blue)
		{
			Red = red;
			Blue = blue;
			Green = green;
		}
	}

	public class GammaTable : IDisposable {
		private HandleRef handle;
		public HandleRef Handle {
			get {
				return handle;
			}
		}
		
//		internal struct GammaTableStruct {
//			public int Count;
//			public ushort StartOfData;  // ushort array Count entries long
//		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsBuildGamma (int entry_count, double gamma);

		public GammaTable (int count, double gamma)
		{
			handle = new HandleRef (this, cmsBuildGamma (count, gamma));
		}

		/*
		  Does build a parametric curve based on parameters.
		  Params[] does contain Gamma, a, b, c, d, e, f

		  Type 1 :
		  X = Y ^ Gamma   | Gamma = Params[0]
		  
		  Type 2 :
		  CIE 122-1966
		  Y = (aX + b)^Gamma  | X >= -b/a
		  Y = 0               | else
		  Type 3:
		  
		  IEC 61966-3
		  Y = (aX + b)^Gamma | X <= -b/a
		  Y = c              | else
		  
		  Type 4:
		  IEC 61966-2.1 (sRGB)
		  Y = (aX + b)^Gamma | X >= d
		  Y = cX             | X < d
		  
		  Type 5:
		  
		  Y = (aX + b)^Gamma + e | X <= d
		  Y = cX + f             | else
		  
		  Giving negative values for Type does result in reversed curve.
		*/

		// FIXME type should be an enum
		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsBuildParametricGamma (int entry_count, int type, double [] values);
		
		public GammaTable (int entry_count, int type, double [] values)
		{
			handle = new HandleRef (this, cmsBuildParametricGamma (entry_count, type, values));
		}
		

//		[DllImport("liblcms-1.0.0.dll")]
//		static extern IntPtr cmsAllocGamma (int entry_count);

		[DllImport ("libfspot")]
		static extern IntPtr f_cms_gamma_table_new (ushort [] values, int start, int length);

		[DllImport ("libfspot")]
		static extern IntPtr f_cms_gamma_table_get_values (HandleRef table);

		[DllImport ("libfspot")]
		static extern int f_cms_gamma_table_get_count (HandleRef table);

		public GammaTable (ushort [] values) : this (values, 0, values.Length)
		{
		}

		public int Count {
			get {
				return f_cms_gamma_table_get_count (handle);
			}
		}

		public IntPtr Values {
			get {
				return f_cms_gamma_table_get_values (handle);
			}
		}

		public ushort this [int index] {
			get {
				unsafe {
					if (handle.Handle == (IntPtr)0)
						throw new ArgumentException ();
					
					if (index < 0 || index >= Count)
						throw new ArgumentOutOfRangeException (String.Format ("index {0} outside of count {1} for {2}", index, Count, handle.Handle));

					ushort *data = (ushort *)Values;
					return data [index];
				}
			}
			set {
				unsafe {
					if (handle.Handle == (IntPtr)0)
						throw new ArgumentException ();
					
					if (index < 0 || index >= Count)
						throw new ArgumentOutOfRangeException (String.Format ("index {0} outside of count {1} for handle {2}", index, Count, handle.Handle));


					ushort *data = (ushort *)Values;
					data [index] = value;
				}
			}
		}
		
		public GammaTable (ushort [] values, int start_offset, int length)
		{
#if true
			handle = new HandleRef (this, f_cms_gamma_table_new (values, start_offset, length));
			//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXhandle = {0}", handle.Handle);
#else
			handle = new HandleRef (this, cmsAllocGamma (length));
			//System.Console.WriteLine ("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXhandle = {0}", handle.Handle);
			for (int i = 0; i < length; i++)
				this [i] = values [start_offset + i];
#endif
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsFreeGamma (HandleRef handle);
		
		protected virtual void Cleanup ()
		{
			cmsFreeGamma (handle);
		}
		
		public void Dispose ()
		{
			Cleanup ();
			System.GC.SuppressFinalize (this);
		}
		
		~GammaTable ()
		{
			Cleanup ();
		}


#if ENABLE_NUNIT
		[TestFixture]
		public class Tests {
			[Test]
			public void TestAlloc ()
			{
				ushort [] values = new ushort [] { 0, 0x00ff, 0xffff };
				GammaTable t = new GammaTable (values);
				for (int i = 0; i < values.Length; i++) {
					Assert.AreEqual (t[i], values [i]);
				}
			} 
		}
#endif 

	}
	
	public class Transform : IDisposable {
		private HandleRef handle;
		public HandleRef Handle {
			get {
				return handle;
			}
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreateMultiprofileTransform (HandleRef [] hProfiles,
								     int nProfiles,
								     Format InputFormat,
								     Format OutputFormat,
								     int Intent,
								     uint dwFlags);

		public Transform (Profile [] profiles,
				  Format input_format,
				  Format output_format,
				  Intent intent, uint flags)
		{
			HandleRef [] handles = new HandleRef [profiles.Length];
			for (int i = 0; i < profiles.Length; i++) {
				handles [i] = profiles [i].Handle;
			}
			
			this.handle = new HandleRef (this, cmsCreateMultiprofileTransform (handles, handles.Length, 
											   input_format,
											   output_format, 
											   (int)intent, flags));
		}
		
		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreateTransform(HandleRef Input,
							Format InputFormat,
							HandleRef Output,
							Format OutputFormat,
							int Intent,
							uint dwFlags);

		public Transform (Profile input, Format input_format,
				  Profile output, Format output_format,
				  Intent intent, uint flags)
		{
			this.handle = new HandleRef (this, cmsCreateTransform (input.Handle, input_format,
									       output.Handle, output_format,
									       (int)intent, flags));
		}
		
		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsDoTransform (HandleRef hTransform, IntPtr InputBuffer, IntPtr OutputBuffer, uint size);
		
		// Fixme this should probably be more type stafe 
		public void Apply (IntPtr input, IntPtr output, uint size)
		{
			cmsDoTransform (Handle, input, output, size);
		}
		
		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsDeleteTransform(HandleRef hTransform);
		
		public void Dispose () 
		{
			Cleanup ();
			System.GC.SuppressFinalize (this);
		}
		
		protected virtual void Cleanup ()
		{
			cmsDeleteTransform (this.handle);
		}
		
		~Transform () 
		{
			Cleanup ();
		}
	       
	}

	public class Profile : IDisposable {
		static Profile srgb = new Profile (cmsCreate_sRGBProfile());

		static Profile ()
		{
			SetErrorAction (ErrorAction.Show);
		}

		private HandleRef handle;
		public HandleRef Handle {
			get {
				return handle;
			}
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr _cmsCreateProfilePlaceholder ();
		
		private Profile () : this (_cmsCreateProfilePlaceholder ())
		{
			
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreate_sRGBProfile ();

		public static Profile CreateSRgb () 
		{
			return CreateStandardRgb ();
		}
		

		public static Profile CreateStandardRgb () 
		{
			return srgb;
		}
		
		public static Profile CreateAlternateRgb ()
		{
			// FIXME I'm basing this off the values set in the camera
			// exif data when the adobe profile is selected.  They could
			// easily be off
			ColorCIExyY wp = new ColorCIExyY (.3127, .329, 1.0);
			ColorCIExyYTriple primaries = new ColorCIExyYTriple (
				new ColorCIExyY (.64, .33, 1.0),
				new ColorCIExyY (.21, .71, 1.0),
				new ColorCIExyY (.15, .06, 1.0));
			
			GammaTable g = new GammaTable (4096, 2.2);
			GammaTable [] gamma = new GammaTable [] { g, g, g, g};

			return new Profile (wp, primaries, gamma);
		}

		[DllImport ("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreateLabProfile (out ColorCIExyY WhitePoint);

		public static Profile CreateLab (ColorCIExyY wp)
		{
			return new Profile (cmsCreateLabProfile (out wp));
		}			

		[DllImport ("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreateLabProfile (IntPtr foo);

		public static Profile CreateLab ()
		{
			return new Profile (cmsCreateLabProfile (IntPtr.Zero));
		}			

		[DllImport ("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreateGrayProfile (ref ColorCIExyY white_point,
							   HandleRef transfer_function);
		
		public static Profile CreateGray (ColorCIExyY white_point, GammaTable transfer)
		{
			if (transfer == null)
				return new Profile (cmsCreateGrayProfile (ref white_point, new GammaTable (4096, 2.2).Handle));
			else
				return new Profile (cmsCreateGrayProfile (ref white_point, transfer.Handle));
		}

		[DllImport ("libfspot")]
		static extern IntPtr f_screen_get_profile (IntPtr screen);

		public static Profile GetScreenProfile (Gdk.Screen screen)
		{
			IntPtr profile = f_screen_get_profile (screen.Handle);
			
			if (profile == IntPtr.Zero)
				return null;
			
			return new Profile (profile);
		}


//		[DllImport("liblcms-1.0.0.dll")]
//		static extern IntPtr cmsCreateBCHSWabstractProfile(int nLUTPoints,
//								   double Bright, 
//								   double Contrast,
//								   double Hue,
//								   double Saturation,
//								   int TempSrc, 
//								   int TempDest);

		[DllImport("libfspot")]
		static extern IntPtr f_cmsCreateBCHSWabstractProfile(int nLUTPoints,
								     double Exposure,
								     double Bright, 
								     double Contrast,
								     double Hue,
								     double Saturation,
								     ref ColorCIExyY src_wp, 
								     ref ColorCIExyY dest_wp,
								     HandleRef [] tables);
		
		public static Profile CreateAbstract (int nLUTPoints,
						      double Exposure,
						      double Bright,
						      double Contrast,
						      double Hue,
						      double Saturation,
						      int TempSrc,
						      int TempDest)
		{
#if true			
			GammaTable gamma = new GammaTable (1024, Math.Pow (10, -Bright/100));
			GammaTable line = new GammaTable (1024, 1.0);
			GammaTable [] tables = new GammaTable [] { gamma, line, line };
			return CreateAbstract (nLUTPoints, Exposure, 0.0, Contrast, Hue, Saturation, tables, 
					       ColorCIExyY.WhitePointFromTemperature (TempSrc), 
					       ColorCIExyY.WhitePointFromTemperature (TempDest));
#else
			GammaTable [] tables = null;
			return CreateAbstract (nLUTPoints, Exposure, Bright, Contrast, Hue, Saturation, tables, 
					       ColorCIExyY.WhitePointFromTemperature (TempSrc), 
					       ColorCIExyY.WhitePointFromTemperature (TempDest));
#endif
		}

		public static Profile CreateAbstract (int nLUTPoints,
						      double Exposure,
						      double Bright,
						      double Contrast,
						      double Hue,
						      double Saturation,
						      GammaTable [] tables,
						      ColorCIExyY src_wp,
						      ColorCIExyY dest_wp)
		{
			if (tables == null) {
				GammaTable gamma = new GammaTable (1024, Math.Pow (10, -Bright/100));
				GammaTable line = new GammaTable (1024, 1.0);
				tables = new GammaTable [] { gamma, line, line };
			}

			/*
			System.Console.WriteLine ("e {0}", Exposure);
			System.Console.WriteLine ("b {0}", Bright);
			System.Console.WriteLine ("c {0}", Contrast);
			System.Console.WriteLine ("h {0}", Hue);
			System.Console.WriteLine ("s {0} {1} {2}", Saturation, src_wp, dest_wp);
			*/
			return new Profile (f_cmsCreateBCHSWabstractProfile (nLUTPoints,
									     Exposure,
									     0.0, //Bright,
									     Contrast,
									     Hue,
									     Saturation,
									     ref src_wp,
									     ref dest_wp,
									     CopyHandles (tables)));
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreateLinearizationDeviceLink (IccColorSpace color_space, HandleRef [] tables);
		
		public Profile (IccColorSpace color_space, GammaTable [] gamma)
		{
			handle = new HandleRef (this, cmsCreateLinearizationDeviceLink (color_space, CopyHandles (gamma)));
		}

		private static HandleRef [] CopyHandles (GammaTable [] gamma)
		{
			if (gamma == null)
				return null;

			HandleRef [] gamma_handles = new HandleRef [gamma.Length];
			for (int i = 0; i < gamma_handles.Length; i++)
				gamma_handles [i] = gamma [i].Handle;
			
			return gamma_handles;
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsCreateRGBProfile (out ColorCIExyY whitepoint, 
						          out ColorCIExyYTriple primaries,
							  HandleRef [] gamma_table);

		public Profile (ColorCIExyY whitepoint, ColorCIExyYTriple primaries, GammaTable [] gamma)
		{
			handle = new HandleRef (this, cmsCreateRGBProfile (out whitepoint, out primaries, CopyHandles (gamma)));
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern int cmsCloseProfile (HandleRef hprofile);

		[DllImport("liblcms-1.0.0.dll")]
		static extern IntPtr cmsOpenProfileFromFile (string ICCProfile, string sAccess);

		public Profile (string path) 
		{
			handle = new HandleRef (this, cmsOpenProfileFromFile (path, "r"));

			if (handle.Handle == IntPtr.Zero)
				throw new Exception ("Error opening ICC profile in file " + path);
		}


		[DllImport("liblcms-1.0.0.dll")]
		static extern unsafe bool _cmsSaveProfileToMem (HandleRef profile, byte *mem, ref uint length);

		public byte [] Save ()
		{
			unsafe {
				uint length = 0;
				if (_cmsSaveProfileToMem (this.Handle, null, ref length)) {
					byte [] data = new byte [length];
					fixed (byte * data_p = &data [0]) {
						if (_cmsSaveProfileToMem (this.Handle, data_p, ref length)) {
							return data;
						}
					}
				}
			}
			throw new SaveException ("Error Saving Profile");
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern unsafe IntPtr cmsOpenProfileFromMem (byte *data, uint length);

		public Profile (byte [] data) : this (data, 0, data.Length) {}

		public Profile (byte [] data, int start_offset, int length)
		{
			if (start_offset < 0)
				throw new System.ArgumentOutOfRangeException ("start_offset < 0");

			if (data.Length - start_offset < 0)
				throw new System.ArgumentOutOfRangeException ("start_offset > data.Length");

			if (data.Length - length - start_offset < 0)
				throw new System.ArgumentOutOfRangeException ("start_offset + length > data.Length");
			 
			IntPtr profileh;
			unsafe {
				fixed (byte * start = & data [start_offset]) {
					profileh = cmsOpenProfileFromMem (start, (uint)length);
				}
			}
			
			if (profileh == IntPtr.Zero)
				throw new System.Exception ("Invalid Profile Data");
			else 
				this.handle = new HandleRef (this, profileh);
		}

		[DllImport("liblcms-1.0.0.dll")]
		extern static bool cmsTakeMediaWhitePoint (out ColorCIEXYZ wp, HandleRef handle);
		
		public ColorCIEXYZ MediaWhitePoint {
			get {
				ColorCIEXYZ wp;
				if (!cmsTakeMediaWhitePoint (out wp, handle))
					throw new ApplicationException ("unable to retrieve white point from profile");
				return wp;
			}
		}

		[DllImport("liblcms-1.0.0.dll")]
		extern static bool cmsTakeMediaBlackPoint (out ColorCIEXYZ black, HandleRef handle);
		
		public ColorCIEXYZ MediaBlackPoint {
			get {
				ColorCIEXYZ black;
				if (!cmsTakeMediaBlackPoint (out black, handle))
					throw new ApplicationException ("unable to retrieve white point from profile");
				
				return black;
			}
		}
		
		[DllImport("liblcms-1.0.0.dll")]
		extern static bool cmsTakeColorants (out ColorCIEXYZTriple colors, HandleRef handle);

		public ColorCIEXYZTriple Colorants {
			get {
				ColorCIEXYZTriple colors;
				if (! cmsTakeColorants (out colors, handle))
					throw new ApplicationException ("Unable to retrieve profile colorants");
				
				return colors;
			}				
		}
		
		[DllImport("liblcms-1.0.0.dll")]
		extern static IntPtr cmsTakeModel (HandleRef handle);
		
		public string Model {
			get {
				lock (srgb) {
					return Marshal.PtrToStringAnsi (cmsTakeModel (handle));
				}
			}
		}

		[DllImport("liblcms-1.0.0.dll")]
		extern static IntPtr cmsTakeProductName (HandleRef handle);

		public string ProductName {
			get {
				lock (srgb) {
					return Marshal.PtrToStringAnsi (cmsTakeProductName (handle));
				}
			}
		}
		[DllImport("liblcms-1.0.0.dll")]
		extern static IntPtr cmsTakeProductDesc (HandleRef handle);

		public string ProductDescription {
			get {
				lock (srgb) {
					return Marshal.PtrToStringAnsi (cmsTakeProductDesc (handle));
				}
			}
		}

		private enum ErrorAction {
			Abort,
			Show,
			Ignore
		}

		[DllImport("liblcms-1.0.0.dll")]
		static extern void cmsErrorAction (ErrorAction action);

		private static void SetErrorAction (ErrorAction act)
		{
			cmsErrorAction (act);
		}

		public override string ToString ()
		{
			return ProductName;
		}

		protected Profile (IntPtr handle)
		{
			this.handle = new HandleRef (this, handle);
		}

		public void Dispose () 
		{
			Cleanup ();

			System.GC.SuppressFinalize (this);
		}

		protected virtual void Cleanup ()
		{
			if (cmsCloseProfile (this.Handle) == 0)
				throw new Exception ("Error closing Handle");

		}

		~Profile ()
		{
			Cleanup ();
		}

#if ENABLE_NUNIT
		[TestFixture]
		public class Tests {
			[Test]
			public void LoadSave ()
			{
				Profile srgb = CreateStandardRgb ();
				byte [] data = srgb.Save ();
				Assert.IsNotNull (data);
				Profile result = new Profile (data);
				Assert.AreEqual (result.ProductName, srgb.ProductName);
				Assert.AreEqual (result.ProductDescription, srgb.ProductDescription);
				Assert.AreEqual (result.Model, srgb.Model);
			}
		}
#endif 		
	}

	public class SaveException : System.Exception {
		public SaveException (string message) : base (message)
		{
		}
	}
}
