// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2006 Alexander Olk
//
// Authors:
//
//  Alexander Olk	alex.olk@googlemail.com
//

// use
// public static int GetIconIndexForFile( string full_filename )
// public static int GetIconIndexForMimeType( string mime_type )
// to get the image index in MimeIconEngine.SmallIcons and MimeIconEngine.LargeIcons

using System;
using System.Reflection;
using System.Drawing;
using System.Collections;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Xml;

namespace System.Windows.Forms
{
	internal enum MimeExtensionHandlerStatus
	{
		OK,
		ERROR
	}
	
	internal enum EPlatformHandler
	{
		Default,
		GNOME
	}
	
	internal class ResourceImageLoader
	{
		static Assembly assembly = typeof (ResourceImageLoader).Assembly;
		
		static internal Bitmap Get (string name)
		{
			Stream stream = assembly.GetManifestResourceStream (name);

			if (stream == null) {
				Console.WriteLine ("Failed to read {0}", name);
				return null;
			}
				
			return new Bitmap (stream);
		}
		
		static internal Icon GetIcon (string name)
		{
			Stream stream = assembly.GetManifestResourceStream (name);

			if (stream == null) {
				Console.WriteLine ("Failed to read {0}", name);
				return null;
			}

			return new Icon (stream);
		}
	}
	
	internal class MimeIconEngine
	{
		public static ImageList SmallIcons = new ImageList();
		public static ImageList LargeIcons = new ImageList();
		
		private static EPlatformHandler platform = EPlatformHandler.Default;
		
		internal static Hashtable MimeIconIndex = new Hashtable ();
		
		private static PlatformMimeIconHandler platformMimeHandler = null;
		
		private static object lock_object = new Object();
		
		static MimeIconEngine ()
		{
			SmallIcons.ColorDepth = ColorDepth.Depth32Bit;
			SmallIcons.TransparentColor = Color.Transparent;
			LargeIcons.ColorDepth = ColorDepth.Depth32Bit;
			LargeIcons.TransparentColor = Color.Transparent;
			
			string session =  Environment.GetEnvironmentVariable ("DESKTOP_SESSION");
			
			if (session != null) {
				session = session.ToUpper ();
				
				if (session == "DEFAULT") {
					string helper = Environment.GetEnvironmentVariable ("GNOME_DESKTOP_SESSION_ID");
					
					if (helper != null)
						session = "GNOME";
				}
			} else
				session = String.Empty;
			
			if (Mime.MimeAvailable && session == "GNOME") {
				SmallIcons.ImageSize = new Size (24, 24);
				LargeIcons.ImageSize = new Size (48, 48);
				
				platformMimeHandler = new GnomeHandler ();
				if (platformMimeHandler.Start () == MimeExtensionHandlerStatus.OK) {
					platform = EPlatformHandler.GNOME;
				} else {
					MimeIconEngine.LargeIcons.Images.Clear ();
					MimeIconEngine.SmallIcons.Images.Clear ();
					platformMimeHandler = new PlatformDefaultHandler ();
					platformMimeHandler.Start ();
				}
			} else {
				SmallIcons.ImageSize = new Size (16, 16);
				LargeIcons.ImageSize = new Size (48, 48);
				
				platformMimeHandler = new PlatformDefaultHandler ();
				platformMimeHandler.Start ();
			}
		}
		
		public static int GetIconIndexForFile (string full_filename)
		{
			lock (lock_object) {
				if (platform == EPlatformHandler.Default) {
					return (int)MimeIconIndex ["unknown/unknown"];
				}
				
				string mime_type = Mime.GetMimeTypeForFile (full_filename);
				
				object oindex = GetIconIndex (mime_type);
				
				// not found, add it
				if (oindex == null) {
					int index = full_filename.IndexOf (':');
					
					if (index > 1) {
						oindex = MimeIconIndex ["unknown/unknown"];
						
					} else {
						oindex = platformMimeHandler.AddAndGetIconIndex (full_filename, mime_type);
						
						// sanity check
						if (oindex == null)
							oindex = MimeIconIndex ["unknown/unknown"];
					}
				}
				
				return (int)oindex;
			}
		}
		
		public static int GetIconIndexForMimeType (string mime_type)
		{
			lock (lock_object) {
				if (platform == EPlatformHandler.Default) {
					if (mime_type == "inode/directory") {
						return (int)MimeIconIndex ["inode/directory"];
					} else {
						return (int)MimeIconIndex ["unknown/unknown"];
					}
				}
				
				object oindex = GetIconIndex (mime_type);
				
				// not found, add it
				if (oindex == null) {
					oindex = platformMimeHandler.AddAndGetIconIndex (mime_type);
					
					// sanity check
					if (oindex == null)
						oindex = MimeIconIndex ["unknown/unknown"];
				}
				
				return (int)oindex;
			}
		}
		
		public static Image GetIconForMimeTypeAndSize (string mime_type, Size size)
		{
			lock (lock_object) {
				object oindex = GetIconIndex (mime_type);
				
				Bitmap bmp = new Bitmap (LargeIcons.Images [(int)oindex], size);
				
				return bmp;
			}
		}
		
		internal static void AddIconByImage (string mime_type, Image image)
		{
			int index = SmallIcons.Images.Add (image, Color.Transparent);
			LargeIcons.Images.Add (image, Color.Transparent);
			
			MimeIconIndex.Add (mime_type, index);
		}
		
		private static object GetIconIndex (string mime_type)
		{
			object oindex = null;
			
			if (mime_type != null) {
				// first check if mime_type is available in the mimetype/icon hashtable
				oindex = MimeIconIndex [mime_type];
				
				if (oindex == null) {
					// it is not available, check if an alias exist for mime_type
					string alias = Mime.GetMimeAlias (mime_type);
					
					if (alias != null) {
						string[] split = alias.Split (new char [] { ',' });
						
						for (int i = 0; i < split.Length; i++) {
							oindex = MimeIconIndex [split [i]];
							
							if (oindex != null)
								return oindex;
						}
					}
					
					// if oindex is still null check if mime_type is a sub class of an other mime type
					string sub_class = Mime.SubClasses [mime_type];
					
					if (sub_class != null) {
						oindex = MimeIconIndex [sub_class];
						
						if (oindex != null)
							return oindex;
					}
					
					// last check, see if we find an entry for the main mime type class
					string mime_class_main = mime_type.Substring (0, mime_type.IndexOf ('/'));
					return MimeIconIndex [mime_class_main];
				}
			}
			
			return oindex;
		}
	}
	
	internal abstract class PlatformMimeIconHandler
	{
		protected MimeExtensionHandlerStatus mimeExtensionHandlerStatus = MimeExtensionHandlerStatus.OK;
		
		public MimeExtensionHandlerStatus MimeExtensionHandlerStatus {
			get {
				return mimeExtensionHandlerStatus;
			}
		}
		
		public abstract MimeExtensionHandlerStatus Start ();
		
		public virtual object AddAndGetIconIndex (string filename, string mime_type)
		{
			return null;
		}
		
		public virtual object AddAndGetIconIndex (string mime_type)
		{
			return null;
		}
	}
	
	internal class PlatformDefaultHandler : PlatformMimeIconHandler
	{
		public override MimeExtensionHandlerStatus Start ()
		{
			MimeIconEngine.AddIconByImage ("inode/directory",  ResourceImageLoader.Get ("folder.png"));
			MimeIconEngine.AddIconByImage ("unknown/unknown",  ResourceImageLoader.Get ("text-x-generic.png"));
			MimeIconEngine.AddIconByImage ("desktop/desktop",  ResourceImageLoader.Get ("user-desktop.png"));
			MimeIconEngine.AddIconByImage ("directory/home",  ResourceImageLoader.Get ("user-home.png"));
			
			MimeIconEngine.AddIconByImage ("network/network",  ResourceImageLoader.Get ("folder-remote.png"));
			MimeIconEngine.AddIconByImage ("recently/recently",  ResourceImageLoader.Get ("document-open.png"));
			MimeIconEngine.AddIconByImage ("workplace/workplace",  ResourceImageLoader.Get ("computer.png"));
			
			return MimeExtensionHandlerStatus.OK; // return always ok
		}
	}
	
	internal class GnomeHandler : PlatformMimeIconHandler
	{
		public override MimeExtensionHandlerStatus Start ()
		{
			CreateUIIcons ();
			
			return MimeExtensionHandlerStatus.OK;
		}
		
		private void CreateUIIcons ()
		{
			AddGnomeIcon ("unknown/unknown", "gnome-fs-regular");
			AddGnomeIcon ("inode/directory", "gnome-fs-directory");
			AddGnomeIcon ("directory/home", "gnome-fs-home");
			AddGnomeIcon ("desktop/desktop", "gnome-fs-desktop");
			AddGnomeIcon ("recently/recently", "gnome-fs-directory-accept");
			AddGnomeIcon ("workplace/workplace", "gnome-fs-client");
			
			AddGnomeIcon ("network/network", "gnome-fs-network");
			AddGnomeIcon ("nfs/nfs", "gnome-fs-nfs");
			AddGnomeIcon ("smb/smb", "gnome-fs-smb");
			
			AddGnomeIcon ("harddisk/harddisk", "gnome-dev-harddisk");
			AddGnomeIcon ("cdrom/cdrom", "gnome-dev-cdrom");
			AddGnomeIcon ("removable/removable", "gnome-dev-removable");
		}
		
		private void AddGnomeIcon (string internal_mime_type, string name)
		{
			int index = -1;
			
			if (MimeIconEngine.MimeIconIndex.ContainsKey (internal_mime_type)) {
				return;
			}
			
			Image image = GnomeUtil.GetIcon (name, 48);
			
			if (image == null) {
				if (internal_mime_type == "unknown/unknown")
					image = ResourceImageLoader.Get ("text-x-generic.png");
				else
				if (internal_mime_type == "inode/directory")
					image = ResourceImageLoader.Get ("folder.png");
				else
				if (internal_mime_type == "directory/home")
					image = ResourceImageLoader.Get ("user-home.png");
				else
				if (internal_mime_type == "desktop/desktop")
					image = ResourceImageLoader.Get ("user-desktop.png");
				else
				if (internal_mime_type == "recently/recently")
					image = ResourceImageLoader.Get ("document-open.png");
				else
				if (internal_mime_type == "workplace/workplace")
					image = ResourceImageLoader.Get ("computer.png");
				else
				if (internal_mime_type == "network/network" || internal_mime_type == "nfs/nfs" || internal_mime_type == "smb/smb")
					image = ResourceImageLoader.Get ("folder-remote.png");
				else
				if (internal_mime_type == "harddisk/harddisk" || internal_mime_type == "cdrom/cdrom" || internal_mime_type == "removable/removable")
					image = ResourceImageLoader.Get ("text-x-generic.png");
			}
			
			index = MimeIconEngine.SmallIcons.Images.Add (image, Color.Transparent);
			MimeIconEngine.LargeIcons.Images.Add (image, Color.Transparent);
			
			MimeIconEngine.MimeIconIndex.Add (internal_mime_type, index);
		}
		
		public override object AddAndGetIconIndex (string filename, string mime_type)
		{
			int index = -1;
			
			Image image = GnomeUtil.GetIcon (filename, mime_type, 48);
			
			index = MimeIconEngine.SmallIcons.Images.Add (image, Color.Transparent);
			MimeIconEngine.LargeIcons.Images.Add (image, Color.Transparent);
			
			MimeIconEngine.MimeIconIndex.Add (mime_type, index);
			
			return index;
		}
		
		public override object AddAndGetIconIndex (string mime_type)
		{
			int index = -1;
			
			Image image = GnomeUtil.GetIcon (mime_type, 48);
			
			index = MimeIconEngine.SmallIcons.Images.Add (image, Color.Transparent);
			MimeIconEngine.LargeIcons.Images.Add (image, Color.Transparent);
			
			MimeIconEngine.MimeIconIndex.Add (mime_type, index);
			
			return index;
		}
	}
	
	internal class GnomeUtil
	{
		const string libgdk = "libgdk-x11-2.0.so.0";
		const string libgdk_pixbuf = "libgdk_pixbuf-2.0.so.0";
		const string libgtk = "libgtk-x11-2.0.so.0";
		const string libglib = "libglib-2.0.so.0";
		const string libgobject = "libgobject-2.0.so.0";
		const string libgnomeui = "libgnomeui-2.so.0";
		const string librsvg = "librsvg-2.so.2";
		
		[DllImport(librsvg)]
		static extern IntPtr rsvg_pixbuf_from_file_at_size (string file_name, int  width, int  height, out IntPtr error);
		
		[DllImport(libgdk_pixbuf)]
		static extern bool gdk_pixbuf_save_to_buffer (IntPtr pixbuf, out IntPtr buffer, out UIntPtr buffer_size, string type, out IntPtr error, IntPtr option_dummy);
		
		[DllImport(libglib)]
		static extern void g_free (IntPtr mem);
		
		[DllImport(libgdk)]
		static extern bool gdk_init_check (IntPtr argc, IntPtr argv);
		
		[DllImport(libgobject)]
		static extern void g_object_unref (IntPtr nativeObject);
		
		[DllImport(libgnomeui)]
		static extern string gnome_icon_lookup (IntPtr icon_theme, IntPtr thumbnail_factory, string file_uri, string custom_icon, IntPtr file_info, string mime_type, GnomeIconLookupFlags flags, IntPtr result);
		
		[DllImport(libgtk)]
		static extern IntPtr gtk_icon_theme_get_default ();
		
		[DllImport(libgtk)]
		static extern IntPtr gtk_icon_theme_load_icon (IntPtr icon_theme, string icon_name, int size, GtkIconLookupFlags flags, out IntPtr error);
		
		[DllImport(libgtk)]
		static extern bool gtk_icon_theme_has_icon (IntPtr icon_theme, string icon_name);
		
		enum GnomeIconLookupFlags
		{
			GNOME_ICON_LOOKUP_FLAGS_NONE = 0,
			GNOME_ICON_LOOKUP_FLAGS_EMBEDDING_TEXT = 1<<0,
			GNOME_ICON_LOOKUP_FLAGS_SHOW_SMALL_IMAGES_AS_THEMSELVES = 1<<1,
			GNOME_ICON_LOOKUP_FLAGS_ALLOW_SVG_AS_THEMSELVES = 1<<2
		};
		
		enum GtkIconLookupFlags
		{
			GTK_ICON_LOOKUP_NO_SVG = 1 << 0,
			GTK_ICON_LOOKUP_FORCE_SVG = 1 << 1,
			GTK_ICON_LOOKUP_USE_BUILTIN = 1 << 2
		};
		
		static bool inited = false;
		
		static IntPtr default_icon_theme = IntPtr.Zero;
		
		static void Init ()
		{
			gdk_init_check (IntPtr.Zero, IntPtr.Zero);
			
			inited = true;
			
			default_icon_theme = gtk_icon_theme_get_default ();
		}
		
		public static Image GetIcon (string file_name, string mime_type, int size)
		{
			if (!inited)
				Init ();
			
			Uri uri = new Uri (file_name);
			
			string icon = gnome_icon_lookup (default_icon_theme, IntPtr.Zero, uri.AbsoluteUri,
							 null, IntPtr.Zero, mime_type,
							 GnomeIconLookupFlags.GNOME_ICON_LOOKUP_FLAGS_NONE, IntPtr.Zero);
			
			IntPtr error = IntPtr.Zero;
			IntPtr pixbuf = gtk_icon_theme_load_icon (default_icon_theme, icon, size,
								  GtkIconLookupFlags.GTK_ICON_LOOKUP_USE_BUILTIN, out error);
			
			if (error != IntPtr.Zero)
				return null;
			
			return GdkPixbufToImage (pixbuf);
		}
		
		public static Image GetIcon (string icon, int size)
		{
			if (!inited)
				Init ();
			
			IntPtr error = IntPtr.Zero;
			IntPtr pixbuf = gtk_icon_theme_load_icon (default_icon_theme, icon, size,
								  GtkIconLookupFlags.GTK_ICON_LOOKUP_USE_BUILTIN, out error);
			
			if (error != IntPtr.Zero)
				return null;
			
			return GdkPixbufToImage (pixbuf);
		}
		
		public static Image GdkPixbufToImage (IntPtr pixbuf)
		{
			IntPtr error = IntPtr.Zero;
			IntPtr buffer;
			UIntPtr buffer_size_as_ptr;
			string type = "png";
			
			bool saved = gdk_pixbuf_save_to_buffer (pixbuf, out buffer, out buffer_size_as_ptr, type, out error, IntPtr.Zero);
			
			if (!saved)
				return null;
			
			int buffer_size = (int) (uint) buffer_size_as_ptr;
			byte[] result = new byte [buffer_size];
			Marshal.Copy (buffer, result, 0, (int) buffer_size);
			g_free (buffer);
			g_object_unref (pixbuf);
			
			Image image = null;
			MemoryStream s = new MemoryStream (result);
			image = Image.FromStream (s);
			
			return image;
		}
		
		public static Image GetSVGasImage (string filename, int width, int height)
		{
			if (!inited)
				Init ();
			
			if (!File.Exists (filename))
				return null;
			IntPtr error = IntPtr.Zero;
			IntPtr pixbuf = rsvg_pixbuf_from_file_at_size (filename, width, height, out error);
			
			if (error != IntPtr.Zero)
				return null;
			
			return GdkPixbufToImage (pixbuf);
		}
		
		public static bool HasImage (string name)
		{
			if (!inited)
				Init ();
			
			return gtk_icon_theme_has_icon (default_icon_theme, name);
		}
	}
}

